# Project export: MyAutoBenefits

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2026
- Tagline: AI finds government benefits you qualify for, then browser agents help fill out applications safely.
- Devpost: https://devpost.com/software/myautobenefits
- GitHub: https://github.com/oski-bear/calhacksllm26
- Demo: https://benefits-finder-ty73.onrender.com/
- Video: https://www.youtube.com/embed/_CagIy39als?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 4 GitHub contributor(s) — Joshua (36 commits), Sanat Singhal (6 commits), Claude Opus 4.8 (5 commits), Ethan Hu (1 commits)

## Devpost submission (written by the team)

### Inspiration

Public benefits can be life-changing, but the path to getting them is still a maze. Families are expected to know which programs exist, understand eligibility rules, gather the right documents, re-enter the same information across multiple portals, and then wait through county or agency review. A missed program or missing proof document can mean leaving food, health coverage, phone discounts, or energy assistance on the table. We wanted to build an AI project that was useful in a high-stakes real-world setting, not just another chatbot. The core idea behind MyAutoBenefits is simple: people should be able to enter their situation once, see the benefits they may qualify for, understand why, and get safe help completing applications.

### What it does

MyAutoBenefits is an AI-powered benefits navigator and application assistant. A user enters one household profile: location, household size, income, expenses, current benefits, immigration/citizenship status, and immediate needs. The app then checks that profile against program rules and surfaces benefits such as CalFresh, WIC, Medi-Cal, LIHEAP energy assistance, California LifeLine, and CalWORKs. For each program, MyAutoBenefits shows: whether the user likely qualifies or may qualify estimated monthly or yearly value when we can estimate it safely expected application time official application links and eligibility links a document checklist so users know what proof to prepare plain-language explanations of the matched rules For the end-to-end demo, CalFresh and WIC are agent-ready. Claude drafts user-specific application answers, and a Browserbase cloud browser agent opens a safe routed portal, fills out the application, submits the demo flow, captures confirmation proof, and saves the application status so judges can return to the proof later. Other benefits are still surfaced with official next steps even when auto-apply is not available yet.

### How we built it

We built the frontend in React, Vite, and Material UI, with a paper-doodle inspired visual direction to make a stressful benefits workflow feel more approachable. The intake flow is modeled after real benefits screeners: it collects household members, income sources, expenses, health coverage, existing benefits, and urgent needs. The backend is a Flask/Python API. It runs deterministic eligibility checks for the programs we support, persists profiles, uploaded documents, and application confirmations in SQLite, and exposes routes for eligibility, document management, Claude-drafted answers, and browser-agent status. We used Anthropic Claude for the parts where natural language helps most: friendly explanations, summaries, and draft answers that the user can review. We intentionally did not let the model be the sole source of truth for eligibility. The core eligibility matching is rule-based, and the app links back to official program sources. We used Browserbase with Playwright to run the application-agent demo in a real cloud browser session. The agent fills a routed benefits portal flow, captures screenshots and confirmation evidence, and returns a live session/proof view. This let us demonstrate the hard part of the product: moving from "you might qualify" to "your application packet is ready."

### Challenges we ran into

The hardest challenge was balancing ambition with safety. Public benefits are high-stakes: a wrong eligibility answer can waste someone's time or discourage them from applying, and a wrong form submission could create real harm. We had to design MyAutoBenefits around trust instead of pure automation. That meant keeping eligibility logic grounded in deterministic rules, showing official links, making the application flow reviewable, and using a safe routed portal for the live demo rather than silently submitting real government forms. Browser automation was also challenging. Form-filling agents are powerful, but real portals are brittle and vary across counties and programs. We scoped the end-to-end demo to CalFresh and WIC while still showing other benefits as reviewable next steps. Finally, we had to make the UI dense enough to be useful without overwhelming users. The app needs to show value, eligibility, documents, application time, official links, and agent status in a way that judges can understand quickly.

### Accomplishments we're proud of

We are proud that MyAutoBenefits is more than a benefits chatbot. It has a real intake flow, rule-based program matching, estimated value, document checklists, official links, AI-generated explanations, draft application answers, and a live Browserbase agent flow for CalFresh and WIC. We are also proud of the trust and safety details: the app does not ask Claude to invent eligibility, it avoids unnecessary sensitive data in model calls, it gives users review points, and it saves browser-agent proof so the result is auditable. The most exciting part is that the demo shows a full loop: enter your situation once, discover benefits, prepare documents, draft application answers, run an agent, and come back to a verified submission record.

