diff --git a/src/backend/api/routers/measurements.py b/src/backend/api/routers/measurements.py index bb466d9..cc264e1 100644 --- a/src/backend/api/routers/measurements.py +++ b/src/backend/api/routers/measurements.py @@ -359,8 +359,11 @@ async def export_measurements_csv( # Format decimals with configured separator # #118: quota non misurata dalla visione -> NaN value_str = "NaN" if m.value is None else str(m.value).replace(".", decimal_separator) + # #124: quota NaN (value None) -> deviazione NaN, non vuota deviation_str = ( - str(m.deviation).replace(".", decimal_separator) + "NaN" + if m.value is None + else str(m.deviation).replace(".", decimal_separator) if m.deviation is not None else "" ) diff --git a/src/backend/services/production_export_service.py b/src/backend/services/production_export_service.py index c547f87..5298984 100644 --- a/src/backend/services/production_export_service.py +++ b/src/backend/services/production_export_service.py @@ -160,7 +160,7 @@ async def build_statistics_csv(db: AsyncSession, run: ProductionRun) -> str: neutralise_csv_cell(subtask.description) if subtask else "", number(subtask.nominal) if subtask else "", number(measurement.value) if measurement.value is not None else "NaN", # #118 - number(measurement.deviation), + "NaN" if measurement.value is None else number(measurement.deviation), # #124: quota NaN -> deviazione NaN measurement.pass_fail, number(subtask.ltl) if subtask else "", number(subtask.lwl) if subtask else "", diff --git a/src/backend/tests/test_nan_measurements.py b/src/backend/tests/test_nan_measurements.py index a0d43be..583f3f7 100644 --- a/src/backend/tests/test_nan_measurements.py +++ b/src/backend/tests/test_nan_measurements.py @@ -111,4 +111,4 @@ async def test_csv_export_writes_nan( row = by_id[str(nan.id)] assert row[header.index("value")] == "NaN" assert row[header.index("pass_fail")] == "fail" - assert row[header.index("deviation")] == "" + assert row[header.index("deviation")] == "NaN" # #124 diff --git a/src/backend/tests/test_production_runs.py b/src/backend/tests/test_production_runs.py index c76c326..c4b196a 100644 --- a/src/backend/tests/test_production_runs.py +++ b/src/backend/tests/test_production_runs.py @@ -836,3 +836,31 @@ async def test_close_without_measurements_writes_no_file( )).json() assert closed["statistics_path"] is None assert not (tmp_path / "statistics").exists() + + +async def test_statistics_csv_writes_nan_value_and_deviation( + client: AsyncClient, measurement_tec_user, admin_user, db_session, +): + """#124: una quota NaN (value NULL) esce NaN sia nel valore sia nella deviazione.""" + from src.backend.services import measurement_service, production_export_service + + station = await _station(db_session, admin_user.id, code="ST-NAN") + recipe = await _recipe_with_interval(db_session, admin_user.id, 10, code="REC-NAN") + opened = (await _open(client, measurement_tec_user, station, recipe)).json() + subtask = await _first_subtask(db_session, recipe.id) + await measurement_service.save_measurement( + db_session, subtask_id=subtask.id, version_id=opened["version_id"], + measured_by=measurement_tec_user.id, value=None, input_method="camera", + production_run_id=opened["id"], enforce_tolerance_gate=False, + ) + await db_session.commit() + run = (await db_session.execute( + select(ProductionRun).where(ProductionRun.id == opened["id"]) + )).scalar_one() + + text = await production_export_service.build_statistics_csv(db_session, run) + lines = [line for line in text.lstrip("").splitlines() if line.strip()] + header = lines[0].split(",") + row = lines[1].split(",") + assert row[header.index("value")] == "NaN" + assert row[header.index("deviation")] == "NaN"