h8jpx ddlZddlZddlZddlZddlZddlZddlZddlmZddlm Z ej e Z e e ZejjjjZeeejvr$ejdeeddlmZeZe dedZde fd ZdaejZd ad Zdd edefdZ dej!ddfdZ"dej!ddfdZ#dej!ddfdZ$de%fdZ&dede'de'ddfdZ(de)fdZ*dej!ddfdZ+dej!ddfdZ,defdZ-defdZ.dej!ddfdZ/dZ0dd"ed#ed$ed%e%d&ed'ed(ed)ede1e'fd*Z2d+e1d"ed#ed$ed,ed&ed'ed(ed)ede1fd-Z3dd%e%d'ede1e'fd.Z4d/ede%fd0Z5de1efd1Z6de1efd2Z7dede8e'dze1e'ffd3Z9dede'de8e%effd4Z:d5e)de'de8e%effd6Z;d5e)de8e%effd7Zdede'de8e%effd:Z?d;edZAd?ZBd@ede'fdAZCdded@edBej!dzde8e%effdCZDdDZEde1efdEZFde1e'fdFZGd$ede1e'fdGZHddHed%e%de1e'fdIZIdJe)de'dzfdKZJd/ede'dzfdLZKdJe)de1e'fdMZLd/ede1e'fdNZMd/ede1e'fdOZNd/ede1e'fdPZOd@timeoutPRAGMA foreign_keys = 1PRAGMA journal_mode = WALPRAGMA synchronous = NORMAL) sqlite3connect NOME_DATABASEexecute Exception_ensure_offline_column_ensure_visibilita_column _ensure_anagrafica_extended_cols_ensure_updated_cols)cons FC:\PYTHON\MyICR_Suite\app\modules\mediatrack\backend\database_logic.py_connr"s /- 6 6 6CKK)***   /0000      KK-...3c"""$S))) JsA AAreturncnddlm}m}|}||ddz dz S)z*Ritorna il path assoluto di gemma.parquet.r) get_app_root load_config parquet_dirzlocal_db/parquetz gemma.parquet)app.config_loaderrrget)rr _app_roots r_get_gemma_parquetr#DsL;;;;;;;; I {{}}((8JKK Ko ]]gcJddl}ddl}t}|s|S|j}t5t t|krt| dd}| } | d|da|n#|wxYw|a tcdddS#1swxYwYdS)uLRitorna il DataFrame di gemma.parquet, leggendolo solo se mtime è cambiato.rN\/zSELECT * FROM read_parquet(''))duckdbpandasr#exists DataFramestatst_mtime_gemma_cache_lock_gemma_cached_df_gemma_cache_mtimestrreplacerrdfclose)r)pdpmtimep_strrs r_get_gemma_cachedr:QsNMMMA 88::||~~ FFHH E     #'9U'B'BFFNN4--E..""C #&;;/We/W/W/W#X#X#[#[#]#]   !&                    s+A D*+C*D*D DDD*wherecolumnscVddl}t}|jr|S|}|d|d|d}|r|d|z } |||S#|wxYw)uLegge gemma.parquet via cache in-memory. Connessione DuckDB per-call su DataFrame. Thread-safe: ogni thread ha la sua connessione locale, il DataFrame è condiviso read-only.rNgemmazSELECT z FROM gemmaz WHERE )r)r:emptyrregisterrr4r5)r<r=r)r4rsqls r _gemma_dfrCfsMMM   B x ..  CLL" (G ( ( (C !     {{3""$$  s &BB(rc  |}|dd|D}d|vr+|d|dSdS#tj$rYdSwxYw)zMGarantisce che la colonna 'scaricato_il' esista in OmdbData (migration safe).zPRAGMA table_info(OmdbData)cg|] }|d Sr.0rows r z/_ensure_scaricato_il_column..}1113A111r$ scaricato_ilzPALTER TABLE OmdbData ADD COLUMN scaricato_il TIMESTAMP DEFAULT CURRENT_TIMESTAMPNcursorrfetchallcommitrErrorrcurcolss r_ensure_scaricato_il_columnrVxs jjll 122211#,,..111  % % KKj k k k JJLLLLL & % =     sA4A::B  B c d|dD}d|vr+|d|dSdS#tj$rYdSwxYw)zMAggiunge is_offline_insert ad AnagraficaMedia se non esiste (migration safe).cg|] }|d SrFrGrIrs rrKz*_ensure_offline_column..[[[![[[r$"PRAGMA table_info(AnagraficaMedia)is_offline_insertzJALTER TABLE AnagraficaMedia ADD COLUMN is_offline_insert INTEGER DEFAULT 0NrrPrQrrRrrUs rrrs [[ckk*NOOXXZZ[[[ d * * KKd e e e JJLLLLL + * =     sAA$$A76A7cH d|dD}d|vrU|d|d|d|dSdS#tj$rYdSwxYw)zPAggiunge visibilita a TipiEvento se non esiste, con seed per i tipi predefiniti.cg|] }|d SrFrGrYs rrKz-_ensure_visibilita_column..sVVV!VVVr$zPRAGMA table_info(TipiEvento) visibilitazDALTER TABLE TipiEvento ADD COLUMN visibilita TEXT DEFAULT 'entrambi'zTUPDATE TipiEvento SET visibilita='distributore' WHERE LOWER(nome_evento) = 'line-up'znUPDATE TipiEvento SET visibilita='prodotto' WHERE visibilita='entrambi' AND LOWER(nome_evento) NOT IN ('note')Nr^r_s rrrs  VVckk*IJJSSUUVVV t # # KK^ _ _ _ KKn o o o KKI J J J JJLLLLL $ # =     sBBB! B!c ddlm}|dz dz }|r?tj|d}|dd Sn#t$rYnwxYwd S) zMLegge il flag di rete scritto dal bootstrap. False = online (default sicuro).rrlauncher_cacheznetwork_status.jsonutf-8encodingonlineTF)r rr+jsonloads read_textr!r)rflagdatas r _is_offlineros 222222|~~ 003HH ;;== 0:dnngn>>??Dxx$/// / 0      5sA(A,, A98A9id_mediadati_anagrafica dati_eventoc ddlm}ddl}|dz dz }|j|||d}t |dd 5}|tj|d d zddddS#1swxYwYdS#t$r(}t d |Yd}~dSd}~wwxYw)zAppende il payload completo al log JSONL degli inserimenti offline. Safety net indipendente dal DB: sopravvive a corruzione di mediatrack.db.rrdNsync_dbzoffline_inserts_log.jsonl)tsrp anagraficaeventoarfrgF) ensure_ascii z[offline] Log JSONL fallito: ) r rrnow isoformatopenwriterjdumpsrloggerwarning) rprqrrrrlog_fileentryfes r_log_offline_insertrsh <222222<>>I-0KK#''))3355 )!    (C' 2 2 2 Ba GGDJu5999D@ A A A B B B B B B B B B B B B B B B B B B <<<:q::;;;;;;;;;.s___3A___r$r\))anno_produzioneTEXT)duratar)paesir)generir)registir)attorir)tramarz'ALTER TABLE AnagraficaMedia ADD COLUMN  NrN)rrTrUnew_colscol_namecol_types rrrs **,,C __#++.R"S"S"\"\"^"^___   #+ ] ] Hht## [h[[QY[[\\\ =     sA0BBBc" d|dD}d|vr|dd|vr|d|dS#tj$rYdSwxYw)zLAggiunge updated_at e updated_by ad AnagraficaMedia (migration idempotente).ch|] }|d SrFrGrYs r z'_ensure_updated_cols..r[r$r\ updated_atz6ALTER TABLE AnagraficaMedia ADD COLUMN updated_at TEXT updated_byz6ALTER TABLE AnagraficaMedia ADD COLUMN updated_by TEXTNr^r_s rrrs [[ckk*NOOXXZZ[[[ t # # KKP Q Q Q t # # KKP Q Q Q =     sA7A;;B BcPtjdS)Nseconds)timespec)rr{r|rGr$r_now_isors <>> # #Y # 7 77r$c~tjdtjddS)N COMPUTERNAMEHOSTNAMEunknown)osenvironr!rGr$r _this_machiners( :>>."*..Y*O*O P PPr$c |} |dd|D}d|vr+|d|dSdS#tj$rYdSwxYw)aDGarantisce che la colonna 'imdb_search_status' esista in AnagraficaMedia. Stati possibili: - NULL/0: non ancora controllato su OMDb - 1: controllato e trovato almeno un match - -1: controllato ma nessun match trovato Questo evita errori quando si esegue la scansione preliminare su DB non migrati. r\cg|] }|d SrFrGrHs rrKz,_ensure_imdb_flag_column..rLr$imdb_search_statuszNALTER TABLE AnagraficaMedia ADD COLUMN imdb_search_status INTEGER DEFAULT NULLNrNrSs r_ensure_imdb_flag_columnrs **,,C  899911#,,..111 t + + KKh i i i JJLLLLL , + =     sA A::B  B ct}|}|d||dS)Na# CREATE TABLE IF NOT EXISTS OmdbData ( id_media_fk TEXT NOT NULL, codice_imdb TEXT, titolo_omdb TEXT, sinossi TEXT, anno_produzione TEXT, data_rilascio TEXT, durata TEXT, generi TEXT, registi TEXT, sceneggiatori TEXT, attori TEXT, lingue TEXT, paesi TEXT, premi TEXT, url_poster TEXT, metascore TEXT, imdb_rating TEXT, imdb_voti TEXT, box_office TEXT, produzione TEXT, scaricato_il TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id_media_fk), FOREIGN KEY (id_media_fk) REFERENCES AnagraficaMedia(id_media) );)rrOrrQr5rrTs rcreate_omdb_tabler sS ''C **,,CKK4JJLLLIIKKKKKr$Ftitolo distributorecontesto includi_gemma codice_imdbanno_min_gemma tipologia tipo_eventoc t}tj|_|} |} |rp|r\t ||d|f} | r| dr| d} d} g} |rD|r0| dz } | d|d|r@|r,| dz } | ||r@|r,| dz } | ||rD|r0| dz } | d|d|r@|r,| d z } | ||rw|rcd | d D}d d gt|z}| d |dz } | || dz } | | t| d| D}||rt#||||| |||| }|S)uu Restituisce i dati per la griglia principale, filtrati in base ai parametri. Questa è la funzione "trapiantata" dal vecchio database_manager.py. Args: titolo: Filtro per titolo distributore: Filtro per distributore contesto: Filtro per contesto/evento includi_gemma: Se True, include anche i risultati dalla tabella Gemma z@SELECT gemma_supporto FROM Contesti WHERE LOWER(nome) = LOWER(?)ra WITH ultimi AS ( SELECT id_media_fk, costo_richiesto_ita, costo_richiesto_spa, ROW_NUMBER() OVER( PARTITION BY id_media_fk ORDER BY date(COALESCE(data_evento,'0001-01-01')) DESC, id_aggiornamento DESC ) rn FROM LogAggiornamenti ), ultimi_contesto AS ( -- Ultimo evento con contesto non NULL (gli allegati hanno contesto NULL) SELECT id_media_fk, dettaglio_contesto, ROW_NUMBER() OVER( PARTITION BY id_media_fk ORDER BY date(COALESCE(data_evento,'0001-01-01')) DESC, id_aggiornamento DESC ) rn FROM LogAggiornamenti WHERE dettaglio_contesto IS NOT NULL AND TRIM(dettaglio_contesto) != '' ) SELECT A.id_media, A.titolo_ufficiale, A.tipo, A.codice_imdb, O.url_poster, O.anno_produzione, A.distributore_nome AS distributore, UC.dettaglio_contesto AS contesto, U.costo_richiesto_ita, U.costo_richiesto_spa, 'mediatrack' AS fonte, NULL AS rda, NULL AS c5, NULL AS i1, NULL AS r4, NULL AS la5, NULL AS i2, NULL AS iris, NULL AS top_crime, NULL AS foc, NULL AS c20, NULL AS cine34, NULL AS twent_seven, NULL AS supporto, NULL AS data_valutazione, (SELECT COUNT(*) FROM LogAggiornamenti WHERE id_media_fk = A.id_media) AS num_eventi, CASE WHEN EXISTS ( SELECT 1 FROM LogAggiornamenti LV LEFT JOIN TipiEvento TE ON LV.id_tipo_evento_fk = TE.id_tipo_evento WHERE LV.id_media_fk = A.id_media AND ( LOWER(COALESCE(TE.nome_evento,'')) = 'venduto' OR (LV.venduto_a IS NOT NULL AND TRIM(COALESCE(LV.venduto_a,'')) != '') ) ) THEN 'Venduto' ELSE NULL END AS acquistato_venduto, A.imdb_search_status FROM AnagraficaMedia A LEFT JOIN ultimi U ON A.id_media = U.id_media_fk AND U.rn = 1 LEFT JOIN ultimi_contesto UC ON A.id_media = UC.id_media_fk AND UC.rn = 1 LEFT JOIN OmdbData O ON A.id_media = O.id_media_fk WHERE 1=1 AND A.id_media NOT LIKE 'G_%' AND A.deleted_at IS NULL z, AND LOWER(A.titolo_ufficiale) LIKE LOWER(?)%z7 AND LOWER(COALESCE(A.distributore_nome,'')) = LOWER(?)z AND EXISTS ( SELECT 1 FROM LogAggiornamenti L2 JOIN TipiEvento TE ON L2.id_tipo_evento_fk = TE.id_tipo_evento WHERE L2.id_media_fk = A.id_media AND LOWER(COALESCE(TE.nome_evento,'')) = LOWER(?) )z AND EXISTS ( SELECT 1 FROM LogAggiornamenti L3 WHERE L3.id_media_fk = A.id_media AND LOWER(COALESCE(L3.dettaglio_contesto,'')) LIKE LOWER(?) )z1 AND LOWER(COALESCE(A.codice_imdb,'')) = LOWER(?)c^g|]*}||+SrG)striprIts rrKz-get_dati_griglia_filtrati..s-EEEa17799E EEEr$,zLOWER(?)z$ AND LOWER(COALESCE(A.tipo,'')) IN ()z ORDER BY titolo_ufficiale ASCc,g|]}t|SrGdictrYs rrKz-get_dati_griglia_filtrati.. , , ,DGG , , ,r$)rrRow row_factoryrOr_ensure_contesti_tablerrappendsplitjoinlenextendtuplerPr5_merge_gemma_in_griglia)rrrrrrrrrrTgemma_contestorJqueryparamstips placeholdersrowss rget_dati_griglia_filtratir-s ''CkCO **,,CN$HNN$$$s###kk N ^^      (**   $3q6 $ VN: E~F -&,,..- ?? +&,,..+++,,,, **,,, JJ l((**++++{((**+     k''))***/HNN$$/     -(..**---...+{((**+ DD k''))***Y__&&EE9??3#7#7EEExx s4yy 899  G GGGG d --EKKuV}}%%% , ,S\\^^ , , ,DIIKKK & &,. K   Kr$mt_rowsrc 8g} |rU|rA|dd} | d| d|rU|rA|dd} | d| d|rU|rA|dd} | d| d|rU|rA|dd} | d| d|ra|rMd |d D}d |}| d |dt | rd | ndd}|jr|Sd|dD}t}tj |_ | }| dtdt|dD]-}|dd|||dzD.| dd|D}| dd|D}| dd|D}| dd|D}| d d!|D}|r\|rH| d"|gd#|D}nd}|g}|r:|r&|nd}|r:|r&|nd}|D]L\}}d$t+|dz} || |vr%|rj|| pd%}!t+|d&pd%}"||!vr|r||"vr|| i}#|| i}$|| }%|%|d&}%|d'}&|&d(vr|&}'n | |vrd)}'nd}'t+|d*pd%}(t|(d+kr |(d,d+nd})|id-| d.|d/d0|d1d2|d3p|#d2d4|#d4d5|)d6|d7d8|%d9|$d9d:|$d:d;d<d=|d>d?|d@dA|dBdC|dDdE|dFdG|dH|dI|dJ|dK|dL|dM|dN|d&d|| d|'ddO N||z}*|*dPQ|*S)Ru Carica i prodotti Gemma (da gemma.parquet via DuckDB), applica i filtri in Python, poi mergia con i dati MediaTrack già estratti da SQLite. I campi che dipendono da SQLite (num_eventi, costo_richiesto_*, contesto da LogAggiornamenti, acquistato_venduto da TipiEvento, url_poster da OmdbData) vengono risolti con una singola query SQLite bulk sui G_ IDs trovati. '''LOWER(A_TITOLO) LIKE LOWER('%%')z,LOWER(COALESCE(A_DISTRIBUTORE,'')) = LOWER('r(z(LOWER(COALESCE(A_COD_IMDB,'')) = LOWER('zSUBSTR(A_DATA, 7, 4) >= 'cg|]P}||ddQS)rr)rlowerr3rs rrKz+_merge_gemma_in_griglia..sL```VWV]V]V_V_` !!))#t44```r$r', 'z%LOWER(COALESCE(A_TIPOLOGIA,'')) IN (' AND NzA_ID, A_TITOLO, A_TIPOLOGIA, A_COD_IMDB, A_DATA, A_DISTRIBUTORE, A_SUPPORTO, A_ACQUISTATO_VENDUTO, V_RDA, V_C5, V_I1, V_R4, V_LA5, V_I2, V_IRIS, V_TOP, V_FOC, V_C20, V_CI34, V_C27r<r=c2g|]}dt|zSG_r2)rIaids rrKz+_merge_gemma_in_griglia..s" = = =TCHH_ = = =r$A_IDz6CREATE TEMPORARY TABLE _tmp_gids (id TEXT PRIMARY KEY)riz*INSERT OR IGNORE INTO _tmp_gids VALUES (?)cg|]}|fSrGrG)rIxs rrKz+_merge_gemma_in_griglia..s888!!888r$ziSELECT id_media_fk, url_poster, codice_imdb FROM OmdbData WHERE id_media_fk IN (SELECT id FROM _tmp_gids)c>i|]}|d|d|ddS) id_media_fk url_posterr)rrrGrYs r z+_merge_gemma_in_griglia..s3{{{gh- <QRS`Qa"b"b{{{r$z~SELECT id_media_fk, COUNT(*) AS cnt FROM LogAggiornamenti WHERE id_media_fk IN (SELECT id FROM _tmp_gids) GROUP BY id_media_fkc,i|]}|d|dS)rcntrGrYs rrz+_merge_gemma_in_griglia..s#EEE!M"AeHEEEr$a SELECT id_media_fk, dettaglio_contesto, costo_richiesto_ita, costo_richiesto_spa FROM ( SELECT id_media_fk, dettaglio_contesto, costo_richiesto_ita, costo_richiesto_spa, ROW_NUMBER() OVER(PARTITION BY id_media_fk ORDER BY date(COALESCE(data_evento,'0001-01-01')) DESC, id_aggiornamento DESC) rn FROM LogAggiornamenti WHERE id_media_fk IN (SELECT id FROM _tmp_gids) ) WHERE rn = 1 c:i|]}|dt|SrrrYs rrz+_merge_gemma_in_griglia..s%DDD!M"DGGDDDr$a SELECT id_media_fk, dettaglio_contesto FROM ( SELECT id_media_fk, dettaglio_contesto, ROW_NUMBER() OVER(PARTITION BY id_media_fk ORDER BY date(COALESCE(data_evento,'0001-01-01')) DESC, id_aggiornamento DESC) rn FROM LogAggiornamenti WHERE id_media_fk IN (SELECT id FROM _tmp_gids) AND dettaglio_contesto IS NOT NULL AND TRIM(dettaglio_contesto) != '' ) WHERE rn = 1 c,i|]}|d|dS)rdettaglio_contestorGrYs rrz+_merge_gemma_in_griglia..s%VVV!Am$a(<&=VVVr$aj SELECT DISTINCT LV.id_media_fk FROM LogAggiornamenti LV LEFT JOIN TipiEvento TE ON LV.id_tipo_evento_fk = TE.id_tipo_evento WHERE LV.id_media_fk IN (SELECT id FROM _tmp_gids) AND (LOWER(COALESCE(TE.nome_evento,'')) = 'venduto' OR (LV.venduto_a IS NOT NULL AND TRIM(COALESCE(LV.venduto_a,'')) != '')) ch|] }|d SrrGrYs rrz*_merge_gemma_in_griglia..s<<<1]#<<.&sDDD1]+DDDr$rr A_SUPPORTOA_ACQUISTATO_VENDUTO)Venduto AcquistatorA_DATA rptitolo_ufficialeA_TITOLOtipo A_TIPOLOGIAr A_COD_IMDBrrrA_DISTRIBUTORErcosto_richiesto_itacosto_richiesto_spafonter?rdaV_RDAc5V_C5i1V_I1r4V_R4la5V_LA5i2V_I2V_IRISV_TOPV_FOCV_C20V_CI34V_C27) iris top_crimefocc20cine34 twent_sevensupportodata_valutazione num_eventiacquistato_vendutorcT|dpdSNrrr!rrs rz)_merge_gemma_in_griglia..js$'9!:!:!@b G G I Ir$key)rr3rrrrCr@tolistrrrrrOrranger executemanyrPr5riterrowsr2r!sort)+rrrrrrrrr where_partst_escd_esci_esca_escrtips_strgdfg_idsrrTiomdb_map eventi_map ultimi_map contesto_map venduto_settipo_evento_set gemma_rowscontesto_lowergemma_contesto_lower_rZrpct_logct_supomdbulctav_rawr a_data anno_prodcombineds+ rrrsK G&,,..G &&sD11E5EEEFFFU **,,U""$$,,S$77S%SSSTTTQ{((**Q!!##++C66OeOOOPPPA...00A$$&&..sD99?u???@@@QY__&&Q``iooc>R>R```;;t$$O8OOOPPP +6@gll;'''D *   C y > =F (:(:(<(< = = =E ''CkCO **,,CKKHIII 1c%jj# & &:: D88uQq3wY'7888 : : : :KK{|||{{lolxlxlzlz{{{HKKQRRREEcllnnEEEJKK   EDS\\^^DDDJKK     WVs||~~VVVLKK   =N>NXX^^%%++---TXN=KpP^PdPdPfPfp>//1177999lp 6 6 1#ai..(  &8?+J+J   "&&x006B==??F!%% --344;;==FV++5I+MaioMoMo||Hb)) ^^Hb ) )   h ' ' :|$$B-.. . . .!'    $ $!*  !% QUU8__*++$'KK2$5$5F1R4LL4   j 1 1  AEE-((  155..I$((=2I2I  $((<00  y   AEE"233    "266*?#@#@  "266*?#@#@  W  155>> $(v 9=aeeFmm  !%%-- "'g 9=aeeFmm EE(OO!%%..w55>>QUU8__QUUSZ^^l++ $$..155"4"&)       .#H MMIIMJJJ Or$ct}|}|r|dd|D}|r-|rd|d}nd}t |d}|js9t|d n t}||z}td |Dd } n3|d d |D} | | S)uD Recupera una lista ordinata di distributori unici da Gemma (fonte unica). Args: includi_gemma: Se True, include distributori da Gemma anno_min_gemma: Se valorizzato, filtra Gemma a prodotti con anno >= valore (usato in Modalità Mercato per limitare agli ultimi 3 anni) z SELECT DISTINCT distributore_nome AS nome FROM AnagraficaMedia WHERE distributore_nome IS NOT NULL AND TRIM(distributore_nome) != '' AND id_media NOT LIKE 'G_%' AND deleted_at IS NULL ch|] }|d SrrGrYs rrz)get_distributori_unici..s000A1Q4000r$zWA_DISTRIBUTORE IS NOT NULL AND TRIM(A_DISTRIBUTORE) != '' AND substr(A_DATA, 7, 4) >= 'rz9A_DISTRIBUTORE IS NOT NULL AND TRIM(A_DISTRIBUTORE) != ''DISTINCT A_DISTRIBUTORErrcg|]}|ddS)r?nomerrG)rIns rrKz*get_distributori_unici..s CCCq733CCCr$c:|dpdSNrOrrr$s rr%z(get_distributori_unici..sTUV\T]TcacSjSjSlSlr$r&a5 SELECT DISTINCT distributore_nome AS nome, 'gemma' AS fonte FROM AnagraficaMedia WHERE distributore_nome IS NOT NULL AND TRIM(distributore_nome) != '' AND id_media NOT LIKE 'G_%' AND deleted_at IS NULL ORDER BY LOWER(distributore_nome) ASC c0g|]}|d|ddS)rrrNrGrYs rrKz*get_distributori_unici..s(EEE!qt,,EEEr$) rrOrrPrrCr@setdropnar(sortedr5) rrrrTmt_nomiwhere_gr3 gemma_nomituttiouts rget_distributori_unicir]ms ''C **,,CF     10000  Rn2244 RJp~qEqEqGqGJJJGGQGg/HIIIIL]S-.5577>>@@AAAX[X]X] *$CCUCCCIlIlmmm     FEcllnnEEEIIKKK Jr$nome_distributorec |r|sdS|}td|t dt dt dzdd}|jsdSt }|}|d|f| d u}| |S) z Verifica se un distributore esiste nella tabella Gemma. Args: nome_distributore: Nome del distributore da cercare Returns: True se il distributore esiste in Gemma, False altrimenti FzLOWER(TRIM(A_DISTRIBUTORE)) = ''rrrTz SELECT 1 FROM AnagraficaMedia WHERE LOWER(TRIM(distributore_nome)) = LOWER(TRIM(?)) AND id_media NOT LIKE 'G_%' AND deleted_at IS NULL LIMIT 1 N) rrrCr3chrr@rrOrrr5)r^ nome_lowerr3rrTresults ris_distributore_in_gemmards $5$;$;$=$=u"((**0022J _ 0B0B3r77CPRGGTWXZT[T[O0\0\___    C 9t ''C **,,CKK    \\^^4 'FIIKKK Mr$ct}t||d}|d|DS)zZRecupera i contesti dalla tabella Contesti (vocabolario controllato), ordinati per ordine.z@SELECT nome FROM Contesti WHERE attivo = 1 ORDER BY ordine, nomecg|] }|d SrKrGrYs rrKz&get_contesti_unici..s   QAaD   r$rrrrPr5rrs rget_contesti_uniciris^ ''C3 ;;J  hjj IIKKK  $   r$ctdd}|jrgStd|dDS)z5Recupera lista ordinata di tipologie uniche da Gemma.z3A_TIPOLOGIA IS NOT NULL AND TRIM(A_TIPOLOGIA) <> ''zDISTINCT A_TIPOLOGIArcPh|]#}t|$SrG)r2upperrIvs rrz'get_tipologie_uniche..s&GGGa3q66<<>>GGGr$r)rCr@rWrV)r3s rget_tipologie_unicheros[ C&   C y GG3}+=+D+D+F+FGGG H HHr$ct}tj|_|}|d|f|}|d|f|}|rt|nd}|d|fg}|D]}t|}| dr`|d} t| trC| dr. tj| |d<n#tj$rYnwxYw|||s|d|dfSt|} |r:dD]7} | | s | | r || | | <8|| ||fS)zd Restituisce i dati di anagrafica e la cronologia degli aggiornamenti per un dato id_media. z SELECT A.*, A.distributore_nome AS distributore_nome FROM AnagraficaMedia A WHERE A.id_media = ? AND A.deleted_at IS NULL z: SELECT * FROM OmdbData WHERE id_media_fk = ? NaU SELECT log.*, tipi.nome_evento, tipi.colore_hex FROM LogAggiornamenti AS log JOIN TipiEvento AS tipi ON log.id_tipo_evento_fk = tipi.id_tipo_evento WHERE log.id_media_fk = ? ORDER BY date(COALESCE(log.data_evento,'0001-01-01')) DESC, log.id_aggiornamento DESC percorso_file[rrrrrr)rrrrrOrrrrPr! isinstancer2 startswithrjrkJSONDecodeErrorrr5) rprrTanagrafica_row omdb_data_row omdb_datalogsrZlog_dictrqrvfields rget_storia_prodottor}s ''CkCO **,,CKK   \\^^NKK LLNNM'4>]###$IKK      D \\^^  77 << ( ( $_5M--- -2J2J32O2O 04 =0I0IH_--+D H   T4n%%J5Z 5 5E>>%(( 5Y]]5-A-A 5$-e$4 5!IIKKK tY &&s#D;;E  E c&d} t}|}ddlm}|d}|s&|}|d}|-t|tr|rtj |nd}|d}|}t|trhg}|D]K} | r| nd} | r/| dsd | z} | | L|rtj |nd}|d ||d ||d |d |d|d||d|d|d|dd|d|dd|d|f|t!| |r|dSdS#t$j$rZ} |r|t+d| dt-| fcYd} ~ |r|SSd} ~ wwxYw#|r|wwxYw)z1Aggiunge un evento (log) a un prodotto esistente.Nr)date data_eventorqlinksrzhttp://https://ra INSERT INTO LogAggiornamenti (id_media_fk, id_tipo_evento_fk, data_evento, dettaglio_contesto, costo_richiesto_ita, costo_richiesto_spa, note, percorso_file, buyer_contatto, budget_produzione, data_reminder, preferito, venduto_a, privato, delivery, links) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) id_tipo_evento_fkrrrnotebuyer_contattobudget_produzione data_reminder preferito venduto_aprivatodelivery)TzEvento aggiunto con successo.z%Errore DB in aggiungi_aggiornamento: F)rrOrrr!todayr|rtlistrjrrrurrrQaggiorna_campi_ereditatir5rrRrollbackprintr2) rprrrrTrrrqrnormalized_linksurlrs raggiungi_aggiornamentors& CEggjjll "!!!!!!oom44  3**,,0022K$88  $-.. U=J T = 9 9 9PT ((  %&& S#%  55C),4#))+++"C5"~~.EFF3",s"2C(//4448HR #3444d    OO/ 0 0  OO0 1 1 OO1 2 2 OO1 2 2 OOF # #  OO, - - OO/ 0 0 OOO , , OOK + + OOK ( ( OOIq ) ) OOJ ' ' !    2  !***4   =   9a99:::c!ff}     s0I,J K48K/K4K7/K44K77Lid_aggiornamentoc\d} t}|}|d}|-t|tr|rt j|nd}|d}|}t|trhg}|D]K}|r|nd}|r/|dsd|z}| |L|rt j|nd}d}| ||d|d |d |d |d ||d |d|dd|d|dd|d||f|  |r| dSdS#tj$rZ} |r|t!d| dt#| fcYd} ~ |r| SSd} ~ wwxYw#|r| wwxYw)z#Modifica un evento (log) esistente.Nrqrrrra UPDATE LogAggiornamenti SET id_tipo_evento_fk = ?, dettaglio_contesto = ?, costo_richiesto_ita = ?, costo_richiesto_spa = ?, note = ?, percorso_file = ?, budget_produzione = ?, data_reminder = ?, preferito = ?, venduto_a = ?, privato = ?, delivery = ?, links = ? WHERE id_aggiornamento = ? rrrrrrrrrrrr)TzModifica salvata con successo.z%Errore DB in modifica_aggiornamento: FrrOr!rtrrjrrrurrrQr5rrRrrr2) rrrrrTrqrrrrrs rmodifica_aggiornamentor`s CCggjjll$88  $-.. U=J T = 9 9 9PT ((  %&& S#%  55C),4#))+++"C5"~~.EFF3",s"2C(//4448HR #3444d " E OO/ 0 0 OO0 1 1 OO1 2 2 OO1 2 2 OOF # #  OO/ 0 0 OOO , , OOK + + OOK ( ( OOIq ) ) OOJ ' '      5   =   9a99:::c!ff}     s0HH&&J58J -J.J JJJ+c4d} t}|}|d|f||jdkr |r|dSdS |r|dSdS#t j$rZ}|r|td|dt|fcYd}~|r|SSd}~wwxYw#|r|wwxYw)z%Elimina un evento (log) dal database.Nz7DELETE FROM LogAggiornamenti WHERE id_aggiornamento = ?r)Fz$Nessun evento trovato con questo ID.)TzEvento eliminato con successo.z$Errore DB in elimina_aggiornamento: F) rrOrrQrowcountr5rrRrrr2)rrrTrs relimina_aggiornamentors5 Cggjjll MP`Obccc <1  @   6   =   8Q88999c!ff}     s0ABC;!8C6C;C>6C;;C>>Dct}tj|_|}|dd|D}||S)zERecupera tutti i tipi di evento disponibili con configurazione campi.z SELECT id_tipo_evento, nome_evento, colore_hex, icona, descrizione, predefinito, campi_visibili, attivo FROM TipiEvento WHERE attivo = 1 ORDER BY predefinito DESC, nome_evento ASC c,g|]}t|SrGrrYs rrKz#get_tipi_evento..rr$rrrrrOrrPr5rrTrs rget_tipi_eventorsk ''CkCO **,,CKK    - ,S\\^^ , , ,DIIKKK Kr$c|r|sdS|}|dd}td|dd}|jsd|fS|}|d|f|rd|fSd d |d fS) zh Valida che un distributore esista in Gemma (fonte unica). Restituisce (valido, messaggio). )FzNome distributore vuotorrzLOWER(A_DISTRIBUTORE) = 'rrTz SELECT 1 FROM AnagraficaMedia WHERE LOWER(distributore_nome) = LOWER(?) AND id_media NOT LIKE 'G_%' AND deleted_at IS NULL LIMIT 1 FzIl distributore 'zD' non esiste in Gemma. Seleziona un distributore valido dalla lista.)rrr3rCr@rOrr)rr^ nome_pulitorbr3rTs r_validate_distributore_in_gemmars 0$5$;$;$=$=0//#))++K""$$,,S$77J 7*777    C 9![   **,,CKK   ||~~![  wkwww wwr$c|drdSt}d} t}|}|dd|D}gg}}gg} }|D]\} } | dkr| nd} | |vr+|| | | >| |vr[|| d| dkr(| r&|  } || |s|s |r| d Sd S|rf|d d gz }|ttgz }d d |d} || t||fz|r|d|f|rKd d|D}|d|dt| |fznpd dg|z}d dgt!|dzz}|d|d|d|ft| z| |r| dSdS#t$j$rP}|r|t+d|dd|fcYd}~|r| SSd}~wwxYw#|r| wwxYw)zNModifica i dati anagrafici di un prodotto media. Solo prodotti MT (non Gemma).r)Fz'I prodotti Gemma non sono modificabili.Nr\ch|] }|d SrFrGrHs rrz&modifica_anagrafica..s777c!f777r$r = ?rFz!Nessun dato valido da aggiornare.zupdated_at = ?zupdated_by = ?UPDATE AnagraficaMedia SET ,  WHERE id_media = ?z,SELECT 1 FROM OmdbData WHERE id_media_fk = ?c3 K|] }|dV dS)rNrGrIcs r z&modifica_anagrafica..s(&F&Fa!zzz&F&F&F&F&F&Fr$UPDATE OmdbData SET  WHERE id_media_fk = ?r?rINSERT INTO OmdbData ( ) VALUES (r)Tz#Anagrafica aggiornata con successo.z"Errore DB in modifica_anagrafica: FErrore del database: )rurUrrOrrPitemsrrrlr5rrrrrrrQrrRrr)rprq _OMDB_FIELDSrrT colonne_amcampi_am valori_am campi_omdb valori_omdbchiavevalorernquery_am set_clauserUrrs rmodifica_anagraficars4  @?? 55L C7ggjjll 899977 777  ")"$bK -3355 $ $NFF B,,DA%%!!&)))""1%%%%:%%6000V### ))A  ### >  >=@  =  B )+;< >#yy##j//A2E)FGG  LTLL\LLLK% "4"44 :   =222   61667771a11111111   2  s7DKE$KL5%.L0L5L80L55L88Mmt_idgemma_idc|drdS|dsdSd} t}|}|d|f|d}|d||f|d|f|d |f|d |d |d |f|r|SS#tj$r?}|r| d d|dfcYd}~|r|SSd}~wwxYw#|r|wwxYw)a Sposta tutti gli eventi LogAggiornamenti da un prodotto MT a uno Gemma ed elimina il prodotto MT (AnagraficaMedia + OmdbData). Vincoli: mt_id non deve iniziare con G_; gemma_id deve iniziare con G_. Ritorna (successo, messaggio, n_eventi_spostati). r)FzFIl prodotto di origine deve essere un prodotto MediaTrack (non Gemma).r)Fz?Il prodotto di destinazione deve essere un prodotto Gemma (G_).rN;SELECT COUNT(*) FROM LogAggiornamenti WHERE id_media_fk = ?rzAUPDATE LogAggiornamenti SET id_media_fk = ? WHERE id_media_fk = ?*DELETE FROM OmdbData WHERE id_media_fk = ?hUPDATE AnagraficaMedia SET deleted_at = datetime('now'), updated_at = datetime('now') WHERE id_media = ?Tz eventi spostati su .FErrore database: ) rurrOrrrQr5rrRr)rrrrTn_eventirs rriconcilia_prodottir6s baa   t $ $[ZZ Cggjjll QTYS[\\\<<>>!$ O u    @5(KKK v H   AAhAAA8K   IIKKKK  =111   LLNNN-!--q000000   IIKKKK  1   IIKKKK s0B7DEE -E.E EEE+cd} |drddif|r|SSt}|}|d|f|}|s4|dd|dif|r|SS|dr|dnd }i}|d |f|d |d <|d |f|d |d<|dd|df|d |d<|d|f|dd|df|d|f|d|d|d}d||f|r|SS#tj$r^}|r| td|ddt|ifcYd}~|r|SSd}~wwxYw#|r|wwxYw)u Elimina completamente un prodotto MediaTrack (CASCADE). ATTENZIONE: Questa operazione è IRREVERSIBILE! Elimina: - Anagrafica prodotto (AnagraficaMedia) - Tutti gli eventi associati (LogAggiornamenti) - Dati OMDb (OmdbData) - Aggiorna items carrello orfani (CarrelloImport) impostando prodotto_json a NULL Args: id_media: ID del prodotto da eliminare Returns: (successo, messaggio, statistiche_eliminazione) statistiche_eliminazione: dict con conteggi di cosa è stato eliminato Raises: Blocca eliminazione se id_media inizia con 'G_' (prodotti Gemma read-only) NrFz0Impossibile eliminare prodotti Gemma (read-only)z`SELECT id_media, titolo_ufficiale FROM AnagraficaMedia WHERE id_media = ? AND deleted_at IS NULLz Prodotto z non trovatorz Senza titolorreventiz3SELECT COUNT(*) FROM OmdbData WHERE id_media_fk = ?rBz` SELECT COUNT(*) FROM CarrelloImport WHERE prodotto_json LIKE ? rcarrello_itemsrz UPDATE CarrelloImport SET prodotto_json = NULL, stato = 'orphaned' WHERE prodotto_json LIKE ? r Prodotto 'z' (z) eliminato con successoTzErrore DB in elimina_prodotto: r) rur5rrOrrrQrrRrrr2)rprrTprodottorstats messaggiors relimina_prodottords, CA   t $ $ QLbPz   IIKKKK wggjjll vzByD E E E<<>> A IIKKK>>WXXX Fw F F F FC?<$$!!###}} 88G   ,T']++ +   /???=!==>>>?sA'BC,B>>C connectionc d} t|}|r|ddkr"dd|df|r|s|SSSid|d|d |d d |d d |dd|dd|dd|dd|dd|dd|dd|dd|dd|d d!|d"d#|d$d%|d&|d'|d(|d)d*gd+}tfd,|Dr |r|s|d-Sd-Sd-S|du}|r|n t }|}t |t||d.|f| }|rg} g}  D]@\} } | dkr5| | d/| | d0kr| ndA| d1d2d3 | d4} | ||| t| nd3 d5z}d3 d6Dd7z}d8D} d9|d:|d;} || t| d<}d=d>g}|g}|D]O}|}|r6|d?kr0| |d@|dA| |P| ||dBd3 |dCt||s(||dD|sdEndFf|r|s|SSS#t"$rG}t%dG|dt'|fcYd}~|r|s|SSSd}~wwxYw#|r|s|wwwxYw)Hz Orchestra il processo di download da OMDB e salvataggio immediato nel database. Se viene fornita una connessione, la usa per operare all'interno di una transazione esistente. Altrimenti, ne crea una nuova e la gestisce autonomamente. NResponseFalseFz Nessun dato trovato su OMDB per rrr titolo_omdbTitlesinossiPlotrYear data_rilascioReleasedrRuntimerGenrerDirector sceneggiatoriWriterrActorslingueLanguagerCountrypremiAwardsrPoster metascore Metascore imdb_rating imdbRating imdbVotes BoxOffice Production) imdb_voti box_office produzione)rrrrrc3vK|]3}| p|dkV4dS)N/AN)r!)rIromdb_data_mappeds rrz*importa_e_salva_da_omdb..sa%%$''***N.>.B.B1.E.E.N%%%%%%r$)FzError getting data6SELECT id_media_fk FROM OmdbData WHERE id_media_fk = ?rrz scaricato_il = CURRENT_TIMESTAMPrrrz, scaricato_ilcg|]}dSrrGrIr?s rrKz+importa_e_salva_da_omdb..&s%K%K%Kac%K%K%Kr$z, CURRENT_TIMESTAMPc"g|] }|dkr|nd S)rNrGrms rrKz+importa_e_salva_da_omdb..'s$PPP177aaPPPr$rrrrszcodice_imdb = ?zimdb_search_status = NULLrz = COALESCE(z, ?)rrTz+Dati OMDB importati e salvati con successo.z"Dati OMDB aggiornati con successo.z#Errore in importa_e_salva_da_omdb: )rr!r5allrrOrrVrrrrrrkeysvaluesrQrrr2)rprrrryis_external_transaction _key_fieldsrTr+campi_da_aggiornarevalorirrrcampir _am_fieldsam_setsam_valsrrnrrs @rimporta_e_salva_da_omdbrs Ce%g..  HIMM*55@@GWGGGG|  .  IIKKKK  u 8 7  9==11  y}}V,,  y}}V44  Y]]:66   immI..  immG,,  y}}Z00  Y]]844  immH--  immJ//  Y]]9--  Y]]8,,  )--11 {33! " 9==66# $#{33#-- 44#-- 55)   0POO %%%%#%%% % % /.~  .  IIKKKKK    w#-D"83@jjjjll !%%%#C((( LxkZZZ  ."$ F"2"8"8":": D D]**'..&???MMFbLL&&dCCC  & &'I J J Ja4995H+I+IaaaE MM( # # # KKuV}} - - - -II.3355669IIE99%K%K3C3H3H3J3J%K%K%KLLOddLPP6F6M6M6O6OPPPFMUMMlMMME KKuV}} - - -[ $&AB) " "A $$Q''A "Q%ZZ!88888999q!!!x    Q$))G*<*< Q Q Q 'NN   '  JJLLL IIKKK&zBBVzz  .  IIKKKK    7A77888c!ff}  .  IIKKKK     .  IIKKKK  s=1S3F0S3 J8S33 U="T?U U?UUU# __nessuno__cddl}t}|}|dg}|D]g\} ||}t |tr|n|g}n#t$r|g}YnwxYw| d|Dh| |S)zDRestituisce tutti i path file allegati presenti in LogAggiornamenti.rNzbSELECT percorso_file FROM LogAggiornamenti WHERE percorso_file IS NOT NULL AND percorso_file != ''c38K|]}|t|VdSNr)rIr7s rrz)get_all_allegati_paths..\s-00a0SVV000000r$) rjrrOrrPrkrtrrrr5)_jsonrrTpathspfparsedrs rget_all_allegati_pathsrOs ''C **,,CKKtuuu E11 [[__F(66DFFVHEE   DEEE  00U0000000IIKKK Ls/B BBct}tj|_|}|dd|D}|g}|D]>}|ddkrt|d<d|d<n |d|d<| |?|S)zRestituisce i contesti che hanno almeno un evento LINE-UP distributor-level. Include una voce speciale per le LINE-UP senza contesto.a SELECT COALESCE(L.dettaglio_contesto, '') AS contesto, COUNT(*) AS num_lineup FROM LogAggiornamenti L JOIN TipiEvento TE ON L.id_tipo_evento_fk = TE.id_tipo_evento WHERE UPPER(TE.nome_evento) = 'LINE-UP' AND L.id_media_fk IS NULL GROUP BY COALESCE(L.dettaglio_contesto, '') ORDER BY contesto ASC c,g|]}t|SrGrrYs rrKz+get_contesti_con_lineup..rrr$rrz(Senza contesto)label) rrrrrOrrPr5LINEUP_NESSUN_CONTESTOr)rrTrrcrZs rget_contesti_con_lineupr!as ''CkCO **,,CKK      - ,S\\^^ , , ,DIIKKK F  Z=B  2AjM+AgJJ:AgJ a Mr$c.t}tj|_|}|t krd}d}nd}|f}|d|d|d|D}||S)z Restituisce tutti gli eventi LINE-UP associati a un contesto. Passare LINEUP_NESSUN_CONTESTO ('__nessuno__') per le LINE-UP senza contesto. z?AND (L.dettaglio_contesto IS NULL OR L.dettaglio_contesto = '')rGz8AND LOWER(COALESCE(L.dettaglio_contesto, '')) = LOWER(?)a SELECT COALESCE(D.nome, '') AS distributore, L.data_evento, L.note, L.percorso_file, L.id_aggiornamento FROM LogAggiornamenti L JOIN TipiEvento TE ON L.id_tipo_evento_fk = TE.id_tipo_evento LEFT JOIN Distributori D ON L.id_distributore_fk = D.id_distributore WHERE UPPER(TE.nome_evento) = 'LINE-UP' AND L.id_media_fk IS NULL zA ORDER BY LOWER(COALESCE(D.nome, '')), L.data_evento c,g|]}t|SrGrrYs rrKz*get_lineup_by_contesto..rr$) rrrrrOr rrPr5)rrrT conditionrrs rget_lineup_by_contestor%s ''CkCO **,,C)))U N KK   - ,S\\^^ , , ,DIIKKK Kr$rOct}tj|_|}|sd}g}|rD|r0|dz }|d|d|dz }||t|d| D}| |Sg}d}|rD|r0|dz }|d|d|dz }||t|d| D} | d } |rW|rC| d d } | d | d z } t| d} | jsT| d} ddg| _d| D}ng}i}| D]4}|dpd }|d|dddd||<5|D]`}|dpd }||vr(||dxx|dz cc<d||d<J|d|dddd||<ag}|D]G}|dr |drd}n |drd}nd}||d|d|dH|d|r|s |dd }|S)!a| Restituisce la lista dei distributori con conteggio prodotti associati. Args: nome: Filtro per nome distributore includi_gemma: Se True, include anche i distributori dalla tabella Gemma Note: - Con includi_gemma=True e senza filtro nome, limita a 100 risultati - Per vedere tutti i distributori Gemma, usare un filtro di ricerca ab SELECT distributore_nome AS nome, COUNT(id_media) as num_prodotti, 'mediatrack' AS fonte FROM AnagraficaMedia WHERE distributore_nome IS NOT NULL AND distributore_nome != '' AND id_media NOT LIKE 'G_%' AND deleted_at IS NULL z+ AND LOWER(distributore_nome) LIKE LOWER(?)rz: GROUP BY distributore_nome ORDER BY distributore_nome ASCc,g|]}t|SrGrrYs rrKz*get_distributori_lista..s000AQ000r$a: SELECT distributore_nome AS nome, COUNT(id_media) as num_prodotti, 'mediatrack' AS fonte FROM AnagraficaMedia WHERE distributore_nome IS NOT NULL AND distributore_nome != '' AND id_media NOT LIKE 'G_%' AND deleted_at IS NULL z GROUP BY distributore_nomec,g|]}t|SrGrrYs rrKz*get_distributori_lista..s///1tAww///r$3A_DISTRIBUTORE IS NOT NULL AND A_DISTRIBUTORE != ''rrz" AND LOWER(A_DISTRIBUTORE) LIKE '%z%'rrrO num_prodotticRg|]$\}}|dt|ddd%S)rOr*r?rOr*r)intrIr?rZs rrKz*get_distributori_lista..sMIIIhlhiklqy#a>O:P:P[bccIIIr$rTF)rOr*_mt_gemmar0r/entrambi mediatrackr?r,c:|dpdSrRrSr$s rr%z(get_distributori_lista..sQvY_"3355r$r&Nd)rrrrrOrrrrrPr5rr3rCr@ value_counts reset_indexr=r+r r,)rOrrrTrrr params_mtquery_mtrrYnome_escr3 gemma_countsr<mergedrZr'rnrs rget_distributori_listar<s  ''CkCO **,,C     /DJJLL / B BE MM-djjll--- . . . MM E5==)))00000  I H . .AA,TZZ\\,,,--- --HKK% **+++// ///GIIKKKDG E E::<<%%''//T::DDDDD '+; < < OX]imnnF3KK D ]]__\\ U8 (  EE uX  EEE QvY.8ITYZZ[[[[II55I666 tzz||DSDz Kr$id_distributorec t}tj|_|}|d|f|}||rt|ndS)z0Recupera i dettagli completi di un distributore.a? SELECT id_distributore, nome, nome_completo, paese, citta, email, telefono, persona_contatto, stato_rapporto, ultimo_contatto FROM Distributori WHERE id_distributore = ? N) rrrrrOrrr5r)r=rrTrJs rget_dettaglio_distributorer? su ''CkCO **,,CKK   ,,..CIIKKK %4999%r$c|dd}td|dd}|jrdS|t|dddS) z Recupera i dettagli limitati di un distributore dal database Gemma. Args: nome_distributore: Nome del distributore da cercare Returns: Dizionario con i dettagli del distributore Gemma o None rrzA_DISTRIBUTORE = 'rrNr?)rOr*rr=)r3rCr@r)r^r9r3s r get_dettaglio_distributore_gemmarA'sh!((d33H :x:::DT U U UC yt!C   r$ct}tj|_|}d}|||fd|D}||S)z6Recupera tutti i prodotti associati a un distributore.a WITH ultimi AS ( SELECT id_media_fk, dettaglio_contesto, costo_richiesto_ita, costo_richiesto_spa, ROW_NUMBER() OVER( PARTITION BY id_media_fk ORDER BY date(COALESCE(data_evento,'0001-01-01')) DESC, id_aggiornamento DESC ) rn FROM LogAggiornamenti ) SELECT A.id_media, A.titolo_ufficiale, A.tipo, O.url_poster, O.anno_produzione, U.dettaglio_contesto AS contesto, U.costo_richiesto_ita, U.costo_richiesto_spa FROM AnagraficaMedia A LEFT JOIN ultimi U ON A.id_media = U.id_media_fk AND U.rn = 1 LEFT JOIN OmdbData O ON A.id_media = O.id_media_fk WHERE A.distributore_nome = ? AND A.deleted_at IS NULL ORDER BY A.titolo_ufficiale ASC c,g|]}t|SrGrrYs rrKz-get_prodotti_distributore..arr$r)r=rrTrrs rget_prodotti_distributorerD<sn ''CkCO **,,C E<KK)*** , ,S\\^^ , , ,DIIKKK Kr$c|dd}td|dd}|jrgSd|dD}t }t j|_|}d d |D}|r9| d |d |d | D}ni}| g}| D]\} } dt| dz} |id| d| dddd| dd| dd| dd| dd| dd| dd | d!d"| d#d$| d%d&| d'd(| d)d*| d+d,| d-d.| d/d0|| i|d12|S)3z Recupera tutti i prodotti associati a un distributore dal database Gemma. Args: nome_distributore: Nome del distributore Returns: Lista di prodotti con fonte 'gemma' rrzLOWER(A_DISTRIBUTORE) = LOWER('r(ztA_ID, A_TITOLO, V_RDA, V_C5, V_I1, V_R4, V_LA5, V_I2, V_IRIS, V_TOP, V_FOC, V_C20, V_CI34, V_C27, A_SUPPORTO, A_DATArc2g|]}dt|zSrr)rIrxs rrKz3get_prodotti_distributore_gemma..xs";;;tc!ff};;;r$rrcg|]}dSrrGrs rrKz3get_prodotti_distributore_gemma..|s222QS222r$zCSELECT id_media_fk, url_poster FROM OmdbData WHERE id_media_fk IN (rc,i|]}|d|dS)rrrGrYs rrz3get_prodotti_distributore_gemma..s#PPPAa &,PPPr$rrprrrr?rrrrr r r r r rrrrrrrrrrrrrrrrrrrrcT|dpdSr"r#r$s rr%z1get_prodotti_distributore_gemma..s$QUU#566<"CCEEr$r&)r3rCr@r(rrrrrOrrrPr5r+r2rr!r,) r^r9r3id_listrrTr poster_maprr?rZrps rget_prodotti_distributore_gemmarLes!((d33H <<<<G   C y <;c&k&8&8&:&:;;;G ''CkCO **,,C8822'22233L iZfiiikrsssPPPPP  IIKKK D   1#ai..(   j 1 1  W  155>>  !%%--  !%%--   !%%--  155>>  !%%--  AEE(OO  w  155>>  155>>  aeeHoo  155>> l++! " h# $ *..22%      ( IIEEIFFF Kr$ct}tj|_|}d}|||fd|D}||S)a Recupera tutti i prodotti MediaTrack associati a un distributore per nome. Esclude i prodotti Gemma importati (ID che inizia con 'G_'). Args: nome_distributore: Nome del distributore Returns: Lista di prodotti con fonte 'mediatrack' a SELECT A.id_media, A.titolo_ufficiale, A.distributore_nome, A.tipo, 'mediatrack' AS fonte, A.ultimo_asking_ita, A.ultimo_asking_spa, O.url_poster FROM AnagraficaMedia A LEFT JOIN OmdbData O ON A.id_media = O.id_media_fk WHERE LOWER(A.distributore_nome) = LOWER(?) AND A.id_media NOT LIKE 'G_%' AND A.deleted_at IS NULL ORDER BY A.titolo_ufficiale ASC c,g|]}t|SrGrrYs rrKz5get_prodotti_by_distributore_nome..rr$r)r^rrTrrs r!get_prodotti_by_distributore_nomerOsq ''CkCO **,,C E$KK)+,,, , ,S\\^^ , , ,DIIKKK Kr$ct}t|tj|_|}|d|f|}|r|dnd}d}||||fg}|D]}t|}| dr.tj |d|d<n#t$rYnwxYw| dr.tj |d|d<n#t$rYnwxYw||||S)a Recupera tutti gli eventi di un distributore per nome: - eventi sui prodotti del distributore (id_media_fk) - eventi a livello distributore (id_distributore_fk) Args: nome_distributore: Nome del distributore Returns: Lista di eventi ESELECT id_distributore FROM Distributori WHERE LOWER(nome) = LOWER(?)r=a SELECT log.id_aggiornamento, log.id_aggiornamento as id_evento, log.id_media_fk as id_media, log.id_distributore_fk, log.id_tipo_evento_fk, log.data_evento, log.dettaglio_contesto, log.costo_richiesto_ita, log.costo_richiesto_spa, log.note, log.percorso_file, log.budget_produzione, log.data_reminder, log.preferito, log.venduto_a, log.privato, log.delivery, log.links, tipi.nome_evento as tipo_evento_nome, tipi.colore_hex, am.titolo_ufficiale, 'prodotto' as tipo_evento FROM LogAggiornamenti AS log JOIN TipiEvento AS tipi ON log.id_tipo_evento_fk = tipi.id_tipo_evento LEFT JOIN AnagraficaMedia AS am ON log.id_media_fk = am.id_media WHERE LOWER(am.distributore_nome) = LOWER(?) AND log.id_distributore_fk IS NULL UNION ALL SELECT log.id_aggiornamento, log.id_aggiornamento as id_evento, NULL as id_media, log.id_distributore_fk, log.id_tipo_evento_fk, log.data_evento, log.dettaglio_contesto, log.costo_richiesto_ita, log.costo_richiesto_spa, log.note, log.percorso_file, log.budget_produzione, log.data_reminder, log.preferito, log.venduto_a, log.privato, log.delivery, log.links, tipi.nome_evento as tipo_evento_nome, tipi.colore_hex, NULL as titolo_ufficiale, 'distributore' as tipo_evento FROM LogAggiornamenti AS log JOIN TipiEvento AS tipi ON log.id_tipo_evento_fk = tipi.id_tipo_evento WHERE log.id_distributore_fk = ? ORDER BY data_evento DESC, id_aggiornamento DESC rqr)r_ensure_distributori_tablerrrrOrrrPrr!rjrkrrr5) r^rrTdist_rowr=rrrZrJs rget_eventi_by_distributore_nomerUsk ''Cs###kCO **,,CKKWZkYmnnn||~~H5=Eh0112O< E|KK)?;<<< D \\^^1gg 77? # # #(, 33G(H(H_%% " " "dd " 777   # $ 3w< 8 8W " " "dd " CIIKKK Ks$C.. C;:C;D11 D>=D>c t|dd}td|dd}|jrdS|jd}idd t|d zd |d d |dd|dd|dd|dd|dd|dd|dd|dd|dd|dd |d!d"|d#d$|d%d&|d'd(|d)id*|d+d,|d-d.|d/d0|d1d2|d3d4|d5d6|d7d8|d9d:|d;d<|d=d>|d?d@|dAdB|dCdD|dEdF|dGdH|dIdJ|dK|dL|dM|dN|dO|dP|dQ|dR|dS|dTdU S)Vz Recupera i dettagli di un singolo prodotto dalla tabella Gemma. Args: gemma_id: ID del prodotto Gemma (senza prefisso G_) Returns: Dizionario con i dati del prodotto o None se non trovato rrzA_ID = 'arA_ID, A_TITOLO, A_DISTRIBUTORE, A_TIPOLOGIA, A_AUTORE_REGISTA, A_CAST, A_COD_IMDB, A_EPISODIO, A_SUPPORTO, A_NOTE_SUPPORTO, A_DEADLINE, A_NOTE_PUBBLICHE, A_NOTE_PRIVATE, A_DATA, V_RDA, V_C5, V_I1, V_R4, V_LA5, V_I2, V_IRIS, V_TOP, V_FOC, V_C20, V_CI34, V_C27, A_CIN, A_INF, A_EMO, A_ENE, A_COM, A_STO, A_CRI, A_ACT, V_CIN, V_INF, V_EMO, V_ENE, V_COM, V_STO, V_CRI, V_ACTrNrrprrrrdistributore_nomerrrrregistaA_AUTORE_REGISTAcastA_CASTrrepisodio A_EPISODIOrr note_supportoA_NOTE_SUPPORTOdeadline A_DEADLINEnote_pubblicheA_NOTE_PUBBLICHE note_privateA_NOTE_PRIVATErrrrrrr r r r r rrrrrrrrrrrrrrra_cinA_CINa_infA_INFa_emoA_EMOa_eneA_ENEa_comA_COMa_stoA_STOa_criA_CRIA_ACTV_CINV_INFV_EMOV_ENEV_COMV_STOV_CRIV_ACT) a_actv_cinv_infv_emov_enev_comv_stov_criv_act)r2r3rCr@ilocr!)rid_escr3rZs rget_prodotto_gemma_dettaglior&s]] " "3 - -F """" E   C yt  A D3qy>>) AEE*--  QUU#344  m$$   QUU=))   155+,,   h  quu\**  AEE,''  AEE,''  011  AEE,''  !%% 233  .//  AEE(OO  quuW~~!  $QUU6]]!   59!%%--! " aeeFmm# "#AEE'NN# "59!%%--# $ h% $"-aeeGnn% $?DQUU7^^% & quuW~~' & (x' &;Hw' ( w) (")!%%..) (;B155>>) * w+ *")!%%..+ *;B155>>+ , w-  ,+,%%..w!%%..155>>w!%%..155>>w!%%..3   r$include_prodottic~t}tj|_|}|r|d||fn|d|fg}|D]}t|}|dr.tj |d|d<n#t$rYnwxYw|dr.tj |d|d<n#t$rYnwxYw| || |S)a$ Recupera gli eventi associati a un distributore. Args: id_distributore: ID del distributore include_prodotti: Se True, include anche eventi dei prodotti del distributore Returns: Lista di eventi con campo 'tipo_evento' = 'distributore' o 'prodotto' a SELECT log.id_aggiornamento, log.id_media_fk, log.id_distributore_fk, log.id_tipo_evento_fk, log.data_evento, log.dettaglio_contesto, log.costo_richiesto_ita, log.costo_richiesto_spa, log.note, log.percorso_file, log.budget_produzione, log.data_reminder, log.preferito, log.venduto_a, log.privato, log.delivery, log.links, tipi.nome_evento, tipi.colore_hex, A.titolo_ufficiale, CASE WHEN log.id_distributore_fk IS NOT NULL THEN 'distributore' ELSE 'prodotto' END AS tipo_evento FROM LogAggiornamenti AS log JOIN TipiEvento AS tipi ON log.id_tipo_evento_fk = tipi.id_tipo_evento LEFT JOIN AnagraficaMedia AS A ON log.id_media_fk = A.id_media WHERE log.id_distributore_fk = ? OR (log.id_media_fk IS NOT NULL AND A.distributore_nome = ?) ORDER BY date(COALESCE(log.data_evento,'0001-01-01')) DESC, log.id_aggiornamento DESC a SELECT log.id_aggiornamento, log.id_media_fk, log.id_distributore_fk, log.id_tipo_evento_fk, log.data_evento, log.dettaglio_contesto, log.costo_richiesto_ita, log.costo_richiesto_spa, log.note, log.percorso_file, log.budget_produzione, log.data_reminder, log.preferito, log.venduto_a, log.privato, log.delivery, log.links, tipi.nome_evento, tipi.colore_hex, NULL AS titolo_ufficiale, 'distributore' AS tipo_evento FROM LogAggiornamenti AS log JOIN TipiEvento AS tipi ON log.id_tipo_evento_fk = tipi.id_tipo_evento WHERE log.id_distributore_fk = ? ORDER BY date(COALESCE(log.data_evento,'0001-01-01')) DESC, log.id_aggiornamento DESC rqr)rrrrrOrrPrr!rjrkrrr5)r=rrrTrrZrJs rget_eventi_distributorer[sY ''CkCO **,,C@!  @ /A 1 1 1 1F  6 7 ! ! !: D \\^^1gg 77? # # #(, 33G(H(H_%% " " "dd " 777   # $ 3w< 8 8W " " "dd " CIIKKK Ks$"C C  C %D DDchd} t}|}|d}|dkrd}|d}t|tr|rt j|nd}|d}|}t|trhg}|D]K}|r|nd}|r/|dsd|z}| |L|rt j|nd}| d||d ||d |d |d |d ||d|d|dd|d|dd|f|  |r| dSdS#tj$rZ} |r|t!d| dt#| fcYd} ~ |r| SSd} ~ wwxYw#|r| wwxYw)zWAggiunge un evento direttamente a un distributore (non legato a un prodotto specifico).Nrrrqrrrar INSERT INTO LogAggiornamenti (id_distributore_fk, id_tipo_evento_fk, data_evento, dettaglio_contesto, costo_richiesto_ita, costo_richiesto_spa, note, percorso_file, budget_produzione, data_reminder, preferito, venduto_a, privato, links) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) rrrrrrrrrrr)Tz*Evento distributore aggiunto con successo.z+Errore DB in aggiungi_evento_distributore: Fr) r=rrrrTrrqrrrrs raggiungi_evento_distributorers C:ggjjll"oom44 "  K$88 mT * * Q9FPDJ}555DM((  %&& S#%  55C),4#))+++"C5"~~.EFF3",s"2C(//4448HR #3444d    OO/ 0 0  OO0 1 1 OO1 2 2 OO1 2 2 OOF # #  OO/ 0 0 OOO , , OOK + + OOK ( ( OOIq ) )     , A   =   ?A??@@@c!ff}     s0H H,,J;8J3J4JJJJ1daticd} t}|}|dd|D}g}g}|D]D\}}||vr;|dkr5||d||dkr|ndE|s |r|dSdSdd |d } |||| t||  |r|d Sd S#tj $rP} |r| td | d d| fcYd} ~ |r|SSd} ~ wwxYw#|r|wwxYw)z#Modifica i dati di un distributore.NzPRAGMA table_info(Distributori)ch|] }|d SrFrGrHs rrz(modifica_distributore..s;;;S#a&;;;r$r=rrrzUPDATE Distributori SET rz WHERE id_distributore = ?)Tz%Distributore aggiornato con successo.z$Errore DB in modifica_distributore: Fr)rrOrrPrrr5rrrQrrRrr) r=rrrTcolonne_valider rrrrrs rmodifica_distributorers  Cggjjll 5666;;CLLNN;;; "jjll @ @NFF''F6G,G,G#**f???;;; " ff$???" >=  f4995H+I+Ieee o&&& E5==))) <   =222   8Q889991a11111111   2  s7B4EA%EF2".F-F2F5-F22F55Grctttjdz}|r|nd}|d|S)z Genera ID univoco per nuovo prodotto. Args: tipo: "Film", "Serie", "Miniserie", "Doc" Returns: str: es. "FILM-1728217845123" o "SERIE-1728217845123" MEDIA-)r2r-timerl)r timestampprefissos r_genera_id_mediar!sNC d*++,,I#0tzz|||H $ $ $ $$r$Tvalidate_distributor skip_eventocd d} |ds |r|dSdS|d}|r5tjd|s dd|ddf|r|SS|d }|r] tjd |s dd |d df|r|SSn&#dd |dfcY|r|SSxYwt }|}|d |dpd}t|} |d} | rs|r]t|| \} } | sE|d|d| df|r|SS| } n| } |d} |d}|d| |d|| |r|nd|d| ||d|d|d|d|d|d|d|d|dttf|s|dkrd}|d }|-t|tr|rtj|nd}|d!| |d"d#||d$| ||d%||d&|d'f |t%rZ|d(| f|t&d)| d*t+| ||t-d+| d,d-|dd.| f|r|SS#t.j$r}|r|t-d/|dt5|vr%dd0|d1dfcYd}~|r|SSdd2|dfcYd}~|r|SSd}~wt.j$rQ}|r|t-d3|dd4|dfcYd}~|r|SSd}~wt:$rQ}|r|t-d5|dd6|dfcYd}~|r|SSd}~wwxYw#|r|wwxYw)7a Crea un nuovo prodotto completo (anagrafica + primo evento) in una transazione atomica. Args: dati_anagrafica: { 'titolo_ufficiale': str (required), 'tipo': str (Film/Serie/Miniserie/Doc), 'distributore_nome': str (opzionale, crea se non esiste), 'codice_imdb': str (opzionale, formato ttXXXXXXX), 'budget_produzione': str (opzionale), 'formato': str (opzionale, solo per Serie, es. "8x45'"), 'piattaforma': str (opzionale, solo per Serie, es. "Netflix") } dati_evento: { 'id_tipo_evento_fk': int (default: 1 = Mercato), 'data_evento': str (ISO format YYYY-MM-DD), 'dettaglio_contesto': str (es. "EFM 2025"), 'costo_richiesto_ita': float (opzionale), 'costo_richiesto_spa': float (opzionale), 'note': str (opzionale), 'buyer_contatto': str (opzionale, es. "Andrea"), 'percorso_file': str (opzionale), 'delivery': str (opzionale, per tutti i tipi, es. "Q1 2025") } Returns: tuple[bool, str, str | None]: (successo, messaggio, id_media_creato) Note: - Genera id_media automaticamente: "FILM-{timestamp}" o "SERIE-{timestamp}" - Se distributore_nome non esiste, lo crea automaticamente - Gestisce la transazione: rollback completo se errore - Popola ultimo_asking_* dal primo evento se presenti Nr)FzTitolo obbligatorio mancanteNrz ^tt\d{7,}$FzCodice IMDb non valido: z. Formato atteso: ttXXXXXXXrz^\d{4}-\d{2}-\d{2}$zData evento non valida: z. Formato atteso: YYYY-MM-DDzBEGIN TRANSACTIONrFILMrWROLLBACKrra INSERT INTO AnagraficaMedia (id_media, titolo_ufficiale, tipo, distributore_nome, codice_imdb, budget_produzione, ultimo_asking_ita, ultimo_asking_spa, formato, piattaforma, anno_produzione, durata, paesi, generi, registi, attori, trama, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) rformato piattaformarrrrrrrrrqaI INSERT INTO LogAggiornamenti (id_media_fk, id_tipo_evento_fk, data_evento, dettaglio_contesto, costo_richiesto_ita, costo_richiesto_spa, note, percorso_file, buyer_contatto, delivery) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) rrrrrrzCUPDATE AnagraficaMedia SET is_offline_insert = 1 WHERE id_media = ?z[offline] Prodotto z marcato come offline insertz#[OK] Prodotto creato con successo: Trz' creato con successou2Errore integrità in inserisci_prodotto_completo: z Codice IMDb 'u' già esistente nel databaseuErrore di integrità: z*Errore DB in inserisci_prodotto_completo: rz3Errore inaspettato in inserisci_prodotto_completo: zErrore inaspettato: )r!r5rematchrrOrrrlrrrrrtrrjrrQrorinforrrIntegrityErrorrr2rrRr)rqrrrrrrrrTrrprWvalidorcultimo_asking_itaultimo_asking_sparqrs rinserisci_prodotto_completor0s3F CO""#566 ?>X  S&))-88  drx {CC d][]]]_ccN  I"oom44  M Mx 6 DDm "f["f"f"fhll~  Am MFFFLLLz  wggjjll '((( ##F++5v<<>>DDFF#D)),//0CDD  ># >!@FW!X!X/KK +++IIKKK &$.P  O%+!!%6$;$;$=$=!(OO,ABB'OO,ABB    . /  & 0KKD    3 4 4      * *    . .    1 2 2    ) )    ( (    ) )    * *    ) )    ( ( JJ OO'    > b  " 'OOO<H>>???\/2D"E\\\^ff.  +  !===   F1FFGGG CFFLLNN * *T+TTTVZZ Z Z Z Z Z  6166< < < < < <   =444   >1>>???-!--t333333   777   GAGGHHH0Q00$666666   7  sR44R4R4C R4 C1R41CR4(J4R44XAUXX3U:X;XX)/V5XX5 X/X1X2XXXX/c d} t}|}|d|f|}|s |r|dSdS|\}}}d}|r7t jd|t j}|r|d}|d|||tt|f| g} |? | dt|dn#ttf$rYnwxYw|? | d t|dn#ttf$rYnwxYw|r| d || rd d | nd } d| f|r|SS#t"j$rZ} |r|t)d| dt+| fcYd} ~ |r|SSd} ~ wwxYw#|r|wwxYw)u Aggiorna i campi ereditati in AnagraficaMedia dall'evento più recente. Args: id_media: ID del prodotto da aggiornare Returns: tuple[bool, str]: (successo, messaggio) Logica: 1. Trova l'evento più recente per questo prodotto (ORDER BY data_evento DESC) 2. Aggiorna AnagraficaMedia: - ultimo_asking_ita = costo_richiesto_ita (se non NULL) - ultimo_asking_spa = costo_richiesto_spa (se non NULL) - budget_produzione = estratto da note se contiene "bdg" o "budget" Pattern estrazione budget dalle note: - "bdg 7mln" -> "7mln" - "Bdg 35mln" -> "35mln" - "budget 12.5 mln" -> "12.5 mln" - Regex case-insensitive: r'b(?:dg|udget)\s*([0-9.,]+\s*mln)' Na SELECT costo_richiesto_ita, costo_richiesto_spa, note FROM LogAggiornamenti WHERE id_media_fk = ? ORDER BY CASE WHEN data_evento IS NULL THEN 1 ELSE 0 END, date(COALESCE(data_evento, '0001-01-01')) DESC, id_aggiornamento DESC LIMIT 1 )Fz)Nessun evento trovato per questo prodottoz b(?:dg|udget)\s*([0-9.,]+\s*mln)raf UPDATE AnagraficaMedia SET ultimo_asking_ita = COALESCE(?, ultimo_asking_ita), ultimo_asking_spa = COALESCE(?, ultimo_asking_spa), budget_produzione = COALESCE(?, budget_produzione), updated_at = ?, updated_by = ? WHERE id_media = ? uITA: €z,.0fuSPA: €zBudget: zCampi ereditati aggiornati: rNessun campo da aggiornareTz'Errore DB in aggiorna_campi_ereditati: F)rrOrrr5rsearch IGNORECASEgrouprrrQrfloatr TypeErrorrrrRrrr2) rprrTrw costo_ita costo_sparbudgetr aggiornamentimsgrs rrrs. CGggjjll  [    FEb  _&," 9d  (IA4WWE (Q  FHJJ  R T T T    $$%Gi0@0@%G%G%GHHHH *       $$%Gi0@0@%G%G%GHHHH *      6  !4F!4!4 5 5 5KXzGTYY}-E-EGGG^zSy   =   ;;;<<<c!ff}     sAG .B G <&D#"G #D74G 6D77G =&E$#G $E85G 7E88.y s888AQ888r$rrzA_COD_IMDB = 'z7A_ID, A_TITOLO, A_TIPOLOGIA, A_DISTRIBUTORE, A_COD_IMDBrrrrrrrrNGemma)rprrrWrrrrz%LOWER(titolo_ufficiale) LIKE LOWER(?)rz(LOWER(A.distributore_nome) LIKE LOWER(?)ra> SELECT A.id_media, A.titolo_ufficiale, A.tipo, A.distributore_nome AS distributore_nome, A.codice_imdb, A.ultimo_asking_ita, A.ultimo_asking_spa, 'MediaTrack' AS fonte FROM AnagraficaMedia A WHERE zW AND A.deleted_at IS NULL ORDER BY A.titolo_ufficiale ASC LIMIT 10 c,g|]}t|SrGrrYs rrKz/cerca_prodotto_per_matching.. s444DGG444r$rrz#LOWER(A_DISTRIBUTORE) LIKE LOWER('%)rrrrrOrrrPr2r3rCr@headr+rr!r5rr sort_values)rrrrrrTresultsimdb_escr3r?rZrwhere_conditions where_clausequery_anagraficagemma_where_partsr.r/s rcerca_prodotto_per_matchingrH s04 ''CkCO **,,CG'  ^    88888999  ;''//T::H2x222QC9 HHRLL1133  DAqNN$(3qy>>$9,-EE*,=,= !m 4 4-.UU3C-D-D'(uu\':':-1-1!( $ $     F % GHHH m&mmm$$$+ JKKK ),)))***   << 011LKK %--000 NN44S\\^^444555  QNN3--E  $ $%OU%O%O%O P P P  W ((d33E  $ $%U5%U%U%U V V V  ll#455QC9 ooj1166r::LLNN  DAqNN$(3qy>>$9,-EE*,=,= !m 4 4-.UU3C-D-D'(uu\':':-1-1!( $ $    IIKKK Nr$ct}|}|dd|D}||S)a] Recupera lista ordinata di buyer unici da LogAggiornamenti. Returns: Lista di nomi buyer (es. ["Andrea", "Clemente", "Mercedes"]) Query: SELECT DISTINCT buyer_contatto FROM LogAggiornamenti WHERE buyer_contatto IS NOT NULL AND TRIM(buyer_contatto) != '' ORDER BY buyer_contatto ASC z SELECT DISTINCT buyer_contatto FROM LogAggiornamenti WHERE buyer_contatto IS NOT NULL AND TRIM(buyer_contatto) != '' ORDER BY buyer_contatto ASC cg|] }|d SrKrGrHs rrKz$get_buyers_unici.. s / / /c!f / / /r$)rrOrrPr5)rrTbuyerss rget_buyers_unicir sd ''C **,,CKK   0 /  / / /FIIKKK Mr$annoctr tdkrtd|td}|r||d<|r||d< tjd|}||}|dd kr d |vr|d Sd S#tjj$r}td |Yd }~d Sd }~wwxYw) a_ Cerca un film su OMDB per titolo e (opzionalmente) anno. Usa l'endpoint di ricerca 's' di OMDB. Args: titolo: Il titolo del film da cercare. anno: L'anno di produzione (opzionale). tipo: Il tipo di media (es. 'movie', 'series'). Returns: Una lista di risultati di ricerca, o None se non trovato. rz Chiave API OMDB non configurata.)sapikeyytypezhttp://www.omdbapi.com/)rrTrueSearchNz4Errore nella richiesta a OMDB (ricerca per titolo): ) rrrr!rrjrrr)rrrrrrnrs rcerca_imdb_per_titolor s =<+>>>;<<<\ 2 2F s  v < 9&III!!###}} 88J  6 ) )h$.>.>> !t   / HQHHIIItttttsA"B C4C  Cdry_run years_limitc Xddl}t}tj|_|}d}||d|D}g}d}|,|dkr&|j j } | |z dz}|D]^d}  d} | rG t| } nY#t$r)} |dd| d | d Yd} ~ n+d} ~ wwxYwtd d } | r| dnd} | rd| vr| d}|r? | dddd}t%|}||krR|d| d|s6|d|dft'd||n6|dd| dd|d|dd z#t(t*f$rJ|dd| dd| ddd YwxYw|d| d|s6|d|dft'd||+t-fd|Ds|dd`|||S) u DEPRECATA: Funzione di associazione automatica IMDb. Sostituita da IMDb Linker Manuale (più preciso e controllabile). Mantenuta per retrocompatibilità ma non più utilizzata nell'interfaccia. Args: dry_run: Se True, non esegue l'aggiornamento ma restituisce solo cosa farebbe. years_limit: Se fornito, limita la ricerca ai prodotti degli ultimi N anni. Returns: Una lista di dizionari, ognuno rappresentante un'azione (trovato, non trovato, errore). rNaN SELECT A.id_media, A.titolo_ufficiale, A.codice_imdb FROM AnagraficaMedia A LEFT JOIN OmdbData O ON A.id_media = O.id_media_fk WHERE (A.codice_imdb IS NULL OR TRIM(A.codice_imdb) = '') OR (A.codice_imdb IS NOT NULL AND TRIM(A.codice_imdb) != '' AND O.id_media_fk IS NULL) c,g|]}t|SrGrrYs rrKz)associa_imdb_mancanti..> s???1tAww???r$rr NON_TROVATOzErrore download per : )statusrreasonrmovie)rimdbIDr0u–TROVATO)rr match_omdbz=UPDATE AnagraficaMedia SET codice_imdb = ? WHERE id_media = ?rp)rz Trovato 'rz ' ma l'anno (u) è precedente al rz ' ma l'anno 'u' non è valido.c3HK|]}|dddkVdS)rrpNrG)rIrZrs rrz(associa_imdb_mancanti..x s6YYQq}Z0HZ4HHYYYYYYr$)rr)rrrrrrOrrPrryearr!rrrrrrr-rrranyrQr5)rrrrrTrprodotti_da_controllarereport start_year current_yearrisultato_omdbimdb_id_preesistenterrisultati_omdbimdb_id_trovato anno_omdb_str anno_omdbrs @rassocia_imdb_mancantir sOOO ''CkCO **,,C  EKK?? ??? FJ;??}**,,1 !K/!3 +3O3O'||M::  K M!23G!H!H M M M HYKpDYKYKHIYKYKLLMMMMMMMM M38. rr$) rrrrrOrrrPr5rs rget_prodotti_senza_imdbr s ''CkCO **,,CS!!!KK   & - ,S\\^^ , , ,DIIKKK Kr$limit distributorcnt}tj|_|}t |d}|r!|d|dd|d|fn|d|d|fd|D}||S)u Restituisce i prodotti che hanno codice_imdb ma NON hanno ancora dati in OmdbData, oppure hanno uno stub (solo id_media_fk + codice_imdb, titolo_omdb NULL) scaricato più di 30 giorni fa — così i titoli non ancora su OMDB vengono ritentati periodicamente. Usato dal batch enrichment schedulato da Frank. Include sia prodotti MediaTrack (MT_) che Gemma (G_). Se distributor è specificato, filtra per nome distributore (LIKE). z A.codice_imdb IS NOT NULL AND TRIM(A.codice_imdb) != '' AND ( O.id_media_fk IS NULL OR (O.titolo_omdb IS NULL AND (O.scaricato_il IS NULL OR O.scaricato_il < datetime('now', '-30 day'))) ) z SELECT A.id_media, A.titolo_ufficiale, A.codice_imdb FROM AnagraficaMedia A LEFT JOIN OmdbData O ON A.id_media = O.id_media_fk WHERE zv AND A.distributore_nome LIKE ? ORDER BY A.titolo_ufficiale ASC LIMIT ? rzd ORDER BY A.id_media LIKE 'G_%' ASC, A.titolo_ufficiale ASC LIMIT ? c,g|]}t|SrGrrYs rrKz.get_prodotti_da_arricchire.. rr$) rrrrrOrVrrPr5)rrrrT base_wherers rget_prodotti_da_arricchirer s ''CkCO **,,C$$$J      !+   % ( * * * *      X    - ,S\\^^ , , ,DIIKKK Kr$ct}|}t||d|d}||S)uConta i prodotti con codice_imdb ma senza dati OmdbData (coda rimanente). Include anche stub 'error getting data' più vecchi di 30 giorni (da ritentare).a| SELECT COUNT(*) FROM AnagraficaMedia A LEFT JOIN OmdbData O ON A.id_media = O.id_media_fk WHERE A.codice_imdb IS NOT NULL AND TRIM(A.codice_imdb) != '' AND ( O.id_media_fk IS NULL OR (O.titolo_omdb IS NULL AND (O.scaricato_il IS NULL OR O.scaricato_il < datetime('now', '-30 day'))) ) r)rrOrVrrr5)rrTrs rcount_prodotti_da_arricchirer si ''C **,,C$$$KK      LLNN1 EIIKKK Lr$ct}|}|d|f|}||r|dr|ddkr|dndS)z2Legge url_poster da OmdbData per un dato id_media.z5SELECT url_poster FROM OmdbData WHERE id_media_fk = ?rrNrrOrrr5)rprrTrJs rget_poster_urlr sq ''C **,,CKKG(UUU ,,..CIIKKK ASV AA%3q66TAr$ct}|}|d|f|}||r|dr|ddkr|dndS)z`Legge url_poster da OmdbData per un dato codice_imdb (fallback per poster non ancora scaricati).z=SELECT url_poster FROM OmdbData WHERE codice_imdb = ? LIMIT 1rrNr)rrrTrJs rget_poster_url_by_imdbr ss ''C **,,CKKOR]Q_``` ,,..CIIKKK ASV AA%3q66TAr$c>t}|}t||d|f|s,|d||f||dS)z Inserisce un record stub in OmdbData con solo l'id_media_fk e codice_imdb. Serve a escludere il prodotto dalla coda del batch enrichment quando OMDB non trova il titolo (codice non valido o non ancora disponibile). rz^INSERT INTO OmdbData (id_media_fk, codice_imdb, scaricato_il) VALUES (?, ?, CURRENT_TIMESTAMP)N)rrOrVrrrQr5)rprrrTs rmark_omdb_not_foundr s ''C **,,C$$$KKH8+VVV <<>> l { #    IIKKKKKr$ct}|}|d|j}|||S)z Elimina i record OmdbData che sono stub vuoti (tutti i campi chiave NULL o N/A). Restituisce il numero di record eliminati. a DELETE FROM OmdbData WHERE (imdb_rating IS NULL OR imdb_rating IN ('N/A', 'N/D', '')) AND (registi IS NULL OR registi IN ('N/A', 'N/D', '')) AND (attori IS NULL OR attori IN ('N/A', 'N/D', '')) AND (sinossi IS NULL OR sinossi IN ('N/A', 'N/D', '')) AND (generi IS NULL OR generi IN ('N/A', 'N/D', '')) )rrOrrrQr5)rrTdeleteds rpurge_omdb_stubsr s] ''C **,,CKK   lGJJLLLIIKKK Nr$cnddl}t}|}t||j}|d||f||j }| |dkrdd|fSdS)a Imposta lo status a -1 (nessun match trovato) per un prodotto specifico e salva la data del controllo per permettere di riproporre dopo 30 giorni. Args: id_media: Identificativo del prodotto. Returns: (successo, messaggio) rNz UPDATE AnagraficaMedia SET imdb_search_status = -1, data_ultimo_controllo_imdb = ? WHERE id_media = ? F(Nessun prodotto aggiornato per id_media=)Tz+Prodotto marcato come non trovabile su OMDB) rrrOrrrr|rrQrr5)rprrrToggiupdateds rmarca_imdb_non_trovabiler( sOOO ''C **,,CS!!! =   * * , ,DKK    JJLLLlGIIKKK!||KKKKK > >r$ct}|}t||d|f||j}||dkrdd|fSdS)z Imposta lo status a 1 (match trovato) per un prodotto specifico e resetta la data di ultimo controllo. Args: id_media: Identificativo del prodotto. Returns: (successo, messaggio) z UPDATE AnagraficaMedia SET imdb_search_status = 1, data_ultimo_controllo_imdb = NULL WHERE id_media = ? rFr)Tz'Prodotto marcato come trovabile su OMDB)rrOrrrQrr5)rprrTrs rmarca_imdb_match_trovatorI s ''C **,,CS!!!KK   JJLLLlGIIKKK!||KKKKK : :r$c  d}d}d}t|D]}d} td|dzd|dtjtd }|d  |d n#t $rYnwxYw|d |}td|d|j}| td|dtd}|j r"| dd|dd|ddcSd}d} t|} td| dt|dD]U\} \} } t!| d}| d}| dp| dpd }|r!t!|nd }| d!}| d"}|d#d$|z||||f|dz }| |zdkr4| td%| d| d&| | z d'zd(d)#t $r9}td*| dd+d,|| dz } Yd}~Od}~wwxYw| td-|d.| d/| d||| d0|d1|d2| d/dcS#tj$r}|r1 || n#YnxYwt!|}d3|vsd4|vrR||dz kr6td5|dzd|d6|d7ddl}|j|Yd}~d8dddd9|d:dcYd}~cStd;|d8dddd|d7ddl}|j|Yd}~d8dddd?t!|dcYd}~cSd}~wwxYwd8dddd@dS)Aus Sincronizza i prodotti dalla tabella Gemma nella tabella AnagraficaMedia. PRIMA cancella tutti i prodotti Gemma già importati, poi importa tutti i prodotti da Gemma. Versione migliorata con: - Timeout aumentato a 60s per operazioni lunghe - Retry automatico (3 tentativi) per gestire lock temporanei - Commit in batch (ogni 1000 record) per ridurre tempo di lock - Gestione errori migliorata con messaggi chiari Returns: dict: { 'success': bool, 'imported': int, 'deleted': int, 'errors': int, 'message': str } rNz[Import Gemma] Tentativo rr'z...gN@r r r ruE==> Cancellazione prodotti Gemma già importati da AnagraficaMedia...z5DELETE FROM AnagraficaMedia WHERE id_media LIKE 'G_%'z==> Cancellati z" prodotti Gemma da AnagraficaMediaz?A_ID, A_TITOLO, A_TIPO, A_TIPOLOGIA, A_DISTRIBUTORE, A_COD_IMDB)r=Trz Cancellati z> prodotti, ma nessun prodotto trovato in Gemma per reimportare)successimportedrerrorsmessagez==> Importazione di z prodotti da Gemma...)startrrA_TIPOrrrrz INSERT OR IGNORE INTO AnagraficaMedia (id_media, titolo_ufficiale, tipo, distributore_nome, codice_imdb) VALUES (?, ?, ?, ?, ?) rz==> Progress: z prodotti importati (r4z.1fz%)zErrore nell'importazione di zN/Drz==> Import completato: z prodotti importati, z errorizImport completato: z cancellati, z importati, zdatabase is lockedlockedz$[WARN] Database bloccato (tentativo z), riprovo tra zs...FzDatabase bloccato dopo zm tentativi. Assicurati che MediaTrack non sia in uso da altri utenti o processi. Chiudi MediaTrack e riprova.z&[ERROR] Errore operazionale database: rz.[ERROR] Errore generale nell'import da Gemma: z[WARN] Riprovo tra zErrore durante import: z8Errore imprevisto: massimo numero di tentativi raggiunto)r)rrrrrrrOrrQrCr@r5r enumerater+r2r!rlOperationalErrorrrrsleep) max_retries retry_delay batch_sizeattemptrrT deleted_countr3r r total_productsidxr?rJid_gemmartipo_rawrrrr error_msgrs rimporta_da_gemmarh s(KKJ%%PPN  LgkLLKLLL M M M/->>>C KK1 2 2 2  78888     KK5 6 6 6**,,C Y Z Z Z KKO P P PLM JJLLL UMUUU V V V$efffCy  # !,z]zzz HF XXN NNNN O O O"+3<<>>!C!C!C  Xa "3v;//H WWZ00F"wwx00TCGGM4J4JTfH4<H3x==..000&D#&77+;#<#>>  ;''' % ! AQAA # 6M   sA L2&A<;L2< B L2B  B4L2AL2DJL2 K$.KL2KAL22S P (M-,P -M1/A'P  P 'S /)P S  S -S0(QSQAS(S?S S c |}|d|dS#tj$rYdSwxYw)z.I sTTT!TTTr$zPRAGMA table_info(Contesti)id_tipo_evento_defaultz>ALTER TABLE Contesti ADD COLUMN id_tipo_evento_default INTEGERzSELECT COUNT(*) FROM Contestir))AFM 2025r$rr)EFM 2026r%r) Mipcom2025r'r)CFF 2026r)r()ACQUISTINr2)zHoldback - 2 windowNr<zVINSERT OR IGNORE INTO Contesti (nome, gemma_supporto, attivo, ordine) VALUES (?,?,?,?)N)rOrrQrPrr*rrR)rrTrUrrs rrr: s0" jjll      TTckk*GHHQQSSTTT #4 / / KKX Y Y Y JJLLL ;<<EEGGJ A::F OOh    JJLLLLL : =     sC.i sr W W WMNAaD!A$!A$RSTURVbcdebfCDEFCG H H W W Wr$rgrhs r get_contestir6a su ''C3 ;;~  hjj IIKKK W WRV W W WWr$r3r5r#c t}t||d||r|nd||f||dS#t j$rYdSt$r}dt|fcYd}~Sd}~wwxYw)NzfINSERT INTO Contesti (nome, gemma_supporto, attivo, ordine, id_tipo_evento_default) VALUES (?,?,1,?,?))TzContesto aggiunto.FuNome già esistente.F) rrrrrQr5rrrr2)rOr3r5r#rrs raggiungi_contestor9l s ggs### t ZZ\\^M>//111vWm n     ))  !---,,, c!ff}s$BB C C&B<6C<Cr2r4c : t}t||d|f}|r|dnd}|} |d| |r|nd||||f|r|| kr|d| |f||dS#tj$rYdSt$r} dt| fcYd} ~ Sd} ~ wwxYw)Nz-SELECT nome FROM Contesti WHERE id_contesto=?rznUPDATE Contesti SET nome=?, gemma_supporto=?, attivo=?, ordine=?, id_tipo_evento_default=? WHERE id_contesto=?zKUPDATE LogAggiornamenti SET dettaglio_contesto=? WHERE dettaglio_contesto=?)TzContesto aggiornato.r8F) rrrrrrQr5rrrr2) r2rOr3r4r5r#rrJ vecchio_nome nuovo_nomers raggiorna_contestor=} s[ggs###kkIK>ZZccee!$.s1vv$ ZZ\\  | >K--///tVU[]svA B     LJ66 KK]\*     ++  !---,,, c!ff}s$C C$$D6 D?DDDc t}t||d|f||dS#t $r}dt |fcYd}~Sd}~wwxYw)Nz(DELETE FROM Contesti WHERE id_contesto=?)TzContesto eliminato.F)rrrrQr5rr2)r2rrs relimina_contestor? sggs### >OOO  ** c!ff}sAA B*B:BBc*t}t|tj|_|}|d|f|}||rt|ndS)z Recupera un distributore dal nome. Args: nome: Nome del distributore Returns: Dizionario con id_distributore e nome, oppure None se non trovato zy SELECT id_distributore, nome FROM Distributori WHERE LOWER(nome) = LOWER(?) LIMIT 1 N) rrSrrrrOrrr5r)rOrrTrJs rget_distributore_by_nomerA s ''Cs###kCO **,,CKK   ,,..CIIKKK %4999%r$ct}t|tj|_|}|d|f|}|r |d}n2|d|f||j }| |S)z Recupera o crea un distributore per nome. Se non esiste in Distributori, lo crea automaticamente. Returns: id_distributore (int) rQr=z*INSERT INTO Distributori (nome) VALUES (?)) rrSrrrrOrrrQ lastrowidr5)rOrrTrJid_dists rget_or_create_distributorerE s ''Cs###kCO **,,CKKWZ^Y`aaa ,,..C  '( @4'JJJ -IIKKK Nr$rrWcV|rt|dkrggdSt}tj|_|}d|d}g}|r@|r,|d|||fn|d||fd|D}| dd}d |d }|rE|r1| dd} |d | d z }t|d } | j sG| d |} d| D} ng} ||| dS)a: Ricerca intelligente prodotto in MediaTrack e Gemma (search-as-you-type). Usa ricerca substring (SQL LIKE) senza fuzzy matching. AGGIORNATO: Post-migrazione V11 - usa distributore_nome (fonte unica: Gemma) Args: titolo: Titolo da cercare (anche parziale) distributore_nome: Nome distributore per filtrare (opzionale, fonte unica: Gemma) limit: Numero massimo risultati per archivio Returns: { 'mediatrack': [ { 'id_media': 'FILM-123', 'titolo_ufficiale': 'XENO', 'tipo': 'Film', 'anno': '2024', 'distributore_nome': 'Blue Fox', 'codice_imdb': 'tt12345' } ], 'gemma': [ { 'id': 'G_ABC123', 'titolo': 'XENO', 'distributore': 'Blue Fox', 'tipo': 'Film', 'codice_imdb': 'tt12345' } ] } r )r2r?raU SELECT am.id_media, am.titolo_ufficiale, am.tipo, am.codice_imdb, am.distributore_nome, od.anno_produzione as anno FROM AnagraficaMedia am LEFT JOIN OmdbData od ON am.id_media = od.id_media_fk WHERE am.titolo_ufficiale LIKE ? COLLATE NOCASE AND LOWER(am.distributore_nome) = LOWER(?) AND am.id_media NOT LIKE 'G_%' AND am.deleted_at IS NULL ORDER BY am.titolo_ufficiale LIMIT ? a SELECT am.id_media, am.titolo_ufficiale, am.tipo, am.codice_imdb, am.distributore_nome, od.anno_produzione as anno FROM AnagraficaMedia am LEFT JOIN OmdbData od ON am.id_media = od.id_media_fk WHERE am.titolo_ufficiale LIKE ? COLLATE NOCASE AND am.id_media NOT LIKE 'G_%' AND am.deleted_at IS NULL ORDER BY am.titolo_ufficiale LIMIT ? c,g|]}t|SrGrrHs rrKz*ricerca_prodotto_smart../ s>>>$s))>>>r$rrrrz$ AND LOWER(A_DISTRIBUTORE) = LOWER('r(z7A_ID, A_TITOLO, A_DISTRIBUTORE, A_TIPOLOGIA, A_COD_IMDBrrc g|]o\}}dt|dz|d|d|d|ddpS)rrrrrr)idrrrr)r2r!r.s rrKz*ricerca_prodotto_smart..= s    1 S6^^+%% ++ !&6 7 7m,, uu\22       r$)rrrrrrOrrrPr3rCr@rrr+r5) rrWrrrTsearch_patternmediatrack_results titolo_esc gemma_wheredist_escgdf_g gemma_resultss rricerca_prodotto_smartrQ sF /S[[1__ 2... ''CkCO **,,C#]]]N$&.4466$&  /5577 ?! A A A A&  e $ & & &"?>s||~~>>>T**JA*AAAKK.4466K$**,,44S$??JhJJJJ I   E ; !!*--22599   ((     IIKKK)  r$cddl}ddlm}ddlm} |t}|jdz }|d|jd}|d |d z }| ||td |t}| }| d |d} | d |d} | d|d} td| dtd| dtd| dtd| dtd| dtd| d| d||d| d| d| d|j} td | d| fS#t$$rh} td!| d"t'vr(||d#d$t+| fcYd} ~ Sd} ~ wwxYw)%u Reset COMPLETO MediaTrack. ELIMINA: - AnagraficaMedia (tutti i prodotti MediaTrack) - LogAggiornamenti (tutti gli eventi prodotti) - LogEventiDistributore (eventi distributori) - OmdbData (cache dati OMDB) MANTIENE: - TipiEvento (configurazione base tipi evento) - gemma (database read-only, fonte unica) ATTENZIONE: Questa operazione è IRREVERSIBILE! Creare sempre backup prima di eseguire. Returns: (success, message) rNrrbackupsTexist_ok %Y%m%d_%H%M%Smediatrack_pre_reset_.dbz[BACKUP] Creato backup: z$SELECT COUNT(*) FROM AnagraficaMedia%SELECT COUNT(*) FROM LogAggiornamentizSELECT COUNT(*) FROM OmdbDataz.[RESET] Disabilitazione vincoli FOREIGN KEY...zPRAGMA foreign_keys = OFFz [RESET] Eliminazione OmdbData...zDELETE FROM OmdbDataz([RESET] Eliminazione LogAggiornamenti...zDELETE FROM LogAggiornamentiz-[RESET] Eliminazione LogEventiDistributore...z!DELETE FROM LogEventiDistributorez'[RESET] Eliminazione AnagraficaMedia...zDELETE FROM AnagraficaMediaz-[RESET] Riabilitazione vincoli FOREIGN KEY...zPRAGMA foreign_keys = ONzy DELETE FROM sqlite_sequence WHERE name IN ('LogAggiornamenti', 'LogEventiDistributore') z(Reset MediaTrack completato! Eliminati: z prodotti, z log eventi, z dati OMDB. Backup salvato: z[OK] z[ERRORE] Reset fallito: rFzErrore durante reset: )shutilpathlibrrrparentmkdirr{strftimecopy2rrrOrrrQr5namerlocalsrr2)rZrrdb_path backup_dirr backup_pathrrTcount_prodotti count_log count_omdbrrs rreset_mediatrack_completorhX sG(MMM!!!!!!G8$}%%^i/ $''' HLNN++O<<  #I9#I#I#II  Wk*** 666777ggjjll :;;;* ;<<<LLNN1%  3444\\^^A&  >??? /000 0111 *+++ 8999 2333 =>>> 7888 7888 1222 =>>> .///       2( 2 25> 2 2 2 2 +/ 2 2  g   W} 888 ,,,--- FHH   LLNNN IIKKK7s1vv777777777 8sI/J K4 AK/)K4/K4c t}|}|d|d}||r|ndS#t j$r(dtvr|YdSt$rA}td|dtvr|Yd}~dSd}~wwxYw)z Recupera versione schema corrente dal database. Returns: int: Versione schema corrente (es: 3) Se tabella schema_version non esiste, ritorna 1 (legacy) 'SELECT MAX(version) FROM schema_versionrrrz$[ERROR] get_current_schema_version: N) rrOrrr5rrrarr)rrTversionrs rget_current_schema_versionrl sggjjll =>>>,,..# !(wwq(  # FHH   IIKKKqq  8Q88999 FHH   IIKKKqqqqq sA*A--3C-# C-,6C((C-rdcPddl}ddlm} ||j}|jd|}|r"t |dS tjd|dd }| }| d | d}| |r|ndS#d tvr | n#YnxYwYdSxYw#t$r}td |Yd}~dSd}~wwxYw) aN Estrae versione schema da un file backup. Prova prima a estrarre dal filename (_vX_), poi interroga la tabella schema_version nel backup stesso. Args: backup_path: Path completo al file backup Returns: int: Versione schema del backup (es: 3) Se non trovata, assume v1 (legacy) rNrz_v(\d+)_rfile:?mode=roTurirjrz#[ERROR] get_backup_schema_version: )rr[rr`rr-rrrrOrrr5rarr) rdrrfilenamerrrTrkrs rget_backup_schema_versionrs sbIII4 $$) +x00  'u{{1~~&& & /"?+"?"?"?TJJJC**,,C KKA B B BllnnQ'G IIKKK%,771 ,   IIKKKKD11  7A77888qqqqqsIAC>A6C C;C10C;1C53C;8C>;C>> D%D  D% backup_schemacurrent_schemac\||kr ddd|dfS||kr ddd|d|d fSdd d |d |d fS)u Verifica compatibilità schema tra backup e database corrente. POLITICA: - Schema UGUALE: OK, restore diretto - Schema MINORE (backup < current): WARNING, potrebbe mancare dati post-backup - Schema MAGGIORE (backup > current): ERROR, backup da versione futura (blocca) Args: backup_schema: Versione schema del backup current_schema: Versione schema del database corrente Returns: tuple: (is_compatible, severity, message) severity: "ok" | "warning" | "error" TokzSchema compatibile (vrFerrorz!INCOMPATIBILE: Backup ha schema vz, database corrente ha vze. Impossibile ripristinare backup da versione futura. Aggiorna l'applicazione alla versione corretta.rzSchema NON IDENTICO: Backup vz, database corrente vz. Il restore funzionera' ma potrebbero mancare colonne/tabelle aggiunte dopo il backup. Verifica la compatibilita' prima di procedere.rG)rtrus rcheck_schema_compatibilityrys"&&TD>DDDDD~%%g ?  ? ?%3 ? ? ?    :  : :N : : : r$notacddl}|r|sdS|}|dd}|jdd|}|dd}|S)uq Sanitizza nota per uso sicuro in filename. Regole: - Solo alfanumerici, underscore, trattini - Spazi convertiti in underscore - Max 30 caratteri - Lowercase Args: nota: Nota descrittiva dall'utente Returns: str: Nota sanitizzata per filename (es: "prima_pulizia") Stringa vuota se nota è None/vuota rNrrr?z [^a-z0-9_-]r()rrrr3sub)rzrs r sanitize_notar}2s"III tzz||r ::<<    D <<S ! !D 26."d + +D 9D Kr$cddl}ddlm}ddlm} |t}|jdz }|dt}|j d}|rt|nd }|r d |d |d |d } n d |d |d } || z } | || tj t| } | } | d | d} | | dkr| dS| j}|dz }d| d|dd|d}t-d|d|| fS#t.$rI}t-d|ddl}|ddt|dfcYd}~Sd}~wwxYw)aE Crea backup manuale del database con versione schema. Il filename include: - Tipo: 'manual' - Versione schema corrente (es: v3) - Nota sanitizzata (opzionale) - Timestamp Formato: mediatrack_manual_v3_nota_20251030_143052.db Args: nota: Nota descrittiva opzionale (max 30 char, sanitizzata) Returns: tuple: (success, message, backup_filename) success: True se backup creato con successo message: Messaggio descrittivo backup_filename: Nome file backup creato (None se error) rNrrrSTrTrVrmediatrack_manual_vr?rXPRAGMA integrity_checkrw)Fz&Backup fallito: integrita' compromessaNzBackup creato con successo: z (.2fz MB, schema vrz [BACKUP] z[ERROR] backup_database: FzErrore durante backup: )rZr[rrrr\r]rlr{r^r}r_rrr2rOrrr5unlinkr-st_sizerr traceback print_exc)rzrZrrrbrcschema_versionrnota_sanitizedrrrd con_backup cur_backup integrity size_bytessize_mbrrrs rbackup_databaserWs*MMM!!!!!!3?$}%%^i/ $'''455!HLNN++O<< 15<t,,,"  M]^]]n]]y]]]HHL^LLiLLLH 8+   Wk***_S%5%566 &&(( 3444''))!,        HH!%%''/  ,hhhWhhhWehhh #'##$$$Wh&& ??? -!--...8A88$>>>>>>> ?s%D8F AF G!>GG!G!cddlm}ddlm}ddl} |tjdz }|sgSg}|jd}|dD]}| |j }|s | \}} } } | rt| nd} |j | d } | d } n #| } YnxYw|j}|d z }|d d } t#jd|dd}|}|d|d}|d|d}|n5#t.$r(}t1d|j d|d}d}Yd}~nd}~wwxYw||j || r| nd| | | t5|d|||t7|d |dd|S#t.$r6}t1d|ddl}|gcYd}~Sd}~wwxYw)u Recupera lista completa backup con metadata. Ogni backup include: - filename, tipo, nota, timestamp, data_creazione - schema_version: Versione schema del backup - size_mb, size_human: Dimensione file - num_prodotti, num_eventi: Conteggi record (query sul backup) - path_completo: Path assoluto al file Returns: list: Lista dizionari ordinata per timestamp DESC (più recenti prima) rrrNrSzU^mediatrack_(manual|auto_restore|pre_reset)(?:_v(\d+))?(?:_(.+?))?_(\d{8}_\d{6})\.db$zmediatrack_*.dbrrVz%d/%m/%Y %H:%M:%Srr MBrnroTrpBSELECT COUNT(*) FROM AnagraficaMedia WHERE id_media NOT LIKE 'G_%'rYz.[WARNING] Impossibile leggere metadata backup rrRr ) rrrrzrdata_creazionerr size_humanr*r path_completoc|dS)NrrGr$s rr%zlist_backups..s 1[>r$)r'reversez[ERROR] list_backups: )r[rrrrr\r+compileglobrr`groupsr-strptimer^r-rrrrOrrr5rrrroundr2r,rr)rrrrcrSpattern file_pathrrrrzrdtrrrrrrTr*rrrs r list_backupsrs+!!!!!! IIIQT-((/);   "" I"* #  $):;;6 6 IMM).11E 49LLNN 1D.$ 5CIS000N +&X&y/BB!#-@!A!A +!*#))1J K0G#,,,,J o&Ai&A&A&AtLLLjjll `aaa"||~~a0  CDDD \\^^A.     \y~\\YZ\\]]]!   NN%N $.$&"0"0 !,,( ,(!$Y        114 @@@  *q**+++ sh-IA3I4&CIC!)I B F*)I* G4GIGA#I J +I;5J;Jc  t}|s ddddddddStd|D}t|t|d|dd|dd td |Dtd |Dtd |DdS#t$r&}t d |ddddddddcYd}~Sd}~wwxYw)af Recupera statistiche aggregate sui backup. Returns: dict: { "total_backups": 12, "total_size_mb": 45.67, "total_size_human": "45.67 MB", "ultimo_backup": "30/10/2025 16:35:41", "manual_count": 8, "auto_restore_count": 3, "pre_reset_count": 1 } rz0 MBN) total_backups total_size_mbtotal_size_human ultimo_backup manual_countauto_restore_countpre_reset_countc3&K|] }|dV dS)rNrGrIbs rrz#get_backup_stats..)s&77!9777777r$r rrrc*g|]}|ddk|S)rmanualrGrs rrKz$get_backup_stats..0s& M M MqqyH7L7L7L7L7Lr$c*g|]}|ddk|S)r auto_restorerGrs rrKz$get_backup_stats..1s&&Y&Y&YQQvY.=X=Xq=X=X=Xr$c*g|]}|ddk|S)r pre_resetrGrs rrKz$get_backup_stats..2s&#S#S#S!!F){:R:RA:R:R:Rr$z[ERROR] get_backup_stats: rR)rsumrrrr)rS total_sizers rget_backup_statsr s`$ .. !"!"$*!% !&'#$ 77w77777 !\\":q11#- 6 6 6 6$QZ(89 M MG M M MNN"%&Y&Y'&Y&Y&Y"Z"Z"#S#Sw#S#S#STT         .1../// '!"#           s#B/BB// C9CCCbackup_filenameforcec@ ddl}ddlm}ddlm}ddl} |t }|jdz }||z }|s dd|difStt|} t} t| | \} } } | | | | | d}td | d | | d krd| d|fS| d kr |s dd | d|fStd tjd|dd}|}|d|d}||dkrddd|fSn-#t&$r }ddt|d|fcYd}~Sd}~wwxYwtd|jd}t}d|d|d}||z } |||td|n-#t&$r }ddt|d|fcYd}~Sd}~wwxYwtd|td| |||td n#t&$r}td! |||td"dd#t|||fcYd}~S#t&$r'}td$|dd%||fcYd}~cYd}~Sd}~wwxYwd}~wwxYwtd& t1}|}|d|d}|dkr?td'||||dd(||fS|d)|d} |d*|dpd+}n#tj$r| }YnwxYw|td,|d-|n?#t&$r2}td.|dd/t|||fcYd}~Sd}~wwxYwd0|d1|d2|d3|}td4|d|||fS#t&$rJ}td5|ddl}|dd6t|difcYd}~Sd}~wwxYw)7u Ripristina database da backup con verifica compatibilità schema. ATTENZIONE: Operazione CRITICA! - Verifica compatibilità schema (blocca se backup da versione futura) - Crea backup automatico pre-restore OBBLIGATORIO - Sostituisce database corrente - Valida integrità pre e post-restore - Rollback automatico in caso di errore Args: backup_filename: Nome file backup da ripristinare (solo filename, no path) force: Se True, procede anche con warning compatibilità (non ignora errori) Returns: tuple: (success, message, auto_backup_filename, compatibility_info) compatibility_info: { "backup_schema": 3, "current_schema": 3, "is_compatible": True, "severity": "ok"|"warning"|"error", "message": "..." } rNrrrSFBackup non trovato: )rtru is_compatibleseverityrz![RESTORE] Compatibilita' schema: z - rxrzConferma richiesta: z3[RESTORE] Validazione integrita' backup sorgente...rnroTrprrwz9Backup sorgente corrotto (PRAGMA integrity_check fallito)z&Impossibile validare backup sorgente: z4[RESTORE] Creazione backup automatico pre-restore...rVmediatrack_auto_restore_vr?rXz%[RESTORE] Backup pre-restore creato: z7ERRORE CRITICO: impossibile creare backup pre-restore: z*[RESTORE] Chiusura connessioni database...z[RESTORE] Ripristino da: z*[RESTORE] Database sostituito con successoz8[RESTORE] ERRORE durante restore, esecuzione rollback...zG[RESTORE] Rollback completato, database ripristinato a stato precedentez%Restore fallito (rollback eseguito): z,[RESTORE] ERRORE CRITICO: rollback fallito! zVERRORE CRITICO: restore fallito E rollback fallito! Database potrebbe essere corrotto!z0[RESTORE] Validazione integrita' post-restore...zE[RESTORE] Integrita' compromessa post-restore, esecuzione rollback...zQRestore fallito: integrita' database compromessa post-restore (rollback eseguito)rrjrz%[RESTORE] Validazione OK - Prodotti: z , Schema: vz,[RESTORE] Validazione post-restore fallita: z+Restore completato ma validazione fallita: z;Restore completato con successo! Database ripristinato da: z . Schema: vz , Prodotti: z. Backup pre-restore salvato: z [RESTORE] z[ERROR] restore_database: z$Errore inaspettato durante restore: )rZr[rrgcrr\r+rsr2rlryrrrrOrrr5rr{r^r_collectrrrr)rrrZrrrrbrc source_backuprtrurrcompat_messagecompatibility_info con_source cur_sourcerrr auto_schemaauto_backup_filenameauto_backup_pathrollback_error con_restored cur_restoredr*restored_schemarrs rrestore_databaserBs2MMM!!!!!! IIIMP$}%%^i/ "_4 ##%% MBBBD"L L2#m2D2DEE 3552L >3 3 / x +,* %    O(OO~OOPPP w  .$0BB B y  AAA4I[[ [ CDDD f )H)H)H)HdSSSJ#**,,J   7 8 8 8"++--a0I      D  Y[_asss! f f fK3q66KKTSee e e e e e e f DEEE HLNN++O<< 122 W;WWWWW%(<< w LL"2 3 3 3 P:NPP Q Q Q Q w w w\TWXYTZTZ\\^bdvv v v v v v v w :;;;  ;/;;<<< b LL 0 0 0 ? @ @ @ @ b b b M N N N b -w777_```Nc!ffNNPdfxxxxxxxx b b bU^UUVVVwzNPbbbbbbbbbbbbb b b @AAA" { 77L'..00L  !9 : : :$--//2ID  ^___""$$$ -w777qtHJ\\\  !e f f f'002215L 0$$%NOOO"."7"7"9"9!"<"A+ 0 0 0"/ 0    d,ddSbdd e e e e { { { DDD E E EPAPPRfhzz z z z z z z {  B)8 B B' B B5A B B,@ B B  $7$$%%%W24FFF PPP .1..///ESVVEEtROOOOOOO PsS;S A&S 9S S AT-((/);  ?2 !!## CBBBB B**=99 @??;**3z??;; EDD  =O==>>>;/;;;; >>> +++,,,=SVV=========>s/7B:B:/B: ,B:: C4%C/)C4/C4 root_pathc " ddl}||}|s dd|dgdS|s dd|dgdSg}|dD]}|s |j}t|dz d } | |}t|}n#t$rt|}YnwxYw|j j } ||j t||| |d #t $r} t#d |d | Yd} ~ d} ~ wwxYw|d t#dt'|d|dt'||dS#t $rH} t#d| ddl} | dt| dgdcYd} ~ Sd} ~ wwxYw)a Scansiona ricorsivamente una directory per trovare tutti i file PDF. Args: root_path: Path della directory radice da scansionare Returns: dict: { 'success': bool, 'pdf_count': int, 'pdfs': [ { 'filename': str, # Nome file (es: "Inception.pdf") 'full_path': str, # Path completo assoluto 'relative_path': str, # Path relativo dalla root 'parent_folder': str, # Nome directory padre (potenziale fornitore) 'size_mb': float # Dimensione file in MB }, ... ], 'error': str (opzionale) } rNFzDirectory non trovata: )r rx pdf_countpdfsuIl path non è una directory: z*.pdfrr )rr full_path relative_path parent_folderrz[WARNING] Errore lettura file rc6|dS)NrrrSr$s rr%z$scan_pdf_directory..qs* 3 3 5 5r$r&z[SCREENPLAY] Scansionati z PDF in T)r rrz[ERROR] scan_pdf_directory: )r[rr+is_dirrglobis_filer-rr relative_tor2rr\r`rabsoluterrr,rrr) rr[rootrpdf_filerrrelativerr file_errorrrs rscan_pdf_directoryr s0L ||I&&{{}}  >9>>  {{}}  E)EE   7++  H##%%  %]]__4  k :A>>2'33D99H$'MMMM!222$'MMMMM2!) 4  ( !$X%6%6%8%8!9!9%2%2&    OxOO:OOPPP  55 666 H#d))HHYHHIIIT        0Q00111VV          sx6F<F</F<,E4$CEC52E4C55AEF< E-E(#F<(E--AF<< H=H H Hct}tj|_|}|d|dd}|d|d}tdd}|jst|nd}|d |d}|d |d}|d |d}| ||||||d S) a Recupera le statistiche per la dashboard: - Prodotti totali in MediaTrack - Prodotti aggiunti negli ultimi 7 giorni - Distributori totali (da Gemma) - Eventi totali - Eventi aggiunti negli ultimi 7 giorni - Sessioni attive z-SELECT COUNT(*) as count FROM AnagraficaMediazFSELECT COUNT(*) as count FROM AnagraficaMedia WHERE deleted_at IS NULLrz SELECT COUNT(DISTINCT id_media_fk) as count FROM LogAggiornamenti WHERE data_log >= date('now', '-7 days') r)rLrrz3SELECT COUNT(*) as count FROM LogEventiDistributorez} SELECT COUNT(*) as count FROM LogEventiDistributore WHERE data_evento >= date('now', '-7 days') zCSELECT COUNT(*) as count FROM SessioniImport WHERE stato = 'attiva')prodotti_totaliprodotti_ultimi_7ggdistributori_totali eventi_totalieventi_ultimi_7ggsessioni_attive) rrrrrOrrrCr@rr5) rrTrr _gdf_distrrrrs rget_dashboard_statsrss ''CkCO **,,CKK?@@@kk"jkkttvvw~OKK   ,,..1C)I1:F#i...QKKEFFFLLNN7+MKK    w/KKUVVVllnnW-OIIKKK+22&.*   r$c xt}tj|_|}|d|f|}|g}|D]B}||d|d|d|d|d|ddC|S) u Recupera le attività recenti (ultimi log eventi con dettagli prodotto). Restituisce una lista di dict con: - id_media - titolo_ufficiale - distributore_nome - data_evento - dettaglio_contesto - tipo (nome tipo evento, se disponibile) a SELECT l.id_media_fk as id_media, a.titolo_ufficiale, a.distributore_nome, l.data_evento, l.dettaglio_contesto, t.nome_tipo as tipo FROM LogEventiDistributore l JOIN AnagraficaMedia a ON l.id_media_fk = a.id_media LEFT JOIN TipiEvento t ON l.id_tipo_evento_fk = t.id_tipo_evento ORDER BY l.data_evento DESC LIMIT ? rprrWrrr)rprrWrrr) rrrrrOrrPr5r)rrrTr activitiesrJs rget_recent_activityrs ''CkCO **,,CKK      <<>>DIIKKKJ  J #$6 7!$%8!9}-"%&:";K        r$cV t}tj|_|}|d|}|d|D}|S#t$r}td|gcYd}~Sd}~wwxYw)z~ Recupera tutti i tipi evento dal database. Returns: list: Lista di dizionari con i dati dei tipi evento aV SELECT id_tipo_evento, nome_evento, colore_hex, icona, descrizione, funzione_speciale, predefinito, campi_visibili, attivo, COALESCE(visibilita, 'entrambi') AS visibilita FROM TipiEvento WHERE attivo = 1 ORDER BY predefinito DESC, nome_evento ASC c,g|]}t|SrGrrHs rrKz'get_all_event_types..s111StCyy111r$z%[ERROR] Errore recupero tipi evento: N rrrrrOrrPr5rr)rrOr event_typesrs rget_all_event_typesrsgg!+       11D111   9a99::: sA=B B( B#B(#B(cb t}tj|_|}|d|f|}||rt|ndS#t$r}td|Yd}~dSd}~wwxYw)z Recupera un tipo evento specifico per ID. Args: id_tipo_evento (int): ID del tipo evento Returns: dict or None: Dati del tipo evento o None se non trovato z SELECT id_tipo_evento, nome_evento, colore_hex, icona, descrizione, funzione_speciale, attivo FROM TipiEvento WHERE id_tipo_evento = ? Nz%[ERROR] Errore recupero tipo evento: rrrrrOrrr5rrrid_tipo_eventorrOrJrs rget_event_type_by_idr sgg!+      oo )tCyyyT)  9a99:::tttttsBB B.B))B. fa-calendarnoner1c  |r|sdSt}|}|d|f|r|dS|d|||||||pdf|j} ||td|d| dd d | fS#t$r1} td | d d t| dfcYd} ~ Sd} ~ wwxYw)a Crea un nuovo tipo evento. Args: nome_evento (str): Nome del tipo evento colore_hex (str): Colore in formato hex (#RRGGBB) icona (str): Classe icona FontAwesome descrizione (str): Descrizione del tipo evento funzione_speciale (str): Funzione speciale associata campi_visibili (str): JSON con configurazione campi visibili Returns: tuple: (success: bool, message: str, id_tipo_evento: int or None) )FzNome tipo evento obbligatorioNz;SELECT id_tipo_evento FROM TipiEvento WHERE nome_evento = ?)F*Esiste già un tipo evento con questo nomeNz INSERT INTO TipiEvento (nome_evento, colore_hex, icona, descrizione, funzione_speciale, campi_visibili, predefinito, attivo, visibilita) VALUES (?, ?, ?, ?, ?, ?, 0, 1, ?) r1zNFO] Tipo evento creato:  (ID: rTzTipo evento creato con successoz&[ERROR] Errore creazione tipo evento: FErrore: N) rrrOrrr5rCrQrrr2) nome_evento colore_hexicona descrizionefunzione_specialecampi_visibilirbrrOrrs rcreate_event_typer@s 0 @+"3"3"5"5 @??ggT"((**, . . . ??   M IIKKKLL  !!:ukCTVdfpf~t~  A A A  )   PKPP~PPPQQQ6FF 000 :q::;;;)Q))4///////0s*D A3D A:D E&E:EEc t}|} | d|f| } | s|dS| ddk} |rT| d||f| r|dSg} g} | rZ|*| d| ||*| d | |n\|<| d | ||*| d| ||*| d | ||@| d | |r|nd|*| d | ||*| d| ||*| d | || s|dS| |dd| d}| || ||td|dS#t$r0}td|ddt|fcYd}~Sd}~wwxYw)a Aggiorna un tipo evento esistente. Args: id_tipo_evento (int): ID del tipo evento da aggiornare nome_evento (str, optional): Nuovo nome colore_hex (str, optional): Nuovo colore icona (str, optional): Nuova icona descrizione (str, optional): Nuova descrizione funzione_speciale (str, optional): Nuova funzione speciale campi_visibili (str, optional): JSON con configurazione campi visibili Returns: tuple: (success: bool, message: str) z;SELECT predefinito FROM TipiEvento WHERE id_tipo_evento = ?FzTipo evento non trovatorrz SELECT id_tipo_evento FROM TipiEvento WHERE nome_evento = ? AND id_tipo_evento != ? )FrNzcolore_hex = ?zvisibilita = ?znome_evento = ?z icona = ?zdescrizione = ?zfunzione_speciale = ?zcampi_visibili = ?FrzUPDATE TipiEvento SET rz WHERE id_tipo_evento = ?z"[INFO] Tipo evento aggiornato: ID )Tz#Tipo evento aggiornato con successoz*[ERROR] Errore aggiornamento tipo evento: Fr) rrOrrr5rrrrQrrr2)rrrrrrrrbrrOrcis_predefinitoupdatesrrrs rupdate_event_typerqs"L*gg T%' ) ) )"" 4 IIKKK33a  K NN##%%~6 8 8 8   K JJ  *%/000 j)))%/000 j)))&0111 k//11222%/000 j))) {+++ e$$$&0111 [->->-@-@JkkdKKK ,6777 /000)3444 n---%/000 j))) 7 IIKKK66 n%%%V7););VVVuf%%%   C>CCDDD:: *** >1>>???)Q)))))))))*s3A#L'A L GLA>L M'%M MMc t}|}|d|f|}|s|dS|d|f||t d|dd|ddS#t$r0}t d |d d t|fcYd }~Sd }~wwxYw) z Elimina un tipo evento (soft delete - imposta attivo = 0). Args: id_tipo_evento (int): ID del tipo evento da eliminare Returns: tuple: (success: bool, message: str) z;SELECT nome_evento FROM TipiEvento WHERE id_tipo_evento = ?rz9UPDATE TipiEvento SET attivo = 0 WHERE id_tipo_evento = ?z [INFO] Tipo evento disattivato: rrr)Tz"Tipo evento eliminato con successoz)[ERROR] Errore eliminazione tipo evento: FrN) rrOrrr5rQrrr2rs rdelete_event_typer s.*gg T%' ) ) )oo 4 IIKKK33 R%' ) ) )   PQPP~PPPQQQ99 *** =!==>>>)Q)))))))))*s%A#C'AC C>%C93C>9C>cZ t}tj|_|}|d|f|}|d|D}|S#t$r}td|gcYd}~Sd}~wwxYw)z Recupera tutti gli eventi marcati come preferiti. Rispetta la privacy: eventi pubblici + eventi privati dell'utente corrente. Returns: list: Lista di dizionari con informazioni eventi preferiti a; SELECT l.id_aggiornamento, l.id_media_fk, l.id_tipo_evento_fk, l.data_evento, l.dettaglio_contesto, l.note, l.data_reminder, l.preferito, l.privato, l.venduto_a, m.titolo_ufficiale as titolo_prodotto, m.tipo as genere, te.nome_evento as tipo_evento, te.colore_hex as colore_evento FROM LogAggiornamenti l JOIN AnagraficaMedia m ON l.id_media_fk = m.id_media JOIN TipiEvento te ON l.id_tipo_evento_fk = te.id_tipo_evento WHERE l.preferito = 1 AND (l.privato = 0 OR l.created_by = ?) ORDER BY l.data_evento DESC c,g|]}t|SrGrrHs rrKz'get_favorite_events..!///3T#YY///r$z#[ERROR] Errore recupero preferiti: Nr)user_idrrTr favoritesrs rget_favorite_eventsrs%gg!+jjll  ,Z-   0||~~ //$///   7A77888 A?B B* B%B*%B*cZ t}tj|_|}|d|f|}|d|D}|S#t$r}td|gcYd}~Sd}~wwxYw)z Recupera tutti gli eventi con reminder attivi. Rispetta la privacy: eventi pubblici + eventi privati dell'utente corrente. Returns: list: Lista di dizionari con informazioni eventi con reminder a SELECT l.id_aggiornamento, l.id_media_fk, l.id_tipo_evento_fk, l.data_evento, l.dettaglio_contesto, l.note, l.data_reminder, l.preferito, l.privato, l.venduto_a, m.titolo_ufficiale as titolo_prodotto, m.tipo as genere, te.nome_evento as tipo_evento, te.colore_hex as colore_evento, -- Calcola giorni al reminder CAST(julianday(l.data_reminder) - julianday('now') as INTEGER) as giorni_al_reminder FROM LogAggiornamenti l JOIN AnagraficaMedia m ON l.id_media_fk = m.id_media JOIN TipiEvento te ON l.id_tipo_evento_fk = te.id_tipo_evento WHERE l.data_reminder IS NOT NULL AND l.data_reminder != '' AND (l.privato = 0 OR l.created_by = ?) ORDER BY l.data_reminder ASC c,g|]}t|SrGrrHs rrKz'get_reminder_events..Tr r$z"[ERROR] Errore recupero reminder: Nr)r rrTr remindersrs rget_reminder_eventsr)s(gg!+jjll  2Z3   6||~~ //$///   6166777 rc* t}|}|d|f|d}||S#t $r}t d|Yd}~dSd}~wwxYw)zq Conta gli eventi preferiti per l'utente corrente. Returns: int: Numero di eventi preferiti z SELECT COUNT(*) FROM LogAggiornamenti WHERE preferito = 1 AND (privato = 0 OR created_by = ?) rz$[ERROR] Errore conteggio preferiti: NrrOrrr5rrr rrTrrs rcount_favorite_eventsr\sggjjll  Z     q!   8Q88999qqqqqA(A++ B5B  Bc* t}|}|d|f|d}||S#t $r}t d|Yd}~dSd}~wwxYw)z~ Conta gli eventi con reminder attivi per l'utente corrente. Returns: int: Numero di eventi con reminder z SELECT COUNT(*) FROM LogAggiornamenti WHERE data_reminder IS NOT NULL AND data_reminder != '' AND (privato = 0 OR created_by = ?) rz#[ERROR] Errore conteggio reminder: Nrrs rcount_reminder_eventsrxsggjjll  Z     q!   7A77888qqqqqrfiltric t}tj|_|}g}g}d}|dr |dz }||d|dz }|drIdd|dD}|d|d z }||d|d r |d z }||d |d r |d z }||d |||d}|dz }td|td|| ||| } | d| D} tdt| dtd|dt|d |r|d stdg} |dr>dd|dD} | d| d|d r5|d dd} | d| d| rd| nd }t!|d!"}|js|D]\}}| id#d$t'|d%zd&d'd(|d)d*|d+d,|d-d.d d/|d0d1d d d d2d d3d d4d d5d d6d d7d d8|d+d9|d:|d;|d<|d=|d>|d?|d@|dA|dB|dC|dD|dEd dF tdG|jst|nddtdHt| dI| S#t($r6}tdJ|dd l}|gcYd }~Sd }~wwxYw)Ka Recupera dati prodotti per export Excel con filtri applicati. Args: filtri: dict con chiavi: - distributori: list[str] - lista distributori selezionati - tipologia: str - tipo prodotto (Film, Serie, etc.) - data_da: str - data evento minima (YYYY-MM-DD) includi_gemma: bool - Include prodotti da Gemma Returns: list[dict]: Lista di prodotti con tutti i campi necessari per Excel export a. SELECT DISTINCT a.id_media, 'MediaTrack' as fonte, a.titolo_ufficiale as titolo, a.tipo, a.distributore_nome as distributore, o.anno_produzione, a.formato, a.piattaforma, l.dettaglio_contesto as contesto, l.data_evento, l.costo_richiesto_ita as ask_ita, l.costo_richiesto_spa as ask_spa, l.delivery, l.note, o.imdb_rating as rating_imdb, o.generi as genere, NULL as v_rda, NULL as v_c5, NULL as v_i1, NULL as v_r4, NULL as v_las, NULL as v_i2, NULL as v_iris, NULL as v_top, NULL as v_foc, NULL as v_c20, NULL as v_c34, NULL as v_27, l.id_aggiornamento -- Per ordinamento (ultimo evento) FROM AnagraficaMedia a LEFT JOIN ( SELECT id_media_fk, dettaglio_contesto, data_evento, costo_richiesto_ita, costo_richiesto_spa, delivery, note, id_aggiornamento, ROW_NUMBER() OVER (PARTITION BY id_media_fk ORDER BY id_aggiornamento DESC) as rn FROM LogAggiornamenti WHERE 1=1 data_daz AND data_evento >= ?z ) l ON a.id_media = l.id_media_fk AND l.rn = 1 LEFT JOIN OmdbData o ON a.id_media = o.id_media_fk WHERE 1=1 AND a.deleted_at IS NULL distributorircg|]}dSrrGrs rrKz#get_export_data..s$I$I$IQS$I$I$Ir$z AND a.distributore_nome IN (rrz AND a.tipo = ?rz AND l.dettaglio_contesto = ?rz* ORDER BY id_aggiornamento DESC NULLS LASTz[EXPORT] Query MT: z[EXPORT] Params MT: c,g|]}t|SrGrrHs rrKz#get_export_data..s...#DII...r$z[EXPORT] MediaTrack: z prodottiz"[EXPORT] includi_gemma parameter: z (type: z"[EXPORT] Aggiunta Gemma via DuckDBrc3BK|]}|ddVdS)rrN)r3)rIds rrz"get_export_data..s0']'] #t(<(<']']']']']']r$zA_DISTRIBUTORE IN ('r(rrzA_TIPOLOGIA = 'rNzA_ID, A_TITOLO, A_TIPOLOGIA, A_DISTRIBUTORE, A_EPISODIO, V_RDA, V_C5, V_I1, V_R4, V_LA5, V_I2, V_IRIS, V_TOP, V_FOC, V_C20, V_CI34, V_C27rrprrrrrrrrrrrrr]rrask_itaask_sparr rating_imdbgenerev_rdarrr r rrrrrrrr) v_c5v_i1v_r4v_lasv_i2v_irisv_topv_focv_c20v_c34v_c27rz[EXPORT] Gemma aggiunti: z[EXPORT] Trovati z prodotti totaliz[ERROR] get_export_data: )rrrrrOr!rrrrrrPr5rrr3rCr@r+r2rrr)rrrrT query_partsrr8r final_queryrproductsr dist_listtip_escrMgdf_expr?rZrrs rget_export_datar:s#Dgg!+jjll  @ ::i  - / /H MM&+ , , ,   ::n % % 288$I$I&2H$I$I$IJJL G GGG GH MM&0 1 1 1 ::k " " / ) )H MM&- . . . ::j ! ! . 7 7H MM&, - - -8$$$"!n CC  1K11222 -V--... K(((||~~ ..... >c(mm>>>??? `=``$}J]J]```aaa ' cJ!7!7' c 6 7 7 7 " zz.)) O"KK']']f^F\']']']]] !(()M )M)M)MNNNzz+&& G -55c4@@!(()E7)E)E)EFFF=NX',,'8999TXK!dG= #,,..DAqOO%"D3qy>>$9%%!!%% "3"3%m 4 4 % '.>(?(? % *4 %"155#6#6%&t%#D%&t%"4%"4%#D%%&t% !!%% "6"6!%" w#%":;vPQPUPUV\P]P] !f gPQPUPUV\P]P]"#%%//AEE'NNUVUZUZ[bUcUc!"w!%%//TUTYTYZaTbTb,0+%%%. a'-.Vc'lllUVaaa b b b A#h--AAABBB  -!--... sTT U *+UU U cZ t}tj|_|}|d|f|}|d|D}|S#t$r}td|gcYd}~Sd}~wwxYw)z Recupera tutti i preset export di un utente. Args: user_id: ID utente Returns: list[dict]: Lista preset con tutti i campi a' SELECT id_preset, nome_preset, filtri_json, includi_gemma, is_default, data_creazione, data_ultimo_uso FROM UserExportPresets WHERE user_id = ? ORDER BY is_default DESC, data_ultimo_uso DESC NULLS LAST, nome_preset ASC c,g|]}t|SrGrrHs rrKz+get_user_export_presets..Hs---499---r$z![ERROR] get_user_export_presets: Nr)r rrTrpresetsrs rget_user_export_presetsr>.sgg!+jjll  Z    ||~~ -----  5!55666 rcd t}tj|_|}|d||f|}||rt|ndS#t$r}td|Yd}~dSd}~wwxYw)z Recupera un preset specifico (con verifica ownership). Args: preset_id: ID preset user_id: ID utente (per verifica ownership) Returns: dict or None: Dati preset o None se non trovato/non autorizzato z SELECT id_preset, nome_preset, filtri_json, includi_gemma, is_default, data_creazione, data_ultimo_uso FROM UserExportPresets WHERE id_preset = ? AND user_id = ? Nz![ERROR] get_export_preset_by_id: r) preset_idr rrTrJrs rget_export_preset_by_idrAPsgg!+jjll   !  # # #llnn )tCyyyT)  5!55666tttttsBB B/B**B/c t}|}|r|d|f|d|||||f|j}||t d|d|d|dd|fS#t$r1}t d|d d t|d fcYd }~Sd }~wwxYw) a Crea un nuovo preset export. Args: user_id: ID utente nome_preset: Nome descrittivo del preset filtri_json: JSON string con filtri (distributori, tipologia, data_da) includi_gemma: bool - Include Gemma products is_default: bool - Marca come preset di default Returns: tuple: (success: bool, message: str, preset_id: int or None) z UPDATE UserExportPresets SET is_default = 0 WHERE user_id = ? AND is_default = 1 z INSERT INTO UserExportPresets (user_id, nome_preset, filtri_json, includi_gemma, is_default) VALUES (?, ?, ?, ?, ?) z[PRESET] Creato preset 'z' (ID: z ) per user TzPreset salvato con successoz[ERROR] create_export_preset: FzErrore salvataggio preset: N) rrOrrCrQr5rrr2) r  nome_preset filtri_jsonr is_defaultrrTr@rs rcreate_export_presetrFqs8Cggjjll   KK      {K K M M M M    \\\Y\\SZ\\]]]2I== CCC 2q22333c!ff>>>>>>>>>?s2A#H' H C9HBH I%H<6I<Ic t}|}|d||f|jdkr|dS||t d|d|dS#t$r0}t d|dd t|fcYd }~Sd }~wwxYw) z Elimina un preset (con verifica ownership). Args: preset_id: ID preset user_id: ID utente (per verifica ownership) Returns: tuple: (success: bool, message: str) zc DELETE FROM UserExportPresets WHERE id_preset = ? AND user_id = ? r)Fz$Preset non trovato o non autorizzatoz[PRESET] Eliminato preset ID rH)TzPreset eliminato con successoz[ERROR] delete_export_preset: FzErrore eliminazione preset: N) rrOrrr5rQrrr2r@r rrTrs rdelete_export_presetrLs>ggjjll   ! # # # <1   IIKKK@@   LiLL7LLMMM44 >>> 2q22333=SVV=========>s$AB=B C&%C CCc  t}|}|d||f||dS#t $r}t d|Yd}~dSd}~wwxYw)z Aggiorna data_ultimo_uso di un preset (per ordinamento) Args: preset_id: ID preset user_id: ID utente (per verifica ownership) Returns: bool: Success z UPDATE UserExportPresets SET data_ultimo_uso = CURRENT_TIMESTAMP WHERE id_preset = ? AND user_id = ? Tz![ERROR] update_preset_last_used: NF)rrOrrQr5rrrKs rupdate_preset_last_usedrNsggjjll   !  # # #  t  5!55666uuuuusA"A&& B 0BB )Nr;)rrrFrrrr)Frr)rF)F)TF)NNNT)NN)TN)rN)rN)Nr)r)rNrNr1)NNNNNNN)NNNN)rr threadingrloggingsysrjrr[r getLogger__name__r__file__resolve _current_filer\_myicr_suite_dirr2pathinsert config_loaderrrrrr#r0Lockr/r1r:rC ConnectionrVrrboolrorrr-rrrrrrrrrrr]rdrirorr}rrrrrrrrrrrr rr!r%r<r?rArDrLrOrUrrrrrrrrrrrrrrrrrrrrrrrrSrr6r9r=r?rArErQrhrlrsryr}rrrrrrrrrrrrr rrrrr:r>rArFrIrLrNrGr$rr^s{    8 $ $X&&((  '.5<3((HOOAss+,,---,,,,,,"!##  5m 5 5666D^D^^^^"IN$$   *S#$  W%7  D       2 t      7#5  $      T    <#<<4ST$ZDhhh$h4PT:hhhhT&&t &&&&6t *'s'tDz''''R7s7tDz7777r$$d$$$$L`s`tDz````D3334$;3333j\\S\D\UYZ^U_\\\\|=#=D=USWY\S\M]====~ 3 d uT3Y?O    J  %3 %3 % % % %sssDs`dsz~sLQRVX[]`cg]gRgLhssssl_s_uT3Y/?____DIMOSNNN#N-0NHLNX\]aXbNNNNb$s)B!!#!S4Z!cDj!\`ae\fim\m!!!!H__4_S4Z_SWX\S]____B%d%%%%N,,c,c,TRVZ,,,,^c,BSBS4ZBBBBBBd BBBB#CD&#,?s?uT3Y/?????B;s;uT3Y/?;;;;>q$qqqqr G$6 4    2$  2$ t$ $ $ $ NWd4jWWWWCtSfilpfp}BCGILCL}M"3c3:WZdgBEHLBLX]^bdg^gXh2 # %c *:    &3&4$;&&&&8SS6vv3v3vcv[_vvvv~_85s#3_8_8_8_8LCL?L?L?L?^cd4jccccL3 $3 3 3 3 lkPkPckP$kP5sTWY]I]C^kPkPkPkP\)>3)>5s+;)>)>)>)>`d #d $d d d d V:::z----p@@6Cak.0.0.0.0bDHko]*]*]*]*@!*!*!*P---`000f8BRRDRRRRRjDB)C)C)C)CXLP7;M?M?M?M?`!>!>!>Hr$