Ë
    éƒtj
c  ã                  óT  — 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	<   d0d
„Zd1d„Zd2d„Z G d„ d«      Z G d„ d«      Zda ed«      j/                  «       Zd3d„Zd„ Zd4d„Zd„ Zd5d„Zd4d„Z	 	 	 	 	 	 	 	 	 	 	 	 d6d„Zd7d8d„Z d9d„Z!dddddddœ	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d:d„Z"	 	 	 	 	 	 d;	 	 	 	 	 	 	 	 	 	 	 	 	 d<d„Z#dddœ	 	 	 	 	 d=d„Z$d>d „Z%d!d"œd?d#„Z&ddd$d%œ	 	 	 	 	 	 	 d@d&„Z'dAd'„Z(dBd(„Z)dCd)„Z*dDdEd*„Z+dFdGd+„Z,dHd,„Z-dId-„Z.dId.„Z/dId/„Z0y)JuÛ   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_PATHc                 ó–   — t        «       j                  xs dj                  «       } | j                  d«      xs | j                  d«      S ©NÚ zpostgres://zpostgresql://)r   Údatabase_urlÚstripÚ
startswith©Úurls    ú(/var/www/html/CA-Chatbot/app/database.pyÚis_postgresr      s:   € Ü‹>×&Ñ&Ò,¨"×
3Ñ
3Ó
5€CØ�>‰>˜-Ó(ÒK¨C¯N©N¸?Ó,KÐKó    c                 ó¢   — t         €Dt        «       } | j                  j                  dz  a t         j                  j	                  dd¬«       t         S )Nz
chatbot.dbT)ÚparentsÚexist_ok)r	   r   Úchroma_persist_dirÚparentÚmkdir)Úsettingss    r   Ú_db_pathr      sA   € äÐÜ“>ˆØ×.Ñ.×5Ñ5¸ÑDˆÜ�‰×Ñ d°TÐÔ:Ü€Or   c                ó2  — t        «       s| S t        j                  d| «      rKt        j                  dd| «      } d| j	                  «       vr"| j                  «       j                  d«      dz   } | j                  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)Úsqls    r   Ú
_adapt_sqlr'   #   s€   € äŒ=Øˆ
ä	‡x�xÐ:¸CÔ@Ü�f‰fÐ?ÀÐQTÓUˆØ §	¡	£Ñ+Ø—*‘*“,×%Ñ% cÓ*Ð-FÑFˆCà
�+‰+�c˜4Ó
 €CÜ
