Monitor: quota con soli NaN resta in elenco con media null e fuori tolleranza

Refs #118

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-28 21:30:11 +01:00
parent 06f308be6f
commit 17344aae13
3 changed files with 36 additions and 8 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ class MonitorSubtask(BaseModel):
description: str description: str
unit: str unit: str
count: int count: int
mean: float mean: Optional[float] = None # #118: None = solo quote NaN
ltl: Optional[float] = None ltl: Optional[float] = None
utl: Optional[float] = None utl: Optional[float] = None
# True when ltl <= mean <= utl (a missing limit does not constrain). # True when ltl <= mean <= utl (a missing limit does not constrain).
+9 -7
View File
@@ -96,19 +96,21 @@ async def list_open_runs(db: AsyncSession) -> list[MonitorRun]:
per_sub[row.subtask_id].append(row) per_sub[row.subtask_id].append(row)
subtasks = [] subtasks = []
for sid, group in sorted(per_sub.items()): for sid, group in sorted(per_sub.items()):
# #118: le quote NaN (value None) non entrano nella media; senza # #118: le quote NaN (value None) non entrano nella media; con soli
# nessun valore numerico la quota non ha una media da mostrare # NaN la quota resta in elenco con mean None ed e' fuori tolleranza
numeric = [float(g.value) for g in group if g.value is not None] numeric = [float(g.value) for g in group if g.value is not None]
if not numeric: mean = sum(numeric) / len(numeric) if numeric else None
continue
mean = sum(numeric) / len(numeric)
first = group[0] first = group[0]
subtasks.append(MonitorSubtask( subtasks.append(MonitorSubtask(
subtask_id=sid, description=first.description, unit=first.unit, subtask_id=sid, description=first.description, unit=first.unit,
count=len(group), mean=round(mean, 6), count=len(group),
mean=round(mean, 6) if mean is not None else None, # #118
ltl=float(first.ltl) if first.ltl is not None else None, ltl=float(first.ltl) if first.ltl is not None else None,
utl=float(first.utl) if first.utl is not None else None, utl=float(first.utl) if first.utl is not None else None,
in_tolerance=_in_tolerance(mean, first.ltl, first.utl), in_tolerance=(
_in_tolerance(mean, first.ltl, first.utl)
if mean is not None else False # #118: tutte NaN = fail
),
)) ))
timer = production_service.describe(run) timer = production_service.describe(run)
ev = last_event.get(run.id) ev = last_event.get(run.id)
+26
View File
@@ -81,3 +81,29 @@ async def test_open_lists_only_open_runs_with_means(client, db_session, admin_us
async def test_open_empty_for_admin(client, admin_user): async def test_open_empty_for_admin(client, admin_user):
resp = await client.get("/api/production-runs/open", headers=auth_headers(admin_user)) resp = await client.get("/api/production-runs/open", headers=auth_headers(admin_user))
assert resp.status_code == 200 and resp.json() == [] assert resp.status_code == 200 and resp.json() == []
async def test_nan_quotes_in_the_monitor(client, db_session, admin_user):
"""#118: solo NaN -> la quota resta, mean null e fuori tolleranza; mista -> media dei numeri."""
sup = await _create_user(db_session, "capo", roles=["Supervisor"])
r1, s1, v1 = await _run(db_session, admin_user, "N1")
r2, s2, v2 = await _run(db_session, admin_user, "N2")
for i in range(2):
db_session.add(_m(r1, s1, v1, admin_user, None, "fail", i))
for i, (val, pf) in enumerate([(None, "fail"), (10.0, "pass"), (10.2, "pass")]):
db_session.add(_m(r2, s2, v2, admin_user, val, pf, i))
await db_session.commit()
resp = await client.get("/api/production-runs/open", headers=auth_headers(sup))
assert resp.status_code == 200
body = {r["station_code"]: r for r in resp.json()}
only_nan = body["ST-N1"]["subtasks"]
assert len(only_nan) == 1
assert only_nan[0]["mean"] is None
assert only_nan[0]["count"] == 2
assert only_nan[0]["in_tolerance"] is False
assert body["ST-N1"]["fail_count"] == 2
mixed = body["ST-N2"]["subtasks"][0]
assert mixed["count"] == 3
assert round(mixed["mean"], 6) == 10.1
assert mixed["in_tolerance"] is True