DXF compare: task dxf_compare con CompareDxf (VisionSuite) nel worker vision #46
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
COSA: task
dxf_comparein TieMeasureFlow. Il pezzo fotografato si confronta col disegno DXF tramiteCompareDxfdi VisionSuite, che gira nel worker Dockervision. 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):
/api/vision/execute;point_a= origine del disegno,point_a→point_b= angolo). Niente MatchPattern;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).
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_bcollegati 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):
vs-tools(entry pointvisionsuite.task.tools, extradxf= vs-dxf,locate= vs-pm2d; si installavs-tools[locate,dxf]= tutta la suite);load_tools()li registra, senza import espliciti. Dal contesto gli strumenti leggono soloimage,calibration,edge_shift_px,meta,recipe_dir(etemplates, non supportato in TMF: MatchPattern usamodel=.npzinmeta.models);CompareDxf(packages/vs-tools/src/visionsuite/tools/dxf.py): DXF dameta.dxf[<key>].pathrelativo arecipe_dir, oppuredxf_path; scala damm_per_pxo dacontext["calibration"]; uscite floatpoints_in_pct,distance_avg,distance_min,distance_max, intn_points, stringhe*_outcome/outcome; overlay = lista{"type":"contour","role":"reference"|"roi"|"found","rows":[...],"cols":[...]}in px immagine;schema_version: 2(la v1 viene rifiutata, nessuna migrazione);Task.from_dict(data, *, recipe_dir=None, draft=False)sollevaRecipeError(sottoclasse di ValueError);Session.from_dict(data, recipe_dir, calibration=None, draft=False)mette nel contestorecipe_dir,metaecalibration(packages/vs-task/src/visionsuite/task/session.py:89);visionsuite.task.toolsnon esiste più.CONTRATTO A — worker
vision(src/vision_worker/main.py), HTTP interno:POST /runmultipart:image(file),graph(str JSON v2),recipe_dir(str opzionale: percorso RELATIVO sotto la radiceVISION_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_mse le altre già presenti) piùoverlay(lista di dict come sopra, contool_idaggiunto a ogni elemento; lista vuota se nessuno strumento ne produce) eoutcome(str o null, quello del TaskReport).POST /validatemultipart: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}]}.outputselenca solo le uscite numeriche (float/int, non bool) degli strumenti. Validazione non in bozza: un file dimetamancante è un errore.CONTRATTO B — backend (prefisso /api/vision, src/backend/api/routers/vision.py):
recipe_tasks.vision_recipe_dirString(500) nullable, percorso relativo sottosettings.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 alpathdi una voce qualunque dimeta(meta.dxf.*,meta.models.*,meta.calibration, …: ogni dict con chiavepath); nome base mancante o non abbinato → 422 con l'elenco. Salvataggio inuploads/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: seok=false→ 422 conerrors, etask.vision_json/vision_recipe_dirnon 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 tipocamera_measureodxf_compare(altrimenti 422).GET /api/vision/tasks/{task_id}/recipe(require_maker):{"task_id", "recipe_dir": str|null, "recipe": dict|null, "files": [str], "outputs": [...]}, conoutputsletto dal worker/validate([] se manca la ricetta).POST /api/vision/execute: ingressi invariati. Accettatask_typein (camera_measure,dxf_compare). Passa al workerrecipe_dir=task.vision_recipe_dir. Salva l'immagine inuploads/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.RecipeSubtask.vision_output= unakeydioutputs, come oggi. Il verdetto lo dàmeasurement_servicecon utl/ltl del subtask, non*_outcomedi VisionSuite.REGOLE PER OGNI UNITÀ:
_(); non toccare i cataloghi di traduzione.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.(#46), e alla finegit push -u origin <ramo>. Ogni commit termina con la rigaCo-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>.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:
git -C vendor/visionsuite fetch origin && git -C vendor/visionsuite checkout bc0df16, poi committa il nuovo pin.[tool.uv.sources]aggiungi vs-tools e vs-dxf (path editable sotto vendor/visionsuite/packages/, come gli altri). Extravision: aggiungivs-tools[locate,dxf]con lo stesso marker python>=3.13 degli altri. Poiuv lockeuv sync --extra vision-worker --extra dev.run_graph(image, graph, calibration=None, recipe_dir=None)costruisce conSession.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, contool_idaggiunto) eoutcomedel report.RecipeError→GraphBuildErrorcome oggi. Le uscite numeriche restano filtrate come oggi (float/int finiti, niente bool). Aggiungivalidate_graph(graph, recipe_dir=None) -> (ok, errors, outputs)per/validatedel contratto A: outputs = uscite numeriche dichiarate dagli strumenti del grafo (leggivisionsuite.task.graphper Property/Direction e come elencarle),quantity/unitse lo strumento le dichiara, altrimenti null./runaccettarecipe_direcalibration(contratto A), con controllo del percorso sottoVISION_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/runconoverlayeoutcome.visionsuite.tools.*,schema_version: 2). Aggiungi test per: ricetta con DetectCircle/quote e ricetta con CompareDxfanchor="two_points"su immagine sintetica, con DXF di fixture (vendor/visionsuite/packages/vs-dxf/tests/fixtures/sample.dxf; prendi spunto davendor/visionsuite/tests/test_visione_nel_grafo.py),recipe_dir+meta.dxf; overlay presente;/validateok e con file mancante;recipe_dircon..→ 422.[tool.uv.sources]e scrivi nel rapporto che la build va provata altrove.PROVA:
uv run pytest src/vision src/vision_worker -qverde; poiuv run pytest -qintero, 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):
recipe_tasks.vision_recipe_dir(su e giù) + campo ORM.POSTeGET /api/vision/tasks/{task_id}/recipecome 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.vision_service._call_workerpassarecipe_dir(ecalibrationse 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.execute_task: ammettecamera_measureedxf_compare; salva immagine e overlay (contratto B) e riempieimage_path/overlay_path; la risposta di/executeportaoutcome,overlay,image_url,failures. AggiungiGET /api/vision/results/{id}/image.vision_recipe_direvision_json(la cartella è immutabile, basta il riferimento). Vedi services/recipe_service.py:117-127.dxf_comparesia trattato come task di misura dove serve (MEASURING_TASK_TYPESin 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_comparecon 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 -qverde (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_clientcome fanno i test esistenti del frontend).DA FARE:
camera_measure(«Misura camera») edxf_compare(«Confronto profilo»).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,GETdella ricetta corrente.vision_outputpopolata dalleoutputsdella ricetta (key + quantity/unit), salvata con il meccanismo con cui il Maker salva già i subtask. Verifica che l'API dei subtask accettivision_output; se non lo accetta, scrivilo nel rapporto e non toccare il backend. Le tolleranze del subtask restano quelle di oggi.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_clientcome fanno quelli esistenti).DA FARE:
camera_measureè trattato come misura normale): percamera_measureedxf_comparela 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 chiamaacquire(blob), così in seguito la camera dell'agente (#3) la potrà chiamare con lo stesso blob.POST /api/vision/execute(multipart: task_id, image, lot_number, serial_number, production_run_id come nel flusso di misura esistente) e versoGET /api/vision/results/{id}/image.outcome,failuresin chiaro. Immagine con l'overlay sopra (canvas o SVG): contornirows/colsin px immagine, colori perrole(reference, found, roi), scalati alla dimensione mostrata. Pulsante «Riprova» che rifà l'acquisizione.PROVA: test frontend verdi (
uv run pytest src/frontend/flask_app/tests -q), più un test della route proxy di execute.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:
9238f8984a4b5faccf416e640c4a3856ee318d0db8ee0f65e8629027fc1902af986c17PROVA: 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.pyDIFFERENZE 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 daa829bf3).