API and Dashboard for GLOW longitudinal questionnaire data.
This project has two components:
- API (
/api) — a read-only FastAPI service that provides suppression-safe access to student questionnaire data - Dashboard (
/dashboard) — a SvelteKit app for authenticated users to view and query data via interactive charts
In local compose, the Glow services are exposed directly:
- Dashboard →
http://localhost:3000 - API →
http://localhost:8000
The self-hosted ODK Central stack is available behind the optional odk compose profile.
Its public entrypoint defaults to http://localhost:8080 and https://localhost:8443.
For the most consistent development experience:
- Install Docker and VS Code with the Dev Containers extension
- Open this repo in VS Code
- Click "Reopen in Container" when prompted (or use Command Palette → "Dev Containers: Reopen in Container")
- Wait for the devcontainer to build and app services to start
- Generate and seed test data (see Data Collection & Format section below)
- Create users:
docker compose exec api glow-api users create --admin admin
The dashboard will be available at http://localhost:3000 and the API at http://localhost:8000.
The devcontainer provides:
- Python 3.12 + Node 22 pre-installed
uvfor fast Python package management- Playwright with browser dependencies
- All app services running through the production-like compose base plus dev overrides
compose.yml now defines the production-like core stack: Glow dashboard, Glow API, Glow Postgres, and an optional ODK Central profile. Local development layers on compose.override.yml, which switches the API and dashboard into reload/watch mode and bind-mounts the workspace.
# Set a strong JWT secret
export GLOW_SECRET_KEY="your-strong-secret-here"
# Set a Postgres password for the local stack
export POSTGRES_PASSWORD="your-local-postgres-password"
# Start the full stack including ODK Central
docker compose --profile odk up --build
# In a separate terminal: Generate and seed test data
# (see "Data Collection & Format" section below for detailed steps)
# Create the first admin user (in a separate terminal)
docker compose exec api glow-api users create --admin adminThe dashboard will be available at http://localhost:3000.
The optional odk profile adds a self-hosted ODK Central stack using the official Central service/nginx images plus the supporting Postgres, Redis, Enketo, SMTP, and secrets services they require.
Network boundaries in the compose setup are:
- ODK Central internal services talk only on the
odk_internalnetwork. - Glow Dashboard can only reach Glow API on
dashboard_api. - Glow API can only reach its own Postgres on
api_db. - The only shared route between ODK Central and Glow is
service↔apionodk_api.
This keeps ODK Central isolated from the dashboard while still allowing API-mediated data flow.
| Variable | Default | Description |
|---|---|---|
GLOW_ODK_API_URL |
http://localhost:8383 |
ODK Central API base URL |
GLOW_ODK_API_EMAIL |
test@example.com |
ODK Central admin email |
GLOW_ODK_API_PASSWORD |
test-password |
ODK Central admin password |
GLOW_ODK_PROJECT_ID |
1 |
ODK Central project ID |
GLOW_ODK_FORM_ID |
bewell_questionnaire |
ODK form ID |
GLOW_DATA_CACHE_PATH |
(none) | Optional path for DataFrame cache (e.g., /data/cache.parquet) |
GLOW_DATA_REFRESH_HOURS |
1 |
How often to poll ODK Central for new data |
GLOW_MIN_N |
5 |
Minimum distinct student count for suppression |
GLOW_SECRET_KEY |
(insecure default) | JWT signing secret — must be set in production |
GLOW_ALGORITHM |
HS256 |
JWT algorithm |
GLOW_ACCESS_TOKEN_EXPIRE_MINUTES |
480 |
Token lifetime (8 hours) |
GLOW_METADATA_DATABASE_URL |
sqlite:///./metadata.db |
SQLAlchemy metadata database URL |
GLOW_CORS_ORIGINS |
["*"] |
JSON list of allowed CORS origins |
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/health |
— | Health check |
POST |
/auth/login |
— | Login (returns JWT) |
GET |
/schools |
User | List schools with query options |
POST |
/query |
User | Execute query with blanket suppression |
GET |
/admin/users |
Admin | List users |
POST |
/admin/users |
Admin | Create user |
PUT |
/admin/users/{id} |
Admin | Update user |
DELETE |
/admin/users/{id} |
Admin | Delete user |
GET |
/admin/me |
User | Current user info |
Interactive documentation is available at /docs (Swagger UI) and /redoc.
# Initialise the database
glow-api db init
# List users
glow-api users list
# Create a regular user
glow-api users create alice
# Create an admin user
glow-api users create --admin bob
# Update a user
glow-api users update alice --scope '{"filters": {"school": ["Greenwood"]}}'
# Delete a user
glow-api users delete aliceN is counted as distinct students (uid), not rows.
Any materialized result cell where the contributing student count is less than GLOW_MIN_N is suppressed
(set to empty in the CSV output) and recorded in the suppressions field of the response.
The SvelteKit dashboard provides:
- Login — JWT-based authentication
- Home — pre-built overview charts
- Query Builder — a step-based analytical query builder with built-in suppression
- Admin — user CRUD (admin users only)
This repo uses a centralized approach to minimize drift between dev/test/prod:
compose.yml— production-like core stack (dashboard, API, Postgres)- Used by: CI smoke tests and as the base for local development
- Defines runtime topology and network boundaries
- Includes the optional
odkprofile for self-hosted ODK Central
compose.override.yml— local development overrides- Enables source bind mounts, Vite/Uvicorn reload, and dev-friendly defaults
- Applied automatically by
docker compose up
compose.test.yml— test-specific overrides- Deterministic secrets, tighter healthchecks, no restart policies
- Usage:
docker compose -f compose.yml -f compose.test.yml up
.devcontainer/— VS Code devcontainer configuration- Provides tooling shell (Python 3.12, Node 22, uv, Playwright)
- Delegates app services to
compose.yml(no duplication) - Mounts Docker socket for "Docker outside of Docker"
.env.example— documented environment variable defaults
The devcontainer provides a fully configured development environment. After opening in the devcontainer:
API development:
cd api
uv pip install -e ".[test]"
uv run pytestDashboard development:
cd dashboard
npm install
npm run check # Type checking
npm run lint # LintingRunning smoke tests:
bash scripts/smoke_compose.shAPI:
cd api
python3.12 -m venv .venv
source .venv/bin/activate
pip install -e ".[test]"
glow-api db init
uvicorn glow_api.main:app --reload
pytestDashboard:
cd dashboard
npm install
npm run dev
npm run checkSee deploy/terraform/ for infrastructure definitions (EC2 + ALB + Route53).
See DEPLOYMENT.md for the quick start guide using ./deploy/deploy.sh.
The deployment uses:
- EC2 instance running Docker Compose
- Application Load Balancer with subdomain routing
- Route 53 for DNS management
- ACM for TLS certificates
- Auto-configured ODK Central integration
See deploy/terraform/variables.tf for all available variables. Key ones:
| Variable | Description |
|---|---|
image_tag |
Docker image tag (set by deploy.sh) |
api_secret_key |
JWT secret (set by deploy.sh from env) |
aws_region |
AWS region |
api_min_n |
Minimum N for suppression (default: 5) |
certificate_arn |
ACM certificate ARN for HTTPS (optional) |
In production, data is collected via ODK Central:
Real users → ODK Collect app → ODK Central → Glow API → Dashboard
The BeWell questionnaire form is already included in this repo at odk-forms/bewell_questionnaire.xml. The form includes:
uid— student identifier (used for N counting in suppression)wave— survey wave numberschool,yearGroup,class— school structuresex,ethnicity— demographicsd_age,d_city,d_country— derived/custom fields- Questionnaire items from the #BeeWell GM Survey (e.g.
bw_wbeing_1–bw_wbeing_7for SWEMWBS wellbeing,bw_emodies_1–bw_emodies_10for emotional difficulties, and ~120 further items)
The form is automatically uploaded to ODK Central during deployment (see deploy/scripts/activate-stack.sh).
For local development and testing, synthetic data is generated and seeded into ODK Central:
glow-dummies → data/data.csv → seed_odk_test_data.py → ODK Central → Glow API → Dashboard
Use glow-dummies to generate realistic test data:
# Install glow-dummies
pip install glow-dummies
# Generate synthetic BeWell data
glow_dummies \
--config https://raw.githubusercontent.com/OxfordRSE/glow-dummies/main/examples/beewell_model.toml \
--seed 42 \
--output csv \
> data/data.csvThis creates a CSV with the same structure as production data (student×wave long format with ~140 columns).
Upload the generated data to your local ODK Central instance:
cd deploy/scripts
pip install -r requirements.txt
# Seed all rows from data.csv
python seed_odk_test_data.py \
--csv ../../data/data.csv \
--odk-url http://localhost:8080 \
--email admin@example.com \
--password your-odk-password \
--project-id 1 \
--form-id bewell_questionnaire
# Or seed just the first 100 rows for faster testing
python seed_odk_test_data.py \
--csv ../../data/data.csv \
--odk-url http://localhost:8080 \
--email admin@example.com \
--password your-odk-password \
--project-id 1 \
--limit 100The seeding script is idempotent — it uses the uid field as the instance ID, so running it multiple times won't create duplicates.
The Glow API polls ODK Central hourly (configurable via GLOW_DATA_REFRESH_HOURS). To force an immediate refresh during development:
# Restart the API container to trigger a fresh data load
docker compose restart api
# Check the API logs
docker compose logs -f apiYou should see logs showing data being fetched from ODK Central and cached.
For quick testing with minimal data (21 rows, 2 waves, 2 schools), use the included demo dataset:
python deploy/scripts/seed_odk_test_data.py \
--csv testdata/demo_data.csv \
--odk-url http://localhost:8080 \
--email admin@example.com \
--password your-odk-password \
--project-id 1