�&‰&�˜˜cÓ
"€CØ€Jr   c                  ó    — e Zd Zdd„Zd„ Zd„ Zy)Ú	_PgCursorc                óD   — || _         d | _        t        |dd«      | _        y )NÚrowcountéÿÿÿÿ)Ú_curÚ	lastrowidÚgetattrr+   )ÚselfÚcurs     r   Ú__init__z_PgCursor.__init__3   s    € ØˆŒ	Ø%)ˆŒÜ  Z°Ó4ˆ�r   c                ó6   — | j                   j                  «       S ©N)r-   Úfetchone©r0   s    r   r5   z_PgCursor.fetchone8   ó   € Ø�y‰y×!Ñ!Ó#Ð#r   c                ó6   — | j                   j                  «       S r4   )r-   Úfetchallr6   s    r   r9   z_PgCursor.fetchall;   r7   r   N)r1   r   )Ú__name__Ú
__module__Ú__qualname__r2   r5   r9   © r   r   r)   r)   2   s   „ ó5ò
$ó$r   r)   c                  óP   — 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
y)Ú_PgConnectionz;Minimal sqlite3.Connection-compatible wrapper over psycopg.N©Úpoolc               ó    — || _         || _        y r4   )Ú_connÚ_pool)r0   ÚconnrA   s      r   r2   z_PgConnection.__init__B   s   € ØˆŒ
Øˆ�
r   c                ó\  — t        |«      }|�t        |«      nd}d}t        j                  d|«      r…d|j	                  «       vrsd|j	                  «       vrat        j                  d|«      }|r|j                  d«      j                  «       nd}|d	v r$|j                  «       j                  d
«      dz   }d}| j                  j                  ||«      }t        |«      }|r:|j                  «       }	|	r(t        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$   rC   Úexecuter)   r5   ÚintÚ
isinstanceÚdictr.   r+   )
r0   r&   ÚparamsÚadaptedÚ	returningÚtable_mÚtabler1   ÚwrappedÚrows
             r   rO   z_PgConnection.executeF   s  € Ü˜S“/ˆØ"(Ð"4”�v”¸"ˆàˆ	ä�H‰HÐ4°gÔ>Ø 7§=¡=£?Ñ2Ø(°·±³Ñ?ô —h‘hÐ@À'ÓJˆGÙ18�W—]‘] 1Ó%×+Ñ+Ô-¸bˆEØÐ1Ñ1Ø!Ÿ.™.Ó*×1Ñ1°#Ó6¸ÑH�Ø �	Ø�j‰j× Ñ  ¨&Ó1ˆÜ˜C“.ˆÙØ—,‘,“.ˆCÙÜ$'´ZÀÄTÔ5J¨¨Dª	ÐPSÐTUÑPVÓ$W�Ô!ØŸ<™<ˆÔØˆr   c                óÆ   — t        j                  dd|t         j                  ¬«      }|j                  d«      D ]&  }|j	                  «       }|sŒ| j                  |«       Œ( y )Nz--.*?$r   )Úflagsr   )r    r"   ÚMÚsplitr   rO   )r0   ÚscriptÚcleanedÚstmts       r   Úexecutescriptz_PgConnection.executescript_   sG   € ä—&‘&˜ B¨´b·d±dÔ;ˆØ—M‘M #Ö&ˆDØ—:‘:“<ˆDÚØ—‘˜TÕ"ñ 'r   c                ó8   — | j                   j                  «        y r4   )rC   Úcommitr6   s    r   rc   z_PgConnection.commitg   s   € Ø�
‰
×ÑÕr   c                óœ   — | j                   �&| j                   j                  | j                  «       y | j                  j                  «        y r4   )rD   ÚputconnrC   Úcloser6   s    r   rf   z_PgConnection.closej   s5   € Ø�:‰:Ð!Ø�J‰J×Ñ˜tŸz™zÔ*ØØ�
‰
×ÑÕr   c                ó   — | S r4   r=   r6   s    r   Ú	__enter__z_PgConnection.__enter__p   s   € Øˆr   c                ó¾   — 	 |€| j                   j                  «        n| j                   j                  «        | j                  «        y # | j                  «        w xY wr4   )rC   rc   Úrollbackrf   )r0   Úexc_typeÚexcÚtbs       r   Ú__exit__z_PgConnection.__exit__s   s@   € ð	ØÐØ—
‘
×!Ñ!Õ#à—
‘
×#Ñ#Ô%à�J‰J�LøˆD�J‰J�Lús   ‚7A
 Á
A)rE   r   rA   r   r4   )r&   ÚstrrS   zIterable[Any] | None)r^   ro   ÚreturnÚNone©rp   rq   )rp   r?   )r:   r;   r<   Ú__doc__r2   rO   ra   rc   rf   rh   rn   r=   r   r   r?   r?   ?   s+   „ ÙEà15õ ôó2#óóóôr   r?   Ú	threadingc                ór   — | xs dj                  «       } | j                  d«      rd| t        d«      d  z   S | S r   )r   r   Úlenr   s    r   Ú_normalize_pg_urlrw   �   s=   € ØŠ9�"×
Ñ
Ó
€CØ
‡~�~�mÔ$Ø ¤S¨Ó%7Ð%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 Y   yxY w)uB   Lazy singleton pool â€” avoids a new TCP handshake on every query.Nr   )Údict_row)ÚConnectionPoolrH   é   é   é
   )Úrow_factoryÚconnect_timeoutT)ÚconninfoÚmin_sizeÚmax_sizeÚtimeoutÚkwargsÚopen)	Ú_pg_poolÚ_pg_pool_lockÚpsycopg.rowsry   Úpsycopg_poolrz   rw   r   r   )ry   rz   r   s      r   Ú_get_pg_poolrŠ   ˆ   sl   € ô ÐÜˆÞ	ÜÐÜ÷ 
