Small Flask API for looking up Canadian PRIZM segments by postal code. It is shaped for a Salesforce Flow that submits up to 10 Canadian postal codes per run.
The current PRIZM site no longer needs browser scraping. The React frontend calls public JSON services:
- Environics geocoder: resolves a postal code to PRIZM segmentation code
- Supabase REST: returns PRIZM segment profile data
This app now uses those APIs directly instead of Selenium/Chromium, which makes Railway deployment much lighter and avoids the old long-running browser crash mode.
GET /healthGET /dashboardThe dashboard shows cache totals, recent daily additions, recent failures, searchable cached postal-code data, CSV export, and a manual weekly-report send button. It uses HTTP Basic Auth.
Recommended dashboard/reporting variables:
DASHBOARD_USERNAME=dena
DASHBOARD_PASSWORD=<set a dashboard password>
WEEKLY_REPORT_RECIPIENTS=dena@example.org
SMTP_HOST=<smtp host>
SMTP_PORT=587
SMTP_USERNAME=<smtp username>
SMTP_PASSWORD=<smtp password>
SMTP_FROM=<sender email>
SMTP_STARTTLS=1If DASHBOARD_PASSWORD is not set, the dashboard can temporarily use PRIZM_API_KEY as the password unless ALLOW_API_KEY_AS_DASHBOARD_PASSWORD=0 is set.
GET /api/prizm?postal_code=V8A0A8POST /api/prizm/batch
Content-Type: application/json
{
"postal_codes": ["V8A0A8", "M5V3L9"]
}The default batch limit is 10 postal codes. Override with MAX_BATCH_POSTAL_CODES.
GET /api/segmentsUseful if you want the full segment reference data without postal-code lookup.
GET /api/cache/entries?status=success&search=V8A&limit=500
GET /api/cache/export.csvGET /api/reports/weekly
POST /api/reports/weekly/sendFor Railway weekly email delivery, create a cron service/worker that runs:
python send_weekly_report.pyRailway evaluates native cron schedules in UTC; Monday 8am Vancouver is 0 15 * * 1 during PDT.
For Wilderness Committee Salesforce delivery, WCPrizmWeeklyReportEmail can be scheduled instead. It calls /api/reports/weekly with the configured API key/custom label and sends the plain-text report via Salesforce email. The deployed production schedule is 0 0 8 ? * MON (WC PRIZM Weekly Report).
{
"postal_code": "V8A 0A8",
"prizm_code": "21",
"segment_number": "21",
"segment_name": "Scenic Retirement",
"segment_description": "Older, middle-income suburbanites",
"average_household_income": "$140,223",
"education": "High School/College",
"urbanity": "Suburban",
"occupation": "Mix",
"diversity": "Low",
"family_life": "Couples/Families",
"tenure": "Own",
"home_type": "Single Detached/Row",
"status": "success"
}python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python app.pyThen open http://localhost:8080/health.
Railway can deploy this repo with either the Dockerfile or the Procfile.
Recommended Railway variables:
PRIZM_API_KEY=<set-this-and-send-it-from-salesforce>
MAX_BATCH_POSTAL_CODES=10
PRIZM_CACHE_DB_PATH=/data/prizm_cache_v2.db
PRIZM_SUCCESS_CACHE_DAYS=3650
PRIZM_INVALID_CACHE_DAYS=90
PRIZM_ERROR_CACHE_DAYS=30
WEB_CONCURRENCY=2
GUNICORN_THREADS=4
GUNICORN_TIMEOUT=60If you set PRIZM_CACHE_DB_PATH=/data/..., add a Railway volume mounted at /data so cached lookups survive redeploys. The cache is optional; the API works without a persistent volume.
To preload Railway's persistent cache from the existing CSV export, run this in a Railway shell after the volume is mounted:
python cache_cli.py import-csv prizm_import_09112025.csv --replace
python cache_cli.py statsSuccessful lookups default to a 10-year cache, invalid postal-code formats default to 90 days, and cacheable not-found/error results default to 30 days. Upstream quota/network failures are not cached.
When PRIZM_API_KEY is set, all /api/* routes require either:
X-API-Key: <key>or:
Authorization: Bearer <key>Optional override variables:
PRIZM_GEOCODER_API_URL=https://api.environicsanalytics.com/geocoder-openauth
PRIZM_GEOCODER_COUNTRY=ca
PRIZM_GEOCODER_VINTAGE=2026
PRIZM_SUPABASE_URL=https://rkfddhcgcubrelqdzajw.supabase.co
PRIZM_SUPABASE_KEY=<public publishable key>
PRIZM_UPSTREAM_TIMEOUT=12
CORS_ALLOW_ORIGIN=*python3 -m unittest test_prizm_api.py- Selenium files are left in the repo for historical reference, but the Flask API no longer imports or uses Selenium.
- Rural postal codes are resolved directly from the frontend's Supabase
rural_postal_codestable. - Urban postal codes still depend on the Environics geocoder API. During triage on June 21, 2026, that endpoint returned
403 Quota Exceeded, so urban postal-code lookups may fail until the public quota replenishes or a licensed/geocoding credential is available. prizm_cache_v2.db, CSV exports, debug screenshots, andnode_modulesare runtime/local artifacts and are ignored by Docker.- The old crash was most likely caused by a globally reused Selenium Chrome session combined with Flask debug mode and repeated screenshot/page-source capture. Removing the browser from request handling eliminates that failure path.