Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
51b4a17
feat: First household CRUD setup
anth-volk Feb 2, 2026
58cd691
feat: User-household associations
anth-volk Feb 2, 2026
77c5a3d
feat: Household analysis
anth-volk Feb 3, 2026
3c28466
fix: Improve code quality
anth-volk Feb 3, 2026
78f8780
test: Add tests
anth-volk Feb 3, 2026
6f90fbe
feat: Use Alembic for db migrations
anth-volk Feb 4, 2026
0fb609b
refactor: Make household impact async pattern match economic impact
anth-volk Feb 4, 2026
dbb6534
fix: Break up Alembic; add smaller seed scripts
anth-volk Feb 4, 2026
cf8511f
test: Tests
anth-volk Feb 5, 2026
34c34c4
fix: Fix household user models; add variable default values
anth-volk Feb 6, 2026
013304f
refactor: Simplify seed scripts to use policyengine.py for default_va…
anth-volk Feb 6, 2026
a97f473
fix: FINALLY use the ACTUAL Alembic script to generate migrations
anth-volk Feb 6, 2026
bdebc9e
test: Add Variable model tests for default_value field
anth-volk Feb 7, 2026
b15c268
fix: Convert string report_id to UUID in _run_local_household_impact
anth-volk Feb 7, 2026
298540b
refactor: Use policyengine.py's Simulation class directly now that US…
anth-volk Feb 11, 2026
6e9ca41
test: Add tests for US policy reform application
anth-volk Feb 11, 2026
43135e2
fix: Install policyengine.py from app-v2-migration branch
anth-volk Feb 16, 2026
6e77d4d
fix: Allow direct references in hatch metadata
anth-volk Feb 16, 2026
754126e
chore: Remove test scripts, Nevada seed, and archived Supabase migrat…
anth-volk Feb 16, 2026
01e1806
feat: Add regions support for geographic analysis
anth-volk Feb 10, 2026
3751dc6
feat: Wire filter_field/filter_value through to policyengine.py
anth-volk Feb 10, 2026
1015dae
feat: Add filter pass-through to Modal functions + region unit tests
anth-volk Feb 10, 2026
ca580fb
feat: Add seed script for US and UK regions
anth-volk Feb 11, 2026
df422aa
feat: Integrate regions seeding into main seed script
anth-volk Feb 12, 2026
4732650
refactor: Change regions CLI to use skip flags instead of include flags
anth-volk Feb 12, 2026
dd523ef
refactor: Split seed.py into modular subscripts with presets
anth-volk Feb 12, 2026
f48fe17
fix: Update region tests to pass simulation_type argument
anth-volk Feb 16, 2026
cdcfd72
refactor: Consolidate region test files into test_analysis.py
anth-volk Feb 17, 2026
73b0db2
fix: Mark TestEconomicImpactNotFound as integration test
anth-volk Feb 17, 2026
5cdd5c2
feat: add user-policy associations
SakshiKekre Jan 12, 2026
ef0683d
feat: Add tax_benefit_model_id to Policy, remove country_id from User…
SakshiKekre Jan 23, 2026
3aae41e
feat: Add base schema migration and allow duplicate user-policy saves
SakshiKekre Feb 3, 2026
8c3f4bf
fix: Update tests for tax_benefit_model_id and duplicate policy behavior
SakshiKekre Feb 3, 2026
6f70723
feat: Add Alembic migration infrastructure
SakshiKekre Feb 6, 2026
5d7388c
chore: Rename migration to 0002 for readability
SakshiKekre Feb 6, 2026
bd3ae47
refactor: Switch to auto-generated initial migration from PR #80
SakshiKekre Feb 12, 2026
8bc80f6
feat: Remove user FK constraint for client-generated UUIDs
SakshiKekre Feb 12, 2026
3ee5e68
feat: Use country_id for user-policy filtering
SakshiKekre Feb 16, 2026
8193c51
chore: Remove docs, temp files, and fix user policy tests for country_id
SakshiKekre Feb 16, 2026
87a0ecf
feat: Add ownership checks and Literal validation for user-policies
SakshiKekre Feb 16, 2026
ea74166
fix: Restore docs and supabase/.temp files
SakshiKekre Feb 16, 2026
4a4edd1
refactor: Extract user-policy test fixtures and revert cli-latest
anth-volk Feb 17, 2026
9fcd249
fix: Reject extra fields in UserPolicyUpdate
anth-volk Feb 17, 2026
2d05884
fix: Pass tax_benefit_model_id when creating policies in household tests
anth-volk Feb 17, 2026
17063f9
fix: Merge divergent Alembic migration branches
anth-volk Feb 18, 2026
7d9791b
feat: Add filter_field/filter_value columns to simulations table
anth-volk Feb 18, 2026
80d8d36
feat: Remove unused parent_report_id from Report model
anth-volk Feb 18, 2026
322f2fc
feat: Add user_simulation_associations table and CRUD endpoints
anth-volk Feb 18, 2026
fe90c06
feat: Add user_report_associations table and CRUD endpoints
anth-volk Feb 19, 2026
f8b9a02
feat: Add standalone simulation endpoints (/simulations/household, /s…
anth-volk Feb 19, 2026
cdb9d70
feat: Add household_impact_uk and household_impact_us Modal functions
anth-volk Feb 19, 2026
845c138
feat: Add _run_local_economy_comparison_us local fallback
anth-volk Feb 19, 2026
fcb8561
feat: Wire up poverty computation in economy comparison
anth-volk Feb 19, 2026
affe15a
feat: Wire up inequality computation in economy comparison
anth-volk Feb 19, 2026
c4c8a09
feat: Wire up poverty by age group in economy comparison
anth-volk Feb 19, 2026
b29907c
feat: Wire up poverty by gender in economy comparison
anth-volk Feb 19, 2026
306f0b7
feat: Wire up poverty by race in US economy comparison
anth-volk Feb 19, 2026
96608a6
feat: Add budget summary table and wire into economy comparison
anth-volk Feb 20, 2026
3cbc98d
feat: Add intra-decile impact table and 5-category income change comp…
anth-volk Feb 20, 2026
98209f3
feat: Add detailed_budget response field and expand UK local program …
anth-volk Feb 20, 2026
89b3816
test: Add Phase 2 tests for intra-decile and economic impact response
anth-volk Feb 20, 2026
1e1c352
feat: Add congressional district impact to economy analysis
anth-volk Feb 20, 2026
fca5a54
feat: Add UK constituency impact to economy comparison
anth-volk Feb 20, 2026
74c2c81
feat: Add UK local authority impact to economy comparison
anth-volk Feb 20, 2026
ef09a75
feat: Add wealth decile impact and migrate intra-decile to policyengi…
anth-volk Feb 20, 2026
dc6cc4c
test: Add tests for Phase 3 economic impact response fields
anth-volk Feb 20, 2026
75287ad
feat: Add economy analysis module registry
anth-volk Feb 20, 2026
d3ef57c
feat: Add GET /analysis/options endpoint
anth-volk Feb 23, 2026
939c6fe
feat: Add POST /analysis/economy-custom endpoint
anth-volk Feb 23, 2026
4b99237
refactor: Extract composable computation modules from monolithic func…
anth-volk Feb 23, 2026
89ba96f
test: Expand Phase 4 unit tests from 43 to 119
anth-volk Feb 23, 2026
8dbd870
feat: Add POST /parameters/by-name endpoint
anth-volk Feb 23, 2026
4b5be06
feat: Add GET /parameters/children endpoint for lazy tree loading
anth-volk Feb 23, 2026
c2efa1e
feat: Add POST /variables/by-name endpoint
anth-volk Feb 23, 2026
fc60e7e
feat: Add GET /tax-benefit-models/by-country/{country_id} endpoint
anth-volk Feb 23, 2026
4b07d34
refactor: Use country_id instead of tax_benefit_model_name in paramet…
anth-volk Feb 23, 2026
db6ecb8
feat: Add testing seed preset with variable/parameter whitelisting
anth-volk Feb 25, 2026
4f66e73
fix: Set tax_benefit_model_id when seeding example policies
anth-volk Feb 26, 2026
59adc8e
feat: Configure Modal for testing env with cloud Supabase
anth-volk Feb 27, 2026
792d28f
feat: Add region_id FK to simulations and full report endpoints
anth-volk Feb 27, 2026
334a3c2
feat: Add rerun endpoint and fix country-specific decile income variable
anth-volk Feb 27, 2026
b639bf6
refactor: Store simulation outputs under outputs/ folder in Supabase
anth-volk Mar 3, 2026
7ba5980
fix: Update computation module tests for country_id parameter
SakshiKekre Mar 3, 2026
150c2f6
feat: Replace regions.dataset_id FK with region_datasets join table
anth-volk Mar 3, 2026
b495485
fix: Update test fixtures to use RegionDatasetLink instead of dataset_id
anth-volk Mar 3, 2026
7bd8a89
fix: Address PR review issues — bugs, security, error handling, type …
anth-volk Mar 3, 2026
4f29c96
fix: Populate possible_values and set data_type='Enum' for enum varia…
SakshiKekre Mar 3, 2026
2dc1bee
Merge pull request #91 from PolicyEngine/fix/enum-possible-values-v2
anth-volk Mar 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
301 changes: 301 additions & 0 deletions .claude/skills/database-migrations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
# Database Migration Guidelines

## Overview

This project uses **Alembic** for database migrations with **SQLModel** models. Alembic is the industry-standard migration tool for SQLAlchemy/SQLModel projects.

**CRITICAL**: SQL migrations are the single source of truth for database schema. All table creation and schema changes MUST go through Alembic migrations.

## Architecture

```
┌─────────────────────────────────────────────────────────────┐
│ SQLModel Models (src/policyengine_api/models/) │
│ - Define Python classes │
│ - Used for ORM queries │
│ - NOT the source of truth for schema │
└─────────────────────────────────────────────────────────────┘
│ alembic revision --autogenerate
┌─────────────────────────────────────────────────────────────┐
│ Alembic Migrations (alembic/versions/) │
│ - Create/alter tables │
│ - Add indexes, constraints │
│ - SOURCE OF TRUTH for schema │
└─────────────────────────────────────────────────────────────┘
│ alembic upgrade head
┌─────────────────────────────────────────────────────────────┐
│ PostgreSQL Database (Supabase) │
│ - Actual schema │
│ - Tracked by alembic_version table │
└─────────────────────────────────────────────────────────────┘
```

## Essential Rules

### 1. NEVER use SQLModel.metadata.create_all() for schema creation

The old pattern of using `SQLModel.metadata.create_all()` is deprecated. All tables are created via Alembic migrations.

### 2. Every schema change requires a migration

When you modify a SQLModel model (add column, change type, add index), you MUST:
1. Update the model in `src/policyengine_api/models/`
2. Generate a migration: `uv run alembic revision --autogenerate -m "Description"`
3. **Read and verify the generated migration** (see below)
4. Apply it: `uv run alembic upgrade head`

### 3. ALWAYS verify auto-generated migrations before applying

**This is critical for AI agents.** After running `alembic revision --autogenerate`, you MUST:

1. **Read the generated migration file** in `alembic/versions/`
2. **Verify the `upgrade()` function** contains the expected changes:
- Correct table/column names
- Correct column types (e.g., `sa.String()`, `sa.Uuid()`, `sa.Integer()`)
- Proper foreign key references
- Appropriate nullable settings
3. **Verify the `downgrade()` function** properly reverses the changes
4. **Check for Alembic autogenerate limitations:**
- It may miss renamed columns (shows as drop + add instead)
- It may not detect some index changes
- It doesn't handle data migrations
5. **Edit the migration if needed** before applying

Example verification:
```python
# Generated migration - verify this looks correct:
def upgrade() -> None:
op.add_column('users', sa.Column('phone', sa.String(), nullable=True))

def downgrade() -> None:
op.drop_column('users', 'phone')
```

**Never blindly apply a migration without reading it first.**

### 4. Migrations must be self-contained

Each migration should:
- Create tables it needs (never assume they exist from Python)
- Include both `upgrade()` and `downgrade()` functions
- Be idempotent where possible (use `IF NOT EXISTS` patterns)

### 5. Never use conditional logic based on table existence

Migrations should NOT check if tables exist. Instead:
- Ensure migrations run in the correct order (use `down_revision`)
- The initial migration creates all base tables
- Subsequent migrations build on that foundation

## Common Commands

```bash
# Apply all pending migrations
uv run alembic upgrade head

# Generate migration from model changes
uv run alembic revision --autogenerate -m "Add users email index"

# Create empty migration (for manual SQL)
uv run alembic revision -m "Add custom index"

# Check current migration state
uv run alembic current

# Show migration history
uv run alembic history

# Downgrade one revision
uv run alembic downgrade -1

# Downgrade to specific revision
uv run alembic downgrade <revision_id>
```

## Local Development Workflow

```bash
# 1. Start Supabase
supabase start

# 2. Initialize database (runs migrations + applies RLS policies)
uv run python scripts/init.py

# 3. Seed data
uv run python scripts/seed.py
```

### Reset database (DESTRUCTIVE)

```bash
uv run python scripts/init.py --reset
```

## Adding a New Model

1. Create the model in `src/policyengine_api/models/`

```python
# src/policyengine_api/models/my_model.py
from sqlmodel import SQLModel, Field
from uuid import UUID, uuid4

class MyModel(SQLModel, table=True):
__tablename__ = "my_models"

id: UUID = Field(default_factory=uuid4, primary_key=True)
name: str
```

2. Export in `__init__.py`:

```python
# src/policyengine_api/models/__init__.py
from .my_model import MyModel
```

3. Generate migration:

```bash
uv run alembic revision --autogenerate -m "Add my_models table"
```

4. Review the generated migration in `alembic/versions/`

5. Apply the migration:

```bash
uv run alembic upgrade head
```

6. Update `scripts/init.py` to include the table in RLS policies if needed.

## Adding an Index

1. Generate a migration:

```bash
uv run alembic revision -m "Add index on users.email"
```

2. Edit the migration:

```python
def upgrade() -> None:
op.create_index("idx_users_email", "users", ["email"])

def downgrade() -> None:
op.drop_index("idx_users_email", "users")
```

3. Apply:

```bash
uv run alembic upgrade head
```

## Production Considerations

### Applying migrations to production

1. Migrations are automatically applied when deploying
2. Always test migrations locally first
3. For data migrations, consider running during low-traffic periods

### Transitioning production from old system to Alembic

Production databases that were created before Alembic (using the old `SQLModel.metadata.create_all()` approach or raw Supabase migrations) need special handling. Running `alembic upgrade head` would fail because the tables already exist.

**The solution: `alembic stamp`**

The `alembic stamp` command marks a migration as "already applied" without actually running it. This tells Alembic "the database is already at this state, start tracking from here."

**How it works:**

1. `alembic stamp <revision_id>` inserts a row into the `alembic_version` table with the specified revision ID
2. Alembic now thinks that migration (and all migrations before it) have been applied
3. Future migrations will run normally starting from that point

**Step-by-step production transition:**

```bash
# 1. Connect to production database
# (set SUPABASE_DB_URL or other connection env vars)

# 2. Check if alembic_version table exists
# If not, Alembic will create it automatically

# 3. Verify production schema matches the initial migration
# Compare tables/columns in production against alembic/versions/20260204_d6e30d3b834d_initial_schema.py

# 4. Stamp the initial migration as applied
uv run alembic stamp d6e30d3b834d

# 5. If production also has the indexes from the second migration, stamp that too
uv run alembic stamp a17ac554f4aa

# 6. Verify the stamp worked
uv run alembic current
# Should show: a17ac554f4aa (head)

# 7. From now on, new migrations will apply normally
uv run alembic upgrade head
```

**Handling partially applied migrations:**

If production has some but not all changes from a migration:

1. Manually apply the missing changes via SQL
2. Then stamp that migration as complete
3. Or: create a new migration that only adds the missing pieces

**After stamping:**

- All future schema changes go through Alembic migrations
- Developers generate migrations with `alembic revision --autogenerate`
- Deployments run `alembic upgrade head` to apply pending migrations
- The `alembic_version` table tracks what's been applied

## File Structure

```
alembic/
├── env.py # Alembic configuration (imports models, sets DB URL)
├── script.py.mako # Template for new migrations
├── versions/ # Migration files
│ ├── 20260204_d6e30d3b834d_initial_schema.py
│ └── 20260204_a17ac554f4aa_add_parameter_values_indexes.py
alembic.ini # Alembic settings

supabase/
├── migrations/ # Supabase-specific migrations (storage only)
│ ├── 20241119000000_storage_bucket.sql
│ └── 20241121000000_storage_policies.sql
└── migrations_archived/ # Old table migrations (now in Alembic)
```

## Troubleshooting

### "Target database is not up to date"

Run `alembic upgrade head` to apply pending migrations.

### "Can't locate revision"

The alembic_version table has a revision that doesn't exist in your migrations folder. This can happen if someone deleted a migration file. Fix by stamping to a known revision:

```bash
alembic stamp head # If tables are current
alembic stamp d6e30d3b834d # If at initial schema
```

### "Table already exists"

The migration is trying to create a table that already exists. Options:
1. If this is a fresh setup, drop and recreate: `uv run python scripts/init.py --reset`
2. If in production, stamp the migration as applied: `alembic stamp <revision>`
21 changes: 16 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,22 @@ AGENT_USE_MODAL=false
POLICYENGINE_API_URL=http://localhost:8000

# =============================================================================
# MODAL SECRETS (for production)
# MODAL SERVERLESS COMPUTE
# =============================================================================
# Modal secrets are NOT set via .env - they're managed via Modal CLI:
#
# 1. modal secret create policyengine-db \
# Modal environment to use (main, staging, testing).
# Only relevant when AGENT_USE_MODAL=true.
# The Modal SDK authenticates via ~/.modal.toml (from `modal setup`).
# For production (Cloud Run), set MODAL_TOKEN_ID and MODAL_TOKEN_SECRET instead.
MODAL_ENVIRONMENT=main

# For production (Cloud Run) only:
# MODAL_TOKEN_ID=ak-...
# MODAL_TOKEN_SECRET=as-...

# =============================================================================
# MODAL SECRETS (managed via Modal CLI, not .env)
# =============================================================================
# 1. modal secret create policyengine-db [--env testing] \
# DATABASE_URL='postgresql://...' \
# SUPABASE_URL='https://...' \
# SUPABASE_KEY='...' \
Expand All @@ -75,5 +86,5 @@ POLICYENGINE_API_URL=http://localhost:8000
# 2. modal secret create anthropic-api-key \
# ANTHROPIC_API_KEY='sk-ant-...'
#
# 3. modal secret create logfire-token \
# 3. modal secret create policyengine-logfire \
# LOGFIRE_TOKEN='...'
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,6 @@ docs/.env.local
data/
*.h5
*.db

# macOS
.DS_Store
16 changes: 15 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,21 @@ Use `gh` CLI for GitHub operations to ensure Actions run correctly.

## Database

`make init` resets tables and storage. `make seed` populates UK/US models with variables, parameters, and datasets.
This project uses **Alembic** for database migrations. See `.claude/skills/database-migrations.md` for detailed guidelines.

**Key rules:**
- All schema changes go through Alembic migrations (never use `SQLModel.metadata.create_all()`)
- After modifying a model: `uv run alembic revision --autogenerate -m "Description"`
- Apply migrations: `uv run alembic upgrade head`

**Local development:**
```bash
supabase start # Start local Supabase
uv run python scripts/init.py # Run migrations + apply RLS policies
uv run python scripts/seed.py # Seed data
```

`scripts/init.py --reset` drops and recreates everything (destructive).

## Modal sandbox + Claude Code CLI gotchas

Expand Down
Loading