111 lines
4.2 KiB
Python
111 lines
4.2 KiB
Python
|
|
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
|
||
|
|
]
|