ML-002: Trainingspipeline mit Tainted-Data-Check

This commit is contained in:
2026-06-11 00:21:21 +02:00
parent 627ee03230
commit 24be7a4f11
3 changed files with 88 additions and 3 deletions

View File

@@ -1,5 +1,5 @@
"""Machine-Learning-Grundbausteine für SillyHome Next."""
__all__ = ["FeatureStore", "FeatureVector"]
from app.ml.feature_store import FeatureStore, FeatureVector
__all__ = ["FeatureStore", "FeatureVector"]
from app.ml.training import TrainedArtifact, TrainingPipeline

37
app/ml/training.py Normal file
View File

@@ -0,0 +1,37 @@
from __future__ import annotations
import logging
from dataclasses import dataclass
from typing import Sequence
from app.ml.feature_store import FeatureVector, FeatureStore
logger = logging.getLogger(__name__)
@dataclass
class TrainedArtifact:
artifact_id: str
supported_sensors: tuple[str, ...]
class TrainingPipeline:
def __init__(self, store: FeatureStore) -> None:
self._store = store
self._artifacts: dict[str, TrainedArtifact] = {}
def run(self, artifact_id: str) -> TrainedArtifact:
vectors = self._store.all()
if not vectors:
raise ValueError("FeatureStore enthält keine Trainingsdaten.")
sensors = tuple({vector.sensor_id for vector in vectors})
artifact = TrainedArtifact(artifact_id=artifact_id, supported_sensors=sensors)
self._artifacts[artifact_id] = artifact
logger.info("Training abgeschlossen für %s mit %d Sensoren", artifact_id, len(sensors))
return artifact
def export(self, artifact_id: str) -> TrainedArtifact:
if artifact_id not in self._artifacts:
raise KeyError(f"Artifact '{artifact_id}' nicht gefunden.")
return self._artifacts[artifact_id]

48
tests/ml/test_training.py Normal file
View File

@@ -0,0 +1,48 @@
from __future__ import annotations
import pytest
from app.ml.feature_store import FeatureStore, FeatureVector
from app.ml.training import TrainingPipeline, TrainedArtifact
def _vector(sensor_id: str, temperature: float, label: str | None = None) -> FeatureVector:
return FeatureVector(sensor_id=sensor_id, values={"temperature": temperature}, label=label)
def store_with_data() -> TrainingPipeline:
store = FeatureStore()
store.add_batch(
[
_vector("sensor.kitchen", 19.0),
_vector("sensor.kitchen", 20.0),
_vector("sensor.bedroom", 18.5),
]
)
return TrainingPipeline(store)
def test_run_returns_trained_artifact() -> None:
pipeline = store_with_data()
artifact = pipeline.run("artifact_v1")
assert artifact.artifact_id == "artifact_v1"
assert artifact.supported_sensors == ("sensor.bedroom", "sensor.kitchen")
def test_run_without_data_raises_value_error() -> None:
pipeline = TrainingPipeline(FeatureStore())
with pytest.raises(ValueError):
pipeline.run("artifact_v1")
def test_export_returns_registered_artifact() -> None:
pipeline = store_with_data()
pipeline.run("artifact_v1")
exported = pipeline.export("artifact_v1")
assert exported == pipeline.export("artifact_v1")
def test_export_missing_artifact_raises_key_error() -> None:
pipeline = store_with_data()
with pytest.raises(KeyError):
pipeline.export("artifact_v1")