BuyingAgent/backend/app/sources/manual.py

111 lines
4.2 KiB
Python
Raw Normal View History

import json
from datetime import datetime
from decimal import Decimal
from pathlib import Path
from uuid import UUID
from app.errors import AppError
from app.sources.base import (
SourceAttribute,
SourceOffer,
SourcePriceObservation,
SourceProduct,
SourceVariant,
)
def _parse_dt(value: str) -> datetime:
return datetime.fromisoformat(value.replace("Z", "+00:00"))
class ManualSource:
source_key = "manual"
def __init__(self, path: Path) -> None:
self.path = path
self._data = json.loads(path.read_text(encoding="utf-8"))
self._products = {item["external_id"]: item for item in self._data["products"]}
self._offers = {item["external_id"]: item for item in self._data.get("offers", [])}
self._prices = self._data.get("prices", [])
def list_external_product_ids(self) -> list[str]:
return list(self._products)
def get_product(self, external_id: str) -> SourceProduct:
raw = self._products.get(external_id)
if raw is None:
raise AppError(404, "NOT_FOUND", "manual product not found", {"external_id": external_id})
variants = []
for variant in raw["variants"]:
attributes = tuple(
SourceAttribute(
id=UUID(item["id"]),
attribute=item["attribute"],
raw_value=item["raw_value"],
normalized_value=item.get("normalized_value"),
unit=item.get("unit"),
normalization_status=item["normalization_status"],
normalizer_version=item.get("normalizer_version", "manual-v1"),
confidence=Decimal(str(item["confidence"])),
source_reference=item["source_reference"],
observed_at=_parse_dt(item["observed_at"]),
normalization_error=item.get("normalization_error"),
)
for item in variant["attributes"]
)
variants.append(
SourceVariant(
id=UUID(variant["id"]),
name=variant["name"],
manufacturer_sku=variant.get("manufacturer_sku"),
identifiers=variant.get("identifiers") or {},
attributes=attributes,
)
)
return SourceProduct(
source_key=self.source_key,
external_id=external_id,
id=UUID(raw["id"]),
category=raw["category"],
brand=raw["brand"],
name=raw["name"],
model=raw.get("model"),
variants=tuple(variants),
)
def get_offers(self, external_product_id: str) -> list[SourceOffer]:
product = self.get_product(external_product_id)
variant_ids = {variant.id for variant in product.variants}
return [
SourceOffer(
id=UUID(item["id"]),
variant_id=UUID(item["variant_id"]),
external_id=item["external_id"],
seller=item["seller"],
url=item.get("url"),
market=item["market"],
currency=item["currency"],
condition=item["condition"],
)
for item in self._offers.values()
if UUID(item["variant_id"]) in variant_ids
]
def get_price_history(self, external_offer_id: str) -> list[SourcePriceObservation]:
return [
SourcePriceObservation(
id=UUID(item["id"]) if item.get("id") else None,
external_offer_id=external_offer_id,
external_observation_id=item["external_observation_id"],
observed_at=_parse_dt(item["observed_at"]),
item_price=Decimal(str(item["item_price"])),
shipping_price=None if item.get("shipping_price") is None else Decimal(str(item["shipping_price"])),
currency=item["currency"],
availability=item["availability"],
raw_payload=item["raw_payload"],
confidence=Decimal(str(item["confidence"])),
)
for item in self._prices
if item["external_offer_id"] == external_offer_id
]