o
    99j                  
   @  s  d Z ddlmZ ddlZddlZddlZddlZddlZddlm	Z	 ddl
mZmZmZ ejd Ze Zi dd	d
dddddddddddddddddddddd d!d"d#d$d%d&d'd(d)i d*d+d,d-d.d/d0d1d2d3d4d5d6d7d8d9d:d;d<d=d>d?d@dAdBdCdDdEdFdGdHdIdJdKdLdMdNdOdPdQdRdSdTZdmdXdYZdZd[ eD Zd\d] Zd^d_ ZdndbdcZdodedfZdgdh Zdidj ZdodkdlZdS )pu   Lokaler Daten-Layer: spiegelt ElevenLabs-Gespräche in SQLite + Vokabel-Tracking.

Das ist das detaillierte Tracking (#3): jedes Gespräch, jeder Turn, jede gelernte
Vokabel wird gespeichert und vom Eltern-Dashboard (#2/#6) gelesen.
    )annotationsN)Path   )configconvailearningzplappi.sqlitepasHundu   mačkaKatzepticaVogelribaFischkonjPferdkravaKuhzecHasemedvedu   Bärlavu   LöweslonElefantzdravoHallou   ćaou   TschüsshvalaDankemolimBittedaJaneNeinmamaMamatataPapabakaOmadekaOpabebaBabycrvenorotplavoblauu   žutogelbzelenou   grüncrnoschwarzbelou   weißjedaneinsdvazweitridreiu   četirivierpetu   fünfvodaWasserhlebBrotMilchApfelSonneMondBlumeHausAutoBall)mlekojabukasuncemeseccvetu   kućaautoloptasstrreturnc                 C  s$   t d|  } ddd | D S )NNFKD c                 s  s    | ]
}t |s|V  qd S N)unicodedata	combining).0c r[   1/home/nk/hobo-godmode/plappi-mvp/backend/store.py	<genexpr>#   s    z_norm.<locals>.<genexpr>)rW   	normalizelowerjoin)rQ   r[   r[   r\   _norm!   s   ra   c                 C  s   i | ]}t ||qS r[   )ra   )rY   kr[   r[   r\   
<dictcomp>'   s    rc   c                  C  s   t t} t j| _| S rV   )sqlite3connectDBRowrow_factoryrZ   r[   r[   r\   _conn*   s   
rj   c                  C  s$   t  } | d |   |   d S )Na  
    CREATE TABLE IF NOT EXISTS conversations(
        id TEXT PRIMARY KEY, started_at INT, duration_secs INT, num_turns INT, synced_at INT);
    CREATE TABLE IF NOT EXISTS turns(
        conv_id TEXT, idx INT, role TEXT, text TEXT, PRIMARY KEY(conv_id, idx));
    CREATE TABLE IF NOT EXISTS vocab(
        word_sr TEXT PRIMARY KEY, word_de TEXT, times_taught INT, times_practiced INT,
        first_seen INT, last_seen INT);
    )rj   executescriptcommitcloseri   r[   r[   r\   init0   s   
	rn   text	list[str]c                 C  sF   t | }g }t D ]\}}tdt| d|r || q
|S )Nz\b)ra   	_NORM_MAPitemsresearchescapeappend)ro   nfoundnksrr[   r[   r\   _find_words>   s   
r{   dictc                  C  sd  t   tjsdddS t } t }d}|D ]}}|dp!|d}|s%qzt|}W n	 ty5   Y qw t	|}t
|trF|di ni }|dpS|dpSd}|d	p_|d	p_d}	| d
|||	t|tt f | d|f t|D ]\}
}| d||
|d |d f q||d7 }q|   t|  |   |   tjtdd  d|dS )uV   Holt neue/aktualisierte Gespräche von ElevenLabs, speichert Turns, baut Vokabeln neu.Fzkein ELEVENLABS_AGENT_ID)okerrorr   conversation_ididmetadatastart_time_unix_secscall_duration_secsz6INSERT OR REPLACE INTO conversations VALUES(?,?,?,?,?)z!DELETE FROM turns WHERE conv_id=?z,INSERT OR REPLACE INTO turns VALUES(?,?,?,?)rolero   r   T)targetdaemon)r}   conversations)rn   r   ELEVENLABS_AGENT_IDrj   r   list_conversationsgetget_conversation	Exceptiontranscript_turns
isinstancer|   executeleninttime	enumeraterl   _rebuild_vocabrm   	threadingThread_background_scorestart)rZ   convspulledmetacidfullturnsmstartedduritr[   r[   r\   syncG   s>   



r   c                  C  s  t jddsdS zt  t } | d }dd |D }|   d}|D ]P\}}t }dd |d	|f D }|  zt||pFd|}|	d
rW|	dsW|d7 }W q& t
yv }	 ztd| d|	 dd W Y d}	~	q&d}	~	ww |rzddlm}
 |
