A reusable Flask + HTMX app that scores a curated shortlist of family
vehicles against weighted criteria, with live links to AutoTrader and
Craigslist listings. Built originally for the kaan-and-tess household
in Vancouver BC; structured to run as one repo serving multiple
families, each at their own domain (VEHICLE_DATA_DIR selects the
family). Currently assumes every family is in BC — see the note under
"Use for a different family".
dasAuto/
├── CLAUDE.md # architectural rules (authoritative on structure)
├── docs/ # ARCHITECTURAL docs — apply to every family
│ ├── new_family_playbook.md # the buildout sequence for a new family
│ ├── scoring_framework.md # the 9 criteria + rubric + formula
│ ├── tco_methodology.md # NPV, warranty cliff, residual math
│ ├── brand_repair_tail.md # OOW maintenance multipliers
│ ├── tax_math_canada.md # BC PST tiers, GST, dealer fees
│ ├── selection_principle.md # hard-filter vs scored-criterion rule
│ └── multi_family_todo.md # generalisation status (done + deferred)
├── vehicle-app/ # the SKILL — reusable across families
│ ├── app.py # Flask routes; reads $VEHICLE_DATA_DIR
│ ├── scoring.py # criteria + weighted-sum framework
│ ├── tco.py # NPV-based TCO computation
│ ├── repair_ratios.json # brand×powertrain OOW-maintenance multipliers
│ ├── scrapers/ # AutoTrader + Craigslist + image fetcher
│ ├── templates/, static/, tests/test_smoke.py
│ ├── briefs/ # research-brief templates to seed a new family
│ └── run.sh, "Launch Vehicle Browser.bat"
└── user-<family>/ # one folder per family (e.g. user-kaan-and-tess)
├── family.md # PURE FAMILY INPUT — the canonical brief
├── site.json # display identity (name, region, insurance ref)
├── weights.json # default importance weights
├── vehicles.json # the cohort, with per-criterion scores
├── image_seeds.json # curated image URLs (input to fetch_images)
├── manual_listings.json # AI-curated market listings
├── dealer_quotes/<id>.json # one JSON per active dealer quote
├── images/ # downloaded galleries
└── research/ # AI-generated deliverables
├── tco_research.md, cohort_rationale.md, decisions_log.md
The skill folder is portable: nothing inside it names a specific family.
Every per-family input/output lives in user-<family>/. The split
between architectural and family-specific knowledge is enforced — see
CLAUDE.md.
macOS / Linux:
bash vehicle-app/run.shWindows: double-click vehicle-app\Launch Vehicle Browser.bat.
First run creates a .venv, installs deps, downloads ~68 vehicle images
from Wikimedia, and starts the server. Subsequent runs reuse all of
that. Defaults to http://localhost:5000; falls back to 5057/5500/8080
on macOS when AirPlay holds 5000. Pin with PORT=5500 bash ….
The full sequence (intake → criteria → cohort → TCO → images → deploy)
is in docs/new_family_playbook.md — follow it end-to-end. The
short version:
- Create
user-<their-label>/. - Have the family fill in
family.mdfromvehicle-app/briefs/family_brief_template.md. This is canonical input — don't edit it later; clarifications go toresearch/decisions_log.md. - Add
site.json(display name, region label, insurance ref) and build outweights.json,vehicles.json,image_seeds.json, andresearch/tco_research.md. A vehicle that omitsmaint_oow_per_yearinherits the brand×powertrain default fromrepair_ratios.json; set it explicitly only for a per-vehicle age/km premium. - Run with the new data dir:
VEHICLE_DATA_DIR=$(pwd)/user-<their-label> bash vehicle-app/run.sh
- To deploy, provision a separate Railway service pointed at the new folder and CNAME a subdomain (see "Deploying to Railway").
BC-only for now. The tax engine, listing-scope filter, and
insurance references assume British Columbia. If site.json sets
region_code to anything other than BC, the app logs a startup
warning — that's the signal to generalise the items tracked in
docs/multi_family_todo.md before trusting all-in pricing.
/— ranked card grid; edit weights in the sidebar to re-rank live/vehicle/<id>— full profile, gallery, scores, listings/compare?ids=<a>&ids=<b>...— side-by-side, up to 4 vehicles/siennas— Sienna AWD-Hybrid cross-shop table (kaan-and-tess; will generalise to/cross-shop/<vehicle-id>once a second family converges — seedocs/multi_family_todo.md)/active/<id>— archive/restore a vehicle from the web UI/health— image count, listing cache, paths/api/vehicles?w_<key>=<val>...— ranked JSON list under chosen weights
Customised weights flow through every link via ?w_<key>=<value> query
params, so a rerank on the index persists into detail and compare pages.
cd vehicle-app && .venv/bin/python -m unittest tests.test_smoke17 smoke tests cover every route, weight threading, custom-weight rerank behaviour, the variable-horizon TCO model (warranty cliff, per-year maintenance, renormalisation), garbage-input tolerance, and the JSON API.
Delete user-kaan-and-tess/images/ and re-run the launcher — it'll
re-fetch from image_seeds.json. To swap individual images, edit the
URLs in the seed file first.
The repo is set up to auto-deploy to Railway on push to main. The
moving parts that matter:
Procfileat the repo root tells Railway tocd vehicle-app && gunicorn app:app ….requirements.txtat the repo root re-exportsvehicle-app/requirements.txtso Nixpacks auto-detects Python.- Images ship in the git tree (no longer gitignored) so the container doesn't depend on Wikimedia at boot.
cache.db(favourites + per-vehicle notes + listings cache) must live on a persistent volume — see env-var setup below.
- Create project → Deploy from GitHub repo → pick
kaanow/dasAuto, branchmain. Railway will run the first build automatically. - Settings → Networking → Generate Domain. You'll get a
<project>.up.railway.appURL. - Add a Volume: Settings → Volumes → + New Volume, mount path
/data, size 1 GB. (~$0.25/month at Railway's rate.) - Set environment variables (Settings → Variables):
VEHICLE_DB_PATH=/data/cache.db— points the app at the volume so favourites/notes survive every deploy.PYTHONUNBUFFERED=1— makes gunicorn access logs flush immediately so Railway's log viewer is live.VEHICLE_DATA_DIR— the family folder this service serves (e.g.user-kaan-and-tessoruser-theo). Leave unset only for the default kaan-and-tess service. Each family runs as its own Railway service off the same repo/branch, differentiated by this var, with its own volume and CNAME'd subdomain (dasauto.alti2.de→ kaan-and-tess,brudersauto.alti2.de→ theo).
- Trigger redeploy (Deployments → Redeploy) so the new env vars
take effect. Subsequent pushes to
mainauto-deploy.
The intended workflow:
- Edit
vehicles.json,weights.json,image_seeds.json, or any prose locally (Claude or your editor). - If you added a vehicle, run
python vehicle-app/scrapers/fetch_images.pyto pull its images intouser-kaan-and-tess/images/. git push origin main→ Railway rebuilds and serves the new version in ~60 s.
Notes and favourites added through the deployed UI live on the volume and are NOT version-controlled — they survive deploys but not volume deletion. Vehicle data is canonical in git.
vehicles.json entries can carry "active": false to drop them from
the index, /compare, /api, and TCO normalization without deleting them
from the file. Direct /vehicle/<id> URLs still resolve (the detail
page renders with "Archived — not in active cohort" in place of the
rank). Flip the flag back to true to re-enable.