whats-on

●live active personal

TV show tracker - What's On tonight?

README

# What's On? πŸ“Ί

A simple, mobile-first TV show tracker. No automatic syncing, no clutter from shared accounts β€” just your shows.

## Features

- 🟒 **Ready to Watch** β€” Shows you're current on with new episodes
- ⏳ **Catching Up** β€” Shows you're behind on
- πŸ“± **Mobile-first** β€” Designed for phone/iPad
- πŸ¦‰ **Henry Integration** β€” Tell Henry what you watched, he updates the tracker

## Tech Stack

- FastAPI + Jinja2
- SQLite
- Tailwind CSS (CDN)
- Deployed on Render

## API Endpoints

- `GET /` β€” Main dashboard
- `GET /api/shows` β€” List all shows
- `POST /api/shows` β€” Add a show
- `PUT /api/shows/{id}` β€” Update a show
- `DELETE /api/shows/{id}` β€” Delete a show
- `POST /api/shows/{id}/caught-up` β€” Mark show as caught up

## Local Development

```bash
pip install -r requirements.txt
python main.py
```

Then open http://localhost:8005
# Trigger redeploy

STATUS

---
type: project-status
project: What's On
last_updated: 2026-06-04
---

# STATUS β€” What's On

*The freshest file. Answers "where am I on this project?" Updated at the end of every substantive session.*

---

## Current state

**Recommender rebuild (Phases 1–6) is merged to `main` and pushed** (merge `e6fdb1d`,
pushed 2026-06-05) β€” Render is redeploying. See [RECOMMENDER_REBUILD.md](RECOMMENDER_REBUILD.md).
22 unit tests passing. Working tree is on `main`; the `recommender-rebuild` branch still
exists locally (can be deleted).

What shipped: `show_tags` + AI tagger (P1-2); signed taste profile + cosine scoring,
`random.random()` and popularity bonus removed, deterministic ranking (P3-4); math-based
why/why-not + cached Henry-voice verdict/risk/plan (P5); match badge + review on the cards
(P6); plus background (non-blocking) cache refresh so page loads never wait on the API.

## In progress / post-deploy checklist (Ken must do these β€” CC can't)

- [ ] **Set `ANTHROPIC_API_KEY` in the Render dashboard** β€” required for tagging/reviews
      in prod. Not in render.yaml; must be added as a dashboard env var.
- [ ] **Confirm `TMDB_API_KEY` is in Render** β€” recs are hidden without it (service filter).
- [ ] **After deploy, POST `/api/admin/retag` once on the live site** to tag the library on
      the Render DB (idempotent, pennies).
- [ ] **Push-away demo:** add a genuine 1–2 rating on a sentimental show (adding `This Is Us`
      at rating 1 is the clean test; baseline: it scored 97), refresh, confirm sentimental
      candidates drop.

## Next up

1. Verify the live site after Render finishes deploying + the steps above.
2. (Optional) `seed_tags.json` so a fresh Render DB isn't empty without the retag call.
3. (Optional) delete the merged `recommender-rebuild` branch.

## Known issues

- Recs require `TMDB_API_KEY` (service filter) β€” invisible locally without it.
- ~6/40 reviews can be `None` after a refresh if Anthropic returns 529; they regenerate
  on the next refre

…(truncated for upload size)

DECISIONS

---
type: project-decisions
project: What's On
last_updated: 2026-06-04
---

# DECISIONS β€” What's On

*Architectural and scope choices. Append-only log. Each entry is a decision that shouldn't be re-litigated without new information. If you find yourself reopening a decision, either add a new entry that overrides the old (and say why) or leave both so the history is visible.*

---

## How to use this file

Each decision gets a dated entry with: what was decided, why, what was considered instead, and what would change our mind. Never delete entries β€” if a decision is reversed, add a new one that supersedes it.

---

## 2026-06-05 β€” Recommender: taste-dimension model with deterministic cosine scoring

**Decision:** Replace the genre-based recommender with an 11-dimension taste model.
Shows/candidates are tagged 0–5 on each dimension (AI, stored in `show_tags`); a signed
taste profile is built from Ken's ratings (`weight = rating βˆ’ 3`); candidates are scored by
cosine similarity to that profile, mapped to 0–100. AI is used only for tagging and for the
Henry-voice verdict/risk/plan; the match score and the why/why-not lines are pure math.

**Context:** The old engine used Trakt genres only (too coarse), let dislikes add instead of
subtract, and injected `random.random()` into the score (noisy, unstable rankings).

**Alternatives considered:** Embeddings/ML similarity β€” rejected; the transparent, explainable
dimension model is the point. Keeping AI in the scoring path β€” rejected; scores must be
deterministic and trustworthy, so AI is confined to tagging and prose.

