1"""Project-wide constants and paths (UQO WP36)."""2from __future__ import annotations34import os5from pathlib import Path67ROOT = Path(os.environ.get("UCML_ROOT", Path(__file__).resolve().parents[2]))8DATA = ROOT / "data"9RAW = DATA / "raw"10INTERIM = DATA / "interim"11PROCESSED = DATA / "processed"12SYNTHETIC = DATA / "synthetic"13RESULTS = ROOT / "results"14FIGURES = ROOT / "figures"15TABLES = ROOT / "paper" / "tables"1617SNAPSHOT = Path(os.environ.get("UCML_SNAPSHOT", RAW / "autoka-snapshot-20261101.db"))18SNAPSHOT_DATE = "2026-11-01"19# last sync in the snapshot (UTC) — used as the right-censoring point20def _manifest_ts() -> tuple[float, float]:21 """(first sync ts, last sync ts) from data/interim/manifest.json when available."""22 import json23 m = INTERIM / "manifest.json"24 if m.exists():25 d = json.loads(m.read_text())26 return float(d["sync_ts_min"]), float(d["sync_ts_max"])27 return 1786537835.64518, 1793515782.732466 # 2026-08-12 12:30:35 UTC, 2026-11-01 06:49:42 UTC282930WINDOW_START_TS, SNAPSHOT_TS = _manifest_ts()3132# --- Source classification (DECISIONS.md D-003) ---------------------------------33SAMPLED_PORTALS = {"autotrader", "cargurus"} # capped sliding windows -> no exit information34PRIVATE_MARKETPLACE = {"kijiji"} # private sellers, no VIN35FULL_PORTALS = {"otogo", "autousagee"} # full-inventory portals (dealer-identified)36BANNERS = {"hgregoire", "automobileendirect", "megacentredeliquidation", "clubautozone", "leprixdugros"}37SMALL_PORTALS = {"okaze", "classeauto", "ototr"}38FLICKER_OR_FAILED = {"leprixdugros", "albioccasion", "baiecomeaugm", "septilesgm",39 "stjeromeautodepot", "excelmoto"}40UID_COLLISIONS = {"classeauto:-98"}4142# --- Cleaning thresholds (analysis_plan.md §1-2) --------------------------------43PRICE_MIN, PRICE_MAX = 2_000.0, 250_000.044KM_MIN, KM_MAX = 1_000.0, 400_000.045YEAR_MIN, YEAR_MAX = 2005, 202646TRANSIENT_SECONDS = 48 * 3600 # A->B->A level shorter than this is removed47OSCILLATION_MAX_TRANSIENTS = 3 # >= 3 transients -> oscillating uid48MARKDOWN_MIN_PCT, MARKDOWN_MIN_ABS = 0.01, 100.049EXIT_CONFIRM_DAYS = 750LEFT_CENSOR_CUTOFF_TS = 1786579200.0 # 2026-08-13 00:00:00 UTC: entries before this are the day-1 stock51SOURCE_STARTUP_SECONDS = 24 * 3600 # entries within 24 h of a source's first non-empty crawl = start-up stock (D-014)5253RESTRICTED_COLUMNS = ["description", "images", "url", "carfax_url", "features"]54