‰õ 	*Ý/ä¤£× ;Ñ ;Ó<ˆÙ!ØØØØØ#+ÀÑCØô
ˆô ÷ 
�Šús   “A2©?A2Á2A;c                 ó  — t        «       syt        «       } | j                  «       }	 |j                  d«       |j	                  «        	 | j                  |«       y# t
        $ r |j                  «        ‚ w xY w# | j                  |«       w xY w)zMOpen the pool early so the first UI request is not paying connect cost alone.NzSELECT 1)r   rŠ   ÚgetconnrO   rc   Ú	Exceptionrj   re   )rA   rE   s     r   Úwarm_pg_poolrŽ   Ÿ   sp   € äŒ=ØÜ‹>€DØ�<‰<‹>€DðØ�‰�ZÔ Ø�‰�ð
 	�‰�TÕøô	 ò Ø�‰ŒØðûð 	�‰�TÕús   §!A ÁA6Á6A9 Á9Bc                 ó   — t        «       r't        «       } | j                  «       }t        || ¬«      S t	        j
                  t        t        «       «      «      }t        j                  |_	        |j                  d«       |S )zEReturn a connection (Postgres or SQLite) usable as a context manager.r@   zPRAGMA journal_mode=WAL)r   rŠ   rŒ   r?   Úsqlite3Úconnectro   r   ÚRowr~   rO   )rA   ÚrawrE   s      r   Úget_connectionr”   ¯   sX   € ä„}Ü‹~ˆØ�l‰l‹nˆÜ˜S tÔ,Ð,ä�?‰?œ3œx›z›?Ó+€DÜ—{‘{€DÔØ‡L�LÐ*Ô+Ø€Kr   c                ób  — t        «       rk| j                  d|f«      j                  «       }t        «       }|D ]9  }t	        |t
        «      r|d   n|d   }|sŒ |j                  t        |«      «       Œ; |S | j                  d|› d�«      j                  «       }|D �ch c]  }|d   ’Œ	 c}S c c}w )Nz«
            SELECT column_name AS name
            FROM information_schema.columns
            WHERE table_schema = 'public' AND lower(table_name) = lower(?)
            Únamer   zPRAGMA table_info(Ú)rH   )r   rO   r9   ÚsetrQ   rR   Úaddro   )rE   rW   ÚrowsÚoutÚrr–   s         r   Útable_columnsr�   ¼   s¯   € Ü„}Ø�|‰|ðð
 ˆHó
÷ ‰(‹*ð 	ô ›ˆÛˆAÜ *¨1¬dÔ 3�1�V’9¸¸1¹ˆDÚØ—‘œ˜D›	Õ"ð ð ˆ
Ø�<‰<Ð,¨U¨G°1Ð5Ó6×?Ñ?ÓA€DÙÓ™$�QˆAˆa‹D˜$ÑÐùÒs   ÂB,c                 ó˜  — t        «       } | rdnd}t        «       5 }|j                  d|› d|› d�«       t        |d«      }dD ]  }||vsŒ|j	                  d|› d	�«       Œ t        |d
«      }d|vr|j	                  d«       	 | r|j	                  d«       n|j	                  d«       d d d «       y # t
        $ r Y Œw xY w# 1 sw Y   y xY w)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);
            rI   )Ú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”   ra   r�   rO   r�   )ÚpgÚid_typerE   Úfb_colsÚcolÚ	sess_colss         r   Úinit_dbr­   Ð   sü   € Ü	‹€BÙ)+Ñ%Ð1T€GÜ	Ô	˜TØ×Ñðà$˜Ið 
