Commit Graph

9 Commits

Author SHA1 Message Date
Adriano 486e1288bf Backend: UTC nel DB, +00:00 nelle API, ora italiana solo in presentazione (#42)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-06 11:41:22 +02:00
Adriano 1d93eded27 Ripristino delle quote già misurate nel ciclo in corso dopo un reload (#40)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-06 11:31:57 +02:00
Adriano 7b015078e9 fix(measurements): value NaN/Inf/scala e coerenza quota/versione/run (#5, #20)
#20 - models/api/measurement.py: value accettava NaN/Infinity/fuori scala,
ogni confronto tornava falso ("pass") e poi l'INSERT falliva. Il controllo
sposta in measurement_service._validate_measured_value (math.isfinite +
|value|<1e6): un Field pydantic (allow_inf_nan/gt/lt) rifiuta la stessa cosa
ma FastAPI, costruendo l'errore 422, riecheggia il NaN nel campo "input" e
Starlette lo nega con allow_nan=False, producendo un 500 al posto del 422 -
verificato con una chiamata reale, non solo a livello di modello.
calculate_pass_fail e save_measurement arrotondano value a 6 decimali (stessa
scala di DECIMAL(12,6)) prima di calcolare e salvare, cosi' il verdetto e il
valore persistito non divergono mai.

#5 - measurement_service.save_measurement: prima dell'insert verifica che la
quota appartenga a un task della version_id dichiarata (422 altrimenti), e se
production_run_id e' dato usa la nuova validate_run_for_version(db, run_id,
version_id): il run deve esistere (404), essere della stessa ricetta della
versione (422), essere misurabile - riusa production_service._require_measurable
(409 se chiuso o in pausa). Import locale per evitare un ciclo con
production_service, che gia' importa measurement_service.

Test: gia' presenti in test_measurements.py (TestMeasurementValueBounds,
TestSaveMeasurementCoherence) - finiti nel commit 0dce229 di un altro agente
che lavorava in parallelo sullo stesso file (export CSV, #22); confermato con
`git show 0dce229 -- test_measurements.py`, contenuto intatto.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 22:47:58 +02:00
Adriano 85d7976f4e docs(vision): commenta perché camera resta fuori dal pattern client
MeasurementCreate.input_method accetta solo usb_caliper|manual anche se
l'enum ORM e il database permettono ormai camera. È voluto: una misura
da camera deve nascere in vision_service.execute_task, mai da un client
che posta JSON direttamente - non c'è dietro né immagine, né grafo, né
engine_version. Commento aggiunto perché non venga "corretto" tornando
alla lettera dell'enum.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BBnuACZSCJqXrMYC3LUMU
2026-08-16 19:49:21 +02:00
Adriano Dal Pastro 98b1c5ae85 feat(measure): l'operatore entra nella sequenza, non in un elenco
Punto 10. Sceglere una ricetta apriva la lista completa dei task e chiedeva
all'operatore di decidere da dove cominciare prima ancora di aver fatto
qualcosa. Ora AVVIA IN SEQUENZA porta dentro il primo task; la lista resta,
un livello sotto, per quando serve vederla tutta o tornare indietro.

- nuova rotta /measure/start/<ricetta>: memorizza lotto e seriale (prima lo
  faceva la lista, che ora si salta), verifica la tracciabilità obbligatoria
  e apre il primo task
- GET /api/measurements/task-progress: quante quote ha già preso ogni task di
  misura, contate per quota e non per tentativo, delimitate dalla produzione
  aperta o, fuori produzione, dall'operatore
- la lista distingue «Incompiuto 1/3» da «Completato 3/3»: un task lasciato a
  metà non somigliava più a uno mai aperto
- «Visualizza singolo TASK» non è più riservato al Maker — dire «incompiuto»
  senza dare la strada per tornarci sarebbe una lamentela, non una funzione
- nel task «Riepilogo» diventa «Completato» e si apre solo quando il ciclo è
  chiuso: altrimenti sarebbe la scorciatoia che rende facoltative le quote
- «Fine ciclo misura» si vede da subito, spento, e dice quante quote mancano;
  prima compariva a task già finito, quando non serviva più saperlo

Il pulsante verde del footer diventa «Task successivo»: due bottoni con la
stessa parola addosso sono uno di troppo.

Test: 334 (erano 309). Il side effect dei mock del client Flask ora risponde
per endpoint invece che per turno — una lista posizionale si rompe appena una
pagina fa una domanda in più al server, che è come crescono tutte.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 21:31:42 +00:00
Adriano Dal Pastro e2aae88858 feat(measure): il fuori tolleranza si autorizza, non si chiude
Il punto 5 c'era a meta': la schermata apriva il modale del capoturno e bloccava
l'avanzamento automatico, ma il modale si chiudeva con Annulla o con un click
sullo sfondo, la misura era gia' salvata, e `pendingAdvance` veniva impostato e
non letto da nessuno. Era una conferma, non uno sbarramento.

Ora l'autorizzazione finisce sulla misura: due colonne, chi ha autorizzato e
quando. Finche' una quota e' fuori tolleranza e nessuno l'ha autorizzata, il
server rifiuta la misura della quota successiva e la chiusura del ciclo. Non e'
la schermata a impedirlo: la schermata risparmia solo il viaggio.

Rimisurare la stessa quota resta possibile — il calibro scivola, il pezzo si
riposiziona — e una seconda lettura in tolleranza libera il blocco, perche' conta
l'ultima lettura di ogni quota. Quante volte si possa riprovare e' il punto 4, e
tutte le letture restano comunque a registro. Un warning non blocca: fuori dai
limiti di attenzione ma dentro la tolleranza e' dentro la tolleranza.

Ricaricare la pagina era il modo piu' semplice per scavalcare il vecchio gate.
Non lo e' piu': la schermata chiede al server, all'apertura, se una quota sta
aspettando, e si ritrova davanti lo stesso blocco.

Sparisce /validate-supervisor, che verificava le credenziali e buttava via la
risposta. Al suo posto un endpoint che le credenziali le usa per scrivere
l'approvazione dove serve. Il controllo del capoturno si sposta in auth_service,
accanto al resto delle credenziali: fermo linea, chiusura e fuori tolleranza
fanno la stessa domanda, e solo una delle tre riguarda la produzione.

Il file di statistica guadagna authorised_by e authorised_at: mostrava il
fallimento e non la decisione, che e' la meta' che un auditor chiede.

Migrazione 010: due colonne nullable sulle misure. Le righe esistenti restano
nulle — retrodatare un'autorizzazione mai avvenuta sarebbe inventarsi un record
di audit, e quelle produzioni sono chiuse da un pezzo.

Test: +18 (309). Coprono il rifiuto della quota successiva, la rimisura ammessa,
il rilascio del blocco con una lettura buona, il ciclo che non si chiude, il
capoturno registrato sulla misura, le credenziali sbagliate e chi capoturno non
e', il file di statistica, e il fatto che una produzione chiusa non blocchi la
successiva. Aggiunto uploads/statistics/ al gitignore: i test che chiudono una
produzione scrivevano nel repository.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 20:54:24 +00:00
Adriano Dal Pastro 7bc3c1f938 feat(production): fermo linea e fine produzione fanno finalmente qualcosa
Punto 6 del documento modifiche del 28/07. I due pulsanti esistevano, chiedevano
correttamente l'autorizzazione del capoturno, e poi ricadevano su un commento:
"handled by GAIA integration (future)". E' la riga di apertura del documento.

Ora agiscono sulla produzione aperta: fermo linea sospende e congela il conto alla
rovescia, la ripresa lo fa ripartire restituendo il tempo del fermo, fine
produzione chiude ed emette il file di statistica. Le credenziali del capoturno
vanno direttamente all'endpoint, che le verifica e registra sulla traccia della
produzione chi ha autorizzato cosa: validarle a parte non lascerebbe traccia, e
sarebbe un giro in piu'.

In interfaccia il pulsante di fermo diventa "Riprendi" quando la linea e' ferma, e
una banda dichiara lo stato con il valore congelato a video: una linea ferma che
sembra in marcia e' il modo in cui si salta un intervallo di misura senza
accorgersene.

Il file di statistica ha richiesto di legare le misure alla produzione (migrazione
006): Measurement conosceva solo versione, lotto e seriale, nessuno dei quali
separa una produzione dalla successiva sulla stessa ricetta e sullo stesso lotto,
quindi "le misure dell'intera produzione" non era una query. Il CSV rispetta i
separatori configurati in system_settings e porta con se' i limiti di tolleranza:
senza quelli un esito pass/fail non e' piu' ricalcolabile dal file a distanza di
anni, che e' il senso di un documento per audit. Le misure esportate vengono
marcate synced_to_csv. Se la produzione non ha misure non viene scritto nulla: un
file vuoto sarebbe rumore nella cartella, non evidenza.

L'invio a GAIA resta assente e non abbozzato, con il punto d'innesto dichiarato in
close_run: una chiamata vuota che sembra collegata e' peggio di niente. Tutto il
resto del punto 6 non dipende dal gestionale e funziona adesso.

Anche la 006 e' stata eseguita su SQLite usa e getta prima di essere considerata
buona, e anche qui la prova ha trovato un difetto: aggiungere una colonna con
foreign key fa emettere ad alembic un ALTER di vincolo, che SQLite rifiuta. Ora usa
batch_alter_table con il vincolo nominato, che su MySQL resta un ALTER normale e su
SQLite ricostruisce la tabella - cosi' la chiave esterna non va persa per far
contento il dialetto dei test.

Corretto anche il proxy di salvataggio misura, che chiamava /api/measurements senza
slash finale e pagava un redirect 307 a ogni singola misura.

Traduzioni: pybabel aveva di nuovo indovinato sette voci marcandole fuzzy, e in
italiano "Linea ferma" era diventato "Lingua Preferita" su una banda di sicurezza.
Tradotte per esteso in IT ed EN e tolti i flag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 17:32:43 +00:00
Adriano 3f03ec8ab1 feat(measure): memorizza tempo di inserimento misura (input_duration_ms)
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>
2026-07-28 14:42:24 +02:00
Adriano 1a0431366f chore(v2): restructure monorepo to src/ layout with uv
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>
2026-04-25 12:26:47 +02:00