Skip to content

Key Concepts

Understanding these concepts before reading any code will save you hours of confusion.

1. One database = one agency = one server

This is the single most important architectural fact about Orchid.

Each fertility agency gets its own isolated MySQL database and its own dedicated AWS Elastic Beanstalk instance. There is no shared database. No agency can ever see another agency’s data — not through a bug, not through a misconfigured query, because the databases are physically separate.

Agency A → EB Instance A → MySQL Database A
Agency B → EB Instance B → MySQL Database B
Agency C → EB Instance C → MySQL Database C

Consequence for queries: Because each server only ever connects to one agency’s database, you will see this pattern constantly:

agency = models.Agency.query.first()

This is not lazy code — it is the correct pattern. There is always exactly one Agency record in the database, because this database belongs to exactly one agency.

Consequence for cron jobs: cronapp.py is the exception. It runs once and iterates all databases by doing a SHOW DATABASES and creating a dynamic SQLAlchemy engine for each one. If you’re debugging a cron job, remember it runs against every agency.

2. The g object — the request context carrier

Flask’s g is a per-request object that lives for the duration of one HTTP request and is then discarded. Orchid uses it as the primary way to pass context through the call stack without threading it through function arguments.

The before_request hook in flaskapp.py populates g at the start of every request:

g.request_id # UUID4 — unique per request, used for log correlation
g.request_start_time # monotonic timestamp for request duration
g.agency # The Agency model instance for this server
g.admin # The logged-in Admin model (or None)
g.user # The logged-in User model (or None)
g.provider # The logged-in Provider model (or None)
g.np_user # Non-participating user (or None)
g.third_party # Third-party user (or None)
g.client # Browser/mobile info (is_mobile, ip_address, browser_class)
g.today # date.today() — use this, not date.today() directly
g.current_time # datetime.utcnow()
g.FORM_KEY # Encryption key for form fields
g.cloudfront_available # Whether CloudFront is configured

Why does this matter? When you see g.admin in a view function, you don’t need to pass the admin in — it is already there from before_request. When writing utility functions, import from flask import g and access it directly. Never pass g as a function argument — just use it.

3. _LOCAL_MODE — dev vs. prod behavior

Many side effects are disabled in local development. The _LOCAL_MODE boolean flag (set from the config file) is checked throughout the codebase:

if not _LOCAL_MODE:
send_email_via_sendgrid(...) # Only in production
upload_to_s3(...) # Only in production
log_to_cloudwatch(...) # Only in production

In local mode: email sending is silenced (or logged to console), S3 uploads are skipped, CloudWatch logging falls back to console. This is why your local dev environment works without real AWS credentials.

The flag is set in flaskapp.py based on whether _LOCAL is in the config. If localflaskapp.cfg exists, it enables local mode automatically.

4. Blueprint pairs — views + API

Every major feature has two Python files:

orchid/views/matching.py → HTML pages (GET routes, returns render_template())
orchid/api/matching.py → JSON API (mostly POST routes, returns jsonify())

The views file handles page rendering. The API file handles data mutations and AJAX calls from the page. They register as separate Flask blueprints with different URL prefixes (/admin/matching vs /api/matching).

When you need to find where a button’s click handler goes, look in the API file for the feature. When you need to find how a page is rendered, look in the views file.

5. The Crudler pattern

orchid/utils/flaskutils.py contains a Crudler class that wraps SQLAlchemy operations with automatic Flask context:

from orchid.utils.flaskutils import Crudler
# Instead of:
db.session.add(obj)
db.session.commit()
# Use:
Crudler.add(obj)
Crudler.commit()

Crudler automatically extracts user_id, user_type, user_agent, and request path from the Flask context and passes them to model-level change tracking. It also handles rollback and error logging on commit failure.

6. large_dicts.py — the giant config file

orchid/large_dicts.py is a 653 KB Python file containing lookup tables, display name mappings, and configuration data used throughout the app. Things like:

  • All valid admin role types
  • Case stage definitions with their display names and URL slugs
  • Country/state/ethnicity lists
  • Document type mappings
  • Status label configs

When you see code like large_dicts.SOME_DICT[some_key], the value comes from this file. Before writing a hardcoded string, check if there’s already a dict in large_dicts.py for it.

7. Model inheritance and mixins

Most models extend BaseModel (defined in orchid/models/__init__.py), which provides:

  • id primary key
  • add(), update(), delete() instance methods with change tracking
  • created_at / updated_at timestamps (on most models)

There are also mixins in orchid/models/mixins.py that add specific capabilities:

  • AddressMixin — street/city/state/zip/country fields
  • DocumentMixin — S3 file reference fields
  • FlagsMixin — boolean flag columns
  • TaskMixin — task completion tracking
  • EncryptedSsnMixin — encrypted SSN storage

When adding new models, always check if an existing mixin covers what you need before adding raw columns.

8. Error models

orchid/models/__init__.py defines three error tracking tables:

  • Errors — general application errors
  • ErrAPI — API-level errors
  • Err400 — 400-series HTTP errors

They all use content-based deduplication: the same error message only creates one record. This prevents runaway error floods. The add_error() function handles this automatically — call it instead of just logging.

9. Jinja filters and macros

There are 70+ custom Jinja filters registered in orchid/jinja_filters.py. Before writing Python logic inside a template, check whether a filter already does what you need:

{{ amount | currency }} {# $1,234.56 #}
{{ date_obj | date_readability }} {# January 15, 2025 #}
{{ phone | phone_readability }} {# (555) 123-4567 #}
{{ s3_key | cloudfront }} {# presigned CloudFront URL #}

Similarly, check templates/macros.html (and the other macro files) before writing raw HTML. The macro system covers modals, file uploaders, date pickers, page headers, cards, and much more.

See Jinja Filters Reference and Jinja Macros for complete lists.