### What we learned

We learned that AI agents are most useful when they are paired with narrow, well-defined responsibility boundaries. A model is great at explaining rules and drafting text, but eligibility and submission need structure, citations, and human review. We also learned that benefits access is a UX problem as much as it is a technical problem. Showing "you may qualify" is not enough. People need to know how much it might help, how long it takes, what documents to bring, what official site to trust, and what happens next. Most importantly, we learned that in high-impact domains, the winning AI pattern is not maximum autonomy. It is grounded automation: rules first, AI assistance second, user control always.

### What's next

Next, we want to expand beyond the CalFresh and WIC demo flows into a broader California benefits assistant that can support Medi-Cal, CalWORKs, LIHEAP, California LifeLine, housing assistance, and local nonprofit programs. We would also add multilingual intake, OCR for uploaded documents, stronger rule retrieval over official PDFs, county-specific eligibility differences, secure document storage, and partnerships with benefits navigators or legal aid groups. Longer term, MyAutoBenefits could become a trusted application layer between families and fragmented public benefits systems: one profile, many programs, transparent eligibility, and safe AI help with the paperwork.

## README (from the GitHub repository)

# Benefits Navigator

Benefits Navigator helps a low-income California household discover benefits,
review what information will be reused, and auto-apply to the two programs we
demo end-to-end: CalFresh and WIC.

The demo is intentionally safe. Browserbase opens realistic BenefitsCal and
California WIC URLs, then Playwright routes those page loads to local
high-fidelity mock portals. The agent creates/fills/reviews/submits inside the
mock portals only, captures confirmation proof, and saves the verified
application status to SQLite.

## What Works

- Rich household intake with saved profile data
- Rule-based eligibility results for California benefit programs
- CalFresh and WIC prioritized in demo mode
- Other eligible/maybe benefits still surface with official next-step links,
  even when they do not have AI auto-apply
- Impact summary showing programs found, estimated monthly value, and
  auto-apply time saved
- Per-program document preflight checklists before auto-apply
- Official apply/rules links and compact program branding on each benefit card
- Claude-generated explanations/draft answers when `ANTHROPIC_API_KEY` exists
- Browserbase live cloud browser for CalFresh and WIC auto-apply flows
- Safe fallback animation when Browserbase credentials are missing
- Submitted confirmations, Browserbase evidence, and portal screenshots saved
  back to the dashboard
- Completed applications can be reopened from the dashboard with **View agent
  proof**

## Stack

React + MUI, Flask, SQLite, Anthropic, Browserbase, Playwright.

## Setup

From the repo root:

```sh
python3 -m venv venv
venv/bin/pip install -r backend/requirements.txt
cd frontend && npm install
```

Create `backend/.env`:

```sh
ANTHROPIC_API_KEY=
BROWSERBASE_API_KEY=
BROWSERBASE_PROJECT_ID=
```

Anthropic is optional. Browserbase is required for the live cloud-browser proof.

## Run Locally

Backend:

```sh
PYTHONPATH=backend venv/bin/python backend/app.py
```

Frontend:

```sh
cd frontend
npm run dev -- --host 127.0.0.1
```

Open `http://127.0.0.1:5173/` and use **Load CalFresh + WIC demo profile**.

## Fast Checks

These do not need API keys:

```sh
PYTHONPATH=backend venv/bin/python -m unittest discover -s tests
venv/bin/python scripts/verify_agent_fallback.py
cd frontend && npm run lint && npm run build
```

With Flask and Vite running, and Browserbase configured:

```sh
venv/bin/python scripts/verify_demo.py
```

Expected:

```text
Demo E2E passed
Screenshots: /tmp/benefits_frontend_e2e
```

## Safety Boundary

This project does not submit real government applications. The cloud browser
uses real-looking public URLs only as the navigation target; requests are
intercepted and fulfilled with local static portal HTML.


## Detected evidence (automated analysis)

