from __future__ import annotations import json from pathlib import Path from typing import Any from urllib.parse import parse_qs, urlparse from app.discovery.enums import FilterType from app.errors import AppError FIXTURE_DIR = Path(__file__).resolve().parents[3] / "fixtures" / "geizhals" class GeizhalsAdapter: source_key = "geizhals" def __init__(self, fixture_dir: Path | None = None, live: bool = False) -> None: self.fixture_dir = fixture_dir or FIXTURE_DIR self.live = live def discover_taxonomy(self, category_ref: str) -> dict[str, Any]: payload = self._load(f"{category_ref}-taxonomy.json") return payload def build_query(self, criteria: list[dict[str, Any]], mappings: list[dict[str, Any]]) -> dict[str, Any]: from app.discovery.urlgen import generate_search_url category_url = f"https://geizhals.de/?cat={self._category(mappings, criteria)}" url, internal_only = generate_search_url( category_url=category_url, criteria_items=criteria, mappings=mappings, ) return {"url": url, "internal_only": internal_only} def fetch_results(self, query: dict[str, Any], pagination: dict[str, Any]) -> dict[str, Any]: if self.live: raise AppError(422, "LIVE_FETCH_DISABLED", "live Geizhals fetch is not enabled in this process") page = int(pagination.get("page") or 1) category = parse_qs(urlparse(query["url"]).query).get("cat", ["nb"])[0] return self._load(f"{category}-search-page{page}.json") def parse_results(self, response: dict[str, Any]) -> list[dict[str, Any]]: return list(response.get("items") or []) def normalize_result(self, result: dict[str, Any]) -> dict[str, Any]: attrs = result.get("normalized_attributes") or {} return { "source_external_id": result.get("source_external_id"), "source_url": result.get("source_url"), "source_title": result.get("source_title"), "manufacturer": result.get("manufacturer"), "model": result.get("model"), "manufacturer_sku": result.get("manufacturer_sku"), "gtin_ean": result.get("gtin_ean"), "best_price": result.get("best_price"), "currency": result.get("currency") or "EUR", "availability": result.get("availability") or "unknown", "seller_count": result.get("seller_count"), "raw_attributes": result.get("raw_attributes") or {}, "normalized_attributes": attrs, } def _load(self, name: str) -> dict[str, Any]: path = self.fixture_dir / name if not path.exists(): raise AppError(404, "FIXTURE_NOT_FOUND", f"geizhals fixture missing: {name}") return json.loads(path.read_text(encoding="utf-8")) def _category(self, mappings: list[dict[str, Any]], criteria: list[dict[str, Any]]) -> str: for mapping in mappings: if mapping.get("category_key"): return str(mapping["category_key"]) return "nb" def filter_type(raw: str) -> str: try: return FilterType(raw).value except ValueError: return FilterType.UNKNOWN.value