**Reasoning:** Signed weights make dislikes actively push away along the axes a disliked show
scores high on. Cosine + no random = stable, reproducible rankings (verified identical across
back-to-back refreshes). Math-based explanations are cheap and auditable.

**Would reconsider if:** The dimension set proves too coarse to separate shows Ken rates very
differently, or single-user assumptions break (multi-use

…(truncated for upload size)

MEMORY

---
type: project-memory
project: What's On
last_updated: 2026-06-04
---

# MEMORY β€” What's On

*Standing facts, preferences, and accumulated context. Long-lived β€” not "what I did yesterday" (that's STATUS.md). Update when you learn something worth keeping.*

---

## Purpose and scope

A personal TV-show tracker for Ken β€” single user, no login. Answers "what's on / what
should I watch next?" on a phone or iPad. Tracks watch progress, surfaces taste-aware
recommendations, and pulls air days/posters from external APIs. Standalone personal
app, not part of the tax suite.

## Domain knowledge

- **Show categories on the home page** (computed in `main.py:home`):
  - `between_seasons` β€” `status == 'hiatus'`
  - `backup_shows` β€” `priority == 3` ("watch if nothing else is on")
  - `catching_up` β€” `current_episode != 99` and `status == 'watching'`
  - `priority_shows` β€” everything else (the "ready to watch" hero list)
- **`current_episode = 99` is a sentinel meaning "caught up"** β€” not a real episode
  number. Lots of logic keys off this; don't treat 99 as data.
- **Statuses:** `watching`, `current`, `hiatus`, `dropped` (`VALID_STATUSES`).
  `dropped` shows are hidden from `get_all_shows()`.
- **Priority** 1–5 (1 = highest). **Rating** 1–5 (drives recommendation weighting;
  unrated defaults to 2).
- **Services** Ken uses (`USER_SERVICES`): Max, Apple TV+, Hulu, Peacock, Paramount+,
  Prime Video, Netflix. Plus Disney+/Other allowed as `VALID_SERVICES`.

## User preferences discovered

- Wants the standard four project files (CLAUDE/MEMORY/STATUS/DECISIONS) maintained and
  kept current as the repo changes β€” established 2026-06-04 when cloning the repo local.
- Mobile-first, low-clutter UI. Brand-colored service badges, "yeti/Henry" mascot.

## Integrations and external systems

- **Trakt API** (`api.trakt.tv`) β€” show search, full details (air day/time, genres,
  next episode), and `related` shows for collaborative recommendations. Auth via
  `trakt-api-key` header; client I

…(truncated for upload size)

CLAUDE.md

# CLAUDE.md β€” What's On

*Project-specific rules. User-level conventions come from `~/.claude/CLAUDE.md` β€” do not duplicate here.*

---

## What this is

"What's On?" β€” a personal, mobile-first TV show tracker for Ken. Tracks what he's
watching, what he's caught up on, what's on hiatus, and surfaces taste-aware
recommendations. Single-user, no auth, no clutter. **This is a standalone personal
app β€” NOT part of the Sherpa tax suite.**

## Tech stack

- **Backend:** FastAPI (Python) β€” `main.py` (routes) + `database.py` (data layer)
- **Frontend:** Server-rendered Jinja2, single template `templates/index.html`; Tailwind CSS via CDN
- **Database:** Dual-mode β€” SQLite locally, PostgreSQL (psycopg3) on Render when `DATABASE_URL` is set
- **Hosting:** Render (`render.yaml`); SQLite on a 1GB persistent disk by default
- **Dependencies:** pip (`requirements.txt`) β€” not Poetry

## Session startup

Read all four root files (CLAUDE.md, MEMORY.md, STATUS.md, DECISIONS.md) at the
start of substantive work. No other required reading.

## Conventions

- This app does **not** inherit the Sherpa-suite database/color rules from
  `~/.claude/CLAUDE.md`. Specifically:
  - **No shared Supabase DB.** It owns its own SQLite/Postgres database. No `firm_id`,
    no central `clients` table.
  - **No tax data-entry color system** (red/yellow/green). UI colors here are
    service-brand colors (Max, Hulu, etc.) in `index.html`.
- Tests, if added, go in `tests/` as `test_{module}.py` (pytest) β€” per global rules.
- Keep the SQLite/Postgres dual-path working: `database.py` branches on `DATABASE_URL`.
  Any new query must use the `_ph()` placeholder helper so it works on both engines.

## Do not redesign without asking

- The category logic on the home page (priority / backup / catching-up / between-seasons)
  and the `current_episode = 99` "caught up" sentinel. Both are load-bearing.
- The recommendation engine scoring in `refresh_recommendation_cache()` β€” taste-aware,
  genre-weighted, collaborat

…(truncated for upload size)

Diary mentions

No recent diary mentions for this app.

Render