diff --git a/app/ml/__init__.py b/app/ml/__init__.py new file mode 100644 index 0000000..1a48763 --- /dev/null +++ b/app/ml/__init__.py @@ -0,0 +1,5 @@ +"""Machine-Learning-Grundbausteine für SillyHome Next.""" + +from app.ml.feature_store import FeatureStore, FeatureVector + +__all__ = ["FeatureStore", "FeatureVector"] \ No newline at end of file diff --git a/app/ml/feature_store.py b/app/ml/feature_store.py new file mode 100644 index 0000000..9e2540d --- /dev/null +++ b/app/ml/feature_store.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +from collections import defaultdict +from dataclasses import dataclass, field +from typing import Iterable + + +@dataclass(frozen=True) +class FeatureVector: + sensor_id: str + values: dict[str, float] + label: str | None = None + + +class FeatureStore: + def __init__(self) -> None: + self._vectors: dict[str, list[FeatureVector]] = defaultdict(list) + + def add(self, vector: FeatureVector) -> None: + self._vectors[vector.sensor_id].append(vector) + + def add_batch(self, vectors: Iterable[FeatureVector]) -> None: + for vector in vectors: + self.add(vector) + + def latest(self, sensor_id: str) -> FeatureVector | None: + series = self._vectors.get(sensor_id) + return series[-1] if series else None + + def all(self) -> list[FeatureVector]: + return [vector for vectors in self._vectors.values() for vector in vectors] \ No newline at end of file diff --git a/tests/ml/test_feature_store.py b/tests/ml/test_feature_store.py new file mode 100644 index 0000000..2d881ea --- /dev/null +++ b/tests/ml/test_feature_store.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +import pytest + +from app.ml.feature_store import FeatureStore, FeatureVector + + +def _vector(sensor_id: str, temperature: float, label: str | None = None) -> FeatureVector: + return FeatureVector(sensor_id=sensor_id, values={"temperature": temperature}, label=label) + + +def test_append_and_latest_returns_last_vector() -> None: + store = FeatureStore() + vectors = [_vector("sensor.living_room", 20.0), _vector("sensor.living_room", 21.5)] + for item in vectors: + store.add(item) + assert store.latest("sensor.living_room") == vectors[-1] + + +def test_latest_returns_none_when_empty() -> None: + store = FeatureStore() + assert store.latest("sensor.living_room") is None + + +def test_add_batch_appends_all_vectors() -> None: + store = FeatureStore() + vectors = [ + _vector("sensor.kitchen", 19.0), + _vector("sensor.kitchen", 20.0), + _vector("sensor.bathroom", 23.5), + ] + store.add_batch(vectors) + assert len(store.all()) == 3 + assert store.latest("sensor.kitchen").values["temperature"] == 20.0 + + +def test_different_sensors_are_stored_independently() -> None: + store = FeatureStore() + store.add(_vector("sensor.living_room", 21.0)) + store.add(_vector("sensor.bedroom", 18.5)) + assert store.latest("sensor.living_room").values["temperature"] == 21.0 + assert store.latest("sensor.bedroom").values["temperature"] == 18.5 \ No newline at end of file