System Architecture
System diagram
graph TB subgraph "Client Browsers" Admin["Agency Staff\n(Admin Portal /admin)"] User["Clients\n(IPs, EDs, GCs — /user)"] Provider["Medical Providers\n(/provider)"] Guest["Guest / 3rd Party\n(/guest)"] end
subgraph "AWS Elastic Beanstalk — one instance per agency" subgraph "flaskapp.py — Main Web Application" direction TB BR["before_request hook\nResolves agency, user, admin, provider"]
subgraph "Views Layer — returns HTML" AV["/admin/* — 12 admin blueprints"] UV["/user/* — client portal"] PV["/provider/* — provider portal"] FV["/forms/* — form intake"] GV["/guest/* — guest access"] end
subgraph "API Layer — returns JSON" AA["/api/admin/*"] UA["/api/user/*"] PA["/api/profile/*"] MA["/api/matching/*"] EA["/api/email/*"] WA["/api/workflow/*"] MoreAPIs["...18 more API blueprints"] end
AR["after_request hook\nMetrics, X-Ray, PostHog"] end
subgraph "cronapp.py — Scheduled Jobs" CJ["Iterates ALL agency DBs\nvia SHOW DATABASES"] end
subgraph "orchid_jobs — Huey Task Queue" EMS["email_sync"] EMM["email_matching"] end end
subgraph "Per-Agency MySQL Database" DB[(Agency DB\ncase, user, profile,\nmatching, workflow...)] end
subgraph "External Services" SG["SendGrid — email"] GM["Gmail API — sync"] MS["Microsoft Graph — sync"] HS["HelloSign — e-signatures"] SF["Salesforce — CRM sync"] TW["Twilio — SMS"] GU["Gurgle — messaging"] end
subgraph "AWS Infrastructure" S3["S3 — files & images"] CF["CloudFront — CDN"] CW["CloudWatch — logs"] XR["X-Ray — tracing"] end
Admin --> AV & AA User --> UV & UA & PA & MA Provider --> PV Guest --> GV
AV & UV & PV & FV & GV --> BR AA & UA & PA & MA & EA & WA & MoreAPIs --> BR BR --> DB AR --> CW & XR
CJ --> DB EMS & EMM --> DB EMS --> GM & MS EA --> SG & GM & MS AA --> HS & SF GU --> DB DB --> S3 & CFEntry points
There are three separate Python applications:
flaskapp.py — The main web app
This is the application that end users interact with. It registers 50+ Flask blueprints — some serve HTML pages (the “views”), some serve JSON (the “APIs”). They share the same process, the same database connection, and the same g (Flask globals) object.
See Key Concepts for how the g object works.
cronapp.py — Background scheduler
A separate Flask application that runs on a schedule. Unlike flaskapp.py, which connects to one agency’s database, cronapp.py iterates over all agency databases using a SHOW DATABASES query and creates a fresh SQLAlchemy engine for each one.
Jobs in cronapp.py include: sending scheduled notifications, running health checks, processing overnight reports.
orchid_jobs/ — Async task queue
A Huey-based job queue application that handles work that is too slow for a web request. Currently the two main job types are:
email_sync— Fetches new emails from Gmail/Outlook and stores them in the databaseemail_matching— Analyzes stored emails and links them to the right case or contact
Request lifecycle
Every request through flaskapp.py follows this sequence:
1. Request arrives2. before_request() fires: a. Generate g.request_id (UUID4) b. Record g.request_start_time c. Parse user agent → g.client d. Load g.agency (always Agency.query.first()) e. Check session → load g.admin, g.user, or g.provider f. Check session timeout (20 min for users, 4 hr for admins)3. Route handler runs → returns HTML or JSON4. after_request() fires: a. Attach X-Request-ID header b. Log request metrics (status, duration_ms, query_count) c. Fire PostHog analytics event5. Response sentBlueprint organization
All blueprints follow the same naming convention:
| Pattern | Example | Purpose |
|---|---|---|
{feature}_views | admin_case_management_views | Returns HTML pages |
{feature}_api | matching_api | Returns JSON responses |
Views and APIs for the same feature are always in separate files (orchid/views/matching.py + orchid/api/matching.py).
Frontend build
The frontend is not a single-page application. It is server-rendered HTML with JavaScript layered on top via jQuery and custom modules. Webpack bundles the JS and SASS into 6 feature-specific bundles:
| Bundle | Entry point | Who loads it |
|---|---|---|
admin | pack_admin.mjs | All admin portal pages |
user | pack_user.mjs | Client portal pages |
gurgleclient | pack_gurgleclient.mjs | In-app messaging |
journey_page | pack_journey.mjs | Case journey page |
form_intake | pack_intake.mjs | Form submission pages |
profile | pack_profile.mjs | Profile editing pages |
Infrastructure
- Hosting: AWS Elastic Beanstalk, one EB environment per agency
- Database: MySQL (one database per agency, isolated)
- Files: S3 for documents, images, videos; CloudFront as CDN
- Logging: Structured JSON to CloudWatch via
watchtower - Tracing: AWS X-Ray
- Analytics: PostHog (product events)
- Email delivery: SendGrid
- SMS: Twilio
- Secrets: Environment variables via EB environment config (never committed)