Punto 1 del documento modifiche del 28/07, il prerequisito su cui poggiano i
punti 3, 4 e 6.
Lo stato di una produzione viveva dentro una pagina del browser: timer, conteggio
cicli e flag "produzione avviata" erano variabili Alpine di task_execute.html, e
la navigazione fra task e' un ricaricamento completo, quindi cambiando task si
perdeva tutto. Da qui il loop di misura che non reggeva, il fermo linea che non
aveva nulla da fermare e l'assenza di storico.
Nuove tabelle production_runs e production_events (migrazione 005), endpoint REST
senza stato in memoria di processo - con un'app di stazione installata su ogni PC
il database e' l'unico posto condiviso - e il frontend che legge lo stato
all'apertura invece di tenerlo in memoria.
Tre scelte di modello:
- la scadenza e' un timestamp assoluto (next_measurement_at), non un contatore:
il countdown si ricalcola da li' a ogni caricamento, e lasciarne andare la
differenza sotto zero dara' gratis il contatore del ritardo del punto 3. Al
client vanno i secondi gia' calcolati, non il timestamp: un datetime naive
verrebbe letto nel fuso del browser e il conto sarebbe sfasato dell'offset UTC;
- l'intervallo di misura e' copiato sulla produzione, non referenziato: modificare
la ricetta a produzione avviata non deve spostare una scadenza in corso;
- active_station_id rispecchia la stazione finche' la produzione e' aperta e va a
NULL alla chiusura. Con un vincolo unico sopra, "una stazione = una produzione
aperta" e' una garanzia del database e non un controllo soggetto a race; i NULL
non collidono, quindi le produzioni chiuse si accumulano senza disturbo.
Il fermo linea congela il conto alla rovescia e alla ripresa la scadenza viene
traslata della durata del fermo, non ricalcolata: un fermo non regala ne' toglie
tempo all'operatore. L'autorizzazione del capoturno passa da authenticate_user e
non da un login, che rigenererebbe la sua API key buttando giu' la sessione che ha
aperta altrove.
La migrazione e' stata eseguita davvero, non solo scritta, su uno SQLite usa e
getta: upgrade e downgrade girano e le colonne coincidono con i modelli. La prova
ha trovato un difetto - create_unique_constraint dopo create_table e' un ALTER,
che SQLite rifiuta - ora il vincolo e' dichiarato dentro create_table.
Fuori da questo commit, per stare nei confini del punto 1: l'API espone gia'
pause, resume e close, ma i pulsanti fermo linea e fine produzione restano da
collegare (punto 6), e il rientro forzato sulla misura allo scadere e' il punto 3.
Corretti due difetti trovati strada facendo: env.py non importava ne' Station ne'
ProductionRun, quindi l'autogenerate di Alembic era gia' cieco sulle stazioni; e
task_execute.html, lo schermo con piu' JavaScript dell'applicazione, non era
coperto dal test di sintassi. Aggiungerlo ha richiesto di correggere l'helper, che
validava le espressioni Alpine solo come espressione singola e bocciava
@click="a = false; b = true", forma che Alpine accetta: ora prova entrambe le
letture e fallisce solo se cadono tutte e due.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Punto 7 del documento modifiche del 28/07, terza richiesta di Menoncin.
GET /api/stations ora usa StationWithRecipesResponse, che era gia' definita nello
schema senza che nessun endpoint la usasse. Non costa query in piu':
Station.assignments e' gia' selectin-loaded. In lista compaiono le sole ricette
attive, coerente con cio' che l'operatore vede davvero a quella stazione.
DELETE /api/stations/{id}/recipes azzera le assegnazioni di una stazione senza
eliminarla, cosi' si riassegna da capo. E' idempotente: azzerare una stazione gia'
vuota risponde removed=0, non 404. In tabella e' un pulsante per riga, con
conferma perche' l'azione e' distruttiva.
Il cambio stazione al volo (?station=CODE, override in sessione) serve al
collaudo: senza, provare N stazioni richiede N PC. E' dietro il flag
STATION_SWITCH_ENABLED, default 0. Non e' legato al ruolo admin perche'
/measure/select richiede MeasurementTec, e gatearlo su is_admin avrebbe escluso
proprio chi fa il collaudo. In fabbrica l'identita' della stazione viene
dall'installazione locale, e misurare contro le ricette di un'altra stazione
romperebbe in silenzio la tracciabilita': per questo il codice richiesto e'
validato prima di essere memorizzato - un refuso lascia l'operatore sulla
stazione configurata con un messaggio, invece di incastrarlo su una stazione
inesistente - e quando l'override e' attivo la pagina lo dichiara, con il codice
configurato accanto e un link per ripristinare.
Le traduzioni: pybabel update aveva indovinato dieci voci da stringhe simili
marcandole fuzzy, in entrambi i cataloghi. "Azzera Stazione" era diventato "Crea
Stazione", l'opposto, su un dialogo distruttivo. Non si vedeva perche' la
compilazione salta le fuzzy e ricade sul sorgente italiano, ma sarebbe emerso al
primo build con --use-fuzzy. Tradotte per esteso le 17 stringhe nuove in IT ed EN
e tolti i flag.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Punto 12 del documento modifiche del 28/07. L'installazione a Trafilo e'
on-premise su rete di produzione isolata: finche' Alpine.js, Plotly, PDF.js,
Fabric.js e i Google Fonts venivano scaricati da CDN a ogni apertura di pagina,
senza internet l'interfaccia non partiva affatto (Alpine governa tutta la UI).
Le librerie passano in static/vendor/ con la versione nel nome del file, e la
CSP del backend si chiude a solo-origine. Il worker di PDF.js e' ripuntato in
tutti e quattro i template che lo usano: ripuntando solo lo script principale la
libreria si carica in locale ma il worker continua a cercare internet, e sembra
funzionare finche' non si apre un disegno.
Alpine e' pinnato a 3.15.12, cioe' la versione a cui "3.x.x" risolveva oggi: il
congelamento non cambia il comportamento di quanto e' gia' in esercizio, e toglie
il fatto che l'applicazione cambiasse da sola a ogni rilascio degli autori -
rilevante per le evidenze ISO 9001 / IATF 16949.
Inter e JetBrains Mono sono font variabili: un solo woff2 per subset copre tutti
i pesi, dove Google serviva lo stesso file sotto otto URL. Tenuti i soli subset
latin e latin-ext, che coprono per intero italiano e inglese.
Provenienza, versioni e impronte SHA-256 in static/vendor/VERSIONS.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Il rebase aveva creato due head Alembic (003_input_duration e
003_measurement_interval entrambi figli di 002): upgrade head all'avvio
del server sarebbe fallito. Rinominata la revision in 004_input_duration
con down_revision 003_measurement_interval — catena di nuovo lineare.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Colonna nullable su measurements + migration 003, campo opzionale negli
schemi API (create singola e batch), passthrough nel proxy Flask e timer
lato client in task_execute: parte all'attivazione del subtask, si
resetta su auto-advance e navigazione manuale.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Until now the out-of-tolerance authorization ("Autorizzazione capoturno")
required is_admin, conflating shift-leader authority with full system
administration. Introduce a combinable Supervisor role so a capoturno can
authorize overrides without admin privileges (roadmap D-0.8 / Phase 2).
Backend:
- users router: add "Supervisor" to the allowed roles (centralized as
VALID_ROLES, deduplicating the create/update validation).
- middleware: add require_supervisor dependency (admins still bypass).
Frontend:
- measure.validate_supervisor: authorize users with the Supervisor role OR
is_admin (was is_admin only).
- admin/users: Supervisor checkbox + orange role badge.
- profile: explicit Supervisor badge styling.
- EN translation for the new role label.
roles is a free JSON column, so no migration is required.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The reorder_tasks endpoint loaded RecipeTask with selectinload(subtasks)
only. Serializing via TaskResponse reads RecipeTask.recipe_id, a @property
that traverses the version relationship, triggering a lazy load outside the
async greenlet context -> MissingGreenlet error.
Add selectinload(RecipeTask.version), matching the pattern already used in
the get-task flow. Fixes the previously order-dependent test_reorder_tasks
failure; full suite now 179 passed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Nuovo servizio ai_service.py: estrae testo da PDF (pdfplumber) + analisi AI (OpenRouter)
- Endpoint POST /api/recipes/{id}/parse-technical-sheet con validazione file
- UI: bottone "Importa da PDF" nel task editor con modale upload + preview editabile
- Task suggeriti modificabili/rimovibili prima della creazione bulk
- Config: OPENROUTER_API_KEY e OPENROUTER_MODEL in .env
- Dipendenze: pdfplumber + httpx aggiunti a server deps
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Recipe: nuovo campo measurement_interval_minutes (migration 003) con UI nel recipe editor
- Auto-logout: impostazione di sistema configurabile da admin/settings, timer inattività JS
con warning modale 60s prima del logout, tracking eventi utente throttled
- Navbar: aggiunto link "Impostazioni" per admin (desktop + mobile)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- setup: corregge path templates (sibling di api/, non figlio) e firma TemplateResponse
- setup: invalida sessione client dopo operazioni che ruotano l'API key (init DB, creazione admin, cambio password, attivazione utente)
- api_client: include il nome del campo nei messaggi di errore 422 (FastAPI validation)
- ui: nuovo componente _app_logo.html (SVG inline theme-aware) usato su login e navbar
- /measure/select: aggiunta miniatura 64×64 accanto al nome ricetta
(object-contain per mantenere l'immagine intera, non croppata).
Lo schema backend RecipeSummary esponeva solo id/code/name/active:
aggiunti image_path e description così la card può rendere thumb
e sottotitolo.
- /maker/recipes: la miniatura passa da object-cover (crop) a
object-contain (fit), per visualizzare l'intera immagine della
ricetta senza tagli.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- test_recipes: split update test into in-place vs copy-on-write paths
(PUT now updates in-place when current version has no measurements).
Added _seed_measurement() helper to force copy-on-write where needed.
- test_tasks::reorder: expunge_all() between POST and GET so the shared
test session re-fetches RecipeVersion.tasks instead of returning the
cached collection. Prod uses one session per request, so identity-map
staleness only affects tests.
- test_measure::save_measurement_proxy: payload aligned to current API
(version_id is required; pass_fail/deviation are computed server-side).
All 179 tests green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GET /api/measurements required Metrologist, but the operator workflow
calls it from task_complete.html to render the post-recipe riepilogo —
silently empty for every non-Metrologist user. Open to any authenticated
user; payload carries no PII beyond numeric values + recipe ref.
Regression test added.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both came from the src/ restructure and only show up at runtime, so
the test suite had not caught them.
- src/backend/config.py: env_file was "../../.env", which pydantic-
settings resolves against the *cwd*, not the file. Running uvicorn
or alembic from the project root therefore looked for
../../.env one level above the repo and silently fell back to the
default DB_PASSWORD ("change_me_in_production"), hiding the real
password. Now resolved as Path(__file__).resolve().parents[2] /
".env" so the lookup is always against the project root regardless
of cwd.
- src/backend/models/orm/__init__.py: Station and
StationRecipeAssignment were never imported here, so anything that
triggers Base.metadata.create_all without first importing the
setup router (which has its own Station import) ended up with no
stations / station_recipe_assignments tables. Verified locally:
/api/setup/seed used to fail with "Table tiemeasureflow.stations
doesn't exist" before this fix.
- .gitignore: ignore src/frontend/flask_app/package.json and
package-lock.json (local npm-install artifacts; the Dockerfile
installs tailwindcss directly).
Smoke verified end-to-end: uvicorn + gunicorn + MySQL, login + admin
stations + select_recipe + admin users all 200 OK.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cleanup
- Remove src/backend/Dockerfile.legacy and
src/frontend/flask_app/Dockerfile.legacy (history is in git, build
uses the new uv-based root Dockerfile / Dockerfile.frontend).
- Remove src/frontend/flask_app/verify_i18n.py (had hardcoded paths
pointing at the old client/ tree).
Group docs/
- New docs/README.md indexes everything in one place.
- New docs/architecture/STATO_PROGETTO.md: snapshot of what works in
V2.0.0 (inherited V1.0.7 features, rev04 Phase 1 stations,
worker scaling, src/ restructure, test status, stack, decisions).
- New docs/architecture/ROADMAP.md: what's next — Phases 2-7 of the
rev04 migration with status, open client decisions (D-0.1 through
D-0.10), tech debt and time estimates for M1 / M2.
- Move PIANO_IMPLEMENTAZIONE.md (90KB V1.0.0 plan) to
docs/archive/2026-02-06-piano-implementazione-v1.md (historical).
- Move Schema sviluppo SW TieFlow_rev04-2026.docx to docs/specs/
with ISO date filename so the customer spec is now tracked.
- Move src/frontend/flask_app/I18N_SETUP.md to docs/I18N_SETUP.md
and rewrite paths to the new src/frontend/flask_app/ tree.
.dockerignore: simplified now that legacy Dockerfiles are gone;
docs/ stays excluded from the build context.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aligns the repo with the python-project-spec-design.md template chosen
for V2.0.0. Big move, no logic changes. The 3 pre-existing test
failures (test_recipes::test_update_recipe, test_recipes::
test_recipe_versioning, test_tasks::test_reorder_tasks, plus the
client test_save_measurement_proxy) survive unchanged.
Layout changes
- server/ -> src/backend/
- server/middleware/ -> src/backend/api/middleware/
- server/routers/ -> src/backend/api/routers/
- server/models/ -> src/backend/models/orm/
- server/schemas/ -> src/backend/models/api/
- server/uploads/ -> uploads/ (project root, mounted volume)
- server/tests/ -> src/backend/tests/
- client/ -> src/frontend/flask_app/ (Flask kept; React
deroga is documented in CLAUDE.md, justified by tablet UX, USB
caliper/barcode workflow and Fabric.js integration)
Tooling
- pyproject.toml: monorepo with [project] core deps and
optional-dependencies server / client / dev. Replaces both
server/requirements.txt and client/requirements.txt.
- uv.lock + .python-version (3.11) committed for reproducible builds.
- Dockerfile (root, backend) and Dockerfile.frontend rewritten to use
uv sync --frozen --no-dev --extra server|client; legacy Dockerfiles
preserved as Dockerfile.legacy for reference but excluded from build
context via .dockerignore.
- docker-compose.dev.yml + docker-compose.yml: build context now ".",
dockerfile pointing to the root files.
Code adjustments forced by the move
- Every "from config|database|models|schemas|services|routers|middleware
import ..." rewritten to its src.backend.* equivalent (50+ files
including indented inline imports inside test bodies).
- src/backend/migrations/env.py: insert project root into sys.path so
alembic can resolve src.backend.* imports regardless of cwd.
- src/backend/config.py: env_file ../../.env (was ../.env), upload_path
resolves project root via parents[2].
- src/backend/tests/conftest.py + tests: import ... from src.backend.*
instead of bare names; old per-directory pytest.ini files removed in
favor of root pyproject.toml [tool.pytest.ini_options].
- .gitignore: uploads/ at root, src/frontend/flask_app/static/css/
tailwind.css path; .dockerignore tightened.
- CLAUDE.md: rewrote sections "Layout del repository", "Comandi di
Sviluppo", "Database & Migrations", "Test", "i18n", and all path
references throughout the architecture sections.
Verified
- uv lock resolves 77 packages; uv sync --extra server --extra client
--extra dev installs cleanly.
- uv run pytest: 171 passed, 4 pre-existing failures.
- uv run alembic -c src/backend/migrations/alembic.ini check loads
config and metadata (errors only on the absent local MySQL).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>