276 lines
6.0 KiB
Python
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, ...]
|