--- name: project-flussi-knowledge description: "Base di conoscenza logistica dei flussi dati del dominio ICR (pipeline, provenienza, trasformazioni — letti oggi via MyICR_Suite/PowerBricks, dominio agnostico rispetto all'applicativo) — costruita con Mauro, letta dall'esperto persistente expert-flussi" metadata: type: project --- Conoscenza sulla logistica dei flussi dati per l'ecosistema MyICR_Suite/PowerBricks (Mediaset), costruita insieme a Mauro a partire dal 2026-07-17. **(30/08/2026) Letta all'avvio dall'esperto persistente `expert-flussi`** (`scripts/slave-sentinels/expert-flussi/CLAUDE.md`) — il subagente `flussi-expert` che la leggeva prima è stato dismesso lo stesso giorno (stessa preferenza di Mauro già applicata a `data-expert`/`expert-data`), il suo ruolo è assorbito da `expert-flussi`. **Why:** complementare a [[data-expert]] (che documenta la semantica di business dei dati as-is). Questo documento tiene traccia di dove nascono i dati (OnAir e altre fonti), che pipeline li trasformano (Frank, DataHub_v2, ParquetToAccess, PipelinePython), e le anomalie diagnosticate risalendo al codice a monte — non la semantica dei campi, quella resta nell'altra KB. **Nota storica (04/08/2026)**: questo file e il subagente si chiamavano `myicr-flussi.md`/`myicr-flussi-expert` — rinominati insieme a `data-expert` (ex `powerbricks-expert`) perché "myicr" legava il nome all'applicativo, mentre il dominio (provenienza e trasformazioni dei dati ICR) è agnostico rispetto a quale app li consulta. **How to apply:** Adrian scrive qui direttamente durante una sessione di lavoro sul dominio, in parallelo alla conversazione — `expert-flussi` oggi è sola lettura, segnala eventuali scoperte ma non scrive qui direttamente (stesso criterio operativo/architetturale di `expert-data`, vedi `archivio/Adrian/riferimento/claude-cli-headless.md`). Ogni voce porta un tag di stato: `[CONFERMATO]` (validato con Mauro/Adrian) o `[IPOTESI]` (dedotto da codice/dati, non ancora validato). Sezioni vuote finché non emerge conoscenza reale. ## 🔥 Domande aperte (tenere calde — aggiornare a ogni sessione) 1. **Consumer di `db_Osservatorio.accdb`/`tabOsservatorio`** (da non confondere con l'item #5 sotto, "fix canale secondario DVB-T" — nome simile, item distinto e ancora aperto) — `[CHIUSO E FIXATO 2026-08-04, v. Passo 19]`: il file era stato **cancellato da Mauro stesso** (confermato a voce), non una sparizione accidentale — nessun consumer Python trovato in tutto l'albero `PYTHON/`, rimozione legittima, nessun impatto perso. Lo step orfano `integrazione_update_day()` (che da inizio giugno girava ogni giorno a vuoto in `ParquetToAccessDaily`/`run_all()`) è stato **rimosso per intero** dall'istanza Ufficio (04/08/2026): funzione, chiamata in `run_all()`, riga nel docstring, e la chiave `db_osservatorio` in `config/settings.json` — nessun residuo (grep verificato). Commit `8fff774` nel repo `PYTHON/PIPELINE_PYTHON`, **deployato in produzione** su `I:\SOFTWARE\SCHEDULATORE\PIPELINE_PYTHON` via `deploy.bat`. Bonus trovato durante il deploy: bug reale in `deploy.bat` stesso (`%~dp0` termina sempre con `\`, rompeva il quoting di robocopy) — fixato nello stesso giro, commit `1601958`. 2. **`gemma.parquet` ↔ Acquisti (W:\)** — collegamento mai verificato a voce con Mauro (solo ipotizzato), a differenza di Osservatorio/MonitorOsservatorioPy che è stato chiuso per intero. 3. **`ICR_MONDO`** — prefissi noti (`CON`, `MED`, `SKY`, `FOX`, `PREMIUM`-obsoleto), ma il significato esatto di `CON`=Concorrenza / `MED`=Mediaset non confermato a voce. 4. **`sync_DEV_LOCAL.bat`** — ponte ipotizzato tra `I:\` (output pipeline) e il path Dropbox `MyICR_Suite/local_db/parquet` che leggiamo — file gitignored, non verificabile da Nave, non confermato al 100%. 5. **Fix "canale secondario" DVB-T** (v. Passo 18) — non una domanda ma un lavoro concreto da fare quando/se Mauro deciderà: esporre in MyICR_Suite/PowerBricks che un prodotto è arrivato tramite il workaround DVB-T, oggi invisibile. *(v. anche `data-expert.md` per le domande aperte lato semantica dati: scala `veg` A-H criterio di attribuzione, `situazione`/`causale` in diritti mai approfonditi, perché coesistono tre codifiche rete, contenuto business di linker.db/ott.db/mediatrack.db mai interrogato)* ## Provenienza e sistemi sorgente `[CONFERMATO]` (2026-07-17, ereditato da [[data-expert]]) — quasi tutti i parquet in `PYTHON/MyICR_Suite/local_db/parquet/` arrivano da **OnAir** (sistema aziendale Mediaset), eccetto `gemma.parquet`, `imdb_full.parquet`, `reti_cluster.parquet`. ## Pipeline note (schema, non ancora indagate nel dettaglio) `[IPOTESI]` (da `PYTHON/CLAUDE.md`, non ancora verificato con lettura diretta del codice): - **Frank** — orchestratore job ETL, coordina ImdbUpdate, GemmaReport, DataHub_v2, ecc. - **PIPELINE_PYTHON** (`I:\SOFTWARE\SCHEDULATORE\PIPELINE_PYTHON`, **non raggiungibile da Nave**) — genera il mirror SQLite dei parquet L2 per query Access ("sqlite_mirror", nota 2026-04-27 in CLAUDE.md). - **DataHub_v2**, **ParquetToAccess** (`I:\SOFTWARE\SCHEDULATORE\UPDATE_PYTHON\...`, **non raggiungibili da Nave**) — ruolo esatto ancora da chiarire. ## Catena completa fino ai parquet — in costruzione (2026-07-17) `[CONFERMATO]` (Mauro, 2026-07-17) — **punto di partenza della catena**: tutto parte dalla directory `I:\SOFTWARE\SCHEDULATORE`. Contenuto già osservato via Frank-relay (list_dir): `FTP`, `INPUT`, `PIPELINE_PYTHON`, `UPDATE_PYTHON`, `UPDATE_PYTHON_WEEKLY`, `vecchia procedura VBA`. Mauro segnala che la catena da qui fino ai parquet finali è **lunga e complessa** — costruzione passo-passo in corso, non riassumere/semplificare finché non è raccontata per intero. **Passo 1 — primo problema identificato (Mauro, 2026-07-17)**: `[CONFERMATO]` — il codice che gestisce la pipeline si trova **solo** in `I:\SOFTWARE\SCHEDULATORE\PIPELINE_PYTHON` — segnalato esplicitamente da Mauro come "il primo problema" (natura del problema da chiarire: fragilità/single point of failure? mancanza di versionamento git? non ancora specificato, in attesa del seguito del racconto). **Implementato (2026-07-17, sessione ufficio)**: `[CONFERMATO]` — copia versionata creata in `Dropbox/adrian/PYTHON/PIPELINE_PYTHON/`. Cosa è stato fatto: - Robocopy da `I:\SOFTWARE\SCHEDULATORE\PIPELINE_PYTHON` → `PYTHON/PIPELINE_PYTHON/` escludendo `.git`, `.claude`, `LOG/`, `__pycache__/`, `logs/`, `parquet/`, `parquet_layer1/`, `*.pkl`, `*.accdb`, `*.db` - `.gitignore` aggiornato (aggiunto `.claude/`) - `CLAUDE.md` minimale creato (v1, eredita Costituzione, documenta architettura e gotcha principali, punta a CLAUDE.md originale su I:\ per dettaglio completo) - `deploy.bat` creato con robocopy inverso (PYTHON/ → I:\), con pausa di conferma, da usare solo manualmente - Mappa progetti in `PYTHON/CLAUDE.md` aggiornata - `I:\SOFTWARE\SCHEDULATORE\PIPELINE_PYTHON` non toccata in alcun modo **Passo 2 — origine dei file di input (Mauro, 2026-07-17)**: `[CONFERMATO]` — i file grezzi vengono postati **dalla Direzione Sistemi Mediaset, in automatico**, dentro `I:\SOFTWARE\SCHEDULATORE\INPUT`. Cadenza mista: alcuni file arrivano con frequenza **settimanale**, altri **giornaliera**. Collegamento con quanto già noto: la **Direzione Sistemi** è la stessa citata in precedenza come tramite per `osservatorio.parquet` — probabilmente lo stesso meccanismo generale di deposito file (Direzione Sistemi → `INPUT/` → pipeline), non solo per il caso Osservatorio. **Correzione importante — dove gira davvero il codice (Mauro, 2026-07-17)**: `[CONFERMATO]` — il Python che fa l'ingestione **non gira sulla macchina di Mauro (A)**, gira su **un'altra macchina della Direzione Sistemi**. Loro lanciano: - `I:\SOFTWARE\SCHEDULATORE\UPDATE_PYTHON\start.bat` per i caricamenti **giornalieri** - `I:\SOFTWARE\SCHEDULATORE\UPDATE_PYTHON_WEEKLY\start.bat` per quelli **settimanali** Quindi `PIPELINE_PYTHON` (appena copiato in `PYTHON/`) è il codice sorgente/repository, ma l'esecuzione avviene tramite `UPDATE_PYTHON`/`UPDATE_PYTHON_WEEKLY` su una macchina Direzione Sistemi, non tramite Frank né sulla macchina A. `[IPOTESI aperta]`: non ancora chiarito se `UPDATE_PYTHON`/`UPDATE_PYTHON_WEEKLY` siano una copia deployata di `PIPELINE_PYTHON` (stesso codice, posizione diversa per l'esecuzione) o moduli/wrapper distinti che richiamano `PIPELINE_PYTHON` — da verificare quando si guarda dentro quelle due cartelle. **Nome macchina esecutrice (Mauro, 2026-07-17)**: `[CONFERMATO]` — la macchina Direzione Sistemi che esegue `start.bat` si chiama **`wincoarcapp01`**. **Accesso remoto disponibile ma non usato (Mauro, 2026-07-17)**: `[CONFERMATO]` — Mauro ha la possibilità di accesso remoto a `wincoarcapp01` se volesse, ma in pratica non gli serve: la macchina non ha mai problemi, e il suo ruolo è puramente eseguire il Python (`start.bat`) — non fa nient'altro di suo, è solo l'host di esecuzione. **Passo 3 — architettura completa ricostruita da Adrian (2026-07-17, letta da `PYTHON/PIPELINE_PYTHON/CLAUDE.md` appena copiato)**: `[CONFERMATO]` (fonte: documentazione ufficiale del progetto, non inferenza) — invitato da Mauro a ricostruire da qui in poi usando gli strumenti disponibili. Catena completa: ``` run_weekly.bat / run_daily.bat ← lanciato da Direzione Sistemi su wincoarcapp01 │ ├─ [1] Venv check C:\venv_ICR (auto-crea se assente, pacchetti: pyodbc pywin32 duckdb pandas loguru) │ (NON lo stesso venv di sviluppo C:\PYTHON\_runtimes\venv su macchina A) │ ├─ [2] CsvToParquet_layer1 │ CSV sorgente (da INPUT/, postati da Direzione Sistemi) → Parquet Layer 1 locali │ → C:\PYTHON_LOCAL\CsvToParquet_layer1\parquet\ │ ├─ [3] Pubblica Layer 1 su rete │ → I:\SOFTWARE\PYTHON_SRV\parquet_layer1\ │ ├─ [4] Parquet_layer1ToParquet_layer2 │ L1 rete → L2 rete (trasformazioni business + enrich), solo DuckDB │ → I:\SOFTWARE\PYTHON_SRV\PYTHON_LOCAL\MyICR_Suite\local_db\parquet\ │ ⚠️ QUESTO è (presumibilmente) l'origine dei parquet che analizziamo in data-expert │ (diritti.parquet, prodotti.parquet, emesso.parquet, ecc.) — path da riconciliare │ con `Dropbox/adrian/PYTHON/MyICR_Suite/local_db/parquet/` che leggiamo noi: stesso file │ sincronizzato in qualche modo, o due copie distinte? [IPOTESI aperta, da verificare] │ ├─ [5, solo weekly] SQLite mirror L2 (`sqlite_mirror.py`) │ L2 parquet → mirror_l2.sqlite │ → I:\SOFTWARE\PYTHON_SRV\sqlite_layer2\mirror_l2.sqlite │ ⚠️ Relazione con i mirror SQLite che leggiamo noi (linker.db, ott.db, mediatrack.db in │ `sync_db/`/`local_db/`) non ancora chiarita — nomi diversi, da verificare se è lo stesso │ file rinominato/ricollocato o un mirror parallelo. [IPOTESI aperta] │ ├─ [6/7, weekly] ParquetToAccessWeekly → DB_WEEK.accdb (6 tabelle Access) │ → I:\SOFTWARE\ProcedureICR\ICRmonitor\MonitorLinker\DB_WEEK.accdb │ └─ [6/7, daily] ParquetToAccessDaily → DB_DAY.accdb (enrichment giornaliero) → I:\SOFTWARE\ProcedureICR\ICRmonitor\MonitorLinker\DB_DAY.accdb ``` **Moduli**: `CsvToParquet_layer1/` (CSV→L1, CLAUDE.md proprio), `Parquet_layer1ToParquet_layer2/` (L1→L2 + `sqlite_mirror.py`), `ParquetToAccessWeekly/`, `ParquetToAccessDaily/` (CLAUDE.md proprio), `WriteToAccess/` (mattoncino riusabile DataFrame→Access via TransferText). **Gotcha rilevanti per l'interpretazione dei dati** (utili anche per `data-expert`): - **Date sentinella**: `2201-01-01` (lato parquet) diventa `9999-12-31` (lato VBA/legacy); `0001-01-01` diventa `1001-01-01` (Access non gestisce anni <100) — **spiega retroattivamente** il valore `decr=1001-01-01` osservato in `diritti.parquet` durante l'esplorazione del blocco WIN (sessione precedente, causale '01'/'03') — non un dato sporco, è una sentinella nota della pipeline. - **TCDURLORDA**: formula secondi→minuti `(x + 10) // 60`, soglia 50s (non 30s come si potrebbe assumere) — coerente con lo scarto residuo ±1min segnalato nel parallelo Python vs VBA su CUSTOM_EMESSO (97,47% match). - Separatore decimale italiano (Access usa virgola), schema.ini order-sensitive, Access non deve essere aperto durante i run. **Stato parallelo Python vs VBA (dal CLAUDE.md, post-fix 2026-04-23)**: CUSTOM_EMESSO 97,47%, CUSTOM_ANAGR_ORIZ 99,76%, EDIZIONI 100%, SUPERSERIE 99,84%, CUSTOM_DIRITTI 100%, BOXOFFICE 99,55% — la pipeline Python è quindi già validata contro il legacy VBA con match altissimo, non un sistema nuovo/non testato. **Passo 4 — dettaglio codice `CsvToParquet_layer1` (Adrian, 2026-07-17, letto codice + CLAUDE.md interno del modulo, non solo il riepilogo)**: `[CONFERMATO]` (fonte diretta: codice + documentazione del modulo) - **Nome storico**: questo modulo si chiamava **"DataHub v2"** internamente (v. suo CLAUDE.md, non ancora rinominato ovunque) — `[IPOTESI]`: probabile che sia la stessa cosa della voce `DataHub_v2` in `PYTHON/CLAUDE.md` (mappa progetti, path `I:\SOFTWARE\SCHEDULATORE\UPDATE_PYTHON\DataHub_v2`), solo con nome/posizione diversi tra i due documenti — da riconciliare, non dato per certo. - **`INPUT_DIR` reale** (da `config/settings.py`): `\\mediaset.it\share\Indirizzo_controllo_risorse\SOFTWARE\SCHEDULATORE\INPUT` — **conferma testuale diretta** che "Indirizzo e Controllo Risorse" (ICR) è nel nome della share di rete, non solo un acronimo detto a voce. - **Mapping CSV → parquet** (da `CSV_FILES` in `settings.py`): `prodotti←estr_prod.csv`, `emesso←estr_emesso.csv`, `diritti←estr_acq.csv` (nome file interno: "acq", coerente con l'ipotesi che i diritti siano prevalentemente contratti di **acq**uisizione), `imdb←estr_imdb.csv`, `boxoffice←estr_box_totali.csv`, `boxoffice_det←estr_box_dettaglio.csv`, `cast←estr_cast.csv`, `veg←estr_veg.csv`, `scelte_rete←estr_scelte_rete.csv`, `gemma←estr_gemma.csv`, `osservatorio←osservatorio.txt` (unico senza prefisso `estr_`, formato posizionale senza header). - **`osservatorio.txt` ha più colonne del parquet finale**: il file sorgente elenca 25 colonne inclusi `titolo_int`, `produzione`, `distribuzione`, `sceneggiatura`, `showrunner`, `anno_screening` — non tutte viste nello schema di `osservatorio.parquet` esplorato in sessione 1 (dove ne avevamo contate meno). `[IPOTESI aperta]`: alcune colonne vengono scartate/rinominate nella trasformazione CSV→parquet, da verificare con un confronto diretto schema-vs-schema. - **`weekly.py` produce tutti i parquet; `daily.py` produce solo `gemma.parquet` e `osservatorio.parquet`** — quindi questi due dataset hanno una cadenza di aggiornamento più frequente (giornaliera) rispetto al resto (settimanale). - **Filosofia "bidone"**: parquet = dato unico trasversale per tutto l'ecosistema (MyICR_Suite, DBeaver, tool futuri). Tabelle raw fedeli al CSV (solo dedup+type parsing), tabelle `_enrich` con colonne derivate lette dal parquet stesso (non dal CSV) — **niente logica di business nelle tabelle raw**, coerente con la nostra regola "prendi i dati as-is". - **Ponte trovato con il path Dropbox che leggiamo noi**: dal CLAUDE.md del modulo — *"Sync su A: `C:\PYTHON\MyICR_Suite\sync_DEV_LOCAL.bat` include sync Parquet"*. `[IPOTESI, ma molto probabile]`: questo è il meccanismo che porta il parquet da `I:\SOFTWARE\PYTHON_SRV\...\MyICR_Suite\local_db\parquet\` (destinazione della pipeline) fino a `Dropbox/adrian/PYTHON/MyICR_Suite/local_db/parquet/` (quello che leggiamo noi) — un sync script sulla macchina A che gira dentro la cartella ora Dropbox-sincronizzata. Da confermare leggendo `sync_DEV_LOCAL.bat` in `MyICR_Suite/`. - **Gotcha DuckDB rilevanti**: date in parquet sono `%Y-%m-%d` (non `%d/%m/%Y` come nel CSV sorgente); fan-out da JOIN se `_prod_anno` ha più righe per prodotto, fix con `GROUP BY prodotto + MAX(anno_produzione)` — utile per capire eventuali duplicazioni inattese in analisi future. - **Roadmap dichiarata nel modulo**: "prossimo: integrare `run_weekly/daily_SYS_SRV.bat` in Frank" — conferma che oggi Frank **non** orchestra questa pipeline (coerente con quanto detto da Mauro: gira su `wincoarcapp01`, non tramite Frank), ma è previsto in futuro. **Passo 5 — letto `sqlite_mirror.py` per intero (Adrian, 2026-07-17)**: `[CONFERMATO]` (codice diretto) - **`mirror_l2.sqlite` NON è `linker.db`/`ott.db`/`mediatrack.db`** — sono **sistemi distinti**, question chiusa. Il file `mirror_l2.sqlite` contiene UN SOLO database con le tabelle: `emesso`, `emesso_enrich`, `reti_cluster`, `osservatorio`, `imdb`, `scelte_rete`, `boxoffice`, `prodotti`, `diritti_enrich`, `diritti`, `cast`, `gemma` — copiate full da Layer 2 (con `_id` surrogato via `ROW_NUMBER()` per le tabelle senza PK intera, per compatibilità Access ODBC). Nessuna tabella `linker`/`ott`/`mediatrack` qui dentro — quei mirror devono avere una pipeline di provenienza completamente diversa (probabile: moduli interni MyICR_Suite — Linker, integrazione OTT, MediaTrack — non ancora indagati). - **Path L2 confermato per intero** (da codice, non ipotesi): `\\mediaset.it\share\Indirizzo_controllo_risorse\SOFTWARE\PYTHON_SRV\PYTHON_LOCAL\MyICR_Suite\local_db\parquet` — conferma che `I:\` = `\\mediaset.it\share\Indirizzo_controllo_risorse\SOFTWARE\` (stesso prefisso ICR visto anche in `INPUT_DIR`). - **Uso di `mirror_l2.sqlite`**: dichiarato nel docstring come "per uso con MS Access via ODBC" — quindi è un ponte verso Access, non verso MyICR_Suite/le nostre analisi. **E infatti esiste** (confermato da Mauro, 09/09/2026): `mirror_l2.accdb`, stessa cartella `I:\SOFTWARE\PYTHON_SRV\sqlite_layer2\`, costruito **a mano da Mauro** — tabelle collegate via ODBC verso lo `.sqlite`. Non è un artefatto della pipeline (nessun generatore nel codice), si aggiorna da solo ogni volta che si apre perché le tabelle sono linkate live, non copiate — nessuna vita propria da tracciare come job. - **Verifica non completata**: `sync_DEV_LOCAL.bat` (il presunto ponte verso il path Dropbox che leggiamo) è **gitignored esplicitamente** in `MyICR_Suite/.gitignore` ("Solo su A, non deve andare su B/C(n)") — non leggibile da Nave, il collegamento resta un'ipotesi non verificata direttamente da codice. Da chiedere a Mauro o verificare da sessione ufficio. **Passo 6 — letto `ParquetToAccessDaily` per intero, codice + CLAUDE.md (Adrian, 2026-07-17)**: `[CONFERMATO]` (codice diretto) - **⚠️ Scoperta importante — collegamento diretto `osservatorio.parquet` ↔ `MonitorOsservatorioPy`**: il job `osservatorio_to_db_day` (in `config/exports.json`) esporta `osservatorio.parquet` → `DB_DAY.accdb`, tabella **`SCHEDA_PRODOTTO`**. Questo è **lo stesso identico nome di tabella** che `MonitorOsservatorioPy` mirrora in `monitor.db` via ODBC all'avvio ("Tabella `SCHEDA_PRODOTTO` — mirror da `db.accdb` Access", v. sezione dedicata sopra). **Ipotesi fortemente supportata** (stesso nome tabella, stesso dominio Osservatorio): `DB_DAY.accdb` generato qui **è** (o è imparentato con) il `db.accdb` che `MonitorOsservatorioPy` legge — chiudendo quasi per intero la catena: `osservatorio.txt` (Direzione Sistemi) → `CsvToParquet_layer1` (daily) → `osservatorio.parquet` → `ParquetToAccessDaily` → `DB_DAY.accdb/SCHEDA_PRODOTTO` → `MonitorOsservatorioPy/monitor.db`. `[IPOTESI forte, non confermata a voce da Mauro]` — da validare esplicitamente: sono davvero lo stesso file `.accdb`, o due file distinti con schema identico per coincidenza? - **Altro job noto**: `gemma_to_db_day` — `gemma.parquet` → `DB_DAY.accdb`, tabella `GEMMA`. Consumer non ancora identificato (candidato: qualcosa lato Acquisti, coerente col filone `gemma.parquet`/W:\ non confermato in precedenza — v. sopra). - **⚠️ Natura dichiarata del modulo — target di smantellamento futuro**: il CLAUDE.md del modulo dice esplicitamente: *"Strato legacy temporaneo — esiste finché i consumer Access non vengono migrati a Parquet. Obiettivo finale: spegnere Access del tutto."* — **questo è precisamente il tipo di "pezzo da smantellare in futuro" che Mauro ha segnalato essere l'obiettivo di questa indagine**. `ParquetToAccessDaily`/`Weekly` sono ponti verso un sistema (Access) che si vuole eliminare, non componenti permanenti dell'architettura. - **Architettura**: `main.py` → legge `config/exports.json` (lista job: source parquet, target accdb, tabella, mode `replace`/`append`, mapping colonne opzionale) → per ogni job, `core/exporter.py` (DuckDB legge il parquet, pyodbc scrive in Access) → step finale di enrichment (`core/enrichment.py`) e pubblicazione su rete (copia il file `.accdb` locale verso destinazioni di rete elencate in `settings.json`). - **Girato su `wincoarcapp01`** con lo stesso venv `C:\venv_ICR` già visto, richiede **MS Access Application installata** (non solo driver ODBC) per la fase di enrichment. - **Gotcha rilevanti**: DAO/COM non funziona su path UNC (serve sempre pyodbc); `mode=replace` fa DROP+CREATE+INSERT (la struttura tabella viene ricreata ogni volta, non solo i dati); batch insert a chunk da 5000 righe per evitare timeout; Access non ha tipo DATE puro via ODBC, va sempre coerto a DATETIME. **Passo 7 — letto `ParquetToAccessWeekly/core/custom_diritti.py` per intero (Adrian, 2026-07-17)**: `[CONFERMATO]` (codice diretto) — **rilevante anche per `data-expert`**, risolve domande aperte lì. - **Colonne `tipo_diritto`/`piattaforma` — CORREZIONE (v. Passo 16 sotto)**: non fanno parte del `diritti.parquet` che leggiamo in `data-expert` (L2) — vivono solo nella versione **L1 (grezza)** che questo codice legge (`ParquetToAccessWeekly` punta a `parquet_dir=C:\PYTHON_LOCAL\CsvToParquet_layer1\parquet`, non a L2). Nel nostro L2 quelle due colonne vengono rimosse dopo il filtro (verificato empiricamente: il nostro parquet non le ha). Non erano "colonne mancanti nel campione precedente" come pensavo inizialmente qui sotto — semplicemente non esistono nella versione che analizziamo. - **`fr_rr` — RISOLTO** (era `[IPOTESI aperta]` in `data-expert.md`): la logica `CalFrRr` calcola **F = First (prima occorrenza)**, **R = Repeat/rinnovo** — un diritto diventa `R` se esiste un'altra riga per lo stesso `PROD` con `DECR` (decorrenza) **antecedente** (escludendo l'anno sentinella 1001). Quindi `fr_rr`/`CalFrRr` è sostanzialmente **First-run vs Repeat/Rinnovo** del diritto sullo stesso prodotto — ipotesi confermata dal codice, va comunque validata a voce con Mauro prima di considerarla definitiva per `data-expert` (che resta la sede giusta per questa informazione, qui la registriamo come cross-reference). - **Nuovo termine scoperto**: `ICR_MONDO` (colonna di `CUSTOM_EMESSO`/probabilmente derivata da `emesso`), valori visti nel codice: `MED_GEN`, `MED_TEM` — usati per determinare se un prodotto è stato effettivamente trasmesso ai fini del calcolo disponibilità/inibizione. Significato esteso non ancora chiarito — `[IPOTESI aperta]`. - **Business logic di `calMotivoInibiz`** (motivo di inibizione per anno, dal 2015 a oggi+5): per ogni prodotto/anno, controlla se i diritti attivi in quell'anno sono "disponibili" o "inibiti", con motivi possibili: `"passaggi esauriti"` (se `PASS_CONS_TOT - PASS_EFF_TOT <= 0`), `"inibito"` (se `FLAG_INIB='S'`), `"<100%"` (se la somma percentuali diritti nel periodo è sotto 100%), `"<30gg"` (se i giorni di disponibilità nell'overlap sono <30). Se il prodotto risulta comunque trasmesso (`emesso_dates` con `ICR_MONDO` in `MED_GEN`/`MED_TEM`) nel periodo nonostante l'inibizione calcolata, il motivo viene azzerato (considerato disponibile). Logica applicata solo alle tipologie: FILM, MINISERIE, SIT COM, TELEFILM, TV MOVIE, SOAP, TELENOVELAS — **esattamente le tipologie core ICR già identificate in `data-expert`** (manca CORTOMETRAGGIO e DOCUMENTARI rispetto all'elenco core — da segnalare come discrepanza minore). - **`CUSTOM_DIRITTI` non è "tutti i diritti"**: è una vista filtrata — solo `tipo_diritto='FREE'` + `piattaforma='ANALOGICO'`, più un fallback per `piattaforma='DVB-T'` quando non esiste una riga Analogico corrispondente (stesso prod/decr/scad) — un workaround esplicito documentato nel docstring ("workaround DVB-T"). Quindi non tutti i diritti passano in questa tabella legacy, solo quelli rilevanti per la trasmissione free/analogica. - **Conferma sentinelle date nel codice**: `IF(decr = '0001-01-01', '1001-01-01', decr)` — stessa sentinella già documentata, vista ora applicata esplicitamente in query SQL. **Passo 8 — letto `custom_emesso.py` per intero (Adrian, 2026-07-17)**: `[CONFERMATO]` (codice diretto) — **cross-validazione importante con `data-expert`**. - **Le 10 tipologie filtrate in `_TIPOLOGIE_EMESSO`** (`FILM, MINISERIE, SIT COM, TELEFILM, TV MOVIE, SOAP, TELENOVELAS, DOCUMENTARI, CORTOMETRAGGIO, TELEROMANZO`) **coincidono esattamente** con le 10 tipologie core ICR confermate a voce da Mauro in `data-expert.md` — commento nel codice: *"Tipologie 'scripted/acquistato' — contenuti che hanno diritti"*. Ottima conferma indipendente (codice vs dichiarazione a voce) che quella lista è corretta e stabile. - **`ICR_MONDO` chiarito ulteriormente**: viene da un file di lookup esterno `emesso_tyfx0160.csv`, joinato su `rete = CCRETETEL` — non è nel parquet `emesso` stesso, è un arricchimento da tabella di mapping rete→mondo. Filtro applicato: solo reti con `ICR_MONDO LIKE 'CON%'` o `'MED%'` (es. `MED_GEN`/`MED_TEM` già visti) — quindi `ICR_MONDO` ha più prefissi/categorie oltre a `CON`/`MED`, non ancora tutte censite. `[IPOTESI aperta]`: `CON` potrebbe stare per "Concorrenza" e `MED` per "Mediaset" — coerente con `emesso.parquet` che include anche reti concorrenti (già noto da sessione precedente) — da validare a voce. - **`PDF_ESISTE`**: flag business — vero solo per reti `C5`/`I1`/`R4`, fascia `PR` (probabile "PRIMA SERATA"), nelle date in cui esiste un PDF secondo la tabella `tabPdfEsiste` in un DB Access "warehouse" (`db_warehouse.accdb`) — presumibilmente traccia l'esistenza di un documento/palinsesto cartaceo/scansionato per la prima serata di quelle 3 reti principali. Dettaglio di nicchia, non approfondito oltre. - **`emesso.parquet` è "Layer 1"**: confermato dal docstring — tutte le reti, nessun dedup — `CUSTOM_EMESSO` applica poi il filtro CON/MED + tipologie sopra. **Passo 9 — letto `custom_anagrafica.py` per intero, il più ricco finora (Adrian, 2026-07-17)**: `[CONFERMATO]` (codice diretto) — moltissimi cross-reference per `data-expert`. - **"RIFER" è letteralmente il nome di colonna nel codice** (`prodotto AS RIFER`) — conferma definitiva, non solo terminologia parlata, che è il nome ufficiale usato anche nelle pipeline. - **`TIPOL_MAP` — codici legacy VBA a 1 carattere per tipologia** (campo Access `VARCHAR(1)`): FILM=`F`, TV MOVIE=`M`, CORTOMETRAGGIO=`7`, DOCUMENTARI=`D`, TELEFILM=`T`, MINISERIE=`E`, SIT COM=`G`, TELENOVELAS=`A`, SOAP=`O`, TELEROMANZO=`Z` — utile tabella di riferimento se si incontrano questi codici altrove (es. in report legacy). - **Mapping `veg` → codice breve Access**: `DA ATTRIBUIRE→N.C`, `UNIVOCA→UNIV.`, `EVER GREEN→EV.GR`, `FICTION AUTOPRODOTTA→F-AUT`, **`FICTION AUTOPRODOTTA RAI→NULL`** (esclusa esplicitamente, coerente con "meno affidabile" già segnalato da Mauro), `NON GESTITA→N.G`, `SUPER TOP→SUPER`, `VISTO-DA ATTRIBUIRE→VISTO`; le lettere A-H passano invariate. - **`DF_ICR_FLAG` — semantica scoperta** (flag business sui diritti free/analogico di un prodotto): `'I'` = inibito (`flag_inib='S'`), `'+'` = più contratti attivi contemporaneamente (append), `'S'` = scaduto/Senza diritti attivi, `'T'` = solo DVB-T (nessun Analogico) — con logica di override esplicitamente replicata dal comportamento legacy VBA (il commento nel codice descrive un workaround storico: "VBA inseriva DVB-T come 'analogico' PRIMA della logica DF_*"). - **`tipo_diritto` ha almeno 3 valori**: `FREE`, `TVOD` (Transactional Video On Demand — nuovo valore mai visto prima), e implicitamente altri non filtrati qui. - **Decodifica `scelte_rete` — rete e slot per esteso** (mai vista prima, utile per completare lo schema di `scelte_rete.parquet` in `data-expert`): - Reti: `CANALE 5→C5`, `ITALIA 1→I1`, `RETEQUATTRO→R4`, `LA 5→L5`, `IRIS→IR`, `CINE34→C34`, `TWENTYSEVEN→C27`, `ITALIA 2→I2`, `20→20`, `FOCUS→FO`, `TOP CRIME→TC` - Slot: `NOTTE→NO`, `PRIMETIME GARANZIA→PT G`, `MATTINA→MA`, `POMERIGGIO→PO`, `SECONDA SERATA GARANZIA→SS G`, `PRIMETIME ESTATE→PT E`, `SECONDA SERATA ESTATE→SS E`, `ACCESS PRIMETIME GARANZIA→AP G`, `PRIMETIME STRENNE→PT S`, `SECONDA SERATA STRENNE→SS S`, `ACCESS PRIMETIME STRENNE→AP S`, `ACCESS PRIMETIME ESTATE→AP E` - **`ICR_MONDO` — lista più ampia di categorie** scoperta qui: `CONCOR`, `MED_GEN` (generaliste), `MED_TEM`, `CON_TEM` (tematiche), `SKY`, `FOX` (satellite), `PREMIUM` (legacy, commento nel codice: *"VBA eliminava i record Premium come obsoleti"* — categoria di fatto dismessa). - **Ruoli cast identificati**: `'FRE'` = regista (usato per popolare REGISTA_NOME/COGN), `'C001'` = attore principale (i primi 3 per `progr_cast` diventano ATTORE1/2/3). - **Boxoffice — logica di aggregazione precisa**: prima uscita = `MIN(data_debutto)` tra i soli record `tipo_programmazione='D'` (debutto); incasso/spettatori sommati **solo per l'anno solare della prima uscita**, non su tutta la storia. - **Logica stagione corrente**: cutoff 10 settembre — dopo quella data la stagione è `anno/anno+1`, prima è `anno-1/anno`. **Passo 10 — letto `boxoffice.py` (Adrian, 2026-07-17)**: `[CONFERMATO]` (codice diretto) - **Mappa completa rete→broadcaster** (`_RETE_TO_NETWORK`, decine di codici canale): gruppi risultanti sono `MEDIASET` (generaliste C5/I1/R4 + tematiche + Premium legacy), `RAI` (R1/R2/R3/RAI4/RAIM/RAIP), `LA7` (LA7/LA7C/LA7D), `SKY` (canali tematici + ex-FOX confluiti in Sky), `BOING`, `DISCOVERY` (+NOVE, canali correlati), `PARAMOUNT`, `SONY` — è di fatto la mappa competitiva completa usata dalla pipeline per calcolare `RETE_FREE`/broadcaster di prima TV. Utile riferimento se serve raggruppare `emesso.parquet`/reti per competitor. - **`BOXOFFICE` (vista legacy) filtra solo prodotti con uscita cinema dal 2010** (`BOX_DATA >= 2010-01-01`) — coerente con l'essere una vista storica per confronto VBA, non tutto `boxoffice.parquet`. - **`PRIMA_TV_LIBERA`**: prima trasmissione free (generalista o tematica, la più antica tra `EM_PG_*`/`EM_PT_*`), formattata come stringa `"RETE dd/mm/yy HH.MM"` — poi usata per derivare il broadcaster. **Passo 11 — letto `WriteToAccess/writer.py` (Adrian, 2026-07-17)**: `[CONFERMATO]` — il "mattoncino" riusabile che tutti i moduli usano per scrivere DataFrame→Access. Conferma i gotcha già noti dal CLAUDE.md (fix decimali italiani, `schema.ini` con ordine colonne del DataFrame, gestione date/None) — nessuna sorpresa rispetto a quanto già documentato, coerente e ben fattorizzato. **Passo 12 — ricerca provenienza `linker.db`/`ott.db`/`mediatrack.db` (Adrian, 2026-07-17)**: `[IPOTESI aperta, NON risolta]` — cercato dentro `PYTHON/MyICR_Suite/app/` per capire dove questi tre mirror SQLite vengono generati. **Non ancora trovato un generatore esplicito** nel codice esplorato finora — sono presenti come file dati (`sync_db/linker.db`, `sync_db/mediatrack.db`, `local_db/ott.db`, `local_db/linker.db`) ma non ho ancora individuato lo script/job che li scrive. Ipotesi da verificare: (a) generati da moduli interni di MyICR_Suite (`app/modules/linker/`, integrazione OTT, MediaTrack — nomi che corrispondono a moduli visti in `PYTHON/CLAUDE.md`), non dalla pipeline SCHEDULATORE; (b) alimentati da fonti esterne (es. OTT da piattaforme streaming, MediaTrack da un sistema terzo) più che da CSV Direzione Sistemi. **Prossimo passo consigliato**: usare `flussi-expert`/`glob` per cercare riferimenti a `linker.db`/`ott.db`/`mediatrack.db` dentro `app/modules/` — non ancora fatto in questa sessione per limiti di tempo, segnato come thread aperto prioritario. **Passo 13 — RISOLTO: provenienza `linker.db`/`mediatrack.db`** (Adrian, 2026-07-17, `grep` mirato + letti `app/modules/linker/CLAUDE.md` e `MyICR_Suite/CLAUDE.md`): `[CONFERMATO]` (codice/doc diretti) - **`linker.db`** = repository sidecar SQLite del modulo **Linker** (PyQt6, erede del vecchio tool VBA "MyICR" in Excel) — **non generato dalla pipeline SCHEDULATORE**, è lo storage delle scritture proprie dell'app Linker (aggancio manuale RIFER↔titoli, valutazioni, ecc.). Migrazione da MS Access completata il 2026-04-29 — prima usava Access, ora SQLite + Parquet DataHub_v2 in sola lettura per i dati di riferimento. - **`mediatrack.db`** = aggiornato dal job Frank **"OMDB Batch Enrich"** (attivo, lun-ven 13:30 — già in elenco job Frank nella Costituzione). Server = fonte di verità: `I:\SOFTWARE\PYTHON_SRV\sync_db\mediatrack.db`, sincronizzato via `pull_and_preserve_local()` in `launcher_src/shared_db_sync.py` (pull server → merge locale → push modifiche nuove, ogni 5 min + al quit app). - **`ott.db`**: non ancora trovato un generatore esplicito, ma dato il pattern (modulo `ott_sheet.py` dentro Linker) probabile sidecar analogo, gestito dallo stesso modulo Linker — `[IPOTESI]`, non verificato quanto gli altri due. **⚠️ Scoperta rilevante — rischio operativo reale (non solo curiosità)**: `MyICR_Suite/CLAUDE.md` documenta che **`\\mediaset.it\share\Indirizzo_Controllo_Risorse` è un namespace DFS con repliche fuori sync dal 17/03/2026** — regola esplicita: *"Non usare mai UNC nel codice"*, va sempre usato il path locale/drive mappato (`I:\`) invece. Questo è esattamente il tipo di "pezzo da conoscere per problemi futuri" segnalato da Mauro come motivazione dell'indagine — un disallineamento di replica DFS silenzioso potrebbe restituire dati stale a seconda di quale replica risponde, se qualcosa nel sistema usasse ancora UNC invece di `I:\`. **RISOLTO — "diritti.parquet solo FREE/Analogico-DVB-T" (Adrian, 2026-07-17)**: `[CONFERMATO]` (codice + verifica empirica diretta) — non era una discrepanza, era comprensione incompleta mia. Spiegazione completa: - **Esistono due `diritti.parquet` distinti, stesso nome, contenuto diverso**: L1 (grezzo, `C:\PYTHON_LOCAL\CsvToParquet_layer1\parquet\diritti.parquet` — fedele al CSV sorgente `estr_acq.csv`, contiene tutti i `tipo_diritto`: FREE, TVOD, IPTV, DVB-H, Internet) e **L2** (filtrato, `...\MyICR_Suite\local_db\parquet\diritti.parquet` — quello che leggiamo noi in `data-expert`). - **La trasformazione L1→L2** (`_transform_diritti()` in `Parquet_layer1ToParquet_layer2/main.py`) filtra esplicitamente: mantiene **tutti** i Free+Analogico, più i Free+DVB-T **solo se non coperti** da un Analogico per lo stesso prodotto/periodo (decr/scad) — esclude TVOD, IPTV, DVB-H, Internet, e i DVB-T ridondanti. Rimuove anche le colonne `tipo_diritto`/`piattaforma` dopo il filtro perché diventano ridondanti (tutto ciò che resta è Free by construction). - **Verifica empirica**: il nostro `diritti.parquet` (Dropbox, L2) **non ha le colonne `tipo_diritto`/`piattaforma`** — confermato via query diretta, coerente al 100% col codice. - **`ParquetToAccessWeekly` (custom_diritti.py/custom_anagrafica.py) legge L1, non L2**: il suo `config/settings.json` ha `"parquet_dir": "C:\\PYTHON_LOCAL\\CsvToParquet_layer1\\parquet"` — cioè l'output grezzo di Layer 1, non quello filtrato di Layer 2. Ecco perché quel codice può referenziare `tipo_diritto='TVOD'` (per i campi legacy `DTVOD_*`) — lavora sul dataset completo non filtrato, e applica la propria logica di filtro Free/Analogico+DVB-T in autonomia per costruire `CUSTOM_DIRITTI`, indipendentemente dal filtro già applicato a L2. - **Conclusione per `data-expert`**: il nostro `diritti.parquet` è davvero solo Free/Analogico+DVB-T(non coperto) — `MyICR_Suite/CLAUDE.md` aveva ragione. Nessuna azione necessaria sulla KB data-expert, era solo la mia comprensione della catena a essere incompleta. **Architettura contrastante MyICR_Suite vs pipeline SCHEDULATORE**: `MyICR_Suite/CLAUDE.md` dichiara una regola architetturale opposta a quella della pipeline: *"MyICR_Suite non deve usare MS Access... NO MS Access"* — mentre `ParquetToAccessDaily`/`Weekly` esistono apposta per scrivere in Access (legacy temporaneo per consumer non ancora migrati). Le due parti dell'ecosistema hanno direzioni tecniche opposte e consapevoli: MyICR_Suite va verso DuckDB/Parquet/SQLite puro, la pipeline SCHEDULATORE mantiene un ponte Access finché serve. **Passo 14 — TROVATO IL DIZIONARIO DATI COMPLETO (Adrian, 2026-07-17)**: `[CONFERMATO]` — esiste già un documento che copre gran parte di quello che abbiamo ricostruito a mano: **`PYTHON/MyICR_Suite/app/modules/powerbricks/NL_semantic_layer/NL_QUERY_SCHEMA.md`** (v1.1, 2026-06-08) — dizionario dati completo per tutti i parquet L2 + `linker.db` + `ott.db` + `mediatrack.db`, con 16 gotcha già documentati. **Da trattare come fonte primaria di riferimento per entrambe le KB (flussi + data-expert) andando avanti** — leggerlo direttamente quando serve un dettaglio di schema, non duplicarlo qui riga per riga. **Risoluzioni ottenute da questo documento**: - **`ott.db` — provenienza RISOLTA (parzialmente superata, v. Passo 20)**: mirror del file Excel settimanale Qlik/Marketing, **rimpiazzato completamente ad ogni import** (non incrementale), distribuito ai client via pull al boot (come i parquet). Fonte server dichiarata in questo documento (2026-06-08): `I:\SOFTWARE\PYTHON_SRV\PYTHON_LOCAL\MyICR_Suite\local_db\ott.db`. Quindi non viene dalla pipeline SCHEDULATORE né da OnAir — è un canale a parte, alimentato da un file Excel Marketing/Qlik. **Attenzione**: il nome file `ott.db` è oggi superato nel codice reale — vedi Passo 20, il file davvero scritto è `ott.parquet`. - **`OTT_EXT`/`LINKER_REPOSITORY`/`LINKER_VALUTAZIONE`/`COMPETITIVE_IMDB_MAP`** vivono tutte in `sync_db/linker.db` (server: `I:\SOFTWARE\PYTHON_SRV\sync_db\linker.db`) — schema completo con PK, join, e note già in `NL_QUERY_SCHEMA.md`. - **`fr_rr` — versione più precisa**: nel parquet grezzo i valori sono `F`=First Run, `R`=Repeat, `S`=Sospeso, `M`=Mixed (i valori M/S che avevamo lasciato "da spiegare" in data-expert sono quindi stati "Mixed"/"Sospeso" — non anomalie). `CalFrRr` (calcolato in `custom_diritti.py`, v. Passo 7) è una **ricalcolazione derivata** F/R per la vista legacy, non il valore originale. - **⚠️ Discrepanza trovata — probabile ERRORE nel documento, non un dubbio alla pari**: il documento dice esplicitamente *"⚠ RIFER vs prodotto: In tutta l'applicazione il termine RIFER indica `prodotti.program_id` (VARCHAR, es. 'ANA00101', '338467'). Il campo `prodotto` (INTEGER) è l'ID interno numerico. Sono due chiavi diverse."* — questo **contraddice direttamente** la conferma esplicita di Mauro all'inizio di questa sessione (2026-07-17): *"program_id è un campo che non uso... non rilevante per il business"* e *"rifer = il campo prodotto"* (il codice, INTEGER). Contraddice anche il codice di `custom_anagrafica.py` (Passo 9), che fa letteralmente `prodotto AS RIFER`. **Conclusione — CHIUSO, confermato esplicitamente da Mauro (2026-07-17)**: *"nel parquet prodotti il rifer si chiama prodotto. Program_id non so cosa sia"* — la frase in `NL_QUERY_SCHEMA.md` era **sbagliata/superata** (documento datato 2026-06-08, mai validato a voce con Mauro). `[CONFERMATO]` in via definitiva: `rifer` = `prodotti.prodotto`, `program_id` è un campo di cui Mauro stesso non conosce lo scopo — da trattare come non rilevante/non affidabile per il business, non come chiave alternativa di rifer. **Nota generale**: questo è un buon esempio del perché documenti trovati nel codice vanno sempre incrociati con la conferma diretta di Mauro prima di darli per validi — possono essere scritti da altri agenti (es. Elon, autore della spec PowerBricks originale) senza revisione business successiva. - **Tabelle di decodifica aggiuntive trovate** (non ancora in `data-expert`): `fascia` in emesso (MA/ME/PO/PS/PR/SS/NO/AR/GR con orari indicativi), ruoli cast estesi (FSS=sceneggiatore, FMN=montatore, FFO=fotografo, C025=musicista, FSU=autore soggetto, C006=conduttore, VOC=voce, SS=se stesso, FDP=doppiatore — oltre a FRE/C001 già noti). - **Il progetto PowerBricks stesso** (per cui questo documento esiste) ha già convenzioni di output stabilite indipendentemente dalle nostre: prima colonna sempre `program_id AS rifer`, scadenze aggregate con `MAX(scad)`, "ambiguità → chiedere sempre prima di eseguire" — coerenti nello spirito con le convenzioni già fissate in `data-expert` (sintesi prima del dettaglio, segnalare semplificazioni). **Passo 15 — RISOLTO definitivamente: `DB_DAY.accdb` → `MonitorOsservatorioPy`, con meccanismo esatto** (Adrian, 2026-07-17, letti `config/settings.json` + `core/enrichment.py` per intero): `[CONFERMATO]` (codice diretto, non più ipotesi) **Path esatti** (da `ParquetToAccessDaily/config/settings.json`): - `DB_DAY.accdb` locale: `C:\PYTHON_LOCAL\ParquetToAccessDaily\DB_DAY.accdb`, pubblicato su `\\mediaset.it\share\Indirizzo_Controllo_Risorse\SOFTWARE\ProcedureICR\ICRmonitor\MonitorLinker\DB_DAY.accdb` - `db.accdb` (MonitorOsservatorioPy): **`\\mediaset.it\cologno\osservatorio_progetti\MonitorOsservatorio\db.accdb`** — path diverso, share diversa (`cologno` vs `share\Indirizzo_Controllo_Risorse`) **Meccanismo esatto** (funzione `enrich_osservatorio()` in `core/enrichment.py`): 1. Conta le righe in `SCHEDA_PRODOTTO` dentro `DB_DAY.accdb` locale 2. **Solo se count > 30.000** (soglia di sicurezza contro un mirroring di dato incompleto/corrotto), copia la tabella `SCHEDA_PRODOTTO` da `DB_DAY.accdb` verso `db.accdb` (il file di MonitorOsservatorioPy) via `Access.Application.TransferDatabase` (COM) 3. **In parallelo**, copia anche `osservatorio.txt` grezzo (lo stesso file letto da `CsvToParquet_layer1`) direttamente dentro la cartella progetto di MonitorOsservatorio — un secondo canale di distribuzione, indipendente dal parquet/Access **Conferma quindi la catena completa**: `osservatorio.txt` (Direzione Sistemi, in `SCHEDULATORE\INPUT`) → **due strade in parallelo**: (a) via `CsvToParquet_layer1`/`ParquetToAccessDaily`/`enrich_osservatorio` → `db.accdb` di MonitorOsservatorioPy (tabella `SCHEDA_PRODOTTO`, solo se count>30k); (b) copia diretta del file grezzo nella cartella progetto MonitorOsservatorio. **Scoperta collaterale — terzo file Osservatorio**: esiste ANCHE un `db_Osservatorio.accdb` (path: `...\SOFTWARE\ProcedureICR\ICRmonitor\MonitorLinker\db_Osservatorio.accdb`, diverso sia da `DB_DAY.accdb` che da `db.accdb`), popolato dalla funzione `integrazione_update_day()` con una tabella `tabOsservatorio` (schema quasi identico a `osservatorio.parquet`) — consumer non ancora identificato, probabilmente un modulo Access legacy separato da MonitorOsservatorioPy. `[IPOTESI aperta]`. **Passo 17 — quantificato con precisione il filtro Free+DVB-T (Adrian, 2026-07-17, query dirette via Frank-relay `duckdb_query` su L1)**: `[CONFERMATO]` (dato reale, non solo codice) - **Distribuzione `tipo_diritto` su tutto `diritti.parquet` L1** (273.213 righe totali): Free 96.516 (35,3%), Pay-SVOD 58.949 (21,6%), TVOD 42.527 (15,6%), Pay-Premium 31.273 (11,4%), Pay-Basic 30.272 (11,1%), FOD-AVOD 13.676 (5,0%). - **Distribuzione `piattaforma` dentro `tipo_diritto='Free'`** (96.516 righe): Analogico 51.209 (53,0%), DVB-T 27.232 (28,2%), DVB-H 18.075 (18,7%). - **Filtro Free+DVB-T verificato con query diretta**: delle 27.232 righe Free+DVB-T, solo **169 (0,6%)** non sono coperte da un Analogico corrispondente (stesso `prod`, intervallo `decr-scad` incluso) — le altre 27.063 (99,4%) sono scartate come ridondanti. `51.209 (Analogico) + 169 (DVB-T non coperto) = 51.378` — combacia esattamente con la cardinalità nota di `diritti.parquet` L2. - **Free+DVB-H (18.075 righe) escluso interamente**, nessuna eccezione — a differenza del DVB-T non ha un percorso di "salvataggio" condizionale. - **Quadro completo**: solo il **18,8%** dei 273.213 diritti totali gestiti da ICR arriva in L2/MyICR_Suite/PowerBricks; l'**81,2%** (quasi tutto DVB-H e DVB-T, tutto il pay/streaming: SVOD/TVOD/Premium/Basic/AVOD) resta invisibile a chi usa quei sistemi. - **Composizione delle 169 righe Free+DVB-T "salvate"** (query su `ragsoc_distr`, confermando ipotesi di Mauro): **Paramount Pictures International Limited = 75/169 (44,4%)**, di gran lunga il distributore più rappresentato. Resto frammentato: Universal Studios (28, 16,6%), vari festival cinema, Disney Italia (10), BBC Worldwide (2), Rai Cinema (2), altri distributori minori a 1-2 righe ciascuno. **Passo 18 — la storia vera dietro il workaround DVB-T (Mauro, 2026-07-17)**: `[CONFERMATO]` — **il pezzo di business logic più importante scoperto finora**, spiega tutto quello quantificato al Passo 17. - **Il bacino "core" per ICR è sempre stato Free+Analogico al 100%** — sono i diritti usati dalle reti Free principali di Mediaset (C5/I1/R4 e affini). Tutte le elaborazioni ICR sono storicamente pensate per questo bacino. - **Origine del DVB-T (~1-2 anni fa)**: l'**ufficio diritti/contratti** — lo stesso che sovraintende anagrafica ed emesso OnAir, responsabile **Sara Ragazzi** — ha introdotto la categoria "Free DVB-T" per gestire contratti di prodotti trasmissibili su **canali secondari** (non C5/I1/R4). **ICR non è stato informato/coinvolto in questa decisione.** - **Conseguenza per ICR**: i prodotti con diritto solo-DVB-T sono diventati "invisibili"/introvabili nei sistemi ICR (perché il bacino core era solo Analogico). ICR ha dovuto risalire da sola alla causa (la "sporcacciata" fatta dall'ufficio diritti) e costruire una **pezza** per rendere questi prodotti assimilabili al resto — è esattamente la logica `NOT EXISTS`/"workaround DVB-T" trovata in `_transform_diritti()` (Passo 16/17): tenere il DVB-T solo se non già coperto da un Analogico, per evitare duplicati, e farlo confluire nello stesso bacino. - **Il problema che resta aperto, esplicitamente segnalato da Mauro come da sistemare**: la pezza attuale è considerata "orrenda" perché **non espone da nessuna parte** (né in L2/MyICR_Suite, né in PowerBricks) il fatto che un prodotto sia arrivato tramite questo canale DVB-T "secondario" — anzi, il codice **rimuove esplicitamente** le colonne `tipo_diritto`/`piattaforma` dopo il filtro (Passo 16), cancellando ogni traccia distintiva. **Dovrebbe invece essere sempre segnalato ovunque** (specialmente in MyICR_Suite) che un prodotto è "utilizzabile sui secondary channels" — oggi non lo è. Nella vista legacy Access (`DB_WEEK.accdb`/`CUSTOM_ANAGR_ORIZ`) esiste un flag parziale (`DF_ICR_FLAG='T'`, v. Passo 9) ma **solo se il prodotto non ha ALCUN diritto Analogico** — non copre il caso (probabilmente più comune) di un prodotto con diritti misti Analogico+DVB-T su edizioni/periodi diversi, e comunque MyICR_Suite (che ha bandito Access per policy propria) non eredita nemmeno questo flag parziale. - **Candidato concreto per lavoro futuro** (esattamente il tipo di "pezzo da fixare" anticipato da Mauro a inizio di questa indagine): aggiungere un flag esplicito "canale secondario/DVB-T" visibile in MyICR_Suite/PowerBricks per i prodotti che passano da questo workaround — oggi assente. **Passo 19 — indagine dedicata sul consumer di `db_Osservatorio.accdb`/`tabOsservatorio` (Adrian, 2026-08-04, letto `core/enrichment.py` per intero + `config/settings.json` + query Frank-relay dirette su `I:\`)**: `[CONFERMATO]` (codice + evidenza diretta su `I:\`) — il file **non esiste più**, la pipeline se ne accorge da sola e si disattiva in silenzio; nessun consumer trovato. - **Trovata la funzione per intero** (era solo citata a memoria in precedenza): `integrazione_update_day()` in `PYTHON/PIPELINE_PYTHON/ParquetToAccessDaily/core/enrichment.py` (7° e ultimo step di `run_all()`, chiamato incondizionatamente a ogni run **giornaliero** — non weekly). Replica la vecchia sub VBA `IntegrazioneUpdateDay`: `DELETE FROM tabOsservatorio` → `INSERT` da una tabella `osservatorio` (dentro lo stesso `db_Osservatorio.accdb`, popolata a monte da un altro step non ancora tracciato) → update del campo `episodi` → `CompactRepair` finale via COM. - **Path esatto confermato da `config/settings.json`**: `db_osservatorio` = `\\mediaset.it\share\Indirizzo_Controllo_Risorse\SOFTWARE\ProcedureICR\ICRmonitor\MonitorLinker\db_Osservatorio.accdb` = `I:\SOFTWARE\ProcedureICR\ICRmonitor\MonitorLinker\db_Osservatorio.accdb` — combacia esattamente col path già noto dal Passo 15/16, non un file diverso. - **Guard esplicito nel codice**: prima di fare qualunque cosa, la funzione controlla `os.path.isfile(db_path)` — se il file non c'è, stampa `"integrazione_update_day: file non trovato (...) — saltato"` e **ritorna subito, senza errore, senza toccare i marker `_s_updater`**. Questo è il meccanismo chiave per capire cosa è successo. - **Verifica diretta su `I:\` via Frank-relay (2026-08-04)**: `glob` di `db_Osservatorio*.accdb` su tutto `I:\SOFTWARE` → **zero risultati**. Il file non è in `MonitorLinker\` (coerente con quanto già osservato dall'utente in list_dir) né altrove sotto `I:\SOFTWARE`. Non è stato rinominato/spostato in una sottocartella nota (es. `ARCHIVIO\`) — semplicemente non esiste più in quell'albero. - **Prova indiretta ma molto forte, dai marker di log** (`list_dir` su `I:\SOFTWARE\ProcedureICR\ICRmonitor\MonitorLinker\UPDATES\log`, 2026-08-04): ogni altro step di `run_all()` (GEMMA, VALUTAZIONI, MOSSERVATORIO_Schede, Manutenzione_CalRicStato, Manutenzione_InfoGlobal, GEMMA_ONIARPLUS/typo originale) ha un marker `UPDATE_ al 04-08-26.txt` — cioè la pipeline daily gira ed **è arrivata in fondo regolarmente oggi**. Il marker `UPDATE_IntegrazioneUpdateDay al 05-06-26.txt` è invece fermo al **5 giugno 2026** — quasi due mesi fa. Dato che `_s_updater("...", "End", ...)` viene chiamato solo se la funzione arriva alla fine (e qui il file mancante causa un `return` immediato, prima ancora della chiamata "Start"), questo pattern è coerente al 100% con: **il file è sparito attorno al 5 giugno 2026, e da allora questo step fa un no-op silenzioso ogni singolo giorno**, senza mai comparire come errore/alert da nessuna parte. - **Nessun consumer trovato**: `grep -rli "tabosservatorio\|db_osservatorio"` su tutto `Dropbox/adrian/PYTHON/` trova **solo** i due file già noti (`settings.json` e `enrichment.py` stesso — cioè lo scrittore, non un lettore). Nessun altro progetto Python (`MyICR_Suite`, `MonitorOsservatorioPy`, `Frank`, ecc.) referenzia questa tabella o questo file. `[IPOTESI, non verificabile oltre da Nave]`: probabile che il consumer fosse un tool Access/VBA legacy (coerente con la cartella `I:\SOFTWARE\SCHEDULATORE\vecchia procedura VBA` già vista nell'albero, mai esplorata in dettaglio) ormai decommissionato — il codice VBA dentro un `.accdb` non è ispezionabile in sola lettura via Frank-relay con gli strumenti attuali (non è testo semplice). Non confondere con `MonitorOsservatorioPy`/`db.accdb` (share `cologno`, tabella `SCHEDA_PRODOTTO`) — quel canale è vivo e confermato attivo oggi (marker `UPDATE_MOSSERVATORIO_Schede al 04-08-26.txt`), è un sistema del tutto separato. - **Conclusione pratica**: non un'anomalia da "dato mancante" nel senso classico — è un ramo di pipeline morto ma innocuo (fallisce in modo pulito, non produce errori, non blocca gli altri step). Segnalabile a Mauro come possibile candidato di pulizia (rimuovere lo step orfano da `run_all()`), ma **nessuna proposta di fix fatta qui** — resta una diagnosi, non un intervento. **Passo 20 — incidente reale OTT, meccanismo di import risolto (Adrian, 2026-09-02, letto `app/modules/linker/src/gui/ott_sheet.py` per intero + verifica diretta su copia del file server)**: `[CONFERMATO]` (codice + dato reale) — Mauro temeva che un file OTT appena caricato non fosse arrivato sul server; falso allarme, ma ha fatto emergere il vero meccanismo, mai documentato prima. - **`ott.db` è un nome superato**: nel codice attuale non esiste più un `ott.db` generato dall'import — il file scritto è `ott.parquet` (`I:\SOFTWARE\PYTHON_SRV\PYTHON_LOCAL\MyICR_Suite\local_db\parquet\ott.parquet`). La riga sopra (Passo 14, dal `NL_QUERY_SCHEMA.md` del 2026-06-08) documentava un nome ormai obsoleto — controllare sempre `ott.parquet`, non `ott.db`, quando si diagnostica un problema di import OTT. - **Funzione di import**: `_carica_file()` in `ott_sheet.py` (righe 1710-1788), collegata al pulsante "Importa" del foglio OTT dentro Linker. Flusso in due fasi: 1. **Locale, sincrona**: scrive `ott.parquet` locale (`local_db/parquet/ott.parquet`) e aggiorna `linker.db` SQLite locale — sempre completata, la UI la riflette subito. 2. **Server, asincrona**: in un thread separato (righe 1769-1778), `shutil.copy2()` copia il parquet verso `I:\...\local_db\parquet\ott.parquet` (path letto da `config.json`, campo `_ott_parquet_server_path`). - **⚠️ Gap reale, non un bug ma un design fragile**: il messaggio "record caricati" in UI (riga 1787) appare **subito dopo aver avviato** il thread di push, non dopo che sia riuscito. Se il push fallisce (rete, permessi, file bloccato da un'altra istanza), l'utente vede comunque conferma di successo — **nessun errore in UI**. L'unica traccia è `logger.warning(f"Push ott.parquet fallito: {e}")`, destinazione log da verificare (non necessariamente lo stesso handler del launcher). - **La vista OTT dentro Linker legge solo dati locali**: `get_ott_joined_for_mode` (`parquet_db.py:773`) fa un JOIN DuckDB tra `ott.parquet` locale e `ott_ext` (tabella in-memory caricata da `linker.db` locale) — **mai** dal file server. Quindi vedere dati aggiornati nella griglia Linker **non prova** che il push al server sia riuscito, prova solo che l'import locale è riuscito. Nessun indicatore/colonna distingue le due cose. - **Caso reale del 02/09/2026**: verificato con query diretta sul file copiato manualmente dal server (Dropbox non disponibile in quel momento sul lato ufficio, query duckdb via tunnel-nave-ufficio mai arrivata) — i titoli del batch odierno (`The Djinn`, `Northern Shade`, `Rogue Hostage`, `Corvo Bianco - Il cecchino del Donbas`, con provider/date/flag `smontato` corrispondenti esattamente a quanto visto in UI) erano già presenti nel file server. **Il push aveva funzionato**, il falso allarme veniva dal controllare `ott.db` (nome sbagliato/superato) invece di `ott.parquet`. - **Suggerimento per la prossima volta che càpita un dubbio simile**: verificare `ott.parquet` sul server (non `ott.db`), e se serve conferma sul contenuto (non solo la data di modifica) cercare titoli specifici noti nel file — la data del file da sola non basta se si sospetta un import parziale o silenzioso. *(catena principale ricostruita, quantificata e ora spiegata nel suo perché business — punto `db_Osservatorio.accdb` chiuso del tutto: cancellato da Mauro stesso, confermato a voce 04/08/2026, nessun consumer perso)* ## Anomalie diagnosticate *(vuoto — popolare quando emerge un caso concreto da diagnosticare)* ## Limiti di visibilità da Nave — SUPERATO per la sola lettura (2026-07-17) `[CONFERMATO]` (2026-07-17, canale aggiornato il 03/09/2026) — `DataHub_v2`, `ParquetToAccess`, `PIPELINE_PYTHON` vivono su `I:\SOFTWARE\SCHEDULATORE\...`, share di rete Windows-ufficio non raggiungibile **direttamente** da Nave/Linux (nessuna via di rete). **Esplorabile in sola lettura tramite `SendMessage` diretto a `gemello-ufficio`** (v. sezione "Protocollo attivo" sotto) — nessun protocollo/schema busta, conversazione diretta tra due sessioni Adrian. Resta vero che non c'è accesso diretto/di rete da Nave, ma il canale lo rende comunque raggiungibile in pratica per diagnosi read-only. ## Protocollo attivo — SendMessage diretto a gemello-ufficio (dal 03/09/2026) `[CONFERMATO]` — le query di sola lettura (`list_dir`, `read_file`, `glob`, `duckdb_query` su `I:\`/`W:\`/`X:\`) non passano più da nessun file-tunnel: vanno con `SendMessage` diretto a `gemello-ufficio` (sessione Claude Code sulla stessa macchina ufficio, root `PYTHON\`), che ha accesso locale naturale a quei path e può eseguire lettura file/query duckdb come qualunque sessione interattiva — nessun protocollo/schema busta da rispettare, è conversazione diretta tra due sessioni Adrian. Deciso con Mauro (03/09/2026) durante il ridisegno fire-and-forget di `gate-ufficio`: quelle letture sono diagnosi con giudizio dietro (capire un flusso dati), non trigger meccanici — non hanno mai avuto senso a stare nello stesso canale del trigger job puramente meccanico. `gemello-ufficio` risponde sempre e solo ad Adrian (mai a Mauro direttamente, come ogni istanza — v. `CLAUDE.md`), quindi il ponte per arrivare a `flussi-expert` resta comunque mediato da una sessione Adrian viva. ## Protocolli storici — dismessi, entrambi sostituiti da SendMessage diretto (vedi sopra) `[STORICO]` — due canali precedenti, ormai solo cronaca: il **Frank-relay** (07/2026, file JSON `tasks/to-frank/`↔`tasks/from-frank/` via Dropbox, `list_dir`/`read_file`/`glob`/`duckdb_query`) e la sua copia 1:1 dentro **gate-ufficio** (`readonly_actions.py`, 08/2026). Entrambi dismessi il 03/09/2026 col ridisegno fire-and-forget, codice rimosso fisicamente il 04/09/2026 — nessuna traccia rimasta nel repo. ## Altri drive di rete mappati — organigramma aziendale `[CONFERMATO]` (Mauro, 2026-07-17) — oltre a `I:\` (SCHEDULATORE, territorio ICR), risultano mappati anche `W:\` e `X:\`, ma appartengono a **Direzioni aziendali diverse da ICR**: - **`X:\` = struttura Osservatorio (per esteso: Osservatorio Internazionale)** — Direzione separata. - **`W:\` = struttura Acquisti** — Direzione separata. - **Verso del flusso confermato (Mauro, 2026-07-17)**: `[CONFERMATO]` — **è ICR che fornisce informazioni/applicativi a Osservatorio e Acquisti, non il contrario**. La mia prima ipotesi ("questi drive sono probabile fonte di osservatorio.parquet/gemma.parquet") aveva il verso sbagliato. - **Provenienza reale di `osservatorio.parquet` chiarita (Mauro, 2026-07-17)**: `[CONFERMATO]` — arriva sì (indirettamente) da Osservatorio, ma **tramite Direzione Sistemi**, non dal drive mappato `X:\`. `X:\` è quindi scartato come canale di provenienza per questo dataset — il collegamento nome/dataset era fuorviante. Il percorso reale (Osservatorio → Direzione Sistemi → ICR/parquet) non è ancora stato indagato nel dettaglio: non sappiamo se Direzione Sistemi esponga un export/feed che Frank/una pipeline preleva, o se avvenga altrimenti. - **`gemma.parquet`/Acquisti**: nessun chiarimento ancora ricevuto — non dare per scontato lo stesso pattern (via Direzione Sistemi) senza conferma esplicita. - Testati entrambi via Frank-relay (2026-07-17): primo tentativo `WinError 3` (drive non ancora mappato in quella sessione), secondo tentativo `ok` — contenuto osservato: `X:\` = trailer PDF, template PPT; `W:\` = presentazioni/documenti (es. "Amazon MGM Slate Presentation", "backup slide library cinema") — coerente con condivisioni documentali di Direzioni diverse da ICR, non dati strutturati come i parquet SCHEDULATORE. - **Nota per lo scope**: essendo Direzioni diverse da ICR e riceventi (non fonte) rispetto a ICR, questi drive sono di interesse marginale per la diagnostica flussi quotidiana — il focus resta `I:\SOFTWARE\SCHEDULATORE\` e `PYTHON/`. ## MonitorOsservatorioPy — applicativo ICR per Osservatorio `[CONFERMATO]` (2026-07-17, letto `PYTHON/MonitorOsservatorioPy/CLAUDE.md`, sola lettura) — esempio concreto del flusso ICR→Osservatorio appena confermato: Mauro sta sviluppando questo applicativo (evoluzione Python di un tool VBA/Excel) **per** la Direzione Osservatorio. - **Scopo**: importa file `.xlsx` con dati ascolti TV (share/rating) per Spagna e Germania, **ricevuti dal Marketing** (non da Osservatorio direttamente, non da Direzione Sistemi) — mostra in griglia editabile, permette aggancio a `SCHEDA_PRODOTTO` (mirror Access via `monitor.db`), esporta xlsx sidecar. - **Deploy**: `\\Mediaset.it\cologno\Osservatorio_Progetti\MonitorOsservatorio\MonitorOsservatorioPy\` (path UNC, da CLAUDE.md) = **`X:\MonitorOsservatorio\MonitorOsservatorioPy`** (Mauro, 2026-07-17) — stesso posto, il drive `X:\` (struttura Osservatorio, v. sopra) è il mapping locale di quella share. Quindi `X:\` **non è solo di interesse marginale come segnato sopra** — è anche la destinazione di deploy di un applicativo che ICR sviluppa attivamente, esplorabile via Frank-relay se serve ispezionare la versione in produzione. Tier 1 (Python embedded, zero setup), non passa da Frank per ora (`frank_sync/sync_db.py` è solo un placeholder). - **Relazione con `osservatorio.parquet` chiarita (Mauro, 2026-07-17)**: `[CONFERMATO]` — **`osservatorio.parquet` è sostanzialmente il database di Osservatorio** (mirror/copia del loro DB dentro MyICR_Suite), mentre **MonitorOsservatorioPy è lo strumento che carica i dati grezzi di "emesso internazionale" nella loro intranet** — cioè alimenta a monte il database di cui `osservatorio.parquet` è poi la copia lato ICR. Non sono lo stesso sistema, ma sono nella stessa catena: MonitorOsservatorioPy (carica dati grezzi → intranet Osservatorio) → database Osservatorio → `osservatorio.parquet` (mirror lato ICR). Il passaggio esatto "database Osservatorio → osservatorio.parquet" (quale pipeline/export lo fa, con che frequenza) non è ancora stato indagato.