&ð %˜Ið  &ð,ô.	
ôb    jÓ1ˆó
ˆCð ˜'Ò!Ø—‘Ð?À¸uÀEÐJÕKð
ô " $¨
Ó3ˆ	Ø˜)Ñ#Ø�L‰LÐEÔFð	ÙØ—‘ðõð —‘ðô÷] 
Ð	øôj ò 	Ùð	ú÷k 
Ð	ús/   ›.C Á
8C Â%B1Â1	B=Â:C Â<B=Â=C Ã C	c                ó  — t        «       j                  syt        «       5 }|j                  d|| |||t	        «       f«      }|j
                  xs d}d d d «       |rt        || ¬«       t        xs d«      S # 1 sw Y   Œ'xY w)Nr   z{INSERT INTO audit_log(session_id, question, answer, citations, duration_ms, created_at)
               VALUES (?,?,?,?,?,?))Ú
title_hint)r   Úpersist_user_datar”   rO   Ú_nowr.   Útouch_sessionrP   )ÚquestionÚanswerÚcitations_jsonÚduration_msÚ
session_idrE   r1   Úlog_ids           r   Ú	log_queryr¹   .  s{   € ô ‹>×+Ò+Øä	Ô	˜TØ�l‰lð'à˜ 6¨>¸;ÌËÐOó
ˆð
 —‘Ò# !ˆ÷ 
ñ Ü�j¨XÕ6äˆvŠ{˜ÓÐ÷ 
Ð	ús    1A7Á7B c                óÂ   — t        «       5 }|j                  d| |f«      j                  «       }|D �cg c]  }t        |«      ‘Œ c}cd d d «       S c c}w # 1 sw Y   y xY w)Nz9SELECT * FROM audit_log ORDER BY id DESC LIMIT ? OFFSET ?)r”   rO   r9   rR   )ÚlimitÚoffsetrE   rš   rœ   s        r   Úget_audit_logr½   F  s\   € Ü	Ô	˜TØ�|‰|ØGØ�FˆOó
÷ ‰(‹*ð 	ñ "&Ó&¡˜A”�Q• Ñ&÷ 
Ñ	ùò
 '÷ 
Ð	ús   ‹'A²AÁAÁAÁAc                óì   — t        «       j                  sg S t        «       5 }|j                  d| f«      j	                  «       }|D �cg c]  }t        |«      ‘Œ c}cd d d «       S c c}w # 1 sw Y   y xY w)Nz•SELECT id, question, answer, created_at, duration_ms
               FROM audit_log
               WHERE session_id = ?
               ORDER BY id ASC)r   r°   r”   rO   r9   rR   )r·   rE   rš   rœ   s       r   Úget_session_messagesr¿   O  sn   € Ü‹>×+Ò+Øˆ	Ü	Ô	˜TØ�|‰|ð"ð ˆMó
