§
    éƒtj
c  ã                  óô  — U d Z ddlmZ ddlZddlZddlZddlmZ ddlmZ ddl	m
Z
mZ ddlmZ daded	<   dVd„ZdWd„ZdXd„Z G d„ d¦  «        Z G d„ d¦  «        Zda ed¦  «                             ¦   «         ZdYd„Zd„ ZdZd„Zd„ Zd[d„ZdZd „Zd\d(„Zd]d^d-„Z d_d.„Z!ddddddd/œd`d:„Z"	 	 	 	 	 	 dadbd<„Z#ddd=œdcd?„Z$dddA„Z%dBdCœdedF„Z&dddGdHœdfdI„Z'dgdK„Z(dhdL„Z)didM„Z*djdkdO„Z+dldmdQ„Z,dndR„Z-dodS„Z.dodT„Z/dodU„Z0dS )puÛ   Database layer â€” Postgres when DATABASE_URL is set, otherwise local SQLite.

Supports feedback, audit logs, sessions, and SaaS tables via a thin
sqlite-compatible wrapper (`?` placeholders, dict rows, executescript).
é    )ÚannotationsN)Údatetime)ÚPath)ÚAnyÚIterable)Úget_settingszPath | NoneÚ_DB_PATHÚreturnÚboolc                 ó¤   — t          ¦   «         j        pd                     ¦   «         } |                      d¦  «        p|                      d¦  «        S ©NÚ zpostgres://zpostgresql://)r   Údatabase_urlÚstripÚ
startswith©Úurls    ú(/var/www/html/CA-Chatbot/app/database.pyÚis_postgresr      sB   € Ý‰>Œ>Ô&Ð,¨"×
3Ò
3Ñ
5Ô
5€CØ�>Š>˜-Ñ(Ô(ÐK¨C¯NªN¸?Ñ,KÔ,KÐKó    r   c                 óš   — t           €>t          ¦   «         } | j        j        dz  a t           j                             dd¬¦  «         t           S )Nz
chatbot.dbT)ÚparentsÚexist_ok)r	   r   Úchroma_persist_dirÚparentÚmkdir)Úsettingss    r   Ú_db_pathr      sA   € åÐÝ‘>”>ˆØÔ.Ô5¸ÑDˆÝŒ×Ò d°TÐÑ:Ô:Ð:Ý€Or   ÚsqlÚstrc                óT  — t          ¦   «         s| S t          j        d| ¦  «        rVt          j        dd| ¦  «        } d|                      ¦   «         vr*|                      ¦   «                              d¦  «        dz   } |                      dd¦  «        } t          j        dd	| ¦  «        } | S )
z-Convert SQLite-ish SQL to the active dialect.z'(?is)^\s*INSERT\s+OR\s+IGNORE\s+INTO\s+zINSERT INTO zON CONFLICTú;z ON CONFLICT DO NOTHINGú%z%%z\?z%s)r   ÚreÚmatchÚsubÚupperÚrstripÚreplace)r   s    r   Ú
_adapt_sqlr*   #   s�   € å‰=Œ=ð Øˆ
å	„xÐ:¸CÑ@Ô@ð GÝŒfÐ?ÀÐQTÑUÔUˆØ §	¢	¡¤Ð+Ð+Ø—*’*‘,”,×%Ò% cÑ*Ô*Ð-FÑFˆCà
�+Š+�c˜4Ñ
 Ô
 €CÝ
Œ&�˜˜cÑ
"Ô
"€CØ€Jr   c                  ó"   — e Zd Zdd„Zd„ Zd„ ZdS )Ú	_PgCursorÚcurr   c                óN   — || _         d | _        t          |dd¦  «        | _        d S )NÚrowcountéÿÿÿÿ)Ú_curÚ	lastrowidÚgetattrr/   )Úselfr-   s     r   Ú__init__z_PgCursor.__init__3   s'   € ØˆŒ	Ø%)ˆŒÝ  Z°Ñ4Ô4ˆŒˆˆr   c                ó4   — | j                              ¦   «         S ©N)r1   Úfetchone©r4   s    r   r8   z_PgCursor.fetchone8   ó   € ØŒy×!Ò!Ñ#Ô#Ð#r   c                ó4   — | j                              ¦   «         S r7   )r1   Úfetchallr9   s    r   r<   z_PgCursor.fetchall;   r:   r   N)r-   r   )Ú__name__Ú
__module__Ú__qualname__r5   r8   r<   © r   r   r,   r,   2   sF   € € € € € ð5ð 5ð 5ð 5ð
$ð $ð $ð$ð $ð $ð $ð $r   r,   c                  óR   — e Zd ZdZddœdd„Zddd„Zdd„Zdd„Zdd„Zdd„Z	dd„Z
dS )Ú_PgConnectionz;Minimal sqlite3.Connection-compatible wrapper over psycopg.N©ÚpoolÚconnr   rD   c               ó"   — || _         || _        d S r7   )Ú_connÚ_pool)r4   rE   rD   s      r   r5   z_PgConnection.__init__B   s   € ØˆŒ
ØˆŒ
ˆ
ˆ
r   r   r    ÚparamsúIterable[Any] | Nonec                óº  — t          |¦  «        }|�t          |¦  «        nd}d}t          j        d|¦  «        rœd|                     ¦   «         vr†d|                     ¦   «         vrpt          j        d|¦  «        }|r'|                     d¦  «                             ¦   «         nd}|d	v r,|                     ¦   «                              d
¦  «        dz   }d}| j         	                    ||¦  «        }t          |¦  «        }|rM|                     ¦   «         }	|	r7t          t          |	t          ¦  «        r|	d         n|	d         ¦  «        |_        |j        |_        |S )Nr@   Fz(?is)^\s*INSERT\s+INTO\s+\w+Ú	RETURNINGzON CONFLICT DO NOTHINGz(?is)^\s*INSERT\s+INTO\s+(\w+)é   r   >   ÚfeedbackÚ	audit_logr"   z RETURNING idTÚidr   )r*   Útupler$   r%   r'   ÚgroupÚlowerr(   rG   Úexecuter,   r8   ÚintÚ
isinstanceÚdictr2   r/   )
r4   r   rI   ÚadaptedÚ	returningÚtable_mÚtabler-   ÚwrappedÚrows
             r   rT   z_PgConnection.executeF   sM  € Ý˜S‘/”/ˆØ"(Ð"4•�v‘”�¸"ˆàˆ	åŒHÐ4°gÑ>Ô>ð
	!à 7§=¢=¡?¤?Ð2Ð2Ø(°·²±´Ð?Ð?õ ”hÐ@À'ÑJÔJˆGØ18Ð@�W—]’] 1Ñ%Ô%×+Ò+Ñ-Ô-Ð-¸bˆEØÐ1Ð1Ð1Ø!Ÿ.š.Ñ*Ô*×1Ò1°#Ñ6Ô6¸ÑH�Ø �	ØŒj× Ò  ¨&Ñ1Ô1ˆÝ˜C‘.”.ˆØð 	XØ—,’,‘.”.ˆCØð XÝ$'µZÀÅTÑ5JÔ5JÐ(V¨¨D¬	¨	ÐPSÐTUÔPVÑ$WÔ$W�Ô!Øœ<ˆÔØˆr   Úscriptr
   ÚNonec                óÐ   — t          j        dd|t           j        ¬¦  «        }|                     d¦  «        D ]-}|                     ¦   «         }|r|                      |¦  «         Œ.d S )Nz--.*?$r   )Úflagsr"   )r$   r&   ÚMÚsplitr   rT   )r4   r^   ÚcleanedÚstmts       r   Úexecutescriptz_PgConnection.executescript_   si   € å”&˜ B¨µb´dÐ;Ñ;Ô;ˆØ—M’M #Ñ&Ô&ð 	#ð 	#ˆDØ—:’:‘<”<ˆDØð #Ø—’˜TÑ"Ô"Ð"øð	#ð 	#r   c                ó8   — | j                              ¦   «          d S r7   )rG   Úcommitr9   s    r   rh   z_PgConnection.commitg   s   € ØŒ
×ÒÑÔÐÐÐr   c                óˆ   — | j         �!| j                              | j        ¦  «         d S | j                             ¦   «          d S r7   )rH   ÚputconnrG   Úcloser9   s    r   rk   z_PgConnection.closej   sA   € ØŒ:Ð!ØŒJ×Ò˜tœzÑ*Ô*Ð*ØˆFØŒ
×ÒÑÔÐÐÐr   c                ó   — | S r7   r@   r9   s    r   Ú	__enter__z_PgConnection.__enter__p   s   € Øˆr   c                óÌ   — 	 |€| j                              ¦   «          n| j                              ¦   «          |                      ¦   «          d S # |                      ¦   «          w xY wr7   )rG   rh   Úrollbackrk   )r4   Úexc_typeÚexcÚtbs       r   Ú__exit__z_PgConnection.__exit__s   s[   € ð	ØÐØ”
×!Ò!Ñ#Ô#Ð#Ð#à”
×#Ò#Ñ%Ô%Ð%à�JŠJ‰LŒLˆLˆLˆLøˆD�JŠJ‰LŒLˆLˆLøøøs   ‚5A ÁA#)rE   r   rD   r   r7   )r   r    rI   rJ   )r^   r    r
   r_   ©r
   r_   )r
   rB   )r=   r>   r?   Ú__doc__r5   rT   rf   rh   rk   rm   rs   r@   r   r   rB   rB   ?   s¸   € € € € € ØEÐEà15ð ð ð ð ð ð ðð ð ð ð ð2#ð #ð #ð #ðð ð ð ðð ð ð ðð ð ð ðð ð ð ð ð r   rB   Ú	threadingr   c                ó�   — | pd                      ¦   «         } |                      d¦  «        rd| t          d¦  «        d …         z   S | S r   )r   r   Úlenr   s    r   Ú_normalize_pg_urlry   �   sN   € Øˆ9�"×
Ò
Ñ
Ô
€CØ
‡~‚~�mÑ$Ô$ð ;Ø ¥S¨Ñ%7Ô%7Ð%9Ð%9Ô!:Ñ:Ð:Ø€Jr   c            
     ó"  — t           �t           S t          5  t           �t           cddd¦  «         S ddlm}  ddlm} t          t          ¦   «         j        ¦  «        } ||ddd| dd	œd
¬¦  «        a t           cddd¦  «         S # 1 swxY w Y   dS )uB   Lazy singleton pool â€” avoids a new TCP handshake on every query.Nr   )Údict_row)ÚConnectionPoolrM   é   é   é
   )Úrow_factoryÚconnect_timeoutT)ÚconninfoÚmin_sizeÚmax_sizeÚtimeoutÚkwargsÚopen)	Ú_pg_poolÚ_pg_pool_lockÚpsycopg.rowsr{   Úpsycopg_poolr|   ry   r   r   )r{   r|   r   s      r   Ú_get_pg_poolrŒ   ˆ   s  € õ ÐÝˆÝ	ð ð ÝÐÝðð ð ð ñ ô ð ð ð 	*Ð)Ð)Ð)Ð)Ð)Ø/Ð/Ð/Ð/Ð/Ð/å¥¡¤Ô ;Ñ<Ô<ˆØ!�>ØØØØØ#+ÀÐCÐCØð
ñ 
ô 
ˆõ ðð ð ð ñ ô ð ð ð ð ð ð øøøð ð ð ð ð ð s   –B±ABÂBÂBr_   c                 óf  — t          ¦   «         sdS t          ¦   «         } |                      ¦   «         }	 |                     d¦  «         |                     ¦   «          n## t
          $ r |                     ¦   «          ‚ w xY w	 |                      |¦  «         dS # |                      |¦  «         w xY w)zMOpen the pool early so the first UI request is not paying connect cost alone.NzSELECT 1)r   rŒ   ÚgetconnrT   rh   Ú	Exceptionro   rj   )rD   rE   s     r   Úwarm_pg_poolr�   Ÿ   s±   € å‰=Œ=ð ØˆÝ‰>Œ>€DØ�<Š<‰>Œ>€DðØ�Š�ZÑ Ô Ð Ø�Š‰ŒˆˆøÝð ð ð Ø�Š‰ŒˆØðøøøð 	ð
 	�Š�TÑÔÐÐÐøˆ�Š�TÑÔÐÐøøøs   ´)A ÁB Á A>Á>B ÂB0c                 ó.  — t          ¦   «         r3t          ¦   «         } |                      ¦   «         }t          || ¬¦  «        S t	          j        t          t          ¦   «         ¦  «        ¦  «        }t          j        |_	        | 
                    d¦  «         |S )zEReturn a connection (Postgres or SQLite) usable as a context manager.rC   zPRAGMA journal_mode=WAL)r   rŒ   rŽ   rB   Úsqlite3Úconnectr    r   ÚRowr€   rT   )rD   ÚrawrE   s      r   Úget_connectionr–   ¯   sr   € å�}„}ð -Ý‰~Œ~ˆØ�lŠl‰nŒnˆÝ˜S tÐ,Ñ,Ô,Ð,åŒ?�3�x™zœz™?œ?Ñ+Ô+€DÝ”{€DÔØ‡L‚LÐ*Ñ+Ô+Ð+Ø€Kr   r[   úset[str]c                óš  — t          ¦   «         r‡|                      d|f¦  «                             ¦   «         }t          ¦   «         }|D ]K}t	          |t
          ¦  «        r|d         n|d         }|r"|                     t          |¦  «        ¦  «         ŒL|S |                      d|› d�¦  «                             ¦   «         }d„ |D ¦   «         S )Nz«
            SELECT column_name AS name
            FROM information_schema.columns
            WHERE table_schema = 'public' AND lower(table_name) = lower(?)
            Únamer   zPRAGMA table_info(ú)c                ó   — h | ]
}|d          ’ŒS )rM   r@   ©Ú.0Úrs     r   ú	<setcomp>z table_columns.<locals>.<setcomp>Í   s   € ÐÐÐ�QˆAˆaŒDÐÐÐr   )r   rT   r<   ÚsetrV   rW   Úaddr    )rE   r[   ÚrowsÚoutrž   r™   s         r   Útable_columnsr¤   ¼   sØ   € Ý�}„}ð Ø�|Š|ðð
 ˆHñ
ô 
÷ Š(‰*Œ*ð 	õ ™œˆØð 	#ð 	#ˆAÝ *¨1­dÑ 3Ô 3Ð=�1�V”9�9¸¸1¼ˆDØð #Ø—’�˜D™	œ	Ñ"Ô"Ð"øØˆ
Ø�<Š<Ð5¨UÐ5Ð5Ð5Ñ6Ô6×?Ò?ÑAÔA€DØÐ˜$ÐÑÔÐr   c                 óè  — t          ¦   «         } | rdnd}t          ¦   «         5 }|                     d|› d|› d�¦  «         t          |d¦  «        }dD ]}||vr|                     d|› d	�¦  «         Œ t          |d
¦  «        }d|vr|                     d¦  «         	 | r|                     d¦  «         n|                     d¦  «         n# t
          $ r Y nw xY wd d d ¦  «         d S # 1 swxY w Y   d S )NzBIGSERIAL PRIMARY KEYz!INTEGER PRIMARY KEY AUTOINCREMENTzP
            CREATE TABLE IF NOT EXISTS audit_log (
                id          aI  ,
                session_id  TEXT,
                question    TEXT NOT NULL,
                answer      TEXT NOT NULL,
                citations   TEXT,
                duration_ms INTEGER,
                created_at  TEXT NOT NULL
            );

            CREATE TABLE IF NOT EXISTS feedback (
                id          a…  ,
                log_id      INTEGER,
                session_id  TEXT,
                question    TEXT NOT NULL,
                answer      TEXT NOT NULL,
                rating      TEXT NOT NULL CHECK(rating IN ('up','down')),
                correction  TEXT,
                reviewed    INTEGER DEFAULT 0,
                created_at  TEXT NOT NULL,
                org_id      TEXT,
                agent_id    TEXT,
                user_id     TEXT,
                message_id  TEXT,
                mode        TEXT,
                model_name  TEXT,
                updated_at  TEXT
            );

            CREATE TABLE IF NOT EXISTS sessions (
                id          TEXT PRIMARY KEY,
                title       TEXT,
                created_at  TEXT NOT NULL,
                last_seen   TEXT NOT NULL,
                message_count INTEGER DEFAULT 0
            );

            CREATE INDEX IF NOT EXISTS idx_audit_session ON audit_log(session_id);
            CREATE INDEX IF NOT EXISTS idx_feedback_rating ON feedback(rating);
            CREATE INDEX IF NOT EXISTS idx_feedback_reviewed ON feedback(reviewed);
            CREATE INDEX IF NOT EXISTS idx_sessions_last_seen ON sessions(last_seen);
            CREATE INDEX IF NOT EXISTS idx_feedback_org_agent ON feedback(org_id, agent_id);
            CREATE INDEX IF NOT EXISTS idx_feedback_message ON feedback(message_id);
            rN   )Úorg_idÚagent_idÚuser_idÚ
message_idÚmodeÚ
model_nameÚ
updated_atz ALTER TABLE feedback ADD COLUMN z TEXTÚsessionsÚtitlez*ALTER TABLE sessions ADD COLUMN title TEXTzÙ
                    CREATE UNIQUE INDEX IF NOT EXISTS idx_feedback_message_unique
                    ON feedback(message_id)
                    WHERE message_id IS NOT NULL AND message_id <> ''
                    zÙ
                    CREATE UNIQUE INDEX IF NOT EXISTS idx_feedback_message_unique
                    ON feedback(message_id)
                    WHERE message_id IS NOT NULL AND message_id != ''
                    )r   r–   rf   r¤   rT   r�   )ÚpgÚid_typerE   Úfb_colsÚcolÚ	sess_colss         r   Úinit_dbr´   Ð   sÆ  € Ý	‰Œ€BØ)+ÐTÐ%Ð%Ð1T€GÝ	Ñ	Ô	ð V˜TØ×Òð,à$ð,ð ,ð %ð,ð ,ð ,ñ.	
ô .	
ð .	
õb    jÑ1Ô1ˆð
ð 
	Lð 
	LˆCð ˜'Ð!Ð!Ø—’ÐJÀÐJÐJÐJÑKÔKÐKøå! $¨
Ñ3Ô3ˆ	Ø˜)Ð#Ð#Ø�LŠLÐEÑFÔFÐFð	Øð Ø—’ðñô ð ð ð —’ðñô ð øøõ ð 	ð 	ð 	ØˆDð	øøøðkVð Vð Vñ Vô Vð Vð Vð Vð Vð Vð Vð Vøøøð Vð Vð Vð Vð Vð Vs6   £A8C'Â-C
Ã	C'Ã

CÃC'ÃCÃC'Ã'C+Ã.C+ÚquestionÚanswerÚcitations_jsonÚduration_msrU   Ú
session_idú
str | Nonec                ó"  — t          ¦   «         j        sdS t          ¦   «         5 }|                     d|| |||t	          ¦   «         f¦  «        }|j        pd}d d d ¦  «         n# 1 swxY w Y   |rt          || ¬¦  «         t          |pd¦  «        S )Nr   z{INSERT INTO audit_log(session_id, question, answer, citations, duration_ms, created_at)
               VALUES (?,?,?,?,?,?))Ú
title_hint)r   Úpersist_user_datar–   rT   Ú_nowr2   Útouch_sessionrU   )rµ   r¶   r·   r¸   r¹   rE   r-   Úlog_ids           r   Ú	log_queryrÁ   .  sß   € õ ‰>Œ>Ô+ð Øˆqå	Ñ	Ô	ð $˜TØ�lŠlð'à˜ 6¨>¸;ÍÉÌÐOñ
ô 
ˆð
 ”Ð# !ˆð$ð $ð $ñ $ô $ð $ð $ð $ð $ð $ð $øøøð $ð $ð $ð $ð ð 7Ý�j¨XÐ6Ñ6Ô6Ð6åˆvˆ{˜ÑÔÐs   ¤2A"Á"A&Á)A&éd   ÚlimitÚoffsetú
list[dict]c                ó¾   — t          ¦   «         5 }|                     d| |f¦  «                             ¦   «         }d„ |D ¦   «         cd d d ¦  «         S # 1 swxY w Y   d S )Nz9SELECT * FROM audit_log ORDER BY id DESC LIMIT ? OFFSET ?c                ó,   — g | ]}t          |¦  «        ‘ŒS r@   ©rW   rœ   s     r   ú
<listcomp>z!get_audit_log.<locals>.<listcomp>L  ó   € Ð&Ð&Ð&˜A•�Q‘”Ð&Ð&Ð&r   )r–   rT   r<   )rÃ   rÄ   rE   r¢   s       r   Úget_audit_logrË   F  s­   € Ý	Ñ	Ô	ð '˜TØ�|Š|ØGØ�FˆOñ
ô 
÷ Š(‰*Œ*ð 	ð 'Ð& Ð&Ñ&Ô&ð'ð 'ð 'ð 'ñ 'ô 'ð 'ð 'ð 'ð 'ð 'ð 'øøøð 'ð 'ð 'ð 'ð 'ð 's   �6AÁAÁAc                óæ   — t          ¦   «         j        sg S t          ¦   «         5 }|                     d| f¦  «                             ¦   «         }d„ |D ¦   «         cd d d ¦  «         S # 1 swxY w Y   d S )Nz•SELECT id, question, answer, created_at, duration_ms
               FROM audit_log
               WHERE session_id = ?
               ORDER BY id ASCc                ó,   — g | ]}t          |¦  «        ‘ŒS r@   rÈ   rœ   s     r   rÉ   z(get_session_messages.<locals>.<listcomp>Z  rÊ   r   )r   r½   r–   rT   r<   )r¹   rE   r¢   s      r   Úget_session_messagesrÎ   O  sÃ   € Ý‰>Œ>Ô+ð Øˆ	Ý	Ñ	Ô	ð '˜TØ�|Š|ð"ð ˆMñ
ô 
÷ Š(‰*Œ*ð 	ð 'Ð& Ð&Ñ&Ô&ð'ð 'ð 'ð 'ñ 'ô 'ð 'ð 'ð 'ð 'ð 'ð 'øøøð 'ð 'ð 'ð 'ð 'ð 's   ¤5A&Á&A*Á-A*)r¦   r§   r¨   r©   rª   r«   rÀ   ú
int | NoneÚratingÚ
correctionr¦   r§   r¨   r©   rª   r«   c               óV  — |dvrt          d¦  «        ‚t          ¦   «         }|pt          ¦   «         j        }t	          ¦   «         5 }d}|	r)|                     d|	f¦  «                             ¦   «         }|rct          |t          ¦  «        r|d         n|d         }|                     d| |||||||||
|||f¦  «         t          |¦  «        cddd¦  «         S |                     d| |||||||||	|
|||f¦  «        }t          |j
        pd¦  «        cddd¦  «         S # 1 swxY w Y   dS )	z:Persist thumbs up/down for model training (always writes).>   ÚupÚdownzrating must be 'up' or 'down'Nz,SELECT id FROM feedback WHERE message_id = ?rP   r   aÞ  UPDATE feedback SET
                       log_id=?, session_id=?, question=?, answer=?, rating=?,
                       correction=?, org_id=COALESCE(?, org_id),
                       agent_id=COALESCE(?, agent_id),
                       user_id=COALESCE(?, user_id),
                       mode=COALESCE(?, mode),
                       model_name=COALESCE(?, model_name),
                       updated_at=?,
                       reviewed=0
                   WHERE id=?a  INSERT INTO feedback(
                   log_id, session_id, question, answer, rating, correction,
                   org_id, agent_id, user_id, message_id, mode, model_name,
                   created_at, updated_at
               ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?))Ú
ValueErrorr¾   r   Úollama_modelr–   rT   r8   rV   rW   rU   r2   )rÀ   r¹   rµ   r¶   rÐ   rÑ   r¦   r§   r¨   r©   rª   r«   ÚnowÚmodelrE   ÚexistingÚeidr-   s                     r   Úsave_feedbackrÛ   _  sã  € ð  �^Ð#Ð#ÝÐ8Ñ9Ô9Ð9å
‰&Œ&€CØÐ5�,™.œ.Ô5€Eå	Ñ	Ô	ð >'˜TØˆØð 	Ø—|’|Ø>Ø�ñô ÷ Šh‰jŒjð ð
 ð 	Ý$.¨x½Ñ$>Ô$>ÐO�(˜4”.�.ÀHÈQÄKˆCØ�LŠLð	!ð ØØØØØØØØØØØØðñô ð õ6 �s‘8”8ðK>'ð >'ð >'ð >'ñ >'ô >'ð >'ð >'ðN �lŠlð9ð ØØØØØØØØØØØØØðñ
ô 
ˆõ. �3”=Ð% AÑ&Ô&ð}>'ð >'ð >'ð >'ñ >'ô >'ð >'ð >'ð >'ð >'ð >'ð >'øøøð >'ð >'ð >'ð >'ð >'ð >'s   ÁBDÃ9DÄD"Ä%D"Úreviewedc                óp  — g }g }|r*|                      d¦  «         |                      |¦  «         |�*|                      d¦  «         |                      |¦  «         |r*|                      d¦  «         |                      |¦  «         |r*|                      d¦  «         |                      |¦  «         |rdd                     |¦  «        z   nd}t          ¦   «         5 }	|	                     d|› d	�g |¢| ‘|‘R ¦  «                             ¦   «         }
d
„ |
D ¦   «         cd d d ¦  «         S # 1 swxY w Y   d S )Nz
rating = ?zreviewed = ?ú
org_id = ?úagent_id = ?úWHERE ú AND r   zSELECT * FROM feedback z" ORDER BY id DESC LIMIT ? OFFSET ?c                ó,   — g | ]}t          |¦  «        ‘ŒS r@   rÈ   rœ   s     r   rÉ   z get_feedback.<locals>.<listcomp>Ò  rÊ   r   )ÚappendÚjoinr–   rT   r<   )rÃ   rÄ   rÐ   rÜ   r¦   r§   ÚclausesrI   ÚwhererE   r¢   s              r   Úget_feedbackrç   ¶  s   € ð €GØ€FØð Ø�Š�|Ñ$Ô$Ð$Ø�Š�fÑÔÐØÐØ�Š�~Ñ&Ô&Ð&Ø�Š�hÑÔÐØð Ø�Š�|Ñ$Ô$Ð$Ø�Š�fÑÔÐØð  Ø�Š�~Ñ&Ô&Ð&Ø�Š�hÑÔÐØ29ÐAˆX˜Ÿš WÑ-Ô-Ñ-Ð-¸r€EÝ	Ñ	Ô	ð '˜TØ�|Š|ØO eÐOÐOÐOØ$ˆfÐ$�eÐ$˜VÐ$Ð$ñ
ô 
÷ Š(‰*Œ*ð 	ð 'Ð& Ð&Ñ&Ô&ð'ð 'ð 'ð 'ñ 'ô 'ð 'ð 'ð 'ð 'ð 'ð 'øøøð 'ð 'ð 'ð 'ð 'ð 's   Ã?D+Ä+D/Ä2D/)r¦   r§   údict[str, int]c           	     ó   — g }g }| r*|                      d¦  «         |                      | ¦  «         |r*|                      d¦  «         |                      |¦  «         |rdd                     |¦  «        z   nd}t          ¦   «         5 }|                     d|› d�|¦  «                             ¦   «         }|sddddd	œcd
d
d
¦  «         S t          |¦  «        }t          |                     d¦  «        pd¦  «        t          |                     d¦  «        pd¦  «        t          |                     d¦  «        pd¦  «        t          |                     d¦  «        pd¦  «        d	œcd
d
d
¦  «         S # 1 swxY w Y   d
S )z>Return {up, down, total, with_correction} for scoped feedback.rÞ   rß   rà   rá   r   aŽ  
            SELECT
              COALESCE(SUM(CASE WHEN rating = 'up' THEN 1 ELSE 0 END), 0) AS up_count,
              COALESCE(SUM(CASE WHEN rating = 'down' THEN 1 ELSE 0 END), 0) AS down_count,
              COUNT(*) AS total,
              COALESCE(SUM(CASE WHEN correction IS NOT NULL AND TRIM(correction) != '' THEN 1 ELSE 0 END), 0) AS with_correction
            FROM feedback
            z
            r   ©rÓ   rÔ   ÚtotalÚwith_correctionNÚup_countÚ
down_countrë   rì   )rã   rä   r–   rT   r8   rW   rU   Úget)r¦   r§   rå   rI   ræ   rE   r¢   Úds           r   Úfeedback_rating_countsrñ   Õ  sú  € ð €GØ€FØð Ø�Š�|Ñ$Ô$Ð$Ø�Š�fÑÔÐØð  Ø�Š�~Ñ&Ô&Ð&Ø�Š�hÑÔÐØ29ÐAˆX˜Ÿš WÑ-Ô-Ñ-Ð-¸r€EÝ	Ñ	Ô	ð 
˜TØ�|Š|ðð ðð ð ð ñ
ô 
÷ Š(‰*Œ*ð 	ð ð 	JØ Q°ÀqÐIÐIð
ð 
ð 
ð 
ñ 
ô 
ð 
ð 
õ �‰JŒJˆå�a—e’e˜JÑ'Ô'Ð,¨1Ñ-Ô-Ý˜Ÿš˜lÑ+Ô+Ð0¨qÑ1Ô1Ý˜Ÿš˜w™œÐ,¨1Ñ-Ô-Ý" 1§5¢5Ð):Ñ#;Ô#;Ð#@¸qÑAÔAð	
ð 
ð!
ð 
ð 
ð 
ñ 
ô 
ð 
ð 
ð 
ð 
ð 
ð 
øøøð 
ð 
ð 
ð 
ð 
ð 
s   Â5E3Ã	BE3Å3E7Å:E7údict[str, dict[str, int]]c           	     óB  — t          ¦   «         5 }|                     d| f¦  «                             ¦   «         }ddd¦  «         n# 1 swxY w Y   i }|D ]Ê}t          |¦  «        }t	          |                     d¦  «        pd¦  «        }|sŒ8t          |                     d¦  «        pd¦  «        t          |                     d¦  «        pd¦  «        t          |                     d¦  «        pd¦  «        t          |                     d	¦  «        pd¦  «        d
œ||<   ŒË|S )z)Map agent_id -> rating counts for an org.a  
            SELECT
              agent_id,
              COALESCE(SUM(CASE WHEN rating = 'up' THEN 1 ELSE 0 END), 0) AS up_count,
              COALESCE(SUM(CASE WHEN rating = 'down' THEN 1 ELSE 0 END), 0) AS down_count,
              COUNT(*) AS total,
              COALESCE(SUM(CASE WHEN correction IS NOT NULL AND TRIM(correction) != '' THEN 1 ELSE 0 END), 0) AS with_correction
            FROM feedback
            WHERE org_id = ? AND agent_id IS NOT NULL AND agent_id != ''
            GROUP BY agent_id
            Nr§   r   rí   r   rî   rë   rì   rê   )r–   rT   r<   rW   r    rï   rU   )r¦   rE   r¢   r£   r]   rð   Úaids          r   Úfeedback_counts_by_agentrõ   ü  s_  € å	Ñ	Ô	ð ˜TØ�|Š|ð
ð ˆIñ
ô 
÷ Š(‰*Œ*ð 	ðð ð ñ ô ð ð ð ð ð ð øøøð ð ð ð ð &(€CØð 

ð 

ˆÝ�‰IŒIˆÝ�!—%’%˜
Ñ#Ô#Ð) rÑ*Ô*ˆØð 	Øå�a—e’e˜JÑ'Ô'Ð,¨1Ñ-Ô-Ý˜Ÿš˜lÑ+Ô+Ð0¨qÑ1Ô1Ý˜Ÿš˜w™œÐ,¨1Ñ-Ô-Ý" 1§5¢5Ð):Ñ#;Ô#;Ð#@¸qÑAÔAð	
ð 
ˆˆC‰ˆð €Js   �*AÁA	ÁA	é   ©Údaysrø   úlist[dict[str, int | str]]c           
     óè  — ddl m}m} t          dt	          t          |¦  «        d¦  «        ¦  «        }|                     ¦   «          ||dz
  ¬¦  «        z
  }|                     ¦   «         }t          ¦   «         5 }| 	                    d| |f¦  «         
                    ¦   «         }ddd¦  «         n# 1 swxY w Y   d„ |D ¦   «         }g }	t          |¦  «        D ]˜}
| ||
¬¦  «        z   }|                     ¦   «         }|                     |i ¦  «        }|	                     |t          |                     d	¦  «        pd¦  «        t          |                     d
¦  «        pd¦  «        dœ¦  «         Œ™|	S )z5Daily thumbs up/down for the last N days (inclusive).r   )ÚdateÚ	timedeltarM   r~   r÷   a§  
            SELECT
              SUBSTR(created_at, 1, 10) AS day,
              COALESCE(SUM(CASE WHEN rating = 'up' THEN 1 ELSE 0 END), 0) AS up_count,
              COALESCE(SUM(CASE WHEN rating = 'down' THEN 1 ELSE 0 END), 0) AS down_count
            FROM feedback
            WHERE org_id = ? AND SUBSTR(created_at, 1, 10) >= ?
            GROUP BY SUBSTR(created_at, 1, 10)
            ORDER BY day ASC
            Nc                ó²   — i | ]T}t          |¦  «                             d ¦  «        ¯$t          t          |¦  «        d          ¦  «        t          |¦  «        “ŒUS )Úday)rW   rï   r    rœ   s     r   ú
<dictcomp>z)feedback_daily_counts.<locals>.<dictcomp>1  sI   € ÐOÐOÐO¨q½DÀ¹G¼G¿KºKÈÑ<NÔ<NÐO�c•$�q‘'”'˜%”.Ñ!Ô!¥4¨¡7¤7ÐOÐOÐOr   rí   rî   )rû   rÓ   rÔ   )r   rû   rü   ÚmaxÚminrU   ÚtodayÚ	isoformatr–   rT   r<   Úrangerï   rã   )r¦   rø   rû   rü   ÚstartÚcutoffrE   r¢   Úby_dayr£   Úirð   Úkeyr]   s                 r   Úfeedback_daily_countsr
    sÇ  € à(Ð(Ð(Ð(Ð(Ð(Ð(Ð(åˆq•#•c˜$‘i”i Ñ$Ô$Ñ%Ô%€DØ�JŠJ‰LŒL˜9˜9¨$°©(Ð3Ñ3Ô3Ñ3€EØ�_Š_ÑÔ€FÝ	Ñ	Ô	ð ˜TØ�|Š|ð	ð �VÐñ
ô 
÷ Š(‰*Œ*ð 	ðð ð ñ ô ð ð ð ð ð ð øøøð ð ð ð ð PÐO°DÐOÑOÔO€FØ&(€CÝ�4‰[Œ[ð 

ð 

ˆØ�I�I 1Ð%Ñ%Ô%Ñ%ˆØ�kŠk‰mŒmˆØ�jŠj˜˜bÑ!Ô!ˆØ�
Š
àÝ˜#Ÿ'š' *Ñ-Ô-Ð2°Ñ3Ô3Ý˜CŸGšG LÑ1Ô1Ð6°QÑ7Ô7ðð ñ	
ô 	
ð 	
ð 	
ð €Js   Á:+B1Â1B5Â8B5iˆ  ©r¦   r§   rÃ   c                ó(   — t          |d| |¬¦  «        S )Nr   )rÃ   rÄ   r¦   r§   )rç   r  s      r   Úget_training_pairsr  A  s   € õ ˜e¨A°fÀxÐPÑPÔPÐPr   Úfeedback_idc                óž   — t          ¦   «         5 }|                     d|t          ¦   «         | f¦  «         d d d ¦  «         d S # 1 swxY w Y   d S )Nz7UPDATE feedback SET reviewed=?, updated_at=? WHERE id=?©r–   rT   r¾   )r  rÜ   rE   s      r   Úupdate_feedback_reviewr  J  s–   € Ý	Ñ	Ô	ð 
˜TØ�ŠØEØ•t‘v”v˜{Ð+ñ	
ô 	
ð 	
ð
ð 
ð 
ñ 
ô 
ð 
ð 
ð 
ð 
ð 
ð 
ð 
øøøð 
ð 
ð 
ð 
ð 
ð 
ó   �&AÁAÁ	Ac                óž   — t          ¦   «         5 }|                     d|t          ¦   «         | f¦  «         d d d ¦  «         d S # 1 swxY w Y   d S )Nz9UPDATE feedback SET correction=?, updated_at=? WHERE id=?r  )r  rÑ   rE   s      r   Úupdate_feedback_correctionr  R  s–   € Ý	Ñ	Ô	ð 
˜TØ�ŠØGØ�™œ Ð-ñ	
ô 	
ð 	
ð
ð 
ð 
ñ 
ô 
ð 
ð 
ð 
ð 
ð 
ð 
ð 
øøøð 
ð 
ð 
ð 
ð 
ð 
r  c                óf  — | pd                      ¦   «         }|                     d¦  «        r*|                     dd¦  «                              ¦   «         pd}d                     |                     ¦   «         ¦  «        }t          |¦  «        dk    r|d d…                              ¦   «         dz   S |pdS )	Nr   z[uploaded PDF]z
PDF uploadú é0   é-   u   â€¦úNew chat)r   r   r)   rä   rc   rx   r(   )rµ   Útexts     r   Ú_title_from_questionr  \  s¤   € ØˆN˜×!Ò!Ñ#Ô#€DØ‡‚Ð'Ñ(Ô(ð JØ�|Š|Ð,¨bÑ1Ô1×7Ò7Ñ9Ô9ÐI¸\ˆØ�8Š8�D—J’J‘L”LÑ!Ô!€DÝ
ˆ4�y„y�2‚~€~Ø�C�R�CŒy×ÒÑ!Ô! EÑ)Ð)ØÐ�:Ðr   r¼   c                óÜ  — t          ¦   «         }|rt          |¦  «        nd }t          ¦   «         5 }|                     d| f¦  «                             ¦   «         }|€|                     d| |pd||f¦  «         n[t          |t          ¦  «        r|d         n|d         }|r|s|                     d||| f¦  «         n|                     d|| f¦  «         d d d ¦  «         d S # 1 swxY w Y   d S )Nz+SELECT id, title FROM sessions WHERE id = ?zkINSERT INTO sessions(id, title, created_at, last_seen, message_count)
                   VALUES (?,?,?,?,1)r  r®   rM   z€UPDATE sessions
                       SET last_seen=?, message_count=message_count+1, title=?
                       WHERE id=?zwUPDATE sessions
                       SET last_seen=?, message_count=message_count+1
                       WHERE id=?)r¾   r  r–   rT   r8   rV   rW   )r¹   r¼   r×   r®   rE   rÙ   Úexisting_titles          r   r¿   r¿   f  s{  € Ý
‰&Œ&€CØ0:ÐDÕ  Ñ,Ô,Ð,À€EÝ	Ñ	Ô	ð ˜TØ—<’<Ø9ØˆMñ
ô 
÷ Š(‰*Œ*ð 	ð ÐØ�LŠLð)à˜UÐ0 j°#°sÐ;ñô ð ð õ 3=¸XÅtÑ2LÔ2LÐ]˜X gÔ.Ð.ÐRZÐ[\ÔR]ˆNØð ˜^ð Ø—’ð%ð ˜% Ð,ñ	ô ð ð ð —’ð%ð ˜*Ð%ñ	ô ð ð+ð ð ñ ô ð ð ð ð ð ð ð øøøð ð ð ð ð ð s   °B$C!Ã!C%Ã(C%é(   c           
     óª  — t          ¦   «         j        sg S t          ¦   «         5 }|                     d¦  «                             ¦   «         }t          ¦   «         }|D �]©}t          |t          ¦  «        r|d         n|d         }|                     d|f¦  «                             ¦   «         }|                     d|f¦  «                             ¦   «         }|                     d|f¦  «                             ¦   «         }t          |t          ¦  «        r|d         n|d         }	|rt          |t          ¦  «        r|d         n|r|d         nd	}
|rt          |t          ¦  «        r|d
         n|r|d         n|}|rt          |t          ¦  «        r|d
         n|r|d         n|}t          ¦   «         r*|                     d|t          |
¦  «        |||	f¦  «         �Œ�|                     d|t          |
¦  «        |||	f¦  «         �Œ«|                     d| f¦  «                             ¦   «         }d„ |D ¦   «         cd d d ¦  «         S # 1 swxY w Y   d S )Nzõ
            SELECT DISTINCT a.session_id
            FROM audit_log a
            WHERE a.session_id IS NOT NULL
              AND a.session_id != ''
              AND NOT EXISTS (SELECT 1 FROM sessions s WHERE s.id = a.session_id)
            r¹   r   zUSELECT question, created_at FROM audit_log WHERE session_id=? ORDER BY id ASC LIMIT 1zLSELECT created_at FROM audit_log WHERE session_id=? ORDER BY id DESC LIMIT 1z6SELECT COUNT(*) AS c FROM audit_log WHERE session_id=?Úcrµ   ÚChatÚ
created_atrM   z‹INSERT INTO sessions(id, title, created_at, last_seen, message_count)
                       VALUES (?,?,?,?,?) ON CONFLICT (id) DO NOTHINGzyINSERT OR IGNORE INTO sessions(id, title, created_at, last_seen, message_count)
                       VALUES (?,?,?,?,?)aì  
            SELECT
                s.id,
                COALESCE(
                    NULLIF(s.title, ''),
                    (
                        SELECT CASE
                            WHEN a.question LIKE '[uploaded PDF]%' THEN trim(replace(a.question, '[uploaded PDF]', ''))
                            ELSE a.question
                        END
                        FROM audit_log a
                        WHERE a.session_id = s.id
                        ORDER BY a.id ASC
                        LIMIT 1
                    ),
                    'New chat'
                ) AS title,
                s.created_at,
                s.last_seen,
                s.message_count,
                (
                    SELECT COUNT(*) FROM audit_log a WHERE a.session_id = s.id
                ) AS turn_count
            FROM sessions s
            WHERE EXISTS (SELECT 1 FROM audit_log a WHERE a.session_id = s.id)
            ORDER BY s.last_seen DESC
            LIMIT ?
            c                ó,   — g | ]}t          |¦  «        ‘ŒS r@   rÈ   rœ   s     r   rÉ   z&list_conversations.<locals>.<listcomp>Ò  rÊ   r   )r   r½   r–   rT   r<   r¾   rV   rW   r8   r   r  )rÃ   rE   Úorphanr×   r]   ÚsidÚfirstÚlastÚ	count_rowÚcountÚfirst_qÚfirst_atÚlast_atr¢   s                 r   Úlist_conversationsr-  †  sü  € Ý‰>Œ>Ô+ð Øˆ	Ý	Ñ	Ô	ð I'˜TØ—’ðñ
ô 
÷ Š(‰*Œ*ð 	õ ‰fŒfˆØð 	ñ 	ˆCÝ'1°#µtÑ'<Ô'<ÐH�#�lÔ#Ð#À#ÀaÄ&ˆCØ—L’LØgØ�ñô ÷ Šh‰jŒjð ð —<’<Ø^Ø�ñô ÷ Šh‰jŒjð ð ŸšØHØ�ñô ÷ Šh‰jŒjð õ '1°½DÑ&AÔ&AÐS�I˜c”N�NÀyÐQRÄ|ˆEØ+0ÐqµZÀÅtÑ5LÔ5LÐq�e˜JÔ'Ð'Ð_dÐSpÐSXÐYZÔS[ÐS[ÐjpˆGØ.3Ðq½
À5Í$Ñ8OÔ8OÐq�u˜\Ô*Ð*ÐbgÐVpÐV[Ð\]ÔV^ÐV^ÐmpˆHØ,0ÐkµZÀÅdÑ5KÔ5KÐk�d˜<Ô(Ð(Ð]aÐRjÐRVÐWXÔRYÐRYÐgjˆGÝ‰}Œ}ð Ø—’ðIàÕ.¨wÑ7Ô7¸À7ÈEÐRñô ð ñ ð —’ð-àÕ.¨wÑ7Ô7¸À7ÈEÐRñô ð ñ ð �|Š|ðð8 ˆHñ;
ô 
÷< Š(‰*Œ*ð= 	ð> 'Ð& Ð&Ñ&Ô&ðSI'ð I'ð I'ð I'ñ I'ô I'ð I'ð I'ð I'ð I'ð I'ð I'øøøð I'ð I'ð I'ð I'ð I'ð I's   ¤HIÉIÉIc                óÞ   — t          ¦   «         5 }|                     d| f¦  «         |                     d| f¦  «         |                     d| f¦  «         d d d ¦  «         d S # 1 swxY w Y   d S )Nz*DELETE FROM audit_log WHERE session_id = ?z)DELETE FROM feedback WHERE session_id = ?z!DELETE FROM sessions WHERE id = ?)r–   rT   )r¹   rE   s     r   Údelete_conversationr/  Õ  sÇ   € Ý	Ñ	Ô	ð I˜TØ�ŠÐAÀJÀ=ÑQÔQÐQØ�ŠÐ@À:À-ÑPÔPÐPØ�ŠÐ8¸:¸-ÑHÔHÐHðIð Ið Iñ Iô Ið Ið Ið Ið Ið Ið Ið Iøøøð Ið Ið Ið Ið Ið Is   �AA"Á"A&Á)A&c                 óB   — t          t          j        ¦   «         ¦  «        S r7   )r    ÚuuidÚuuid4r@   r   r   Únew_session_idr3  Ü  s   € Ý�tŒz‰|Œ|ÑÔÐr   c                 ó&   — t          ¦   «         rdndS )NÚpostgresÚsqlite)r   r@   r   r   Ú
db_backendr7  à  s   € Ý$™œÐ4ˆ:ˆ:¨HÐ4r   c                 óV   — t          j        ¦   «                              d¬¦  «        dz   S )NÚseconds)ÚtimespecÚZ)r   Úutcnowr  r@   r   r   r¾   r¾   ä  s&   € ÝŒ?ÑÔ×&Ò&°	Ð&Ñ:Ô:¸SÑ@Ð@r   )r
   r   )r
   r   )r   r    r
   r    )r   r    r
   r    rt   )r[   r    r
   r—   )rµ   r    r¶   r    r·   r    r¸   rU   r¹   rº   r
   rU   )rÂ   r   )rÃ   rU   rÄ   rU   r
   rÅ   )r¹   r    r
   rÅ   )rÀ   rÏ   r¹   rº   rµ   r    r¶   r    rÐ   r    rÑ   rº   r¦   rº   r§   rº   r¨   rº   r©   rº   rª   rº   r«   rº   r
   rU   )rÂ   r   NNNN)rÃ   rU   rÄ   rU   rÐ   rº   rÜ   rÏ   r¦   rº   r§   rº   r
   rÅ   )r¦   rº   r§   rº   r
   rè   )r¦   r    r
   rò   )r¦   r    rø   rU   r
   rù   )r¦   rº   r§   rº   rÃ   rU   r
   rÅ   )r  rU   rÜ   rU   r
   r_   )r  rU   rÑ   rº   r
   r_   )rµ   r    r
   r    r7   )r¹   r    r¼   rº   r
   r_   )r  )rÃ   rU   r
   rÅ   )r¹   r    r
   r_   )r
   r    )1ru   Ú
__future__r   r$   r’   r1  r   Úpathlibr   Útypingr   r   Ú
app.configr   r	   Ú__annotations__r   r   r*   r,   rB   rˆ   Ú
__import__ÚLockr‰   ry   rŒ   r�   r–   r¤   r´   rÁ   rË   rÎ   rÛ   rç   rñ   rõ   r
  r  r  r  r  r¿   r-  r/  r3  r7  r¾   r@   r   r   ú<module>rD     sØ  ððð ð ð #Ð "Ð "Ð "Ð "Ð "à 	€	€	€	Ø €€€Ø €€€Ø Ð Ð Ð Ð Ð Ø Ð Ð Ð Ð Ð Ø  Ð  Ð  Ð  Ð  Ð  Ð  Ð  à #Ð #Ð #Ð #Ð #Ð #à€Ð Ð Ð Ñ ðLð Lð Lð Lð
ð ð ð ðð ð ð ð
$ð 
$ð 
$ð 
$ð 
$ñ 
$ô 
$ð 
$ð;ð ;ð ;ð ;ð ;ñ ;ô ;ð ;ð| €Ø�
˜;Ñ'Ô'×,Ò,Ñ.Ô.€ðð ð ð ðð ð ð.ð ð ð ð 
ð 
ð 
ð ð  ð  ð  ð(Yð Yð Yð Yð|ð ð ð ð0'ð 'ð 'ð 'ð 'ð'ð 'ð 'ð 'ð0 ØØØ!ØØ!ðT'ð T'ð T'ð T'ð T'ð T'ðp ØØØØØð'ð 'ð 'ð 'ð 'ðB Øð$
ð $
ð $
ð $
ð $
ð $
ðNð ð ð ð@ 78ð "ð "ð "ð "ð "ð "ðN ØØð	Qð Qð Qð Qð Qð Qð
ð 
ð 
ð 
ð
ð 
ð 
ð 
ðð ð ð ðð ð ð ð ð@L'ð L'ð L'ð L'ð L'ð^Ið Ið Ið Iðð ð ð ð5ð 5ð 5ð 5ðAð Að Að Að Að Ar   