feat(measure): proxy JSON per il progresso di un task (#37.8, parziale)

GET /measure/api/task-progress/<task_id>: il browser non puo' chiamare
/api/measurements/task-progress del backend (niente X-API-Key). Login +
stesso controllo di stazione di task_execute() (_recipe_allowed_at_station),
poi inoltra a _task_progress() con lo stesso version_id/production_run_id
gia' usato da task_list.html.

Non tocca task_execute.html: /api/measurements/task-progress risponde
con conteggi aggregati per task (quotes/measured/state), non con le
quote-per-quota misurate (subtask_id, value, pass_fail, deviation) che
`this.measurements` richiede - ripristinarlo avrebbe richiesto inventare
valori o uno stato non offerto dal backend. MANCA: mostrato in dettaglio
nel rapporto finale.

Test Flask della route: riga per il task richiesto, default a "empty"
se assente, 403 fuori stazione, propagazione 404, login richiesto.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-21 23:36:42 +02:00
parent b27d56d4f7
commit cc686129c0
2 changed files with 113 additions and 0 deletions
@@ -565,6 +565,38 @@ def save_measurement():
return jsonify(resp), 201
# ---------------------------------------------------------------------------
# Route: task progress (#37.8) - browser cannot call the backend directly
# (X-API-Key), same as every other /api/* proxy in this file.
# ---------------------------------------------------------------------------
@measure_bp.route("/api/task-progress/<int:task_id>", methods=["GET"])
@login_required
@role_required("MeasurementTec")
def api_task_progress(task_id: int):
"""Proxy: how many quotes of this task are already measured.
Reuses _task_progress() - same version_id/production_run_id scoping as
task_list.html's badges - so this answers with exactly the counts
(quotes/measured/state) the backend can give without conflating one
cycle with another; it does not, and cannot, say which quotes or with
what values (task-progress is an aggregate, not a per-subtask list).
"""
task_resp = api_client.get(f"/api/tasks/{task_id}")
if isinstance(task_resp, dict) and task_resp.get("error"):
return jsonify(task_resp), task_resp.get("status_code", 500)
recipe_id = task_resp.get("recipe_id")
if recipe_id and not _recipe_allowed_at_station(recipe_id):
abort(403, description=_("Ricetta non assegnata a questa stazione"))
version_id = task_resp.get("version_id")
progress = _task_progress(version_id, _current_run_id())
row = progress.get(task_id, {
"task_id": task_id, "quotes": 0, "measured": 0, "state": "empty",
})
return jsonify(row), 200
# ---------------------------------------------------------------------------
# Routes: Out-of-tolerance authorisation (point 5)
# ---------------------------------------------------------------------------