Skip to content

chrisgleissner/callback-tracker

Repository files navigation

Callback 8020 Tracker

Live sales dashboard for the Callback 8020 flip-phone, scraped from the Commodore store once every 10 minutes.

Build codecov Python 3.8+ No dependencies License: MIT

Every sample is stored in SQLite, so the tracker survives restarts and resumes without losing history. The dashboard is a zoomable Plotly chart with one-click PNG export.

Lite Mode

In this screenshot, we enabled monotone (i.e. smooth) lines. Beneath the sales chart is a heat chart that shows strong sales performance in red and slower performance in blue.

Dashboard

Dark Mode

Here's a screenshot where we enabled stepped lines as well as the burn-down of sales batches with its legend shown on the right-hand Y axis. Each batch consists of 500 phones.

Dashboard

Quick start

./build            # run tests (default task)
./build run        # start the dashboard at http://localhost:8020

Requires Python 3.8+ (3.9+ recommended). The dashboard loads Plotly from a CDN, so the viewing browser needs internet access. No pip install is required.

Tasks

./build                    # run tests
./build run [port] [args]  # start dashboard locally, forwarding extra tracker.py args
./build once               # scrape once, print JSON (cron mode)
./build png [--out F.png]  # export a chart PNG (or HTML snapshot if plotly is absent)
./build db stats           # row count, range, latest values
./build db export --fmt csv --out sales.csv
./build db trim --keep 1000
./build db reset
./build deploy-pi          # deploy to the Raspberry Pi and restart it
./build pi-status          # tracker status on the Pi

Deploy to a Raspberry Pi

The tracker runs headless on a Pi and auto-starts at boot via an @reboot crontab (rootless — no sudo needed). Passwordless SSH to the Pi is assumed.

./build deploy-pi          # copies code, restarts, refreshes the cron entry

Defaults: host pi, remote dir ~/callback-tracker, port 8020. Override with PI_HOST, PI_DIR, PI_PORT. Discoverable on the Pi via ~/CALLBACK-TRACKER.txt and ~/callback-tracker/status.sh. History (data.db) is preserved across deploys.

How it works

The store page renders a live "units left in Batch N" widget in its server-side HTML, so a plain HTTP fetch is enough — no browser, no JS, no API key:

<strong class="commobot-battery__count">272</strong>
<span class="commobot-battery__units-label"> / 500 pre-order units left in <b>Batch 5</b></span>

From that: units_left=272, batch_size=500, batch=5, sold_in_batch=228, and cumulative sold = (batch − 1) × batch_size + sold_in_batch = 2228 — so already sold-out batches (1–4) are counted automatically.

Architecture

Layered, single process, zero dependencies:

  • scraper.pyfetch() the store page, parse() the widget HTML, derive cumulative_sold.
  • store.py — thread-safe SQLite; one row per sample, upserted by minute, so re-scraping is idempotent.
  • server.py — serves dashboard.html and a small JSON API.
  • app.py — runs the scraper on a daemon thread alongside the HTTP server; both share one Store behind a single lock, so reads and writes never race.
  • admin.py — a separate CLI for PNG export and DB maintenance (stats, export, trim, reset).
  • config.py — env-driven defaults (LAUNCH_TIME, BASE_SOLD, BATCH_SIZE), imported by every module above and importing none of them back.

API: GET /api/history (full history), /api/latest, /api/meta, /api/scrape (scrape now).

The dashboard is static HTML — it polls /api/history and renders the chart entirely client-side with Plotly from a CDN; the server does no templating. This shape follows from the source: the store page server-renders its data, so a plain fetch stands in for a headless browser, and one scrape every 10 minutes is light enough that a single SQLite writer is all the database this needs.

Configuration (env)

env default meaning
LAUNCH_TIME 2026-06-30 08:00 UTC (hard-coded) launch anchor on the chart; all times shown in UTC
BASE_SOLD auto: (batch−1) × batch_size absolute units sold before the current batch
BATCH_SIZE the size seen on the page (500) override the batch size used by the auto calculation

Project layout

callback_tracker/   scraper, store, server, admin, app, config
tests/              stdlib unittest (parser + store), no dependencies
tracker.py          entry point (scripts / cron / `./build run`)
dashboard.html      Plotly dashboard served at /
build               task runner

About

Tracks sales stats of the Commodore Callback 8020

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors