BuyingAgent/backend/app/api/schemas.py

275 lines
6.6 KiB
Python
Raw Normal View History

from datetime import datetime
from decimal import Decimal
from typing import Any
from uuid import UUID
from pydantic import BaseModel, ConfigDict, Field, field_serializer
class StrictModel(BaseModel):
model_config = ConfigDict(extra="forbid")
class AttributeDefinitionIn(StrictModel):
key: str
value_type: str
canonical_unit: str | None = None
minimum: Decimal | None = None
maximum: Decimal | None = None
description: str | None = None
class AttributeDefinitionOut(StrictModel):
key: str
value_type: str
canonical_unit: str | None
minimum: Decimal | None
maximum: Decimal | None
description: str | None
class ProductIn(StrictModel):
id: UUID | None = None
category: str
brand: str
name: str
model: str | None = None
class ProductOut(StrictModel):
id: UUID
category: str
brand: str
name: str
model: str | None
created_at: datetime
class VariantIn(StrictModel):
id: UUID | None = None
name: str
manufacturer_sku: str | None = None
identifiers: dict[str, Any] = Field(default_factory=dict)
class AttributeIn(StrictModel):
id: UUID | None = None
attribute: str
raw_value: Any
normalized_value: Any | None = None
unit: str | None = None
normalization_status: str
normalizer_version: str | None = None
confidence: Decimal
source_id: str
source_reference: str
observed_at: datetime
normalization_error: str | None = None
class AttributeOut(StrictModel):
id: UUID
variant_id: UUID
attribute: str
raw_value: Any
normalized_value: Any | None
unit: str | None
normalization_status: str
normalizer_version: str
confidence: Decimal
source_id: str
source_reference: str
observed_at: datetime
ingested_at: datetime
normalization_error: str | None
class VariantOut(StrictModel):
id: UUID
product_id: UUID
name: str
manufacturer_sku: str | None
identifiers: dict[str, Any]
created_at: datetime
attributes: list[AttributeOut]
class OfferIn(StrictModel):
id: UUID | None = None
source_id: str
external_id: str
seller: str
url: str | None = None
market: str
currency: str
condition: str
class OfferOut(StrictModel):
id: UUID
variant_id: UUID
source_id: str
external_id: str
seller: str
url: str | None
market: str
currency: str
condition: str
created_at: datetime
class PriceIn(StrictModel):
id: UUID | None = None
source_id: str
external_observation_id: str
observed_at: datetime
item_price: Decimal
shipping_price: Decimal | None = None
currency: str
availability: str
raw_payload: Any
confidence: Decimal
@field_serializer("item_price", "shipping_price")
def serialize_money(self, value: Decimal | None) -> str | None:
return None if value is None else format(value, "f")
class PriceOut(StrictModel):
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: str
raw_payload: Any
confidence: Decimal
@field_serializer("item_price", "shipping_price")
def serialize_money(self, value: Decimal | None) -> str | None:
return None if value is None else format(Decimal(value).quantize(Decimal("0.01")), "f")
class EvaluateIn(StrictModel):
profile_id: str
profile_version: int
offer_id: UUID
as_of: datetime | None = None
class BuyingProjectIn(StrictModel):
id: UUID | None = None
name: str
product_category_id: str | None = None
market: str | None = None
currency: str | None = None
target_budget: Decimal | None = None
template_id: str | None = None
template_version: int | None = None
profile_id: str | None = None
profile_version: int | None = None
workspace_id: str | None = None
shortlist_size: int = 5
schedule_interval_minutes: int | None = None
criteria: dict[str, Any]
class BuyingProjectPatch(StrictModel):
name: str | None = None
target_budget: Decimal | None = None
shortlist_size: int | None = None
status: str | None = None
class ProjectCriteriaIn(StrictModel):
items: list[dict[str, Any]]
class CriterionDefinitionIn(StrictModel):
key: str
value_type: str
unit: str | None = None
semantic: str | None = None
category: str | None = None
description: str | None = None
origin: str | None = None
source: str | None = None
product_category_id: str | None = None
label: str | None = None
scoring_attribute_key: str | None = None
class DiscoverySearchIn(StrictModel):
buying_project_id: UUID | None = None
source_id: str
source_category_id: UUID
schedule_interval_minutes: int = 180
broad_result_threshold: int = 300
class DiscoverySearchPatch(StrictModel):
schedule_interval_minutes: int | None = None
status: str | None = None
class SourceAttributeMappingIn(StrictModel):
source_id: str
source_category_id: UUID | None = None
raw_name: str
criterion_definition_id: UUID
status: str | None = None
class SourceAttributeMappingPatch(StrictModel):
raw_name: str | None = None
criterion_definition_id: UUID | None = None
status: str | None = None
class CriterionMappingIn(StrictModel):
criterion_definition_id: UUID | None = None
criterion_key: str | None = None
source_id: str
source_category_id: UUID
external_filter_key: str
source_value: str | None = None
encoding: dict[str, Any] = Field(default_factory=lambda: {"url_param": "xf"})
status: str | None = None
value_type: str | None = None
unit: str | None = None
semantic: str | None = None
description: str | None = None
operator: str | None = None
probe_value: Any = None
class CriterionMappingPatch(StrictModel):
source_value: str | None = None
encoding: dict[str, Any] | None = None
status: str | None = None
probe_value: Any = None
operator: str | None = None
class Page(StrictModel):
items: list[Any]
limit: int
offset: int
total: int
def page(items: list[Any], limit: int, offset: int, total: int) -> dict[str, Any]:
return {"items": items, "limit": limit, "offset": offset, "total": total}
def parse_page(limit: int = 50, offset: int = 0) -> tuple[int, int]:
from app.errors import AppError
if limit < 1 or limit > 200 or offset < 0:
raise AppError(422, "PAGINATION", "limit must be 1-200 and offset >= 0")
return limit, offset