diff --git a/src/delta_barth/constants.py b/src/delta_barth/constants.py index 0ba61ec..b168a9a 100644 --- a/src/delta_barth/constants.py +++ b/src/delta_barth/constants.py @@ -1,7 +1,7 @@ import enum from typing import Final -from delta_barth.types import HttpContentHeaders +from delta_barth.types import DualDict, HttpContentHeaders # ** error handling DEFAULT_INTERNAL_ERR_CODE: Final[int] = 100 @@ -20,13 +20,14 @@ class KnownDelBarApiErrorCodes(enum.Enum): # ** API response parsing # ** column mapping [API-Response --> Target-Features] -COL_MAP_SALES_PROGNOSIS: Final[dict[str, str]] = { - "artikelId": "artikel_refid", - "firmaId": "firma_refid", - "betrag": "betrag", - "menge": "menge", - "buchungsDatum": "buchungs_datum", -} +COL_MAP_SALES_PROGNOSIS: Final[DualDict[str, str]] = DualDict( + artikelId="artikel_refid", + firmaId="firma_refid", + betrag="betrag", + menge="menge", + buchungsDatum="buchungs_datum", +) + FEATURES_SALES_PROGNOSIS: Final[frozenset[str]] = frozenset( ( "firma_refid", @@ -34,3 +35,5 @@ FEATURES_SALES_PROGNOSIS: Final[frozenset[str]] = frozenset( "buchungs_datum", ) ) + +MIN_NUMBER_DATAPOINTS: Final[int] = 100 diff --git a/src/delta_barth/types.py b/src/delta_barth/types.py index 9399ff2..3b070e2 100644 --- a/src/delta_barth/types.py +++ b/src/delta_barth/types.py @@ -2,13 +2,51 @@ from __future__ import annotations import enum import typing as t +from collections.abc import Iterator, MutableMapping from dataclasses import dataclass, field import pandas as pd from pydantic import BaseModel, ConfigDict, SkipValidation +# ** Data Structures +K = t.TypeVar("K") +V = t.TypeVar("V") + + +class DualDict(MutableMapping[K, V]): + def __init__(self, **kwargs: V): + self._store: dict[K, V] = dict(**kwargs) + self._inverted = self._calc_inverted() + + @property + def inverted(self) -> dict[V, K]: + return self._inverted + + def _calc_inverted(self) -> dict[V, K]: + return {val: key for key, val in self.items()} + + def __setitem__(self, key: K, value: V) -> None: + self._store[key] = value + self._inverted[value] = key + + def __getitem__(self, key: K) -> V: + return self._store[key] + + def __delitem__(self, key: K) -> None: + value = self._store[key] + del self._store[key] + del self._inverted[value] + + def __iter__(self) -> Iterator[K]: + return iter(self._store) + + def __len__(self) -> int: + return len(self._store) + + # ** Pipeline state management StatusDescription: t.TypeAlias = tuple[str, int, str] +R = t.TypeVar("R", bound="ExportResponse") class IError(t.Protocol): @@ -24,6 +62,15 @@ class Status(BaseModel): api_server_error: SkipValidation[DelBarApiError | None] = None +class ResponseType(BaseModel): + pass + + +class ExportResponse(BaseModel): + response: ResponseType + status: Status + + @dataclass(slots=True) class DataPipeStates: SUCCESS: Status @@ -32,9 +79,35 @@ class DataPipeStates: @dataclass(slots=True) -class PipeResult: - status: Status +class PipeResult(t.Generic[R]): data: pd.DataFrame | None + status: Status + results: R | None = None + + def success( + self, + data: pd.DataFrame, + status: Status, + ) -> None: + self.data = data + self.status = status + self.results = None + + def fail( + self, + status: Status, + ) -> None: + self.data = None + self.status = status + self.results = None + + def export( + self, + response: R, + ) -> None: + self.data = None + self.status = response.status + self.results = response # ** API