# Stato Progetto TieMeasureFlow — V3.0.0 > Snapshot al 2026-07-28. Aggiornare ad ogni milestone. > Lo snapshot V2.0.0 è in [`../archive/STATO_PROGETTO_V2.0.0_2026-04-27.md`](../archive/STATO_PROGETTO_V2.0.0_2026-04-27.md). ## Versione corrente **V3.0.0** (in sviluppo, branch `V3.0.0`). Versione precedente: `V2.0.0`. In esercizio su `tieflow.tielogic.xyz` (Docker Compose + Traefik + Let's Encrypt), schema alla migrazione `010_meas_authorisation`. ## Sintesi esecutiva V2.0.0 aveva chiuso stazioni per-tablet, ristrutturazione monorepo con `uv` e la tenuta multi-tablet. **V3.0.0 nasce dal sopralluogo del 28/07 e lavora sui quindici punti** raccolti in [`TieMeasureFlow_modifiche_2026-07-28.md`](../../TieMeasureFlow_modifiche_2026-07-28.md). Il filo che li tiene insieme: fino a V2.0.0 il sistema *registrava* misure ma non *governava* la produzione. Le regole vivevano nell'interfaccia — una modale che si poteva chiudere, un timer che moriva al cambio pagina, un tipo di task dedotto dalla presenza di quote. V3.0.0 le sposta sul server, dove non si aggirano. Tredici punti su quindici sono fatti e in esercizio. Uno è fermo sulle risposte del cliente, uno è fuori offerta. ## I quindici punti | # | Punto | Stato | Dove vive | |---|---|---|---| | 1 | Memoria della produzione in corso | ✅ | `production_runs`, `production_service` | | 2 | Tipo di task esplicito | ✅ | `RecipeTask.task_type`, migrazione 007 | | 3 | Loop di misura e ripetizione | ✅ | `production-clock.js`, `production_service`, migrazione 008 | | 4 | Limite di tentativi prima del capoturno | ⛔ **Fermo su D-6, D-7, D-9** | — | | 5 | Avanzamento solo se in tolleranza | ✅ | `measurement_service.pending_authorisation`, migrazione 010 | | 6 | Fermo linea e Fine produzione con effetto | ✅ | `production_service`, `production_export_service` | | 7 | Gestione delle stazioni | ✅ | `station_service`, `/admin/stations` | | 8 | Tracciabilità obbligatoria | ✅ | `Recipe.requires_lot/serial`, migrazione 009 | | 9 | Blocco dell'inserimento manuale | ✅ | `Recipe.allow_manual_input`, `numpad.js` | | 10 | Interfaccia operatore: sequenza e conferme | ✅ | `/measure/start`, `task-progress`, `task_list`, `task_execute` | | 11 | Formattazione delle descrizioni | ✅ | filtro `rich_text`, `rich-text.js` | | 12 | Funzionamento senza internet | ✅ | `static/vendor/`, CSP in `app.py` | | 13 | Generazione task dalla scheda tecnica con l'AI | — **Fuori offerta** | — | | 14 | Stabilità del layout | ✅ | `.tmf-page` in `themes.css`, [`LAYOUT.md`](LAYOUT.md) | | 15 | Statistica separata dalla registrazione | ✅ | `@role_required("Metrologist")`, test dedicato | ## Cosa è entrato in V3.0.0 ### Produzione come stato, non come pulsante (punti 1, 6) - Tabelle `production_runs` e `production_events`; una produzione si apre su una stazione, accumula eventi (`start`, `cycle_completed`, `task_measured`, `remeasure`, `line_stop`, `resume`, `close`) e si chiude. - Fermo linea, ripresa e fine produzione richiedono le credenziali del capoturno e **cambiano lo stato**: prima erano modali che si chiudevano. - La fine produzione emette il file di statistica dell'intera produzione (`production_export_service`), CSV con i delimitatori configurati, marcando le misure come esportate. Il punto d'innesto verso GAIA è lì accanto, in attesa di D-1 e D-2. ### Il ciclo di misura (punto 3) - L'intervallo della ricetta è calcolato dal server (`seconds_to_next_measurement`, `overdue`, `server_time`): sopravvive al cambio task, che è un ricaricamento di pagina, e non si azzera cambiando schermata. - Il conteggio **prosegue oltre lo zero**: il ritardo si vede, in rosso. - A scadenza l'operatore viene riportato al primo task di misura della ricetta, con cinque secondi di preavviso; chi sta già misurando viene lasciato finire. - Cicalino via WebAudio, nessun file audio da caricare. - `remeasure` distingue una seconda lettura dello stesso pezzo da un ciclo nuovo: girare il pezzo e rimisurare non deve far ripartire l'intervallo. ### Le regole della ricetta (punti 8, 9) - `requires_lot`, `requires_serial`, `allow_manual_input` per ricetta, verificati in `measurement_service`: **ogni porta d'ingresso passa di lì**, barcode compreso. - Con `allow_manual_input` a falso il tastierino non viene disegnato — non nascosto: il markup non esiste — e il server rifiuta comunque un valore digitato a mano. - Il rilevamento del calibro USB è stato reso più tollerante (Enter veloce), perché con la regola stretta una lettura corta come `9.5` veniva scambiata per digitazione. ### Il fuori tolleranza (punto 5) - Una quota fuori tolleranza e non autorizzata **blocca** il salvataggio della quota successiva e la chiusura del ciclo (409). - Conta l'**ultima** lettura di ogni quota: rimisurare la stessa quota resta possibile — il calibro scivola, il pezzo si riposiziona — e una lettura dentro i limiti scioglie il blocco senza chiamare nessuno. - L'autorizzazione del capoturno resta scritta sulla misura (`supervisor_id`, `authorised_at`) e finisce in due colonne del CSV di produzione: il file mostra il guasto **e** la decisione. - Quanti tentativi siano ammessi è il punto 4, non questo. ### L'interfaccia dell'operatore (punti 10, 11, 14, 15) - La ricetta si apre sul primo task (`/measure/start/`), non su un elenco. La lista scende a secondo livello e mostra quali task sono rimasti incompiuti (`GET /api/measurements/task-progress`, contato per quota e non per tentativo). - «Fine ciclo misura» è visibile da subito e spento finché mancano quote, con scritto quante ne mancano. - Descrizioni con `**grassetto**` e a capo: marcatura, non HTML, così la sanificazione è per costruzione. - Una cornice sola per tutte le viste, allineata alla navbar — vedi [`LAYOUT.md`](LAYOUT.md). - Nessun percorso dell'operatore porta alla statistica, ed è un test. ### Fuori dalla rete (punto 12) - Alpine, Plotly, PDF.js (+ worker), Fabric e i font sono nell'installazione, con versione nel nome e impronta SHA-256 verificata da un test. - Content-Security-Policy a sola origine locale servita **dal client Flask**: prima esisteva solo sul backend, cioè sulle risposte API e non sulle pagine. - Tailwind fissato a `3.4.19` nel `Dockerfile.frontend`. ## Migrazioni | # | Contenuto | |---|---| | 001–004 | image_path, stazioni, intervallo di misura + auto-logout, `input_duration_ms` | | 005–006 | `production_runs`, misure legate alla produzione | | 007 | `task_type` dichiarato | | 008 | tipi di evento del ciclo + `task_id` sull'evento | | 009 | `requires_lot`, `requires_serial`, `allow_manual_input` | | 010 | `supervisor_id`, `authorised_at` sulla misura | Le 008 e 010 usano il *batch mode* per compatibilità SQLite (i test) e sono state verificate anche in resa MySQL (`alembic upgrade X:Y --sql`) prima del deploy. La 009 imposta `allow_manual_input = 1` sulle ricette esistenti: conserva il comportamento in essere invece di irrigidire di colpo ricette già in uso. È una scelta, e va confermata dal cliente ricetta per ricetta. ## Test status | | Test | Fail | |---|---|---| | Backend (`src/backend/tests/`) | 212 | 0 | | Frontend (`src/frontend/flask_app/tests/`) | 148 | 0 | | **Totale** | **360** | **0** | I quattro fallimenti pre-esistenti tracciati nello snapshot V2.0.0 non ci sono più. Tre file di test non renderizzano niente e leggono i sorgenti, perché guardano proprietà che sopravvivono solo se qualcuno le controlla: | File | Cosa impedisce | |---|---| | `test_offline.py` | Una libreria caricata dalla rete, un worker PDF.js lasciato sul CDN, una libreria sostituita senza aggiornare l'impronta | | `test_layout_shell.py` | Una vista che torna a dichiararsi la propria larghezza | | `test_template_js_syntax.py` | Una traduzione con l'apostrofo dentro una stringa JS a virgolette singole, che spegne Alpine su tutta la pagina | ## Cosa non è stato provato Va detto perché non si confonda «i test passano» con «funziona in reparto». - **Il collaudo sul campo non è mai stato percorso.** Le ricette `COLLAUDO-A` e `COLLAUDO-B` sono state seminate sul sistema in esercizio (`scripts/seed_collaudo.py`) insieme a un utente `capoturno`, ma nessuno ha ancora guidato il flusso su un tablet vero. - **Il punto 14 non è stato riprodotto su un dispositivo**: è dimostrato che il layout *poteva* muoversi per quattro motivi e che ora non può più, non che l'operatore vedesse esattamente quelli. - **Il punto 12 non è stato provato a rete staccata.** È dimostrato che nessuna risorsa esterna viene richiesta. Il giro con la rete staccata va fatto, ed è ora il momento giusto perché la CSP è appena entrata in vigore. - **Il cicalino** è il suono del browser. Se serva una colonnina luminosa è D-5. ## Stack - **Backend:** FastAPI + SQLAlchemy 2.0 async + MySQL 8 + Alembic + Pydantic v2 + WeasyPrint + Plotly/Kaleido. 12 router. - **Frontend:** Flask + Jinja2 + Alpine.js 3.15.12 + TailwindCSS 3.4.19 + Fabric.js 5.3.1 + PDF.js 3.11.174 + Plotly.js 2.32.0 + Flask-Babel — **tutte copie locali**, nessun CDN. - **Deploy:** Docker Compose. Dev = Nginx; Prod = Traefik + Let's Encrypt. - **Tooling:** uv, pytest + pytest-asyncio + httpx + aiosqlite. ## Decisioni architetturali rilevanti | Decisione | Stato | Note | |---|---|---| | Le regole di misura stanno sul server, non sullo schermo | **Confermata (V3.0.0)** | Tracciabilità, inserimento manuale, fuori tolleranza: la schermata può nasconderle, il server le rifiuta. | | Una app di stazione per PC, dati sul server | **Decisa il 28/07** (D-3) | Validazione con l'IT di Tràfilo aperta come D-4. | | Descrizioni in marcatura, non in HTML | **Confermata** | Niente HTML accettato in ingresso: la sanificazione è per costruzione, non per filtro. | | Frontend Flask invece di React (deroga vs spec §8) | **Confermata** | Tablet UX server-side, calibri USB, editor Fabric.js, i18n Babel collaudato. | | NATS messaging (spec §7) | **Skippato** | Monorepo single-host, nessun microservizio. | | Envelope risposta `{success,data,error}` (spec §6) | **Rimandato** | Costo alto, rotture client. Eventuale API v2. | | Header `X-API-Key` vs spec `X-Api-Key` | **Mantenuto attuale** | Rinominare è breaking per i deploy esistenti. | ## Branch git - **Corrente:** `V3.0.0` - **Precedenti:** `V2.0.0`, `V1.0.0` … `V1.0.7` (release storiche)