"""Measurement service - pass/fail calculation, data storage.""" from decimal import Decimal from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from src.backend.models.orm.measurement import Measurement from src.backend.models.orm.task import RecipeSubtask def calculate_pass_fail( value: float, subtask: RecipeSubtask ) -> tuple[str, float | None]: """Calculate pass/fail status and deviation based on tolerances. Returns (status, deviation) where status is 'pass', 'warning', or 'fail'. Logic: - If value is outside UTL/LTL -> 'fail' - If value is outside UWL/LWL but inside UTL/LTL -> 'warning' - Otherwise -> 'pass' """ deviation = None if subtask.nominal is not None: deviation = float(Decimal(str(value)) - Decimal(str(subtask.nominal))) # Check fail (outside tolerance limits) if subtask.utl is not None and value > float(subtask.utl): return "fail", deviation if subtask.ltl is not None and value < float(subtask.ltl): return "fail", deviation # Check warning (outside warning limits) if subtask.uwl is not None and value > float(subtask.uwl): return "warning", deviation if subtask.lwl is not None and value < float(subtask.lwl): return "warning", deviation return "pass", deviation async def save_measurement( db: AsyncSession, subtask_id: int, version_id: int, measured_by: int, value: float, lot_number: str | None = None, serial_number: str | None = None, input_method: str = "manual", ) -> Measurement: """Save a single measurement with auto-calculated pass/fail.""" # Get subtask for tolerance values result = await db.execute( select(RecipeSubtask).where(RecipeSubtask.id == subtask_id) ) subtask = result.scalar_one_or_none() if subtask is None: raise ValueError(f"Subtask {subtask_id} not found") pass_fail, deviation = calculate_pass_fail(value, subtask) measurement = Measurement( subtask_id=subtask_id, version_id=version_id, measured_by=measured_by, value=value, pass_fail=pass_fail, deviation=deviation, lot_number=lot_number, serial_number=serial_number, input_method=input_method, ) db.add(measurement) await db.flush() await db.refresh(measurement) return measurement