Visione: quota non prodotta salvata come NaN (value NULL, fail) invece del 422; migrazione 022
Refs #118 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
"""#118: una quota non misurata dalla visione e' una misura con value NULL (NaN), sempre fail.
|
||||
|
||||
Il valore NULL non deve far cadere statistiche, report, CSV e liste, e non entra
|
||||
nei calcoli numerici (medie, indici, istogramma); conta solo nell'esito.
|
||||
"""
|
||||
import csv
|
||||
|
||||
from httpx import AsyncClient
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from src.backend.models.orm.measurement import Measurement
|
||||
from src.backend.models.orm.user import User
|
||||
from src.backend.services import measurement_service
|
||||
from src.backend.tests.conftest import auth_headers, create_test_recipe
|
||||
from src.backend.tests.test_statistics import _seed_measurements
|
||||
|
||||
|
||||
async def _recipe_with_nan(client, db_session, tec_user):
|
||||
"""Una ricetta con tre misure numeriche e una NaN (value NULL, fail)."""
|
||||
recipe = await create_test_recipe(db_session, tec_user.id)
|
||||
subtask_id, version_id = await _seed_measurements(
|
||||
client, tec_user, recipe.id, [10.0, 10.1, 9.9],
|
||||
)
|
||||
nan = await measurement_service.save_measurement(
|
||||
db_session, subtask_id=subtask_id, version_id=version_id,
|
||||
measured_by=tec_user.id, value=None, input_method="camera",
|
||||
enforce_tolerance_gate=False,
|
||||
)
|
||||
await db_session.commit()
|
||||
return recipe, subtask_id, version_id, nan
|
||||
|
||||
|
||||
async def test_save_measurement_none_is_fail_without_deviation(
|
||||
client: AsyncClient, db_session: AsyncSession, measurement_tec_user: User,
|
||||
):
|
||||
_, _, _, nan = await _recipe_with_nan(client, db_session, measurement_tec_user)
|
||||
assert nan.value is None
|
||||
assert nan.pass_fail == "fail"
|
||||
assert nan.deviation is None
|
||||
|
||||
|
||||
async def test_manual_create_still_requires_a_value(
|
||||
client: AsyncClient, db_session: AsyncSession, measurement_tec_user: User,
|
||||
):
|
||||
"""Solo il percorso visione puo' produrre NaN: la creazione manuale resta float."""
|
||||
recipe = await create_test_recipe(db_session, measurement_tec_user.id)
|
||||
subtask_id, version_id = await _seed_measurements(
|
||||
client, measurement_tec_user, recipe.id, [],
|
||||
)
|
||||
resp = await client.post(
|
||||
"/api/measurements/", headers=auth_headers(measurement_tec_user),
|
||||
json={"subtask_id": subtask_id, "version_id": version_id, "value": None},
|
||||
)
|
||||
assert resp.status_code == 422
|
||||
|
||||
|
||||
async def test_statistics_skip_the_nan_measurement(
|
||||
client: AsyncClient, db_session: AsyncSession,
|
||||
measurement_tec_user: User, metrologist_user: User,
|
||||
):
|
||||
recipe, subtask_id, _, _ = await _recipe_with_nan(
|
||||
client, db_session, measurement_tec_user,
|
||||
)
|
||||
params = {"recipe_id": recipe.id, "subtask_id": subtask_id}
|
||||
headers = auth_headers(metrologist_user)
|
||||
|
||||
cap = await client.get("/api/statistics/capability", headers=headers, params=params)
|
||||
assert cap.status_code == 200
|
||||
assert cap.json()["n"] == 3
|
||||
assert abs(cap.json()["mean"] - 10.0) < 1e-9
|
||||
|
||||
chart = await client.get("/api/statistics/control-chart", headers=headers, params=params)
|
||||
assert chart.status_code == 200
|
||||
assert len(chart.json()["values"]) == 3
|
||||
|
||||
hist = await client.get("/api/statistics/histogram", headers=headers, params=params)
|
||||
assert hist.status_code == 200
|
||||
|
||||
summary = await client.get(
|
||||
"/api/statistics/summary", headers=headers, params={"recipe_id": recipe.id},
|
||||
)
|
||||
assert summary.status_code == 200
|
||||
|
||||
|
||||
async def test_measurement_list_returns_null_value(
|
||||
client: AsyncClient, db_session: AsyncSession, measurement_tec_user: User,
|
||||
):
|
||||
_, _, version_id, nan = await _recipe_with_nan(client, db_session, measurement_tec_user)
|
||||
resp = await client.get(
|
||||
"/api/measurements/", headers=auth_headers(measurement_tec_user),
|
||||
params={"version_id": version_id, "per_page": 50},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
rows = {m["id"]: m for m in resp.json()["items"]}
|
||||
assert rows[nan.id]["value"] is None
|
||||
assert rows[nan.id]["pass_fail"] == "fail"
|
||||
assert rows[nan.id]["deviation"] is None
|
||||
|
||||
|
||||
async def test_csv_export_writes_nan(
|
||||
client: AsyncClient, db_session: AsyncSession, measurement_tec_user: User,
|
||||
):
|
||||
_, _, _, nan = await _recipe_with_nan(client, db_session, measurement_tec_user)
|
||||
resp = await client.get(
|
||||
"/api/measurements/export/csv", headers=auth_headers(measurement_tec_user),
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
rows = list(csv.reader(resp.text.splitlines()))
|
||||
header = rows[0]
|
||||
by_id = {r[header.index("id")]: r for r in rows[1:]}
|
||||
row = by_id[str(nan.id)]
|
||||
assert row[header.index("value")] == "NaN"
|
||||
assert row[header.index("pass_fail")] == "fail"
|
||||
assert row[header.index("deviation")] == ""
|
||||
Reference in New Issue
Block a user