÷ ‰(‹*ð 	ñ "&Ó&¡˜A”�Q• Ñ&÷ 
Ñ	ùò '÷ 
Ð	ús   ¡&A*ÁA%ÁA*Á%A*Á*A3)rŸ   r    r¡   r¢   r£   r¤   c               óþ  — |dvrt        d«      ‚t        «       }|xs t        «       j                  }t	        «       5 }d}|	r!|j                  d|	f«      j                  «       }|rMt        |t        «      r|d   n|d   }|j                  d| |||||||||
|||f«       t        |«      cddd«       S |j                  d| |||||||||	|
|||f«      }t        |j                  xs d«      cddd«       S # 1 sw Y   yxY w)	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 = ?rK   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”   rO   r5   rQ   rR   rP   r.   )r¸   r·   r³   r´   ÚratingÚ
correctionrŸ   r    r¡   r¢   r£   r¤   ÚnowÚmodelrE   ÚexistingÚeidr1   s                     r   Úsave_feedbackrË   _  s8  € ð  �^Ñ#ÜÐ8Ó9Ð9ä
‹&€CØÒ5œ,›.×5Ñ5€Eä	Ô	˜TØˆÙØ—|‘|Ø>Ø�ó÷ ‰h‹jð ñ
 Ü$.¨x¼Ô$>�(˜4’.ÀHÈQÁKˆCØ�L‰Lð	!ð ØØØØØØØØØØØØðôô6 �s“8÷K 
Ñ	ðN �l‰lð9ð ØØØØØØØØØØØØØðó
ˆô. �3—=‘=Ò% AÓ&÷} 
×	Ò	ús   ¼A+C3Â18C3Ã3C<c                ó,  — g }g }|r"|j                  d«       |j                  |«       |�"|j                  d«       |j                  |«       |r"|j                  d«       |j                  |«       |r"|j                  d«       |j                  |«       |rddj                  |«      z   nd}t        «       5 }	|	j                  d|› d	�g |¢| ‘|‘­«      j	                  «       }
|
D �cg c]  }t        |«      ‘Œ c}cd d d «       S c c}w # 1 sw Y   y xY w)
Nz
rating = ?zreviewed = ?ú
org_id = ?úagent_id = ?úWHERE ú AND r   zSELECT * FROM feedback z" ORDER BY id DESC LIMIT ? OFFSET ?)ÚappendÚjoinr”   rO   r9   rR   )r»   r¼   rÅ   ÚreviewedrŸ   r    ÚclausesrS   ÚwhererE   rš   rœ   s               r   Úget_feedbackrÖ   ¶  s  € ð €GØ€FÙØ�‰�|Ô$Ø�‰�fÔØÐØ�‰�~Ô&Ø�‰�hÔÙØ�‰�|Ô$Ø�‰�fÔÙØ�‰�~Ô&Ø�‰�hÔÙ29ˆX˜Ÿ™ WÓ-Ò-¸r€EÜ	Ô	˜TØ�|‰|Ø% e WÐ,NÐOØ$ˆfÐ$�eÐ$˜VÑ$ó
÷ ‰(‹*ð 	ñ "&Ó&¡˜A”�Q• Ñ&÷ 
Ñ	ùò
 '÷ 
Ð	ús   Â70D
Ã'DÃ9D
ÄD
Ä
D)rŸ   r    c           	     óz  — g }g }| r"|j                  d«       |j                  | «       |r"|j                  d«       |j                  |«       |rddj                  |«      z   nd}t        «       5 }|j                  d|› d�|«      j	                  «       }|sddddd	œcd
d
d
«       S t        |«      }t        |j                  d«      xs d«      t        |j                  d«      xs d«      t        |j                  d«      xs d«      t        |j                  d«      xs d«      d	œcd
d
d
«       S # 1 sw Y   y
xY w)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”   rO   r5   rR   rP   Úget)rŸ   r    rÔ   rS   rÕ   rE   rš   Úds           r   Úfeedback_rating_countsrß   Õ  s%  € ð €GØ€FÙØ�‰�|Ô$Ø�‰�fÔÙØ�‰�~Ô&Ø�‰�hÔÙ29ˆX˜Ÿ™ WÓ-Ò-¸r€EÜ	Ô	˜TØ�|‰|ðð ˆGð ðð ó
÷ ‰(‹*ð 	ñ Ø Q°ÀqÑI÷ 
Ñ	ô �‹Jˆä�a—e‘e˜JÓ'Ò,¨1Ó-Ü˜Ÿ™˜lÓ+Ò0¨qÓ1Ü˜Ÿ™˜w›Ò,¨1Ó-Ü" 1§5¡5Ð):Ó#;Ò#@¸qÓAñ	
÷! 
×	Ò	ús   Á/-D1Â&BD1Ä1D:c           	     óä  — t        «       5 }|j                  d| f«      j                  «       }ddd«       i }D ]¨  }t        |«      }t	        |j                  d«      xs d«      }|sŒ/t        |j                  d«      xs d«      t        |j                  d«      xs d«      t        |j                  d«      xs d«      t        |j                  d	«      xs d«      d
œ||<   Œª |S # 1 sw Y   ŒºxY w)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”   rO   r9   rR   ro   rÝ   rP   )rŸ   rE   rš   r›   rY   rÞ   Úaids          r   Úfeedback_counts_by_agentrâ   ü  sà   € ä	Ô	˜TØ�|‰|ð
ð ˆIó
÷ ‰(‹*ð 	÷ 
ð &(€CÛˆÜ�‹IˆÜ�!—%‘%˜
Ó#Ò) rÓ*ˆÙØä�a—e‘e˜JÓ'Ò,¨1Ó-Ü˜Ÿ™˜lÓ+Ò0¨qÓ1Ü˜Ÿ™˜w›Ò,¨1Ó-Ü" 1§5¡5Ð):Ó#;Ò#@¸qÓAñ	
ˆˆCŠð ð €J÷7 
Ð	ús   ‹"C&Ã&C/é   ©Údaysc           
     óæ  — ddl m}m} t        dt	        t        |«      d«      «      }|j                  «        ||dz
  ¬«      z
  }|j                  «       }t        «       5 }|j                  d| |f«      j                  «       }ddd«       D �ci c]>  }t        |«      j                  d«      sŒt        t        |«      d   «      t        |«      “Œ@ }	}g }
