feat: measurement workflow improvements and recipe update-in-place

- Auto-advance to next task after completing all subtask measurements
- 1s pause between measurements to show pass/fail/warning result
- Colored marker strip (green/red/amber) based on measurement status
- Replace duplicate measurements instead of appending (fixes progress bar)
- Add Task column and Date/Time column to measurement summary table
- Enrich summary with task_info for each measurement
- Update-in-place for recipe versions without measurements (no copy-on-write)
- Dark theme improvements and navbar cleanup
- Server config: ignore extra env vars

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Adriano
2026-02-09 19:36:42 +01:00
parent 4854966bf7
commit e8b88d48c1
9 changed files with 232 additions and 118 deletions
+42 -9
View File
@@ -121,11 +121,21 @@ def task_execute(task_id: int):
lot_number = session.get("lot_number", "")
serial_number = session.get("serial_number", "")
# Load all task IDs for this recipe (ordered) for auto-advance
recipe_id = task_resp.get("recipe_id")
all_task_ids = []
if recipe_id:
tasks_resp = api_client.get(f"/api/recipes/{recipe_id}/tasks")
if isinstance(tasks_resp, list):
sorted_tasks = sorted(tasks_resp, key=lambda t: t.get("order_index", 0))
all_task_ids = [t["id"] for t in sorted_tasks]
return render_template(
"measure/task_execute.html",
task=task_resp,
lot_number=lot_number,
serial_number=serial_number,
all_task_ids=all_task_ids,
)
@@ -150,18 +160,44 @@ def task_complete(recipe_id: int):
)
return redirect(url_for("measure.select_recipe"))
# Load tasks+subtasks for this recipe to build subtask and task lookup
tasks_resp = api_client.get(f"/api/recipes/{recipe_id}/tasks")
subtask_map = {}
subtask_task_map = {} # subtask_id → task info
if isinstance(tasks_resp, list):
for task in tasks_resp:
for st in task.get("subtasks", []):
subtask_map[st["id"]] = st
subtask_task_map[st["id"]] = {
"id": task["id"],
"title": task.get("title", ""),
"order_index": task.get("order_index", 0),
}
# Load measurements if version_id provided
measurements = []
if version_id:
meas_resp = api_client.get(
"/api/measurements",
params={"version_id": version_id},
params={"version_id": version_id, "per_page": 500},
)
if not (isinstance(meas_resp, dict) and meas_resp.get("error")):
measurements = (
raw = (
meas_resp if isinstance(meas_resp, list)
else meas_resp.get("items", [])
)
# Enrich each measurement with nested subtask data
for m in raw:
st = subtask_map.get(m.get("subtask_id"), {})
m["subtask"] = st
m["task_info"] = subtask_task_map.get(m.get("subtask_id"), {})
# Compute deviation if not present
if m.get("deviation") is None and st.get("nominal") is not None:
try:
m["deviation"] = m["value"] - st["nominal"]
except (TypeError, KeyError):
m["deviation"] = 0.0
measurements = raw
lot_number = session.get("lot_number", "")
serial_number = session.get("serial_number", "")
@@ -243,25 +279,22 @@ def save_measurement():
# Validate required fields
subtask_id = data.get("subtask_id")
task_id = data.get("task_id")
version_id = data.get("version_id")
value = data.get("value")
if subtask_id is None or task_id is None or value is None:
if subtask_id is None or version_id is None or value is None:
return jsonify({
"error": True,
"detail": _("Dati mancanti: subtask_id, task_id e value sono obbligatori"),
"detail": _("Dati mancanti: subtask_id, version_id e value sono obbligatori"),
}), 400
# Build payload for the FastAPI backend
payload = {
"subtask_id": subtask_id,
"task_id": task_id,
"version_id": version_id,
"value": value,
"pass_fail": data.get("pass_fail", ""),
"deviation": data.get("deviation"),
"lot_number": data.get("lot_number", session.get("lot_number", "")),
"serial_number": data.get("serial_number", session.get("serial_number", "")),
"measured_by": session.get("user_id"),
"input_method": data.get("input_method", "manual"),
}