adding result objects, renaming components

This commit is contained in:
Florian Förster 2025-03-05 14:35:29 +01:00
parent ec63840ccc
commit c375d8e9d4
7 changed files with 73 additions and 57 deletions

View File

@ -11,11 +11,10 @@ from xgboost import XGBRegressor
from delta_barth._management import ERROR_HANDLER
from delta_barth.analysis import parse
from delta_barth.constants import COL_MAP_SALES_PROGNOSIS, FEATURES_SALES_PROGNOSIS
from delta_barth.types import CustomerDataSalesForecast, DataPipelineErrors, doptResult
from delta_barth.types import CustomerDataSalesForecast, DataPipeStates, PipeResult
if TYPE_CHECKING:
from delta_barth.api.common import SalesPrognosisResponse
from delta_barth.types import FcResult
# TODO check pandera for DataFrame validation
@ -66,7 +65,7 @@ def sales_per_customer(
data: pd.DataFrame,
customer_id: int,
min_num_data_points: int = 100,
) -> doptResult:
) -> PipeResult:
"""_summary_
Parameters
@ -106,7 +105,7 @@ def sales_per_customer(
# check data availability
if len(df_cust) < min_num_data_points:
return doptResult(resp=ERROR_HANDLER.data_pipelines.TOO_FEW_POINTS, res=None)
return PipeResult(status=ERROR_HANDLER.pipe_states.TOO_FEW_POINTS, data=None)
else:
# Entwicklung der Umsätze: definierte Zeiträume Monat
df_cust["year"] = df_cust["date"].dt.year
@ -145,4 +144,4 @@ def sales_per_customer(
test = test.reset_index(drop=True)
# umsetzung, prognose
return doptResult(resp=ERROR_HANDLER.data_pipelines.SUCCESS, res=test)
return PipeResult(status=ERROR_HANDLER.pipe_states.SUCCESS, data=test)

View File

@ -14,22 +14,11 @@ from delta_barth.errors import (
UnknownApiErrorCode,
UnspecifiedRequestType,
)
from delta_barth.types import HttpRequestTypes
from delta_barth.types import DelBarApiError, HttpRequestTypes
LOGIN_ERROR_CODES_KNOWN: Final[frozenset[int]] = frozenset((400, 401, 409, 500))
class DelBarApiError(BaseModel):
status_code: int
message: str = ""
code: str | None = None
hints: str | None = None
type: str | None = None
title: str | None = None
errors: dict | None = None
traceId: str | None = None
def _raise_for_unknown_error(
resp: Response,
) -> Never:

View File

@ -5,6 +5,7 @@ from delta_barth.types import CurrentConnection, HttpContentHeaders
# ** error handling
DEFAULT_INTERNAL_ERR_CODE: Final[int] = 100
DEFAULT_API_ERR_CODE: Final[int] = 400
HTTP_BASE_CONTENT_HEADERS: Final[HttpContentHeaders] = {
"Content-type": "application/json",

View File

@ -2,11 +2,11 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Final
from delta_barth.constants import DEFAULT_INTERNAL_ERR_CODE
from delta_barth.types import DataPipelineErrors, doptResponse
from delta_barth.constants import DEFAULT_API_ERR_CODE, DEFAULT_INTERNAL_ERR_CODE
from delta_barth.types import DataPipeStates, Status
if TYPE_CHECKING:
from delta_barth.types import ErrorDescription
from delta_barth.types import DelBarApiError, ErrorDescription
class UnspecifiedRequestType(Exception):
@ -26,7 +26,7 @@ class FeaturesMissingError(Exception):
## ** internal error handling
DATA_PIPELINE_ERRORS_DESCR: Final[tuple[ErrorDescription, ...]] = (
DATA_PIPELINE_STATUS_DESCR: Final[tuple[ErrorDescription, ...]] = (
("SUCCESS", 0, "Erfolg"),
("TOO_FEW_POINTS", 1, "Datensatz besitzt nicht genügend Datenpunkte"),
("BAD_QUALITY", 2, "Prognosequalität des Modells unzureichend"),
@ -35,33 +35,48 @@ DATA_PIPELINE_ERRORS_DESCR: Final[tuple[ErrorDescription, ...]] = (
class ErrorHandler:
def __init__(self) -> None:
self._data_pipelines: DataPipelineErrors | None = None
self._parse_data_pipeline_errors()
self._pipe_states: DataPipeStates | None = None
self._parse_data_pipe_states()
@property
def data_pipelines(self) -> DataPipelineErrors:
assert self._data_pipelines is not None, (
def pipe_states(self) -> DataPipeStates:
assert self._pipe_states is not None, (
"tried to access not parsed data pipeline errors"
)
return self._data_pipelines
return self._pipe_states
def _parse_data_pipeline_errors(self) -> None:
if self._data_pipelines is not None:
def _parse_data_pipe_states(self) -> None:
if self._pipe_states is not None:
return
parsed_errors: dict[str, doptResponse] = {}
for err in DATA_PIPELINE_ERRORS_DESCR:
parsed_errors[err[0]] = doptResponse(status_code=err[1], description=err[2])
parsed_errors: dict[str, Status] = {}
for err in DATA_PIPELINE_STATUS_DESCR:
parsed_errors[err[0]] = Status(status_code=err[1], description=err[2])
self._data_pipelines = DataPipelineErrors(**parsed_errors)
self._pipe_states = DataPipeStates(**parsed_errors)
def internal_error(
def error(
self,
description: str,
message: str = "",
err_code: int = DEFAULT_INTERNAL_ERR_CODE,
) -> doptResponse:
return doptResponse(
) -> Status:
return Status(
status_code=err_code,
description=description,
message=message,
)
def api_error(
self,
error: DelBarApiError,
) -> Status:
description = "Es ist ein Fehler bei der Kommunikation mit dem API-Server aufgetreten"
message = (
"Bitte beachten Sie die zusätzliche Fehlerausgabe des Servers in dieser Antwort"
)
return Status(
status_code=DEFAULT_API_ERR_CODE,
description=description,
message=message,
api_server_error=error,
)

View File

@ -12,17 +12,29 @@ from pydantic import BaseModel, SkipValidation
ErrorDescription: TypeAlias = tuple[str, int, str]
class doptResponse(BaseModel):
class Status(BaseModel):
status_code: SkipValidation[int]
description: SkipValidation[str]
message: SkipValidation[str] = ""
api_server_error: SkipValidation[DelBarApiError | None] = None
class DelBarApiError(BaseModel):
status_code: int
message: str = ""
code: str | None = None
hints: str | None = None
type: str | None = None
title: str | None = None
errors: dict | None = None
traceId: str | None = None
@dataclass(slots=True)
class DataPipelineErrors:
SUCCESS: doptResponse
TOO_FEW_POINTS: doptResponse
BAD_QUALITY: doptResponse
class DataPipeStates:
SUCCESS: Status
TOO_FEW_POINTS: Status
BAD_QUALITY: Status
class HttpRequestTypes(enum.StrEnum):
@ -92,6 +104,6 @@ class CustomerDataSalesForecast:
@dataclass(slots=True)
class doptResult:
resp: doptResponse
res: pd.DataFrame | None
class PipeResult:
status: Status
data: pd.DataFrame | None

View File

@ -5,18 +5,18 @@ from delta_barth.analysis import forecast as fc
def test_sales_per_customer_success(sales_data):
customer_id = 1133
err, res = fc.sales_per_customer(sales_data, customer_id)
res = fc.sales_per_customer(sales_data, customer_id)
assert err == 0
assert res is not None
assert res.status.status_code == 0
assert res.data is not None
def test_sales_per_customer_too_few_data_points(sales_data):
customer_id = 1000
err, res = fc.sales_per_customer(sales_data, customer_id)
res = fc.sales_per_customer(sales_data, customer_id)
assert err == 1
assert res is None
assert res.status.status_code == 1
assert res.data is None
def test_parse_api_resp_to_df(exmpl_api_sales_prognosis_resp):

View File

@ -5,25 +5,25 @@ from typing import cast
import delta_barth._management
from delta_barth import errors
from delta_barth.types import doptResponse
from delta_barth.types import Status
def test_error_handler_parsing():
predef_errs = errors.DATA_PIPELINE_ERRORS_DESCR
predef_errs = errors.DATA_PIPELINE_STATUS_DESCR
err_hdlr = delta_barth._management.ErrorHandler()
assert err_hdlr.data_pipelines is not None
parsed_pipe_errs = err_hdlr.data_pipelines
assert err_hdlr.pipe_states is not None
parsed_pipe_errs = err_hdlr.pipe_states
parsed_pipe_errs = asdict(parsed_pipe_errs)
for err in predef_errs:
dopt_err = cast(doptResponse, parsed_pipe_errs[err[0]])
assert isinstance(dopt_err, doptResponse)
dopt_err = cast(Status, parsed_pipe_errs[err[0]])
assert isinstance(dopt_err, Status)
assert dopt_err.status_code == err[1]
assert dopt_err.description == err[2]
assert dopt_err.message == ""
err_hdlr._parse_data_pipeline_errors()
err_hdlr._parse_data_pipe_states()
def test_error_handler_internal():
@ -32,7 +32,7 @@ def test_error_handler_internal():
ERR_CODE = 101
err_hdlr = delta_barth._management.ErrorHandler()
new_err = err_hdlr.internal_error(
new_err = err_hdlr.error(
description=DESCRIPTION,
message=MESSAGE,
err_code=ERR_CODE,