
    1$jh                       d dl Z d dlZd dlZd dlZd dlZd dlZd dlZd dlZd dlmZm	Z	 d dl
mZ d dlmZmZ d dlmZmZ d dlmZmZ d dlmZmZ d dlmZ  ee                                          j        Zed	z  d
z  Zedz  Zh dZ dZ!da" ej#                    Z$d Z%d Z&d Z'd Z(d[dZ)d Z*d\dZ+d Z,d Z-d Z.d Z/d Z0d Z1d Z2d]dZ3d  Z4d! Z5d" Z6d# Z7d$ Z8d[d%Z9d& Z:d' Z;d( Z<d) Z=d^d+Z>d, Z?d- Z@d. ZAd/ ZBd0 ZCd1 ZDd2 ZEd3 ZFd_d6ZGd7 ZHd8 ZId`d9ZJd: ZDd; ZKd< ZLd= ZMd> ZNd? ZOd@ ZPdA ZQdB ZRdC ZSdadEZTdF ZUd`dGZVe!fdHZWdI ZXdbdJZYdK ZZdL Z[dM Z\ G dN dOe          Z]dcdPZ^dcdQZ_dcdRZTd`dSZVe`dTk    r} eaejb                  dUk    r ecejb        dU                   ndVZd e'              e)d*W            e\              eedXed dY            edZedfe]          f                                 dS dS )d    N)datetimetimezone)Path)ThreadingHTTPServerSimpleHTTPRequestHandler)urlparseparse_qs)Requesturlopen)	HTTPErrorURLError)load_databaseDATABASEzGRAFO_D_DATABASE.sqlitezrender_excel_like.py>   folders
event_metafolder_metagraph_set_cards   Fc                  N    t          j        dt          j                              S )N%Y-%m-%dT%H:%M:%SZ)timestrftimegmtime     grafo_d_server.pynow_isor      s    =-t{}}===r   c                      t          j        t          d          } t           j        | _        |                     d           |                     d           |                     d           | S )N   timeoutzPRAGMA foreign_keys = ONzPRAGMA journal_mode = WALzPRAGMA busy_timeout = 5000)sqlite3connectDBRowrow_factoryexecutecons    r   
connect_dbr*   !   s]    
/"b
)
)
)CkCOKK*+++KK+,,,KK,---Jr   c                      t                      5 } |                     d           |                     d           t          |            |                                  ddd           dS # 1 swxY w Y   dS )zFIdempotent schema extension inside the same canonical SQLite database.z
            CREATE TABLE IF NOT EXISTS ui_state (
                key TEXT PRIMARY KEY,
                value_json TEXT NOT NULL,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL
            )
        aL  
            CREATE TABLE IF NOT EXISTS api_ingest_log (
                id INTEGER PRIMARY KEY,
                api_id TEXT NOT NULL,
                event_legacy_id TEXT NOT NULL,
                action TEXT NOT NULL,
                created_at TEXT NOT NULL,
                UNIQUE(api_id, event_legacy_id)
            )
        N)r*   r'   repair_malformed_api_eventscommitr(   s    r   ensure_runtime_schemar.   *   s    	   	 	 	 	 	 		 		 		 	$C(((

)                 s   AA**A.1A.c                  :    t                       t                      S N)r.   r   r   r   r   load_dbr1   C   s    ??r   c                 x    d }| r |             d S t          j        |d                                           d S )Nc                  
   t                                           sd S t          5  t          j        t
          j        t          t                     gt          t                    d           d d d            d S # 1 swxY w Y   d S )NF)cwdcheck)	RENDERexists_render_lock
subprocessrunsys
executablestrROOTr   r   r   _runzrender_frontend.<locals>._runI   s    }} 	F 	V 	VNCNCKK8c$iiuUUUU	V 	V 	V 	V 	V 	V 	V 	V 	V 	V 	V 	V 	V 	V 	V 	V 	V 	Vs   AA88A<?A<Ttargetdaemon)	threadingThreadstart)blockr?   s     r   render_frontendrG   H   sU    V V V
  ;T22288:::::r   c                    | dS t          |                                           }|sdS 	 t          |          }n# t          $ r Y dS w xY w|dk    r|dz  }|dk     s|dk    rdS t	          j        |t          j                  }|                    d          |                    d          |j	        |j
        |j        fS )	z^Return (iso_datetime, yyyy-mm-dd, year, month, day) for valid Unix epoch seconds/milliseconds.Nl    d(	 g     @@iCm8l    Wi )tzr   z%Y-%m-%d)r=   stripfloat	Exceptionr   fromtimestampr   utcr   yearmonthday)valuerawnumberdts       r   epoch_to_utc_partsrV   T   s    }t
e**



C ts   tt &	Vj00t		8<	8	8	8B
())
J


 s   ; 
