Tu primer agente
Conecta una API real a un agente conversacional sin escribir código.
# Portal taller-wox.fitlabs.dev — Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Build a FastAPI service that serves a branded FactorIT landing page + lead-capture registration + gated download of 2 ZIPs, plus 5 REST endpoints consumed live by IBM watsonx Orchestrate agents during the workshop on 2026-05-14. **Architecture:** Single FastAPI service in one Docker container deployed on Coolify. SQLite for leads, file-based CSVs/JSON loaded in memory at boot, Jinja2 + Plotly for the report endpoint, `itsdangerous` for download tokens. HTTPS via Let's Encrypt (auto from Coolify). Three persistent volumes: `leads.db`, `app/data/reports_output/`, `material/`. **Tech Stack:** Python 3.11 · FastAPI · Uvicorn · SQLite · pandas · Jinja2 · Plotly · itsdangerous · pydantic-settings · pytest · Docker **Source of truth for design decisions:** `docs/superpowers/specs/2026-05-12-taller-wox-design.md`. When the original SPEC (`SPEC_taller_wox_fitlabs.md`) and this design disagree, the design wins. --- ## File Structure Files this plan creates (locked decomposition): ``` app/ ├── __init__.py # empty, marks package ├── main.py # FastAPI app, CORS, static mount, router includes, startup ├── config.py # Settings via pydantic-settings, env vars ├── db.py # SQLite init + schema + helpers (upsert_lead, log_download, list_leads, stats) ├── security.py # token sign/verify (itsdangerous), basic auth dep, honeypot check ├── frontend.py # router: GET /, GET /descargas, POST /register, GET /download/{filename} ├── benefits_api.py # router: endpoints 1-4 (historical, available, member-insights, schedule) ├── reports_api.py # router: endpoint 5 (generate-report) + the StaticFiles mount for /api/reports/output ├── admin.py # router: /admin/leads.json, /admin/leads.csv, /admin/stats ├── data/ │ ├── historical_procedures.csv # ~50 rows, synthetic │ ├── available_procedures.csv # ~28 rows, synthetic │ ├── member_insights.json # static, dates re-calibrated to 2026 │ ├── schedule_response.txt # static text │ ├── combined_email.txt # for endpoint 5 │ ├── provider_email.txt # for endpoint 5 │ ├── aetna_email.txt # for endpoint 5 │ ├── aetna_claim_review_summary.csv # for endpoint 5, ~10 rows │ └── reports_output/ # generated HTMLs land here (persistent volume in prod) │ └── .gitkeep └── templates/ ├── base.html # shared layout: header, footer, fonts, palette ├── index.html # landing + inline registration form ├── descargas.html # post-registration with 2 download cards └── report.html # wrapper for endpoint 5 generated reports static/ ├── css/styles.css # FIT palette, hero, cards, form, responsive ├── img/ │ ├── LogoFIT.png # existing, compressed copy goes here │ └── favicon.ico # placeholder generated from LogoFIT └── js/app.js # honeypot bot detection (light), smooth scroll, form UX tests/ ├── __init__.py ├── conftest.py # pytest fixtures: TestClient, temp SQLite, env vars ├── test_db.py ├── test_security.py ├── test_frontend.py ├── test_benefits_api.py ├── test_reports_api.py └── test_admin.py material/ # empty in repo (gitignored); Felipe uploads zips to volume in prod └── .gitkeep Dockerfile requirements.txt .env.example README.md ``` **Responsibility per file:** - `config.py` owns env-var loading. No other module reads `os.environ` directly. - `db.py` owns the SQLite connection lifecycle and schema. No SQL strings outside this file. - `security.py` owns token serialization and the basic-auth dependency. No router does its own crypto. - Each router file (`frontend.py`, `benefits_api.py`, `reports_api.py`, `admin.py`) only knows its own routes and depends on `db`, `security`, `config` as needed. - `main.py` is the only place that wires routers, middleware, and static mounts. Touching `main.py` should be rare after Task 2. --- ## Task ordering rationale Tasks 1-4 are foundation (project skeleton, config, DB, security). Tasks 5-9 are the workshop API (the critical path for Thursday — must work end-to-end first). Tasks 10-13 are the frontend (visible but lower risk; you can ship the API without it for last-minute use). Task 14 is admin (lowest priority). Task 15 is Dockerization + deploy notes. If time runs out, the order above is also the priority order to abandon from the bottom up. --- ## Task 1: Project bootstrap **Files:** - Create: `requirements.txt` - Create: `.env.example` - Create: `app/__init__.py` (empty) - Create: `app/main.py` - Create: `tests/__init__.py` (empty) - Create: `tests/conftest.py` - Create: `tests/test_smoke.py` - [ ] **Step 1: Write `requirements.txt`** ```text fastapi==0.115.0 uvicorn[standard]==0.32.0 pydantic==2.9.2 pydantic-settings==2.6.1 python-multipart==0.0.12 itsdangerous==2.2.0 jinja2==3.1.4 pandas==2.2.3 plotly==5.24.1 pytest==8.3.3 httpx==0.27.2 ``` `httpx` is needed for FastAPI's `TestClient`. `python-multipart` for form parsing. Pinned versions for reproducibility. - [ ] **Step 2: Write `.env.example`** ```bash # Secret for signing download tokens. Use `python -c "import secrets; print(secrets.token_urlsafe(48))"` to generate SECRET_KEY=replace-me-with-a-long-random-string # HTTP Basic auth for /admin/* ADMIN_USER=admin ADMIN_PASS=replace-me # Download token expiration in hours TOKEN_EXPIRY_HOURS=24 # Public base URL (used in report URLs and elsewhere) BASE_URL=https://taller-wox.fitlabs.dev # Path where Felipe uploads the 2 zips (mounted volume in Coolify, ./material in dev) MATERIAL_DIR=./material # Path where SQLite file lives (mounted file in Coolify, ./leads.db in dev) DB_PATH=./leads.db # Where generated reports are written (mounted dir in Coolify) REPORTS_OUTPUT_DIR=./app/data/reports_output ``` - [ ] **Step 3: Write `app/main.py` (minimal)** ```python from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware app = FastAPI(title="taller-wox.fitlabs.dev", version="1.0.0") app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=False, allow_methods=["GET", "POST"], allow_headers=["*"], ) @app.get("/health") def health(): return {"status": "ok"} ``` - [ ] **Step 4: Write `tests/conftest.py`** ```python import os import tempfile from pathlib import Path import pytest # Set test env BEFORE importing the app os.environ["SECRET_KEY"] = "test-secret-key-only-for-tests-not-secure" os.environ["ADMIN_USER"] = "testadmin" os.environ["ADMIN_PASS"] = "testpass" os.environ["TOKEN_EXPIRY_HOURS"] = "24" os.environ["BASE_URL"] = "http://testserver" _tmp = Path(tempfile.mkdtemp(prefix="taller-wox-test-")) os.environ["DB_PATH"] = str(_tmp / "test_leads.db") os.environ["MATERIAL_DIR"] = str(_tmp / "material") os.environ["REPORTS_OUTPUT_DIR"] = str(_tmp / "reports_output") (_tmp / "material").mkdir(parents=True, exist_ok=True) (_tmp / "reports_output").mkdir(parents=True, exist_ok=True) @pytest.fixture def client(): from fastapi.testclient import TestClient from app.main import app return TestClient(app) ``` - [ ] **Step 5: Write `tests/test_smoke.py`** ```python def test_health(client): response = client.get("/health") assert response.status_code == 200 assert response.json() == {"status": "ok"} ``` - [ ] **Step 6: Install deps and run the test** ```bash python3.11 -m venv .venv source .venv/bin/activate pip install -r requirements.txt pytest tests/test_smoke.py -v ``` Expected: `1 passed`. - [ ] **Step 7: Verify dev server starts** ```bash uvicorn app.main:app --reload --port 8000 ``` In another terminal: ```bash curl http://localhost:8000/health ``` Expected output: `{"status":"ok"}`. Stop the server with Ctrl+C. - [ ] **Step 8: Commit** ```bash git add requirements.txt .env.example app/ tests/ git commit -m "feat(bootstrap): minimal FastAPI app with health endpoint and test harness" ``` --- ## Task 2: Settings via pydantic-settings **Files:** - Create: `app/config.py` - Modify: `app/main.py` - Create: `tests/test_config.py` - [ ] **Step 1: Write the failing test** ```python # tests/test_config.py from app.config import get_settings def test_settings_loaded_from_env(): settings = get_settings() assert settings.secret_key == "test-secret-key-only-for-tests-not-secure" assert settings.admin_user == "testadmin" assert settings.admin_pass == "testpass" assert settings.token_expiry_hours == 24 assert settings.base_url == "http://testserver" assert settings.db_path.endswith("test_leads.db") assert settings.material_dir.endswith("material") assert settings.reports_output_dir.endswith("reports_output") ``` - [ ] **Step 2: Run test to verify it fails** ```bash pytest tests/test_config.py -v ``` Expected: `ModuleNotFoundError: No module named 'app.config'`. - [ ] **Step 3: Implement `app/config.py`** ```python from functools import lru_cache from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): secret_key: str admin_user: str admin_pass: str token_expiry_hours: int = 24 base_url: str = "https://taller-wox.fitlabs.dev" db_path: str = "./leads.db" material_dir: str = "./material" reports_output_dir: str = "./app/data/reports_output" model_config = SettingsConfigDict(env_file=".env", case_sensitive=False) @lru_cache def get_settings() -> Settings: return Settings() ``` - [ ] **Step 4: Run test to verify it passes** ```bash pytest tests/test_config.py -v ``` Expected: `1 passed`. - [ ] **Step 5: Wire settings into `app/main.py`** Replace `app/main.py` with: ```python from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from app.config import get_settings settings = get_settings() app = FastAPI(title="taller-wox.fitlabs.dev", version="1.0.0") app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=False, allow_methods=["GET", "POST"], allow_headers=["*"], ) @app.get("/health") def health(): return {"status": "ok", "base_url": settings.base_url} ``` - [ ] **Step 6: Re-run smoke test** ```bash pytest -v ``` Expected: `2 passed`. - [ ] **Step 7: Commit** ```bash git add app/config.py app/main.py tests/test_config.py git commit -m "feat(config): typed settings via pydantic-settings" ``` --- ## Task 3: SQLite layer (db.py) **Files:** - Create: `app/db.py` - Create: `tests/test_db.py` - [ ] **Step 1: Write failing tests** ```python # tests/test_db.py from datetime import datetime import pytest from app.db import ( init_db, upsert_lead, get_lead_by_email, log_download, list_leads, stats, ) @pytest.fixture(autouse=True) def fresh_db(tmp_path, monkeypatch): """Each test gets its own DB file so they don't bleed into each other.""" db_file = tmp_path / "test.db" monkeypatch.setenv("DB_PATH", str(db_file)) from app import config config.get_settings.cache_clear() init_db() yield config.get_settings.cache_clear() def test_upsert_new_lead_creates_row(): lead_id = upsert_lead( nombre="Felipe", email="felipe@factorit.com", empresa="FactorIT", ip="1.2.3.4", user_agent="pytest", consent=True, ) assert lead_id > 0 lead = get_lead_by_email("felipe@factorit.com") assert lead["nombre"] == "Felipe" assert lead["empresa"] == "FactorIT" assert lead["times_registered"] == 1 def test_upsert_duplicate_email_increments_times_registered(): upsert_lead( nombre="Felipe", email="felipe@factorit.com", empresa="FactorIT", ip="1.2.3.4", user_agent="pytest", consent=True, ) upsert_lead( nombre="Felipe Arentsen", email="felipe@factorit.com", empresa="FactorIT Chile", ip="5.6.7.8", user_agent="pytest-2", consent=True, ) lead = get_lead_by_email("felipe@factorit.com") assert lead["times_registered"] == 2 # latest data wins assert lead["nombre"] == "Felipe Arentsen" assert lead["empresa"] == "FactorIT Chile" def test_log_download_records_event(): log_download( lead_email="felipe@factorit.com", filename="taller-wox-tecnico.zip", ip="1.2.3.4", ) s = stats() assert s["total_downloads"] == 1 assert s["downloads_por_archivo"]["taller-wox-tecnico.zip"] == 1 def test_list_leads_paginates(): for i in range(5): upsert_lead( nombre=f"User {i}", email=f"user{i}@test.com", empresa=f"Co {i}", ip="1.2.3.4", user_agent="pytest", consent=True, ) page = list_leads(limit=2, offset=0) assert len(page) == 2 page2 = list_leads(limit=2, offset=2) assert len(page2) == 2 assert page[0]["email"] != page2[0]["email"] def test_stats_top_empresas(): for empresa in ["ACME", "ACME", "ACME", "Globex", "Globex", "Initech"]: upsert_lead( nombre="X", email=f"{empresa}-{datetime.utcnow().timestamp()}@x.com", empresa=empresa, ip="1.2.3.4", user_agent="pytest", consent=True, ) s = stats() assert s["total_leads"] == 6 assert s["top_5_empresas"][0] == {"empresa": "ACME", "count": 3} ``` - [ ] **Step 2: Run tests to verify they fail** ```bash pytest tests/test_db.py -v ``` Expected: `ModuleNotFoundError` or `ImportError` on `app.db`. - [ ] **Step 3: Implement `app/db.py`** ```python import sqlite3 from contextlib import contextmanager from pathlib import Path from typing import Iterator from app.config import get_settings SCHEMA = """ CREATE TABLE IF NOT EXISTS leads ( id INTEGER PRIMARY KEY AUTOINCREMENT, nombre TEXT NOT NULL, email TEXT NOT NULL UNIQUE, empresa TEXT NOT NULL, ip TEXT, user_agent TEXT, consent INTEGER NOT NULL DEFAULT 0, times_registered INTEGER NOT NULL DEFAULT 1, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS downloads ( id INTEGER PRIMARY KEY AUTOINCREMENT, lead_email TEXT NOT NULL, filename TEXT NOT NULL, ip TEXT, downloaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_downloads_email ON downloads(lead_email); CREATE INDEX IF NOT EXISTS idx_downloads_filename ON downloads(filename); """ @contextmanager def _conn() -> Iterator[sqlite3.Connection]: settings = get_settings() Path(settings.db_path).parent.mkdir(parents=True, exist_ok=True) conn = sqlite3.connect(settings.db_path) conn.row_factory = sqlite3.Row try: yield conn conn.commit() finally: conn.close() def init_db() -> None: with _conn() as conn: conn.executescript(SCHEMA) def upsert_lead( nombre: str, email: str, empresa: str, ip: str | None, user_agent: str | None, consent: bool, ) -> int: """Insert a new lead, or update the existing row if email already exists. Returns the lead's id. Increments times_registered on duplicates. """ with _conn() as conn: cur = conn.execute( """ INSERT INTO leads (nombre, email, empresa, ip, user_agent, consent) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(email) DO UPDATE SET nombre = excluded.nombre, empresa = excluded.empresa, ip = excluded.ip, user_agent = excluded.user_agent, consent = excluded.consent, times_registered = times_registered + 1, last_seen = CURRENT_TIMESTAMP RETURNING id """, (nombre, email, empresa, ip, user_agent, 1 if consent else 0), ) return cur.fetchone()["id"] def get_lead_by_email(email: str) -> dict | None: with _conn() as conn: row = conn.execute( "SELECT * FROM leads WHERE email = ?", (email,) ).fetchone() return dict(row) if row else None def log_download(lead_email: str, filename: str, ip: str | None) -> None: with _conn() as conn: conn.execute( "INSERT INTO downloads (lead_email, filename, ip) VALUES (?, ?, ?)", (lead_email, filename, ip), ) def list_leads(limit: int = 100, offset: int = 0) -> list[dict]: with _conn() as conn: rows = conn.execute( "SELECT * FROM leads ORDER BY id ASC LIMIT ? OFFSET ?", (limit, offset), ).fetchall() return [dict(r) for r in rows] def stats() -> dict: with _conn() as conn: total_leads = conn.execute("SELECT COUNT(*) AS c FROM leads").fetchone()["c"] total_downloads = conn.execute("SELECT COUNT(*) AS c FROM downloads").fetchone()["c"] per_file = { r["filename"]: r["c"] for r in conn.execute( "SELECT filename, COUNT(*) AS c FROM downloads GROUP BY filename" ).fetchall() } top_empresas = [ {"empresa": r["empresa"], "count": r["c"]} for r in conn.execute( "SELECT empresa, COUNT(*) AS c FROM leads GROUP BY empresa ORDER BY c DESC LIMIT 5" ).fetchall() ] return { "total_leads": total_leads, "total_downloads": total_downloads, "downloads_por_archivo": per_file, "top_5_empresas": top_empresas, } ``` - [ ] **Step 4: Run tests to verify they pass** ```bash pytest tests/test_db.py -v ``` Expected: `5 passed`. - [ ] **Step 5: Commit** ```bash git add app/db.py tests/test_db.py git commit -m "feat(db): sqlite schema + lead/download helpers with upsert-on-email" ``` --- ## Task 4: Security (tokens, basic auth, honeypot) **Files:** - Create: `app/security.py` - Create: `tests/test_security.py` - [ ] **Step 1: Write failing tests** ```python # tests/test_security.py import time import pytest from itsdangerous import BadSignature, SignatureExpired from app.security import ( create_download_token, verify_download_token, is_honeypot_filled, ) def test_token_roundtrip(): token = create_download_token(email="x@y.com", nombre="X") data = verify_download_token(token) assert data["email"] == "x@y.com" assert data["nombre"] == "X" def test_token_tampering_raises(): token = create_download_token(email="x@y.com", nombre="X") tampered = token[:-3] + "AAA" with pytest.raises(BadSignature): verify_download_token(tampered) def test_token_expiry(monkeypatch): monkeypatch.setenv("TOKEN_EXPIRY_HOURS", "0") # expire immediately from app import config config.get_settings.cache_clear() token = create_download_token(email="x@y.com", nombre="X") time.sleep(1.1) with pytest.raises(SignatureExpired): verify_download_token(token) config.get_settings.cache_clear() def test_honeypot_empty_returns_false(): assert is_honeypot_filled("") is False assert is_honeypot_filled(None) is False def test_honeypot_filled_returns_true(): assert is_honeypot_filled("https://spam.com") is True assert is_honeypot_filled("anything") is True ``` - [ ] **Step 2: Run tests to verify they fail** ```bash pytest tests/test_security.py -v ``` Expected: `ImportError` on `app.security`. - [ ] **Step 3: Implement `app/security.py`** ```python from fastapi import Depends, HTTPException, status from fastapi.security import HTTPBasic, HTTPBasicCredentials from itsdangerous import URLSafeTimedSerializer import secrets from app.config import get_settings _basic = HTTPBasic() def _serializer() -> URLSafeTimedSerializer: return URLSafeTimedSerializer(get_settings().secret_key, salt="download") def create_download_token(email: str, nombre: str) -> str: return _serializer().dumps({"email": email, "nombre": nombre}) def verify_download_token(token: str) -> dict: """Returns {'email': ..., 'nombre': ...}. Raises SignatureExpired or BadSignature.""" settings = get_settings() max_age_seconds = max(1, settings.token_expiry_hours * 3600) return _serializer().loads(token, max_age=max_age_seconds) def is_honeypot_filled(value: str | None) -> bool: return bool(value) def require_admin(credentials: HTTPBasicCredentials = Depends(_basic)) -> str: """FastAPI dependency: enforces HTTP Basic auth against env-configured admin creds.""" settings = get_settings() user_ok = secrets.compare_digest(credentials.username, settings.admin_user) pass_ok = secrets.compare_digest(credentials.password, settings.admin_pass) if not (user_ok and pass_ok): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials", headers={"WWW-Authenticate": "Basic"}, ) return credentials.username ``` - [ ] **Step 4: Run tests to verify they pass** ```bash pytest tests/test_security.py -v ``` Expected: `5 passed`. - [ ] **Step 5: Commit** ```bash git add app/security.py tests/test_security.py git commit -m "feat(security): signed download tokens, basic-auth dep, honeypot check" ``` --- ## Task 5: Static datasets for the workshop API This task creates the data files the 5 endpoints read at boot. Coherence between files matters (see design doc §6). **Files:** - Create: `app/data/member_insights.json` - Create: `app/data/schedule_response.txt` - Create: `app/data/combined_email.txt` - Create: `app/data/provider_email.txt` - Create: `app/data/aetna_email.txt` - Create: `app/data/aetna_claim_review_summary.csv` - Create: `app/data/historical_procedures.csv` - Create: `app/data/available_procedures.csv` - Create: `app/data/reports_output/.gitkeep` (empty) - [ ] **Step 1: Write `app/data/member_insights.json`** Dates re-calibrated for today (2026-05-12): ```json { "result": { "member": { "name": "Charlie Smith", "date_of_birth": "2013-03-04", "plan": "Gold PPO", "member_id": "CS-001-2024" }, "medical_plan": { "name": "Gold PPO", "deductible": 1500, "deductible_met": 850, "out_of_pocket_max": 6000, "out_of_pocket_met": 1200, "coinsurance": "20%", "primary_care_copay": 25, "specialist_copay": 50, "emergency_room_copay": 250 }, "pharmacy_plan": { "tier_1_copay": 10, "tier_2_copay": 30, "tier_3_copay": 60, "tier_4_coinsurance": "30%", "mail_order_available": true }, "mental_health": { "covered_visits_per_year": 20, "visits_used": 4, "telehealth": true, "in_network_providers": "https://taller-wox.fitlabs.dev/docs/mental-health-providers" }, "wellness": { "gym_reimbursement_annual": 300, "gym_reimbursement_used": 150, "annual_checkup_covered": true, "preventive_care_100_percent": true, "flu_shot_covered": true }, "tax_documents": { "form_1095_available": true, "form_1095_url": "https://taller-wox.fitlabs.dev/docs/1095-2025.pdf", "instructions": "Tu formulario 1095 está disponible en el portal del afiliado bajo Documentos > Impuestos. Si no lo puedes acceder, llama al 1-800-FIT-CARE." }, "overdue_procedures": [ { "procedure": "Annual Physical Exam", "last_date": "2024-05-15", "recommended_frequency_months": 12, "due_since_months": 12, "priority": "high" }, { "procedure": "Dental Cleaning", "last_date": "2025-01-10", "recommended_frequency_months": 6, "due_since_months": 10, "priority": "medium" }, { "procedure": "Vision Exam", "last_date": "2023-08-22", "recommended_frequency_months": 24, "due_since_months": 8, "priority": "medium" }, { "procedure": "Blood Test - Cholesterol Panel", "last_date": "2024-11-04", "recommended_frequency_months": 12, "due_since_months": 6, "priority": "low" } ] } } ``` - [ ] **Step 2: Write `app/data/schedule_response.txt`** Single line (no JSON wrapper — endpoint adds that): ```text Para agendar una cita médica, sigue estos pasos: 1) Confirma con el afiliado el día y hora preferidos, y el tipo de procedimiento. 2) Verifica que el procedimiento esté cubierto por su plan (Gold PPO en este caso). 3) Llama al sistema de agendamiento de FIT Care al 1-800-FIT-CARE o entra al portal en https://taller-wox.fitlabs.dev/agenda. 4) Indica el procedimiento, el proveedor preferido (City Hospital, Green Valley Clinic, Sunrise Health o Regional Medical Center) y la fecha. 5) Confirma la cita y registra el número de confirmación. 6) Envía recordatorios automáticos al afiliado 24h y 1h antes. Recuerda: si el procedimiento requiere autorización previa, gestiónala antes de confirmar la cita. ``` - [ ] **Step 3: Write `app/data/combined_email.txt`** ```text From: dr.martinez@cityhospital.com Subject: Follow-up after appointment Hi Charlie, Following our visit last week, I'm recommending a follow-up CT scan to confirm the diagnosis. Please schedule within the next 2 weeks. Best, Dr. Martinez --- From: charlie.smith@gmail.com Subject: Re: Follow-up Thanks Dr. Martinez. Will the CT scan be covered by my Gold PPO plan? Also, can I get a copy of the lab results from last visit? Charlie ``` - [ ] **Step 4: Write `app/data/provider_email.txt`** ```text Patient presented with bilateral lower quadrant tenderness, WBC 14k, CRP elevated. Differential includes appendicitis vs diverticulitis. Recommending CT abdomen/pelvis with contrast STAT. CPT 74177 ordered. Pre-auth obtained: AUTH-2024-8821. Patient stable, NPO since midnight. Will reassess in 2h. ``` - [ ] **Step 5: Write `app/data/aetna_email.txt`** ```text Claim #AET-2024-9912 has been processed. Service date: 2024-04-15 Provider: City Hospital Total billed: $1,847.50 Plan allowance: $1,200.00 Plan paid: $960.00 Patient responsibility (20% coinsurance after deductible): $240.00 EOB available at member portal. ``` - [ ] **Step 6: Write `app/data/aetna_claim_review_summary.csv`** ```csv Date,CPT_Code,Description,Charged_Amount,Allowed_Amount,Plan_Paid,Patient_Responsibility 2024-04-15,74177,CT Abdomen/Pelvis w/contrast,1847.50,1200.00,960.00,240.00 2024-03-22,99213,Office visit established patient,185.00,120.00,96.00,24.00 2024-02-10,80061,Lipid panel,82.00,55.00,44.00,11.00 2024-01-18,99395,Preventive visit adult,310.00,250.00,250.00,0.00 2023-12-05,90686,Influenza vaccine,55.00,40.00,40.00,0.00 2023-11-12,87880,Strep A direct test,42.00,28.00,22.40,5.60 2023-10-08,71046,Chest X-ray 2 views,210.00,140.00,112.00,28.00 2023-09-22,93000,Electrocardiogram complete,138.00,90.00,72.00,18.00 2023-08-14,99214,Office visit established mod complexity,250.00,170.00,136.00,34.00 2023-07-03,36415,Venipuncture,18.00,12.00,9.60,2.40 ``` - [ ] **Step 7: Write `app/data/historical_procedures.csv`** 50 rows. Charlie Smith's rows MUST line up with `member_insights.json`'s `overdue_procedures` (matching `last_date`). Columns (exact order): `member_name,relationship,age,gender,procedure,procedure_type,location,date,in_network,member_plan,accepted_plans,cost_facility,cost_physician,cost_anesthesia,cost_medication,total_cost,facility_rating,notes` Write this exact CSV (Charlie Smith rows first 4 — coherent with `member_insights.json`; rest are filler): ```csv member_name,relationship,age,gender,procedure,procedure_type,location,date,in_network,member_plan,accepted_plans,cost_facility,cost_physician,cost_anesthesia,cost_medication,total_cost,facility_rating,notes Charlie Smith,Self,13,Male,Annual Physical Exam,preventive,City Hospital,2024-05-15,true,Gold PPO,"Gold PPO, Medicare Advantage",80.00,180.00,0.00,5.00,265.00,4.7,"Annual Physical Exam performed at City Hospital." Charlie Smith,Self,13,Male,Dental Cleaning,preventive,Green Valley Clinic,2025-01-10,true,Gold PPO,"Gold PPO, Family Plan - Silver EPO",60.00,120.00,0.00,0.00,180.00,4.5,"Dental Cleaning performed at Green Valley Clinic." Charlie Smith,Self,13,Male,Vision Exam,preventive,Sunrise Health,2023-08-22,true,Gold PPO,"Gold PPO, Bronze HDHP",40.00,110.00,0.00,0.00,150.00,4.3,"Vision Exam performed at Sunrise Health." Charlie Smith,Self,13,Male,Blood Test,diagnostic,City Hospital,2024-11-04,true,Gold PPO,"Gold PPO, Medicare Advantage",25.00,55.00,0.00,2.50,82.50,4.7,"Cholesterol Panel performed at City Hospital." Alice Smith,Spouse,42,Female,Annual Physical Exam,preventive,Regional Medical Center,2024-09-18,true,Gold PPO,"Gold PPO, Family Plan - Silver EPO",95.00,210.00,0.00,4.50,309.50,4.6,"Annual Physical Exam performed at Regional Medical Center." Alice Smith,Spouse,42,Female,MRI,diagnostic,City Hospital,2023-11-02,true,Gold PPO,"Gold PPO, Medicare Advantage",1450.00,820.00,0.00,12.00,2282.00,4.7,"Lumbar MRI performed at City Hospital." Alice Smith,Spouse,42,Female,Dental Cleaning,preventive,Green Valley Clinic,2024-08-12,true,Gold PPO,"Gold PPO, Family Plan - Silver EPO",65.00,125.00,0.00,0.00,190.00,4.5,"Dental Cleaning performed at Green Valley Clinic." Bob Johnson,Self,55,Male,Appendectomy,surgery,Regional Medical Center,2024-02-04,true,Family Plan - Silver EPO,"Family Plan - Silver EPO, Gold PPO",8200.00,5400.00,2100.00,180.00,15880.00,4.6,"Laparoscopic appendectomy performed at Regional Medical Center." Bob Johnson,Self,55,Male,Annual Physical Exam,preventive,Regional Medical Center,2024-10-22,false,Family Plan - Silver EPO,"Gold PPO",120.00,260.00,0.00,6.00,386.00,4.6,"Annual Physical Exam performed at Regional Medical Center." Bob Johnson,Self,55,Male,CT Scan,diagnostic,City Hospital,2024-02-03,true,Family Plan - Silver EPO,"Gold PPO, Medicare Advantage",1600.00,750.00,0.00,8.00,2358.00,4.7,"Pre-op CT abdomen/pelvis at City Hospital." Diana Roberts,Mother,67,Female,Annual Physical Exam,preventive,Sunrise Health,2024-07-09,true,Gold PPO,"Gold PPO, Bronze HDHP",95.00,220.00,0.00,5.00,320.00,4.3,"Annual Physical Exam performed at Sunrise Health." Diana Roberts,Mother,67,Female,Vision Exam,preventive,Sunrise Health,2024-10-14,true,Gold PPO,"Gold PPO, Bronze HDHP",40.00,115.00,0.00,0.00,155.00,4.3,"Vision Exam performed at Sunrise Health." Diana Roberts,Mother,67,Female,Blood Test,diagnostic,City Hospital,2025-02-18,true,Gold PPO,"Gold PPO, Medicare Advantage",28.00,60.00,0.00,3.00,91.00,4.7,"Cholesterol Panel performed at City Hospital." Diana Roberts,Mother,67,Female,MRI,diagnostic,Regional Medical Center,2023-06-30,true,Gold PPO,"Gold PPO, Family Plan - Silver EPO",1520.00,840.00,0.00,10.00,2370.00,4.6,"Knee MRI performed at Regional Medical Center." Ethan Smith,Son,11,Male,Annual Physical Exam,preventive,City Hospital,2025-04-02,true,Gold PPO,"Gold PPO, Medicare Advantage",70.00,170.00,0.00,4.00,244.00,4.7,"Annual Physical Exam performed at City Hospital." Ethan Smith,Son,11,Male,Dental Cleaning,preventive,Green Valley Clinic,2025-03-15,true,Gold PPO,"Gold PPO, Family Plan - Silver EPO",55.00,115.00,0.00,0.00,170.00,4.5,"Dental Cleaning performed at Green Valley Clinic." Ethan Smith,Son,11,Male,Vision Exam,preventive,Sunrise Health,2024-12-08,true,Gold PPO,"Gold PPO, Bronze HDHP",35.00,105.00,0.00,0.00,140.00,4.3,"Vision Exam performed at Sunrise Health." Ethan Smith,Son,11,Male,X Ray,diagnostic,City Hospital,2024-09-21,true,Gold PPO,"Gold PPO, Medicare Advantage",105.00,135.00,0.00,2.00,242.00,4.7,"Wrist X Ray performed at City Hospital." Alice Smith,Spouse,42,Female,Blood Test,diagnostic,City Hospital,2024-04-19,true,Gold PPO,"Gold PPO, Medicare Advantage",30.00,58.00,0.00,3.50,91.50,4.7,"Lipid panel at City Hospital." Alice Smith,Spouse,42,Female,Vision Exam,preventive,Sunrise Health,2024-06-11,true,Gold PPO,"Gold PPO, Bronze HDHP",38.00,108.00,0.00,0.00,146.00,4.3,"Vision Exam performed at Sunrise Health." Bob Johnson,Self,55,Male,Dental Cleaning,preventive,Green Valley Clinic,2024-12-03,true,Family Plan - Silver EPO,"Family Plan - Silver EPO, Gold PPO",60.00,120.00,0.00,0.00,180.00,4.5,"Dental Cleaning performed at Green Valley Clinic." Bob Johnson,Self,55,Male,Blood Test,diagnostic,Regional Medical Center,2025-01-22,true,Family Plan - Silver EPO,"Family Plan - Silver EPO, Gold PPO",28.00,55.00,0.00,3.00,86.00,4.6,"A1C panel performed at Regional Medical Center." Charlie Smith,Self,13,Male,X Ray,diagnostic,City Hospital,2025-03-28,true,Gold PPO,"Gold PPO, Medicare Advantage",110.00,140.00,0.00,2.00,252.00,4.7,"Ankle X Ray performed at City Hospital." Frank Lee,Father,72,Male,CT Scan,diagnostic,Regional Medical Center,2024-08-29,false,Bronze HDHP,"Gold PPO, Medicare Advantage",1750.00,820.00,0.00,9.00,2579.00,4.6,"Head CT performed at Regional Medical Center." Frank Lee,Father,72,Male,Annual Physical Exam,preventive,Sunrise Health,2024-11-19,true,Bronze HDHP,"Gold PPO, Bronze HDHP",95.00,225.00,0.00,5.50,325.50,4.3,"Annual Physical Exam performed at Sunrise Health." Frank Lee,Father,72,Male,Vision Exam,preventive,Sunrise Health,2024-09-02,true,Bronze HDHP,"Gold PPO, Bronze HDHP",42.00,118.00,0.00,0.00,160.00,4.3,"Vision Exam performed at Sunrise Health." Frank Lee,Father,72,Male,Blood Test,diagnostic,City Hospital,2025-02-25,true,Bronze HDHP,"Gold PPO, Medicare Advantage",30.00,58.00,0.00,4.00,92.00,4.7,"Comprehensive metabolic panel at City Hospital." Grace Wong,Daughter,16,Female,Annual Physical Exam,preventive,Green Valley Clinic,2025-02-11,true,Gold PPO,"Gold PPO, Family Plan - Silver EPO",75.00,175.00,0.00,4.00,254.00,4.5,"Annual Physical Exam performed at Green Valley Clinic." Grace Wong,Daughter,16,Female,Dental Cleaning,preventive,Green Valley Clinic,2024-11-30,true,Gold PPO,"Gold PPO, Family Plan - Silver EPO",58.00,118.00,0.00,0.00,176.00,4.5,"Dental Cleaning performed at Green Valley Clinic." Grace Wong,Daughter,16,Female,Vision Exam,preventive,Sunrise Health,2025-01-26,true,Gold PPO,"Gold PPO, Bronze HDHP",36.00,106.00,0.00,0.00,142.00,4.3,"Vision Exam performed at Sunrise Health." Henry Park,Self,38,Male,MRI,diagnostic,City Hospital,2024-07-15,true,Family Plan - Silver EPO,"Gold PPO, Family Plan - Silver EPO",1500.00,830.00,0.00,11.00,2341.00,4.7,"Shoulder MRI performed at City Hospital." Henry Park,Self,38,Male,Annual Physical Exam,preventive,Regional Medical Center,2024-08-08,true,Family Plan - Silver EPO,"Gold PPO, Family Plan - Silver EPO",90.00,210.00,0.00,5.00,305.00,4.6,"Annual Physical Exam performed at Regional Medical Center." Henry Park,Self,38,Male,X Ray,diagnostic,City Hospital,2024-07-10,true,Family Plan - Silver EPO,"Gold PPO, Medicare Advantage",120.00,145.00,0.00,2.50,267.50,4.7,"Shoulder X Ray performed at City Hospital." Henry Park,Self,38,Male,Dental Cleaning,preventive,Green Valley Clinic,2024-10-04,true,Family Plan - Silver EPO,"Gold PPO, Family Plan - Silver EPO",62.00,122.00,0.00,0.00,184.00,4.5,"Dental Cleaning performed at Green Valley Clinic." Isabel Cruz,Mother,69,Female,Appendectomy,surgery,City Hospital,2024-03-12,true,Gold PPO,"Gold PPO, Medicare Advantage",9100.00,5800.00,2200.00,195.00,17295.00,4.7,"Open appendectomy performed at City Hospital." Isabel Cruz,Mother,69,Female,CT Scan,diagnostic,City Hospital,2024-03-11,true,Gold PPO,"Gold PPO, Medicare Advantage",1620.00,780.00,0.00,8.50,2408.50,4.7,"Pre-op CT abdomen/pelvis at City Hospital." Isabel Cruz,Mother,69,Female,Annual Physical Exam,preventive,Sunrise Health,2024-06-22,true,Gold PPO,"Gold PPO, Bronze HDHP",95.00,225.00,0.00,5.50,325.50,4.3,"Annual Physical Exam performed at Sunrise Health." Isabel Cruz,Mother,69,Female,Blood Test,diagnostic,City Hospital,2025-01-30,true,Gold PPO,"Gold PPO, Medicare Advantage",32.00,62.00,0.00,3.50,97.50,4.7,"Comprehensive metabolic panel at City Hospital." Jack Miller,Self,29,Male,X Ray,diagnostic,Sunrise Health,2024-05-04,true,Bronze HDHP,"Gold PPO, Bronze HDHP",95.00,130.00,0.00,2.00,227.00,4.3,"Knee X Ray performed at Sunrise Health." Jack Miller,Self,29,Male,Annual Physical Exam,preventive,Sunrise Health,2025-02-06,true,Bronze HDHP,"Gold PPO, Bronze HDHP",85.00,200.00,0.00,4.50,289.50,4.3,"Annual Physical Exam performed at Sunrise Health." Jack Miller,Self,29,Male,Dental Cleaning,preventive,Green Valley Clinic,2024-10-17,true,Bronze HDHP,"Family Plan - Silver EPO",58.00,118.00,0.00,0.00,176.00,4.5,"Dental Cleaning performed at Green Valley Clinic." Karen Davis,Spouse,46,Female,MRI,diagnostic,Regional Medical Center,2024-04-09,true,Gold PPO,"Gold PPO, Family Plan - Silver EPO",1480.00,810.00,0.00,11.00,2301.00,4.6,"Brain MRI performed at Regional Medical Center." Karen Davis,Spouse,46,Female,Annual Physical Exam,preventive,Regional Medical Center,2024-11-25,true,Gold PPO,"Gold PPO, Family Plan - Silver EPO",100.00,225.00,0.00,5.50,330.50,4.6,"Annual Physical Exam performed at Regional Medical Center." Karen Davis,Spouse,46,Female,Dental Cleaning,preventive,Green Valley Clinic,2024-09-30,true,Gold PPO,"Gold PPO, Family Plan - Silver EPO",60.00,120.00,0.00,0.00,180.00,4.5,"Dental Cleaning performed at Green Valley Clinic." Liam O'Brien,Son,9,Male,Annual Physical Exam,preventive,City Hospital,2025-03-05,true,Gold PPO,"Gold PPO, Medicare Advantage",70.00,170.00,0.00,4.00,244.00,4.7,"Annual Physical Exam performed at City Hospital." Liam O'Brien,Son,9,Male,Vision Exam,preventive,Sunrise Health,2024-12-19,true,Gold PPO,"Gold PPO, Bronze HDHP",35.00,105.00,0.00,0.00,140.00,4.3,"Vision Exam performed at Sunrise Health." Liam O'Brien,Son,9,Male,Dental Cleaning,preventive,Green Valley Clinic,2025-04-11,true,Gold PPO,"Gold PPO, Family Plan - Silver EPO",55.00,115.00,0.00,0.00,170.00,4.5,"Dental Cleaning performed at Green Valley Clinic." Maria Lopez,Daughter,19,Female,Annual Physical Exam,preventive,Green Valley Clinic,2025-01-08,true,Family Plan - Silver EPO,"Gold PPO, Family Plan - Silver EPO",80.00,185.00,0.00,4.50,269.50,4.5,"Annual Physical Exam performed at Green Valley Clinic." Maria Lopez,Daughter,19,Female,Blood Test,diagnostic,City Hospital,2025-02-14,true,Family Plan - Silver EPO,"Gold PPO, Medicare Advantage",28.00,55.00,0.00,3.00,86.00,4.7,"Iron panel performed at City Hospital." Maria Lopez,Daughter,19,Female,Vision Exam,preventive,Sunrise Health,2024-08-20,true,Family Plan - Silver EPO,"Gold PPO, Bronze HDHP",38.00,108.00,0.00,0.00,146.00,4.3,"Vision Exam performed at Sunrise Health." ``` - [ ] **Step 8: Write `app/data/available_procedures.csv`** 28 rows covering MRI, CT Scan, X Ray, Annual Physical Exam, Appendectomy, Dental Cleaning, Vision Exam, Blood Test, Angioplasty, Ultrasound across 4 locations: Columns: `procedure,location,facility_rating,distance_miles,gold_ppo_plan_accepted,silver_epo_plan_accepted,accepted_plans,cost_facility,cost_physician,cost_anesthesia,cost_medication,total_cost` ```csv procedure,location,facility_rating,distance_miles,gold_ppo_plan_accepted,silver_epo_plan_accepted,accepted_plans,cost_facility,cost_physician,cost_anesthesia,cost_medication,total_cost MRI,City Hospital,4.7,5.2,true,true,"Gold PPO, Family Plan - Silver EPO, Medicare Advantage",1450.00,820.00,0.00,12.00,2282.00 MRI,Regional Medical Center,4.6,12.6,true,true,"Gold PPO, Family Plan - Silver EPO",1380.00,790.00,0.00,11.00,2181.00 MRI,Green Valley Clinic,4.5,8.4,true,false,"Gold PPO, Bronze HDHP",1520.00,860.00,0.00,12.50,2392.50 MRI,Sunrise Health,4.3,15.1,true,false,"Gold PPO, Bronze HDHP",1610.00,880.00,0.00,13.00,2503.00 CT Scan,City Hospital,4.7,5.2,true,true,"Gold PPO, Family Plan - Silver EPO, Medicare Advantage",1600.00,750.00,0.00,8.00,2358.00 CT Scan,Regional Medical Center,4.6,12.6,true,true,"Gold PPO, Family Plan - Silver EPO",1540.00,720.00,0.00,7.50,2267.50 CT Scan,Sunrise Health,4.3,15.1,true,false,"Gold PPO, Bronze HDHP",1750.00,820.00,0.00,9.00,2579.00 X Ray,City Hospital,4.7,5.2,true,true,"Gold PPO, Family Plan - Silver EPO, Medicare Advantage",110.00,140.00,0.00,2.00,252.00 X Ray,Green Valley Clinic,4.5,8.4,true,true,"Gold PPO, Family Plan - Silver EPO, Bronze HDHP",95.00,125.00,0.00,1.50,221.50 X Ray,Sunrise Health,4.3,15.1,true,false,"Gold PPO, Bronze HDHP",90.00,120.00,0.00,1.50,211.50 X Ray,Regional Medical Center,4.6,12.6,true,true,"Gold PPO, Family Plan - Silver EPO",100.00,130.00,0.00,1.50,231.50 Annual Physical Exam,City Hospital,4.7,5.2,true,true,"Gold PPO, Family Plan - Silver EPO, Medicare Advantage",80.00,180.00,0.00,5.00,265.00 Annual Physical Exam,Green Valley Clinic,4.5,8.4,true,true,"Gold PPO, Family Plan - Silver EPO, Bronze HDHP",75.00,170.00,0.00,4.50,249.50 Annual Physical Exam,Sunrise Health,4.3,15.1,true,false,"Gold PPO, Bronze HDHP",85.00,200.00,0.00,4.50,289.50 Annual Physical Exam,Regional Medical Center,4.6,12.6,true,true,"Gold PPO, Family Plan - Silver EPO",90.00,210.00,0.00,5.00,305.00 Appendectomy,City Hospital,4.7,5.2,true,true,"Gold PPO, Family Plan - Silver EPO, Medicare Advantage",9100.00,5800.00,2200.00,195.00,17295.00 Appendectomy,Regional Medical Center,4.6,12.6,true,true,"Gold PPO, Family Plan - Silver EPO",8200.00,5400.00,2100.00,180.00,15880.00 Dental Cleaning,Green Valley Clinic,4.5,8.4,true,true,"Gold PPO, Family Plan - Silver EPO, Bronze HDHP",58.00,118.00,0.00,0.00,176.00 Dental Cleaning,City Hospital,4.7,5.2,true,true,"Gold PPO, Family Plan - Silver EPO",60.00,120.00,0.00,0.00,180.00 Dental Cleaning,Sunrise Health,4.3,15.1,true,false,"Gold PPO, Bronze HDHP",55.00,115.00,0.00,0.00,170.00 Vision Exam,Sunrise Health,4.3,15.1,true,false,"Gold PPO, Bronze HDHP",35.00,105.00,0.00,0.00,140.00 Vision Exam,Green Valley Clinic,4.5,8.4,true,true,"Gold PPO, Family Plan - Silver EPO, Bronze HDHP",38.00,108.00,0.00,0.00,146.00 Vision Exam,City Hospital,4.7,5.2,true,true,"Gold PPO, Family Plan - Silver EPO",40.00,110.00,0.00,0.00,150.00 Blood Test,City Hospital,4.7,5.2,true,true,"Gold PPO, Family Plan - Silver EPO, Medicare Advantage",25.00,55.00,0.00,2.50,82.50 Blood Test,Regional Medical Center,4.6,12.6,true,true,"Gold PPO, Family Plan - Silver EPO",28.00,55.00,0.00,3.00,86.00 Angioplasty,City Hospital,4.7,5.2,true,true,"Gold PPO, Family Plan - Silver EPO, Medicare Advantage",9432.80,4774.57,1894.37,834.80,16936.54 Angioplasty,Regional Medical Center,4.6,12.6,true,true,"Gold PPO, Family Plan - Silver EPO",8920.00,4520.00,1850.00,810.00,16100.00 Ultrasound,City Hospital,4.7,5.2,true,true,"Gold PPO, Family Plan - Silver EPO, Medicare Advantage",380.00,290.00,0.00,3.00,673.00 ``` - [ ] **Step 9: Create `app/data/reports_output/.gitkeep`** Empty file. Run: ```bash mkdir -p app/data/reports_output && touch app/data/reports_output/.gitkeep ``` - [ ] **Step 10: Sanity-check the CSVs load with pandas** Run a quick REPL check: ```bash python3.11 -c " import pandas as pd h = pd.read_csv('app/data/historical_procedures.csv') a = pd.read_csv('app/data/available_procedures.csv') c = pd.read_csv('app/data/aetna_claim_review_summary.csv') print('historical:', len(h), 'rows,', list(h.columns)[:5], '...') print('available:', len(a), 'rows,', list(a.columns)[:5], '...') print('aetna:', len(c), 'rows,', list(c.columns)[:5], '...') charlie = h[h['member_name'] == 'Charlie Smith'] print('Charlie rows:', len(charlie)) assert len(charlie) >= 4, 'Charlie should have ≥4 rows (matches member_insights overdue list)' " ``` Expected: `historical: 50 rows`, `available: 28 rows`, `aetna: 10 rows`, `Charlie rows: 5`. - [ ] **Step 11: Commit** ```bash git add app/data/ git commit -m "feat(data): synthetic datasets for the 5 workshop endpoints" ``` --- ## Task 6: Endpoints 3 (member-insights) and 4 (schedule) These are static — no logic. Implementing them first builds confidence in the routing setup. **Files:** - Create: `app/benefits_api.py` - Modify: `app/main.py` - Create: `tests/test_benefits_api.py` - [ ] **Step 1: Write failing tests** ```python # tests/test_benefits_api.py def test_member_insights_returns_static_object(client): response = client.get("/api/member-insights") assert response.status_code == 200 body = response.json() assert "result" in body assert body["result"]["member"]["name"] == "Charlie Smith" assert body["result"]["member"]["plan"] == "Gold PPO" assert len(body["result"]["overdue_procedures"]) == 4 def test_schedule_returns_text_wrapped(client): response = client.get("/api/schedule") assert response.status_code == 200 body = response.json() assert "result" in body assert "agendar una cita" in body["result"].lower() assert "1-800-FIT-CARE" in body["result"] ``` - [ ] **Step 2: Run tests to verify they fail** ```bash pytest tests/test_benefits_api.py -v ``` Expected: 404 (routes don't exist yet). - [ ] **Step 3: Implement `app/benefits_api.py`** ```python import json from pathlib import Path from fastapi import APIRouter router = APIRouter(prefix="/api", tags=["benefits"]) _DATA_DIR = Path(__file__).parent / "data" with (_DATA_DIR / "member_insights.json").open() as f: _MEMBER_INSIGHTS = json.load(f) _SCHEDULE_TEXT = (_DATA_DIR / "schedule_response.txt").read_text(encoding="utf-8").strip() @router.get("/member-insights") def member_insights(): return _MEMBER_INSIGHTS @router.get("/schedule") def schedule(): return {"result": _SCHEDULE_TEXT} ``` - [ ] **Step 4: Wire router into `app/main.py`** Add to imports: ```python from app import benefits_api ``` Add after `app.add_middleware(...)`: ```python app.include_router(benefits_api.router) ``` - [ ] **Step 5: Run tests to verify they pass** ```bash pytest tests/test_benefits_api.py -v ``` Expected: `2 passed`. - [ ] **Step 6: Commit** ```bash git add app/benefits_api.py app/main.py tests/test_benefits_api.py git commit -m "feat(api): endpoints 3 and 4 - member-insights and schedule" ``` --- ## Task 7: Endpoint 1 (historical-procedures) Adds the filter + group_by logic. Shared with endpoint 2 in Task 8. **Files:** - Modify: `app/benefits_api.py` - Modify: `tests/test_benefits_api.py` - [ ] **Step 1: Write failing tests** Append to `tests/test_benefits_api.py`: ```python def test_historical_procedures_no_filter_returns_all(client): response = client.post( "/api/historical-procedures", json={"filters": "[]", "group_by": "[]"}, ) assert response.status_code == 200 body = response.json() assert "result" in body assert len(body["result"]) == 50 def test_historical_procedures_filter_equals(client): response = client.post( "/api/historical-procedures", json={ "filters": '[{"column": "member_name", "operator": "equals", "value": "Charlie Smith"}]', "group_by": "[]", }, ) assert response.status_code == 200 rows = response.json()["result"] assert len(rows) == 5 # Charlie Smith has 5 rows in the dataset assert all(r["member_name"] == "Charlie Smith" for r in rows) def test_historical_procedures_filter_contains(client): response = client.post( "/api/historical-procedures", json={ "filters": '[{"column": "procedure", "operator": "contains", "value": "mri"}]', "group_by": "[]", }, ) assert response.status_code == 200 rows = response.json()["result"] assert len(rows) > 0 assert all("MRI" in r["procedure"] for r in rows) def test_historical_procedures_filter_gt(client): response = client.post( "/api/historical-procedures", json={ "filters": '[{"column": "total_cost", "operator": "gt", "value": 5000}]', "group_by": "[]", }, ) assert response.status_code == 200 rows = response.json()["result"] assert len(rows) > 0 assert all(r["total_cost"] > 5000 for r in rows) def test_historical_procedures_group_by(client): response = client.post( "/api/historical-procedures", json={"filters": "[]", "group_by": '["relationship"]'}, ) assert response.status_code == 200 rows = response.json()["result"] assert len(rows) > 0 # group_by should leave only group key + aggregated numeric cols assert "relationship" in rows[0] assert "age" in rows[0] ``` - [ ] **Step 2: Run tests — expect failures** ```bash pytest tests/test_benefits_api.py -v ``` Expected: 4 new tests fail (404). - [ ] **Step 3: Implement filter+group_by helper and route** Replace `app/benefits_api.py` with: ```python import json from pathlib import Path import pandas as pd from fastapi import APIRouter, HTTPException router = APIRouter(prefix="/api", tags=["benefits"]) _DATA_DIR = Path(__file__).parent / "data" with (_DATA_DIR / "member_insights.json").open() as f: _MEMBER_INSIGHTS = json.load(f) _SCHEDULE_TEXT = (_DATA_DIR / "schedule_response.txt").read_text(encoding="utf-8").strip() _HISTORICAL = pd.read_csv(_DATA_DIR / "historical_procedures.csv") _OPS = { "equals": lambda s, v: s == v, "ne": lambda s, v: s != v, "contains": lambda s, v: s.astype(str).str.contains(str(v), case=False, na=False), "gt": lambda s, v: s > v, "lt": lambda s, v: s < v, "ge": lambda s, v: s >= v, "le": lambda s, v: s <= v, } def _apply_filters_and_group(df: pd.DataFrame, filters_raw: str, group_by_raw: str) -> list[dict]: try: filters = json.loads(filters_raw) if filters_raw else [] group_by = json.loads(group_by_raw) if group_by_raw else [] except json.JSONDecodeError as exc: raise HTTPException(status_code=400, detail=f"Invalid JSON in filters or group_by: {exc}") result = df.copy() for f in filters: col, op, val = f.get("column"), f.get("operator"), f.get("value") if col not in result.columns: raise HTTPException(status_code=400, detail=f"Unknown column: {col}") if op not in _OPS: raise HTTPException(status_code=400, detail=f"Unsupported operator: {op}") result = result[_OPS[op](result[col], val)] if group_by: missing = [c for c in group_by if c not in result.columns] if missing: raise HTTPException(status_code=400, detail=f"Unknown group_by columns: {missing}") numeric_cols = result.select_dtypes(include="number").columns.tolist() result = result.groupby(group_by)[numeric_cols].mean().reset_index() return result.to_dict(orient="records") @router.get("/member-insights") def member_insights(): return _MEMBER_INSIGHTS @router.get("/schedule") def schedule(): return {"result": _SCHEDULE_TEXT} @router.post("/historical-procedures") def historical_procedures(payload: dict): rows = _apply_filters_and_group( _HISTORICAL, payload.get("filters", "[]"), payload.get("group_by", "[]"), ) return {"result": rows} ``` - [ ] **Step 4: Run tests — expect pass** ```bash pytest tests/test_benefits_api.py -v ``` Expected: `7 passed`. - [ ] **Step 5: Commit** ```bash git add app/benefits_api.py tests/test_benefits_api.py git commit -m "feat(api): endpoint 1 - historical-procedures with filter+group_by" ``` --- ## Task 8: Endpoint 2 (available-procedures) Reuses the helper from Task 7. **Files:** - Modify: `app/benefits_api.py` - Modify: `tests/test_benefits_api.py` - [ ] **Step 1: Write failing tests** Append to `tests/test_benefits_api.py`: ```python def test_available_procedures_no_filter(client): response = client.post( "/api/available-procedures", json={"filters": "[]", "group_by": "[]"}, ) assert response.status_code == 200 rows = response.json()["result"] assert len(rows) == 28 def test_available_procedures_filter_mri(client): response = client.post( "/api/available-procedures", json={ "filters": '[{"column": "procedure", "operator": "contains", "value": "MRI"}]', "group_by": "[]", }, ) rows = response.json()["result"] assert len(rows) >= 3 assert all(r["procedure"] == "MRI" for r in rows) def test_available_procedures_group_by_procedure(client): response = client.post( "/api/available-procedures", json={"filters": "[]", "group_by": '["procedure"]'}, ) rows = response.json()["result"] assert len(rows) > 0 assert "procedure" in rows[0] assert "total_cost" in rows[0] # numeric column should be averaged ``` - [ ] **Step 2: Run tests — expect 404** ```bash pytest tests/test_benefits_api.py -v ``` Expected: 3 new tests fail (404). - [ ] **Step 3: Add the route to `app/benefits_api.py`** After the `_HISTORICAL = ...` line, add: ```python _AVAILABLE = pd.read_csv(_DATA_DIR / "available_procedures.csv") ``` Append at the end of the file: ```python @router.post("/available-procedures") def available_procedures(payload: dict): rows = _apply_filters_and_group( _AVAILABLE, payload.get("filters", "[]"), payload.get("group_by", "[]"), ) return {"result": rows} ``` - [ ] **Step 4: Run tests — expect pass** ```bash pytest tests/test_benefits_api.py -v ``` Expected: `10 passed`. - [ ] **Step 5: Commit** ```bash git add app/benefits_api.py tests/test_benefits_api.py git commit -m "feat(api): endpoint 2 - available-procedures (reuses filter helper)" ``` --- ## Task 9: Endpoint 5 (generate-report) The trickiest. Generates HTML with Jinja2 + Plotly, writes to disk, returns public URL. **Files:** - Create: `app/reports_api.py` - Create: `app/templates/report.html` - Modify: `app/main.py` - Create: `tests/test_reports_api.py` - [ ] **Step 1: Write `app/templates/report.html`** ```html
FACTORIT · FIT