Skip to content

Commit 194609d

Browse files
Merge pull request #20 from PolicyEngine/feat/modal-economy-sims
feat: Remove worker, add Modal auth to Cloud Run
2 parents 6cedcc9 + 511d0b0 commit 194609d

10 files changed

Lines changed: 26 additions & 114 deletions

File tree

.env.example

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@ LOGFIRE_TOKEN=your-logfire-token-here
2121
LOGFIRE_ENVIRONMENT=local
2222

2323
# Modal.com (compute backend)
24-
# Authentication is via `modal token set` command, not env vars
25-
# Modal functions need a secret called "policyengine-db" with:
26-
# DATABASE_URL, SUPABASE_URL, SUPABASE_KEY, STORAGE_BUCKET
24+
# Get tokens from modal.com dashboard or via `modal token new`
25+
MODAL_TOKEN_ID=ak-...
26+
MODAL_TOKEN_SECRET=as-...

.github/workflows/deploy.yml

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ jobs:
8585
TF_VAR_supabase_db_url: ${{ secrets.SUPABASE_DB_URL }}
8686
TF_VAR_logfire_token: ${{ secrets.LOGFIRE_TOKEN }}
8787
TF_VAR_logfire_environment: prod
88+
TF_VAR_modal_token_id: ${{ secrets.MODAL_TOKEN_ID }}
89+
TF_VAR_modal_token_secret: ${{ secrets.MODAL_TOKEN_SECRET }}
8890
run: terraform apply -auto-approve
8991

