Live Marketplace Price Tracker
In progressA full-stack price-tracking tool for Old School RuneScape's Grand Exchange, built on the OSRS Wiki's live pricing API. A scheduled background job continuously pulls price data for roughly 4,450 tradeable items into PostgreSQL, and a React frontend gives fast, searchable access to it. Each item has its own historical price and trading volume charts, so you can track how it's moved over time. The app also surfaces the margin between buy and sell price for each item, making it easy to spot which items are actually worth flipping on the Grand Exchange right now rather than just seeing raw prices.
Roadmap
3 of 22 completeA sequenced plan for turning the price table into a trading tool, ordered so each phase unblocks the next.
Land what's already in flight
Finish the quota fix and close the blast radius before building anything new on top of it.
- Done
Ship the batching branch
The bulk upsert rewrite, gzip, the history cache, /healthz and the lifespan migration. Done when a poll emits 5 INSERT statements, not 4,450.
- Done
Repoint the keep-alive at /healthz
A cron ping hitting a database-backed route every 10 minutes defeats scale-to-zero on its own, no matter how fast ingestion gets.
- Done
Cap the Neon blast radius
Set max autoscale to 0.25 CU on the existing compute, then add a consumption quota as a tripwire. Turns a worst case of $1,238/month into $19.
- In progress
Logo and Favicon to replace default Vite
Use claude code design to create new logo and favicon to replace default images.
- Planned
Bound page and page_size
Both are currently unbounded. ?page_size=100000 pulls the whole table. Neither needs an attacker, just a curious person with the URL.
- Planned
Cache /api/prices/latest in process
Same bounded-LRU pattern as the history endpoint, keyed on query parameters. Decouples request volume from database load entirely.
Make the numbers true
Correctness before features. Everything downstream ranks on these numbers, so they have to be right first.
- Planned
Grand Exchange tax in the margin
2% of the sale price, rounded down, capped at 5,000,000 per item, with an exempt-item list. Rank on post-tax margin, not the raw spread.
- Planned
ROI as a column
A 50,000gp margin on a 2.7M item and on a 300k item are not the same trade. Percentage return is what makes them comparable.
- Planned
First tests
The tax calculation is pure arithmetic and _build_records is a pure function. The easiest possible place to start, with no database container.
Turn the table into a tool
This is the phase that answers “why not just use the wiki”. Almost all of the data is already on disk.
- Planned
Alembic — do this first
Blocks everything else in this phase. create_all only creates missing tables; it will never add a column to an existing one.
- Planned
Ingest the fields you already download
mappingsData.json carries limit, highalch, lowalch, value and members. You use icon and id. Buy limits exist for 4,126 of 4,589 items.
- Planned
Potential profit per 4 hours
Post-tax margin × buy limit. The metric flippers actually rank by, and almost nothing surfaces it well.
- Planned
Volume, and a staleness filter
A wide spread on four trades a day is a trap, and a margin on an item last traded three days ago is fiction.
- Planned
Alch profit view
highalch − instasell − nature rune price. A second product for a different player, from data already in the table.
Make the repo read well
Cheap, quick, and it is the first thing a reviewer forms an impression from.
- Planned
ruff plus a pre-commit hook
Three modules are indented with tabs, one with spaces. It reads as three people who never spoke. Fix it once, automate it, stop thinking about it.
- Planned
GitHub Actions
Run the Phase 1 tests and the linter on every push. A green badge in the README is worth more than another feature bullet.
- Planned
Structured logging instead of print
Log poll duration and row count so the next regression shows up as a number, not a bill.
- Planned
Self-host the item icons
Icons are hotlinked straight from the wiki, so someone else's bandwidth pays for every page load — and a wiki that starts blocking hotlinking would silently break every icon in the table. All 4,589 are a few hundred bytes each, so caching them is a handful of megabytes that Vercel serves from its CDN for free. Removes a dependency you don't control.
- Planned
Resolve item_time_stamp
Defined in models.py, given a schema, never written to and never read. Dead schema reads as abandoned work.
History, cheaply
The “find an edge in the market” goal, sized to fit the budget.
- Planned
Daily aggregates, not full timeseries
One row per item per day rather than every five-minute tick. About 200 MB a year — roughly 7 cents a month. Raw ticks would blow the budget in weeks.
- Planned
Spread versus its own 30-day norm
Not “this margin is big” but “this margin is unusually big for this item”. The genuinely novel bit.
- Planned
Sparkline per row
Cheap once the daily table exists, and it makes the list scannable without opening a chart for every item.