|
  td| ddd W n" t
y }	 ztd|	 dd W Y d}	~	nd}	~	ww W t   dS W t   dS W t   dS t   w )u   Bewertet noch nicht ausgewertete Gespräche (begrenzt) und aktualisiert danach
    den Agent-Prompt mit den neuen Fokus-Wörtern. Läuft asynchron, mit Lock gegen Parallelläufe.F)blockingNa(  SELECT conv.id AS id, conv.started_at AS started_at
                            FROM conversations conv
                            LEFT JOIN scored_convs s ON s.conv_id = conv.id
                            WHERE s.conv_id IS NULL
                            ORDER BY conv.started_at ASC LIMIT 8c                 S  s   g | ]
}|d  |d fqS )r   
started_atr[   rY   rr[   r[   r\   
<listcomp>v   s    z%_background_score.<locals>.<listcomp>r   c                 S     g | ]}|d  |d dqS r   ro   )r   ro   r[   rY   r   r[   r[   r\   r   {   s    9SELECT role, text FROM turns WHERE conv_id=? ORDER BY idxr}   skippedr   z[score] z fehlgeschlagen: Tflush)profileu2    Gespräch(e) bewertet, Agent-Prompt aktualisiert.z.[score] Prompt-Aktualisierung fehlgeschlagen: )_score_lockacquirer   rn   rj   r   fetchallrm   apply_conversationr   r   printrU   r   push_to_agentget_profilerelease)rZ   rowstodonewlyr   r   c2r   reser   r[   r[   r\   r   i   sR   $r   c                 C  s  |  d i }|  d }|D ]N}|d pd}t|d D ]?}||dd|p(d |p+d g}|d dkr=|d  d7  < n|d  d7  < |r]t|d	 pM|||d	< t|d
 pX|||d
< qq| D ]\}}|  d|t|d|d |d |d	 |d
 f qcd S )NzDELETE FROM vocabzSELECT t.role, t.text, conv.started_at
                        FROM turns t JOIN conversations conv ON conv.id=t.conv_id
                        ORDER BY conv.started_at, t.idxr   r   ro   r   plappir         z0INSERT OR REPLACE INTO vocab VALUES(?,?,?,?,?,?)rU   )	r   r   r{   
setdefaultminmaxrr   VOCAB_SR_DEr   )rZ   aggr   r   tsrz   ar[   r[   r\   r      s,   
	&r   c            	   
   C  s<  t   t } | d }| d }tdd |D }g }|d d D ]%}| d|d f }||d |d |d	 |d
 dd |D d q%|   zt }W n" t	yw } zt
d| dd ddi g d}W Y d }~nd }~ww t|t|d dtdd |D tdd |D d|dd |D |dS )Nz4SELECT * FROM conversations ORDER BY started_at DESCzDSELECT * FROM vocab ORDER BY times_practiced DESC, times_taught DESCc                 s  s    | ]	}|d  p	dV  qdS )duration_secsr   Nr[   r   r[   r[   r\   r]      s    z!dashboard_data.<locals>.<genexpr>   r   r   r   r   	num_turnsc                 S  r   r   r[   r   r[   r[   r\   r      s    z"dashboard_data.<locals>.<listcomp>)r   r   r   r   r   z$[dashboard] mastery_summary failed: Tr   r   zpre-A1)levelcefrcountswords<   r   c                 s       | ]}|d  dkrdV  qdS )times_taughtr   r   Nr[   rY   vr[   r[   r\   r]          c                 s  r   )times_practicedr   r   Nr[   r   r[   r[   r\   r]      r   )r   minuteswords_taughtwords_practicedc                 S  s*   g | ]}|d  |d |d |d dqS )word_srword_der   r   )rz   detaught	practicedr[   r   r[   r[   r\   r      s
    
)statsmasteryvocabr   )rn   rj   r   r   sumrv   rm   r   mastery_summaryr   r   r   round)	rZ   r   r   
total_secs	out_convscvr   r   r   r[   r[   r\   dashboard_data   s>   
r   )rQ   rR   rS   rR   )ro   rR   rS   rp   )rS   r|   )__doc__
__future__r   rs   rd   r   r   rW   pathlibr   rU   r   r   r   DATA_DIRrf   Lockr   r   ra   rq   rj   rn   r{   r   r   r   r   r[   r[   r[   r\   <module>   s    




	"&