BuyingAgent/backend/app/sources/manual.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

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
]