Update ARCHITECTURE.md and roadmap to reflect v0.4.0

Architecture doc: add notifications table, v0.4.0 feature history, updated
beat schedule (fetch-actions-active-bills, dispatch-notifications), expanded
admin API table (17 endpoints), new /api/notifications section, BriefPanel
and ActionTimeline component docs, chamberBadgeColor utility, migrations
0010-0011, live LLM model picker endpoint, queue routing corrections.

Roadmap: mark Phase 1 notifications complete, check off LLM model picker,
admin health panel, chamber badges, action history fallback, backfill all
actions, brief regeneration UI; remove completed Phase 1 section.

Authored-By: Jack Levy
This commit is contained in:
Jack Levy
2026-03-01 11:45:21 -05:00
parent f3a8c1218a
commit 91790fd798
2 changed files with 217 additions and 35 deletions

View File

@@ -140,7 +140,7 @@ OPENAI_MODEL=gpt-4o
ANTHROPIC_API_KEY= ANTHROPIC_API_KEY=
ANTHROPIC_MODEL=claude-opus-4-6 ANTHROPIC_MODEL=claude-opus-4-6
GEMINI_API_KEY= GEMINI_API_KEY=
GEMINI_MODEL=gemini-1.5-pro GEMINI_MODEL=gemini-2.0-flash
OLLAMA_BASE_URL=http://host.docker.internal:11434 OLLAMA_BASE_URL=http://host.docker.internal:11434
OLLAMA_MODEL=llama3.1 OLLAMA_MODEL=llama3.1
@@ -247,17 +247,58 @@ Indexes: `bill_id`, `topic_tags` (GIN for JSONB containment queries)
### `members` ### `members`
Primary key: `bioguide_id` (Congress.gov canonical identifier). Primary key: `bioguide_id` (Congress.gov canonical identifier).
| Column | Type | | Column | Type | Notes |
|---|---| |---|---|---|
| bioguide_id | varchar (PK) | | bioguide_id | varchar (PK) | |
| name | varchar | | name | varchar | Stored as "Last, First" |
| first_name / last_name | varchar | | first_name / last_name | varchar | |
| party | varchar | | party | varchar | |
| state | varchar | | state | varchar | |
| chamber | varchar | | chamber | varchar | |
| district | varchar (nullable, House only) | | district | varchar (nullable) | House only |
| photo_url | varchar (nullable) | | photo_url | varchar (nullable) | |
| created_at / updated_at | timestamptz | | official_url | varchar (nullable) | Member's official website |
| congress_url | varchar (nullable) | congress.gov profile link |
| birth_year | varchar(10) (nullable) | |
| address | varchar (nullable) | DC office address |
| phone | varchar(50) (nullable) | DC office phone |
| terms_json | json (nullable) | Array of `{congress, startYear, endYear, chamber, partyName, stateName, district}` |
| leadership_json | json (nullable) | Array of `{type, congress, current}` |
| sponsored_count | int (nullable) | Total bills sponsored (lifetime) |
| cosponsored_count | int (nullable) | Total bills cosponsored (lifetime) |
| detail_fetched | timestamptz (nullable) | Set when bio detail was enriched from Congress.gov |
| created_at / updated_at | timestamptz | |
Member detail fields (`congress_url` through `detail_fetched`) are populated lazily on first profile view via a Congress.gov detail API call. The `detail_fetched` timestamp is the gate for scheduling member interest scoring.
### `member_trend_scores`
One record per member per day (mirrors `trend_scores` for bills).
| Column | Type | Notes |
|---|---|---|
| id | int (PK) | |
| member_id | varchar (FK → members, CASCADE) | bioguide_id |
| score_date | date | |
| newsapi_count | int | Articles from NewsAPI (30-day window) |
| gnews_count | int | Articles from Google News RSS |
| gtrends_score | float | Google Trends interest 0100 |
| composite_score | float | Weighted combination 0100 (same formula as bill trend scores) |
Unique constraint: `(member_id, score_date)`. Indexes: `member_id`, `score_date`, `composite_score`.
### `member_news_articles`
News articles correlated to a specific member of Congress.
| Column | Type | Notes |
|---|---|---|
| id | int (PK) | |
| member_id | varchar (FK → members, CASCADE) | bioguide_id |
| source | varchar | News outlet |
| headline | text | |
| url | varchar | Unique per `(member_id, url)` |
| published_at | timestamptz | |
| relevance_score | float | Default 1.0 |
| created_at | timestamptz | |
--- ---
@@ -269,7 +310,8 @@ Primary key: `bioguide_id` (Congress.gov canonical identifier).
| email | varchar (unique) | | | email | varchar (unique) | |
| hashed_password | varchar | bcrypt | | hashed_password | varchar | bcrypt |
| is_admin | bool | First registered user = true | | is_admin | bool | First registered user = true |
| notification_prefs | jsonb | Future: ntfy, Telegram, RSS config | | notification_prefs | jsonb | ntfy topic URL, ntfy auth token, ntfy enabled, RSS token |
| rss_token | varchar (nullable) | Unique token for personal RSS feed URL |
| created_at | timestamptz | | | created_at | timestamptz | |
--- ---
@@ -296,7 +338,7 @@ Unique constraint: `(user_id, follow_type, follow_value)`
| bill_id | varchar (FK → bills, CASCADE) | | | bill_id | varchar (FK → bills, CASCADE) | |
| source | varchar | News outlet | | source | varchar | News outlet |
| headline | varchar | | | headline | varchar | |
| url | varchar (unique) | Deduplication key | | url | varchar | Unique per `(bill_id, url)` — same article can appear across multiple bills |
| published_at | timestamptz | | | published_at | timestamptz | |
| relevance_score | float | Default 1.0 | | relevance_score | float | Default 1.0 |
| created_at | timestamptz | | | created_at | timestamptz | |
@@ -347,6 +389,22 @@ Key-value store for runtime-configurable settings.
--- ---
### `notifications`
Stores notification events for dispatching to user channels (ntfy, RSS).
| Column | Type | Notes |
|---|---|---|
| id | int (PK) | |
| user_id | int (FK → users, CASCADE) | |
| bill_id | varchar (FK → bills, SET NULL) | nullable |
| event_type | varchar | e.g. `new_brief`, `bill_updated`, `new_action` |
| headline | text | Short description for ntfy title |
| body | text | Longer description for ntfy message / RSS content |
| dispatched_at | timestamptz (nullable) | NULL = not yet sent |
| created_at | timestamptz | |
---
## Alembic Migrations ## Alembic Migrations
| File | Description | | File | Description |
@@ -357,6 +415,11 @@ Key-value store for runtime-configurable settings.
| `0004_add_brief_type.py` | BillBrief.brief_type column (`full`/`amendment`) | | `0004_add_brief_type.py` | BillBrief.brief_type column (`full`/`amendment`) |
| `0005_add_users_and_user_follows.py` | users table + user_id FK on follows; drops global follows | | `0005_add_users_and_user_follows.py` | users table + user_id FK on follows; drops global follows |
| `0006_add_brief_govinfo_url.py` | BillBrief.govinfo_url for frontend source links | | `0006_add_brief_govinfo_url.py` | BillBrief.govinfo_url for frontend source links |
| `0007_add_member_bio_fields.py` | Member extended bio: `congress_url`, `birth_year`, `address`, `phone`, `terms_json`, `leadership_json`, `sponsored_count`, `cosponsored_count`, `detail_fetched` |
| `0008_add_member_interest_tables.py` | New tables: `member_trend_scores`, `member_news_articles` |
| `0009_fix_news_articles_url_uniqueness.py` | Changed `news_articles.url` from globally unique to per-bill unique `(bill_id, url)` |
| `0010_backfill_bill_congress_urls.py` | Backfill congress_url on existing bill records |
| `0011_add_notifications.py` | `notifications` table + `rss_token` column on users |
Migrations run automatically on API startup: `alembic upgrade head`. Migrations run automatically on API startup: `alembic upgrade head`.
@@ -390,8 +453,10 @@ Auth header: `Authorization: Bearer <jwt>`
| Method | Path | Auth | Description | | Method | Path | Auth | Description |
|---|---|---|---| |---|---|---|---|
| GET | `/` | — | Paginated members. Query: `chamber`, `party`, `state`, `q`, `page`, `per_page`. | | GET | `/` | — | Paginated members. Query: `chamber`, `party`, `state`, `q`, `page`, `per_page`. |
| GET | `/{bioguide_id}` | — | Member detail. | | GET | `/{bioguide_id}` | — | Member detail. On first view, lazily enriches bio from Congress.gov and queues member interest scoring. Returns `latest_trend` if scored. |
| GET | `/{bioguide_id}/bills` | — | Member's sponsored bills, paginated. | | GET | `/{bioguide_id}/bills` | — | Member's sponsored bills, paginated. |
| GET | `/{bioguide_id}/trend` | — | Member trend score history. Query: `days` (7365, default 30). |
| GET | `/{bioguide_id}/news` | — | Member's recent news articles, limit 20. |
### `/api/follows` ### `/api/follows`
@@ -419,7 +484,17 @@ Auth header: `Authorization: Bearer <jwt>`
|---|---|---|---| |---|---|---|---|
| GET | `/` | Required | Current settings (DB overrides env). | | GET | `/` | Required | Current settings (DB overrides env). |
| PUT | `/` | Admin | Update `{key, value}`. Allowed keys: `llm_provider`, `llm_model`, `congress_poll_interval_minutes`. | | PUT | `/` | Admin | Update `{key, value}`. Allowed keys: `llm_provider`, `llm_model`, `congress_poll_interval_minutes`. |
| POST | `/test-llm` | Admin | Test LLM connection. Returns `{status, provider, model, summary_preview}`. | | POST | `/test-llm` | Admin | Test LLM connection with a lightweight ping (max_tokens=20). Returns `{status, provider, model, reply}`. |
| GET | `/llm-models?provider=X` | Admin | Fetch available models from the live provider API. Supports openai, anthropic, gemini, ollama. |
### `/api/notifications`
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | `/settings` | Required | User's notification preferences (ntfy URL/token, ntfy enabled, RSS token). |
| PUT | `/settings` | Required | Update notification preferences. |
| POST | `/settings/rss-reset` | Required | Regenerate RSS token (invalidates old URL). |
| GET | `/feed/{rss_token}.xml` | — | Personal RSS feed of notification events for this user. |
### `/api/admin` ### `/api/admin`
@@ -428,11 +503,18 @@ Auth header: `Authorization: Bearer <jwt>`
| GET | `/users` | Admin | All users with follow counts. | | GET | `/users` | Admin | All users with follow counts. |
| DELETE | `/users/{id}` | Admin | Delete user (cannot delete self). Cascades follows. | | DELETE | `/users/{id}` | Admin | Delete user (cannot delete self). Cascades follows. |
| PATCH | `/users/{id}/toggle-admin` | Admin | Promote/demote admin status (cannot change self). | | PATCH | `/users/{id}/toggle-admin` | Admin | Promote/demote admin status (cannot change self). |
| GET | `/stats` | Admin | Pipeline progress: total bills, docs fetched, briefs generated, remaining. | | GET | `/stats` | Admin | Pipeline counters: total bills, docs fetched, briefs generated, pending LLM, missing metadata/sponsors/actions, uncited briefs. |
| GET | `/api-health` | Admin | Test each external API in parallel; returns status + latency for Congress.gov, GovInfo, NewsAPI, Google News. |
| POST | `/trigger-poll` | Admin | Queue immediate Congress.gov poll. | | POST | `/trigger-poll` | Admin | Queue immediate Congress.gov poll. |
| POST | `/trigger-member-sync` | Admin | Queue member sync. | | POST | `/trigger-member-sync` | Admin | Queue member sync. |
| POST | `/trigger-trend-scores` | Admin | Queue trend score calculation. | | POST | `/trigger-trend-scores` | Admin | Queue trend score calculation. |
| POST | `/backfill-sponsors` | Admin | Queue one-off task to populate `sponsor_id` on all bills where it is NULL. | | POST | `/trigger-fetch-actions` | Admin | Queue action fetch for recently active bills (last 30 days). |
| POST | `/backfill-all-actions` | Admin | Queue action fetch for ALL bills with no action history (one-time catch-up). |
| POST | `/backfill-sponsors` | Admin | Queue one-off task to populate `sponsor_id` on bills where it is NULL. |
| POST | `/backfill-metadata` | Admin | Fill null `introduced_date`, `chamber`, `congress_url` by re-fetching bill detail. |
| POST | `/backfill-citations` | Admin | Delete pre-citation briefs and re-queue LLM using stored document text. |
| POST | `/resume-analysis` | Admin | Re-queue LLM for docs with no brief; re-queue doc fetch for bills with no doc. |
| POST | `/bills/{bill_id}/reprocess` | Admin | Queue document + action fetches for a specific bill (debugging). |
| GET | `/task-status/{task_id}` | Admin | Celery task status and result. | | GET | `/task-status/{task_id}` | Admin | Celery task status and result. |
### `/api/health` ### `/api/health`
@@ -453,10 +535,10 @@ Auth header: `Authorization: Bearer <jwt>`
| Queue | Workers | Tasks | | Queue | Workers | Tasks |
|---|---|---| |---|---|---|
| `polling` | worker | `poll_congress_bills`, `sync_members` | | `polling` | worker | `app.workers.congress_poller.*`, `app.workers.notification_dispatcher.*` |
| `documents` | worker | `fetch_bill_documents` | | `documents` | worker | `fetch_bill_documents` |
| `llm` | worker | `process_document_with_llm` | | `llm` | worker | `process_document_with_llm` |
| `news` | worker | `fetch_news_for_bill`, `fetch_news_for_active_bills`, `calculate_all_trend_scores` | | `news` | worker | `app.workers.news_fetcher.*`, `app.workers.trend_scorer.*`, `app.workers.member_interest.*` |
**Worker settings:** **Worker settings:**
- `task_acks_late = True` — task removed from queue only after completion, not on pickup - `task_acks_late = True` — task removed from queue only after completion, not on pickup
@@ -470,6 +552,10 @@ Auth header: `Authorization: Bearer <jwt>`
| Configurable (default 30 min) | `poll_congress_bills` | Continuous | | Configurable (default 30 min) | `poll_congress_bills` | Continuous |
| Every 6 hours | `fetch_news_for_active_bills` | Ongoing | | Every 6 hours | `fetch_news_for_active_bills` | Ongoing |
| Daily 2 AM UTC | `calculate_all_trend_scores` | Nightly | | Daily 2 AM UTC | `calculate_all_trend_scores` | Nightly |
| Every 12 hours (at :30) | `fetch_news_for_active_members` | Ongoing |
| Daily 3 AM UTC | `calculate_all_member_trend_scores` | Nightly |
| Daily 4 AM UTC | `fetch_actions_for_active_bills` | Nightly |
| Every 5 minutes | `dispatch_notifications` | Continuous |
--- ---
@@ -503,8 +589,8 @@ Auth header: `Authorization: Bearer <jwt>`
↳ → fetch_news_for_bill.delay(bill_id) ↳ → fetch_news_for_bill.delay(bill_id)
4. news_fetcher.fetch_news_for_bill(bill_id) 4. news_fetcher.fetch_news_for_bill(bill_id)
↳ Queries NewsAPI using bill title + topic_tags ↳ Queries NewsAPI + Google News RSS using bill title/number
↳ Deduplicates by URL ↳ Deduplicates by (bill_id, url) — same article can appear for multiple bills
↳ Stores NewsArticle records ↳ Stores NewsArticle records
5. trend_scorer.calculate_all_trend_scores() [nightly] 5. trend_scorer.calculate_all_trend_scores() [nightly]
@@ -513,6 +599,21 @@ Auth header: `Authorization: Bearer <jwt>`
↳ Fetches: NewsAPI count + Google News RSS count + Google Trends score ↳ Fetches: NewsAPI count + Google News RSS count + Google Trends score
↳ Calculates composite_score (0100) ↳ Calculates composite_score (0100)
↳ Stores TrendScore record ↳ Stores TrendScore record
Member interest pipeline (independent of bill pipeline):
6. member_interest.fetch_member_news(bioguide_id) [on first profile view + every 12h]
↳ Triggered on first member profile view (non-blocking via .delay())
↳ Queries NewsAPI + Google News RSS using member name + title
↳ Deduplicates by (member_id, url)
↳ Stores MemberNewsArticle records
7. member_interest.calculate_member_trend_score(bioguide_id) [on first profile view + nightly]
↳ Triggered on first member profile view (non-blocking via .delay())
↳ Only runs if member detail has been fetched (gate: detail_fetched IS NOT NULL)
↳ Fetches: NewsAPI count + Google News RSS count + Google Trends score
↳ Uses the same composite formula as bills
↳ Stores MemberTrendScore record
``` ```
--- ---
@@ -597,7 +698,7 @@ class ReverseBrief:
| `/bills` | Browse all bills with search, chamber/topic filters, pagination | | `/bills` | Browse all bills with search, chamber/topic filters, pagination |
| `/bills/[id]` | Bill detail — brief with § citations, action timeline, news, trend chart | | `/bills/[id]` | Bill detail — brief with § citations, action timeline, news, trend chart |
| `/members` | Browse members of Congress, filter by chamber/party/state | | `/members` | Browse members of Congress, filter by chamber/party/state |
| `/members/[id]` | Member profile + sponsored bills | | `/members/[id]` | Member profile — bio, contact info, leadership roles, service history, sponsored bills, public interest trend chart, recent news |
| `/following` | User's followed bills, members, and topics | | `/following` | User's followed bills, members, and topics |
| `/topics` | Browse and follow policy topics | | `/topics` | Browse and follow policy topics |
| `/settings` | Admin panel (admin only) | | `/settings` | Admin panel (admin only) |
@@ -606,6 +707,9 @@ class ReverseBrief:
### Key Components ### Key Components
**`BriefPanel.tsx`**
Orchestrates AI brief display. If the latest brief is type `amendment`, shows an amber "What Changed" badge. Renders the latest brief via `AIBriefCard`. Below it, a collapsible "Version History" lists all older briefs; clicking one expands an inline `AIBriefCard`.
**`AIBriefCard.tsx`** **`AIBriefCard.tsx`**
Renders the LLM brief. For cited items (new format), shows a `§ Section X(y)` chip next to each bullet. Clicking the chip expands an inline panel with: Renders the LLM brief. For cited items (new format), shows a `§ Section X(y)` chip next to each bullet. Clicking the chip expands an inline panel with:
- Blockquoted verbatim excerpt from the bill - Blockquoted verbatim excerpt from the bill
@@ -613,11 +717,17 @@ Renders the LLM brief. For cited items (new format), shows a `§ Section X(y)` c
- One chip open at a time per card - One chip open at a time per card
- Old plain-string briefs render without chips (graceful backward compat) - Old plain-string briefs render without chips (graceful backward compat)
**`ActionTimeline.tsx`**
Renders the legislative action history as a vertical timeline. Accepts optional `latestActionDate`/`latestActionText` fallback props — when `actions` is empty but a latest action exists (actions not yet fetched from Congress.gov), shows a single "latest known action" entry with a note that full history loads in the background.
**`MobileHeader.tsx`**
Top bar shown only on mobile (`md:hidden`). Displays the PocketVeto logo and a hamburger button that opens the slide-in drawer.
**`AuthGuard.tsx`** **`AuthGuard.tsx`**
Client component wrapping the entire app. Waits for Zustand hydration, then redirects unauthenticated users to `/login`. Public paths (`/login`, `/register`) bypass the guard. Client component wrapping the entire app. Waits for Zustand hydration, then redirects unauthenticated users to `/login`. Public paths (`/login`, `/register`) bypass the guard. Implements the responsive shell: desktop sidebar always-visible (`hidden md:flex`), mobile drawer with backdrop overlay controlled by `drawerOpen` state.
**`Sidebar.tsx`** **`Sidebar.tsx`**
Navigation with: Home, Bills, Members, Following, Topics, Settings (admin only). Shows current user email + logout button at the bottom. Navigation with: Home, Bills, Members, Following, Topics, Settings (admin only). Shows current user email + logout button at the bottom. Accepts optional `onClose` prop — when provided (mobile drawer context), renders an X close button in the header and calls `onClose` on every nav link click.
**`BillCard.tsx`** **`BillCard.tsx`**
Compact bill preview showing bill ID, title, sponsor with party badge, latest action date, and status. Compact bill preview showing bill ID, title, sponsor with party badge, latest action date, and status.
@@ -633,6 +743,10 @@ partyBadgeColor(party) → Tailwind classes
"Democrat" "bg-blue-600 text-white" "Democrat" "bg-blue-600 text-white"
other "bg-slate-500 text-white" other "bg-slate-500 text-white"
chamberBadgeColor(chamber) Tailwind badge classes
"Senate" amber/gold (bg-amber-100 text-amber-700 )
"House" slate/silver (bg-slate-100 text-slate-600 )
partyColor(party) text color class (used inline) partyColor(party) text color class (used inline)
trendColor(score) color class based on score thresholds trendColor(score) color class based on score thresholds
billLabel(type, number) "H.R. 1234", "S. 567", etc. billLabel(type, number) "H.R. 1234", "S. 567", etc.
@@ -730,6 +844,77 @@ Nginx uses `resolver 127.0.0.11 valid=10s` (Docker's internal DNS) so upstream c
- AuthGuard with login/register pages - AuthGuard with login/register pages
- Analysis status dashboard (auto-refresh every 30s) - Analysis status dashboard (auto-refresh every 30s)
### v0.3.0 — Member Profiles & Mobile UI
**Member Interest Tracking:**
- `member_trend_scores` and `member_news_articles` tables (migration 0008)
- `member_interest` Celery worker: `fetch_member_news`, `calculate_member_trend_score`, `fetch_news_for_active_members`, `calculate_all_member_trend_scores`
- Member interest scoring uses the identical composite formula as bills (NewsAPI + GNews + pytrends)
- New beat schedules: member news every 12h, member trend scores nightly at 3 AM UTC
- Lazy enrichment: on first profile view, bio is fetched from Congress.gov detail API and interest scoring is queued non-blocking
- Member detail fields added: `congress_url`, `birth_year`, `address`, `phone`, `terms_json`, `leadership_json`, `sponsored_count`, `cosponsored_count`, `detail_fetched` (migration 0007)
- New API endpoints: `GET /api/members/{id}/trend` and `GET /api/members/{id}/news`
- Member detail page redesigned: photo, bio header with party/state/district/birth year, contact info (address, phone, website, congress.gov), current leadership badges, trend chart ("Public Interest"), news panel, legislation stats (sponsored/cosponsored counts), full service history timeline, all leadership roles history
**News Deduplication Fix:**
- `news_articles.url` changed from globally unique to per-bill unique `(bill_id, url)` (migration 0009)
- The same article can now appear in multiple bills' news panels
- `fetch_news_for_bill` now fetches from both NewsAPI and Google News RSS (previously GNews was volume-signal only)
**Mobile UI:**
- `MobileHeader.tsx` — hamburger + logo top bar, hidden on desktop (`md:hidden`)
- `AuthGuard.tsx` — responsive shell: desktop sidebar always-on, mobile slide-in drawer with backdrop
- `Sidebar.tsx``onClose` prop for drawer mode (X button + close on nav click)
- Dashboard grid: `grid-cols-1 md:grid-cols-3` (single column on mobile)
- Members page: `grid-cols-1 sm:grid-cols-2` (single column on mobile, two on tablet+)
- Topics page: `grid-cols-1 sm:grid-cols-2`
### v0.4.0 — Notifications, Admin Health Panel, Bill Action Pipeline
**Notifications (Phase 1 complete):**
- `notifications` table — stores events per user (new_brief, bill_updated, new_action)
- ntfy dispatch — Celery task POSTs to user's ntfy topic URL (self-hosted or ntfy.sh); optional auth token
- RSS feed — tokenized per-user XML feed at `/api/notifications/feed/{token}.xml`
- `dispatch_notifications` beat task — runs every 5 minutes, fans out unsent events to enabled channels
- Notification settings UI — ntfy topic URL, auth token, enable/disable, RSS URL with copy button
**Bill Action Pipeline:**
- `fetch_bill_actions` Celery task — fetches full legislative history from Congress.gov, idempotent on `(bill_id, action_date, action_text)`, updates `Bill.actions_fetched_at`
- `fetch_actions_for_active_bills` nightly batch — queues action fetches for bills active in last 30 days
- `backfill_all_bill_actions` — one-time task to fetch actions for all bills with `actions_fetched_at IS NULL`
- Beat schedule entry at 4 AM UTC
- `ActionTimeline` updated: shows full history when fetched; falls back to `latest_action_date`/`latest_action_text` with "latest known action" label when history not yet loaded
**"What Changed" — BriefPanel:**
- New `BriefPanel.tsx` component wrapping `AIBriefCard`
- When latest brief is type `amendment`: shows amber "What Changed" badge row + date
- Collapsible "Version History" section listing older briefs (date, type badge, truncated summary)
- Clicking a history row expands an inline `AIBriefCard` for that version
**LLM Provider Improvements:**
- Live model picker — `GET /api/settings/llm-models?provider=X` fetches available models from each provider's API (OpenAI SDK, Anthropic REST, Gemini SDK, Ollama tags endpoint)
- DB overrides now fully propagated: `get_llm_provider(provider, model)` accepts explicit params; all call sites read from `app_settings`
- Default Gemini model updated: `gemini-1.5-pro` (deprecated) → `gemini-2.0-flash`
- Test connection replaced with lightweight ping (max_tokens=20, 3-word prompt) instead of full brief generation
**Admin Panel Overhaul:**
- Bill Pipeline section: progress bar + breakdown table (total, text published, no text yet, AI briefs, pending LLM, uncited)
- External API Health: Run Tests button, parallel health checks for Congress.gov / GovInfo / NewsAPI / Google News RSS with latency display
- Manual Controls redesigned as health panel: each action has a status dot (green/red/gray), description, contextual count badge (e.g. "⚠ 12 bills missing metadata"), and Run button
- Task status polling: after triggering a task, button shows spinning icon; polls `/api/admin/task-status/{id}` every 5s; shows task ID prefix + completion/failure state
- New stat fields: `bills_missing_sponsor`, `bills_missing_metadata`, `bills_missing_actions`, `pending_llm`, `no_text_bills`
- New admin tasks: Backfill Dates & Links, Backfill All Action Histories, Resume Analysis
**Chamber Color Badges:**
- `chamberBadgeColor(chamber)` utility: amber/gold for Senate, slate/silver for House
- Applied everywhere chamber is displayed: BillCard, bill detail header
**Bill Detail Page:**
- "No bill text published" state — shown when `has_document=false` and no briefs; includes bill label, date, and congress.gov link
- `has_document` field added to `BillDetailSchema` and `BillDetail` TypeScript type
- `introduced_date` shown conditionally (not rendered when null, preventing "Introduced: —")
- Admin reprocess endpoint: `POST /api/admin/bills/{bill_id}/reprocess`
### v0.2.2 — Sponsor Linking & Search Fixes ### v0.2.2 — Sponsor Linking & Search Fixes
- **Root cause fixed:** Congress.gov list API does not return sponsor data — only the detail endpoint does. Poller now calls the detail endpoint for each new bill to get the sponsor and populate `bill.sponsor_id` - **Root cause fixed:** Congress.gov list API does not return sponsor data — only the detail endpoint does. Poller now calls the detail endpoint for each new bill to get the sponsor and populate `bill.sponsor_id`
- **Backfill task:** `backfill_sponsor_ids` Celery task + `/api/admin/backfill-sponsors` endpoint + "Backfill Sponsors" button in Admin UI — fixes existing bills with `NULL` sponsor_id (~10 req/sec, ~3 min for 1,600 bills) - **Backfill task:** `backfill_sponsor_ids` Celery task + `/api/admin/backfill-sponsors` endpoint + "Backfill Sponsors" button in Admin UI — fixes existing bills with `NULL` sponsor_id (~10 req/sec, ~3 min for 1,600 bills)

View File

@@ -24,6 +24,13 @@
- [x] What Changed (Amendment Briefs) — BriefPanel surfaces amendment briefs with "What Changed" badge and collapsible version history - [x] What Changed (Amendment Briefs) — BriefPanel surfaces amendment briefs with "What Changed" badge and collapsible version history
- [x] Source Viewer — "View source" link in § citation popover opens GovInfo document in new tab (Option A; Option B = in-app highlighted viewer deferred pending UX review) - [x] Source Viewer — "View source" link in § citation popover opens GovInfo document in new tab (Option A; Option B = in-app highlighted viewer deferred pending UX review)
- [x] Admin Reprocess — POST /api/admin/bills/{bill_id}/reprocess queues document + action fetches for a specific bill - [x] Admin Reprocess — POST /api/admin/bills/{bill_id}/reprocess queues document + action fetches for a specific bill
- [x] LLM Model Picker — live model list fetched from each provider's API; custom model name fallback
- [x] Admin Health Panel — bill pipeline breakdown table, external API health tests with latency, manual controls with status dots + task polling
- [x] Chamber Badges — amber/gold for Senate, slate/silver for House; applied on bill cards and detail pages
- [x] Action History Fallback — shows latest_action_date/text while full history loads; full timeline once fetched
- [x] Backfill All Actions — admin task to fetch action history for all pre-existing bills
- [x] Notifications (Phase 1) — ntfy dispatch, RSS feed, per-user settings UI, 5-min dispatcher beat task
- [x] Brief Regeneration UI — admin button to delete existing briefs for a bill and re-queue LLM processing. Useful for improving citation/diff logic without a full re-poll. (Backend reprocess endpoint already exists.)
--- ---
@@ -31,15 +38,6 @@
--- ---
### Phase 1 — Notifications Plumbing *(prerequisite for Alerts and Weekly Digest)*
- [ ] `notification_events` table — `(user_id, bill_id, event_type, payload, dispatched_at)`
- [ ] ntfy dispatch — Celery task POSTs to user's ntfy topic URL; user supplies their own topic URL (public ntfy.sh or self-hosted ntfy server with optional auth token)
- [ ] RSS feed — tokenized per-user feed at `/api/feed/{token}.xml`; token stored on user row
- [ ] User settings UI — ntfy topic URL field + optional ntfy auth token + RSS feed link/copy button
---
### Phase 2 — High Impact *(can run in parallel after Phase 1)* ### Phase 2 — High Impact *(can run in parallel after Phase 1)*
- [ ] **Change-driven Alerts** — emit `notification_event` from poller/document fetcher on material changes: new doc version, substitute text, committee report, vote scheduled/result. Filter out procedural-only action text. Fan out to ntfy + RSS. - [ ] **Change-driven Alerts** — emit `notification_event` from poller/document fetcher on material changes: new doc version, substitute text, committee report, vote scheduled/result. Filter out procedural-only action text. Fan out to ntfy + RSS.
@@ -68,7 +66,6 @@
- [ ] **Search Improvements** — filters on global search (bill type, status, chamber, date range); search within a member's sponsored bills; topic-scoped search. - [ ] **Search Improvements** — filters on global search (bill type, status, chamber, date range); search within a member's sponsored bills; topic-scoped search.
- [ ] **Desktop View** — wider multi-column layout optimized for large screens (sticky sidebar, expanded grid, richer bill detail layout). - [ ] **Desktop View** — wider multi-column layout optimized for large screens (sticky sidebar, expanded grid, richer bill detail layout).
- [ ] **Brief Regeneration UI** — admin button to delete existing briefs for a bill and re-queue LLM processing. Useful for improving citation/diff logic without a full re-poll. (Backend reprocess endpoint already exists.)
- [ ] **first_name / last_name Backfill** — Celery task to populate empty first/last from stored "Last, First" `name` field via split. - [ ] **first_name / last_name Backfill** — Celery task to populate empty first/last from stored "Last, First" `name` field via split.
--- ---