DXF compare: task dxf_compare con CompareDxf (VisionSuite) nel worker vision #46

Closed
opened 2026-10-07 09:46:23 +00:00 by Adriano · 2 comments
Owner

COSA: task dxf_compare in TieMeasureFlow. Il pezzo fotografato si confronta col disegno DXF tramite CompareDxf di VisionSuite, che gira nel worker Docker vision. La posa del disegno è data da due punti (anchor="two_points").
PERCHÉ: decisione di Adriano del 07/10/2026. Il motore serve a ATD e Sider Engineering, e alla fiera del 27-29/10.
DECISIONI (Adriano, 07/10/2026):

  • immagine: upload dal tablet adesso; camera via agente (#3) dopo, sulla stessa API /api/vision/execute;
  • posa: due punti (point_a = origine del disegno, point_a→point_b = angolo). Niente MatchPattern;
  • ricetta impostata lato server.
    VERIFICATO: CompareDxf (visionsuite origin/main bc0df16, packages/vs-tools/src/visionsuite/tools/dxf.py:14) non cerca la posa da solo. La nota della scheda del 14/09 («l'ICP converge da solo») è superata.
    CRITERIO DI FATTO: dal Maker si carica una ricetta VisionSuite v2 con DXF; l'operatore carica una foto; si salvano le quote (points_in_pct, distance_max…) con esito sulle tolleranze del subtask, e si mostra l'overlay disegno/trovato.
    COLLEGATE: #3 (camera via agente), #45 (worker per stazione: prenderà la migrazione 017; questa prende la 016).
COSA: task `dxf_compare` in TieMeasureFlow. Il pezzo fotografato si confronta col disegno DXF tramite `CompareDxf` di VisionSuite, che gira nel worker Docker `vision`. La posa del disegno è data da due punti (`anchor="two_points"`). PERCHÉ: decisione di Adriano del 07/10/2026. Il motore serve a ATD e Sider Engineering, e alla fiera del 27-29/10. DECISIONI (Adriano, 07/10/2026): - immagine: upload dal tablet adesso; camera via agente (#3) dopo, sulla stessa API `/api/vision/execute`; - posa: due punti (`point_a` = origine del disegno, `point_a→point_b` = angolo). Niente MatchPattern; - ricetta impostata lato server. VERIFICATO: `CompareDxf` (visionsuite origin/main bc0df16, packages/vs-tools/src/visionsuite/tools/dxf.py:14) non cerca la posa da solo. La nota della scheda del 14/09 («l'ICP converge da solo») è superata. CRITERIO DI FATTO: dal Maker si carica una ricetta VisionSuite v2 con DXF; l'operatore carica una foto; si salvano le quote (`points_in_pct`, `distance_max`…) con esito sulle tolleranze del subtask, e si mostra l'overlay disegno/trovato. COLLEGATE: #3 (camera via agente), #45 (worker per stazione: prenderà la migrazione 017; questa prende la 016).
Adriano added the sviluppo label 2026-10-07 09:46:23 +00:00
Author
Owner

ANALISI (passo 1, Opus 5.5, daitarn, 07/10/2026). Allargata su richiesta di Adriano: tutta la suite di visione, non solo il DXF compare.

CONTESTO COMUNE — gitea:Adriano/TieMeasureFlow#46 (suite di visione VisionSuite completa in TMF: PM, primitive, UCS, quote, forma, DXF compare). Testo identico per tutte le unità.

Repo: ssh://git@git.tielogic.xyz:222/Adriano/TieMeasureFlow.git, base V3.0.0 a829bf3. Macchina: daitarn (Windows 11, Git Bash + PowerShell, uv 0.11, Python 3.13.14, niente Docker).
Decisioni di Adriano (07/10/2026): immagine caricata dal tablet adesso, camera via agente (#3) dopo, sulla stessa API; posa del disegno a due punti (CompareDxf.anchor="two_points", point_a/point_b collegati ad altri strumenti); per il DXF compare niente MatchPattern; TMF deve eseguire QUALUNQUE ricetta VisionSuite v2 (tutti gli strumenti di vs-tools: locate/MatchPattern, locate_ucs, detect, contours*, construct, dimensions, form, dxf, advanced), non solo CompareDxf; la ricetta si imposta lato server, si compone nell'editor di VisionSuite e si carica in TMF. TMF non ha un editor di grafi.

VisionSuite (submodule vendor/visionsuite, oggi f095fcc → si porta a origin/main bc0df16):

  • strumenti grafo in vs-tools (entry point visionsuite.task.tools, extra dxf = vs-dxf, locate = vs-pm2d; si installa vs-tools[locate,dxf] = tutta la suite); load_tools() li registra, senza import espliciti. Dal contesto gli strumenti leggono solo image, calibration, edge_shift_px, meta, recipe_dir (e templates, non supportato in TMF: MatchPattern usa model = .npz in meta.models);
  • CompareDxf (packages/vs-tools/src/visionsuite/tools/dxf.py): DXF da meta.dxf[<key>].path relativo a recipe_dir, oppure dxf_path; scala da mm_per_px o da context["calibration"]; uscite float points_in_pct, distance_avg, distance_min, distance_max, int n_points, stringhe *_outcome/outcome; overlay = lista {"type":"contour","role":"reference"|"roi"|"found","rows":[...],"cols":[...]} in px immagine;
  • ricette solo schema_version: 2 (la v1 viene rifiutata, nessuna migrazione); Task.from_dict(data, *, recipe_dir=None, draft=False) solleva RecipeError (sottoclasse di ValueError); Session.from_dict(data, recipe_dir, calibration=None, draft=False) mette nel contesto recipe_dir, meta e calibration (packages/vs-task/src/visionsuite/task/session.py:89);
  • strumenti rinominati: Distance2P→DistancePointPoint, DistancePL→DistancePointLine, DistanceCL→DistanceCircleLine, Angle2L→AngleLines; visionsuite.task.tools non esiste più.

CONTRATTO A — worker vision (src/vision_worker/main.py), HTTP interno:

  • POST /run multipart: image (file), graph (str JSON v2), recipe_dir (str opzionale: percorso RELATIVO sotto la radice VISION_RECIPES_ROOT, default /app/uploads; assoluto, .. o fuori radice → 422), calibration (str JSON opzionale: {"mm_per_px": float} o {"px_per_mm": float}). Risposta 200: le chiavi di oggi invariate (outputs {"<tool_id>.": float}, failures, engine_version, duration_ms e le altre già presenti) più overlay (lista di dict come sopra, con tool_id aggiunto a ogni elemento; lista vuota se nessuno strumento ne produce) e outcome (str o null, quello del TaskReport).
  • POST /validate multipart: graph (str), recipe_dir (str opzionale, stesse regole). Risposta 200: {"ok": bool, "errors": [str], "outputs": [{"key": "<tool_id>.<prop>", "quantity": str|null, "unit": str|null}]}. outputs elenca solo le uscite numeriche (float/int, non bool) degli strumenti. Validazione non in bozza: un file di meta mancante è un errore.

CONTRATTO B — backend (prefisso /api/vision, src/backend/api/routers/vision.py):

  • Migrazione 016: colonna recipe_tasks.vision_recipe_dir String(500) nullable, percorso relativo sotto settings.upload_path. La 017 resta a #45.
  • POST /api/vision/tasks/{task_id}/recipe (require_maker), multipart: recipe (file .json, ricetta v2), files (lista di file, 0..n). Ogni file va abbinato per nome base al path di una voce qualunque di meta (meta.dxf.*, meta.models.*, meta.calibration, …: ogni dict con chiave path); nome base mancante o non abbinato → 422 con l'elenco. Salvataggio in uploads/vision/recipes/<task_id>/<sha256 della ricetta, primi 16>/ (recipe.json + file ai loro path relativi, path traversal rifiutato), cartella immutabile mai cancellata. Poi chiama il worker /validate: se ok=false → 422 con errors, e task.vision_json/vision_recipe_dir non cambiano. Se ok → task.vision_json = ricetta, task.vision_recipe_dir = cartella. Risposta 200: {"task_id": int, "recipe_dir": str, "files": [str], "outputs": [{"key","quantity","unit"}]}. Solo per task di tipo camera_measure o dxf_compare (altrimenti 422).
  • GET /api/vision/tasks/{task_id}/recipe (require_maker): {"task_id", "recipe_dir": str|null, "recipe": dict|null, "files": [str], "outputs": [...]}, con outputs letto dal worker /validate ([] se manca la ricetta).
  • POST /api/vision/execute: ingressi invariati. Accetta task_type in (camera_measure, dxf_compare). Passa al worker recipe_dir = task.vision_recipe_dir. Salva l'immagine in uploads/vision/results/<task_id>/<uuid>.<ext> → vision_results.image_path, e l'overlay JSON accanto (<uuid>.overlay.json) → overlay_path. Risposta = campi di oggi + outcome (str|null), overlay (lista), image_url (str, = /api/vision/results/{vision_result_id}/image), failures (lista {"tool_id","tool_name","error"}).
  • GET /api/vision/results/{id}/image (require_measurement_tec): il file immagine salvato.
  • Mappatura quote: RecipeSubtask.vision_output = una key di outputs, come oggi. Il verdetto lo dà measurement_service con utl/ltl del subtask, non *_outcome di VisionSuite.

REGOLE PER OGNI UNITÀ:

  • Lavori solo nella tua cartella e sul tuo ramo. Tocchi solo i file elencati nella tua unità. Se serve toccarne un altro, non lo tocchi e lo scrivi nel rapporto (MANCA).
  • Ogni modifica porta un breve commento inline (cosa/perché), nello stile del file. Le stringhe UI vanno in _(); non toccare i cataloghi di traduzione.
  • Battito: prima di ogni passo, e comunque almeno una volta al minuto, aggiungi una riga a C:/Users/Adriano/AppData/Local/Temp/claude/D--Sorgenti-AI-OS/aa35fa14-0bee-4ddf-9436-78539ec0ee61/scratchpad/heartbeat-<unità>.txt: echo "$(date +%H:%M:%S) sto facendo X" >> <file>. Costa nulla ed è l'unico modo di distinguerti da un processo bloccato.
  • Budget: punta a ~60 chiamate. Se un problema resiste dopo tre tentativi, fermati e riportalo. Un pezzo dichiarato non fatto è un esito accettabile; un parziale committato vale più di un completo perso.
  • Committa presto e spesso sul tuo ramo, messaggi brevi in italiano con (#46), e alla fine git push -u origin <ramo>. Ogni commit termina con la riga Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>.
  • Se Adriano o chi coordina ti scrive «stato», rispondi con una riga e continua il lavoro: non è un ordine di fermarti.
  • Rapporto finale in questo formato (campi vuoti omessi): ESITO: fatto|parziale|bloccato / RAMO + ultimo commit / FILE: path — modifica / PROVA: comando → risultato / MANCA: … / MODELLO: l'ID del modello dichiarato nel tuo prompt di sistema.

SUDDIVISIONE

Quattro unità con file disgiunti, in parallelo. Integrazione su lavoro/daitarn-46 e poi su V3.0.0 a cura del coordinatore (Opus), con prova d insieme e build Docker del worker su goldrake.

UNITÀ 1 — motore (heartbeat-u1.txt)
Cartella: D:/Sorgenti/lavoro/TMF-motore · Ramo: lavoro/daitarn-46-motore (da V3.0.0 a829bf3).
FILE: vendor/visionsuite (submodule), pyproject.toml, uv.lock, src/vision/, src/vision_worker/, Dockerfile.vision, docker-compose.yml (solo il servizio vision), .env.example (solo righe del worker).
NON TOCCARE: src/backend/, src/frontend/. Se un test di src/backend si rompe per il cambio di VisionSuite, NON correggerlo: scrivi nel rapporto quale e perché.
DA FARE:

  1. Submodule: git -C vendor/visionsuite fetch origin && git -C vendor/visionsuite checkout bc0df16, poi committa il nuovo pin.
  2. pyproject.toml: in [tool.uv.sources] aggiungi vs-tools e vs-dxf (path editable sotto vendor/visionsuite/packages/, come gli altri). Extra vision: aggiungi vs-tools[locate,dxf] con lo stesso marker python>=3.13 degli altri. Poi uv lock e uv sync --extra vision-worker --extra dev.
  3. src/vision/runner.py: run_graph(image, graph, calibration=None, recipe_dir=None) costruisce con Session.from_dict(graph, recipe_dir or <cartella temporanea vuota>, calibration) (leggi vendor/visionsuite/packages/vs-task/src/visionsuite/task/session.py per come si esegue). Restituisce, oltre a quello di oggi, overlay (lista dei dict overlay di tutti gli strumenti, con tool_id aggiunto) e outcome del report. RecipeError → GraphBuildError come oggi. Le uscite numeriche restano filtrate come oggi (float/int finiti, niente bool). Aggiungi validate_graph(graph, recipe_dir=None) -> (ok, errors, outputs) per /validate del contratto A: outputs = uscite numeriche dichiarate dagli strumenti del grafo (leggi visionsuite.task.graph per Property/Direction e come elencarle), quantity/unit se lo strumento le dichiara, altrimenti null.
  4. src/vision_worker/main.py: /run accetta recipe_dir e calibration (contratto A), con controllo del percorso sotto VISION_RECIPES_ROOT (default /app/uploads). Calibrazione JSON → dict passato a run_graph (verifica in vs-tools tools/dxf.py e tools/_base.py che il dict con mm_per_px/px_per_mm sia accettato). Nuovo /validate. Risposta di /run con overlay e outcome.
  5. Test: porta src/vision/tests e src/vision_worker/tests alla v2 (nomi nuovi, import da visionsuite.tools.*, schema_version: 2). Aggiungi test per: ricetta con DetectCircle/quote e ricetta con CompareDxf anchor="two_points" su immagine sintetica, con DXF di fixture (vendor/visionsuite/packages/vs-dxf/tests/fixtures/sample.dxf; prendi spunto da vendor/visionsuite/tests/test_visione_nel_grafo.py), recipe_dir + meta.dxf; overlay presente; /validate ok e con file mancante; recipe_dir con .. → 422.
  6. Dockerfile.vision: deve copiare e installare i pacchetti nuovi (vs-tools, vs-dxf). Qui Docker non c'è: controlla che i COPY coprano i path di [tool.uv.sources] e scrivi nel rapporto che la build va provata altrove.
    PROVA: uv run pytest src/vision src/vision_worker -q verde; poi uv run pytest -q intero, e riporta i fallimenti fuori dalla tua area senza correggerli.

UNITÀ 2 — backend (heartbeat-u2.txt)
Cartella: D:/Sorgenti/lavoro/TMF-backend · Ramo: lavoro/daitarn-46-backend (da V3.0.0 a829bf3).
FILE: src/backend/** (api/routers/vision.py, services/vision_service.py, models/orm/task.py, models/orm/vision.py se serve, models/api/vision.py, services/recipe_service.py per il copy-on-write, migrations/versions/016_*.py, test nuovi o aggiornati).
NON TOCCARE: src/vision/, src/vision_worker/, vendor/, pyproject.toml, uv.lock, src/frontend/**. Nei test il worker è finto (come in src/backend/tests/test_vision_worker_transport.py): non serve VisionSuite installato.
DA FARE (contratto B; il worker risponde come nel contratto A):

  1. Migrazione 016 recipe_tasks.vision_recipe_dir (su e giù) + campo ORM.
  2. POST e GET /api/vision/tasks/{task_id}/recipe come da contratto B. Path traversal rifiutato (riusa il controllo di api/routers/files.py se adatto), limite di dimensione come gli altri upload. .dxf/.npz/.json/immagini ammessi solo qui: non allargare /api/files/upload. La cartella <sha16> è immutabile: se esiste già, non riscriverla.
  3. vision_service._call_worker passa recipe_dir (e calibration se il task o la stazione ne hanno una; se oggi non esiste nessuna fonte, non passare nulla e scrivilo nel rapporto). Nuova funzione per /validate.
  4. execute_task: ammette camera_measure e dxf_compare; salva immagine e overlay (contratto B) e riempie image_path/overlay_path; la risposta di /execute porta outcome, overlay, image_url, failures. Aggiungi GET /api/vision/results/{id}/image.
  5. Copy-on-write della versione ricetta: il task copiato si porta dietro vision_recipe_dir e vision_json (la cartella è immutabile, basta il riferimento). Vedi services/recipe_service.py:117-127.
  6. Verifica che dxf_compare sia trattato come task di misura dove serve (MEASURING_TASK_TYPES in models/orm/task.py:21 e i suoi usi). Se aggiungerlo rompe qualcosa, fermati e riporta.
    TEST: upload ricetta ok e 422 (file mancante, nome non abbinato, traversal, task di tipo sbagliato, worker che risponde ok=false); GET; execute dxf_compare con worker finto che restituisce overlay → file salvati, risposta estesa, misure con pass/fail sulle tolleranze del subtask; copy-on-write; migrazione 016 su e giù.
    PROVA: uv run pytest src/backend -q verde (riporta il conteggio prima e dopo).

UNITÀ 3 — Maker (heartbeat-u3.txt)
Cartella: D:/Sorgenti/lavoro/TMF-maker · Ramo: lavoro/daitarn-46-maker (da V3.0.0 a829bf3).
FILE: src/frontend/flask_app/blueprints/maker.py, src/frontend/flask_app/templates/maker/** (task_editor.html in particolare), file statici nuovi con un nome tuo (es. static/js/vision_recipe.js), test frontend del Maker.
NON TOCCARE: blueprints/measure.py, templates/measure/, src/backend/, app.py (se serve registrare qualcosa, scrivilo nel rapporto), cataloghi di traduzione.
Il backend del contratto B non è ancora unito: lavora contro il contratto (nei test mocka api_client come fanno i test esistenti del frontend).
DA FARE:

  1. task_editor.html (righe ~276-280 e ~542-547): rendi selezionabili i tipi camera_measure («Misura camera») e dxf_compare («Confronto profilo»).
  2. Per questi tipi, sezione «Ricetta visione»: caricamento del file .json della ricetta + N file allegati (DXF, modelli .npz, calibrazione) → route proxy in maker.py verso POST /api/vision/tasks/{task_id}/recipe (multipart; segui come maker.py fa già il proxy di /api/upload, riga ~347). Mostra cartella, file e uscite numeriche; gli errori 422 si elencano in chiaro. All'apertura, GET della ricetta corrente.
  3. Per ogni subtask (quota) del task: select vision_output popolata dalle outputs della ricetta (key + quantity/unit), salvata con il meccanismo con cui il Maker salva già i subtask. Verifica che l'API dei subtask accetti vision_output; se non lo accetta, scrivilo nel rapporto e non toccare il backend. Le tolleranze del subtask restano quelle di oggi.
  4. Stile e componenti come il resto del Maker (Alpine.js e Tailwind già presenti).
    PROVA: test frontend verdi (uv run pytest src/frontend/flask_app/tests -q), più un test della route proxy.

UNITÀ 4 — operatore (heartbeat-u4.txt)
Cartella: D:/Sorgenti/lavoro/TMF-operatore · Ramo: lavoro/daitarn-46-operatore (da V3.0.0 a829bf3).
FILE: src/frontend/flask_app/blueprints/measure.py, src/frontend/flask_app/templates/measure/** (task_execute.html in particolare), file statici nuovi con un nome tuo (es. static/js/vision_execute.js), test frontend della misura.
NON TOCCARE: blueprints/maker.py, templates/maker/, src/backend/, app.py, cataloghi di traduzione.
Il backend del contratto B non è ancora unito: lavora contro il contratto (nei test mocka api_client come fanno quelli esistenti).
DA FARE:

  1. task_execute.html (intorno alla riga 1776 camera_measure è trattato come misura normale): per camera_measure e dxf_compare la schermata offre «Acquisisci immagine» con <input type="file" accept="image/*" capture="environment"> (sul tablet apre la fotocamera, sul PC sceglie un file). La funzione JS che invia si chiama acquire(blob), così in seguito la camera dell'agente (#3) la potrà chiamare con lo stesso blob.
  2. Route proxy in measure.py verso POST /api/vision/execute (multipart: task_id, image, lot_number, serial_number, production_run_id come nel flusso di misura esistente) e verso GET /api/vision/results/{id}/image.
  3. Esito: tabella quote (valore, pass/fail con i colori già usati per le misure), outcome, failures in chiaro. Immagine con l'overlay sopra (canvas o SVG): contorni rows/cols in px immagine, colori per role (reference, found, roi), scalati alla dimensione mostrata. Pulsante «Riprova» che rifà l'acquisizione.
  4. Non cambiare i flussi già presenti in task_execute.html (avvio produzione, fine produzione, tracciabilità). Il difetto noto dei pulsanti Fermo linea/Fine produzione visibili prima di Avvio Produzione non si corregge qui.
    PROVA: test frontend verdi (uv run pytest src/frontend/flask_app/tests -q), più un test della route proxy di execute.
ANALISI (passo 1, Opus 5.5, daitarn, 07/10/2026). Allargata su richiesta di Adriano: tutta la suite di visione, non solo il DXF compare. CONTESTO COMUNE — gitea:Adriano/TieMeasureFlow#46 (suite di visione VisionSuite completa in TMF: PM, primitive, UCS, quote, forma, DXF compare). Testo identico per tutte le unità. Repo: ssh://git@git.tielogic.xyz:222/Adriano/TieMeasureFlow.git, base V3.0.0 a829bf3. Macchina: daitarn (Windows 11, Git Bash + PowerShell, uv 0.11, Python 3.13.14, niente Docker). Decisioni di Adriano (07/10/2026): immagine caricata dal tablet adesso, camera via agente (#3) dopo, sulla stessa API; posa del disegno a due punti (`CompareDxf.anchor="two_points"`, `point_a`/`point_b` collegati ad altri strumenti); per il DXF compare niente MatchPattern; TMF deve eseguire QUALUNQUE ricetta VisionSuite v2 (tutti gli strumenti di vs-tools: locate/MatchPattern, locate_ucs, detect, contours*, construct, dimensions, form, dxf, advanced), non solo CompareDxf; la ricetta si imposta lato server, si compone nell'editor di VisionSuite e si carica in TMF. TMF non ha un editor di grafi. VisionSuite (submodule vendor/visionsuite, oggi f095fcc → si porta a origin/main bc0df16): - strumenti grafo in `vs-tools` (entry point `visionsuite.task.tools`, extra `dxf` = vs-dxf, `locate` = vs-pm2d; si installa `vs-tools[locate,dxf]` = tutta la suite); `load_tools()` li registra, senza import espliciti. Dal contesto gli strumenti leggono solo `image`, `calibration`, `edge_shift_px`, `meta`, `recipe_dir` (e `templates`, non supportato in TMF: MatchPattern usa `model` = `.npz` in `meta.models`); - `CompareDxf` (packages/vs-tools/src/visionsuite/tools/dxf.py): DXF da `meta.dxf[<key>].path` relativo a `recipe_dir`, oppure `dxf_path`; scala da `mm_per_px` o da `context["calibration"]`; uscite float `points_in_pct`, `distance_avg`, `distance_min`, `distance_max`, int `n_points`, stringhe `*_outcome`/`outcome`; overlay = lista `{"type":"contour","role":"reference"|"roi"|"found","rows":[...],"cols":[...]}` in px immagine; - ricette solo `schema_version: 2` (la v1 viene rifiutata, nessuna migrazione); `Task.from_dict(data, *, recipe_dir=None, draft=False)` solleva `RecipeError` (sottoclasse di ValueError); `Session.from_dict(data, recipe_dir, calibration=None, draft=False)` mette nel contesto `recipe_dir`, `meta` e `calibration` (packages/vs-task/src/visionsuite/task/session.py:89); - strumenti rinominati: Distance2P→DistancePointPoint, DistancePL→DistancePointLine, DistanceCL→DistanceCircleLine, Angle2L→AngleLines; `visionsuite.task.tools` non esiste più. CONTRATTO A — worker `vision` (src/vision_worker/main.py), HTTP interno: - `POST /run` multipart: `image` (file), `graph` (str JSON v2), `recipe_dir` (str opzionale: percorso RELATIVO sotto la radice `VISION_RECIPES_ROOT`, default `/app/uploads`; assoluto, `..` o fuori radice → 422), `calibration` (str JSON opzionale: `{"mm_per_px": float}` o `{"px_per_mm": float}`). Risposta 200: le chiavi di oggi invariate (`outputs` {"<tool_id>.<prop>": float}, `failures`, `engine_version`, `duration_ms` e le altre già presenti) più `overlay` (lista di dict come sopra, con `tool_id` aggiunto a ogni elemento; lista vuota se nessuno strumento ne produce) e `outcome` (str o null, quello del TaskReport). - `POST /validate` multipart: `graph` (str), `recipe_dir` (str opzionale, stesse regole). Risposta 200: `{"ok": bool, "errors": [str], "outputs": [{"key": "<tool_id>.<prop>", "quantity": str|null, "unit": str|null}]}`. `outputs` elenca solo le uscite numeriche (float/int, non bool) degli strumenti. Validazione non in bozza: un file di `meta` mancante è un errore. CONTRATTO B — backend (prefisso /api/vision, src/backend/api/routers/vision.py): - Migrazione 016: colonna `recipe_tasks.vision_recipe_dir` String(500) nullable, percorso relativo sotto `settings.upload_path`. La 017 resta a #45. - `POST /api/vision/tasks/{task_id}/recipe` (require_maker), multipart: `recipe` (file .json, ricetta v2), `files` (lista di file, 0..n). Ogni file va abbinato per nome base al `path` di una voce qualunque di `meta` (`meta.dxf.*`, `meta.models.*`, `meta.calibration`, …: ogni dict con chiave `path`); nome base mancante o non abbinato → 422 con l'elenco. Salvataggio in `uploads/vision/recipes/<task_id>/<sha256 della ricetta, primi 16>/` (recipe.json + file ai loro path relativi, path traversal rifiutato), cartella immutabile mai cancellata. Poi chiama il worker `/validate`: se `ok=false` → 422 con `errors`, e `task.vision_json`/`vision_recipe_dir` non cambiano. Se ok → `task.vision_json` = ricetta, `task.vision_recipe_dir` = cartella. Risposta 200: `{"task_id": int, "recipe_dir": str, "files": [str], "outputs": [{"key","quantity","unit"}]}`. Solo per task di tipo `camera_measure` o `dxf_compare` (altrimenti 422). - `GET /api/vision/tasks/{task_id}/recipe` (require_maker): `{"task_id", "recipe_dir": str|null, "recipe": dict|null, "files": [str], "outputs": [...]}`, con `outputs` letto dal worker `/validate` ([] se manca la ricetta). - `POST /api/vision/execute`: ingressi invariati. Accetta `task_type` in (`camera_measure`, `dxf_compare`). Passa al worker `recipe_dir` = `task.vision_recipe_dir`. Salva l'immagine in `uploads/vision/results/<task_id>/<uuid>.<ext>` → `vision_results.image_path`, e l'overlay JSON accanto (`<uuid>.overlay.json`) → `overlay_path`. Risposta = campi di oggi + `outcome` (str|null), `overlay` (lista), `image_url` (str, = `/api/vision/results/{vision_result_id}/image`), `failures` (lista `{"tool_id","tool_name","error"}`). - `GET /api/vision/results/{id}/image` (require_measurement_tec): il file immagine salvato. - Mappatura quote: `RecipeSubtask.vision_output` = una `key` di `outputs`, come oggi. Il verdetto lo dà `measurement_service` con utl/ltl del subtask, non `*_outcome` di VisionSuite. REGOLE PER OGNI UNITÀ: - Lavori solo nella tua cartella e sul tuo ramo. Tocchi solo i file elencati nella tua unità. Se serve toccarne un altro, non lo tocchi e lo scrivi nel rapporto (MANCA). - Ogni modifica porta un breve commento inline (cosa/perché), nello stile del file. Le stringhe UI vanno in `_()`; non toccare i cataloghi di traduzione. - Battito: prima di ogni passo, e comunque almeno una volta al minuto, aggiungi una riga a `C:/Users/Adriano/AppData/Local/Temp/claude/D--Sorgenti-AI-OS/aa35fa14-0bee-4ddf-9436-78539ec0ee61/scratchpad/heartbeat-<unità>.txt`: `echo "$(date +%H:%M:%S) sto facendo X" >> <file>`. Costa nulla ed è l'unico modo di distinguerti da un processo bloccato. - Budget: punta a ~60 chiamate. Se un problema resiste dopo tre tentativi, fermati e riportalo. Un pezzo dichiarato non fatto è un esito accettabile; un parziale committato vale più di un completo perso. - Committa presto e spesso sul tuo ramo, messaggi brevi in italiano con `(#46)`, e alla fine `git push -u origin <ramo>`. Ogni commit termina con la riga `Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>`. - Se Adriano o chi coordina ti scrive «stato», rispondi con una riga e continua il lavoro: non è un ordine di fermarti. - Rapporto finale in questo formato (campi vuoti omessi): ESITO: fatto|parziale|bloccato / RAMO + ultimo commit / FILE: path — modifica / PROVA: comando → risultato / MANCA: … / MODELLO: l'ID del modello dichiarato nel tuo prompt di sistema. ## SUDDIVISIONE Quattro unità con file disgiunti, in parallelo. Integrazione su lavoro/daitarn-46 e poi su V3.0.0 a cura del coordinatore (Opus), con prova d insieme e build Docker del worker su goldrake. UNITÀ 1 — motore (heartbeat-u1.txt) Cartella: D:/Sorgenti/lavoro/TMF-motore · Ramo: lavoro/daitarn-46-motore (da V3.0.0 a829bf3). FILE: vendor/visionsuite (submodule), pyproject.toml, uv.lock, src/vision/**, src/vision_worker/**, Dockerfile.vision, docker-compose.yml (solo il servizio `vision`), .env.example (solo righe del worker). NON TOCCARE: src/backend/**, src/frontend/**. Se un test di src/backend si rompe per il cambio di VisionSuite, NON correggerlo: scrivi nel rapporto quale e perché. DA FARE: 1. Submodule: `git -C vendor/visionsuite fetch origin && git -C vendor/visionsuite checkout bc0df16`, poi committa il nuovo pin. 2. pyproject.toml: in `[tool.uv.sources]` aggiungi vs-tools e vs-dxf (path editable sotto vendor/visionsuite/packages/, come gli altri). Extra `vision`: aggiungi `vs-tools[locate,dxf]` con lo stesso marker python>=3.13 degli altri. Poi `uv lock` e `uv sync --extra vision-worker --extra dev`. 3. src/vision/runner.py: `run_graph(image, graph, calibration=None, recipe_dir=None)` costruisce con `Session.from_dict(graph, recipe_dir or <cartella temporanea vuota>, calibration)` (leggi vendor/visionsuite/packages/vs-task/src/visionsuite/task/session.py per come si esegue). Restituisce, oltre a quello di oggi, `overlay` (lista dei dict overlay di tutti gli strumenti, con `tool_id` aggiunto) e `outcome` del report. `RecipeError` → `GraphBuildError` come oggi. Le uscite numeriche restano filtrate come oggi (float/int finiti, niente bool). Aggiungi `validate_graph(graph, recipe_dir=None) -> (ok, errors, outputs)` per `/validate` del contratto A: outputs = uscite numeriche dichiarate dagli strumenti del grafo (leggi `visionsuite.task.graph` per Property/Direction e come elencarle), `quantity`/`unit` se lo strumento le dichiara, altrimenti null. 4. src/vision_worker/main.py: `/run` accetta `recipe_dir` e `calibration` (contratto A), con controllo del percorso sotto `VISION_RECIPES_ROOT` (default `/app/uploads`). Calibrazione JSON → dict passato a run_graph (verifica in vs-tools tools/dxf.py e tools/_base.py che il dict con mm_per_px/px_per_mm sia accettato). Nuovo `/validate`. Risposta di `/run` con `overlay` e `outcome`. 5. Test: porta src/vision/tests e src/vision_worker/tests alla v2 (nomi nuovi, import da `visionsuite.tools.*`, `schema_version: 2`). Aggiungi test per: ricetta con DetectCircle/quote e ricetta con CompareDxf `anchor="two_points"` su immagine sintetica, con DXF di fixture (`vendor/visionsuite/packages/vs-dxf/tests/fixtures/sample.dxf`; prendi spunto da `vendor/visionsuite/tests/test_visione_nel_grafo.py`), `recipe_dir` + `meta.dxf`; overlay presente; `/validate` ok e con file mancante; `recipe_dir` con `..` → 422. 6. Dockerfile.vision: deve copiare e installare i pacchetti nuovi (vs-tools, vs-dxf). Qui Docker non c'è: controlla che i COPY coprano i path di `[tool.uv.sources]` e scrivi nel rapporto che la build va provata altrove. PROVA: `uv run pytest src/vision src/vision_worker -q` verde; poi `uv run pytest -q` intero, e riporta i fallimenti fuori dalla tua area senza correggerli. UNITÀ 2 — backend (heartbeat-u2.txt) Cartella: D:/Sorgenti/lavoro/TMF-backend · Ramo: lavoro/daitarn-46-backend (da V3.0.0 a829bf3). FILE: src/backend/** (api/routers/vision.py, services/vision_service.py, models/orm/task.py, models/orm/vision.py se serve, models/api/vision.py, services/recipe_service.py per il copy-on-write, migrations/versions/016_*.py, test nuovi o aggiornati). NON TOCCARE: src/vision/**, src/vision_worker/**, vendor/, pyproject.toml, uv.lock, src/frontend/**. Nei test il worker è finto (come in src/backend/tests/test_vision_worker_transport.py): non serve VisionSuite installato. DA FARE (contratto B; il worker risponde come nel contratto A): 1. Migrazione 016 `recipe_tasks.vision_recipe_dir` (su e giù) + campo ORM. 2. `POST` e `GET /api/vision/tasks/{task_id}/recipe` come da contratto B. Path traversal rifiutato (riusa il controllo di api/routers/files.py se adatto), limite di dimensione come gli altri upload. `.dxf`/`.npz`/`.json`/immagini ammessi solo qui: non allargare `/api/files/upload`. La cartella `<sha16>` è immutabile: se esiste già, non riscriverla. 3. `vision_service._call_worker` passa `recipe_dir` (e `calibration` se il task o la stazione ne hanno una; se oggi non esiste nessuna fonte, non passare nulla e scrivilo nel rapporto). Nuova funzione per `/validate`. 4. `execute_task`: ammette `camera_measure` e `dxf_compare`; salva immagine e overlay (contratto B) e riempie `image_path`/`overlay_path`; la risposta di `/execute` porta `outcome`, `overlay`, `image_url`, `failures`. Aggiungi `GET /api/vision/results/{id}/image`. 5. Copy-on-write della versione ricetta: il task copiato si porta dietro `vision_recipe_dir` e `vision_json` (la cartella è immutabile, basta il riferimento). Vedi services/recipe_service.py:117-127. 6. Verifica che `dxf_compare` sia trattato come task di misura dove serve (`MEASURING_TASK_TYPES` in models/orm/task.py:21 e i suoi usi). Se aggiungerlo rompe qualcosa, fermati e riporta. TEST: upload ricetta ok e 422 (file mancante, nome non abbinato, traversal, task di tipo sbagliato, worker che risponde ok=false); GET; execute `dxf_compare` con worker finto che restituisce overlay → file salvati, risposta estesa, misure con pass/fail sulle tolleranze del subtask; copy-on-write; migrazione 016 su e giù. PROVA: `uv run pytest src/backend -q` verde (riporta il conteggio prima e dopo). UNITÀ 3 — Maker (heartbeat-u3.txt) Cartella: D:/Sorgenti/lavoro/TMF-maker · Ramo: lavoro/daitarn-46-maker (da V3.0.0 a829bf3). FILE: src/frontend/flask_app/blueprints/maker.py, src/frontend/flask_app/templates/maker/** (task_editor.html in particolare), file statici nuovi con un nome tuo (es. static/js/vision_recipe.js), test frontend del Maker. NON TOCCARE: blueprints/measure.py, templates/measure/**, src/backend/**, app.py (se serve registrare qualcosa, scrivilo nel rapporto), cataloghi di traduzione. Il backend del contratto B non è ancora unito: lavora contro il contratto (nei test mocka `api_client` come fanno i test esistenti del frontend). DA FARE: 1. task_editor.html (righe ~276-280 e ~542-547): rendi selezionabili i tipi `camera_measure` («Misura camera») e `dxf_compare` («Confronto profilo»). 2. Per questi tipi, sezione «Ricetta visione»: caricamento del file .json della ricetta + N file allegati (DXF, modelli .npz, calibrazione) → route proxy in maker.py verso `POST /api/vision/tasks/{task_id}/recipe` (multipart; segui come maker.py fa già il proxy di `/api/upload`, riga ~347). Mostra cartella, file e uscite numeriche; gli errori 422 si elencano in chiaro. All'apertura, `GET` della ricetta corrente. 3. Per ogni subtask (quota) del task: select `vision_output` popolata dalle `outputs` della ricetta (key + quantity/unit), salvata con il meccanismo con cui il Maker salva già i subtask. Verifica che l'API dei subtask accetti `vision_output`; se non lo accetta, scrivilo nel rapporto e non toccare il backend. Le tolleranze del subtask restano quelle di oggi. 4. Stile e componenti come il resto del Maker (Alpine.js e Tailwind già presenti). PROVA: test frontend verdi (`uv run pytest src/frontend/flask_app/tests -q`), più un test della route proxy. UNITÀ 4 — operatore (heartbeat-u4.txt) Cartella: D:/Sorgenti/lavoro/TMF-operatore · Ramo: lavoro/daitarn-46-operatore (da V3.0.0 a829bf3). FILE: src/frontend/flask_app/blueprints/measure.py, src/frontend/flask_app/templates/measure/** (task_execute.html in particolare), file statici nuovi con un nome tuo (es. static/js/vision_execute.js), test frontend della misura. NON TOCCARE: blueprints/maker.py, templates/maker/**, src/backend/**, app.py, cataloghi di traduzione. Il backend del contratto B non è ancora unito: lavora contro il contratto (nei test mocka `api_client` come fanno quelli esistenti). DA FARE: 1. task_execute.html (intorno alla riga 1776 `camera_measure` è trattato come misura normale): per `camera_measure` e `dxf_compare` la schermata offre «Acquisisci immagine» con `<input type="file" accept="image/*" capture="environment">` (sul tablet apre la fotocamera, sul PC sceglie un file). La funzione JS che invia si chiama `acquire(blob)`, così in seguito la camera dell'agente (#3) la potrà chiamare con lo stesso blob. 2. Route proxy in measure.py verso `POST /api/vision/execute` (multipart: task_id, image, lot_number, serial_number, production_run_id come nel flusso di misura esistente) e verso `GET /api/vision/results/{id}/image`. 3. Esito: tabella quote (valore, pass/fail con i colori già usati per le misure), `outcome`, `failures` in chiaro. Immagine con l'overlay sopra (canvas o SVG): contorni `rows`/`cols` in px immagine, colori per `role` (reference, found, roi), scalati alla dimensione mostrata. Pulsante «Riprova» che rifà l'acquisizione. 4. Non cambiare i flussi già presenti in task_execute.html (avvio produzione, fine produzione, tracciabilità). Il difetto noto dei pulsanti Fermo linea/Fine produzione visibili prima di Avvio Produzione non si corregge qui. PROVA: test frontend verdi (`uv run pytest src/frontend/flask_app/tests -q`), più un test della route proxy di execute.
Author
Owner

Soluzione

ESITO: fatto
COSA: TMF esegue qualunque ricetta VisionSuite v2 nel worker vision (compreso CompareDxf con anchor two_points); il Maker carica ricetta e file, l'operatore acquisisce l'immagine e vede quote, esito e overlay.
FILE: vendor/visionsuite, pyproject.toml, uv.lock — submodule a bc0df16, vs-tools[locate,dxf] e vs-dxf
FILE: src/vision/runner.py — run_graph con recipe_dir, calibrazione, overlay e outcome; validate_graph
FILE: src/vision_worker/main.py — /run con recipe_dir sotto VISION_RECIPES_ROOT (assoluto, .. e lettera di unità rifiutati su ogni OS) e calibration; nuovo /validate
FILE: src/backend/api/routers/vision.py, src/backend/services/vision_service.py, src/backend/models/api/vision.py — POST/GET /api/vision/tasks/{id}/recipe, execute per camera_measure e dxf_compare con immagine e overlay salvati, GET /api/vision/results/{id}/image; il digest della cartella copre anche i file di meta
FILE: src/backend/models/orm/task.py, src/backend/services/recipe_service.py — vision_recipe_dir, copy-on-write, dxf_compare in MEASURING_TASK_TYPES
FILE: src/frontend/flask_app/static/js/vision_recipe.js, templates/maker/task_editor.html, blueprints/maker.py — sezione Ricetta visione, select vision_output per quota
FILE: src/frontend/flask_app/static/js/vision_execute.js, templates/measure/task_execute.html, blueprints/measure.py — acquire(blob), proxy execute e immagine, overlay per role, Riprova
MIGRAZIONE: 016_vision_recipe_dir
COMMIT: 9238f89 84a4b5f accf416 e640c4a 3856ee3 18d0db8 ee0f65e 8629027 fc1902a f986c17
PROVA: suite goldrake su b7cb061 → 835 passed 2 skipped (pytest 3.11 backend+frontend), 29 passed (pytest 3.13 vision), docker build vision ok; test src/vision/tests/test_runner.py, src/vision_worker/tests/test_worker.py, src/backend/tests/test_vision_recipe_upload.py, test_vision_execute.py, test_migrations.py, src/frontend/flask_app/tests/test_measure_vision.py, test_maker.py
DIFFERENZE DALL'ANALISI: Dockerfile.vision non modificato: COPY vendor/ copre già vs-tools e vs-dxf (build ok su goldrake). Prova a mano dal tablet con una ricetta reale NON registrata in questa issue: il criterio di fatto è coperto dai test (worker finto lato backend, DXF di fixture lato motore).
In V3.0.0 da b7cb061 (ramo lavoro/daitarn-trafilo unito in fast-forward da a829bf3).

**Soluzione** ESITO: fatto COSA: TMF esegue qualunque ricetta VisionSuite v2 nel worker vision (compreso CompareDxf con anchor two_points); il Maker carica ricetta e file, l'operatore acquisisce l'immagine e vede quote, esito e overlay. FILE: vendor/visionsuite, pyproject.toml, uv.lock — submodule a bc0df16, vs-tools[locate,dxf] e vs-dxf FILE: src/vision/runner.py — run_graph con recipe_dir, calibrazione, overlay e outcome; validate_graph FILE: src/vision_worker/main.py — /run con recipe_dir sotto VISION_RECIPES_ROOT (assoluto, .. e lettera di unità rifiutati su ogni OS) e calibration; nuovo /validate FILE: src/backend/api/routers/vision.py, src/backend/services/vision_service.py, src/backend/models/api/vision.py — POST/GET /api/vision/tasks/{id}/recipe, execute per camera_measure e dxf_compare con immagine e overlay salvati, GET /api/vision/results/{id}/image; il digest della cartella copre anche i file di meta FILE: src/backend/models/orm/task.py, src/backend/services/recipe_service.py — vision_recipe_dir, copy-on-write, dxf_compare in MEASURING_TASK_TYPES FILE: src/frontend/flask_app/static/js/vision_recipe.js, templates/maker/task_editor.html, blueprints/maker.py — sezione Ricetta visione, select vision_output per quota FILE: src/frontend/flask_app/static/js/vision_execute.js, templates/measure/task_execute.html, blueprints/measure.py — acquire(blob), proxy execute e immagine, overlay per role, Riprova MIGRAZIONE: 016_vision_recipe_dir COMMIT: 9238f89 84a4b5f accf416 e640c4a 3856ee3 18d0db8 ee0f65e 8629027 fc1902a f986c17 PROVA: suite goldrake su b7cb061 → 835 passed 2 skipped (pytest 3.11 backend+frontend), 29 passed (pytest 3.13 vision), docker build vision ok; test src/vision/tests/test_runner.py, src/vision_worker/tests/test_worker.py, src/backend/tests/test_vision_recipe_upload.py, test_vision_execute.py, test_migrations.py, src/frontend/flask_app/tests/test_measure_vision.py, test_maker.py DIFFERENZE DALL'ANALISI: Dockerfile.vision non modificato: COPY vendor/ copre già vs-tools e vs-dxf (build ok su goldrake). Prova a mano dal tablet con una ricetta reale NON registrata in questa issue: il criterio di fatto è coperto dai test (worker finto lato backend, DXF di fixture lato motore). In V3.0.0 da b7cb061 (ramo lavoro/daitarn-trafilo unito in fast-forward da a829bf3).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Adriano/TieMeasureFlow#46