t        |«      D ]}  }| ||¬«      z   }|j                  «       }|	j                  |i «      }|
j                  |t        |j                  d	«      xs d«      t        |j                  d
«      xs d«      dœ«       Œ |
S # 1 sw Y   ŒáxY wc c}w )z5Daily thumbs up/down for the last N days (inclusive).r   )ÚdateÚ	timedeltarH   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
            NÚdayrÛ   rÜ   )rç   rÁ   rÂ   )r   rç   rè   ÚmaxÚminrP   ÚtodayÚ	isoformatr”   rO   r9   rR   rÝ   ro   ÚrangerÑ   )rŸ   rå   rç   rè   ÚstartÚcutoffrE   rš   rœ   Úby_dayr›   ÚirÞ   ÚkeyrY   s                  r   Úfeedback_daily_countsrô     sD  € ç(äˆq”#”c˜$“i Ó$Ó%€DØ�J‰J‹L™9¨$°©(Ô3Ñ3€EØ�_‰_Ó€FÜ	Ô	˜TØ�|‰|ð	ð �VÐó
÷ ‰(‹*ð 	÷ 
ñ 59ÓO±D¨q¼DÀ»G¿K¹KÈÕ<NŒc”$�q“'˜%‘.Ó!¤4¨£7Ñ*°D€FÐOØ&(€CÜ�4Ž[ˆØ‘I 1Ô%Ñ%ˆØ�k‰k‹mˆØ�j‰j˜˜bÓ!ˆØ�
‰
àÜ˜#Ÿ'™' *Ó-Ò2°Ó3Ü˜CŸG™G LÓ1Ò6°QÓ7ñõ	
ð	 ð €J÷7 
Ð	üò Ps   Á#E"ÂE.Â.#E.Å"E+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ÐPr   c                ó|   — t        «       5 }|j                  d|t        «       | f«       d d d «       y # 1 sw Y   y xY w)Nz7UPDATE feedback SET reviewed=?, updated_at=? WHERE id=?©r”   rO   r±   )Úfeedback_idrÓ   rE   s      r   Úupdate_feedback_reviewrû   J  s1   € Ü	Ô	˜TØ�‰ØEØ”t“v˜{Ð+ô	
÷ 
×	Ñ	úó   ‹2²;c                ó|   — t        «       5 }|j                  d|t        «       | f«       d d d «       y # 1 sw Y   y xY w)Nz9UPDATE feedback SET correction=?, updated_at=? WHERE id=?rù   )rú   rÆ   rE   s      r   Úupdate_feedback_correctionrþ   R  s1   € Ü	Ô	˜TØ�‰ØGØœ› Ð-ô	
÷ 
×	Ñ	úrü   c                ó&  — | xs dj                  «       }|j                  d«      r$|j                  dd«      j                  «       xs d}dj                  |j	                  «       «      }t        |«      dkD  r|d d j                  «       dz   S |xs dS )	Nr   z[uploaded PDF]z
PDF uploadÚ é0   é-   u   â€¦úNew chat)r   r   r%   rÒ   r]   rv   r$   )r³   Útexts     r   Ú_title_from_questionr  \  s„   € ØŠN˜×!Ñ!Ó#€DØ‡�Ð'Ô(Ø�|‰|Ð,¨bÓ1×7Ñ7Ó9ÒI¸\ˆØ�8‰8�D—J‘J“LÓ!€DÜ
ˆ4ƒy�2‚~Ø�C�Rˆy×ÑÓ! EÑ)Ð)ØÒ�:Ðr   c                ó€  — t        «       }|rt        |«      nd }t        «       5 }|j                  d| f«      j	                  «       }|€|j                  d| |xs d||f«       nHt        |t        «      r|d   n|d   }|r|s|j                  d||| f«       n|j                  d|| f«       d d d «       y # 1 sw Y   y xY w)Nz+SELECT id, title FROM sessions WHERE id = ?zkINSERT INTO sessions(id, title, created_at, last_seen, message_count)
                   VALUES (?,?,?,?,1)r  r§   rH   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”   rO   r5   rQ   rR   )r·   r¯   rÇ   r§   rE   rÉ   Úexisting_titles          r   r²   r²   f  sÕ   € Ü
