fix(vision): il gate del fuori tolleranza vale per l'acquisizione, non per la quota
Un'acquisizione camera produce N quote nello stesso istante: non c'e "andare avanti" fra l'una e l'altra, quindi il gate sequenziale di save_measurement non deve scattare dentro il lotto. execute_task valuta pending_authorisation una sola volta per l'intera acquisizione (bloccando solo se il fail pendente non appartiene alle quote di questo task) e passa enforce_tolerance_gate=False a ogni save_measurement del lotto. Query delle quote ordinata per marker_number, cosi l'esito non dipende dall'ordine di ritorno di SQLite/MySQL. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014BBnuACZSCJqXrMYC3LUMU
This commit is contained in:
@@ -52,10 +52,15 @@ async def execute_task(
|
||||
detail="this task carries no vision graph",
|
||||
)
|
||||
|
||||
# Ordered by marker_number - the number the operator sees on the annotated
|
||||
# image and in reports (RecipeSubtask.__repr__, statistics.py, report_service.py
|
||||
# all key off it). Which quote is saved first must never be an accident of
|
||||
# how SQLite/MySQL happens to return rows.
|
||||
quotes = (await db.execute(
|
||||
select(RecipeSubtask)
|
||||
.where(RecipeSubtask.task_id == task.id)
|
||||
.where(RecipeSubtask.vision_output.is_not(None))
|
||||
.order_by(RecipeSubtask.marker_number)
|
||||
)).scalars().all()
|
||||
if not quotes:
|
||||
raise HTTPException(
|
||||
@@ -79,6 +84,28 @@ async def execute_task(
|
||||
),
|
||||
)
|
||||
|
||||
# Ruling R10: the gate is evaluated once per acquisition, not once per
|
||||
# quote. `save_measurement`'s own gate models "moving on" between
|
||||
# measurements taken one after another - a real later moment where the
|
||||
# operator could leave a bad reading behind. A camera acquisition produces
|
||||
# every quote of this task at the same instant: there is no "moving on"
|
||||
# between them, so applying the sequential rule inside the batch would
|
||||
# block on a fail this very request just saved. Check it once here,
|
||||
# against the whole set this acquisition is about to produce, and pass
|
||||
# enforce_tolerance_gate=False to every save_measurement call below.
|
||||
blocking = await measurement_service.pending_authorisation(
|
||||
db, version_id, measured_by, production_run_id,
|
||||
)
|
||||
quote_ids = {q.id for q in quotes}
|
||||
if blocking is not None and blocking.subtask_id not in quote_ids:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail=(
|
||||
"A measurement is out of tolerance and awaiting the supervisor: "
|
||||
"authorise it or measure that quote again before going on"
|
||||
),
|
||||
)
|
||||
|
||||
result = VisionResult(
|
||||
task_id=task.id,
|
||||
engine_version=report["engine_version"],
|
||||
@@ -102,6 +129,7 @@ async def execute_task(
|
||||
input_method="camera",
|
||||
production_run_id=production_run_id,
|
||||
vision_result_id=result.id,
|
||||
enforce_tolerance_gate=False,
|
||||
))
|
||||
|
||||
return result, saved
|
||||
|
||||
Reference in New Issue
Block a user