
    j              
         % S r SSKJr  SSKrSSKJr  SSKJrJr  SSK	r
\" \
R                  R                  S\" \" \5      R                  S-  5      5      5      rSNS jrSOS	 jrSPS
 jrSQS jrSRS jrSSS jrSSS jrSTS jrSTS jrSQS jrSTS jr  SU     SVS jjrSSS jrSSS jrSSKr SSK!J"r#  \ RH                  " S5      r%\ RH                  " S5      r&Sr'Sr(SWS jr)SXSYS jjr* SZ   S[S jjr+Sr,          S\S  jr-   S]           S^S! jjr.        S_S" jr/S#r0        S`S$ jr1  SU         SaS% jjr2SSK3r4SS&K!J!r5J6r7   SSK8r9S'r:S(r<\ RH                  " S)5      r=SbS* jr>ScS+ jr?SdS, jr@SeS- jrAS.S/S.S'S0.S.S1S2SS0.S/S2S3SS0.S2S4S5SS0.S6S6S6SS0.S7.rBS8\CS9'   SfS: jrDSgS; jrEShS< jrFSiS= jrGSjS> jrHSkS? jrISlS@ jrJ  SU           SmSA jjrK          SnSB jrLSoSC jrMSpSD jrNSqSE jrO\P" 1 SFk5      rQSGrRSrSH jrS        SsSI jrT          S\SJ jrU    St             SuSK jjrV        SvSL jrW        SwSM jrXg! \; a    Sr9Sr: GNf = f)xu  
ERGON - Funciones de query para la API de obras.

Cada funcion recibe una conexion SQLite y devuelve un dict o lista lista
para serializar a JSON. No hace networking ni maneja HTTP — es logica pura.

Convenciones:
- Codigos de obra llegan desde la URL (ej: "TDG1").
- Si la obra no existe, las funciones devuelven None o [] segun corresponda.
- Datos demo se etiquetan en el payload con "es_demo": true.
    )annotationsN)Path)AnyOptionalDATABASE_PATHzergon.dbc                     [         R                  " [        [        5      5      n [         R                  U l        U R                  S5        U $ )NzPRAGMA foreign_keys = ON)sqlite3connectstrDB_PATHRowrow_factoryexecute)conns    /app/db/api_obras.pyget_connr      s2    ??3w<(D{{DLL+,K    c                R    U R                  5        Vs0 s H  oX   _M	     sn$ s  snf N)keys)rowks     r   _row_to_dictr      s$    "xxz*z!svIz***s   $c                    U R                  S5      R                  5       nU Vs/ s H  n[        U5      PM     sn$ s  snf )z4Lista compacta de obras para el selector del header.z
        SELECT id, codigo, nombre, cliente, moneda, es_demo,
               fecha_inicio, fecha_fin_plan, fecha_corte_actual,
               dg_nivel_servicio
        FROM obras
        ORDER BY es_demo ASC, codigo
    )r   fetchallr   )r   rowsrs      r   
list_obrasr   '   s?    <<  	 
 	 &**TLOT***s   <c                l    U R                  SU45      R                  5       nU(       a  [        U5      $ S$ )z(Detalle completo de una obra por codigo.z2
        SELECT * FROM obras WHERE codigo = ?
    N)r   fetchoner   r   codigor   s      r   get_obrar#   3   s8    
,, 
HJ  !$<--r   c                `    U R                  SU45      R                  5       nU(       a  US   $ S $ )Nz%SELECT id FROM obras WHERE codigo = ?id)r   r    r!   s      r   _obra_idr&   ;   s/    
,,>	
J
S
S
UC3t9%%r   c                    [        X5      nUc  / $ U R                  SU45      R                  5       nU Vs/ s H  n[        U5      PM     sn$ s  snf )Nzv
        SELECT orden, nombre, peso_pct
        FROM rubros_obra
        WHERE obra_id = ?
        ORDER BY orden
    r&   r   r   r   r   r"   oidr   r   s        r   
get_rubrosr+   D   sZ    
4
 C
{	<< 
 
  	 &**TLOT***   Ac                    [        X5      nUc  / $ U R                  SU45      R                  5       nU Vs/ s H  n[        U5      PM     sn$ s  snf )Nz
        SELECT s.orden, s.nombre, s.contacto, r.nombre AS rubro
        FROM subcontratistas s
        LEFT JOIN rubros_obra r ON r.id = s.rubro_id
        WHERE s.obra_id = ?
        ORDER BY s.orden
    r(   r)   s        r   get_subcontratistasr.   Q   sZ    
4
 C
{	<<    	 &**TLOT***r,   c                B   [        X5      nUc  / / S S.$ U R                  SU45      R                  5       nU R                  SU45      R                  5        Vs/ s H  oDS   PM	     nn/ nU H`  nU R                  SX'S   45      R                  5       nUR                  US   US   US	   U V	s/ s H  n	[	        U	5      PM     sn	S
.5        Mb     U R                  SU45      R                  5       n
UUU
 Vs/ s H  n[	        U5      PM     snS.$ s  snf s  sn	f s  snf )N)rubrosmesesagregadoj
        SELECT id, orden, nombre, peso_pct
        FROM rubros_obra WHERE obra_id = ? ORDER BY orden
    z\
        SELECT DISTINCT mes FROM avance_mensual
        WHERE obra_id = ? ORDER BY mes
    mesz
            SELECT mes, plan_acum_pct, real_acum_pct
            FROM avance_mensual
            WHERE obra_id = ? AND rubro_id = ?
            ORDER BY mes
        r%   ordennombrepeso_pctr5   r6   r7   seriea  
        SELECT a.mes,
               ROUND(SUM(a.plan_acum_pct * r.peso_pct / 100.0), 2) AS plan_global,
               ROUND(SUM(CASE WHEN a.real_acum_pct IS NOT NULL
                              THEN a.real_acum_pct * r.peso_pct / 100.0
                              ELSE 0 END), 2) AS real_global
        FROM avance_mensual a
        JOIN rubros_obra r ON r.id = a.rubro_id
        WHERE a.obra_id = ?
        GROUP BY a.mes
        ORDER BY a.mes
    )r1   r0   r2   r&   r   r   appendr   )r   r"   r*   r0   r   r1   
