Worker di visione configurabile per stazione (vision_worker_url) #45
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: il worker di visione si può far girare dove si vuole (PC di stazione o server). Il backend sceglie quale chiamare in base alla stazione. La ricetta resta sul server.
PERCHÉ: una camera su ogni PC di stazione per misura dimensionale o DXF compare, senza caricare il server e senza riscrivere il worker. Decisione di Adriano del 07/10/2026: versione minima, poco tempo per consegnare.
STATO VERIFICATO su origin/V3.0.0
a829bf3:vision_worker_urlè un URL unico globale (envVISION_WORKER_URL, docker-compose.yml:38)._call_worker(image_bytes, graph)fa POST a{settings.vision_worker_url}/run; chiamata a :213 (senza stazione) e a :380 (percorso constation_id, :300).VisionResultha giàengine_version,executed_on(server|station),station_id,device_code: la tracciabilità c'è.Station: nessun campo per il worker.DA FARE:
stations.vision_worker_urlString(500), nullable. Vuota = worker centrale disettings.vision_worker_url._call_workerriceve l'URL (o la stazione) invece di leggere solosettings. Il chiamante constation_idusa l'URL della stazione se c'è.executed_on=stationquando ha risposto il worker della stazione,serveraltrimenti. Si salva anche l'URL usato, in una colonnavision_results.worker_urlnullable (stessa migrazione).TEST: stazione con URL → la POST va a quell'URL; stazione senza URL → va al centrale;
executed_oneworker_urlscritti giusti; migrazione 016 su e giù.NON TOCCARE: src/vision_worker/ (stesso container, stesso contratto /run), src/vision/runner.py.
DOPO LA CONSEGNA (non in questa issue):
convert("L")in vision_worker, aperto da #43): solo se la camera scelta li produce.COLLEGATA: #3 (agente di stazione e camera).
CRITERIO DI FATTO: due stazioni con URL di worker diversi misurano la stessa ricetta; ogni riga di vision_results dice quale worker ha risposto.
ESITO: richiesta
COSA: numerazione migrazioni riassegnata il 07/10: 016 = #46, 017 = #59 (user_pin), 018 = #53/#54/#55 (task loop e task_results). La migrazione di questa issue diventa 019 (down_revision = la testa al momento del lavoro).
PROSSIMO: controllare
alembic headsprima di scrivere la migrazione.🔵 daitarn — preso in carico 2026-10-11T17:35:02Z
ramo:
lavoro/daitarn· capacità: build-windows, gpuESITO: richiesta
RIF: gitea:Adriano/TieMeasureFlow#45
COSA: analisi (passo 1 di 3) sul ramo lavoro/daitarn-45 da origin/V3.0.0
9306fbf. Stazione presa dalla sessione di produzione (production_run_id→ProductionRun.station_id), non da un nuovo parametro.VERIFICATO: migrazioni in testa → 018_task_loop_results (down 017_user_pin); la nuova è 019.
VERIFICATO:
execute_task(src/backend/services/vision_service.py:321) non hastation_id; riceveproduction_run_id;VisionResultoggi scritto con executed_on="server" e station_id assente (:435-444).VERIFICATO: i fake di
_call_workernei test accettano**kwargs→ un kwarg nuovo non li rompe.MODIFICHE:
stations.vision_worker_urlString(500) nullable;vision_results.worker_urlString(500) nullable. downgrade: drop di entrambe (batch_alter_table se lo usano le migrazioni vicine, per SQLite).Station.vision_worker_url: Mapped[Optional[str]]String(500). src/backend/models/orm/vision.pyVisionResult.worker_urlString(500) nullable.vision_worker_url: Optional[str](max_length 500) in StationCreate, StationUpdate, StationResponse. Validatore: stringa vuota/spazi → None; altrimenti deve iniziare con http:// o https:// (422 se no);rstrip("/"). src/backend/services/station_service.py create_station passa il campo (update già generico)._call_worker(..., worker_url: str | None = None):base = (worker_url or settings.vision_worker_url).rstrip("/"), POST a{base}/run. Nei detail di 502/504 aggiungere in coda({base})per dire quale worker non risponde; gli attesi esistenti di src/backend/tests/test_vision_worker_transport.py non si cambiano. Nessun fallback al centrale.execute_task: seproduction_run_idc'è, leggere la ProductionRun (e la sua Station);station_url = station.vision_worker_url or None. Passareworker_url=station_urla_call_worker. VisionResult:executed_on = "station" if station_url else "server",station_id = run.station_id(se run),worker_url = station_url or settings.vision_worker_url(URL effettivamente chiamato, senza "/" finale). Run inesistente: comportamento attuale (non si aggiunge una 404 nuova se oggi non c'è; se la run è già validata altrove riusare quella lettura)._(), voci in translations/it e en (.po) e compilate come fa il repo.NON TOCCARE: src/vision_worker/, src/vision/runner.py, vendor/visionsuite,
previewevalidate_recipe(il Maker compone sul worker centrale), docker-compose.yml.TEST:
_call_workerriceve worker_url della stazione, VisionResult executed_on=station, worker_url=URL stazione, station_id=stazione; stazione senza URL → worker_url None in chiamata, executed_on=server, worker_url=settings.vision_worker_url; senza run → server._call_workercon worker_url: httpx finto (stesso stile di test_vision_worker_transport.py) → POST a<url>/run; con ConnectError → 502 col nome dell'URL, una sola POST (nessun fallback).uv run --extra server --extra client --extra dev pytest src/backend/tests src/frontend/flask_app/tests -q -p no:cacheproviderverde.CRITERIO DI FATTO: come nel corpo dell'issue.
Soluzione
ESITO: fatto
RIF: gitea:Adriano/TieMeasureFlow#45
COSA: worker di visione per stazione. La stazione si ricava dalla sessione di produzione (production_run_id → ProductionRun.station_id). Se la stazione ha un URL si chiama quello, altrimenti il centrale settings.vision_worker_url. Nessun ripiego.
FILE: src/backend/migrations/versions/019_station_vision_worker.py — stations.vision_worker_url e vision_results.worker_url, String(500) nullable, down 018_task_loop_results
FILE: src/backend/models/orm/station.py, models/orm/vision.py — campi nuovi
FILE: src/backend/models/api/station.py — campo in Create, Update e Response; vuoto → null; deve iniziare con http(s), altrimenti 422; tolta la / finale
FILE: src/backend/services/station_service.py — create_station passa il campo
FILE: src/backend/services/vision_service.py — _call_worker(worker_url=...), l'URL compare nel detail di 502 e 504; execute_task scrive executed_on station|server, station_id e worker_url effettivo
FILE: src/frontend/flask_app/templates/admin/stations.html, translations it/en — campo «Worker visione (URL)»
COMMIT:
95d643d275bbd140eed5c631ba4e, mergeac8604esu V3.0.0PROVA: suite goldrake su lavoro/daitarn-45 (base
9306fbf= base del merge) → 848 passed 2 skipped (pytest 3.11 backend+frontend), 29 passed (pytest 3.13 vision), docker build vision ok. Test nuovi: src/backend/tests/test_vision_station_worker.py (6), test_stations_api.py (+3), test_migrations.py (+1 per la 019 su e giù), frontend test_admin_stations.py (+3)MANCA: preview e validate_recipe del Maker restano sul worker centrale (voluto). Migrazione 019 da applicare al deploy (alembic upgrade head). Criterio «due stazioni misurano la stessa ricetta» provato con un worker finto, non con due worker veri. Trappola: con i .mo compilati in locale (pybabel compile) 5 test frontend esistenti falliscono perché asseriscono stringhe in italiano; non dipende da questa issue.