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 /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.
{
"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.