A	A	 c                     |D ]E}t          | t                    r.|                     |          dvr|                     |          c S F|S )N)NrW   )
isinstancedictget)mappingkeysdefaultkeys       r   first_presentr`   n   sW     $ $gt$$ 	$S)9)9)K)K;;s#####Nr   c                 <   t          | t                    sdS | g}dD ]A}|                     |          }t          |t                    r|                    |           Bd}d}|D ]H}t	          ||d          }t	          ||d          }|"| t          |          t          |          fc S I|                     d          }	t          |	t          t          f          r=t          |	          dk    r*t          |	d                   t          |	d	                   fS dS )
zIExtract latitude/longitude from common API-center and ISS payload shapes.NN)iss_positionpositionlocationcoordinatesgeo)latitudelat)	longitudelonlnglongNrf         r   )	rY   rZ   r[   appendr`   r=   listtuplelen)
item
candidatesr_   vallat_keyslon_keyssrcri   rk   coordss
             r   extract_lat_lonr{   u   s"   dD!! zJM # #hhsmmc4   	#c""""H2H & &C400C400?ss88SXX%%%%XXm$$F&4-(( .S[[A-=-=6!9~~s6!9~~--:r   c                 4   t          | dd          }|rt          |          S t          | t                    r|                     d          nd }t          |t                    r(|                                r|                                S |
|d| d| S dS )N)placelocation_namecitycountryregionrW   re   zLow Earth Orbit / lat z, lon )r`   r=   rY   rZ   r[   rJ   )rt   ri   rk   explicitlocs        r   build_placer      s    T#Z\^__H 8}}",T4"8"8
B$((:


dC#s 		 yy{{
3?88838882r   c                     t          |                     d          p|                     d          pd                                          }d|v pt          |t                    od|v S )NnameidrW   ISSrc   )r=   r[   upperrY   rZ   )api_rowrt   api_names      r   is_iss_payloadr      sa    7;;v&&A'++d*;*;ArBBHHJJHHSD$!7!7!RNd<RSr   c                 0    |                      d           dS )z
    Remove malformed generic ISS rows produced by the earlier API mapper.
    They are identifiable by an epoch value parsed as a year plus empty place.
    Valid historical ISS_POS_* rows are not touched.
    af  
        DELETE FROM events
        WHERE COALESCE(place, '') = ''
          AND COALESCE(date_year, 0) > 9999
          AND (
              UPPER(COALESCE(event_name, '')) LIKE '%ISS-LIVE-LOCATION%'
              OR UPPER(COALESCE(api_text, '')) LIKE '%ISS-LIVE-LOCATION%'
              OR UPPER(COALESCE(who, '')) = 'ISS-LIVE-LOCATION'
          )
        N)r'   r(   s    r   r,   r,      s)     KK		    r   c           
      2   t          |                     d          pU|                     d          p@|                     d          p+|                     d          p|                     d          pd                                          }dd l}t	          |          }|r;|\  }}}}}|                     d          p|| d<   || d<   || d<   || d	<   || d
<   |||fS |                    d|          }	|	rt          |	                    d                    }|	                    d          "t          |	                    d                    nd}|	                    d          "t          |	                    d                    nd}nd |                    d|          D             }
|
sKd |                    dt          |                     dd                              D             }|d d         }
t          |
          dk    r|
d         nd }t          |
          dk    r|
d         nd}t          |
          dk    r|
d         nd}|F|| d<   || d	<   || d
<   |                     d          s"|dk    r|dd|dd|dn| d|dd|d| d<   |||fS )Ndate	date_text	timestampr   rO   rW   r   r   rP   rQ   z+^(-?\d{1,6})(?:-(\d{1,2}))?(?:-(\d{1,2}))?$ro   rn      c                 ,    g | ]}t          |          S r   int.0xs     r   
<listcomp>z$parse_event_date.<locals>.<listcomp>   s    :::1A:::r   z-?\d+c                 ,    g | ]}t          |          S r   r   r   s     r   r   z$parse_event_date.<locals>.<listcomp>   s    ZZZAQZZZr   r   04d-02d)
r=   r[   rJ   rerV   matchr   groupfindallrs   )eventrS   r   epochiso_datetimer   rO   rP   rQ   mnums	name_numss               r   parse_event_dater      s   
eii  D599[#9#9  DUYY{=S=S  DW\W`W`agWhWh  Dlqluluv|l}l}  D  BD  E  E  K  K  M  MCIIIs##E  491iuc!IIj11A\j!ffgeUC
?EEA /1771::#$771::#9AGGAJJq!"!7c!''!**oooQ::

8S 9 9::: 	!ZZHc%))FTVBWBW>X>X)Y)YZZZIRaR=Dd))q..tAwwdt99>>QqTad1ggQfgeyy   	qCG!88t???%???c????TXQpQp[`QpQpQpgjQpQpQpE&Mr   c                    t          | pi           }|                    d|                    d          p4|                    d          pt          t	          j                                         |                    d|                    d          p+|                    d          p|                    d          pd           |                    d	|                    d
          p+|                    d          p|                    d          pd           t          |           |                    d|                    d          pd           |                    d|                    d          p|d                    t          |d                   |d<   t          |d                   |d<   t                      |d<   |                    d|d                    |S )Nr   event_id	legacy_idr   titlelabel
event_nameUntitled eventrowgraphgraph_idrow_keyr^   	start_colrE   ro   end_colend
updated_at
created_at)	rZ   
setdefaultr[   r=   uuiduuid4r   r   r   )payloadr   s     r   normalize_eventr      s   BE	T599Z00_EIIk4J4J_cRVR\R^R^N_N_```	VUYYw//t599W3E3EtS_I`I`tdtuuu	UEIIg..l%))J2G2Gl599U^K_K_lclmmmU	[%))G"4"4"9:::	Y		% 0 0 FE+4FGGGU;/00E+5+,,E)!))E,	\5#6777Lr   c                 B   t          |pd                                          pd}t                      }|                     d|f                                          }|rt          |d                   S |                     d|||f          }t          |j                  S )NUncategorized%SELECT id FROM folders WHERE name = ?r   zNINSERT INTO folders(name, is_open, created_at, updated_at) VALUES (?, 1, ?, ?))r=   rJ   r   r'   fetchoner   	lastrowid)r)   r   folder_nametsr   curs         r   get_or_create_folderr      s    d-o..4466I/K	B
++=~
N
N
W
W
Y
YC
 3t9~~
++X	b" C s}r   r   c                    t          |pd                                          pd}t                      }|                     d|f                                          }|rt          |d                   S t          | |          }|                     d|t          |p|          |||f          }t          |j                  S )Nr^   'SELECT id FROM graphs WHERE row_key = ?r   z
        INSERT INTO graphs(row_key, visible_name, folder_id, color, created_at, updated_at)
        VALUES (?, ?, ?, NULL, ?, ?)
        )r=   rJ   r   r'   r   r   r   r   )	r)   r   visible_namer   r_   r   r   	folder_idr   s	            r   get_or_create_graphr      s    
g"
#
#
)
)
+
+
8yC	B
++?#
H
H
Q
Q
S
SC
 3t9~~$S+66I
++	 
c,%#&&	2r: C s}r   c                     |                      d|f                                          }t          |d         pd          S )NzGSELECT COALESCE(MAX(sort_index), 0) AS n FROM events WHERE graph_id = ?nr   )r'   r   r   )r)   r   r   s      r   max_sort_indexr   	  s<    
++_bjal
m
m
v
v
x
xCs3x}1r   c                    t          |                    d          p+|                    d          p|                    d          pd          }t          | |||                    d          p|                    d          pd          }t          |          \  }}}|                    d          p|                    d	          }|s-|+|d
dt	          |pd          ddt	          |pd          d}t	          |                    d          p'|                    d          pt          | |          dz             }t	          |                    d          p|                    d          p|          }	t	          |                    d          pt          d|	|z
  dz                       }
t	          |                    d          p|          }t                      }i dt          |                    d          p'|                    d          pt          j	                              d|d|dt          |                    d          p|                    d          pd          d	t          |pd          d|d|d|d|d|	d|
dt          |                    d          p|                    d          pd          d t          |                    d           p+|                    d!          p|                    d"          pd          d#t          |                    d#          p@|                    d$          p+|                    d%          p|                    d&          pd          d't          |                    d'          p|                    d(          pd          d)t          |                    d*          p|                    d)          pd          d|d+|iS ),Nr   r   r   r^   r   folderr   r   r   r   r   ro   r   r   rE   r   r   duration
sort_indexr   r   r   r   r   r   rW   	date_year
date_monthdate_dayr}   PLACEwhoWHOactorbrief_descriptionbriefBRIEFdescriptionsource_linkurlapi_textapir   )
r=   r[   r   r   r   r   maxr   r   r   )r)   r   r   r   rO   rP   rQ   r   r   r   r   r   r   s                r   event_to_sql_valuesr     s&   %))E""]eii	&:&:]eii>P>P]T]^^G"3%))G:L:L:vPUPYPYZbPcPc:vgvwwH'..D%		&!!;UYY{%;%;I L)KKK#ejq//KKKCH1KKK	EIIk**eeii.@.@eNSVX`DaDadeDeffI%))I&&G%))E*:*:GiHHG599Z((KC7Y3F3J,K,KLLHUYY|,,9	::J	BS4REIIk,B,BRdjllSSH 	7 	c%))F++Zuyy/F/FZJZ[[	
 	Sb)) 	T 	e 	C 	Y 	7 	H 	UYYw''C599W+=+=CDD 	s599U##Suyy'7'7S599W;M;MSQSTT 	SII)** yy!!yy!! yy'' 
 
* 	s599]33Muyy7G7GM2NN+, 	C		%((GEIIj,A,AGRHH-. 	j/0 	b1  r   c                   	 ddl }ddlm t          | pd                                          }|st          d          |                                                                }|                    dd          }|                    dd|          }i d	d
dd
dd
dd
ddddddddddddddddddddddddddi d dd!dd"d#d$d#d%d#d&d'd(d'd)d'd*d'd+d,d-d,d.d,d/d,d0d1d2d1d3d1d4d5d5d5d6d6d6d6d7}d8 d9 	dC	fd:	}|	                    d;|          }|r ||
                    d
                    S |	                    d<|          }|r2 ||
                    d
          |
                    d                    S |	                    d=|          }|rF ||
                    d
          |
                    d          |
                    d                    S |	                    d>|          }|rF ||
                    d          |
                    d          |
                    d
                    S |	                    d?|          }|rO|
                    d
          |v r8 ||
                    d          ||
                    d
                             S |	                    d@|          }|rc|
                    d          |v rL ||
                    d          ||
                    d                   |
                    d
                    S |	                    dA|          }|rc|
                    d
          |v rL ||
                    d          ||
                    d
                   |
                    d                    S t          dB|z             )Da.  
    Deterministic smart date parser for manual UI/API events.

    Accepted:
    - -600
    - -600-01
    - -600-01-01
    - 1989
    - 1989-01
    - 1989-01-01
    - 01/01/1989
    - 1/1/1989
    - 01-01-1989
    - 1989/01/01
    - June 1989
    - 4 June 1989
    - Junio 1989
    - 4 Junio 1989
    r   N)r   rW   zdate is required, z\s+janro   januaryeneenerofebrn   februaryfebreromarr   marchmarzoapr   aprilabrabrilmay   mayojun   junejuniojul   julyjulioaug   augustagoagostosep	   sept	september
septiembreoct
   octoberoctubrenov      )november	noviembredecdecemberdic	diciembrec                 R    t          |           } | dk    r| dnt          |           S )Nr   r   )r   r=   ys    r   format_yearz%smart_parse_date.<locals>.format_yeare  s)    FFUU!zzzA.r   c                 V    t          |           } | dz  dk    o| dz  dk    p| dz  dk    S )Nr   r   d     r   r  s    r   is_leap_yearz&smart_parse_date.<locals>.is_leap_yeari  s5    FFA
>SA!=SA>r   c                    t          |           } | dk    s| dk     s| dk    rt          d          |t          |           | d d fS t          |          }|dk     s|dk    rt          d          | |            d|d	| |d fS t          |          }| dk    r | ||           n?d
 |           rdndd
dd
dd
d
dd
dd
g}|dk     s|||dz
           k    rt          d           |            d|d	d|d	| ||fS )Nr   ii?B zyear out of rangero   r  zmonth out of ranger   r            r   zday out of range)r   
ValueErrorr=   )r  r   d
month_daysr   r  r  s       r   r5   zsmart_parse_date.<locals>.checkm  sa   FF66Q[[AJJ01119q661dD((FFq55AFF12229!k!nn..q...1d::FFq55DAqMMMMLLOO;bbRRRQSUWY[]_acdJ1uuJq1u--- !3444+a..2212221222Aq!;;r   z(-?\d{1,6})z(-?\d{1,6})[-/](\d{1,2})z%(-?\d{1,6})[-/](\d{1,2})[-/](\d{1,2})z%(\d{1,2})[-/](\d{1,2})[-/](-?\d{1,6})u"   ([a-záéíóúñ]+)\s+(-?\d{1,6})u.   (\d{1,2})\s+([a-záéíóúñ]+)\s+(-?\d{1,6})u.   ([a-záéíóúñ]+)\s+(\d{1,2})\s+(-?\d{1,6})zunsupported date format: rb   )r   r   r   r=   rJ   r$  lowerreplacesub	fullmatchr   )
rR   r   rS   txtmonthsr5   r   r   r  r  s
          @@@r   smart_parse_dater-  7  s   ( III
ekr


 
 
"
"C -+,,,
))++



C
++c3

C
&&c
"
"CqQ %q*11qa!*A 	q 1 &q 	q	 1	 $Q	 )0	
 	q
 ! 	q  ! %a 	q ! %a 	q A  %a *21 	q ! )! .:1 	r b #,R 	r   "r"2  F/ / /? ? ?< < < < < < < <2 	^S))A !uQWWQZZ   
0#66A -uQWWQZZ,,,
=sCCA 9uQWWQZZQWWQZZ888
=sCCA 9uQWWQZZQWWQZZ888
:C@@A 5QWWQZZ6!!uQWWQZZ

!3444
FLLA AQWWQZZ6!!uQWWQZZ

!3QWWQZZ@@@
FLLA AQWWQZZ6!!uQWWQZZ

!3QWWQZZ@@@
036
7
77r   c                    t                       t          | pi           }t          |                    d          p@|                    d          p+|                    d          p|                    d          pd                                          }|st          d          t          |                    d          p+|                    d          p|                    d	          pd                                          }|st          d
          t          |                    d          p|                    d          pd                                          }t          |          \  }}}}t          |                    d          p|                    d          pd                                          }	t          |                    d          p|                    d          pd                                          }
t          |                    d          p@|                    d          p+|                    d          p|                    d          pd                                          }t          |                    d          p|                    d          pd                                          }t          |                    d          p|                    d          pd                                          }t                      }t                      5 }|	                    d           |	                    d|f          
                                }|rt          |d                   }nt          |||d          }|	                    d|f          
                                }t          |d         pd           }| d!| d!| d!| }|	                    d"||||||||d d d |	|
||||||f          }|                                 d#d#d#           n# 1 swxY w Y   d$|j        ||||||d%S )&z
    Insert one manually-created event into canonical SQLite using smart date parsing.
    This is used by POST /api/events.
    r   r   r   	graph_rowrW   zrow_key/row/graph is requiredr   r   r   zevent name is requiredr   r   r}   r   r   r   r   r   r   r   r   r   r   sourcez	manual-uiBEGIN IMMEDIATEz5SELECT id, visible_name FROM graphs WHERE row_key = ?r   r   zSSELECT COALESCE(MAX(sort_index), 0) + 1 AS next_sort FROM events WHERE graph_id = ?	next_sortro   :a  
            INSERT INTO events(
                legacy_id, graph_id, row_key, event_name,
                date_text, date_year, date_month, date_day,
                start_col, end_col, duration,
                place, who, brief_description, source_link, api_text,
                sort_index, created_at, updated_at
            )
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            NT)okr   r   r   r   r   r   r   )r.   rZ   r=   r[   rJ   r$  r-  r   r*   r'   r   r   r   r-   r   )r   datar   r   raw_dater   r   r   r   r}   r   r   r   r   r   r)   r   r   sort_rowr   r   r   s                         r   insert_manual_event_sqlr8    s   
 2D 	88E??	88G	 88K  	   egg   :8999 	88F	88G	 	 
 egg   31222488F##Btxx'<'<BCCIIKKH1A(1K1K.Iy*h!!<TXXg%6%6<"==CCEEE
dhhuoo6%6B
7
7
=
=
?
?C$%% 	88G	88G	 88M""	   egg  dhh}--F%FBGGMMOOK488J''L488H+=+=LMMSSUUH	B	 7%&&&CJ
 
 (** 	
  	S5;''HH*3/RRH;;aK
 
 (** 	 +.3!44
FFFFiFF*FF	kk	 !' 
  
D 	

o7 7 7 7 7 7 7 7 7 7 7 7 7 7 7t m  	 	 	s   3C3P22P69P6c                 	   t                       t          | pi           }t          |                    d          p|                    d          pd                                          }t          |                    d          p+|                    d          p|                    d          pd                                          }t          |                    d          p+|                    d          p|                    d	          pd                                          }t          |                    d
          p|                    d          pd                                          }d}d}d}d}	|rt          |          \  }}}}	t          |                    d          pd                                          }
t          |                    d          pd                                          }t          |                    d          p|                    d          pd                                          }t          |                    d          p|                    d          pd                                          }t          |                    d          p|                    d          pd                                          }t                      }t                      5 }|                    d           d}|r)|                    d|f          	                                }|1|r/|r-|r+|                    d|||f          	                                }|.|r,|r*|                    d||f          	                                }|t          d          t          |d                   }|                    d|f          	                                }|p|d         }||n|d         }||n|d         }||n|d         }||	n|d         }|                    d||||||
||||p|d         ||f           |                    d|f          	                                }|                                 ddd           n# 1 swxY w Y   	 t          d d!d"#           n# t          $ r Y nw xY wd$t          |          d%S )&a|  
    Update canonical event fields from the right-side technical card.

    Matching policy:
    1. legacy_id/id if present.
    2. row_key + event_name + date_text if legacy id is unavailable.
    3. row_key + event_name fallback.

    This updates SQLite canonical columns:
    event_name, date_text/year/month/day, place, who,
    brief_description, source_link, api_text.
    r   r   rW   r   r   r   r   r   r   r   r   Nr}   r   r   r   r   r   r   r   r1  zTSELECT id, legacy_id, row_key, event_name, date_text FROM events WHERE legacy_id = ?z
                SELECT id, legacy_id, row_key, event_name, date_text
                FROM events
                WHERE row_key = ? AND event_name = ? AND date_text = ?
                ORDER BY id DESC
                LIMIT 1
                z
                SELECT id, legacy_id, row_key, event_name, date_text
                FROM events
                WHERE row_key = ? AND event_name = ?
                ORDER BY id DESC
                LIMIT 1
                z$event not found for canonical updatez
            SELECT event_name, date_text, date_year, date_month, date_day,
                   place, who, brief_description, source_link, api_text
            FROM events
            WHERE id = ?
            r   r   r   a  
            UPDATE events
            SET event_name = ?,
                date_text = ?,
                date_year = ?,
                date_month = ?,
                date_day = ?,
                place = ?,
                who = ?,
                brief_description = ?,
                source_link = ?,
                api_text = ?,
                updated_at = ?
            WHERE id = ?
            z
            SELECT id, legacy_id, row_key, event_name, date_text,
                   date_year, date_month, date_day,
                   place, who, brief_description, source_link, api_text
            FROM events
            WHERE id = ?
            zevent-card-saver   r4  dirtystatusTr4  r   )r.   rZ   r=   r[   rJ   r-  r   r*   r'   r   r$  r   r-   record_persistence_auditrL   )r   r5  r   r   r   r6  r   r   r   r   r}   r   r   r   r   r   r)   r   r   currentnew_event_namenew_date_textnew_date_yearnew_date_monthnew_date_daysaveds                             r   update_event_card_sqlrF    sL    2DDHHTNNAdhh{&;&;ArBBHHJJI$((9%%S%SDHHW<M<MSQSTTZZ\\GTXXl++Ztxx/?/?Z488GCTCTZXZ[[aaccJ488F##Btxx'<'<BCCIIKKHIIJH P5Eh5O5O2	9j(!!'R((..00E
dhhuoo#
$
$
*
*
,
,C$%% 	88M""	  egg	 
 dhh}--F%FBGGMMOOK488J''@488E??@bAAGGIIH	B	 c%&&& 	++f  hjj 
 ;7;z;i;++ *i0	 	 hjj  ;7;z;++ *%	 	 hjj  ;CDDDs4y>>++ K
 
 (** 	 $<w|'<%.%:		@T%.%:		@T'0'<',BW#,#8xxgj>Q  !/GJ/!	
 	
 	
@  K	
 	
 (** 	 	

Gc c c c c c c c c c c c c c cJ !2!DIIIII    e  s%   =FRR#&R#+R> >
S
Sc                 b   t                       t          |           }t                      5 }|                    d           t	          ||          }|                    d|d         f                                          }|r%|s#|                                 t          d          |r|                    d|           d}n>|                    d          pt                      |d<   |                    d|           d	}|
                                 d d d            n# 1 swxY w Y   ||d
S )Nr1  z)SELECT id FROM events WHERE legacy_id = ?r   zduplicate event ida?  
                UPDATE events
                SET graph_id=:graph_id, row_key=:row_key, event_name=:event_name,
                    date_text=:date_text, date_year=:date_year, date_month=:date_month,
                    date_day=:date_day, start_col=:start_col, end_col=:end_col,
                    duration=:duration, place=:place, who=:who,
                    brief_description=:brief_description, source_link=:source_link,
                    api_text=:api_text, sort_index=:sort_index, updated_at=:updated_at
                WHERE legacy_id=:legacy_id
                updatedr   a  
                INSERT INTO events(
                    legacy_id, graph_id, row_key, event_name, date_text, date_year,
                    date_month, date_day, start_col, end_col, duration, place, who,
                    brief_description, source_link, api_text, sort_index, created_at, updated_at
                )
                VALUES(
                    :legacy_id, :graph_id, :row_key, :event_name, :date_text, :date_year,
                    :date_month, :date_day, :start_col, :end_col, :duration, :place, :who,
                    :brief_description, :source_link, :api_text, :sort_index, :created_at, :updated_at
                )
                inserted)actionr   )r.   r   r*   r'   r   r   rollbackr$  r[   r   r-   )r   allow_upsertnormr)   valuesexistingrJ  s          r   insert_event_sqlrP    s   5!!D	 *%&&&$S$//;;7&:M9O
 

(** 	  	3L 	3LLNNN1222  	 KK	    FF#'88L#9#9#FWYYF< KK     F

U* * * * * * * * * * * * * * *V t,,,s   C)D!!D%(D%c                    t                       t                      5 }|                    d           |                    dt          |           t          |           f                                          }|s"|                                 	 d d d            d S i d|d         pt          |d                   d|d         d|d         d	|d
         pdd|d         d|d         d|d         d|d         d|d         d|d         d|d         d|d         d|d         d|d         d|d         d|d         }|                    |pi            t          |t          |                    }|d         |d<   |                    d|           |	                                 d d d            n# 1 swxY w Y   |S )Nr1  z@SELECT * FROM events WHERE legacy_id = ? OR CAST(id AS TEXT) = ?r   r   r   r   r   r   r   r   rW   rO   r   rP   r   rQ   r   r   r   r   r}   r   r   r   r   r   r   db_ida  
            UPDATE events
            SET graph_id=:graph_id, row_key=:row_key, event_name=:event_name,
                date_text=:date_text, date_year=:date_year, date_month=:date_month,
                date_day=:date_day, start_col=:start_col, end_col=:end_col,
                duration=:duration, place=:place, who=:who,
                brief_description=:brief_description, source_link=:source_link,
                api_text=:api_text, sort_index=:sort_index, updated_at=:updated_at
            WHERE id=:db_id
            )
r.   r*   r'   r=   r   rK  updater   r   r-   )r   patchr)   r   r?  rN  s         r   update_event_sqlrU    s   	 ,%&&&kkN]]CMM*
 
 (** 	  	LLNNN, , , , , , , ,
#k"4c#d)nn
3y>
 C%
 C$*	

 C$
 S&
 3z?
 [)
 s9~
 J
 S\
 3u:
 323
 3}-
 J
  #l+!
$ 	u{###$S/'*B*BCCd)w	 	
 	
 	
 	

Y, , , , , , , , , , , , , , ,Z Ns   A1F9DF99F= F=c                 :   t                       t                      5 }|                    d           |                    dt          |           t          |           f          }|                                 |j        dk    cd d d            S # 1 swxY w Y   d S )Nr1  z>DELETE FROM events WHERE legacy_id = ? OR CAST(id AS TEXT) = ?r   )r.   r*   r'   r=   r-   rowcount)r   r)   r   s      r   delete_event_sqlrX    s    	  %&&&kkL]]CMM*
 
 	

|a                                   s   A&BBBc           	        
 |sd S d |D             
|                      d                                          }|D ]}|d         }	 t          j        |d                   }n# t          $ r Y 3w xY wd}|dk    rt          |t                    rdD ]Q}t          |                    |          t                    r'
fd||         D             }|||         k    r|||<   d	}Rt          |                    d
          t                    rFt          |d
         	                                          D ]}	t          |	          
v r|d
         |	= d	}|dv rYt          |t                    rDt          |	                                          D ]"t          fd
D                       r|= d	}#|r:|                      dt          j        |dd	          t                      |f           d S )Nc                 ,    h | ]}t          |          S r   r=   r   s     r   	<setcomp>z6cleanup_ui_state_after_graph_delete.<locals>.<setcomp>)  s    ,,,!s1vv,,,r   z$SELECT key, value_json FROM ui_stater_   
value_jsonFr   )deletedRows
deleteRowsc                 6    g | ]}t          |          v|S r   r[  )r   r   deleteds     r   r   z7cleanup_ui_state_after_graph_delete.<locals>.<listcomp>8  s)    MMMas1vvW7L7L7L7L7Lr   TgraphFolder)r   r   r   c              3      K   | ]<}t                                        |d z             pt                    |k    V  =dS )r3  N)r=   
startswith)r   r%  meta_keys     r   	<genexpr>z6cleanup_ui_state_after_graph_delete.<locals>.<genexpr>E  sL      ``STs8}}//C88NCMMQ<N``````r   z@UPDATE ui_state SET value_json = ?, updated_at = ? WHERE key = ?ensure_ascii	sort_keys)r'   fetchalljsonloadsrL   rY   rZ   r[   rq   r]   r=   anydumpsr   )r)   deleted_rowsrowsr   r_   rR   changedknew_list	graph_keyra  re  s             @@r   #cleanup_ui_state_after_graph_deleteru  %  sA    ,,|,,,G;;=>>GGIID ! !%j	Js<011EE 	 	 	H	 )
5$ 7 72 ' 'eiillD11 'MMMM58MMMH58++#+a"&%))M22D99 '!%eM&:&?&?&A&A!B!B ' 'I9~~00!-0;"&BBBzRWY]G^G^B .. # #````X_````` #h"G 	KKREFFF		SVW  =! !s   A##
A0/A0c           	         t                       ddlm} g }| pg D ]I} |t          |pd                                                    }|r||vr|                    |           Jg }g }t                      }t                      5 }|                    d           |D ]}|dk    r|                    |dd           !|                    d|f          	                                }	|	s|                    |d	d           e|                    d
|	d         f          	                                d         }
|
r|                    |d|
d           |                    d|	d         f           |                    |           |                    d          	                                }	|	r.	 t          j        |	d                   }n# t          $ r i }Y nw xY wi }t          |t                    rt          |          fd|                    dg           D             |d<   |                    di           }t          |t                    r0t#          |                                          D ]	}|v r||= 
||d<   |                    di           }t          |t                    r5t#          |                                          D ]\  }}|v rd||<   ||d<   |                    dt          j        |dd          ||f           |                                 ddd           n# 1 swxY w Y   	 t-          ddd           n# t          $ r Y nw xY wd|||dS )z
    Physically delete explicitly selected empty folders from SQLite.

    Rules:
    - Only requested folders are evaluated.
    - Uncategorized is protected.
    - Non-empty folders are rejected.
    - ui_state is cleaned only for deleted folders.
    r   unquoterW   r1  r   	protected)r   reasonz+SELECT id, name FROM folders WHERE name = ?z	not foundz/SELECT COUNT(*) FROM graphs WHERE folder_id = ?r   z	not empty)r   rz  graphsz DELETE FROM folders WHERE id = ?3SELECT value_json FROM ui_state WHERE key='folders'r]  c                 6    g | ]}t          |          v|S r   r[  )r   fdeleted_sets     r   r   z,delete_empty_folders_sql.<locals>.<listcomp>  s4          q66,, ,,,r   r   openrb  a!  
                INSERT INTO ui_state(key, value_json, created_at, updated_at)
                VALUES ('folders', ?, ?, ?)
                ON CONFLICT(key) DO UPDATE SET
                    value_json=excluded.value_json,
                    updated_at=excluded.updated_at
                FTrg  Nzdelete-empty-folderr4  r:  )r4  	requestedra  skipped)r.   urllib.parserx  r=   rJ   rp   r   r*   r'   r   rk  rl  rL   rY   rZ   setr[   rq   r]   itemsrn  r-   r>  )folder_namesrx  namesr   r   ra  r  r   r)   r   graph_countstate
open_stater~  graph_folderr   r   r  s                    @r   delete_empty_folders_sqlr  Q  sx    $$$$$$ER  ws17||$$**,, 	D%%LLGG	B	 N%&&& 	! 	!D&&$+FFGGG++=  hjj 
  $+FFGGG++AT  hjjK
  "))      
 KK:SYLIIINN4    kkA
 

(** 	  	
3|#455    EeT"" 	g,,K        99Y33     E)
 62..J*d++ +joo//00 * *AK''&qM *f 99]B77L,-- 4'+L,>,>,@,@'A'A @ @OGV,,0?W-'3m$KK EFFFBO	 	 	 	

]N N N N N N N N N N N N N N N` !6aMMMMM    	  sJ   D*L31GL3GL3GEL33L7:L7?M 
MMTc                 0   t                       t          | t                    r| g} d | pg D             } | s	dddddg dS d}d}d}d}g }t                      5 }|                    d           | D ]%}|                    d|f                                          }	|	s/t          |	d                   }
|                    d||f                                          }d	 |D             }|D ]#}|                    d
|f          }||j        z  }$|D ]#}|                    d|f          }||j        z  }$|                    d|
|f          }||j        z  }|                    d|
f          }||j        z  }|j        r|	                    |           't          ||           |                                 d d d            n# 1 swxY w Y   |r;t                      5 }|                    d           d d d            n# 1 swxY w Y   d|||||dS )Nc                     g | ]D}t          |                                          #t          |                                          ES r   )r=   rJ   r   s     r   r   z%delete_graphs_sql.<locals>.<listcomp>  s9    KKK1CFFLLNNKAKKKr   Tr   )r4  deleted_graphsdeleted_eventsdeleted_apisdeleted_api_logsrp  r1  z0SELECT id, row_key FROM graphs WHERE row_key = ?r   z>SELECT id FROM api_registry WHERE target_graph = ? OR name = ?c                 8    g | ]}t          |d                    S )r   r[  r   rs     r   r   z%delete_graphs_sql.<locals>.<listcomp>  s"    666s1T7||666r   +DELETE FROM api_ingest_log WHERE api_id = ?%DELETE FROM api_registry WHERE id = ?z4DELETE FROM events WHERE graph_id = ? OR row_key = ?zDELETE FROM graphs WHERE id = ?VACUUM)r.   rY   r=   r*   r'   r   r   rj  rW  rp   ru  r-   )row_keysvacuumr  r  r  r  physically_deleted_rowsr)   r   r   r   api_rowsapi_idsapi_idr   s                  r   delete_graphs_sqlr    s/   (C   :KKRKKKH 
 !
 
 	
 NNL 	 3%&&& -	8 -	8GKKB
  hjj 
  5;''H{{P'"  hjj 
 76X666G! 1 1kkAI  !CL0  ! - -kk;I  ,++F7# C cl*N++1 C cl*N| 8'..w777+C1HIII

g3 3 3 3 3 3 3 3 3 3 3 3 3 3 3j  "\\ 	"SKK!!!	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" (($,'  s%   E#GGG"HHHc                 d   t          | pd                                          }|sd}g }d}|                                D ]G}|                                r|                    |           d}.|s|                    d           d}Hd                    |                              d          pdS )NrW   z	NEW GRAPHF_T	NEW_GRAPH)r=   rJ   r   isalnumrp   join)r   rS   outprev_uschs        r   slugify_graph_keyr     s    
djb//


!
!C 
CGiikk  ::<< 	JJrNNNGG 

3GGCLLs##2{3r   c           	      *   t                       t          | pi           }t          |                    d          p|                    d          pd                                          }|st          d          t                      }t                      5 }|                    d           |                    d|||f           |                    d||f           |                    d          	                                }|r.	 t          j        |d	                   }n# t          $ r i }Y nw xY wi }t          |t                    si }|                    d
g            |                    di            |                    di            |                    dg            |                    dg            d|d
         vr|d
                             dd           ||d
         vr|d
                             |           d|d         |<   |                    dt          j        |dd          ||f           |                                 ddd           n# 1 swxY w Y   	 t'          ddd           n# t          $ r Y nw xY wd|dS )z
    Create a folder physically in SQLite.

    Canonical policy:
    - folders table is the source of truth.
    - ui_state is synchronized for UI open/visibility state.
    - no graph and no event is created.
    r   r   rW   zfolder name is requiredr1  z~
            INSERT OR IGNORE INTO folders(name, is_open, created_at, updated_at)
            VALUES (?, 1, ?, ?)
            z
            UPDATE folders
            SET is_open = 1,
                updated_at = ?
            WHERE name = ?
            r|  r]  r   rb  r  r^  r_  r   r   T	  
            INSERT INTO ui_state(key, value_json, created_at, updated_at)
            VALUES ('folders', ?, ?, ?)
            ON CONFLICT(key) DO UPDATE SET
                value_json=excluded.value_json,
                updated_at=excluded.updated_at
            Frg  Nzcreate-folderr4  r:  )r4  r   )r.   rZ   r=   r[   rJ   r$  r   r*   r'   r   rk  rl  rL   rY   r   insertrp   rn  r-   r>  )r   r5  r   r   r)   r   r  s          r   create_folder_sqlr  7  s(    2Dtxx;488H#5#5;<<BBDDD 42333	B	 =%&&& 2rN	
 	
 	
 	 J	
 	
 	
 kkA
 

(** 	  	
3|#455    E%&& 	EB'''+++$$$+++r***%	"222)##A777uY''')##D)))"fd ZETBBBBK		
 		
 		
 	

{= = = = = = = = = = = = = = =~ $GGGGG    $'''sJ   A0I!	D$#I!$D30I!2D33D"I!!I%(I%-J   
JJc                     t                       t                      } t                      5 }|                    d                                          }|sdddcddd           S 	 t          j        |d                   }n"# t          $ r dddd	cY cddd           S w xY wt          |t                    sddd
d	cddd           S |
                    dg           }t          |t                    sdddd	cddd           S d}|                    d           |D ]}t          |pd                                          }|s(|                    d|f                                          }|                    d|| | f           |                    d| |f           ||dz  }|                                 ddd           n# 1 swxY w Y   d|dS )z
    Convert folders present in ui_state into canonical rows in folders.

    This makes Save meaningful for folders that were created by older UI code
    before POST /api/folders existed.
    r|  Tr   )r4  syncedNr]  Fzinvalid ui_state folders json)r4  r  errorzui_state folders is not objectr   zui_state folders list missingr1  rW   r   z
                INSERT OR IGNORE INTO folders(name, is_open, created_at, updated_at)
                VALUES (?, 1, ?, ?)
                z=UPDATE folders SET is_open = 1, updated_at = ? WHERE name = ?ro   )r.   r   r*   r'   r   rk  rl  rL   rY   rZ   r[   rq   r=   rJ   r-   )r   r)   r   r  r   r  r   befores           r   sync_ui_folders_to_sqlr    s    	B	 1kkA
 

(** 	  	-!,,1 1 1 1 1 1 1 1	XJs<011EE 	X 	X 	X17VWWWW1 1 1 1 1 1 1 1	X %&& 	Y17WXX1 1 1 1 1 1 1 1  ))Ir**'4(( 	X17VWW%1 1 1 1 1 1 1 1( %&&& 	 	Dtzr??((**D [[7  hjj 
 KK r2   KKOT
  
 ~!

c1 1 1 1 1 1 1 1 1 1 1 1 1 1 1f &)))sB   .G'BGB!G B!!G0GB7GGGc           	      d   t                       t          | pi           }t          |                    d          p|                    d          pd                                          }|st          d          t          |                    d          pd                                          pd}t          |                    d          pt          |                                                    t                      }t                      5 }|	                    d           }|}d	}|	                    d
|f          
                                r5| d| }|dz  }|	                    d
|f          
                                5|t          |||          }	|	                    d          
                                }
|
r.	 t          j        |
d                   }n# t          $ r i }Y nw xY wi }|                    dg            |                    di            |                    di            |                    dg            |                    dg            ||d         vr|d                             |           ||d         <   d|d         |<   fd|                    dg           D             |d<   g |d<   |	                    dt          j        |dd          ||f           |                                 ddd           n# 1 swxY w Y   d|	||dddS )z
    Create a graph/container physically in SQLite.

    Policy:
    - A graph is a container/row.
    - Creating a graph must NOT create timeline events.
    - Timeline components are created later by manual event insertion or API ingest.
    r   r   rW   zgraph name is requiredr   r   r   r1  rn   r   r  ro   z5SELECT value_json FROM ui_state WHERE key = 'folders'r]  r   rb  r  r^  r_  Tc                 :    g | ]}t          |          k    |S r   r[  )r   r   r   s     r   r   z$create_graph_sql.<locals>.<listcomp>  s*    ]]]a3q66U\K\K\K\K\K\r   r  Frg  Nr   )r   r   r   r   events_created)r4  r   )r.   rZ   r=   r[   rJ   r$  r  r   r*   r'   r   r   rk  rl  rL   r   rp   rn  r-   )r   r5  r   r   r   r)   base	candidater   r   r   r  r   s               @r   create_graph_sqlr    s    2DtxxA488N#;#;ArBBHHJJD 31222dhhx((;O<<BBDDWK$((9%%@):4)@)@AAGGIIG	B	 4%&&&	kkCi\RR[[]] 	!IFA kkCi\RR[[]] 	 &sGT;GGkkC
 

(** 	  	
3|#455    EB'''+++$$$+++r***eI...)##K000(3mW%%)fk"]]]]599]B+G+G]]]m l ZETBBBBK		
 		
 		
 	

i4 4 4 4 4 4 4 4 4 4 4 4 4 4 4n  !
 
	 	 	s8   B7LG'&L'G63L5G66DLL #L c                  :   t                       ddl} d}d}t                      }t                      5 }|                    d                                          }|sddddcddd           S 	  | j        |d         pd          }n6# t          $ r)}ddt          |          dcY d}~cddd           S d}~ww xY wt          |t                    sddd	dcddd           S |                    d
           |                                D ]\\  }}	t          |	t                    s|dz  }!t          |pd          }
|
                    dd          }t          |          dk    r|d                                         nd}d}d}d}t          |          dk    rZ	 t          |d                   }t          |d                   }n# t          $ r d}d}Y nw xY w|d                                         }t          |	                    d          p-|	                    d          p|p|	                    d          pd                                          }t          |	                    d          p|	                    d          p|pd                                          }t          |	                    d          p|	                    d          pd                                          }t          |	                    d          p|	                    d          pd                                          }t          |	                    d          p|	                    d          pd                                          }t          |	                    d          p@|	                    d          p+|	                    d          p|	                    d          pd                                          }t          |	                    d           p|	                    d!          pd                                          }|r|s|dz  }Ed}|.|,|                    d"||||f                                          }|-|r+|                    d#|||f                                          }|*|                    d$||f                                          }||dz  }|d         }|d%         }|d&         }|d'         }|r&	 t#          |          \  }}}}n# t          $ r Y nw xY w|                    d(||||||||||t          |d)                   f           |dz  }^|                                 ddd           n# 1 swxY w Y   ||d*S )+a  
    Synchronize UI technical-card metadata into canonical events.

    Source:
    - ui_state key = 'event_meta'

    Target:
    - events.place
    - events.who
    - events.brief_description
    - events.source_link
    - events.event_name
    - events.date_text/date_year/date_month/date_day

    This function does not create events. It only updates existing events.
    Matching uses:
    1. event_meta key format row:start:end:name when available.
    2. saved group/name/date fields.
    3. row_key + event_name fallback.
    r   Nz6SELECT value_json FROM ui_state WHERE key='event_meta'zno event_meta)rH  r  rz  r]  z{})rH  r  r  zevent_meta is not objectr1  ro   rW   r3  r   r   rn   r   r   r   r   r   r   r   r}   r   r   r   r   r   r   r   r   r   ai  
                    SELECT id, event_name, date_text, date_year, date_month, date_day
                    FROM events
                    WHERE row_key = ?
                      AND start_col = ?
                      AND end_col = ?
                      AND event_name = ?
                    ORDER BY id DESC
                    LIMIT 1
                    aC  
                    SELECT id, event_name, date_text, date_year, date_month, date_day
                    FROM events
                    WHERE row_key = ?
                      AND event_name = ?
                      AND date_text = ?
                    ORDER BY id DESC
                    LIMIT 1
                    a  
                    SELECT id, event_name, date_text, date_year, date_month, date_day
                    FROM events
                    WHERE row_key = ?
                      AND event_name = ?
                    ORDER BY id DESC
                    LIMIT 1
                    r   r   r   a  
                UPDATE events
                SET event_name = ?,
                    date_text = ?,
                    date_year = ?,
                    date_month = ?,
                    date_day = ?,
                    place = ?,
                    who = ?,
                    brief_description = ?,
                    source_link = ?,
                    updated_at = ?
                WHERE id = ?
                r   )rH  r  )r.   rk  r   r*   r'   r   rl  rL   r=   rY   rZ   r  splitrs   rJ   r   r[   r-  r-   )rk  rH  r  r   r)   r   metaexcr_   rR   key_spartskey_row	key_startkey_endkey_namer   r   r6  r}   r   r   r   rA   r   r   r   r   s                               r   sync_event_meta_to_events_sqlr  ,  s   * KKKGG	B	 fkkD
 

(** 	  	K Q/JJf f f f f f f f	C4:c,/7488DD 	C 	C 	C QSBBBBBBBf f f f f f f f	C $%% 	V Q:TUUf f f f f f f f  	%&&&**,, R	 R	JCeT** 1	rNNEKKQ''E*-e**//eAhnn&&&rGIGH5zzQ# #E!HI!%(mmGG  # # # $I"GGG# !8>>++		%   99Y'' 99W%%   egg  		&!! 99\** 	 
 egg  		&!! 99[))  egg	  		'**Feii.@.@FBGGMMOOEeii&&@%))E*:*:@bAAGGIIC #		-.. 99W%%99W%% 99]++ ! ! egg  eii66P%))E:J:JPbQQWWYYK * 1F$)<	 i*=  (**  ~(~ j(3  (**  ~ j)
 
 (**  ~1{+I{+I-Jj)H AQRZA[A[>Iy*hh    D KK %t%%  : qLGG

Mf f f f f f f f f f f f f f fP 7333s   /V0B	V	
B<B7%B<&V7B<<V&B9V *G
VGVGL0VT"!V"
T/,V.T//AVVVc                  j   t                       t                      } t                       t                      5 }|                    d                                          d         }|                    d                                          d         }|                    d                                          d         }d}	 |                    d                                          d         }n# t          $ r d}Y nw xY wddd           n# 1 swxY w Y   t                       |dk    ||||dS )	af  
    Explicit session save endpoint.

    Operational policy:
    - Does not create data.
    - Does not delete data.
    - Forces frontend render from canonical SQLite.
    - Verifies SQLite integrity.
    - Returns current object counts.

    Most operations already persist transactionally. This endpoint is the
    explicit user-facing Save barrier.
    PRAGMA integrity_checkr   SELECT COUNT(*) FROM graphsSELECT COUNT(*) FROM events!SELECT COUNT(*) FROM api_registryNr4  )r4  sqlite_integrityr{  eventsapis)r.   r  r  r*   r'   r   rL   rG   )event_meta_syncr)   	integrityr  event_count	api_counts         r   save_session_sqlr    ss    355O	 	KK 899BBDDQG	kk"?@@IIKKANkk"?@@IIKKAN		$GHHQQSSTUVII 	 	 	III		 	 	 	 	 	 	 	 	 	 	 	 	 	 	  4%  s6   B
D-C21D2D>D DDDDc                     t                       t                      5 } |                     d           |                     dt                      f           |                                  d d d            d S # 1 swxY w Y   d S )Na\  
            CREATE TABLE IF NOT EXISTS persistence_audit(
                id INTEGER PRIMARY KEY CHECK(id = 1),
                revision INTEGER NOT NULL DEFAULT 0,
                dirty INTEGER NOT NULL DEFAULT 0,
                last_operation TEXT,
                last_status TEXT,
                last_error TEXT,
                sqlite_integrity TEXT,
                graph_count INTEGER DEFAULT 0,
                event_count INTEGER DEFAULT 0,
                folder_count INTEGER DEFAULT 0,
                api_count INTEGER DEFAULT 0,
                updated_at TEXT
            )
            z
            INSERT OR IGNORE INTO persistence_audit(
                id, revision, dirty, last_operation, last_status,
                sqlite_integrity, updated_at
            )
            VALUES (1, 0, 0, 'init', 'ok', 'unknown', ?)
            )r.   r*   r'   r   r-   r(   s    r   ensure_persistence_audit_schemar    s    	 	
 	
 	
$ 	 YYL		
 		
 		
 	

;                 s   AA77A;>A;c                    |                      d                                          d         }|                      d                                          d         }	 |                      d                                          d         }n# t          $ r d}Y nw xY w	 |                      d                                          d         }n# t          $ r d}Y nw xY w||||fS )Nr  r   r  zSELECT COUNT(*) FROM foldersr  )r'   r   rL   )r)   r  r  folder_countr  s        r   persistence_counts_sqlr  @  s    ++;<<EEGGJK++;<<EEGGJK{{#ABBKKMMaP   KK CDDMMOOPQR		   			 \9<<s$   -B
 
BB-C CCautosaver4  c                    t                       t                      5 }|                    d                                          d         }t	          |          \  }}}}	|dk    o|dk    }
t                      }|                    d|rdnd| |
rdndt          |pd          |||||	|f
           |                                 d d d            n# 1 swxY w Y   |
t          |          | |
rdndt          |pd          |||||	|dS )	Nr  r   r4  a  
            UPDATE persistence_audit
            SET revision = revision + 1,
                dirty = ?,
                last_operation = ?,
                last_status = ?,
                last_error = ?,
                sqlite_integrity = ?,
                graph_count = ?,
                event_count = ?,
                folder_count = ?,
                api_count = ?,
                updated_at = ?
            WHERE id = 1
            ro   r  rW   )r4  r;  	operationr<  r  r  r{  r  r   r  r   )	r  r*   r'   r   r  r   r=   r-   bool)r  r;  r<  r  r)   r  r  r  r  r  r4  r   s               r   r>  r>  Q  s|   #%%%	 #KK 899BBDDQG	<RSV<W<W9[,	$16T>YY  !'EKR  !	
 	
 	
: 	

G# # # # # # # # # # # # # # #L e)$$'U[b!!%  s   B'CCCc                     t                       t                      5 } |                     d                                          d         }t	          |           \  }}}}|                     d                                          }|rt          |          ni }|dk    o|                    dd          dk    t          |                    dd                    |                    dd          |                    dd	          |                    dd	          |                    d
d	          ||||||                    dd	          dcd d d            S # 1 swxY w Y   d S )Nr  r   z,SELECT * FROM persistence_audit WHERE id = 1r4  last_statusr;  revisionlast_operationrW   
last_errorr   )r4  r;  r  r  r  r  r  r{  r  r   r  r   )r  r*   r'   r   r  rZ   r[   r  )r)   r  r  r  r  r  r   r5  s           r   persistence_status_sqlr    s   #%%%	 
KK 899BBDDQG	<RSV<W<W9[,	kkHIIRRTT'tCyyyR t#M(E(E(M$((7A..//Q//"hh'7<<88M266((<44 )!!#((<44
 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
s   D-EEEc           
         t                       t          | pi           }t          |                    d          p|                    d          pd                                          pd}d|v r	|d         }nd|v r	|d         }nd|v r	|d         }n|}t          |t                    r*	 t          j        |          }n# t          $ r d|i}Y nw xY wt          |t                    sd|i}t                      }t                      5 }|                    d           |dk    r|                    dg            |                    d	i            |                    d
i            |                    dg            |                    dg            |                    dg           pg D ]}t          |                                          }|s&t          ||          }	 |                    d|                    d
i                               |d          rdnd||f           }# t          $ r Y w xY w|                    d	i           pi                                 D ]\  }}t          |                                          }t          |                                          pd}|                    d|f                                          }	|	sut          ||          }|                    d|||f           |                    d|t          j        |dd          ||f           |                                 ddd           n# 1 swxY w Y   t%          ddd           d|ddS )z
    Persist current UI folder state into SQLite.

    This is required for autosave of folders, graph-folder mappings,
    open/closed folder state, and deleted-row state.
    r_   	state_keyr   rR   r5  r]  rS   r1  rb  r  r^  r_  z;UPDATE folders SET is_open = ?, updated_at = ? WHERE id = ?Tro   r   r   r   zAUPDATE graphs SET folder_id = ?, updated_at = ? WHERE row_key = ?a  
            INSERT INTO ui_state(key, value_json, created_at, updated_at)
            VALUES (?, ?, ?, ?)
            ON CONFLICT(key) DO UPDATE SET
                value_json=excluded.value_json,
                updated_at=excluded.updated_at
            Frg  Nzui-stater4  r:  )r4  r_   rE  )r.   rZ   r=   r[   rJ   rY   rk  rl  rL   r   r*   r'   r   r   r  r   rn  r-   r>  )
r   r5  r_   rR   r   r)   r   r   r   r7   s
             r   save_ui_state_payload_sqlr    s    2D
dhhuooC+!6!6C)
D
D
J
J
L
L
YPYC$W	4V			\"% #	#Ju%%EE 	# 	# 	#ENEEE	# eT"" !% 	B	 7%&&&)Y+++]B///VR(((]B///\2...$yyB77=2  !+..4466" 0kBB	KKU#ii3377TJJQPQSUW`a    !   D */=")E)E)K(R(R(T(T  $g,,,,..!+..4466I/=J  (** 
  0kBB	WG,   
 	 $*U$GGGRP		
 		
 		
 	

o7 7 7 7 7 7 7 7 7 7 7 7 7 7 7r Zq>>>>sT222sK   ,C CC
CM$AH*)M*
H74M6H77DMMMc           	      H   t          | pi           } 	 |                     d          r%t          d|                     d          d           t                       t	          ddd          S # t
          $ r*}t	          ddd	t          |          
          cY d}~S d}~ww xY w)z
    Autosave barrier.

    It first persists UI state when present, then verifies SQLite and regenerates
    the HTML. It does not fabricate application data.
    ui_stater   )r_   rR   r  r   r4  r:  ro   r  )r;  r<  r  N)rZ   r[   r  rG   r>  rL   r=   )r   r  s     r   autosave_session_sqlr    s     7=b!!G
;;z"" 	%  Z00' '   
 	'
!DIIII 
 
 
'c((	
 
 
 	
 	
 	
 	
 	
 	

s   AA- -
B!7BB!B!c                       t          i           S )zK
    Manual Save button uses the same real autosave verification path.
    )r  r   r   r   r  r    s      ###r   c                  ,   t                       dddddddddddddddd} t                      5 }d |                    d	                                          D             }|sg cddd           S d
                    |          }|                    d| d                                          }g }|D ]}t          |          }|                                 D ]\  }}	|                    ||	           d|vr/|                    d          p|                    d          pd|d<   t          |                    d                    |d<   |
                    |           |                    d            |cddd           S # 1 swxY w Y   dS )a2  
    Schema-tolerant API registry reader.

    It reads the actual api_registry columns present in SQLite and fills
    missing operational fields with safe defaults. This prevents /api/health
    from failing when older databases do not yet contain last_status,
    last_latency_ms, last_run_at, etc.
    Nr   unknownrW   TGET)r  last_latency_mslast_run_atlast_insertedlast_updatedlast_skippedr<  raw_jsonr   r   enabledmethodtarget_graphendpointr   c                     g | ]
}|d          S r   r   r  s     r   r   zlist_apis.<locals>.<listcomp>@  s,     
 
 
 fI
 
 
r   PRAGMA table_info(api_registry), zSELECT z FROM api_registryr   r   r  r   r  c                     t          |                     d          pd          t          |                     d          pd          fS )Nr   rW   r   r=   r[   )r   s    r   <lambda>zlist_apis.<locals>.<lambda>[  s9    AEE&MM$7R 8 8#aeeDkk>OR:P:PQ r   )r_   )r.   r*   r'   rj  r  rZ   r  r   r[   r  rp   sort)
wanted_defaultsr)   colsselect_colsrp  r  r  rt   r_   r^   s
             r   	list_apisr  "  s<      O$ 
 
 
[[!BCCLLNN
 
 

  	        iioo{{5k555
 

(** 	  	 	A77D / 5 5 7 7 . .WW----4!XXf--N*1E1ENT
"488I#6#677DOJJtQQRRR;                 s   5F	1DF		FFc                    t                       t          | pi           }t          |                    d          p"dt	          j                    j        d d                    }t          |                    d          p|          }|                    dd          rdnd}t          |                    d	          p|                    d
          pd          }t          |                    d          pd                                          }t          |                    d          pd          }t          |                    d          pd          }t          |                    d          pd          }	||t          |          |||||	d|}
t                      }t                      5 }|                    d           |                    d||||||||	t          j        |
dd          ||f           |rt          |||d           |                                 d d d            n# 1 swxY w Y   |
S )Nr   API_r  r   r  Tro   r   r  r   rW   r  r  r  interval_seconds,  r<  
registered)r   r   r  r  r  r  r  r<  r1  a  
            INSERT INTO api_registry(id, name, enabled, target_graph, method, endpoint,
                                     interval_seconds, status, raw_json, created_at, updated_at)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ON CONFLICT(id) DO UPDATE SET
                name=excluded.name,
                enabled=excluded.enabled,
                target_graph=excluded.target_graph,
                method=excluded.method,
                endpoint=excluded.endpoint,
                interval_seconds=excluded.interval_seconds,
                status=excluded.status,
                raw_json=excluded.raw_json,
                updated_at=excluded.updated_at
            Frg  r   )r.   rZ   r=   r[   r   r   hexr   r   r  r   r*   r'   rk  rn  r   r-   )r   r5  r  r   r  r  r  r  intervalr<  rS   r   r)   s                r   
upsert_apir  `  sq   2D$A#A$*,,*:3B3*?#A#ABBFtxx)6**D88It,,3aa!Gtxx//J488G3D3DJKKL(##,u--3355F488J''-2..H488.//6377H(##3|44F==$$
 
 
C 
B	 %&&& T7L&(HfZ%4@@@"bJ!	
 	
 	
&  	R\<QQQ

/              0 Js   4A4H44H8;H8c                      	 t                      5 } d |                     d                                          D             cd d d            S # 1 swxY w Y   d S # t          $ r g cY S w xY w)Nc                     g | ]
}|d          S r  r   r  s     r   r   z(api_registry_columns.<locals>.<listcomp>  s,        &	  r   r  )r*   r'   rj  rL   r(   s    r   api_registry_columnsr	    s    \\ 	S %FGGPPRR  	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	
    			s4   A 1AA AA AA A*)A*c                     | si S t          | t                    r| S t          | t                    r@	 t          j        |           }t          |t                    r|ni S # t
          $ r i cY S w xY wi S r0   )rY   rZ   r=   rk  rl  rL   )rR   parseds     r   parse_json_objectr    s     	% % 	Z&&F'55=662= 	 	 	III	 Is   ,A A.-A.c                 B   i }t          | t          j                  rt          |           } t          | t                    rLt	          |                     d                    }|                    |           |                    |            |                    d|                    d          p|                    d          pd           |                    d|                    d                     |                    dd           |                    dd	           |                    d|                    d
          pd           |                    d|                    d          p|                    d          pd           |                    d|                    d          pd           |                    dd           |                    dd           |                    dd           t          |                    d                    |d<   |S )z
    Normaliza una fila de api_registry mezclando columnas reales y raw_json.
    Esto permite soportar bases viejas y nuevas.
    r  r   r   r  r   r  Tr  r  r   rW   r  r   r   r<  r  r  r  Nr  r  )	rY   r"   r%   rZ   r  r[   rS  r   r  )r   rt   rS   s      r   normalize_api_rowr    s   
 D#w{## 3ii#t 
 3 344CCOOD$((6**Kdhhz.B.BKeLLLOOFDHHTNN+++OOIt$$$OOHe$$$OOJ 52666OONDHHW$5$5$N%$NBOOOOOHdhh}55BCCCOO%t,,,OOM2&&&OOL"%%%488I..//DOKr   c           	         t                       t          |           } t                      }|sdddS t                      }t	                      5 }|                    d           |                    d| | f                                          }|s&|                                 dd| dcddd           S i }|                                D ]\  }}||v r|||<   d	|v r||d	<   |rvd
	                    d |
                                D                       }	t          |                                          |d         gz   }
|                    d|	 d|
           d|v rzt          d|
                                v r|d         nd          }|                    |           ||d	<   |                    dt          j        |dd          |d         f           |                                 ddd           n# 1 swxY w Y   d| ddS )u   
    Actualiza estado runtime de una API de forma tolerante al esquema.
    Si algunas columnas no existen, las ignora.
    También intenta fusionar raw_json cuando existe.
    Fz!api_registry table has no columnsr4  r  r1  zISELECT * FROM api_registry WHERE CAST(id AS TEXT) = ? OR name = ? LIMIT 1zapi not found)r4  r  r  Nr   r  c                     g | ]}| d S )z = ?r   )r   rr  s     r   r   z"set_api_status.<locals>.<listcomp>  s    $G$G$GAZZZ$G$G$Gr   r   zUPDATE api_registry SET z WHERE id = ?r  rW   z1UPDATE api_registry SET raw_json = ? WHERE id = ?Trg  )r4  r  rH  )r.   r=   r	  r   r*   r'   r   rK  r  r  r]   rq   rN  r  rS  rk  rn  r-   )r  fieldsr  r   r)   r   directr_   rR   assignmentsparamsrS   s               r   set_api_statusr    s    [[F!!D K&IJJJ	B	 '%&&&kkWV
 
 (** 	
  	MLLNNN/VLL' ' ' ' ' ' ' '  ,,.. 	$ 	$JCd{{#s4#%F<  	))$G$G$G$G$GHHK&--//**c$i[8FKKE;EEE  
 #zSXXZZ7O7OC
OOUWXXCJJv "CKKCCetDDDc$iP  
 	

O' ' ' ' ' ' ' ' ' ' ' ' ' ' 'R &T:::s   AG;7D8G;;G?G?c                 *   ddd}|                      d          p+|                      d          p|                      d          pd}t          |          }|                                D ](\  }}|r!|t          |          |t          |          <   )|S )NzGRAFO-D-V55-API-SENSOR/1.0z!application/json, text/plain, */*)z
User-AgentAcceptheaders_jsonheadersrequest_headersrW   )r[   r  r  r=   )r   r  raw_headersr  r_   rR   s         r   request_headers_for_apir    s    25 G 	 	779	77$%%	 	  {++Fllnn + +
U 	+5$ #E

GCHHNr   c                     t          |                     d          p+|                     d          p|                     d          pd                                          S )Nr  r   urirW   )r=   r[   rJ   )r   s    r   endpoint_for_apir   !  s\    
 	775>>	775>>	 	 
 eggr   c                     t          |                     d          pd                                                                          }|dvrdS |S )Nr  r  )r  HEAD)r=   r[   r   rJ   )r   r  s     r   safe_api_methodr#  *  sO    ""+e,,2244::<<F _$$uMr   r   c                 V   t          |           } t          |                     d          p|                     d          pd          }t          |                     d          p|          }t          |           }||t	          |                     d                    t          |                     d          p+|                     d          p|                     d          pd          |t          |           d	d	d
d
t                      d
d}|                     d          sd	|d<   d	|d<   d|d<   |S |sd	|d<   d	|d<   d|d<   |S t          |          }|j        dvrd|d<   d|d<   d|d<   |S t          j
                    }	 t          |t          |           t          |                     }t          ||          5 }	t          t          j
                    |z
  dz            }
t          t          |	dd          p|	                                pd          }||d<   |
|d<   d|cxk    rdk     rn nd|d<   d|d<   d
|d<   n5d|cxk    rdk     rn nd|d<   d|d<   d| |d<   nd|d<   d|d<   d | |d<   d
d
d
           n# 1 swxY w Y   n># t"          $ rp}t          t          j
                    |z
  dz            }
t          t          |d!d          pd          |d<   |
|d<   d|d<   d|d<   d"|d          |d<   Y d
}~nd
}~wt$          $ r`}t          t          j
                    |z
  dz            }
|
|d<   d|d<   d|d<   t          t          |d#|                    |d<   Y d
}~nad
}~wt&          $ rQ}t          t          j
                    |z
  dz            }
|
|d<   d|d<   d|d<   t          |          |d<   Y d
}~nd
}~ww xY w	 t)          ||d         |d         |d         |d$         |d         pd|d$         |d         %           n# t&          $ r Y nw xY w|S )&z
    Verifica conectividad real de una API registrada.

    Estados:
    - active: HTTP 2xx
    - degraded: HTTP 3xx o respuesta rara pero alcanzable
    - inactive: timeout, error de red, HTTP 4xx/5xx
    - unknown: API deshabilitada o sin endpoint
    r   r   r   r  r  r   r   rW   r  N)r   r   r  r  r  r  r<  runtime_statushttp_status
latency_mslast_checkedr  r<  r%  API disabledr  Missing endpointhttphttpsinactiveUnsupported endpoint schemer  r  r      r   r&  r'     r  activer  degradedzRedirect/status HTTP codez
HTTPError rz  r(  r<  r  r  r  r  r(  r%  )r  r=   r[   r   r  r#  r   r   schemer   perf_counterr
   r  r   r   getattrgetcoder   r   rL   r  )r   r!   r  r   r  r  r  startedreqreslatencyr&  r  s                r   check_api_connectivityr@  4  sZ    C
 
 C:#''&//:U;;Fswwv(&))D$$H 	**++CGGN33_swww7G7G_377SX>>_]_``!#&&#		 D 779 "X!*&W "X!**WhF}---#X!+5W!!G/!+C00"3''
 
 
 S'*** 	6c4,..8D@AAGgc8Q77M3;;==MANNK"-D!(Dk''''C'''''!)X)1%& $W))))c)))))!+X)3%& @; @ @W!+X)3%& 5 5 5W%	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6(  ; ; ;t(**W4<==!'#vq"9"9">Q??]$\#X!+:T-%8::W 9 9 9t(**W4<==$\#X!+GC37788W ! ! !t(**W4<==$\#X!+CW!>]+ .^,G}*n- 01		
 		
 		
 		
 		
     Ksk   =J B:J	=J 	JJ JJ 
O A&LOAM33O AOOAP 
P&%P&c           	         |sdS t          |                                          }t          |                     d          pd          t          |                     d          pd          t          |                     d          pd          t          |                     d          pd          t          |                     d          pd          h}||v S )NTr  rW   r   r   r   r   )r=   rJ   r[   )r   r   ru   s      r   api_matches_graphrB    s     tJJE 	CGGN##)r**CGGG"##CGGENN b!!CGGFOO!r""CGGDMMR  J Jr   c                 ~    t                       d t                      D             } fd|D             }d |D             }|sd}nVt          d |D                       rd}n:t          d |D                       rd}nt          d	 |D                       rd}nd
}d |t                      t	          |          |dS )zk
    Endpoint backend para el sensor real:
    GET /api/apis/status
    GET /api/apis/status?graph=ISS
    c                 ,    g | ]}t          |          S r   r  r   r   s     r   r   z"api_status_sql.<locals>.<listcomp>  0        	#  r   c                 4    g | ]}t          |          |S r   rB  r   r   r   s     r   r   z"api_status_sql.<locals>.<listcomp>  9       S%((  r   c                 ,    g | ]}t          |          S r   r@  rF  s     r   r   z"api_status_sql.<locals>.<listcomp>  s0        	s##  r   r  c              3   .   K   | ]}|d          dk    V  dS r<  r.  Nr   r   s     r   rf  z!api_status_sql.<locals>.<genexpr>  +      881Qx[J&888888r   r.  c              3   .   K   | ]}|d          dk    V  dS r<  r4  Nr   r   s     r   rf  z!api_status_sql.<locals>.<genexpr>  rP  r   r4  c              3   .   K   | ]}|d          dk    V  dS r<  r  Nr   r   s     r   rf  z!api_status_sql.<locals>.<genexpr>  +      77!Qx[I%777777r   r3  Tr4  r   r<  
checked_atcountr  r.   r  rm  r   rs   r   r  selectedresultsoveralls   `    r   api_status_sqlr^    .     ;;  D
     H
   G
  		88888	8	8 	88888	8	8 	77w777	7	7  iiW  r   c                    t          |           } t          |           }|st          d          t          |          }|j        dvrt          d          t          |t          |           t          |                     }t          ||          5 }|	                    d          }ddd           n# 1 swxY w Y   |
                    dd	
          }	 t          j        |          S # t          $ r d|icY S w xY w)z:
    Descarga JSON desde la API. Se usa por sync_api.
    zAPI endpoint is requiredr+  r/  r0  r    i   Nutf-8r(  )errorstext)r  r   r$  r   r8  r
   r  r#  r   readdecoderk  rl  rL   )r   r!   r  r  r=  r>  rS   rc  s           r   fetch_api_jsonrf    s[    C
 
 C$$H 53444hF}---6777
',,s##  C 
g	&	&	& $#hh{##$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ ::gi:00Dz$   ~s$   B66B:=B:C- -C>=C>c                     t          | t                    r| S t          | t                    r6dD ]0}|                     |          }t          |t                    r|c S 1| gS d| igS )zE
    Convierte respuestas comunes de APIs en una lista de items.
    )r  r  r\  r5  r   rR   )rY   rq   rZ   r[   )r   r_   rv   s      r   payload_itemsrh  	  s     '4   '4   D 	 	C++c""C#t$$ 


 ygr   c                 ~   t          |           } t          |t                    sd|i}t          |                     d          p@|                     d          p+|                     d          p|                     d          pd          }t          |                     d          p|                     d          pd          }t          |          \  }}t          |dd	          }t          |          }|r|\  }	}
}}}| d
|	 }|
}nKt          t          |d| d|dz                        }t          |pt                      dd                   }t          | |          r*|rd|d          }nd|v rd|                    d           }t          |||          }t          t          |dd	                    }|s8	 t          j        |dd          }n# t          $ r t          |          }Y nw xY wt          t          |dt          j        t          j        |dd                              d                                                    dd                             }|                     d          p| d| |||||||t          t          |d|                    ||t%          |           ||dS )u;   
    Mapeo tolerante API → evento canónico GRAFO_D.
    rR   r  r   r   r   APIr   )r   r   r   r   r   rO   rW   z
 position )r   r   r   r   z event ro   Nr  zISS position r   r   )r   r   r   summaryrc  messageFTrg  )r   r   r   r   ra     r3  )r   r   r0  )r   r   r   r   r   r   r   r}   r   r   r   r   r   r   )r  rY   rZ   r=   r[   r{   r`   rV   r   r   r   rk  rn  rL   hashlibsha1encode	hexdigestr   )r   rt   indexr  r   ri   rk   raw_timer   r   r   rO   rP   rQ   r   
event_dater}   r   	source_ids                      r   event_from_api_itemrv  	  s2    C
 
 CdD!!  	777	775>>	 776??	  L 3776??<cggdmm<u==Ht$$HCF
 H x((E 5491iuc44l44

8//EAI// 
 
 3WYYss^44
c4   ; 	;-58--DDD  :488K#8#8::Dc3''EW	
 	
 K  $	$*TNNNKK 	$ 	$ 	$d))KKK	$ 3L
4etDDDKKGTT ikk#2#	
 	
 I *(88Y88='A8LLMM"(',,  s   G G65G6c                    t                       t          |           } d t                      D             }d}|D ]R}t          |                    d                    | k    s&t          |                    d                    | k    r|} nS|sd| ddS t	          |t
                    }|d	         d
vrd| |ddd|                    d          pddS t          |t
                    }t          |          }d}d}d}	t          |          D ]5\  }
}	 t          |||
          }t          |d          }|                    d          }|dk    r|dz  }n|dk    r|dz  }n|	dz  }		 t                      5 }|                    dt          |                    d          p|           t          |                    d                    t          |pd          t                      f           |                                 ddd           n# 1 swxY w Y   n# t          $ r Y nw xY w!# t          $ r	 |	dz  }	Y 3w xY w	 t!          | |||	dt                                 n# t          $ r Y nw xY wt#                       d| ||||	dS )u   
    Sincronización tolerante de una API registrada.

    - Verifica conectividad real.
    - Si responde, intenta leer JSON.
    - Normaliza items a eventos.
    - Inserta o actualiza eventos en SQLite.
    c                 ,    g | ]}t          |          S r   rE  rF  s     r   r   zsync_api.<locals>.<listcomp>	  rG  r   Nr   r   FzAPI not found)r4  r  r  r    r<  )r3  r4  r   r  zAPI not reachable)r4  r  r<  rI  rH  r  r  )rr  TrL  rJ  rI  ro   rH  z
                        INSERT OR IGNORE INTO api_ingest_log(
                            api_id, event_legacy_id, action, created_at
                        )
                        VALUES (?, ?, ?, ?)
                        upsertr3  )r  r  r  r<  r  )r4  r  r<  rI  rH  r  )r.   r=   r  r[   r@  API_SYNC_TIMEOUT_SECONDSrf  rh  	enumeraterv  rP  r*   r'   r   r-   rL   r  rG   )r  r  r   r  r<  r   r  rI  rH  r  rr  rt   r   resultrJ  r)   s                   r   sync_apir~  v	  s    [[F ;;  D
 C  	y}}T""##v--Y]]65J5J1K1Kv1U1UCE 2V  
$
 
 	
 $C1IJJJFh555ZZ((?,?
 
 	
 S*BCCCG'""EHGG '' # #t"	'T???E%e$???FZZ))F##A9$$11\\ !SKK   788		$00 2(33#II	   JJLLL! ! ! ! ! ! ! ! ! ! ! ! ! ! !       	 	 	qLGGG	
"  			
 	
 	
 	
 	
        sn   AH/+H9BHHH	HH	HH/
H*'H/)H**H//II!I( (
I54I5c                     	 	 t                      } t          j                    }| D ]}|                    d          s|                    d          }|s0t          |                    d          pd          }|                    d          }d}t	          |t          t
          f          r|t          |          z
  |k    }|rTt          |d         ||                    d          pd	
           	 t          |d                    # t          $ r Y w xY wn# t          $ r Y nw xY wt          j	        d           ?)NTr  r  r  r  last_run_epochr   r<  	scheduled)r  r<  r   )
r  r   r[   r   rY   rK   r  r~  rL   sleep)r  nowr   r  r  last_rundues          r   api_poll_workerr  	  so   	;;D)++C  wwy)) 77:.. sww'9::AcBB77#344he55 <h/8;C "3t9SQYIZIZIi^ijjjj T++++$   	$  	 	 	D	
11s6   C/D 3D	D 	
DD DD 
D('D(c                  z    t           rd S da t          j        t          d          } |                                  d S )NTr@   )_api_worker_startedrC   rD   r  rE   )ts    r   start_api_worker_oncer  
  s;     ===AGGIIIIIr   c                   D     e Zd Zd	dZd Z fdZd Zd Zd Zd Z	 xZ
S )
Handlerr2  c                 l   t          j        |dd                              d          }|                     |           |                     dd           |                     dt          t          |                               |                                  | j        	                    |           d S )NFrn   )rh  indentra  zContent-Typezapplication/json; charset=utf-8Content-Length)
rk  rn  rp  send_responsesend_headerr=   rs   end_headerswfilewrite)selfobjr6  r5  s       r   
_send_jsonzHandler._send_json
  s    z#E!<<<CCGLL4   )JKKK)3s4yy>>:::
r   c                     t          | j                            dd                    }|r?t          j        | j                            |                              d                    ni S )Nr  0ra  )r   r  r[   rk  rl  rfilerd  re  )r  r   s     r   _payloadzHandler._payload
  sX      !137788ABJtz$*//!,,33G<<===Jr   c           	      t   t          | j                  }|j        }t          |j                  }	 |dv r%t	                      }|                     |           d S |dk    r#|                     t                                 d S |dk    r[d}|                    d          r|                    ddg          d         }t          |          }|                     |           d S t                      }|dk    r|                     |           d S |d	k    r%t                      }|                     |           d S |d
k    r+|                     |                    dg                      d S |dk    r+|                     |                    dg                      d S |dk    r+|                     |                    dg                      d S |dk    rd |                    dg           D             }|                     dt          t                    t          |          t          t                                |                                 d           d S n># t          $ r1}|                     dt          |          dd           Y d }~d S d }~ww xY wt!                                                      S )N)z/api/autosave/statusz/api/persistence/status	/api/apisz/api/apis/statusrW   r   r   )r   z/api/db/api/session/save/api/eventsr  /api/graphsr{  z/api/alertsalertsz/api/healthc                 ^    g | ]*}t          |                    d                     dk    (|+S )r   __AXIS__r  )r   es     r   r   z"Handler.do_GET.<locals>.<listcomp>K
  s?          155<<((J66 666r   T)r4  dbr  r  r  Fr    )r   pathr	   queryr  r  r  r[   r^  r1   r  r=   r$   rs   r  rL   superdo_GET)
r  
parsed_urlr  r  r}  r   r  graph_eventsr  	__class__s
            r   r  zHandler.do_GET
  s   di((
)**9	JJJ/11'''{""	,,,)))99W%% 8!IIgt44Q7E'e444'''By  ###***)++'''}$$x 4 4555}$$x 4 4555}$$x 4 4555}$$   !vvh33      b''!,//	,,(,(=(=(?(?! !     %  	 	 	OO53q66::C@@@FFFFF	 ww~~sI   'I 'I AI %)I )I ;/I ,/I /I BI 
J&&JJc                     t                      5 }|                    d                                          d         cd d d            S # 1 swxY w Y   d S )Nr  r   )r*   r'   r   )r  r)   s     r   r  zHandler.sqlite_integrity^
  s    \\ 	GS;;788AACCAF	G 	G 	G 	G 	G 	G 	G 	G 	G 	G 	G 	G 	G 	G 	G 	G 	G 	Gs   -A		AAc                    t          | j                  j        }	 |                                 }|dk    r?ddlm}  |            }|                     ||                    d          rdnd           d S |dk    rddlm}  |t                    }|                    d          r%|                    d	          rt          d
           |                     ||                    d          rdnd           d S |dk    r4t          |          }t                       |                     |           d S |dk    r8t          |          }t                       |                     dd
i|           d S |dk    r:t          |d          }t                       |                     dd
i|           d S |dk    r4t          |          }t                       |                     |           d S |dk    r4t          |          }t                       |                     |           d S |dk    r)t          |          }|                     d
|d           d S |                    d          rV|                    d          rA|                    d          d         }t%          |          }|                     |           d S |dk    r(t                       |                     d
d
d           d S |dv r8t'          dt)                      v r|ni           }|                     |           d S |                     dddd           d S # t*          $ rF}d t-          |          v rd!nd}	|                     dt-          |          d|	           Y d }~d S d }~wt.          $ r1}|                     dt-          |          dd"           Y d }~d S d }~ww xY w)#Nz/api/import/graph-inbox/previewr   )preview_graph_inboxr4  r2  r  z/api/import/graph-inbox)import_graph_inboximportedTrF   z/api/ui-stater  z/api/ingestFry  z/api/foldersr  r  r4  r   
/api/apis/z/sync/z/api/render)r4  rendered)z/api/autosave/saver  r   unknown endpointr    	duplicatei  r  )r   r  r  r  r  r  r[   r$   rG   r  r8  rP  r  r  r  rd  endswithr  r~  r  localsr$  r=   rL   )
r  r  r   r  r}  r  r   r  r  r6  s
             r   do_POSTzHandler.do_POSTb
  sI   	""'M	AmmooG 888BBBBBB,,..vzz$/?/?(HSIII 000AAAAAA++B//::d## 0

:(>(> 0#$////vzz$/?/?(HSIII&&27;;!!!'''}$$099!!!t 6v 6777}$$)'FFF!!!t 6v 6777~%%*733!!!'''}$$)'22!!!'''{"" ))tC 8 8999|,, w1G1G C,!&))'''}$$!!!t > >???BBB-fhh9N9NggTVWW'''OO53EFFLLLLL 	B 	B 	B%Q//33SDOO53q66::DAAAAAAAAA 	A 	A 	AOO53q66::C@@@@@@@@@	Ash   AM 4BM 98M 3<M 1>M 18M +8M %-M A)M ?,M -:M )M 
O;NO&OOc                    t          | j                  j        }	 |                                 }|                    d          rq|                    dd          d         }t          ||          }|s|                     dddd           d S t                       |                     d	|d
           d S |                    d          rJ|                    dd          d         }||d<   t          |          }|                     d	|d           d S |                     dddd           d S # t          $ r1}|                     dt          |          dd           Y d }~d S d }~ww xY w)N/api/events/r  ro   Fevent not foundr  r  Tr=  r  r   r  r  r  )r   r  r  rd  rsplitrU  r  rG   r  rL   r=   )r  r  r   r   r   r  r   r  s           r   do_PATCHzHandler.do_PATCH
  s   	""'	AmmooG~.. 
;;sA..r2(7;; OO5;L$M$MsSSSF!!!te < <===|,, S!,,R0 & ))tC 8 8999OO53EFFLLLLL 	A 	A 	AOO53q66::C@@@@@@@@@	As+   A0D/ &D/ 5AD/ D/ /
E*9&E%%E*c                 l   t          | j                  j        }	 |                    d          rn|                    dd          d         }t	          |          s|                     dddd           d S t                       |                     d	|d
           d S |                    d          r`ddlm}  ||                    dd          d                   }t          |g          }t                       |                     |           d S |                    d          rS|                    dd          d         }t          |gd	          }t                       |                     |           d S |                    d          r|                    dd          d         }t                      5 }|                    d           |                    d|f          }	|                    d|f          }
|                                 d d d            n# 1 swxY w Y   t                       |                     d	||
j        |	j        d           d S |                     dddd           d S # t          $ r1}|                     dt!          |          dd           Y d }~d S d }~ww xY w)Nr  r  ro   r  Fr  r  r  T)r4  ra  z/api/folders/r   rw  z/api/graphs/)r  r  r1  r  r  )r4  ra  r  r  r  r  )r   r  rd  r  rX  r  rG   r  rx  r  r  r*   r'   r-   rW  rL   r=   )r  r  r   rx  r   r}  r   r  r)   cur_logsr   r  s               r   	do_DELETEzHandler.do_DELETE
  s"   	""'6	A~.. 	;;sA..r2'11 OO5;L$M$MsSSSF!!!t A ABBB// 000000%gdkk#q&9&9"&=>>1;-@@!!!'''~.. ++c1--b1*G9TBBB!!!'''|,, S!,,R0\\ 
!SKK 1222"{{E	   H ++?	 C JJLLL
! 
! 
! 
! 
! 
! 
! 
! 
! 
! 
! 
! 
! 
! 
!  !!!%$'L(0(9	! !    OO53EFFLLLLL 	A 	A 	AOO53q66::C@@@@@@@@@	As]   AI8 6&I8 A3I8 A&I8 ;?I8 :AHI8 H""I8 %H"&5I8 I8 8
J3&J..J3)r2  )__name__
__module____qualname__r  r  r  r  r  r  r  __classcell__)r  s   @r   r  r  
  s           K K K@  @  @  @  @ DG G GPA PA PAdA A A<9A 9A 9A 9A 9A 9A 9Ar   r  c                    t          |           } t          |           }|st          d          t          |          }|j        dvrt          d          t          j                    }t          |t          |           t          |                     }t          ||          5 }	 |                    d           n# t          $ r Y nw xY wt          t          j                    |z
  dz            }t          t          |dd	          p|                                pd	          }d
|cxk    odk     nc ||d
|cxk    rdk     rn nd nd| ddcd d d            S # 1 swxY w Y   d S )Nr*  r+  r/  r0  r    i   r1  r<  r   r2  r  r5  urllibr4  r&  r'  r  	transport)r  r   r$  r   r8  r   r9  r
   r  r#  r   rd  rL   r   r:  r;  )	r   r!   r  r  r<  r=  r>  r?  r&  s	            r   _v55b_probe_with_urllibr    s   
C
 
 C$$H -+,,,hF}---6777!!G
',,s##  C 
g	&	&	& 
#	HHTNNNN 	 	 	D	 t(**W4<=='#x33Is{{}}IJJ ****s****&! K5555#55555TT;P;;P;P!
 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
s7   'E )B?>E ?
C	E CBE  E$'E$c                 j   t          |           } t          |           }|st          d          t          |          }|j        dvrt          d          t          j                    }dddddd	d
dt          t          |                    |g
}t          j
        |t          t                    dd|dz             }t          t          j                    |z
  dz            }|j        dk    r;|j        p|j        pd                                }t!          |p	d|j                   |j        pd                                                                }	|	r/|	d                                         rt          |	d                   nd}
d|
cxk    odk     nc |
|d|
cxk    rdk     rn nd nd|
 ddS )Nr*  r+  r/  curlz-Lz-sSz-oz	/dev/nullz-wz%{http_code} %{time_total}z
--max-timeTr   )r4   rc  capture_outputr!   r1  r   rW   zcurl returned r2  r  r5  r  )r  r   r$  r   r8  r   r9  r=   r   r9   r:   r>   
returncodestderrstdoutrJ   RuntimeErrorr  isdigit)r   r!   r  r  r<  cmdprocr?  errr  r&  s              r   _v55b_probe_with_curlr  9  s   
C
 
 C$$H -+,,,hF}---6777!!G 	$CLLC >II!  D 4$&&0D899G!{/dk/R66883D"D4?"D"DEEE[B%%''--//E#(FU1X-=-=-?-?F#eAh---QK [&&&&3&&&&"1111c111117L{7L7L  r   c                    t          |           } t          |                     d          p|                     d          pd          }t          |                     d          p|          }t          |           }||t	          |                     d                    t          |                     d          p+|                     d          p|                     d          pd          |t          |           d	d	d
d
t                      d
d
d}|                     d          sd	|d<   d	|d<   d|d<   |S |sd	|d<   d	|d<   d|d<   |S g }d
}	 t          | |          }n# t          $ r~}|	                    dt          |          z              	 t          | |          }n<# t          $ r/}	|	                    dt          |	          z              Y d
}	~	nd
}	~	ww xY wY d
}~nd
}~ww xY w|r|                    d          |d<   |                    d          |d<   |                    d          |d<   |                    d          rd|d<   d|d<   d
|d<   nt          |                    d          pd          }
d|
cxk    rdk     r+n n(d|d<   d|d<   |                    d          pd|
 |d<   nNd|d<   d|d<   |                    d          pd|
 |d<   n&d|d<   d|d<   |rd                    |          nd |d<   	 t          ||d         |d         |d         |d!         |d         pd|d!         |d         "           n# t          $ r Y nw xY w|S )#z
    V55B robust real API connectivity check.
    Fixes false inactive state when urllib times out but curl succeeds locally.
    r   r   r   r  r  r   r   rW   r  N)r   r   r  r  r  r  r<  r%  r&  r'  r(  r  r  r<  r%  r)  r  r*  r    zurllib: zcurl: r&  r'  r  r4  r3  r   r  r  r4  r5  r.  z | zprobe failedr(  r7  )r  r=   r[   r   r  r#  r   r  rL   rp   r  r   r  r  )r   r!   r  r   r  r  rb  prober  exc2status_codes              r   r@  r@  m  s   
 C
 
 C:#''&//:U;;Fswwv(&))D$$H 	**++CGGN33_swww7G7G_377SX>>_]_``!#&&#		 D  779 "X!*&W "X!**WFE0'W=== 0 0 0j3s88+,,,	0)#w???EE 	0 	0 	0MM(SYY.////////	00  I#ii66]"YY|44\!IIk22[99T?? 	L%DN%-D!" DMMeii66;!<<Kk''''C'''''!+X)3%& %		' 2 2 K6Kk6K6KW!+X)3%& %		' 2 2 K6Kk6K6KW#X!+.4H

6***.W>]+ .^,G}*n- 01		
 		
 		
 		
 		
     KsU   E! !
G)+%G$F#"G$#
G-%GG$GG$$G)AM
 

MMc                 ~    t                       d t                      D             } fd|D             }d |D             }|sd}nVt          d |D                       rd}n:t          d |D                       rd}nt          d	 |D                       rd}nd
}d |t                      t	          |          |dS )z\
    V55B endpoint backend:
    GET /api/apis/status
    GET /api/apis/status?graph=ISS
    c                 ,    g | ]}t          |          S r   rE  rF  s     r   r   z"api_status_sql.<locals>.<listcomp>  rG  r   c                 4    g | ]}t          |          |S r   rI  rJ  s     r   r   z"api_status_sql.<locals>.<listcomp>  rK  r   c                 0    g | ]}t          |d           S )r   r    rM  rF  s     r   r   z"api_status_sql.<locals>.<listcomp>  s5        	sB///  r   r  c              3   .   K   | ]}|d          dk    V  dS rO  r   r   s     r   rf  z!api_status_sql.<locals>.<genexpr>  rP  r   r.  c              3   .   K   | ]}|d          dk    V  dS rR  r   r   s     r   rf  z!api_status_sql.<locals>.<genexpr>  rP  r   r4  c              3   .   K   | ]}|d          dk    V  dS rT  r   r   s     r   rf  z!api_status_sql.<locals>.<genexpr>  rU  r   r3  TrV  rY  rZ  s   `    r   r^  r^    r_  r   __main__ro   i="  r  z%GRAFO D API server: http://127.0.0.1:z1/outputs/timeline_excel_like.html?v=sqlite_serverz	127.0.0.1)F)rW   )Nr   )T)r  r   r4  rW   r0   )r   )r   )r   )grn  rk  r"   r9   r;   rC   r   r   r   r   pathlibr   http.serverr   r   r  r   r	   urllib.requestr
   r   urllib.errorr   r   grafo_d_sqlite_adapterr   __file__resolveparentr>   r$   r6   ALLOWED_UI_KEYSr{  r  Lockr8   r   r*   r.   r1   rG   rV   r`   r{   r   r   r,   r   r   r   r   r   r   r-  r8  rF  rP  rU  rX  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@  rB  r^  rf  rh  rv  r~  r  r  r  r  r  r  rs   argvr   portprintserve_foreverr   r   r   <module>r     sq          



       ' ' ' ' ' ' ' '       E E E E E E E E + + + + + + + + + + + + + + + + , , , , , , , , 0 0 0 0 0 0tH~~&	J22	&	&MMM  y~> > >    2  
	; 	; 	; 	;  4     8	 	 	T T T
  (# # #L  
 
 
   "  
% % %Rk8 k8 k8\q q qlW W Wt.- .- .- .-b/ / /d	  	  	 ( ( (Xq q qhX X X Xv4 4 4.W( W( W(t=* =* =*@T T TvD4 D4 D4N& & &V  D= = ="4 4 4 4n
 
 
4Z3 Z3 Z3z
 
 
 
8$ $ $: : :|/ / /h    "  >9; 9; 9;x  ,    t t t tn  ") ) ) )X !9    @     $Y Y Y Yxn n nh  8  |A |A |A |A |A& |A |A |AH#
 #
 #
 #
L1 1 1 1h\ \ \ \~) ) ) )\ z"s38}}q0033sx{dDO$	E
i$
i
i
ijjjd+W55CCEEEEE r   