‹&€CÙ0:Ô  Ô,À€EÜ	Ô	˜TØ—<‘<Ø9ØˆMó
÷ ‰(‹*ð 	ð ÐØ�L‰Lð)à˜UÒ0 j°#°sÐ;õô 3=¸XÄtÔ2L˜X gÒ.ÐRZÐ[\ÑR]ˆNÙ™^Ø—‘ð%ð ˜% Ð,õ	ð —‘ð%ð ˜*Ð%ô	÷+ 
×	Ñ	ús   ¤BB4Â4B=c           
     óÖ  — t        «       j                  sg S t        «       5 }|j                  d«      j	                  «       }t        «       }|D �]F  }t        |t        «      r|d   n|d   }|j                  d|f«      j                  «       }|j                  d|f«      j                  «       }|j                  d|f«      j                  «       }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"|j                  d|t        |
«      |||	f«       �Œ'|j                  d|t        |
«      |||	f«       �ŒI |j                  d| f«      j	                  «       }|D �cg c]  }t        |«      ‘Œ c}cd d d «       S c c}w # 1 sw Y   y xY w)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_atrH   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 ?
            )r   r°   r”   rO   r9   r±   rQ   rR   r5   r   r  )r»   rE   ÚorphanrÇ   rY   ÚsidÚfirstÚlastÚ	count_rowÚcountÚfirst_qÚfirst_atÚlast_atrš   rœ   s                  r   Úlist_conversationsr  †  s  € Ü‹>×+Ò+Øˆ	Ü	Ô	˜TØ—‘ðó
