Core trust scoring: - Five metadata signals (account age, feedback count/ratio, price vs market, category history), composited 0–100 - CV-based price signal suppression for heterogeneous search results (e.g. mixed laptop generations won't false-positive suspicious_price) - Expanded scratch/dent title detection: evasive redirects, functional problem phrases, DIY/repair indicators - Hard filters: new_account, established_bad_actor - Soft flags: low_feedback, suspicious_price, duplicate_photo, scratch_dent, long_on_market, significant_price_drop Search & filtering: - Browse API adapter (up to 200 items/page) + Playwright scraper fallback - OR-group query expansion for comprehensive variant coverage - Must-include (AND/ANY/groups), must-exclude, category, price range filters - Saved searches with full filter round-trip via URL params Seller enrichment: - Background BTF /itm/ scraping for account age (Kasada-safe headed Chromium) - On-demand enrichment: POST /api/enrich + ListingCard ↻ button - Category history derived from Browse API categories field (free, no extra calls) - Shopping API GetUserProfile inline enrichment for API adapter Market comps: - eBay Marketplace Insights API with Browse API fallback (catches 403 + 404) - Comps prioritised in ThreadPoolExecutor (submitted first) Infrastructure: - Staging DB fields: times_seen, first_seen_at, price_at_first_seen, category_name - Migrations 004 (staging tracking) + 005 (listing category) - eBay webhook handler stub - Cloud compose stack (compose.cloud.yml) - Vue frontend: search store, saved searches store, ListingCard, filter sidebar Docs: - README fully rewritten to reflect MVP status + full feature documentation - Roadmap table linked to all 13 Forgejo issues
31 lines
1.2 KiB
Python
31 lines
1.2 KiB
Python
"""PlatformAdapter abstract base and shared types."""
|
|
from __future__ import annotations
|
|
from abc import ABC, abstractmethod
|
|
from dataclasses import dataclass, field
|
|
from typing import Optional
|
|
from app.db.models import Listing, Seller
|
|
|
|
|
|
@dataclass
|
|
class SearchFilters:
|
|
max_price: Optional[float] = None
|
|
min_price: Optional[float] = None
|
|
condition: Optional[list[str]] = field(default_factory=list)
|
|
location_radius_km: Optional[int] = None
|
|
pages: int = 1 # number of result pages to fetch (48 listings/page)
|
|
must_include: list[str] = field(default_factory=list) # client-side title filter
|
|
must_exclude: list[str] = field(default_factory=list) # forwarded to eBay -term AND client-side
|
|
category_id: Optional[str] = None # eBay category ID (e.g. "27386" = GPUs)
|
|
|
|
|
|
class PlatformAdapter(ABC):
|
|
@abstractmethod
|
|
def search(self, query: str, filters: SearchFilters) -> list[Listing]: ...
|
|
|
|
@abstractmethod
|
|
def get_seller(self, seller_platform_id: str) -> Optional[Seller]: ...
|
|
|
|
@abstractmethod
|
|
def get_completed_sales(self, query: str) -> list[Listing]:
|
|
"""Fetch recently completed/sold listings for price comp data."""
|
|
...
|