snipe/app/platforms/ebay/normaliser.py
pyr0ball eb05be0612
Some checks are pending
CI / API — lint + test (pull_request) Waiting to run
CI / Web — typecheck + test + build (pull_request) Waiting to run
feat: wire Forgejo Actions CI/CD workflows (#22)
- ci.yml: API lint (ruff F+I) + pytest, web vue-tsc + vitest + build
- mirror.yml: push to GitHub (CircuitForgeLLC) + Codeberg (CircuitForge) on main/tags
- release.yml: Docker build → Forgejo registry + release via API; GHCR deferred pending BSL policy (cf-agents#3)
- .cliff.toml: git-cliff changelog config for semver releases
- pyproject.toml: add [dev] extras (pytest, ruff), ruff config
- Fix 45 ruff violations across codebase (import sorting, unused vars, unused imports)
2026-04-06 00:00:28 -07:00

94 lines
3.1 KiB
Python

"""Convert raw eBay API responses into Snipe domain objects."""
from __future__ import annotations
import json
from datetime import datetime, timezone
from typing import Optional
from app.db.models import Listing, Seller
def normalise_listing(raw: dict) -> Listing:
price_data = raw.get("price", {})
photos = []
if "image" in raw:
photos.append(raw["image"].get("imageUrl", ""))
for img in raw.get("additionalImages", []):
url = img.get("imageUrl", "")
if url and url not in photos:
photos.append(url)
photos = [p for p in photos if p]
listing_age_days = 0
created_raw = raw.get("itemCreationDate", "")
if created_raw:
try:
created = datetime.fromisoformat(created_raw.replace("Z", "+00:00"))
listing_age_days = (datetime.now(timezone.utc) - created).days
except ValueError:
pass
options = raw.get("buyingOptions", [])
if "AUCTION" in options:
buying_format = "auction"
elif "BEST_OFFER" in options:
buying_format = "best_offer"
else:
buying_format = "fixed_price"
ends_at = None
end_raw = raw.get("itemEndDate", "")
if end_raw:
try:
ends_at = datetime.fromisoformat(end_raw.replace("Z", "+00:00")).isoformat()
except ValueError:
pass
# Leaf category is categories[0] (most specific); parent path follows.
categories = raw.get("categories", [])
category_name: Optional[str] = categories[0]["categoryName"] if categories else None
seller = raw.get("seller", {})
return Listing(
platform="ebay",
platform_listing_id=raw["itemId"],
title=raw.get("title", ""),
price=float(price_data.get("value", 0)),
currency=price_data.get("currency", "USD"),
condition=raw.get("condition", "").lower(),
seller_platform_id=seller.get("username", ""),
url=raw.get("itemWebUrl", ""),
photo_urls=photos,
listing_age_days=listing_age_days,
buying_format=buying_format,
ends_at=ends_at,
category_name=category_name,
)
def normalise_seller(raw: dict) -> Seller:
feedback_pct = float(raw.get("feedbackPercentage", "0").strip("%")) / 100.0
account_age_days: Optional[int] = None # None = registrationDate not in API response
reg_date_raw = raw.get("registrationDate", "")
if reg_date_raw:
try:
reg_date = datetime.fromisoformat(reg_date_raw.replace("Z", "+00:00"))
account_age_days = (datetime.now(timezone.utc) - reg_date).days
except ValueError:
pass
category_history = {}
summary = raw.get("sellerFeedbackSummary", {})
for entry in summary.get("feedbackByCategory", []):
category_history[entry.get("categorySite", "")] = int(entry.get("count", 0))
return Seller(
platform="ebay",
platform_seller_id=raw["username"],
username=raw["username"],
account_age_days=account_age_days,
feedback_count=int(raw.get("feedbackScore", 0)),
feedback_ratio=feedback_pct,
category_history_json=json.dumps(category_history),
)