÷ ‰(‹*ð 	ô ‹fˆÜˆCÜ'1°#´tÔ'<�#�lÒ#À#ÀaÁ&ˆCØ—L‘LØgØ�ó÷ ‰h‹jð ð —<‘<Ø^Ø�ó÷ ‰h‹jð ð Ÿ™ØHØ�ó÷ ‰h‹jð ô '1°¼DÔ&A�I˜c’NÀyÐQRÁ|ˆEÙ+0´ZÀÄtÔ5L�e˜JÒ'Ñ_dÐSXÐYZÒS[ÐjpˆGÙ.3¼
À5Ì$Ô8O�u˜\Ò*ÑbgÐV[Ð\]ÒV^ÐmpˆHÙ,0´ZÀÄdÔ5K�d˜<Ò(Ñ]aÐRVÐWXÒRYÐgjˆGÜŒ}Ø—‘ðIàÔ.¨wÓ7¸À7ÈEÐRöð —‘ð-àÔ.¨wÓ7¸À7ÈEÐRöð3 ð> �|‰|ðð8 ˆHó;
÷< ‰(‹*ð= 	ñ> "&Ó&¡˜A”�Q• Ñ&÷S 
Ñ	ùòR '÷S 
Ð	ús   ¡FGÆ<GÇGÇGÇG(c                ó´   — t        «       5 }|j                  d| f«       |j                  d| f«       |j                  d| f«       d d d «       y # 1 sw Y   y xY w)Nz*DELETE FROM audit_log WHERE session_id = ?z)DELETE FROM feedback WHERE session_id = ?z!DELETE FROM sessions WHERE id = ?)r”   rO   )r·   rE   s     r   Údelete_conversationr  Õ  sI   € Ü	Ô	˜TØ�‰ÐAÀJÀ=ÔQØ�‰Ð@À:À-ÔPØ�‰Ð8¸:¸-ÔH÷ 
×	Ñ	ús   ‹:AÁAc                 ó<   — t        t        j                  «       «      S r4   )ro   ÚuuidÚuuid4r=   r   r   Únew_session_idr  Ü  s   € ÜŒt�z‰z‹|ÓÐr   c                 ó   — t        «       rdS dS )NÚpostgresÚsqlite)r   r=   r   r   Ú
db_backendr  à  s   € Ü$œˆ:Ð4¨HÐ4r   c                 óP   — t        j                  «       j                  d¬«      dz   S )NÚseconds)ÚtimespecÚZ)r   Úutcnowrí   r=   r   r   r±   r±   ä  s"   € Ü�?‰?Ó×&Ñ&°	Ð&Ó:¸SÑ@Ð@r   )rp   Úbool)rp   r   )r&   ro   rp   ro   )r   ro   rp   ro   rr   )rW   ro   rp   zset[str])r³   ro   r´   ro   rµ   ro   r¶   rP   r·   ú
str | Nonerp   rP   )éd   r   )r»   rP   r¼   rP   rp   ú
list[dict])r·   ro   rp   r(  )r¸   ú
int | Noner·   r&  r³   ro   r´   ro   rÅ   ro   rÆ   r&  rŸ   r&  r    r&  r¡   r&  r¢   r&  r£   r&  r¤   r&  rp   rP   )r'  r   NNNN)r»   rP   r¼   rP   rÅ   r&  rÓ   r)  rŸ   r&  r    r&  rp   r(  )rŸ   r&  r    r&  rp   zdict[str, int])rŸ   ro   rp   zdict[str, dict[str, int]])rŸ   ro   rå   rP   rp   zlist[dict[str, int | str]])rŸ   r&  r    r&  r»   rP   rp   r(  )rú   rP   rÓ   rP   rp   rq   )rú   rP   rÆ   r&  rp   rq   )r³   ro   rp   ro   r4   )r·   ro   r¯   r&  rp   rq   )é(   )r»   rP   rp   r(  )r·   ro   rp   rq   )rp   ro   )1rs   Ú
__future__r   r    r�   r  r   Úpathlibr   Útypingr   r   Ú
app.configr   r	   Ú__annotations__r   r   r'   r)   r?   r†   Ú
__import__ÚLockr‡   rw   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   Ú<module>r2     s~  ðòõ #ã 	Û Û Ý Ý ß  å #à€ˆ+Ó óLó
ó÷
$ñ 
$÷;ñ ;ð| €Ù˜;Ó'×,Ñ,Ó.€óòó.ò 
ó ó(Yð|Øðàðð ðð ð	ð
 ðð 	óô0'ó'ð0 ØØØ!ØØ!ñT'ØðT'àðT'ð ðT'ð ð	T'ð
 ðT'ð ðT'ð ðT'ð ðT'ð ðT'ð ðT'ð ðT'ð ðT'ð 	óT'ðp ØØØØØð'Øð'àð'ð ð'ð ð	'ð
 ð'ð ð'ð ó'ðB Øñ$
àð$
ð ð$
ð ó	$
óNð@ 78õ "ðN ØØñ	QàðQð ðQð ð	Qð
 óQó
ó
óôô@L'ó^Ióó5ôAr   