election-tracker
active personal privateGeorgia 2026 primary winner predictor (Flask + weighted scoring model)
- Primary: https://github.com/klill6506/election-tracker
- GitHub: https://github.com/klill6506/election-tracker
- Local:
D:\Personal\election-tracker
README
# sherpa-dispatch
2026 general-election predictor — November 3, 2026.
Weighted prediction model for ~65 competitive U.S. Senate and U.S. House races. Per-race inputs come from Cook Political Report (rating + PVI), Kalshi (prediction-market prices), and structural signals (incumbency). Weights are slider-tweakable. iPad-first.
## Stack
- **Backend:** Flask 3 + PyYAML (production), `ruamel.yaml` + `requests` (data refresh)
- **Frontend:** Vanilla HTML/CSS/JS (no build step)
- **Hosting:** Render free tier, Ohio region (`render.yaml` Blueprint)
- **Data:** YAML files in `data/`; auto-refreshed by GitHub Actions crons
## Local run (Windows 11)
```powershell
cd D:\Personal\election-tracker
.\venv\Scripts\Activate.ps1
python app.py
# http://localhost:5000
```
## Deploy
Push to `main` → Render auto-redeploys via Blueprint.
## Data flow
```
Cook Political Report API ──► scripts/refresh_cook.py ─┐
├──► data/general_races.yaml
Kalshi public REST API ──► scripts/refresh_kalshi.py ─┘ │
▼
data/general_overrides.yaml (pin / exclude / kalshi_markets) app.py + scoring/general_engine.py
│
▼
templates/dashboard.html
```
Both refresh scripts run daily on GitHub Actions:
- `refresh-cook.yml` — 13:30 UTC (requires `COOK_API_EMAIL` + `COOK_API_PASSWORD` secrets)
- `refresh-kalshi.yml` — 14:30 UTC (no auth needed)
To pin or exclude races, or to set challenger/open-seat nominee names, edit [`data/general_overrides.yaml`](data/general_overrides.yaml). Kalshi (Senate) needs no mapping — it's auto-discovered from each race's state.
## Model — two whole-seat forecasts
Each chamber shows two forecasts, both built from Cook ratings:
- **Cook forecast
…(truncated for upload size)
STATUS
# STATUS.md — election-tracker Updated **2026-05-18 (evening)** — Phase 2 cutover landed. ## Done - v1 build (coworker, 2026-05-05): Flask + scoring engine + dashboard + slider UI + iPad styling. - Code review fixes (2026-05-06): - `debug=True` → env-driven `FLASK_DEBUG`. - `/api/recalculate` payload validation (type, allowed keys, range 0–100). - YAML loads wrapped in try/except; 503 instead of crash on missing/corrupt files. - Project hygiene: `.gitignore`, four memory files, README path corrected. - Polling auto-refresh (2026-05-16, branch `claude/add-polling-data-WHiQh`): - Adds `scripts/refresh_polls.py` and `data/polling_sources.yaml`. - Pulls latest poll percentages from Wikipedia poll tables for the three races that actually get polled (US Senate R, Governor R, Governor D). - `.github/workflows/refresh-polls.yml` runs daily at 14:00 UTC + on demand. - 23 offline parser tests cover heading discovery, alias matching, percent parsing, and YAML application. Run with `python -m unittest tests.test_refresh_polls`. - FEC fundraising auto-refresh landed on `main` (commits `ae1bb3c`, `94f9622`). - **v2 Phase 1 — Cook + Kalshi data ingestion (2026-05-18 morning, commits `4d39d64`, `04eb1de`):** - `scripts/refresh_cook.py` + `scripts/refresh_kalshi.py`, `data/general_races.yaml` (auto), `data/general_overrides.yaml` (hand). - GH Actions crons: `refresh-cook.yml` 13:30 UTC daily, `refresh-kalshi.yml` 14:30 UTC daily. - GH secrets `COOK_API_EMAIL` + `COOK_API_PASSWORD` configured. First successful run seeded 11 Senate + 32 House competitive races (auto-commit `301e31a`). - **v2 Phase 2 — full general-election cutover (2026-05-18 evening):** - **Decision changed:** originally targeted for 2026-06-16 post-runoff; Ken chose to do it the day before the primary instead. - **New scoring engine** `scoring/general_engine.py`: signed-lean over four inputs (Cook rating, Cook PVI, Kalshi, incumbency) → sigmoid(score/15) → P(D wins). - **New dashboard:** …(truncated for upload size)
DECISIONS
# DECISIONS.md — election-tracker Architectural choices, recorded once. Don't re-litigate without new information. ## 2026-05-05 — Flask + vanilla HTML/CSS/JS over a build step **Decision:** Server-render Jinja2 templates; vanilla JS for slider interactivity. No React, Vite, or bundler. **Rationale:** Single-evening build. Scope is small (one page, one form). A build pipeline would add weight without delivering value. Vanilla JS is enough for the slider→fetch→render loop. **Reconsider only if:** the UI grows to multiple distinct pages with shared client state. ## 2026-05-05 — YAML files over a database **Decision:** Store all race/candidate data and default weights in `data/*.yaml`, version-controlled in git. **Rationale:** Data is small (~20 KB), changes infrequently (weekly), and is curated by one person. A database adds operational complexity (provisioning, migrations, backups) for zero gain. Git history doubles as an audit log of data edits. Render redeploys on push, so YAML changes flow live with no extra plumbing. **Reconsider only if:** multiple curators need to edit concurrently, or update frequency exceeds daily. ## 2026-05-06 — Render free tier, Ohio region, Blueprint deploy **Decision:** Use Render's `render.yaml` Blueprint to deploy the Flask app on the free web-service tier in the Ohio region. **Rationale:** Free, push-to-deploy, gunicorn-friendly, no Docker. Ohio is fine for an iPad-viewer in Georgia. The 15-min idle spin-down is acceptable for personal/low-traffic use. **Reconsider only if:** cold-start latency becomes annoying ($7/mo Starter eliminates it) or owner wants always-on monitoring. ## 2026-05-06 — Private GitHub repo **Decision:** Repo `election-tracker`, private. **Rationale:** This contains a personal predictive model and curated race notes — not sensitive but not meant to be a public artifact. Easy to flip public later if desired. ## 2026-05-06 — Federal vs. state campaign-finance jurisdiction **Decision:** The FEC auto-refresh co …(truncated for upload size)
MEMORY
# MEMORY.md — election-tracker Accumulated context for future sessions. ## Origin - Built by a coworker in a single evening on **2026-05-05**. - Owner (Ken) reviewed it on **2026-05-06**, fixed three deploy blockers (debug=True, missing input validation, no YAML error handling), and pushed to GitHub + Render the same day. - Public-facing internal name: **sherpa-dispatch**. Folder/repo name: **election-tracker**. ## Scope (v1) - **Modeled (10):** GA Senate, Governor, Lt Gov, SoS, AG (5 statewide) + GA-01, 10, 11, 13, 14 (5 competitive House districts). - **Presumptive (9):** GA-02, 03, 04, 05, 06, 07, 08, 09, 12 — incumbents shown collapsed. - **Primary date:** 2026-05-19. App was originally built ~2 weeks before this. ## v2 history (2026-05-18 — both phases landed same day) - **Phase 1 (morning):** data ingestion. Cook + Kalshi fetchers + new YAML schema + GH Actions crons. Pure addition; primary view untouched. - **Phase 2 (afternoon):** full general-election cutover, ~1 month earlier than originally planned (was 2026-06-16). Ken explicitly chose "Full Phase 2 now — retire primary scoring, swap the dashboard" knowing primary was 24h away. Primary scoring + FEC + Wikipedia refreshers retired entirely. - **Current scope:** ~65 competitive 2026 general-election races (11 Senate, 32 House at time of cutover) per Cook's competitive list. November 3, 2026 target. - **Model:** sigmoid-of-weighted-sum over four signed-lean inputs (Cook rating, Cook PVI, Kalshi market price, incumbency). See `scoring/general_engine.py`. - **Followups:** populate `data/general_overrides.yaml::kalshi_markets` as tradeable markets are discovered on kalshi.com. Add head-to-head polling later if a clean upstream source is found (Wikipedia multi-row colspan parser still unbuilt). ## Data sources reality (verified 2026-05-18 after Phase 2 cutover) - **Cook Political Report API:** Paid (Ken's subscription, started 2026-05-18). Daily ratings + Cook PVI for House and Senate. Rate-limited to 1 ca …(truncated for upload size)
CLAUDE.md
# CLAUDE.md — election-tracker Project-specific rules for Claude. See `~/.claude/CLAUDE.md` for global standards. ## What this is Internal name **sherpa-dispatch**. A Flask app that predicts the **November 3, 2026 general election** for ~65 competitive U.S. Senate + U.S. House races. Sigmoid-of-weighted-sum prediction model over four signed-lean inputs (Cook rating, Cook PVI, Kalshi market price, incumbency). Live-tweakable weights via slider UI. The original GA-primary scoring model was retired on 2026-05-18 (v2 Phase 2 cutover). See DECISIONS.md for the architectural rationale. ## Stack - **Backend:** Flask 3.0 + PyYAML 6.0 + gunicorn 21.2 - **Frontend:** Vanilla HTML/CSS/JS — no build step, no framework - **Hosting:** Render (free tier, Ohio region) via `render.yaml` Blueprint - **Data:** YAML files (`data/general_races.yaml` auto-generated, `data/general_overrides.yaml` hand-maintained, `data/weights.yaml` defaults). No database. - **Refresh:** GitHub Actions crons run `scripts/refresh_cook.py` (Cook API) and `scripts/refresh_kalshi.py` (Kalshi public API) daily. - **Python:** 3.12 ## Conventions - Data lives in YAML, never inline in Python. Code in `app.py` and `scoring/general_engine.py` only reads it. - Weights must be 0–100 numbers. The validator in `app.py` enforces this. Weight keys: `cook`, `pvi`, `kalshi`, `incumbency`. - Kalshi weight auto-redistributes when no market is mapped for a race — preserve this behavior. Same pattern the retired primary engine used for polling. - `data/general_races.yaml` is **auto-generated** by `scripts/refresh_cook.py` and `scripts/refresh_kalshi.py`. Do not hand-edit. - `data/general_overrides.yaml` is the hand-maintained sidecar: `pin` (force-include), `exclude` (force-remove), `kalshi_markets` (race_id → Kalshi ticker, plus optional `yes_party`). ## Do not redesign without explicit ask The iPad-first dashboard (`templates/dashboard.html` + `static/css/style.css`) and the slider/recalc UX are the product. Don't refa …(truncated for upload size)
Diary mentions
No recent diary mentions for this app.