Skip to main content

Engineering

How the platform is built, and the rules that keep a multi-tenant healthcare system from breaking in production.

Architecture at a glance

  • Frontend (staff): Next.js — Basis Flow Web → Firebase Hosting platform.
  • Frontend (patients): Flutter app (white-label) + Next.js client portal (Netlify).
  • Backend: Python Cloud Functions gen2 (3.12), dispatcher pattern (REQUEST_MODEL_MAP), GB_1 memory, per-function secrets=[] scoping.
  • Data: Firestore (clinicsv2 — never legacy clinics), camelCase fields, date-only values as strings.
  • Shared: basiscore (Dart models), basiscoreui, basis-functions.

Deep dives: firebase-backend · firestore-schema · UNIFIED_DATA_ARCHITECTURE · CLINIC_MODULARIZATION · date-time-handling

The non-negotiable rules

The full list lives in CLAUDE.md. The ones that cause the most production pain:

Recurring foot-guns
  • Add Firestore/Storage rules AND deploy before writing new paths — the #1 cause of permission errors. The rule file is inert until deployed.
  • Never updateDoc on clinicsv2/{clinicId} directly from the web client.
  • Firestore field types are never guaranteed at runtime — a TS interface lies; coerce and default every access (String(v), (arr || []), (n ?? 0)).
  • Changing a Pydantic model requires redeploying the function.
  • Never deploy all functions at once--only functions:name --force.
  • Encrypted Stripe fieldsStripeConfig.decrypt(...), never read oauth_token.
  • Adding a new field is a full-stack checklist — model + request models + add handler + update handler + frontend save + frontend load + TS type, or it silently won't persist.

Adding an Atlas tool = 5 silent-fail layers

TOOLS schema → TOOL_PERMISSION_MAP (default-DENY) → intent set (missing = stripped) → dispatch branch → decorator secrets=[]. lint_registrations.py checks them.

Deploy

Never raw firebase deploy. Ship via ship.sh after explicit approval. See deployment and How we work.

Testing & debugging

QA_GUIDE · TESTING_GUIDE · debugging-tools. Test with Admin role, not System Admin (which bypasses permission checks).

Gap

No auto-generated API reference (OpenAPI). We have hand-written contracts (reports-api-contract, atlas-api-webhooks) and no formal ADR log.