BuyingAgent/backend/app/domain/models.py
Lars b276758ee1
All checks were successful
Deploy Development / deploy (push) Successful in 1m19s
Test Suite / pytest-backend (push) Successful in 31s
Test Suite / smoke-dev (push) Successful in 0s
Test Suite / frontend-checks (push) Successful in 28s
Stelle den Frontend-MVP und die v0.3-API auf develop bereit, damit Dev API und PWA zusammen deployen.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-04 13:04:59 +02:00

276 lines
6.0 KiB
Python

from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime
from decimal import Decimal
from typing import Any
from uuid import UUID
from app.domain.enums import (
Availability,
ConstraintOperator,
Eligibility,
NormalizationStatus,
OfferCondition,
PreferenceDirection,
PreferenceStrategy,
PriceBasis,
PriceReferenceScope,
ValueType,
)
@dataclass(frozen=True)
class Budget:
target: Decimal
maximum: Decimal
@dataclass(frozen=True)
class Constraint:
id: str
attribute: str
operator: ConstraintOperator
value: Any = None
mandatory: bool = True
@dataclass(frozen=True)
class Preference:
id: str
attribute: str
strategy: PreferenceStrategy
direction: PreferenceDirection
acceptable: Decimal
ideal: Decimal
weight: Decimal
@dataclass(frozen=True)
class ClassificationBound:
upper: Decimal
inclusive: bool
label: str
@dataclass(frozen=True)
class ScoringModel:
version: str
algorithm_id: str
supported_preference_strategies: tuple[str, ...]
minimum_confidence: Decimal
maximum_price_age_hours: int
optional_constraint_penalty_points: Decimal
missing_preference_score: Decimal
product_weight: Decimal
price_weight: Decimal
budget_weight: Decimal
history_weight: Decimal
history_window_days: int
minimum_history_days: int
history_discount_for_max_score: Decimal
classification_upper_bounds: tuple[ClassificationBound, ...]
definition: dict[str, Any]
definition_hash: str
@dataclass(frozen=True)
class Profile:
id: str
version: int
name: str
category: str
currency: str
market: str
allowed_conditions: tuple[OfferCondition, ...]
budget: Budget
scoring_version: str
constraints: tuple[Constraint, ...]
preferences: tuple[Preference, ...]
definition: dict[str, Any]
definition_hash: str
@dataclass(frozen=True)
class AttributeDefinition:
key: str
value_type: ValueType
canonical_unit: str | None
minimum: Decimal | None
maximum: Decimal | None
description: str | None
@dataclass(frozen=True)
class Product:
id: UUID
category: str
brand: str
name: str
model: str | None
created_at: datetime
@dataclass(frozen=True)
class ProductVariant:
id: UUID
product_id: UUID
name: str
manufacturer_sku: str | None
identifiers: dict[str, Any]
created_at: datetime
@dataclass(frozen=True)
class AttributeStatement:
id: UUID
variant_id: UUID
attribute: str
raw_value: Any
normalized_value: Any
unit: str | None
normalization_status: NormalizationStatus
normalizer_version: str
confidence: Decimal
source_id: str
source_reference: str
observed_at: datetime
ingested_at: datetime
normalization_error: str | None = None
@dataclass(frozen=True)
class Offer:
id: UUID
variant_id: UUID
source_id: str
external_id: str
seller: str
url: str | None
market: str
currency: str
condition: OfferCondition
created_at: datetime
@dataclass(frozen=True)
class PriceObservation:
id: UUID
offer_id: UUID
source_id: str
external_observation_id: str
observed_at: datetime
ingested_at: datetime
item_price: Decimal
shipping_price: Decimal | None
currency: str
availability: Availability
raw_payload: Any
confidence: Decimal
@property
def total_price(self) -> Decimal | None:
if self.shipping_price is None:
return None
return self.item_price + self.shipping_price
@dataclass(frozen=True)
class ConstraintResult:
constraint_id: str
attribute: str
operator: str
expected: Any
actual: Any
status: str
mandatory: bool
attribute_row_id: UUID | None
reason_code: str
@dataclass(frozen=True)
class GateResult:
key: str
status: str
reason_code: str
details: dict[str, Any] = field(default_factory=dict)
@dataclass(frozen=True)
class ScoreComponent:
kind: str
component_key: str
raw_score: Decimal | None
weight: Decimal | None
contribution: Decimal | None
details: dict[str, Any]
@dataclass(frozen=True)
class HistoryWindow:
days: int
values: tuple[Decimal, ...]
observation_ids: tuple[UUID, ...]
@property
def day_count(self) -> int:
return len(self.values)
@dataclass(frozen=True)
class PriceStatistics:
current_total: Decimal | None
median_30d: Decimal | None
median_30d_days: int
median_90d: Decimal | None
median_90d_days: int
lowest_365d: Decimal | None
lowest_365d_days: int
lowest_365d_is_full_year: bool
used_observation_ids: tuple[UUID, ...]
current_observation_id: UUID | None
price_reference_scope: PriceReferenceScope = PriceReferenceScope.OFFER
@dataclass(frozen=True)
class Reason:
code: str
message: str
@dataclass(frozen=True)
class EvaluationResult:
profile_id: str
profile_version: int
scoring_version: str
scoring_hash: str
engine_build: str
variant_id: UUID
offer_id: UUID
as_of: datetime
snapshot_cutoff: datetime
product_eligibility: Eligibility
offer_eligibility: Eligibility
overall_eligibility: Eligibility
eligible: bool | None
product_score: Decimal | None
product_score_raw: Decimal | None
price_score: Decimal | None
price_score_raw: Decimal | None
deal_score: Decimal | None
deal_score_raw: Decimal | None
classification: str
price_basis: PriceBasis | None
product_coverage: Decimal
constraint_results: tuple[ConstraintResult, ...]
product_gates: tuple[GateResult, ...]
offer_gates: tuple[GateResult, ...]
components: tuple[ScoreComponent, ...]
price_statistics: PriceStatistics
price_reference_scope: PriceReferenceScope
reasons: tuple[Reason, ...]
warnings: tuple[str, ...]
input_snapshot: dict[str, Any]
failed_constraints: tuple[ConstraintResult, ...]
unknown_constraints: tuple[ConstraintResult, ...]