Monitor stazioni: GET /api/production-runs/open (#57)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
"""GET /api/production-runs/open - live monitor of the open runs (#57)."""
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
from src.backend.models.orm.measurement import Measurement
|
||||
from src.backend.models.orm.production import ProductionEvent, ProductionRun
|
||||
from src.backend.models.orm.station import Station
|
||||
from src.backend.models.orm.task import RecipeSubtask, RecipeTask
|
||||
from src.backend.tests.conftest import _create_user, auth_headers, create_test_recipe
|
||||
|
||||
|
||||
async def _run(db, user, code, status="running", overdue=False, open_=True):
|
||||
"""A station plus a run on a fresh recipe (limits ltl 9.5 / utl 10.5)."""
|
||||
station = Station(code=f"ST-{code}", name=f"Linea {code}", active=True, created_by=user.id)
|
||||
db.add(station)
|
||||
recipe = await create_test_recipe(db, user_id=user.id, code=f"REC-{code}")
|
||||
await db.flush()
|
||||
version = recipe.versions[0]
|
||||
now = datetime.utcnow()
|
||||
run = ProductionRun(
|
||||
station_id=station.id, recipe_id=recipe.id, version_id=version.id,
|
||||
operator_id=user.id, status=status if open_ else "closed",
|
||||
started_at=now - timedelta(hours=1),
|
||||
measurement_interval_minutes=30,
|
||||
next_measurement_at=now + (timedelta(minutes=-5) if overdue else timedelta(minutes=20)),
|
||||
cycle_count=3,
|
||||
active_station_id=station.id if open_ else None,
|
||||
)
|
||||
db.add(run)
|
||||
await db.flush()
|
||||
db.add(ProductionEvent(run_id=run.id, event_type="start", user_id=user.id))
|
||||
sub = (await db.execute(
|
||||
select(RecipeSubtask).join(RecipeTask, RecipeTask.id == RecipeSubtask.task_id)
|
||||
.where(RecipeTask.version_id == version.id)
|
||||
)).scalars().first()
|
||||
return run, sub, version
|
||||
|
||||
|
||||
def _m(run, sub, version, user, value, pf, minutes=0):
|
||||
return Measurement(
|
||||
subtask_id=sub.id, version_id=version.id, measured_by=user.id, value=value,
|
||||
pass_fail=pf, production_run_id=run.id,
|
||||
measured_at=datetime.utcnow() - timedelta(minutes=minutes),
|
||||
)
|
||||
|
||||
|
||||
async def test_open_requires_high_authority(client, db_session, measurement_tec_user):
|
||||
resp = await client.get("/api/production-runs/open", headers=auth_headers(measurement_tec_user))
|
||||
assert resp.status_code == 403
|
||||
|
||||
|
||||
async def test_open_lists_only_open_runs_with_means(client, db_session, admin_user):
|
||||
sup = await _create_user(db_session, "capo", roles=["Supervisor"])
|
||||
r1, s1, v1 = await _run(db_session, admin_user, "A", overdue=True)
|
||||
r2, s2, v2 = await _run(db_session, admin_user, "B", status="paused")
|
||||
await _run(db_session, admin_user, "C", open_=False)
|
||||
# Run A: mean 10.0 (inside), run B: mean 10.8 (outside, one fail).
|
||||
for i, (val, pf) in enumerate([(9.9, "pass"), (10.1, "pass")]):
|
||||
db_session.add(_m(r1, s1, v1, admin_user, val, pf, i))
|
||||
for i, (val, pf) in enumerate([(10.6, "fail"), (11.0, "fail"), (10.8, "warning")]):
|
||||
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()}
|
||||
assert set(body) == {"ST-A", "ST-B"} # closed run C is absent
|
||||
a, b = body["ST-A"], body["ST-B"]
|
||||
assert a["overdue"] is True and a["seconds_to_next_measurement"] < 0
|
||||
assert b["overdue"] is False and b["status"] == "paused"
|
||||
assert a["duration_seconds"] >= 3600 and a["cycle_count"] == 3
|
||||
assert a["last_event"]["event_type"] == "start"
|
||||
assert a["pass_count"] == 2 and a["fail_count"] == 0
|
||||
assert a["subtasks"][0]["mean"] == 10.0 and a["subtasks"][0]["in_tolerance"] is True
|
||||
assert b["fail_count"] == 2 and b["warning_count"] == 1
|
||||
assert round(b["subtasks"][0]["mean"], 3) == 10.8
|
||||
assert b["subtasks"][0]["in_tolerance"] is False
|
||||
|
||||
|
||||
async def test_open_empty_for_admin(client, admin_user):
|
||||
resp = await client.get("/api/production-runs/open", headers=auth_headers(admin_user))
|
||||
assert resp.status_code == 200 and resp.json() == []
|
||||
Reference in New Issue
Block a user