snipe/app/db/models.py
pyr0ball 98695b00f0 feat(snipe): eBay trust scoring MVP — search, filters, enrichment, comps
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
2026-03-26 23:37:09 -07:00

92 lines
2.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Dataclasses for all Snipe domain objects."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class Seller:
platform: str
platform_seller_id: str
username: str
account_age_days: Optional[int] # None = not yet fetched (scraper tier)
feedback_count: int
feedback_ratio: float # 0.01.0
category_history_json: str # JSON blob of past category sales
id: Optional[int] = None
fetched_at: Optional[str] = None
@dataclass
class Listing:
platform: str
platform_listing_id: str
title: str
price: float
currency: str
condition: str
seller_platform_id: str
url: str
photo_urls: list[str] = field(default_factory=list)
listing_age_days: int = 0
buying_format: str = "fixed_price" # "fixed_price", "auction", "best_offer"
ends_at: Optional[str] = None # ISO8601 auction end time; None for fixed-price
id: Optional[int] = None
fetched_at: Optional[str] = None
trust_score_id: Optional[int] = None
category_name: Optional[str] = None # leaf category from eBay API (e.g. "Graphics/Video Cards")
# Staging DB fields — populated from DB after upsert
first_seen_at: Optional[str] = None
last_seen_at: Optional[str] = None
times_seen: int = 1
price_at_first_seen: Optional[float] = None
@dataclass
class TrustScore:
listing_id: int
composite_score: int # 0100
account_age_score: int # 020
feedback_count_score: int # 020
feedback_ratio_score: int # 020
price_vs_market_score: int # 020
category_history_score: int # 020
photo_hash_duplicate: bool = False
photo_analysis_json: Optional[str] = None
red_flags_json: str = "[]"
score_is_partial: bool = False
id: Optional[int] = None
scored_at: Optional[str] = None
@dataclass
class MarketComp:
platform: str
query_hash: str
median_price: float
sample_count: int
expires_at: str # ISO8601 — checked against current time
id: Optional[int] = None
fetched_at: Optional[str] = None
@dataclass
class SavedSearch:
"""Schema scaffolded in v0.1; background monitoring wired in v0.2."""
name: str
query: str
platform: str
filters_json: str = "{}"
id: Optional[int] = None
created_at: Optional[str] = None
last_run_at: Optional[str] = None
@dataclass
class PhotoHash:
"""Perceptual hash store for cross-search dedup (v0.2+). Schema scaffolded in v0.1."""
listing_id: int
photo_url: str
phash: str # hex string from imagehash
id: Optional[int] = None
first_seen_at: Optional[str] = None