Subscription & bill statement for bunko.me — renewal radar, trial watch, cost normalization, what-if
  • Python 46.5%
  • HTML 23.8%
  • CSS 23.1%
  • JavaScript 6.3%
  • Dockerfile 0.3%
Find a file
bunko 054002d1c4 Make Ask AI reason about cancellability, not raw price
"What can I cancel to save the most?" was recommending the phone plan — the
biggest line item, but an essential bill, not a cut candidate. Now the model is
told to reason about what each entry IS and how cancellable it realistically is:
housing, utilities, phone/internet, and insurance are treated as essential bills
("shop for a cheaper plan", never "cancel"), and cut suggestions focus on
discretionary subscriptions ranked by savings, weighted toward unused/duplicated/
low-value ones. Essential-category entries are also flagged inline in the context
snapshot so the model has the signal directly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012SpwvqyELkyDN4LaPfpZeH
2026-06-29 12:22:34 -05:00
app Make Ask AI reason about cancellability, not raw price 2026-06-29 12:22:34 -05:00
.env.example Add "Ask AI" — answer free-form questions about your statement 2026-06-29 12:11:48 -05:00
.gitignore Add Stub — subscription & bill statement (bunko.me/stub) 2026-06-24 11:02:53 -05:00
docker-compose.yml Add Stub — subscription & bill statement (bunko.me/stub) 2026-06-24 11:02:53 -05:00
README.md Add Stub — subscription & bill statement (bunko.me/stub) 2026-06-24 11:02:53 -05:00

Stub

A subscription & bill statement for bunko.me — tracks every recurring charge, keeps the running monthly/annual burn, flags free trials before they convert, and pings you (ntfy + Discord) before each charge lands.

Lives at bunko.me/stub, SSO-gated.

What it does

  • Statement dashboard — monthly burn (everything normalized to $/mo), annual total, what's left to post this month, the next charge, and a category breakdown.
  • Renewal radar — a daily scan (08:00 CT) pushes to ntfy + Discord NOTIFY_DAYS days before each charge, plus the morning of. Idempotent: one push per charge.
  • Trial watch — trials ending soon are pushed before they convert to paid.
  • Cost normalization — a $120/yr and an $11/mo plan are compared on equal footing.
  • One-click cancel — store each service's manage/cancel link; it rides along on the row and in the push.
  • What-if — tick rows in the ledger to model dropping them; the burn recomputes live.
  • Variable bills — utilities/usage bills flagged with ~, counted as estimates.

Design

The "STUB" identity: a manila bill statement — postmark cancellation stamp over the next due date (the signature), perforated tear lines, OCR/mono numerals, condensed bureaucratic display type (Saira Condensed). Deliberately not a generic dashboard.

Stack

FastAPI + SQLite + Jinja2, one in-process asyncio loop for the daily radar scan. No external services beyond ntfy/Discord webhooks.

Configure

Copy .env.example to .env and fill in:

var meaning
NTFY_URL base ntfy URL (e.g. https://ntfy.bunko.me)
NTFY_TOPIC topic to publish to
NTFY_TOKEN bearer token (if your ntfy requires auth)
DISCORD_WEBHOOK Discord webhook URL (optional)

Compose-level knobs: NOTIFY_HOUR (default 8), NOTIFY_DAYS (default 3), STUB_TZ (default America/Chicago).

Use the Test radar button in the header to confirm both channels are wired.

Deploy

cd ~/apps/stub
docker compose up -d --build

Then add the Caddy route (see the proxy repo) and restart caddy.

Routes

  • GET / — statement dashboard
  • GET /new, POST /new — add an entry
  • GET/POST /{id}/edit — edit
  • POST /{id}/status — active / trial / paused / cancelled
  • POST /{id}/paid — advance to the next cycle
  • POST /{id}/delete — remove
  • POST /seed — load a sample statement (empty-state only)
  • POST /api/test-notify — fire a test push
  • POST /api/run-checks — run the radar scan now
  • GET /health — healthcheck