Developer guide

Layout

auction_oracle/
  config.py db.py secrets_store.py ingest.py api.py cli.py
  connectors/  base.py ahfyi.py everloot.py blizzard.py keyed.py (ForeverDB, wow.gg) local.py (addon, CSV/JSON, manual)
  engines/     series.py market.py forecast.py crafting.py economy.py wealth.py intel.py
  ai/          strategist.py
ui/            vanilla JS SPA (no build step)
addon/         WoW: Forever addon (Interface 16001)
seed/          sourced events + classic-reference recipe quantities (validated against the game graph)
tests/         pytest: data, engines (synthetic markets), API/security

Run

%USERPROFILE%\.auction-oracle\venv\Scripts\python.exe -m auction_oracle serve --no-browser
%USERPROFILE%\.auction-oracle\venv\Scripts\python.exe -m pytest tests -q

AO_DB_PATH, AO_DATA_DIR, AO_PORT override paths/port. Secrets can also come from AO_<NAME> env vars (CI only).

Invariants (enforced by tests)

  1. An absent price is stored as NULL with flag='not_listed' and is never converted to 0.
  2. Collection is idempotent (UNIQUE(source_id, market_id, scanned_at, kind)).
  3. Forecast features at origin i only use data ≤ i (test_features_have_no_lookahead); LightGBM is trained on origins whose targets are before the evaluation cutoff.
  4. A model is served only if it beats naive by ≥3% MAE in walk-forward evaluation; otherwise naive.
  5. Recipes without reagent quantities are UNKNOWN, never priced.
  6. Secrets are never returned by the API nor written to logs (redact).

Adding a connector

Subclass connectors.base.Connector, set id/name/licence/attribution/commercial_ok/per_minute, implement health() (real calls through self.request, which never fakes success) and collect(con) returning a CollectReport. Write through upsert_scan + observations/listings (prices in copper per unit). Register it in ingest.all_connectors(). Add an offline test with fake_request (see tests/test_data.py).

Adding a model

Add m_<name>(series, h) -> log price to engines/forecast.py and to MODELS/SIMPLE_MODELS. evaluate() will compare it walk-forward against naive automatically; it is selected only if it wins.

Database migrations

Append (version, sql) to db.MIGRATIONS. Never edit an applied migration. connect() applies pending migrations; scripts\update.bat backs up first.

Desktop packaging (Tauri path)

The UI is a static SPA talking to http://127.0.0.1:8765/api. To ship a native window, wrap ui/ in Tauri 2 and launch the Python server as a sidecar (PyInstaller one-file build of auction_oracle). No UI code changes required.

Performance (measured on the dev PC, 4,045 items, 1 market)

item_stats ≈ 2 s cold, cached per scan afterwards; scanner/opportunities < 50 ms warm; crafting table ≈ 0.2 s; full briefing (8 agents in parallel) ≈ 4 s.