Indexed codebase: 107 recognized source files, 736 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- Flask (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Redis (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository; commit authorship or trailers

## Codebase structure (from repository index)

### Files (115 of 115)

```
.claude/launch.json
.gitignore
AI SLOP/ai_hackathon_2026_event_info.md
AI SLOP/benefit_card_creation_guide_for_llms.md
AI SLOP/benefits_competitor_research.md
AI SLOP/benefits_navigator_win_spec.md
AI SLOP/calfresh_wic_rules_and_form_fields.md
AI SLOP/competitor_claim_reverification_2026-06-21.md
AI SLOP/demo_profile_calfresh_wic.md
AI SLOP/devpost_submission_draft.md
AI SLOP/devpost_winners_relevant_info.md
AI SLOP/hackathon_win_strategy.md
AI SLOP/myfriendben_intake_form_spec.md
AI SLOP/myfriendben_ui_teardown_agenda.md
AI SLOP/project_idea_deep_evaluation.md
AI SLOP/project_idea_scorecard.csv
AI SLOP/project_ideas_ranked.md
AI SLOP/scraped_winning_projects_dataset.csv
AI SLOP/scraped_winning_projects_table.md
AI SLOP/winner_patterns_vs_project_ideas.md
backend/agent.py
backend/app.py
backend/claude_client.py
backend/db.py
backend/eligibility.py
backend/mock/benefitscal.html
backend/mock/wic.html
backend/programs.py
backend/requirements.txt
DEMO.md
frontend/.gitignore
frontend/eslint.config.js
frontend/index.html
frontend/package.json
frontend/paperdoodle_handoff/CLAUDE_CODE_PROMPT.md
frontend/paperdoodle_handoff/design_system/components/Badge.jsx
frontend/paperdoodle_handoff/design_system/components/Button.jsx
frontend/paperdoodle_handoff/design_system/components/Card.jsx
frontend/paperdoodle_handoff/design_system/components/Checkbox.jsx
frontend/paperdoodle_handoff/design_system/components/Divider.jsx
frontend/paperdoodle_handoff/design_system/components/Highlight.jsx
frontend/paperdoodle_handoff/design_system/components/Input.jsx
frontend/paperdoodle_handoff/design_system/components/WashiTape.jsx
frontend/paperdoodle_handoff/design_system/components/WrittenHeading.jsx
frontend/paperdoodle_handoff/design_system/readme.md
frontend/paperdoodle_handoff/design_system/styles.css
frontend/paperdoodle_handoff/design_system/tokens/animation.css
frontend/paperdoodle_handoff/design_system/tokens/base.css
frontend/paperdoodle_handoff/design_system/tokens/colors.css
frontend/paperdoodle_handoff/design_system/tokens/effects.css
frontend/paperdoodle_handoff/design_system/tokens/fonts.css
frontend/paperdoodle_handoff/design_system/tokens/spacing.css
frontend/paperdoodle_handoff/design_system/tokens/typography.css
frontend/paperdoodle_handoff/INTEGRATION_MUI.md
frontend/public/program-logos/SOURCES.md
frontend/README.md
frontend/src/api.js
frontend/src/App.css
frontend/src/App.jsx
frontend/src/assets/design_handoff_shader_wallpapers/Paperdoodle Wallpapers.html
frontend/src/assets/design_handoff_shader_wallpapers/README.md
frontend/src/assets/design_handoff_shader_wallpapers/shaders.js
frontend/src/assets/design_handoff_shader_wallpapers/webgl-wallpaper.js
frontend/src/components/BenHeader.jsx
frontend/src/components/paperdoodle/Highlight.jsx
frontend/src/components/paperdoodle/index.js
frontend/src/components/paperdoodle/PaperWallpaper.jsx
frontend/src/components/paperdoodle/WashiTape.jsx
frontend/src/components/paperdoodle/WrittenHeading.jsx
frontend/src/components/PenguinLogo.jsx
frontend/src/components/VoiceIntakeButton.jsx
frontend/src/data/demoProfile.js
frontend/src/data/income.js
frontend/src/data/programMetadata.js
frontend/src/data/screenerOptions.js
frontend/src/index.css
frontend/src/main.jsx
frontend/src/screens/AgentView.jsx
frontend/src/screens/ApplicationProofView.jsx
frontend/src/screens/BasicInfoForm.jsx
frontend/src/screens/Dashboard.jsx
frontend/src/screens/DocumentsSection.jsx
frontend/src/screens/DraftReview.jsx
frontend/src/screens/ProgramDetail.jsx
frontend/src/screens/StatusScreens.jsx
frontend/src/theme.js
frontend/src/theme/paperdoodle/components/Badge.jsx
frontend/src/theme/paperdoodle/components/Button.jsx
frontend/src/theme/paperdoodle/components/Card.jsx
frontend/src/theme/paperdoodle/components/Checkbox.jsx
frontend/src/theme/paperdoodle/components/Divider.jsx
frontend/src/theme/paperdoodle/components/Highlight.jsx
frontend/src/theme/paperdoodle/components/Input.jsx
frontend/src/theme/paperdoodle/components/WashiTape.jsx
frontend/src/theme/paperdoodle/components/WrittenHeading.jsx
frontend/src/theme/paperdoodle/readme.md
frontend/src/theme/paperdoodle/styles.css
frontend/src/theme/paperdoodle/tokens/animation.css
frontend/src/theme/paperdoodle/tokens/base.css
frontend/src/theme/paperdoodle/tokens/colors.css
frontend/src/theme/paperdoodle/tokens/effects.css
frontend/src/theme/paperdoodle/tokens/fonts.css
frontend/src/theme/paperdoodle/tokens/spacing.css
frontend/src/theme/paperdoodle/tokens/typography.css
frontend/vite.config.js
PITCH.md
PLAN.md
QA.md
README.md
render.yaml
scripts/verify_agent_fallback.py
scripts/verify_demo_profile.mjs
scripts/verify_demo.py
SUBMISSION_CHECKLIST.md
tests/test_critical_path.py
```

### Dependencies

- backend/requirements.txt: anthropic, browserbase, flask, flask-cors, gunicorn, playwright, python-dotenv
- frontend/package.json: @emotion/react@^11.14.0, @emotion/styled@^11.14.1, @eslint/js@^10.0.1, @mui/icons-material@^9.1.1, @mui/material@^9.1.1, @types/react@^19.2.14, @types/react-dom@^19.2.3, @vitejs/plugin-react@^6.0.1, eslint@^10.3.0, eslint-plugin-react-hooks@^7.1.1, eslint-plugin-react-refresh@^0.5.2, globals@^17.6.0, react@^19.2.6, react-dom@^19.2.6, vite@^8.0.12

### Recent commits (newest first)

- Add judge pitch script and Q&A cheat sheet
- Add Render single-origin deploy config
- Add voice intake button and expand Claude client with API and form updates
- Add paperdoodle UI assets and Devpost draft
- Add benefit program logos
- Add penguin mascot logo
- Surface non auto apply benefits
- Add benefit card creation guide
- Show benefit value and application time
- Add official benefit program logo badges
- Improve benefit cards and saved agent proof
- Add per-program document preflight
- Add submission docs and critical path tests
- Improve WIC portal fidelity
- Persist submitted portal proof
- Verify async agent fallback safety
- Show live Browserbase session while agent runs
- Clean up agent proof wording
- Show agent-ready intake packet
- Improve routed portal proof realism

## Key source files (fetched from GitHub, selected and truncated for size)

### SUBMISSION_CHECKLIST.md

```markdown
# Submission Checklist

Current time pressure: submit by 11:00 AM, judging at 1:00 PM. Optimize for a
stable demo and a clear story, not new features.

## 30-Minute Pre-Submit Check

Run these from the repo root:

```sh
git status --short --branch
PYTHONPATH=backend venv/bin/python -m unittest discover -s tests
venv/bin/python scripts/verify_agent_fallback.py
PATH="/Users/study/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin:$PATH" npm --prefix frontend run lint
PATH="/Users/study/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin:$PATH" npm --prefix frontend run build
```

If there is time and Browserbase is configured, start both servers and run:

```sh
PYTHONPATH=backend venv/bin/python backend/app.py
cd frontend && PATH="/Users/study/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin:$PATH" npm run dev -- --host 127.0.0.1
venv/bin/python scripts/verify_demo.py
```

Expected full-demo output:

```text
Demo E2E passed
Screenshots: /tmp/benefits_frontend_e2e
```

## Demo Story

1. Load the CalFresh + WIC demo profile.
2. Run eligibility and show the rule-grounded recommendations.
3. Point out the agent-ready packet: identity, household, income, location,
   current benefits, and WIC signal.
4. Point out the per-program document preflight: CalFresh county follow-up
   proofs and WIC appointment proofs.
5. Point out each card's official apply link and eligibility/rules link.
6. Auto-apply to CalFresh. Open the live Browserbase session if it appears early.
7. Show verified fields/actions, confirmation `CF-DEMO-4821`, and saved proof.
8. Go back to dashboard, click **View agent proof**, then return.
9. Repeat for WIC.
10. End on the dashboard with both submissions saved and proof thumbnails.

## Judge Talking Points

- The hard part is not another chatbot; it is mapping a messy household profile
  into program-specific eligibility and application fields.
- The agent is auditable: fields, clicks, confirmation, screenshot, and
  persisted status are all visible.
- The document checklist solves a real approval bottleneck: users can submit a
  draft now, but know exactly what to bring/upload for verification.
- The submission boundary is safe: Browserbase drives a routed portal mock, so
  no real government application is filed.
- The user stays in control. In a real deployment the final submit would be
  explicit human review, not silent background submission.

## Do Not Change After 10:30 AM

- Browserbase agent selectors in `backend/agent.py`
- Mock portal `name=...` fields or `data-agent-*` hooks
- `frontend/src/api.js` base URL
- Demo confirmation numbers: `CF-DEMO-4821`, `WIC-DEMO-2048`

```

### DEMO.md

```markdown
# Demo Runbook

## What To Show

1. Open the app and click **Load CalFresh + WIC demo profile**.
2. Click **Find my benefits**.
3. Show the dashboard with CalFresh and WIC in **Ready to auto-apply**.
4. Click **Auto-apply with AI agent** on CalFresh.
5. As soon as the live Browserbase link appears, open it if you want judges to
   watch the agent while it is still filling the portal.
6. Show the Browserbase-completed BenefitsCal-style confirmation receipt.
7. Go back and repeat for WIC.

The agent navigates to realistic BenefitsCal/WIC URLs in Browserbase, but
Playwright intercepts those page loads and serves our local demo portal HTML.
This is intentional: no real government application is submitted during the
demo.
The app shell discloses that this is a safe demo portal; the routed portal
pages themselves are kept high-fidelity so the submitted proof looks realistic.

The demo profile creates a fresh `oski.demo+...@example.com` email each time,
so repeated rehearsals start with clean application statuses.

## Environment

Create `backend/.env`:

```sh
ANTHROPIC_API_KEY=
BROWSERBASE_API_KEY=
BROWSERBASE_PROJECT_ID=
```

Anthropic is optional. Browserbase is needed for the cloud-browser screenshots.

## Start The App

From the repo root:

```sh
PYTHONPATH=backend venv/bin/python backend/app.py
```

In another terminal:

```sh
cd frontend
PATH="/Users/study/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin:$PATH" npm run dev -- --host 127.0.0.1
```

Open `http://127.0.0.1:5173/`.

## Verify Before Judging

Quick no-Browserbase fallback check:

```sh
PYTHONPATH=backend venv/bin/python -m unittest discover -s tests
PATH="/Users/study/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin:$PATH" node scripts/verify_demo_profile.mjs
venv/bin/python scripts/verify_agent_fallback.py
```

The unit tests cover eligibility, agent field mapping, safe simulation fallback,
and Flask route persistence boundaries. The fallback check covers both the old
synchronous agent route and the live Browserbase start/status route, and
verifies simulated runs are not saved as submitted applications.

With both servers running:

```sh
venv/bin/python scripts/verify_demo.py
```

Expected output:

```text
Demo E2E passed
Screenshots: /tmp/benefits_frontend_e2e
```

The script checks the full path:

- demo profile loads
- rich profile data round-trips through `/api/profile`
- eligibility dashboard shows CalFresh and WIC
- CalFresh agent returns confirmation `CF-DEMO-4821`
- WIC agent returns confirmation `WIC-DEMO-2048`
- agent pages show realistic BenefitsCal/WIC portal URLs
- submitted portal screenshots render
- dashboard moves completed programs into **Submitted applications**
- browser console has no React/resource errors

```

### backend/requirements.txt

```
flask
flask-cors
anthropic
python-dotenv
browserbase
playwright
gunicorn

```

### frontend/package.json

```
{
  "name": "frontend",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "@emotion/react": "^11.14.0",
    "@emotion/styled": "^11.14.1",
    "@mui/icons-material": "^9.1.1",
    "@mui/material": "^9.1.1",
    "react": "^19.2.6",
    "react-dom": "^19.2.6"
  },
  "devDependencies": {
    "@eslint/js": "^10.0.1",
    "@types/react": "^19.2.14",
    "@types/react-dom": "^19.2.3",
    "@vitejs/plugin-react": "^6.0.1",
    "eslint": "^10.3.0",
    "eslint-plugin-react-hooks": "^7.1.1",
    "eslint-plugin-react-refresh": "^0.5.2",
    "globals": "^17.6.0",
    "vite": "^8.0.12"
  }
}

```

### backend/app.py

```python
"""Flask backend for Benefits Finder.

Endpoints:
  GET  /api/health       quick check that the server is up
  POST /api/eligibility  body: the form's userInfo  ->  { "programs": [...] }
"""

import os
import threading
from uuid import uuid4

from dotenv import load_dotenv
from flask import Flask, request, jsonify, send_file, send_from_directory
from flask_cors import CORS
from werkzeug.utils import secure_filename

BASE_DIR = os.path.dirname(__file__)

# Load backend/.env so ANTHROPIC_API_KEY is available (file is gitignored).
load_dotenv(os.path.join(BASE_DIR, ".env"))

from eligibility import evaluate_all
from claude_client import generate_explanations, draft_application, parse_voice_intake
import db
from db import init_db, save_profile, get_profile
from agent import run_application

app = Flask(__name__)
CORS(app)  # allow the React dev server to call us during development

MOCK_DIR = os.path.join(BASE_DIR, "mock")

init_db()  # make sure the database and tables exist on startup

# Uploaded files live on disk here; the database only stores metadata.
UPLOAD_DIR = os.path.join(BASE_DIR, "uploads")
os.makedirs(UPLOAD_DIR, exist_ok=True)

# In-memory job state for the demo's live Browserbase runs. The persisted source
# of truth remains SQLite after a verified submission.
AGENT_JOBS = {}
AGENT_JOBS_LOCK = threading.Lock()


@app.get("/api/health")
def health():
    return jsonify({"status": "ok"})


@app.post("/api/eligibility")
def eligibility():
    user = request.get_json(silent=True) or {}
    programs = evaluate_all(user)
    return jsonify({"programs": programs})


@app.post("/api/explain")
def explain():
    """Claude-generated, personalized guidance for the programs the user may get.
    Recomputes eligibility server-side so we don't trust client-sent results."""
    user = request.get_json(silent=True) or {}
    programs = [
        p for p in evaluate_all(user) if p["status"] in ("eligible", "maybe")
    ]
    if user.get("demoMode"):
        programs = [p for p in programs if p["id"] in ("calfresh", "wic")]
    try:
        result = generate_explanations(user, programs)
    except RuntimeError as err:
        # Missing API key is okay in demo mode; keep the rule-based flow quiet.
        return jsonify({"summary": "", "explanations": {}, "skipped": str(err)})
    except Exception as err:  # noqa: BLE001 - report any API failure to the client
        return jsonify({"error": "explanation failed", "detail": str(err)}), 502
    return jsonify(result)


@app.post("/api/voice-intake")
def voice_intake():
    """Parse a freeform spoken transcript into pre-filled form data via Claude."""
    body = request.get_json(silent=True) or {}
    transcript = (body.get("transcript") or "").strip()
    if not transcript:
        return jsonify({"error": "transcript is required"}), 400
    try:
        parsed = parse_voice_intake(transcript)
        return jsonify(parsed)
    except RuntimeError as err:
        return jsonify({"error": str(err), "fieldsExtracted": []}), 503
    except Exception as err:  # noqa: BLE001
        return jsonify({"error": str(err)}), 500


@app.post("/api/draft")
def draft():
    """Claude-drafted application answers for one program (review before applying)."""
    body = request.get_json(silent=True) or {}
    user = body.get("userInfo") or {}
    program_id = body.get("programId")
    program = next(
        (p for p in evaluate_all(user) if p["id"] == program_id), None
    )
    if program is None:
        return jsonify({"error": "unknown program"}), 404
    try:
        result = draft_application(user, program)
    except RuntimeError as err:
        return jsonify({"statement": "", "answers": [], "skipped": str(err)})
    except Exception as err:  # noqa: BLE001 - report any API failure to the client
        return jsonify({"error": "drafting failed", "detail": str(err)}), 502
    return jsonify(result)


@app.post("/api/profile")
def save_profile_route():
    profile = request.get_json(silent=True) or {}
    try:
        saved = save_profile(profile)
    except ValueError as err:
        return jsonify({"error": str(err)}), 400
    return jsonify({"profile": saved})


@app.get("/api/profile")
def get_profile_route():
    profile = get_profile(request.args.get("email", ""))
    if profile is None:
        return jsonify({"profile": None}), 404
    return jsonify({"profile": profile})


@app.post("/api/documents")
def upload_document():
    email = request.form.get("email", "").strip()
    label = request.form.get("label", "").strip()
    file = request.files.get("file")
    if not email or file is None:
        return jsonify({"error": "email and file are required"}), 400

    original_name = secure_filename(file.filename) or "document"
    # Store under a unique name to avoid collisions; keep the extension.
    extension = os.path.splitext(original_name)[1]
    stored_name = uuid4().hex + extension
    file.save(os.path.join(UPLOAD_DIR, stored_name))

    doc = db.add_document(email, label or original_name, stored_name, original_name)
    return jsonify({"document": doc})


@app.get("/api/documents")
def list_documents_route():
    documents = db.list_documents(request.args.get("email", ""))
    return jsonify({"documents": documents})


@app.get("/api/documents/<int:doc_id>/download")
def download_document(doc_id):
    doc = db.get_document(doc_id)
    if doc is None:
        return jsonify({"error": "not found"}), 404
    return send_file(
        os.path.join(UPLOAD_DIR, doc["filename"]),
        as_attachment=False,
        download_name=doc["original_name"],
    )


@app.delete("/api/documents/<int:doc_id>")
def delete_document_route(doc_id):
    doc = db.delete_document(doc_id)
    if doc is None:
        return jsonify({"error": "not found"}), 404
    # Remove the file from disk too (ignore if it's already gone).
    try:
        os.remove(os.path.join(UPLOAD_DIR, doc["filename"]))
    except OSError:
        pass
    return jsonify({"ok": True})


@ap
[truncated — 4950 more characters]
```

### frontend/src/main.jsx

```javascript
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { ThemeProvider } from '@mui/material/styles'
import CssBaseline from '@mui/material/CssBaseline'
import theme from './theme.js'
import './theme/paperdoodle/styles.css' // Paperdoodle tokens + handwriting fonts
import './index.css'
import App from './App.jsx'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <ThemeProvider theme={theme}>
      <CssBaseline />
      <App />
    </ThemeProvider>
  </StrictMode>,
)

