Agency & Multi-Tenancy
What this module does
The Agency module is the foundation layer of Orchid. It establishes the multi-tenant architecture that makes data isolation possible, and it stores all configuration that controls how a specific agency’s Orchid instance behaves — branding, feature flags, matching preferences, DQ rules, and more.
Every single request, every query, and every piece of business logic runs inside the context of exactly one agency. This module is what makes that possible.
Business value
A fertility agency needs to know that their clients’ sensitive data cannot be seen by any other agency. The agency also needs to be able to configure Orchid to match their workflow, their DQ criteria, and their brand. This module handles both.
Key files
Directoryorchid/
Directorymodels/
- agency.py 16 model classes — the core of this module
- __init__.py BaseModel, database initialization
Directoryviews/
- admin_agency.py Agency settings admin UI
- setup.py 137 KB agency setup wizard
Directoryapi/
- admin_general_editables.py Agency config editing API
- flaskapp.py before_request hook that loads g.agency
Data model
erDiagram Agency ||--o| AgencyPreferences : "has" Agency ||--o| AgencyColors : "has" Agency ||--o| AgencyDQSettings : "has" Agency ||--o| MatchingFilterPreferences : "has" Agency ||--o| NotificationPreferences : "has" Agency ||--o| AgencyCustomResponses : "has" Agency ||--o| AgencyCustomCopy : "has" Agency ||--o| ProfileFooterSettings : "has" Agency ||--o{ AgencyTeam : "has many" Agency ||--o{ AgencyDocument : "has many" Agency ||--o{ AgencyDocumentFolder : "has many" AgencyTeam ||--o{ AgencyTeamAdmin : "has many"Key models explained
Agency — The root record. There is exactly one Agency row per database. Stores basic information: name, contact, subdomain, and subscription tier.
AgencyPreferences — Operational preferences: which features are enabled, which case types the agency handles (do they do ED cases? GC cases? Both?), email settings, notification preferences.
AgencyColors — Branding: primary/secondary colors, logo, and custom CSS used to white-label the client portal per agency.
AgencyDQSettings — Disqualification rules. Numeric thresholds (age ranges, BMI limits) and boolean flags that control which applicants are automatically flagged as ineligible.
MatchingFilterPreferences — Which filters appear on the matching UI, their defaults, and which are required. Controls what IPs can filter on when browsing profiles.
AgencyTeam / AgencyTeamAdmin — Organizes admin users into teams. Used for confidential case access control — some cases are visible only to the assigned team.
ProfileAppearance — Controls which sections and fields appear on the public-facing profile pages for each role type (ED, GC, SD).
The multi-tenancy pattern
Database-level isolation
Each agency’s data lives in a completely separate MySQL database. The flaskapp.py process connects to one database (configured via DB_NAME in environment config) and never queries any other database.
# This is the correct way to get the current agency — alwaysagency = models.Agency.query.first()
# Or from the request context (already loaded by before_request)agency = g.agencyThere is no WHERE agency_id = X clause in queries. There is no need — every row in the database belongs to this agency by definition.
The cron exception
cronapp.py is the only place that breaks this pattern. Because cron jobs need to run for all agencies, it uses:
# Cron iterates all agency databases like this:for db_name in get_all_agency_databases(): engine = create_engine(f"mysql+pymysql://user:pass@host/{db_name}") # run job against this engineAny code that runs in cronapp.py must handle this multi-database iteration carefully. Never assume cronapp.py is running against a single database.
How configuration flows
1. Agency is created and seeded with defaults2. Agency admin uses /setup or /admin/agency to configure: - DQ settings - Matching filter preferences - Branding (colors, logo) - Workflow templates - Form configurations3. Configuration is stored in the AgencyPreferences, AgencyDQSettings, AgencyColors, etc. models4. On every request: before_request loads g.agency5. Feature code reads g.agency.preferences, g.agency.dq_settings, etc.Gotchas
Common tasks
Get the current agency in a view or API:
from flask import gagency = g.agency # already loaded by before_requestCheck if the agency has a specific feature enabled:
prefs = models.AgencyPreferences.query.filter_by(agency_id=g.agency.id).first()if prefs.has_gestational_carrier_program: # show GC-related UIGet agency branding colors:
colors = models.AgencyColors.query.filter_by(agency_id=g.agency.id).first()primary_color = colors.primary_color if colors else '#8b3fa8'