9092
- name: Deploy API to Cloud Run
@@ -93,12 +95,6 @@ jobs:
9395
--region=${{ vars.GCP_REGION }} \
9496
--image=$IMAGE_URL:${{ github.sha }}
9597
96-
- name: Deploy worker to Cloud Run
97-
run: |
98-
gcloud run services update ${{ vars.WORKER_SERVICE_NAME }} \
99-
--region=${{ vars.GCP_REGION }} \
100-
--image=$IMAGE_URL:${{ github.sha }}
101-
10298
- name: Get API URL
10399
run: |
104100
API_URL=$(gcloud run services describe ${{ vars.API_SERVICE_NAME }} \

CLAUDE.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ FastAPI backend for tax-benefit policy microsimulations using PolicyEngine's UK
1515

1616
```bash
1717
make install # install dependencies with uv
18-
make dev # start supabase + api + worker via docker compose
18+
make dev # start supabase + api via docker compose
1919
make test # run unit tests
2020
make integration-test # full integration tests
2121
make format # ruff formatting
2222
make lint # ruff linting with auto-fix
23+
make modal-deploy # deploy Modal.com serverless functions
2324
```
2425

2526
Local development uses docker compose with a local Supabase instance. Copy `.env.example` to `.env` for local config.
@@ -29,7 +30,7 @@ Local development uses docker compose with a local Supabase instance. Copy `.env
2930
- `src/policyengine_api/api/` - FastAPI routers (14 endpoint groups)
3031
- `src/policyengine_api/models/` - SQLModel database models
3132
- `src/policyengine_api/services/` - database and storage services
32-
- `src/policyengine_api/tasks/` - background worker for async simulations
33+
- `src/policyengine_api/modal_app.py` - Modal.com serverless functions
3334
- `supabase/migrations/` - SQL migrations for RLS and Postgres features
3435
- `terraform/` - GCP Cloud Run infrastructure
3536
- `docs/` - Next.js documentation site
@@ -38,7 +39,7 @@ Local development uses docker compose with a local Supabase instance. Copy `.env
3839

3940
SQLModel defines all database schemas. Use Pydantic BaseModel for request/response schemas and BaseSettings for configuration. All API endpoints should be async functions.
4041

41-
The background worker processes simulations asynchronously. Clients poll simulation status until complete.
42+
Modal.com serverless functions handle compute-intensive simulations with sub-1s cold starts. Clients poll simulation status until complete.
4243

4344
MCP server exposes all endpoints as Claude tools via streamable HTTP transport.
4445

Dockerfile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# Build docs
2-
FROM node:22-slim AS docs-builder
2+
FROM oven/bun:1 AS docs-builder
33
WORKDIR /docs
44
COPY docs/package.json docs/bun.lock ./
5-
RUN npm install
5+
RUN bun install
66
COPY docs/ ./
7-
RUN npm run build
7+
RUN bun run build
88

99
# Build API
1010
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ modal-deploy:
120120
"DATABASE_URL=$$SUPABASE_POOLER_URL" \
121121
"SUPABASE_URL=$$SUPABASE_URL" \
122122
"SUPABASE_KEY=$$SUPABASE_KEY" \
123+
"STORAGE_BUCKET=$$STORAGE_BUCKET" \
123124
--force
124125
uv run modal deploy src/policyengine_api/modal_app.py
125126

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ FastAPI service for PolicyEngine microsimulations with Supabase backend and obje
66

77
- RESTful API for tax-benefit microsimulations
88
- Supabase for PostgreSQL database and object storage
9-
- Background worker for simulation processing
9+
- Modal.com serverless compute with sub-1s cold starts
1010
- SQLModel for type-safe database models
1111
- Logfire observability and monitoring
1212
- Terraform deployment to GCP Cloud Run
@@ -171,7 +171,7 @@ This drops all tables, deletes the storage bucket, then recreates tables from SQ
171171
- **API server**: FastAPI application (port 8000)
172172
- **Database**: Supabase PostgreSQL
173173
- **Storage**: Supabase object storage for .h5 dataset files
174-
- **Worker**: Polling worker for background simulations
174+
- **Compute**: Modal.com serverless functions for simulations
175175

176176
### Data models
177177

@@ -195,7 +195,7 @@ policyengine-api-v2/
195195
│ ├── config/ # Settings
196196
│ ├── models/ # SQLModel database models
197197
│ ├── services/ # Database, storage
198-
│ ├── tasks/ # Background worker
198+
│ ├── modal_app.py # Modal.com serverless functions
199199
│ └── main.py # FastAPI app
200200
├── supabase/
201201
│ └── migrations/ # RLS policies and storage

docs/README.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,6 @@ This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-
55
First, run the development server:
66

77
```bash
8-
npm run dev
9-
# or
10-
yarn dev
11-
# or
12-
pnpm dev
13-
# or
148
bun dev
159
```
1610

terraform/main.tf

Lines changed: 4 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -106,76 +106,13 @@ resource "google_cloud_run_v2_service" "api" {
106106
name = "DEBUG"
107107
value = "false"
108108
}
109-
}
110-
}
111-
112-
depends_on = [google_project_service.required_apis]
113-
114-
lifecycle {
115-
ignore_changes = [template[0].containers[0].image]
116-
}
117-
}
118-
119-
# Cloud Run service for worker (always-on, polls for pending work)
120-
resource "google_cloud_run_v2_service" "worker" {
121-
name = "${var.project_name}-worker"
122-
location = var.region
123-
ingress = "INGRESS_TRAFFIC_INTERNAL_ONLY"
124-
125-
template {
126-
service_account = google_service_account.cloudrun.email
127-
128-
scaling {
129-
min_instance_count = 1
130-
max_instance_count = 1
131-
}
132-
133-
containers {
134-
image = "${var.region}-docker.pkg.dev/${var.project_id}/${google_artifact_registry_repository.repo.repository_id}/${var.project_name}:latest"
135-
command = ["python", "-m", "policyengine_api.tasks.worker"]
136-
137-
resources {
138-
limits = {
139-
cpu = var.worker_cpu
140-
memory = var.worker_memory
141-
}
142-
}
143-
144-
ports {
145-
container_port = 8080
146-
}
147-
148-
env {
149-
name = "SUPABASE_URL"
150-
value = var.supabase_url
151-
}
152-
env {
153-
name = "SUPABASE_KEY"
154-
value = var.supabase_key
155-
}
156-
env {
157-
name = "SUPABASE_DB_URL"
158-
value = var.supabase_db_url
159-
}
160-
env {
161-
name = "LOGFIRE_TOKEN"
162-
value = var.logfire_token
163-
}
164-
env {
165-
name = "LOGFIRE_ENVIRONMENT"
166-
value = var.logfire_environment
167-
}
168-
env {
169-
name = "STORAGE_BUCKET"
170-
value = var.storage_bucket
171-
}
172109
env {
173-
name = "WORKER_POLL_INTERVAL"
174-
value = "10"
110+
name = "MODAL_TOKEN_ID"
111+
value = var.modal_token_id
175112
}
176113
env {
177-
name = "WORKER_PORT"
178-
value = "8080"
114+
name = "MODAL_TOKEN_SECRET"
115+
value = var.modal_token_secret
179116
}
180117
}
181118
}

terraform/outputs.tf

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,6 @@ output "api_url" {
88
value = google_cloud_run_v2_service.api.uri
99
}
1010

11-
output "worker_service_name" {
12-
description = "Worker service name"
13-
value = google_cloud_run_v2_service.worker.name
14-
}
15-
1611
output "api_service_name" {
1712
description = "API service name"
1813
value = google_cloud_run_v2_service.api.name

terraform/variables.tf

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -76,26 +76,14 @@ variable "api_max_instances" {
7676
default = 10
7777
}
7878

79-
variable "worker_cpu" {
80-
description = "CPU for worker service"
79+
variable "modal_token_id" {
80+
description = "Modal.com token ID for serverless compute"
8181
type = string
82-
default = "2"
82+
sensitive = true
8383
}
8484

85-
variable "worker_memory" {
86-
description = "Memory for worker service"
85+
variable "modal_token_secret" {
86+
description = "Modal.com token secret for serverless compute"
8787
type = string
88-
default = "2Gi"
89-
}
90-
91-
variable "worker_min_instances" {
92-
description = "Minimum number of worker instances"
93-
type = number
94-
default = 1
95-
}
96-
97-
variable "worker_max_instances" {
98-
description = "Maximum number of worker instances"
99-
type = number
100-
default = 5
88+
sensitive = true
10189
}

0 commit comments

Comments
 (0)