The typical cycle is: scan (run.py), optionally run the AI pass (analyze.py), then review in the dashboard (server.py).
python3 run.py
For every enabled portal in config/config.yaml, the scan:
It prints accepted and rejected counts per portal, then the top deals.
A red dot next to a portal means most of its listings were rejected (≤20 % accepted). That’s normal for portals that also list hardtails, non-e-bikes or other frame sizes.
A full debug log goes to logs/run.log.
OutOfStock;A blocked or failed check changes nothing.
app.availability_checks_per_run listings (default 300) are checked per scan, least recently checked first.python3 server.py
This opens a local Flask app at http://127.0.0.1:5050 as a standalone app-mode browser window. It reads the database live on every page load.
Buttons per listing:
| Button | Effect |
|---|---|
| ☆ / ⭐ (Preferito) | Toggle favorite |
| ✕ Scarta | Reject: hidden, and a rescan or reprocess won’t bring it back |
| ✅ Segna venduta | Mark SOLD |
| ↩️ Ripristina attiva | Undo a reject or sold mark |
| 🗑️ Cancella dalla lista | Delete; it won’t be re-imported |
| 📋 Dettagli → 💾 Salva correzioni | Override parsed specs and rescore. A correction that breaks your criteria (wrong size, motor or battery below the minimum) auto-rejects the listing |
Restart server.py after changing code. Reloading the page is not enough.
analyze.py sends listings to Claude Haiku in batches. For each listing it:
It needs ANTHROPIC_API_KEY in .env.
python3 analyze.py --dry-run # what would be analyzed, how many API calls; no key needed
python3 analyze.py # listings never analyzed (or re-checked after a price drop)
python3 analyze.py --problematic # only listings with spec gaps the AI can fix
python3 analyze.py --force --limit 150 # re-analyze everything, in resumable batches of 150
python3 analyze.py --id 42 # one listing (dashboard # or full id, e.g. tutti_12345)
python3 analyze.py --ids 12,44,tutti_123 # several listings
python3 analyze.py --id-range 10 50 # dashboard ids 10..50
--force and --problematic are mutually exclusive. --id, --ids and --id-range ignore listing status. The log goes to logs/analyze.log.
After changing the parser, config/taxonomy.json, locations or filters, you can re-apply them to every listing already in the database:
python3 scripts/reprocess_all.py --dry-run # show what would change
python3 scripts/reprocess_all.py
The script never touches listings you rejected or marked sold by hand, or listings that are SOLD or DELISTED.
run.py and analyze.py also write index.html, a read-only copy of the dashboard for sharing. The file is gitignored. Use server.py for anything interactive.
python3 debug_listing.py --id 140 # by dashboard id
python3 debug_listing.py --url https://... # by URL
It shows the description extraction for that listing.
To scan every 30 minutes:
*/30 * * * * cd /path/to/ebike-hunter && /usr/bin/env python3 run.py >> logs/scan.log 2>&1
Keep analyze.py manual so its API cost stays predictable.