```

### frontend/src/App.jsx

```javascript
import { useState } from 'react'
import BasicInfoForm from './screens/BasicInfoForm.jsx'
import Dashboard from './screens/Dashboard.jsx'
import ProgramDetail from './screens/ProgramDetail.jsx'
import DraftReview from './screens/DraftReview.jsx'
import AgentView from './screens/AgentView.jsx'
import ApplicationProofView from './screens/ApplicationProofView.jsx'
import { LoadingScreen, ErrorScreen } from './screens/StatusScreens.jsx'
import DocumentsSection from './screens/DocumentsSection.jsx'
import { fetchEligibility, saveProfile, fetchExplanations, fetchApplications, getProfile } from './api.js'
import { demoProfile } from './data/demoProfile.js'

// The whole app is a simple "wizard". `screen` decides which page shows,
// and `userInfo` is the shared data we collect and carry between screens.
const emptyUserInfo = {
  name: '',
  email: '',
  income: '',
  maritalStatus: '',
  age: '',
  householdSize: '',
  state: 'CA',
  citizenship: '',
  education: '',
  filedTaxes: '',
  currentBenefits: [],
}

export default function App() {
  const [screen, setScreen] = useState('form')
  const [userInfo, setUserInfo] = useState(emptyUserInfo)
  const [programs, setPrograms] = useState([])
  const [selectedProgramId, setSelectedProgramId] = useState(null)
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState(null)
  const [summary, setSummary] = useState('') // Claude's friendly overall summary
  const [explaining, setExplaining] = useState(false) // loading personalized text
  const [applications, setApplications] = useState({})

  // On submit, ask the backend which programs the user qualifies for.
  async function handleFormSubmit(info) {
    setUserInfo(info)
    setLoading(true)
    setError(null)
    setSummary('')
    try {
      await saveProfile(info) // persist the profile (keyed by email)
      const result = await fetchEligibility(info)
      const applicationRows = await fetchApplications(info.email).catch(() => [])
      setPrograms(result)
      setApplications(indexApplications(applicationRows))
      setScreen('dashboard')
      loadExplanations(info) // enhance with Claude guidance in the background
    } catch (err) {
      setError(err.message)
    } finally {
      setLoading(false)
    }
  }

  // Fetch Claude's personalized guidance and merge it into the programs.
  // This is an enhancement — if it fails (e.g. no API key), the dashboard
  // still shows the rule-based reasons.
  async function loadExplanations(info) {
    setExplaining(true)
    try {
      const { summary: text, explanations } = await fetchExplanations(info)
      setSummary(text || '')
      setPrograms((prev) =>
        prev.map((p) => ({
          ...p,
          personalized: explanations[p.id] || p.personalized,
        })),
      )
    } catch {
      // Ignore — personalized guidance is optional.
    } finally {
      setExplaining(false)
    }
  }

  function handleSelectProgram(programId) {
    setSelectedProgramId(programId)
    setScreen('program')
  }

  function handleContinueToDraft(programId) {
    setSelectedProgramId(programId)
    setScreen('draft')
  }

  function handleStartAgent(programId) {
    setSelectedProgramId(programId)
    setScreen('agent')
  }

  function handleViewApplication(programId) {
    setSelectedProgramId(programId)
    setScreen('proof')
  }

  function handleAgentApplied(programId, result) {
    const application = result.application || {
      program_id: programId,
      status: 'submitted',
      confirmation: result.confirmation || '',
      mode: result.mode || '',
      submitted_at: new Date().toISOString(),
    }
    setApplications((prev) => ({ ...prev, [programId]: application }))
  }

  async function handleLoadProfile(email) {
    const profile = await getProfile(email)
    if (!profile) {
      throw new Error('No saved profile found for that email.')
    }
    return profile
  }

  const selectedProgram =
    programs.find((p) => p.id === selectedProgramId) || null

  if (loading) {
    return <LoadingScreen />
  }

  if (error) {
    return <ErrorScreen message={error} onRetry={() => setError(null)} />
  }

  if (screen === 'dashboard') {
    return (
      <Dashboard
        userInfo={userInfo}
        programs={programs}
        summary={summary}
        explaining={explaining}
        applications={applications}
        onSelectProgram={handleSelectProgram}
        onStartAgent={handleStartAgent}
        onViewApplication={handleViewApplication}
        onEditProfile={() => setScreen('profile')}
      />
    )
  }

  if (screen === 'profile') {
    return (
      <BasicInfoForm
        initialValues={userInfo}
        onSubmit={handleFormSubmit}
        title="Your information"
        subtitle="Update your details anytime. We'll save your changes and refresh the programs you qualify for."
        submitLabel="Save changes"
        onBack={() => setScreen('dashboard')}
      >
        <DocumentsSection email={userInfo.email} />
      </BasicInfoForm>
    )
  }

  if (screen === 'program') {
    return (
      <ProgramDetail
        program={selectedProgram}
        onContinue={handleContinueToDraft}
        onBack={() => setScreen('dashboard')}
      />
    )
  }

  if (screen === 'draft') {
    return (
      <DraftReview
        program={selectedProgram}
        userInfo={userInfo}
        onProceed={() => setScreen('agent')}
        onBack={() => setScreen('dashboard')}
      />
    )
  }

  if (screen === 'agent') {
    return (
      <AgentView
        program={selectedProgram}
        userInfo={userInfo}
        onApplied={handleAgentApplied}
        onBack={() => setScreen('dashboard')}
      />
    )
  }

  if (screen === 'proof') {
    return (
      <ApplicationProofView
        program={selectedProgram}
        application={applications[selectedProgramId]}
        onBack={() => setScreen('dashboard')}
      />
    )
  }

  return (
    <BasicInfoForm
      initialValues={userInfo}
      onSubmit={handle
[truncated — 213 more characters]
```

### frontend/src/components/paperdoodle/index.js

```javascript
export { WrittenHeading } from './WrittenHeading.jsx'
export { WashiTape } from './WashiTape.jsx'
export { Highlight } from './Highlight.jsx'
export { default as PaperWallpaper } from './PaperWallpaper.jsx'

```

### render.yaml

```yaml
# Render blueprint — single-origin deploy (Flask serves the built frontend + API).
# After connecting the repo in Render, set the three secret env vars in the
# dashboard (they are sync:false so they are NOT read from this file).
services:
  - type: web
    name: benefits-finder
    runtime: python
    plan: free
    # Build the frontend (Render's build image includes Node) and install Python deps.
    buildCommand: "pip install -r backend/requirements.txt && npm --prefix frontend install && npm --prefix frontend run build"
    # ONE worker is required: the agent keeps job state in memory and runs a
    # background thread, so all /agent/start + /agent/status calls must hit the
    # same process. Threads give concurrency within it.
    startCommand: "gunicorn --chdir backend --workers 1 --threads 8 --timeout 120 --bind 0.0.0.0:$PORT app:app"
    healthCheckPath: /api/health
    envVars:
      - key: NODE_VERSION
        value: "22.12.0"
      - key: PYTHON_VERSION
        value: "3.12.7"
      - key: ANTHROPIC_API_KEY
        sync: false
      - key: BROWSERBASE_API_KEY
        sync: false
      - key: BROWSERBASE_PROJECT_ID
        sync: false

```

### frontend/index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Benefits Finder</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

```

[93 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]