"""Application service coordinating I/O, validation and prediction.""" from __future__ import annotations from dataclasses import dataclass from pathlib import Path import pandas as pd from .io import FrameReader from .model import PredictionModel from .validation import FrameValidator @dataclass(frozen=True) class DiagnosisResult: measurements: pd.DataFrame submission: pd.DataFrame diagnostics: pd.DataFrame warnings: tuple[str, ...] = () @property def engine_ids(self) -> list[str]: return self.measurements["engine_id"].drop_duplicates().astype(str).tolist() def engine_measurements(self, engine_id: str) -> pd.DataFrame: frame = self.measurements[self.measurements["engine_id"].eq(engine_id)] if frame.empty: raise KeyError(f"Unknown engine_id={engine_id!r}") return frame.reset_index(drop=True) def engine_diagnostics(self, engine_id: str) -> pd.DataFrame: frame = self.diagnostics[self.diagnostics["engine_id"].eq(engine_id)] if frame.empty: raise KeyError(f"Unknown engine_id={engine_id!r}") return frame.reset_index(drop=True) class DiagnosticService: """Constructor-injected use case; no Streamlit or global state dependencies.""" def __init__( self, *, reader: FrameReader, validator: FrameValidator, model: PredictionModel, ) -> None: self._reader = reader self._validator = validator self._model = model def diagnose_bytes(self, payload: bytes) -> DiagnosisResult: return self.diagnose_frame(self._reader.read_bytes(payload)) def diagnose_path(self, path: Path) -> DiagnosisResult: return self.diagnose_frame(self._reader.read_path(path)) def diagnose_frame(self, frame: pd.DataFrame) -> DiagnosisResult: validated = self._validator.validate(frame) predicted = self._model.predict(validated.frame) return DiagnosisResult( measurements=validated.frame, submission=predicted.submission, diagnostics=predicted.diagnostics, warnings=validated.warnings, )