# TODO — Workflow di ricerca, qualità e piattaforma estendibile Backlog operativo emerso dalla review del flusso `archivio → annotazione → dataset → fine-tuning → valutazione`. Non sostituisce la roadmap in `AGENTS.md`: ne dettaglia le priorità e aggiunge la direzione multi-dominio e multi-modello. Historic Shipping Index e MonkeyOCRv2 restano il primo profilo di dominio e il primo adapter modello, non limiti architetturali. ## Controllo review tecnica — aggiornato 2026-08-31 Checklist di controllo derivata dalla review allegata. Viene aggiornata progressivamente: `[x]` significa chiuso e verificato, `[ ]` significa ancora aperto oppure solo parzialmente coperto. ### P0 — sicurezza e integrità collaborativa - [x] Autosave concorrente: revisione pagina, `expected_revision`, risposta `409` con stato remoto e riconciliazione esplicita nell’annotatore. - [x] Condivisione progetto: membri, ruoli editor/viewer, rimozione, trasferimento owner, elenco candidati e audit delle operazioni principali. - [x] Segreti cloud/API: rimosso il salvataggio delle chiavi nel browser; il vault server-side cifrato è implementato, `cryptography` è una dipendenza obbligatoria verificata dagli installer e dalla suite del virtualenv Python 3.12; la CI installa lo stesso `requirements.txt`. - [x] SSRF di inferenza/playground: endpoint riservati, URL validati, destinazioni private bloccate e redirect HTTP non seguiti. ### P1 — operatività, compute e persistenza - [x] Profili compute atomici: adapter, endpoint, modello servito, runtime, hardware, health check e attivazione senza perdere il profilo precedente. - [x] Job locali persistiti in SQLite, con PID/process group, log, stop e riconciliazione dei processi dopo il riavvio del backend. - [x] Backup SQLite online, integrity check, retention, download e restore amministrativo con conferma. - [x] Audit append-only per autenticazione amministrativa e operazioni sensibili su progetti, annotazioni, utenti, compute e training. - [x] Cookie configurabile `Secure`, rate limiting persistente per auth, CORS ristretto, security headers/CSP e verifica SSH tramite known_hosts. - [x] Vincolo FK reale su `projects.owner_id` e migrazione completa delle invarianti di ownership: migrazione SQLite v12 con `ON DELETE RESTRICT`, più regressione su foreign key, cancellazione e disattivazione dell’owner. - [ ] Persistenza/recovery uniforme per tutti gli orchestratori cloud e training remoto; locale, Modal e gli identificativi Vast/RunPod sono persistiti e riconciliati, ma manca ancora la verifica end-to-end uniforme dei lifecycle remoti. - [ ] Executor remoto (locale/SSH/Vast/RunPod), manifest, checksum, upload, checkpoint, resume, stop, cleanup, costi e riferimenti credenziali. Contratti e percorsi principali sono implementati e testati; resta da eseguire il ciclo completo contro provider reali. - [ ] Recipe Vast/RunPod riproducibili: versioni pinnate, branch/commit dichiarati, hardware verificato, manifest e test del contratto API. Il contratto locale è coperto; manca la prova operativa completa sui provider. ### P1/P2 — qualità di prodotto e verifica - [x] UI di collaborazione: accesso al progetto con aggiunta/rimozione, modifica inline del ruolo, trasferimento owner e attività recente leggibile. - [ ] UI completa per impostazioni, accessi/attività, capability dichiarate, studio responsive e componenti/modal coerenti. - [x] Test auth/annotazione non bloccanti e matrice CI sulle versioni Python supportate; la suite backend completa passa nel virtualenv Python 3.12 allineato (`240 passed`) e la CI copre Python 3.11–3.13. - [ ] Pilot reale da 30–50 pagine con LoRA, gold set protetto, analisi errori e successiva scala a 150–300 pagine. ### Evidenze dell’ultima tranche - La suite backend completa è ora eseguibile anche nel virtualenv Python 3.14 tramite il client ASGI confinato ai test: `225 passed, 3 skipped`. Gli skip sono espliciti per `cryptography` assente nel virtualenv e scansioni reali opzionali; nel virtualenv Python 3.12 con `cryptography` installata passa `240 passed`; frontend verificato con `79 passed` e build completata. - I confini verticali piegati (`row_columns`) e i flag (`row_columns_proven`) fanno ora parte del contratto `TableGrid`, vengono salvati nel JSON della tabella e sono riutilizzati dall’overlay dopo il caricamento; l’API valida cardinalità, monotonia e normalizzazione; regressione struttura rilevatore: `4 passed`. - `ProjectDetailPage` carica `/activity`, aggiorna la cronologia dopo le modifiche di accesso e mantiene i nomi lunghi leggibili senza mostrare payload tecnici; `npm run build` passa e il detector UI non segnala rilievi. - `SettingsPage` è ora realmente universale: account, ruolo, email e lingua sono disponibili anche a editor/viewer; le sezioni amministrative restano condizionate dal ruolo. - `ModelsModal` usa la primitiva `Modal` condivisa (focus trap e ripristino del focus), senza shadow/radii divergenti o glyph Unicode; build e detector UI restano puliti. - `AnnotationPage` espone di nuovo poligono, panoramica, flow preview, ordine di lettura e checklist delle convenzioni; i controlli esistenti ricevono i tool e le azioni dello store reale. Build TypeScript verificata. - Lo studio ha ora una modalità persistita per utente: `Tutto`, `Solo canvas` e `Solo trascrizione`; la prima conserva gli splitter, le altre collassano i pannelli non necessari per viewport stretti. Build, test frontend e detector UI passano; resta da validare il comportamento su dispositivi reali. - Test frontend: `13` file e `79` test passati (`npm run test -- --run`). - Il contratto training remoto è ora verificato anche al confine API: `TrainConfig` conserva repository e interprete remoti; recipe Vast/RunPod usa dataset, cache e checkpoint relativi alla directory sincronizzata. Test mirati trainer/executor: `24 passed`; build frontend e detector UI passano. - `.github/workflows/ci.yml` verifica il backend su Python 3.11, 3.12 e 3.13; Python 3.14 resta esplicitamente fuori finché lo stack TestClient non è compatibile e verificato. - La promessa README sul merge LoRA è stata resa onesta: il prodotto conserva adapter, recipe e manifest verificati; resta aperta l’integrazione del merge ufficiale verso un checkpoint servibile, con test end-to-end. - La UI del Training Center espone ora executor locale/SSH/Vast/RunPod e i parametri SSH senza salvare segreti; lo schema API conserva anche repository e Python remoti. Lo script usa percorsi relativi alla recipe sincronizzata, quindi dataset, cache e checkpoint non puntano più al filesystem locale. Il trainer persiste e recupera il `remote_job_id` anche per Vast/RunPod. Il lifecycle remoto completo resta aperto finché non sono verificati upload, checkpoint/resume, cleanup e costi end-to-end. - Il resume è ora esplicito tramite `resume_run_id`: valida che la run appartenga al progetto, copia solo i checkpoint locali verificati nella nuova recipe e li sincronizza anche verso l’executor remoto. - Il provisioning Vast richiede ora un `monkeyocr_ref` esplicito (commit SHA o tag verificato); il ref viene passato all’onstart e la chiave server non è più interpolata nello script, ma solo nella variabile d’ambiente dell’istanza. Contract test: ref obbligatorio, quoting e assenza del plaintext nell’onstart; `bash -n` dello script passa. - La UI blocca ora il noleggio automatico Vast finché il ref pin-nato non è compilato, evitando un’azione che il backend rifiuterebbe. - Verificato anche l’ordine del parsing nello script: `--ref` viene elaborato prima del controllo obbligatorio. Il contract test Vast passa (`1 passed`). - La recipe cloud controlla ora la compute capability minima per BF16 e salva nel `cloud-manifest.json` recipe ID, Python, Transformers, dtype, limiti vLLM, GPU memory utilization e capability hardware. - Anche lo script `vast_onstart.sh` e il comando manuale RunPod richiedono esplicitamente il ref pin-nato; non esiste più un esempio operativo che avvii il setup con il branch implicito. - I task Modal persistono ora PID, process group, template, log, stato ed owner, exit code in `jobs`; dopo un riavvio `status()` recupera log e task ancora vivi invece di perdere lo stato singleton; lo stop dell’app interrompe anche un deploy recuperato. Regressioni Modal: `2 passed`. - Anche il serving locale dei modelli persiste ora `serve` in `jobs` con PID, process group, log, owner e comando; `status`/`stop` recuperano un server ancora vivo dopo il riavvio del backend. Regressione lifecycle + recovery: `2 passed` (suite dedicata verificata fino al limite noto del TestClient). - Il lifespan esegue anche il reconcile dei job `serve`: PID non più presenti vengono marcati `failed` invece di restare bloccati in `running`; la suite non HTTP del serving ora copre lifecycle, recovery e PID morto (`10 passed`). - Verificato il grafo frontend: Handsontable era dichiarato ma non usato e viene rimosso da `package.json` e `package-lock.json`; React Query resta perché è usato da `main.tsx`. Build e test frontend: `66 passed`. - La documentazione ora dichiara il supporto backend realmente coperto dalla CI (Python 3.11–3.13); Python 3.14 resta esplicitamente pendente per il blocco noto nell’adapter TestClient/AnyIO. - Il training remoto espone ora un cleanup esplicito e autenticato: rifiuta run attive, valida il `run_id` dentro la directory del progetto, rimuove solo la directory remota già composta dal provider e lascia intatti i checkpoint locali; l’azione UI richiede una seconda pressione. Contract test SSH e training: `25 passed`. - La documentazione chiarisce i confini multipiattaforma: dashboard, OCR CPU, annotazione ed export funzionano su Linux/macOS/Windows; serving e training CUDA locale richiedono Linux o WSL2, mentre SSH/Vast/RunPod sono disponibili da tutti i client. Gli installer frontend usano ora `npm ci` e il lockfile è verificato con `npm ci --dry-run --offline`. - `/api/system/info` dichiara ora le capability della macchina (`dashboard`, `cpu_ocr`, `local_cuda`, `remote_gpu`) e l’Ambiente le mostra in UI; su Windows espone esplicitamente il percorso CUDA via WSL2. - Il refresh delle istanze Vast/RunPod registra ora anche risorse già create fuori da Tabularium, aggiorna stato/tariffa e calcola la stima persistita; regressione refresh Vast: `2 passed` insieme al contract test di provisioning. - La disattivazione di un utente proprietario ora restituisce `409` finché i suoi progetti non vengono trasferiti; aggiunta regressione in `tests/test_auth.py`. - Il tunnel SSH cloud persiste ora PID, process group e parametri in `jobs`; dopo il riavvio il backend riconcilia il processo e lo stop verifica che il PID sia ancora un processo `ssh`. Gli orchestratori cloud restano comunque aperti per lifecycle remoto, costi e teardown. - Vast/RunPod registrano ora le risorse create in `jobs` con provider, owner, stato, tariffa opzionale e stima cumulativa del costo; start/stop/delete aggiornano il ciclo di vita. La UI mostra la stima corrente e offre `Distruggi` con conferma esplicita per entrambe le piattaforme; test cloud mirati: `4 passed`. - `tests/test_compute_profiles.py`, `tests/test_db_migrations.py`, `tests/test_backup.py` e `tests/test_training.py` sono i test di controllo; insieme a `tests/test_rate_limit.py`, ultimo esito: `26 passed` dopo la migrazione v12 e la correzione della persistenza del rate limit. - Il lifecycle del backend esegue ora `PRAGMA integrity_check` dopo `init_db()` e interrompe l’avvio con un errore diagnostico se il database non è integro; il controllo resta esposto anche nella pagina backup. Regressione lifecycle: `test_startup_stops_on_corrupt_database`. - Corretta una regressione del test health: non assume più la vecchia baseline DB `6`, ma verifica la versione corrente dichiarata da `SCHEMA_VERSION`. - Aggiunti test senza runtime Paddle per il parser ufficiale: varianti JSON annidate, fallback markdown a pagina intera e output vuoto. - La suite HTTP ora usa una compatibilità ASGI solo su Python 3.14: mantiene cookie e stream SSE e aggira il deadlock del portal `TestClient`; il runtime dell’app non viene modificato. - I setup backend shell/PowerShell accettano esplicitamente Python 3.11–3.13 e rifiutano versioni diverse con un messaggio diagnostico; questo evita di creare ambienti apparentemente supportati ma incompatibili con la matrice CI. - Gli installer verificano anche l’import di `cryptography` dopo l’installazione: nessun ambiente viene dichiarato pronto mentre il vault cifrato è assente. - L’helper SSH CLI non usa più `eval` sul comando incollato e applica `StrictHostKeyChecking=yes` con un `known_hosts` dedicato, coerente con il tunnel gestito dal backend; le recipe cloud documentate richiedono ora esplicitamente un ref verificato e passano i secret tramite environment. - L’hook Vast richiede e scarica il setup Tabularium al suo `TABULARIUM_REF`, separato dal `MONKEYOCR_REF` del runner, prima di avviarlo; un onstart copiato da solo non può più puntare a uno script locale inesistente. - Il protocollo pilota non consente più sovrapposizioni gold/pilot: proteggere una pagina la rimuove dal campione salvato e l’API restituisce il conteggio delle esclusioni; regressione `test_gold_pages_cannot_enter_saved_pilot`. - Il campionatore pilot applica ora anche lato backend il contratto 30–50 pagine (`target` fuori intervallo risponde `422`), non solo nello script CLI; regressione `test_pilot_sample_requires_review_sized_target`. - Lo smoke E2E attende ora fino a 30 secondi la readiness del backend quando deve prima compilare il frontend, evitando falsi fallimenti su macchine lente e su Windows/WSL. - Le preferenze Modal usano solo chiavi `tabularium.*`. - Il tipo frontend `InferenceConfig` non dichiara più `api_key`: l’API espone solo `has_api_key`, impedendo che un secret possa rientrare accidentalmente nel contratto client durante refactoring futuri. - Il registro modelli espone ora una maturità esplicita (`supportato`, `sperimentale`, `catalogo`, `non disponibile`) derivata dal contratto dell’adapter: scaricabile non significa più implicitamente pronto per inferenza o training. - La configurazione inferenza non lascia più un riferimento `vault:inference` nel profilo legacy quando la credenziale viene rimossa; il test cloud usa una chiave Fernet effimera e non contiene segreti persistenti. - La recipe Vast generata dal backend richiede ora ref verificati sia per MonkeyOCRv2 sia per Tabularium, scarica lo script dal repository corretto alla revisione dichiarata e non usa più `main` implicito; regressione del provisioning: `10 passed`. - La procedura RunPod nella guida e nel pannello usa anch’essa due ref espliciti (`MONKEYOCR_REF` e `TABULARIUM_REF`) e la credenziale server via env, senza URL su `main` né `--api-key` esposto nel comando copiabile. - Il frontend gestisce anche il caso di una pagina aperta durante una nuova build: se un chunk lazy hashato non è più presente, tenta un solo reload automatico per riallineare `index.html` e gli asset. Il marker viene cancellato solo dopo il mount riuscito dentro Suspense, evitando loop se il server continua a servire asset incoerenti; test frontend `79 passed` e build ripetuta dopo la modifica. - `scripts/run.sh` e `scripts/run.ps1` ricostruiscono ora automaticamente il frontend quando i sorgenti sono più recenti di `dist/index.html`; è inoltre disponibile `TABULARIUM_BUILD_FRONTEND=1` per forzare la build e prevenire mismatch tra manifest HTML e chunk hashati al riavvio. - Gli installer backend shell e PowerShell validano ora Python 3.11–3.13 prima di creare o usare il virtualenv, evitando setup apparentemente riusciti su versioni non supportate; README aggiornato. - Il vault è stato provato con il Python di sistema (`vault probe ok`): il plaintext non compare nel ciphertext; la verifica nel virtualenv resta sospesa finché non è disponibile `cryptography`. - La suite vault passa con il Python di sistema: `2 passed`; il percorso API registra anche sostituzione/rimozione del credential senza salvarne il valore; sono disponibili `POST/DELETE /api/system/secrets` riservati all’admin e gli endpoint Vast/RunPod/Modal accettano riferimenti vault. - `scripts/cloud/setup_cloud_vllm.sh` ora richiede un ref MonkeyOCRv2 esplicito, pinna le dipendenze principali, fallisce sui prerequisiti GPU/disco, rimuove il flag incompatibile e genera `cloud-manifest.json`; `bash -n` passa. - L’onstart Vast quota modello e API key senza interpolazione shell non sicura; la ricerca ora usa `PUT /api/v0/search/asks/` e il provisioning RunPod usa `POST /pods` con schema persistente documentato; contract test mockati: `8/8`. Resta da validare la forma Vast contro l’API reale e completare lifecycle, teardown e costi. - Il training locale usa ora `TrainingRecipe` + `LocalProcessExecutor` e scrive `recipe.json` con hash SHA-256 di script e dataset; test di controllo: `33 passed, 2 skipped`. È disponibile anche `SshExecutor` con sync `rsync --checksum` e host-key verification, con PID remoto persistito, recovery e stop via process group; la riconciliazione non marca più come falliti i job SSH al riavvio. Checkpoint e artefatti hanno ora manifest SHA-256, download SSH e verifica prima della chiusura della run. Restano aperti validazione API reale e lifecycle costi per Vast/RunPod; i relativi executor sono già provider espliciti sopra SSH per istanze/pod già pronti. - La checklist non sostituisce il backlog sottostante: le voci già presenti più in basso conservano il dettaglio storico e tecnico delle implementazioni. ## P0 — Integrità e riproducibilità - [x] Rendere autorevoli i confini di qualità e riproducibilità nel backend. - `approved`/`exported` non sono più impostabili con la PATCH generica; l'approvazione passa da readiness e le pseudo-label non confermate bloccano. - Il preflight legge le famiglie dallo snapshot immutabile, richiede una validation reale e blocca una GPU già saturata da vLLM. - Ogni run usa una copia locale train/val dello snapshot dichiarato; il log ha un solo writer. - [x] Correggere le coordinate della valutazione layout. - GT e predizioni sono confrontati entrambi in scala 0–1000; i pixel sorgente restano separati per generare i crop di testo/tabella. - [x] Conservare la geometria corretta delle tabelle. - `vlines`/`hlines` sono persistite e validate come monotone, invece di essere accettate dall'API e scartate prima del salvataggio. - [x] Impedire all'autosave bulk di cancellare le griglie tabellari. - Il client rimanda gli ID server e il backend aggiorna i blocchi esistenti, inserisce solo quelli nuovi ed elimina solo gli ID davvero rimossi. - Test di regressione: una griglia resta leggibile dopo un PUT bulk successivo. - [x] Invalidare i crop se cambiano sorgente, bbox/poligono o trasformazioni. - Usare hash/versione annotazione nel nome; non riusare crop obsoleti. - Verifica: spostare un blocco e controllare che il crop JSONL cambi. - [x] Correggere il quoting del training: sostituire `shutil.quote` con `shlex.quote` in `services/trainer.py`. - Verifica: script valido con `TABULARIUM_TRAIN_PYTHON` contenente spazi. - [x] Rendere l'export atomico, versionato e immutabile. - Scrivere in directory temporanea, validare, poi pubblicare snapshot `dataset/v0001`, `v0002`, ecc. con manifest, hash e seed. - Verifica: un export fallito non modifica lo snapshot precedente. - [x] Riparare la verifica HTTP backend e usare sempre `.venv/bin/python -m pytest` negli script. - `httpx2` è fissato nei requirements-dev; la suite completa passa anche nel virtualenv Python 3.14 usando una compatibilità ASGI confinata a `tests/conftest.py`, mentre il runtime di produzione resta invariato. - Gli script `test_backend.sh` e `test_backend.ps1` invocano il Python del virtualenv; la matrice CI continua a verificare Python 3.11–3.13. ## P1 — Protocollo di ricerca e dataset affidabile - [x] Creare uno **studio di ricerca** sopra il progetto tecnico. - Wizard: corpus, intervallo temporale, obiettivo, convenzioni, modello e profilo di dominio. - Versionare e mostrare il protocollo dello studio. - L'unità di generalizzazione vive solo come **unità di split** nella pagina Dataset (`split_strategy`): era duplicata nel protocollo, dove però non la leggeva nessuno. - [x] Produrre una mappa del corpus dopo l'import. - Statistiche per anno/numero/tipo pagina/scanner, stima tabelle, duplicati/quasi-duplicati e campione iniziale bilanciato. - [x] Supportare split per gruppi: data/numero, annata, scanner, collezione e tipo pagina; separare `train`, `validation` e `gold test`. - [x] Implementare stati annotazione: `bozza → verificata → approvata`. - Mostrare checklist per layout, reading order, testo, tabelle e warning. - Esportare solo pagine approvate. - [x] Creare QA e gold set. - Il gold set protetto è assegnabile dal registro, escluso dal tuning e subordinato a una seconda revisione; `/qa-report` aggrega gli errori ricorrenti. - Seconda revisione su campione; 8–12 pagine invisibili al tuning; registrazione degli errori ricorrenti. - [x] Preflight export bloccante. - Bbox validi anche dopo normalizzazione; griglie/merge/OTSL round-trip; prompt, trascrizioni, crop e classi verificati. - Distinguere errori bloccanti da warning. ## P1 — Annotazione guidata e ad alta precisione - [x] Home operativa: **Stato dello studio / prossimo miglior compito**. - Avanzamento per stadio, copertura label, warning, dataset e ultime run. - [x] Coda di annotazione guidata. - Continua lavoro, completa pagine quasi finite, rivedi tabelle complesse, copri classi deboli, lavora su campioni bilanciati o incerti. - [x] Rendere espliciti i passaggi pagina: struttura, contenuto, tabella, revisione; consentire il salto dei passaggi non applicabili. - [~] Trasformare l'editor tabellare in vista di lavoro dedicata. - **Fatto**: immagine e griglia sovrapposte e sincronizzate, zoom locale, linee trascinabili con la tastiera come alternativa, supporto per confine leggibile dalla forma (continuo = attestato, tratteggiato = da guardare), e accettazione confine per confine — «Aggiungi colonna/riga» e «Rifiuta confine», con `dropBoundary`/`insertBoundary` che rifiutano i casi ambigui invece di indovinare (`lib/grid.ts`, `studio/components/TableGridOverlay.tsx`). La voce era segnata come completata ma il codice aveva ancora cursori numerici accanto all'immagine: su un ritaglio da 2600 px un cursore largo 80 px sposta la linea di ~32 px per pixel, quindi la correzione fine non era possibile. - **Da fare**: colonne fantasma raggiungibili dall'overlay, `header_rows` esposto nel modale, note e totali come ruoli di riga dichiarabili. - OTSL/HTML restano strumenti avanzati. - [x] Introdurre tile pyramid per scansioni grandi. - Stato ad alta frequenza fuori dal ciclo di rendering React. - Verifica: zoom/pan fluidi con immagini grandi e molte regioni. - [x] Aggiungere browser cartella locale multipiattaforma, mantenendo il percorso manuale come fallback. Il picker `webkitdirectory` carica in modo sicuro una sessione di import senza esporre o modificare il percorso locale. ## P2 — Esperimenti, valutazione e active learning - [x] Training Center con preset: prova rapida, LoRA standard, comparativo e avanzato; preflight GPU/CUDA/disco/dataset/checkpoint. - [x] Collegare ogni run a uno snapshot dataset preciso; salvare manifest, config, script, versione app/modello e metriche incrementali JSONL. - [x] Usare SSE nel frontend per log, metriche e GPU al posto del polling come percorso normale. - [x] Completare M6: confronto base-vs-fine-tuned, overlay, IoU/mAP, ordine, TEDS, CER/WER e fallimenti peggiori. - [x] Trasformare i fallimenti in azioni: “aggiungi esempi simili”, “rivedi queste tabelle”, “copri questa label”. - [x] Rendere il rilevatore di griglia affidabile su tutto il campione. - Righe dalle linee di base dei glifi (componenti connesse) invece che dall'autocorrelazione del profilo, soglia di Otsu, inclinazione stimata e compensata senza ruotare l'immagine, avviso `skewed` esposto e tradotto. - Da 1 pagina su 4 a 4 su 4 sul campione reale; le tre rotture sono fissate da test di regressione che falliscono sulla versione precedente. - Resta noto e documentato (AGENTS.md §2.3.4): su una pagina a più colonne di giornale il rilevatore non sa di essere fuori dominio, e le due ipotesi per accorgersene sono state misurate e respinte. - [x] Prelabel assistito e active learning. - Bozze da modello/OCR restano non confermate, conservano provenienza e confidenza, e la coda privilegia le pagine a bassa confidenza o con errori eval. - [x] Esporre e tracciare il percorso ufficiale MonkeyOCRv2 END2END. - Una chiamata bbox+label+content a 2 MP; OTSL valido viene importato senza seconda inferenza, altrimenti il report dichiara il fallback sul crop. - Il default resta `two_stage` finché l'A/B sul gold set non misura struttura, CER/TEDS, completezza e latenza su più pagine. - [x] Rendere le bande tabella un'augmentazione sicura e sperimentale. - Il crop completo resta incluso; le bande sono create solo da `hlines` verificate, non attraversano rowspan e sono conteggiate separatamente. - Nessuna prima riga viene assunta automaticamente come intestazione. - [x] Assegnare i valori alle celle invece di tagliare a x fisso. - Le colonne derivano di 25–77 px scendendo (0,6–2,0 passi) mentre una cifra è larga ~20 px: nessuna retta descrive la tabella. `snap_boundaries()` porta ogni confine nel varco di bianco della sua riga; dove un varco esiste (86–96% dei tagli) nessun valore viene spezzato, dove non esiste il taglio è marcato non provato e la cella entra fra le `uncertain`. - Da 5,7% a 0,5% di valori spezzati sul campione reale. - L'overlay disegna la spezzata reale sotto la retta modificabile. - I confini piegati e i flag `row_columns_proven` sono ora persistiti nel grid salvato; le vecchie griglie restano compatibili perché i campi sono opzionali. Regressione: forma, cardinalità e normalizzazione verificate. - [x] Far sapere al preflight se la configurazione entra nella GPU. - Il controllo guardava la VRAM libera in assoluto e passava sempre su una scheda scarica; poi `swift sft` andava in OOM dopo aver scaricato i pesi. - `services/vram.py` stima i termini reali: su 8 GB il preset ufficiale (batch 4, 16384) chiede 26 GB, e il termine dominante sono i **logit** (vocabolario da 151936 su hidden 1024), non i pesi. - Tutti e tre i preset spediti non entravano in 8 GB: aggiunto `gpu8` (batch 1, 8192, grad_accum 4 → ~5,4 GB, stesso batch effettivo). - [ ] Eseguire un pilot: 30–50 pagine rappresentative, gold set protetto, prima LoRA, analisi errori, poi scala a 150–300 pagine. - Il campionatore deterministico 30–50 pagine è disponibile e salvabile nel protocollo; Dataset Builder ora offre `pilot_only` per generare uno snapshot isolato dal campione salvato (gold escluso). Restano da eseguire con il corpus reale la prima LoRA, l’analisi degli errori e la scala a 150–300 pagine: `scripts/prepare_pilot.py` automatizza la preparazione e il preflight, ma dati annotati e una GPU dell’utente restano necessari. ## P2 — Generalizzazione oltre Historic Shipping Index e MonkeyOCRv2 - [x] Definire un **core annotation schema** indipendente dal modello. - Pagine, regioni, poligoni/bbox, reading order, trascrizioni, tabelle logiche, celle, merge, metadati, provenienza e stato QA. - Conservare sempre coordinate sorgente e dati ricchi; non salvare OTSL, JSONL ms-swift o prompt come verità primaria. - [x] Introdurre i **domain profiles** configurabili. - `shipping-registers-1900s` iniziale: label, colori, convenzioni, campionamento, prompt, regole QA e metriche preferite. - Profili futuri: registri, quotidiani moderni, manoscritti, fatture, moduli, libri, mappe o documenti tecnici. - [x] Introdurre i **model adapters**. - Contratto comune: capabilities, input, export, training config, inference, checkpoint, metriche e vincoli hardware. - Primo adapter: MonkeyOCRv2-Parsing/ms-swift/OTSL. - Non assumere che tutti i modelli usino prompt, coordinate 0–1000, crop, OTSL o LoRA nello stesso modo. - [x] Separare export dal modello. - Ogni snapshot può produrre più export: MonkeyOCRv2 JSONL, COCO/DocLayNet layout, ALTO/PAGE XML, HTML/CSV tabelle, JSON interno e formati futuri. - Disponibili export `internal`, COCO layout, PAGE XML, ALTO XML e HTML tabelle. Verifica: lo stesso ground truth produce più viste senza alterare l'annotazione primaria. - [x] Catalogo capacità modello/dominio. - Dichiarare per ogni adapter: layout, testo, tabelle, formule, poligoni, ordine di lettura, multilingua, inferenza locale/remota e training. - La UI mostra solo strumenti ed export supportati dalla combinazione scelta. - [x] Progettare una pipeline a plugin, non un monolite. - Interfacce interne per scanner, prelabeler, exporter, trainer, evaluator e inference provider. - Configurazioni YAML/JSON validate, versionate e portabili. - [x] Conservare il focus iniziale. - Non implementare adapter generici prima che il workflow Historic Shipping Index sia validato end-to-end; creare ora solo i confini e le interfacce. ## P3 — Test, documentazione e usabilità - [x] Test frontend per coordinate, merge/split, OTSL preview, autosave e percorso e2e importa → annota → snapshot → script training. - Coordinate, merge/split, struttura i18n e shape del dizionario sono coperti; `scripts/e2e_smoke.sh` verifica ora rendering Chromium, deep-link `/dataset` e health del server. `scripts/e2e_workflow.py` copre ora il percorso UI scan → annotazione deterministica → snapshot dataset → preflight training, inclusa apertura dell’editor tabella, modifica DOM con debounce autosave verificato dal backend e preview OTSL. - [x] Aggiornare README con protocollo pilota, requisiti GPU, riproducibilità, profili di dominio e modello di estensione tramite adapter. ## Ordine consigliato 1. P0: crop, quoting, test backend, export atomico. 2. Protocollo, split a gruppi, QA, preflight e snapshot dataset. 3. Coda guidata, editor tabellare su immagine e tile pyramid. 4. Training riproducibile, SSE e pilot controllato. 5. Valutazione che genera nuova annotazione. 6. Prelabel/active learning. 7. Adapter e profili multi-dominio/modello, usando il core già stabilizzato.