rubros_outrubr9   saggs              r   
get_avancer@   c   se   
4
 C
{rt<<\\    
  $|| -       !uX  E 
 J 
 t9
  (xz 	 	\(mJ/45u!l1ou5	
 	   ,,     .12c\!_c2 G" 6* 3s   D/D8Dc                   [        X5      nUc  / S S.$ U R                  SU45      R                  5       n/ nU H`  nU R                  SX%S   45      R                  5       nUR                  US   US   US   U Vs/ s H  n[	        U5      PM     snS.5        Mb     U R                  S	U45      R                  5       nXH V	s/ s H  n	[	        U	5      PM     sn	S.$ s  snf s  sn	f )
N)r0   r2   r3   z
            SELECT mes, plan, real
            FROM presupuesto_mensual
            WHERE obra_id = ? AND rubro_id = ?
            ORDER BY mes
        r%   r5   r6   r7   r8   a  
        SELECT mes,
               ROUND(SUM(plan), 0) AS plan_mes,
               ROUND(SUM(CASE WHEN real IS NOT NULL THEN real ELSE NULL END), 0) AS real_mes
        FROM presupuesto_mensual
        WHERE obra_id = ?
        GROUP BY mes
        ORDER BY mes
    r:   )
r   r"   r*   r0   outr=   r9   r>   r?   r   s
             r   get_presupuestorC      s   
4
 C
{$//\\    
 C 
 t9
  (xz 	 	

\(mJ/45u!l1ou5	
 	  ,,     'EAQ'EFF 6 (Fs   ;CC!c                   [        X5      nU(       d  gUS   SS n[        US   5      nU R                  SX45      R                  5       nU(       a  US   c  g[        US   5      n[        US   =(       d    S5      nXF-  S	-  nXG-  S	-  n	U R                  S
X45      R                  5       n
[        U
S   =(       d    S5      nX-
  nX-
  nUS:  a  [	        X-  S5      OSnUS:  a  [	        X-  S5      OSnU(       a  [	        XO-  S5      OSnU R                  SU45      R                  5       S   nUS::  a  SnOUS:  a  SnOSUS-
  S-  -
  nU(       a  [	        USUS	-  -
  -  S5      OSnU(       a  [	        USUS	-  -   -  S5      OSnUU[	        US5      [	        US5      [	        U	S5      [	        US5      [	        US5      [	        US5      UUUU[	        US5      UUSU SUS S3S.S.$ )a  Calculo EVM al `fecha_corte_actual` de la obra.
Sigue la formula del CURVA_S del FCAT1:
    BAC = presupuesto_total
    PV (t) = BAC * plan_acum_pct_global(t) / 100
    EV (t) = BAC * real_acum_pct_global(t) / 100
    AC (t) = sum(real de presupuesto_mensual hasta t)
    SV = EV - PV, CV = EV - AC
    SPI = EV / PV, CPI = EV / AC
    EAC (CPI) = BAC / CPI
Devuelve None si la obra no existe o no hay data suficiente.
Nfecha_corte_actual   presupuesto_totala  
        SELECT
            ROUND(SUM(a.plan_acum_pct * r.peso_pct / 100.0), 4) AS plan_global,
            ROUND(SUM(CASE WHEN a.real_acum_pct IS NOT NULL
                           THEN a.real_acum_pct * r.peso_pct / 100.0
                           ELSE 0 END), 4) AS real_global
        FROM avance_mensual a
        JOIN rubros_obra r ON r.id = a.rubro_id
        WHERE a.obra_id = (SELECT id FROM obras WHERE codigo = ?)
          AND a.mes = ?
    plan_globalreal_globalr         Y@z
        SELECT ROUND(SUM(real), 0) AS ac
        FROM presupuesto_mensual
        WHERE obra_id = (SELECT id FROM obras WHERE codigo = ?)
          AND mes <= ?
          AND real IS NOT NULL
    ac   z
        SELECT COUNT(DISTINCT mes) AS n
        FROM avance_mensual
        WHERE obra_id = (SELECT id FROM obras WHERE codigo = ?)
          AND real_acum_pct IS NOT NULL
    n   g      .@   g      @g?z=Intervalo se estrecha a medida que la obra genera datos. Con z$ mes(es) de data real, banda de +/- z.1fz%.)meses_con_data	banda_pcteac_loeac_hinota)r"   	corte_mesbacpvevrK   svcvspicpieaccalibracion)r#   floatr   r    round)r   r"   obrarU   rV   r   rH   rI   rW   rX   ac_rowrK   rY   rZ   r[   r\   r]   meses_con_realrQ   rR   rS   s                        r   get_evmrd      sX    D!D)*2A.I
()
*C ,, 
 	
 'hj  #m$,M*+KM*/a0K		U	"B		U	"B\\  	 'hj  
vd| q	!B	B	B!Av%
4C!Av%
4C!$%	1
$C \\ #
 
 HJs$N 		2		NQ.;??	8;U3!i%//0!4F8;U3!i%//0!4F S!}BlBlBlBlBl,y!,*++OPYZ]^`b
 r   c                L   SSK nUR                  X5      nUR                  S/ 5       Hx  nUS   US'   UR                  S5      =(       d    0 nSU;   a  UR                  SUS   5        SU;   a  UR                  SUS   5        S	U;   d  Mc  UR                  S	US	   5        Mz     U$ )
aR  F11 (2026-04-20): delega a `alertas_engine.evaluar()`.

Motor de reglas con 8 reglas (R1-R8): avance/sobrecosto por rubro,
fecha fin proyectada (EAC_date via SPI), contratos vencidos y sin firma.

Retrocompat: cada alerta incluye `estado` como alias de `severidad` para
no romper frontends pre-F11. El alias se puede retirar en sprint 4.
r   Nalertas	severidadestadocontexto	desvio_ppplan_acum_pctreal_acum_pct)alertas_engineevaluarget
setdefault)r   r"   rm   payloadactxs         r   get_alertasrt   )  s     $$T2G [[B'n(eeJ%2#LLc+&67c!LL#o*>?c!LL#o*>? ( Nr   c                   [        X5      nUc  / S S.$ SnU/nU(       a  US-  nUR                  U5        U(       a  US-  nUR                  U5        US-  nU R                  XV5      R                  5       nU R                  SU45      R	                  5       nU V	s/ s H  n	[        U	5      PM     sn	U(       a  [        U5      S.$ S S.$ s  sn	f )N)diasresumenzoSELECT fecha, total_personal, lluvia_mm, dia_perdido, notas
           FROM asistencia_diaria WHERE obra_id = ? AND fecha >= ? AND fecha <= ?z ORDER BY fechaa   
        SELECT ROUND(AVG(total_personal), 1) AS promedio,
               SUM(dia_perdido) AS dias_perdidos,
               ROUND(SUM(lluvia_mm), 1) AS lluvia_total_mm,
               COUNT(*) AS n_dias
        FROM asistencia_diaria WHERE obra_id = ?
    )r&   r;   r   r   r    r   )
r   r"   desdehastar*   qargsr   resumen_rowr   s
             r   get_asistenciar   F  s     4
 C
{t,,	7AeD	E	E	A<< ))+D,,      +//$Qa$/0;<, AE /s   Cc                    [        X5      nUc  / $ U R                  SU45      R                  5       nU Vs/ s H  n[        U5      PM     sn$ s  snf )NaC  
        SELECT c.tipo, c.descripcion, c.fecha_emision, c.fecha_firma,
               c.fecha_vence, c.estado, c.monto,
               s.nombre AS subcontratista
        FROM contratos c
        LEFT JOIN subcontratistas s ON s.id = c.contratista_id
        WHERE c.obra_id = ?
        ORDER BY c.fecha_emision, c.tipo
    r(   r)   s        r   get_contratosr   g  sZ    
4
 C
{	<<    	 &**TLOT***r,   c                    [        X5      nUc  / $ U R                  SU45      R                  5       nU Vs/ s H  n[        U5      PM     sn$ s  snf )Nz
        SELECT item, unidad, recibido, consumido,
               ROUND(recibido - consumido, 2) AS saldo,
               ultima_update
        FROM materiales
        WHERE obra_id = ?
        ORDER BY item
    r(   r)   s        r   get_materialesr   w  sZ    
4
 C
{	<<    	 &**TLOT***r,   )datez^[A-Z0-9]{3,6}$z^\d{4}-\d{2}-\d{2}$)
r"   r6   clienterG   fecha_iniciofecha_fin_planrE   umbral_desvio_pctumbral_critico_pctdg_nivel_servicio)	ubicaciontipomonedadg_fiscalizadordg_responsabledg_version_templatees_democ                   [        U [        5      (       a  [        R                  U 5      (       d  g  U R	                  S5      u  pn[        [        U5      [        U5      [        U5      5      $ ! [        [        4 a     g f = f)N-)	
isinstancer   	_FECHA_REmatchsplit_dateint
ValueError	TypeError)r>   ymds       r   _parse_dater     sh    aY__Q%7%7''#,aSVSVSV,,	" s   ;A. .B BFc                H   / n[        U [        5      (       d  S/$ [         H.  nU R                  U5      S;   d  M  UR	                  SU 35        M0     U R                  S5      n[        U[
        5      (       a/  [        R                  U5      (       d  UR	                  SU S35         [        U R                  SS5      5      nUS::  a  UR	                  S	5        [        U R                  SS5      5      n[        U R                  SS5      5      n[        U R                  SS5      5      nU R                  S5      (       a  U(       d  UR	                  S5        U R                  S5      (       a  U(       d  UR	                  S5        U R                  S5      (       a  U(       d  UR	                  S5        U(       a  U(       a  Xv::  a  UR	                  S5        U(       a  U(       a  X:  a  UR	                  S5         [        U R                  SS5      5      n	[        U R                  SS5      5      n
U	S:  d  U
S:  a  UR	                  S5        X::  a  UR	                  S5         [        U R                  SS5      5      nUS;  a  UR	                  S5        U R                  S/ 5      n[        U[        5      (       a  [        U5      S:X  a  UR	                  S5        GOTSn[        5       n[!        U5       GH	  u  nn[        U[        5      (       d  UR	                  SUS -    S!35        M6  UR                  S"5      =(       d    SR#                  5       nU(       d  UR	                  SUS -    S#35        M  UR%                  5       U;   a  UR	                  S$U S%35        UR'                  UR%                  5       5         [        UR                  S&S5      5      nUS:  d  US':  a  UR	                  S(U S)35        UU-  nGM     [)        US+-
  5      S,:  a  UR	                  S-US. S/US'-
  S0 S135        U R                  S2/ 5      n[        U[        5      (       d  UR	                  S35        U$ [!        U5       H|  u  nn[        U[        5      (       d  UR	                  S4US -    S!35        M5  UR                  S"5      =(       d    SR#                  5       (       a  Md  UR	                  S4US -    S#35        M~     U$ ! [        [        4 a    UR	                  S
5         GNHf = f! [        [        4 a    UR	                  S5         GNf = f! [        [        4 a    UR	                  S5         GNf = f! [        [        4 a    UR	                  S(U S*35         GM  f = f)5zoValida el payload antes de tocar la DB. Devuelve lista de errores (strings).
Lista vacia = valido. No hace IO.
zPayload debe ser un objeto JSON)N zCampo requerido ausente: r"   zcodigo invalido 'zL'. Formato esperado: 3 a 6 caracteres A-Z o 0-9, sin espacios ni minusculas.rG   r   z'presupuesto_total debe ser mayor que 0.z$presupuesto_total debe ser numerico.r   r   r   rE   z!fecha_inicio debe ser YYYY-MM-DD.z#fecha_fin_plan debe ser YYYY-MM-DD.z'fecha_corte_actual debe ser YYYY-MM-DD.z1fecha_fin_plan debe ser posterior a fecha_inicio.z8fecha_corte_actual no puede ser anterior a fecha_inicio.r   r   zumbrales deben ser >= 0.z8umbral_critico_pct debe ser mayor que umbral_desvio_pct.zumbrales deben ser numericos.r   )rN         z$dg_nivel_servicio debe ser 1, 2 o 3.r0   zDebe incluir al menos un rubro.g        zRubro #rN   z: formato invalido.r6   z: nombre vacio.zRubro duplicado: 'z'.r7   d   zRubro 'z': peso fuera de rango (0-100).z': peso no numerico.rJ   g{Gz?zSuma de pesos de rubros = z.2fu0   %. Debe ser 100% (± 0.01%). Diferencia actual: z+.2fzpp.subcontratistasz#subcontratistas debe ser una lista.zSubcontratista #)r   dict_OBRA_REQUIRED_FIELDSro   r;   r   
_CODIGO_REr   r_   r   r   r   r   listlenset	enumeratestriploweraddabs)rq   	is_updateerrorsfieldr"   presuf_inif_finf_coru_desu_crinivelr0   
suma_pesosnombres_vistosir   r6   pesosubsr>   s                        r   validate_obra_payloadr     s    Fgt$$122 ';;u+MM5eW=> '
 [["F&#z'7'7'?'?x'st	

>gkk"5q9:A:MMCD
 NB78E$4b9:E$8"=>E{{>""59:{{#$$U;<{{'((?@5>IJ5=PQ7gkk"5q9:gkk"6:;19	MM45>MMTU
>GKK 3Q78	!MM@A
 [[2&Ffd##s6{a'778
#&5f%DAqa&&!u,?@AeeHo+224F!uO<=||~/ 26("=>v||~.FQUU:q12!8tczMMGF83R"STd"
 &$ zE!"T)MM,Z,< =&&03&6t%<CA ;;("-DdD!!;< M dODAqa&& 015HIJEE(O)r0022 01_EF $ Ma z" ><=>4 z" 7567 z" ><=>4 z* Fx/CDEEFsJ   )2S? ?A)T' )2U AU7?!T$#T$'!UU!U43U47%V! V!c                   [        U[        U5      S9nU(       a  SUSS.$ UR                  S5      =(       d    SR                  5       R	                  5       nU(       a*  UR	                  5       n[        X5      nUc  SSU S	3/S
S.$ O[        X5      b  SSU S3/SS.$ UR                  S5      =(       d    SR                  5       =(       d    SnUR                  S5      =(       d    SR                  5       =(       d    SnUR                  S5      S;   a  SOSnU R                  5       n	 U	R                  S5        U(       Ga>  U	R                  SUR                  S5      UR                  S5      UR                  S5      UR                  S5      U[        UR                  S5      5      UR                  S5      UR                  S5      UR                  S5      [        UR                  S 5      5      [        UR                  S!5      5      [        UR                  S"5      5      UR                  S#5      UR                  S$5      XxU45        [        X5      n
U	R                  S%U
45        U	R                  S&U
45        SnGOU	R                  S'XAR                  S5      UR                  S5      UR                  S5      UR                  S5      U[        UR                  S5      5      UR                  S5      UR                  S5      UR                  S5      [        UR                  S 5      5      [        UR                  S!5      5      [        UR                  S"5      5      UR                  S#5      UR                  S$5      Xx45        U	R                  n
S(n0 n[        UR                  S)/ 5      SS*9 H  u  pUR                  S5      =(       d    SR                  5       n[        UR                  S+S5      5      n[        UR                  S,U5      5      nU	R                  S-U
UUU45        U	R                  XR                  5       '   M     [        UR                  S./ 5      SS*9 H  u  nnUR                  S5      =(       d    SR                  5       nUR                  S/5      =(       d    SR                  5       R                  5       nUR                  U5      n[        UR                  S,U5      5      nU	R                  S0U
UUUUR                  S15      UR                  S25      45        M     U R                  5         S(XJUS3.$ ! [        R                   aZ  nU R!                  5         [#        U5      nS4UR	                  5       ;   a  U(       a  SS5U S63/SS.s SnA$ SS7U 3/SS.s SnA$ SnAf[$         a$  nU R!                  5         SS8U 3/S9S.s SnA$ SnAff = f):u  Crea o actualiza una obra con sus rubros y subcontratistas.

Si `codigo_existente` es None, INSERT. Si se provee, UPDATE (payload.codigo
debe coincidir — si difiere, se usa el del path).

Transaccional: rollback atomico si cualquier paso falla.

Devuelve:
    {"ok": True, "codigo": "...", "obra_id": N, "created": bool}
o:
    {"ok": False, "errors": [...], "status": 400|404|409|500}
)r   F  okr   statusr"   r   NzObra  no encontrada  zYa existe una obra con codigo z. Use editar en lugar de crear.  r   PYGr   zv1.0r   )rN   T1rN   r   BEGINa   
                UPDATE obras SET
                    nombre = ?, cliente = ?, ubicacion = ?, tipo = ?, moneda = ?,
                    presupuesto_total = ?,
                    fecha_inicio = ?, fecha_fin_plan = ?, fecha_corte_actual = ?,
                    umbral_desvio_pct = ?, umbral_critico_pct = ?,
                    dg_nivel_servicio = ?, dg_fiscalizador = ?, dg_responsable = ?,
                    dg_version_template = ?, es_demo = ?,
                    updated_at = datetime('now')
                WHERE codigo = ?
            r6   r   r   r   rG   r   r   rE   r   r   r   r   r   z-DELETE FROM subcontratistas WHERE obra_id = ?z)DELETE FROM rubros_obra WHERE obra_id = ?a  
                INSERT INTO obras (
                    codigo, nombre, cliente, ubicacion, tipo, moneda,
                    presupuesto_total, fecha_inicio, fecha_fin_plan, fecha_corte_actual,
                    umbral_desvio_pct, umbral_critico_pct,
                    dg_nivel_servicio, dg_fiscalizador, dg_responsable,
                    dg_version_template, es_demo
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            Tr0   )startr7   r5   z|
                INSERT INTO rubros_obra (obra_id, orden, nombre, peso_pct)
                VALUES (?, ?, ?, ?)
            r   rubroz
                INSERT INTO subcontratistas (obra_id, orden, nombre, rubro_id, contacto, notas)
                VALUES (?, ?, ?, ?, ?, ?)
            contactonotas)r   r"   obra_idcreatedzFOREIGN KEYzENo se puede reemplazar la lista de rubros o subcontratistas: la obra z ya tiene avance, presupuesto o contratos registrados que los referencian. Edite los datos escalares (fechas, presupuesto, umbrales) sin cambiar la lista de rubros, o solicite un merge asistido.zRestriccion de base de datos: zError interno:   )r   boolro   r   upperr&   cursorr   r_   r   	lastrowidr   r   commitr	   IntegrityErrorrollbackr   	Exception)r   rq   codigo_existenter   r"   existingr   versionr   curr   r   rubro_id_by_nombreidxr   r6   r   r5   r>   	rubro_refrubro_idemsgs                          r   upsert_obrar     sH    #7d;K6LMFv==kk(#)r00288:F!'')D)eF8>,J+KWZ[[  D!-;F8Cbcd  kk(#,u335>F{{01;VBBDNG;;y)^;aG
++-CpOGKK 
 H%w{{9'=K('++f*=vgkk"567N+W[[9I-J01gkk"567gkk"678GKK 345-.<L0M&, t,G KKG'TKKCgZPGKK  H-w{{9/EK('++f*=vgkk"567N+W[[9I-J01gkk"567gkk"678GKK 345-.<L0M( mmGG .0Hb 9CFCeeHo+224Fz1-.Dgs+,EKK  5&$/1 25||~. D  ,=r B!LFCeeHo+224Fw-2446<<>I)--i8Hgs+,EKK  j!155> M 	fWUU!! 
!f CIIK',<['( )^^ 	 	 7u=>
 	

  O/!(='>#NNOs>   +PU W!A V0W!!	V0*W!0W!=WW!W!)r%   r   fechasectorpisorT   	blob_pathexif_gps_latexif_gps_lon
exif_fechausuario	mime_type
size_bytes
created_atc                   [        X5      nUc  SSU S3/SS.$ UR                  S5      =(       d    SR                  5       nU(       d  SS	/S
S.$ [        R	                  U5      (       d  SSU S3/S
S.$  U R                  SUUUR                  S5      =(       d    SUR                  S5      =(       d    SUR                  S5      =(       d    SUUR                  S5      UR                  S5      UR                  S5      =(       d    SUR                  S5      =(       d    SUR                  S5      =(       d    S[        UR                  S5      =(       d    S5      45      nU R                  5         [        XUR                  5      nSUSS.$ ! [        R                   a$  nU R                  5         SSU 3/S
S.s SnA$ SnAff = f)zQInserta una fila en fotos_obra. Devuelve {ok, foto, status} o {ok:False, errors}.NFobra r   r   r   r   r   !fecha es obligatoria (YYYY-MM-DD)r   fecha invalida ''. Formato YYYY-MM-DD.a  
            INSERT INTO fotos_obra
              (obra_id, fecha, sector, piso, nota, blob_path,
               exif_gps_lat, exif_gps_lon, exif_fecha,
               usuario, mime_type, size_bytes)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            r   r   rT   r   r   r   r   r   z
image/jpegr   r   T   )r   fotor   
Error DB: )r&   ro   r   r   r   r   r   r   get_fotor   r	   r   r   )	r   r"   metar   r   r   r   r   r   s	            r   create_fotor     s    t$G%x~(F'GSVWWXXg$"++-E(K'LX[\\??5!!)%0FGH
 	
Jll (#+t&!)T&!)T((,'/4)$,+&6,DHH\*/a0
. 	cmm4DC88!! J*QC(8'9SIIJs   6DF G#G<GGc                   [        X5      nUc  / $ SSR                  [        5      -   S-   nU/nU(       a  US-  nUR                  U5        U(       a  US-  nUR                  U5        U(       a  US-  nUR                  U5        US-  nU R	                  Xg5      R                  5       nU V	s/ s H  n	[        U	5      PM     sn	$ s  sn	f )zOLista fotos de una obra con filtros opcionales. Ordena por fecha desc, id desc.SELECT , z" FROM fotos_obra WHERE obra_id = ?rx   ry   z AND sector = ? ORDER BY fecha DESC, id DESC)r&   join_FOTO_FIELDSr;   r   r   r   )
r   r"   rz   r{   r   r   sqlparamsr   r   s
             r   
list_fotosr    s     t$G	 	DIIl++ / 	  !	F  e  e  f**C<<$--/D%)*TLOT***s   .Cc                    [        X5      nUc  gU R                  SSR                  [        5      -   S-   X#45      R	                  5       nU(       a  [        U5      $ S$ )z<Devuelve la foto por id, solo si pertenece a la obra codigo.Nr   r   z- FROM fotos_obra WHERE id = ? AND obra_id = ?)r&   r   r   r  r    r   )r   r"   foto_idr   r   s        r   r   r   
  se     t$G
,,DIIl++ /' 	'	 hj	 
 !$<--r   )r%   r   r   autoractividadesobservacionesclimadecisiones_keyr   parse_estadoparse_procesado_atparse_confirmado_atc                   [        X5      nUc  SSU S3/SS.$ UR                  S5      =(       d    SR                  5       nU(       d  SS	/S
S.$ [        R	                  U5      (       d  SSU S3/S
S.$ UR                  S5      =(       d    SR                  5       nUR                  S5      =(       d    SR                  5       nU(       d  U(       d  SS/S
S.$  U R                  SUUUR                  S5      =(       d    SU=(       d    SU=(       d    SUR                  S5      =(       d    SUR                  S5      =(       d    S45      nU R                  5         U R                  SSR                  [        5      -   S-   UR                  45      R                  5       nS[        U5      SS.$ ! [        R                   a$  n	U R                  5         SSU	 3/S
S.s Sn	A	$ Sn	A	ff = f)zQInserta una fila en daily_log. Devuelve {ok, entry, status} o {ok:False, errors}.NFr   r   r   r   r   r   r   r   r   r   r  r	  zCal menos uno de 'actividades' u 'observaciones' debe contener textoz
            INSERT INTO daily_log
              (obra_id, fecha, autor, actividades, observaciones, clima, decisiones_key)
            VALUES (?, ?, ?, ?, ?, ?, ?)
            r  r
  r  r   r   z FROM daily_log WHERE id = ?Tr   )r   entryr   r   )r&   ro   r   r   r   r   r   r   _DAILY_FIELDSr   r    r   r	   r   r   )
r   r"   rq   r   r   r  r	  r   r  r   s
             r   create_daily_logr  &  s    t$G%x~(F'GSVWW[[!'R..0E(K'LX[\\??5!!)%0FGH
 	
 ;;}-3::<K[[17R>>@M}\]
 	
Jll W%-#t%W%--.6$
  			-003QQ]]
 (* 	 \%%8CHH!! J*QC(8'9SIIJs   CF1 1G)G$G)$G)c                \   [        X5      nUc  / $ SSR                  [        5      -   S-   nU/nU(       a  US-  nUR                  U5        U(       a  US-  nUR                  U5        US-  nU R	                  XV5      R                  5       nU Vs/ s H  n[        U5      PM     sn$ s  snf )zALista entradas daily_log de una obra. Ordena fecha desc, id desc.r   r   z! FROM daily_log WHERE obra_id = ?rx   ry   r   )r&   r   r  r;   r   r   r   )	r   r"   rz   r{   r   r  r  r   r   s	            r   list_daily_logr  _  s     t$G	 	DIIm,, 0 	  !	F  e  e**C<<$--/D%)*TLOT***s   B))datetime	timedeltaTrF   z^[^@\s]+@[^@\s]+\.[^@\s]+$c                     [         $ r   )
_BCRYPT_OK r   r   bcrypt_availabler    s    r   c                    [         (       d  [        S5      e[        R                  " U R	                  S5      [        R
                  " 5       5      R                  S5      $ )zEDevuelve hash bcrypt como string utf-8. Lanza si bcrypt no instalado.z0bcrypt no instalado. Ejecute: pip install bcryptutf-8)r  RuntimeError_bcrypthashpwencodegensaltdecode)plains    r   hash_passwordr$    s?    :MNN>>%,,w/1BCJJ7SSr   c                    [         (       a  U(       d  g [        R                  " U R                  S5      UR                  S5      5      $ ! [        [
        4 a     gf = f)zIComparacion constant-time via bcrypt. False si bcrypt no esta disponible.Fr  )r  r  checkpwr   r   r   )r#  hasheds     r   verify_passwordr(    sK    :Vu||G4fmmG6LMM	" s   4A
 
AAc                J   U S   U S   U S   U S   [        U S   5      U S   U S   S.n [        U R                  5       5      nS	U;   a  U S	   US	'   S
U;   a  U S
   US
'   SU;   a  U S   US'   SU;   a  U S   US'   SU;   a  U S   US'   U$ ! [         a    [        5       n N^f = f)z)Nunca incluir password_hash en la salida.r%   emailr6   rolactivor   last_login_at)r%   r*  r6   r+  r,  r   r-  	plan_tiertrial_started_attrial_ends_atsubscription_statusempresa)r   r   r   r   )r   baser   s      r   _usuario_row_to_dictr4    s     $iWh-5zs8}%,'_-D388: d,[T!#&'9#: $ #O 4_$&)*?&@"#Di.YK  us   B B"!B"rN   r   )	max_obrasmax_usuarios
storage_gb	watermark   
         r   i'  )trialn1n2n3
enterprisezdict[str, dict]TIER_LIMITSc           
     F   SSK J nJn  U R                  S5      =(       d    SR                  5       nU R                  S5      =(       d    SR                  5       n[        R                  U[        S   5      nUS:H  nSnSnSn	U(       a  U R                  S5      n
U
(       a   UR                  U
R                  S	S
5      5      nUR                  c  UR                  UR                  S9nUR                  UR                  5      nX-
  nUR                  5       nUS:  a  Sn[        S[        US-  5      5      n	O	SnSn	OSnSn	US;   a  SnU(       a  Sn[        UR                  S5      =(       d    U5      nUUUUUU	U[!        U5      S.$ ! [        [        4 a    SnSn	 N^f = f)a2  Calcula el estado de prueba/suscripcion del usuario.

Retorna dict con:
    plan_tier:           tier vigente
    subscription_status: trial | active | expired | cancelled
    is_trial:            True si plan_tier == 'trial'
    trial_active:        True si trial y dentro de los 14 dias
    trial_expired:       True si trial y vencido
    days_remaining:      enteros >= 0 si trial activo, 0 si expirado, None si no trial
    watermark_required:  True si exports deben llevar marca DEMO
    limits:              dict con max_obras / max_usuarios / storage_gb
r   )r  timezoner.  r=  r1  FNr0   T)tzinfoTiQ    )expired	cancelledr8  )r.  r1  is_trialtrial_activetrial_expireddays_remainingwatermark_requiredlimits)r  rD  ro   r   rB  fromisoformatreplacerG  utcnowtotal_secondsmaxr   r   r   r   r   )r   r  rD  r.  
sub_statusrP  rK  rL  rM  rN  ends_at_strends_dtrT  deltasecsrO  s                   r   get_trial_statusr\    s    ,[)4W;;=I++34?FFHJ__YG(<=FG#HLM$(Nkk/2$"001D1DS#1NO>>)%ooX\\oBGll8<<0**,!8#'L%(C,>%?N$(M%&N  LN -- Mfjj5FG )$&(0v,	 	# 	* $#!#$s   !BF	 8F	 	F F c                V   [        U5      nUS   (       a  gUS   S   nUR                  S5      nU(       d  gUR                  S5      S:X  a  g	U R                  S
[        U5      45      R	                  5       nU(       a  US   OSnXc:  a  SSUS   R                  5        SU SU S34$ g	)zVerifica si el usuario puede crear una obra mas segun su tier.

Retorna (allowed, reason). reason es string vacio si allowed=True.
rM  )FzNTu prueba de 14 dias vencio. Activa una suscripcion para seguir creando obras.rP  r5  r%   )FzUsuario invalido.r+  admin)Tr   z=SELECT COUNT(*) AS c FROM usuarios_obras WHERE usuario_id = ?r   FzTu plan r.  z permite hasta z obra(s). Ya tenes z.. Actualiza a un plan superior para sumar mas.)r\  ro   r   r   r    r   )r   r   r   limit
usuario_id	count_rowcounts          r   can_create_obrarc    s    
 g&Fof8[)ET"J){{5W$G	Z hj  &IaL1E~vk*0023?5' JwLN
 	
 r   c                    [        U[        5      (       a  [        R                  U5      (       d  gU R	                  SUR                  5       45      R                  5       $ )zHDevuelve la Row cruda (incluye password_hash para uso interno de login).Nz5SELECT * FROM usuarios WHERE email = ? AND activo = 1)r   r   	_EMAIL_REr   r   r   r    )r   r*  s     r   get_usuario_by_emailrf  -  sI    eS!!)?)?<<?	 hjr   c                ~    U R                  S[        U5      45      R                  5       nU(       a  [        U5      $ S $ )Nz#SELECT * FROM usuarios WHERE id = ?)r   r   r    r4  )r   r`  r   s      r   get_usuariorh  7  s>    
,,-	Z hj  ),$55r   c                   [         (       d  SS/SS.$ UR                  S5      =(       d    SR                  5       R                  5       nUR                  S5      =(       d    SnUR                  S5      =(       d    SR                  5       nUR                  S	5      =(       d    SR                  5       n/ n[        R                  U5      (       d  UR                  S
5        [        U5      S:  a  UR                  S5        U(       d  UR                  S5        US;  a  UR                  S5        U(       a  SUSS.$ U R                  SU45      R                  5       (       a  SSU S3/SS.$  U R                  SU[        U5      XE45      nU R                  5         S[        XR                  5      SS.$ ! [        R                   a$  nU R!                  5         SSU 3/SS.s SnA$ SnAff = f)zMCrea un usuario. Devuelve {ok, usuario, status} o {ok:False, errors, status}.Fz#bcrypt no instalado en el servidor.r   r   r*  r   passwordr6   r+  z7email invalido (formato esperado: usuario@dominio.tld).   z*password debe tener al menos 6 caracteres.znombre requerido.)r^  r   z!rol debe ser 'admin' o 'cliente'.r   z&SELECT 1 FROM usuarios WHERE email = ?zYa existe usuario con email .r   zLINSERT INTO usuarios (email, password_hash, nombre, rol) VALUES (?, ?, ?, ?)Tr   )r   r   r   r   N)r  ro   r   r   re  r   r;   r   r   r    r$  r   rh  r   r	   r   r   )	r   rq   r*  rj  r6   r+  r   r   r   s	            r   create_usuariorm  ?  s   :(M'NZ]^^[[!'R..0668E{{:&,"Hkk(#)r002F;;u#
*
*
,CF??5!!OP
8}qBC)*
&&9:v==||<uhGPPRR*FugQ(O'P\_``
Jll"M(+V9

 	{4'GSVWW!! J*QC(8'9SIIJs   .AF5 5G-	G("G-(G-c                   U R                  S[        U5      45      R                  5       nUc  SSU S3/SS.$ U R                  S[        U5      45      R                  5       nUc  SS	U S3/SS.$  U R                  S
[        U5      [        U5      45        U R                  5         SSS.$ ! [        R
                   a$  nU R                  5         SSU 3/SS.s SnA$ SnAff = f)z3M2M usuarios_obras. Idempotente (INSERT OR IGNORE).z)SELECT id, rol FROM usuarios WHERE id = ?NFzusuario_id z no existe.r   r   z!SELECT id FROM obras WHERE id = ?zobra_id zHINSERT OR IGNORE INTO usuarios_obras (usuario_id, obra_id) VALUES (?, ?)Tr   )r   r   r   r   )r   r   r    r   r	   r   r   )r   r`  r   uor   s         r   assign_usuario_obrarq  e  s    @3z?BTU^^`Ay+j\(M'NZ]^^83w</JSSUAy(7);(G'HTWXX	JV_c'l+	
 	c**!! J*QC(8'9SIIJs   2:B- -C%C C% C%c                    U R                  S[        U5      45      R                  5       nU Vs1 s H  o3S   iM	     sn$ s  snf )Nz7SELECT obra_id FROM usuarios_obras WHERE usuario_id = ?r   )r   r   r   )r   r`  r   r   s       r   get_obras_ids_de_usuariors  y  sE    <<A	Z hj 	 #''$QiL$'''s   A c                    US:X  a  U $ Uc  Uc  / $ [        XA5      nU  Vs/ s H  oUR                  S5      U;   d  M  UPM     sn$ s  snf )z=Admin ve todas; cliente solo las asignadas en usuarios_obras.r^  r%   )rs  ro   )
obras_listr`  r+  ids_permitidasr   rp  s         r   filter_obras_por_usuariorw    sP     g~<I1$C!Cz!UU4[N%BAzCCCs
   AAc                    US:X  a  g[        X5      nUc  gU R                  S[        U5      U45      R                  5       nUS L$ )Nr^  TFzASELECT 1 FROM usuarios_obras WHERE usuario_id = ? AND obra_id = ?)r&   r   r   r    )r   r`  r+  codigo_obrar*   r   s         r   usuario_tiene_acceso_obrarz    sU     g~
4
%C
{
,,K	Z# hj  d?r   c                p   [         R                  " S5      n[        R                  " 5       [	        [
        S9-   R                  S5      nU R                  SU[        U5      U45        U R                  S[        U5      45        U R                  S[        U5      45        U R                  5         X#[
        S.$ )zCGenera token urlsafe 32 bytes. TTL 7 dias. Actualiza last_login_at.    )daysz%Y-%m-%d %H:%M:%SzDINSERT INTO sesiones (token, usuario_id, expira_at) VALUES (?, ?, ?)z@UPDATE usuarios SET last_login_at = datetime('now') WHERE id = ?zIDELETE FROM sesiones WHERE usuario_id = ? AND expira_at < datetime('now'))token	expira_atttl_days)
_secretstoken_urlsafe_dtutcnow_td_SESSION_TTL_DAYSstrftimer   r   r   )r   r`  r~  expiras       r   create_sessionr    s    ""2&EjjlS&788BBCVWFLLN	J( 	LLJ	Z
 	LLS	Z 	KKM=NOOr   c                    U(       a$  [        U[        5      (       a  [        U5      S:  a  gU R                  SU45      R	                  5       nUc  gUS   US   US   US   US   US	   S
.$ )zXValida token + no expirado + usuario activo. Devuelve dict con datos del usuario o None.   Na  
        SELECT s.token, s.usuario_id, s.expira_at,
               u.email, u.nombre, u.rol, u.activo
        FROM sesiones s
        JOIN usuarios u ON u.id = s.usuario_id
        WHERE s.token = ?
          AND s.expira_at > datetime('now')
          AND u.activo = 1
        r~  r`  r*  r6   r+  r  )r~  r`  r*  r6   r+  r  )r   r   r   r   r    )r   r~  r   s      r   get_sessionr    s    
5#..#e*r/
,,	 
 hj  {W,'Wh-5z% r   c                v    U(       d  gU R                  SU45      nU R                  5         UR                  S:  $ )NFz$DELETE FROM sesiones WHERE token = ?r   )r   r   rowcount)r   r~  r   s      r   delete_sessionr    s3    
,,=x
HCKKM<<!r   >   actar   planoinformecontratogarantia
orden_pagocertificado)r%   r   	categoriatitulotagsr   r   r   sha256	fecha_doc
subido_porr   r   c                   U (       d  gU R                  S5       Vs/ s H7  oR                  5       (       d  M  UR                  5       R                  5       PM9     nnU(       d  gSR                  [	        [        U5      5      5      $ s  snf )zONormaliza CSV: trim, lowercase, dedup, ordenado alfabeticamente. None si vacio.N,)r   r   r   r   sortedr   )tags_rawtitemss      r   _normalize_tags_csvr    sa    (0s(;I(;1wwyQWWY__(;EI88F3u:&'' Js
   B"Bc                    U(       d  / $ U R                  SSR                  [        5      -   S-   [        U5      U45      R	                  5       nU Vs/ s H  n[        U5      PM     sn$ s  snf )zCDevuelve docs existentes en la obra con mismo sha256 (dedup check).r   r   zJ FROM documentos WHERE obra_id = ? AND sha256 = ? ORDER BY created_at DESC)r   r   _DOC_FIELDSr   r   r   )r   r   r  r   r   s        r   find_documento_by_shar    sm     	<<DIIk** .D 	D	Wv hj	 	
 &**TLOT***s   A*c                   [        X5      nUc  SSU S3/SS.$ UR                  S5      =(       d    SR                  5       R                  5       nU[        ;  a*  SS	U S
SR                  [        [        5      5       S3/SS.$ UR                  S5      =(       d    SR                  5       nU(       d  SS/SS.$ [        U5      S:  a  SS/SS.$ UR                  S5      =(       d    SR                  5       R                  5       nU(       a  [        U5      S:w  a  SS/SS.$ UR                  S5      =(       d    SR                  5       =(       d    SnU(       a%  [        R                  U5      (       d  SSU S3/SS.$ U(       a  [        U5      R                  5       (       d  SS/SS.$ [        UR                  S5      =(       d    S5      n	[        XU5      n
 U R                  SXEXiUUR                  S5      =(       d    S[        UR                  S5      =(       d    S5      UUUR                  S5      =(       d    SUR                  S 5      =(       d    S45      nU R                  5         [!        XUR"                  5      nS!US"S#.nU
(       a  XS$'   U$ ! [$        R&                   a$  nU R)                  5         SS%U 3/SS.s SnA$ SnAff = f)&u+  Inserta una fila en documentos.

Valida categoria (enum 8), titulo no vacio, sha256 presente. No escribe a
disco — el blob ya fue guardado por el caller.

Retrocompat: devuelve `{ok, documento, status, duplicados?: [...]}`.
`duplicados` lista docs previos con mismo sha256 (aviso UI, no rechazo).
NFr   r   r   r   r  r   zcategoria invalida 'z'. Validas: r   rl  r   r  ztitulo es obligatorio   z&titulo demasiado largo (max 200 chars)r  @   z*sha256 invalido (hex 64 chars obligatorio)r  zfecha_doc invalida 'r   zblob_path es obligatorior  z
            INSERT INTO documentos
              (obra_id, categoria, titulo, tags, blob_path,
               mime_type, size_bytes, sha256, fecha_doc, subido_por, notas)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            r   zapplication/octet-streamr   r   r  r   Tr   )r   	documentor   
duplicadosr   )r&   ro   r   r   _DOC_CATEGORIAS_VALIDASr   r  r   r   r   r   r  r  r   r   r   get_documentor   r	   r   r   )r   r"   r   r   r   r  r  r  r  r  r  r   docrespr   s                  r   create_documentor    s    t$G%x~(F'GSVWW+&,"335;;=I//&yk99V$;<=>aA 
 	
 hhx &B--/F(?'@CPP
6{S(P'Q]`aahhx &B--/557FS[B&CD
 	
 +&,"335=I33-i[8NOP
 	
 C	N0022(B'CsSStxx/526D 'tf=JJll F)+&D*DDHH\*/a0,'/4'"*d
" 	D#--8 scB!+!! J*QC(8'9SIIJs   B<J K-KKKc                |   [        X5      nUc  / $ SSR                  [        5      -   S-   nU/nU(       a@  UR                  5       R	                  5       n	U	[
        ;  a  / $ US-  nUR                  U	5        U(       aB  UR                  5       R	                  5       n
US-  nUR                  U
U
 S3SU
 3SU
 S3/5        U(       a  US-  nUR                  U5        U(       a  US	-  nUR                  U5        US
-  nU R                  Xx5      R                  5       nU Vs/ s H  n[        U5      PM     sn$ s  snf )zILista documentos con filtros opcionales. Orden: created_at desc, id desc.r   r   z" FROM documentos WHERE obra_id = ?z AND categoria = ?z< AND (tags = ? OR tags LIKE ? OR tags LIKE ? OR tags LIKE ?)z,%z%,z/ AND (fecha_doc IS NOT NULL AND fecha_doc >= ?)z/ AND (fecha_doc IS NOT NULL AND fecha_doc <= ?)z" ORDER BY created_at DESC, id DESC)r&   r   r  r   r   r  r;   extendr   r   r   )r   r"   r  tagrz   r{   r   r  r  cat_normtag_normr   r   s                r   list_documentosr  _  sJ    t$G	 	DIIk** . 	  !	F??$**,22I##h
 99;$$&MMjO
O
"	
 	 @@e@@e//C<<$--/D%)*TLOT***s   !D9c                    [        X5      nUc  gU R                  SSR                  [        5      -   S-   [	        U5      U45      R                  5       nU(       a  [        U5      $ S$ )z;Devuelve el doc por id, solo si pertenece a la obra codigo.Nr   r   z- FROM documentos WHERE id = ? AND obra_id = ?)r&   r   r   r  r   r    r   )r   r"   doc_idr   r   s        r   r  r    sk     t$G
,,DIIk** .' 	'	Vg hj	 
 !$<--r   c                    [        XU5      nUc  SS/SS.$ U R                  S[        U5      45        U R                  5         SUS   SS	.$ )
zXElimina fila documentos. Devuelve `{ok, blob_path}` para que el caller borre el archivo.Fzdocumento no encontrador   r   z#DELETE FROM documentos WHERE id = ?Tr      )r   r   r   )r  r   r   r   )r   r"   r  r  s       r   delete_documentor    sV     f
-C
{(A'BcRRLL6VGKKMS%5EEr   )returnsqlite3.Connection)r   sqlite3.Rowr  zdict[str, Any])r   r  r  
list[dict])r   r  r"   r   r  Optional[dict])r   r  r"   r   r  zOptional[int])r   r  r"   r   r  r  )r   r  r"   r   r  r   )NN)
r   r  r"   r   rz   Optional[str]r{   r  r  r   )r>   r   r  zOptional[_date])F)rq   r   r   r   r  z	list[str]r   )r   r  rq   r   r   r  r  r   )
r   r  r"   r   r   r   r   r   r  r   )NNN)r   r  r"   r   rz   r  r{   r  r   r  r  r  )r   r  r"   r   r  r   r  r  )r   r  r"   r   rq   r   r  r   )
r   r  r"   r   rz   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  ztuple[bool, str])r   r  r*  r   r  zOptional[sqlite3.Row])r   r  r`  r   r  r  )r   r  rq   r   r  r   )r   r  r`  r   r   r   r  r   )r   r  r`  r   r  zset[int])ru  r  r`  r   r+  r   rv  zOptional[set[int]]r   zOptional[sqlite3.Connection]r  r  )
r   r  r`  r   r+  r   ry  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   r  r  )NNNN)r   r  r"   r   r  r  r  r  rz   r  r{   r  r  r  )r   r  r"   r   r  r   r  r  )r   r  r"   r   r  r   r  r   )Y__doc__
__future__r   r	   pathlibr   typingr   r   os_osenvironro   r   __file__parentr   r   r   r   r#   r&   r+   r.   r@   rC   rd   rt   r   r   r   re_rer  r   r   compiler   r   r   _OBRA_OPTIONAL_FIELDSr   r   r   r  r   r  r   r  r  r  secretsr  r  r  r  bcryptr  r  ImportErrorr  re  r  r$  r(  r4  rB  __annotations__r\  rc  rf  rh  rm  rq  rs  rw  rz  r  r  r  	frozensetr  r  r  r  r  r  r  r  r  r   r   <module>r     s  
 #     
s{{DN4I4IJ4V0WX
Y+	+.&
++$1p#GT]H< +/*.''37B+ +&  "[[+,
KK./	 
 iZ 37WO"/WO;?WO|2J
2J2J 2J 	2J
 
2Jp   +
++ + 	+
 + +>.
.. . 	.*6J
6J6J 6J 
	6Jx  	+
++ + 	+
 +>  6J
  KK56	T@ !"AaX\] !AbX]^ !BbX]^ "BcX]^ $d$]bc _ CL<6#JLJ(( *.)-DDD 
D '	D
 'D D"
 
 	
 
(P*8 $ %  
(+
+'*+47++UJ
UJUJ UJ 	UJ
 
UJv  $-+
-+-+ -+ 
	-+
 -+ -+ -+`.
.&).36..	F
	F&)	F36	F		Fu  GJs   I 
I+*I+