Skip to main content
Synced from the repo — do not edit here

Canonical source: docs/claude/programs-protocols.md. This page is generated by docs/scripts/sync-handbook.mjs. Edit the source file in the repo; changes appear here on the next build.

Programs & Protocols + Biomarker Tracking

Programs (also called Protocols) are structured care plans with scheduled activities, exercises, and supplements.

Architecture Overview

Basis Flow Web (Staff) ---assign_protocol---> clinic_service (Firebase Function)
Basis Hybrid (Client) ---event completion---> sync_user_event_to_clinic (Trigger)
Basis Web (Client Portal) ---read-only--->

FIRESTORE:
clinicsv2/{clinicId}/clinic_users/{uid}/
protocols/{protocolId} <-- SINGLE SOURCE OF TRUTH
events/{eventId} <-- Staff reads/writes here

users/{uid}/
protocols/{protocolId} <-- Mirror (backup)
events/{eventId} <-- Client writes here
recurringEvents[] <-- Habit definitions

Current mechanisms (updated 2026-07-18 — epic #398)

The sections below the intro predate these; this block is the current source of truth for how protocols behave now. All in functions_clinic.py unless noted.

  • Canonical habit shape (#387): every write path — assign_protocol, update_protocol, the rich recurringEvents, and BOTH protocol mirrors — serializes habits through the ONE function serialize_protocol_habit() (PROTOCOL_HABIT_SCHEMA_VERSION). Emits id/type(resolved)/name/rule/ui/description/notes/defaultSupplements/normalized workoutPreset/presets/meal fields, so the shape can't fork (the old minimal mirror used to drop defaultSupplements/description/notes → supplements invisible on web). Assign writes both mirrors in one atomic batch; proto docs carry version + changeLog[] audit.
  • #8 notes hierarchy: three levels persist to both mirrors → client-visible: protocol description → activity notes/descriptionitem-level note (per-exercise on workoutPreset.exercises[].note; per-supplement on defaultSupplements[].note / supplementPreset.supplements[].note).
  • Recurrence data-loss stopper (#397): protocol_reconcile.event_has_preservable_state(data)update_protocol PRESERVES occurrences carrying completion/note/move/override state instead of the old delete-all-and-regenerate (which wiped completions on any edit). Full per-instance exceptions[] + edit-scopes (this / this-and-future / all) are still TODO.
  • Lifecycle + auto-expiry: status active | ended | stopped | deleted. Two ways to end, both run the SAME ended-handler in update_protocol (deletes future expected-* events + generates the frozen efficacy summary): (1) manual — the "End Protocol" button in basisflow-web PlanTab.tsx (request_type:'update_protocol', status:'ended'); (2) auto — the expire_protocols daily scheduler (08:00 UTC) finds active protocols past scheduledUntil and ends them. It runs BOTH a string-bound and datetime-bound collection-group query (Firestore filters are type-segregated → catches scheduledUntil stored as a date string AND as a legacy Timestamp). Assign defaults to end-dated now (AssignProtocolModal: pre-fills from template duration, else a 12-week default; "Ongoing" is an explicit opt-out) — because efficacy only renders for ended protocols.
  • Efficacy (#399, #7/#12): _generate_protocol_summary computes adherence + biomarkerProgress[{code,name,startValue,endValue,target,direction,source}], FREEZES the window at scheduledUntil (not now), and writes to BOTH mirrors — the user mirror is client-readable, so the app/portal render it and Atlas (clinical_agent_stream._exec_get_protocols) narrates it. Date-only endpoints are coerced to UTC-aware before comparison.
  • Lab-type biomarker routing (#6): biomarkers whose code is in the lab registry read start/end from users/{uid}/labs (analyteKey), not healthSummaries (which only holds wearable/nutrition series); wearable codes still map via _BIOMARKER_TYPE_KEY (#505). Each biomarkerProgress entry carries source: 'labs'|'healthSummaries'.
  • Activity↔booking (#10): an activity may carry linkedServiceId (+bookingRequired). bridge_booking_to_protocol() auto-completes the day's occurrence (completionSource:'booking') when the client attends the linked session — wired into BOTH group (update_group_attendee_status confirmed) and 1:1 (update_appointment_status CONFIRM) attendance; idempotent. The clinician mapping UI (set linkedServiceId) is #543, still TODO in basisflow-web.
  • Migration: tools/migrate_protocol_shape.py backfills existing (active-only) protocols to the canonical shape. Verify tools: tools/verify_protocol_reconcile.py, tools/verify_booking_bridge_and_lab_progress.py (emulator gates).

Single Source of Truth

clinicsv2/{clinicId}/clinic_users/{uid}/protocols/{protocolId} is the authoritative source for:

  • Protocol metadata (title, description, status, dates)
  • Habit definitions with workoutPreset and supplementPreset
  • Activity scheduling rules

All platforms read protocol details from this location. The users/{uid}/protocols path is a mirror for backup/fallback only.

Data Models

Protocol Document (protocols/{protocolId})

interface ProtocolDocument {
protocolId: string; // e.g., "huberman_fitness_protocol_1234567890"
clinicId: string; // REQUIRED - enables client apps to find clinic path
title: string;
description?: string;
status: 'active' | 'completed' | 'cancelled' | 'paused' | 'ended';
assignedAt: Timestamp;
scheduledFrom?: string; // ISO date
scheduledUntil?: string; // ISO date
habits: HabitDefinition[]; // Array of activities with presets
biomarkers?: any[];
clinicalEvidence?: ClinicalEvidenceItem[];
expectedOutcomes?: ExpectedOutcome[];
}

interface HabitDefinition {
id: string; // Matches event.habitId
type: string; // BasisEventType (e.g., "habitExercise", "habitSupplement")
name: string;
rule: RecurrenceRule;
ui?: { at: string; by: string[] };
workoutPreset?: WorkoutPreset;
supplementPreset?: SupplementPreset;
}

interface WorkoutPreset {
exercises: Exercise[];
}

interface SupplementPreset {
id?: string;
name: string;
form?: string; // "capsule", "tablet", "powder", etc.
dose?: { amount: number; unit: string };
}

Event Document (events/{eventId})

interface EventDocument {
id: string; // Format: "expected-{habitId}-{timestamp}"
protocolId: string; // Links to protocol document
habitId: string; // Links to specific habit in protocol.habits[]
clinicId: string; // Enables sync trigger to find clinic path
eventType: string; // BasisEventType
name: string;
start: Timestamp;
end: Timestamp;
status: 'upcoming' | 'completed' | 'missed' | 'skipped';
source: 'expected'; // Indicates generated from protocol
exercises?: ExerciseHistory[];
supplements?: SupplementV1[];
note?: string;
completionStatus?: string;
}

Dart Models (Basis Hybrid)

// BasisEventV1 - hybrid/basiscore/lib/src/models/event/event.dart
class BasisEventV1 {
String? protocolId; // Links to protocol document
String? habitId; // Links to habit in protocol.habits[]
String? clinicId; // Enables finding clinic path for preset lookup
}

// BasisRecurringEventV1
class BasisRecurringEventV1 {
String? protocolId;
BasisWorkoutPresetV1? workoutPreset;
List<BasisSupplementV1> defaultSupplements;
}

Write Paths

1. Protocol Assignment (Staff -> Backend)

File: basis-functions/functions/src/functions_clinic.py -> assign_protocol()

Basis Flow Web (AssignProtocolModal.tsx)
| clinic_service({ request_type: 'assign_protocol', ... })
|
assign_protocol():
|-- 1. Write to user.recurringEvents[] (habit definitions)
|-- 2. Write protocol doc to clinicsv2/.../protocols/{id} (includes clinicId)
|-- 3. Write protocol doc to users/{uid}/protocols/{id} (mirror)
|-- 4. Generate expected events (next 90 days)
Write to clinicsv2/.../events/{id} AND users/{uid}/events/{id}

2. Protocol Update (Staff -> Backend)

File: functions_clinic.py -> update_protocol()

  • Updates habit definitions, status, scheduling
  • Regenerates future expected events
  • Maintains dual-write pattern

3. Event Completion (Client -> Firestore -> Trigger)

File: functions_event_mirror.py -> sync_user_event_to_clinic()

Basis Hybrid (client completes activity)
| Writes to: users/{uid}/events/{eventId}
|
Firestore Trigger: sync_user_event_to_clinic
| Reads clinicId from event (or user doc)
|-- Mirrors to: clinicsv2/{clinicId}/clinic_users/{uid}/events/{eventId}

Read Paths

Basis Flow Web (Staff)

collection(db, 'clinicsv2', clinicId, 'clinic_users', clientId, 'protocols')
collection(db, 'clinicsv2', clinicId, 'clinic_users', clientId, 'events')

Basis Hybrid (Client)

// Fetches from BOTH paths, deduplicates
firestore.collection('clinicsv2').doc(clinicId)
.collection('clinic_users').doc(uid).collection('protocols')
firestore.collection('users').doc(uid).collection('protocols')

Basis Web (Client Portal)

collection(firebase.db, 'clinicsv2', clinic.clinicId, 'clinic_users', user.uid, 'protocols')

Key Backend Functions

FunctionFilePurpose
assign_protocolfunctions_clinic.pyCreate protocol + generate events
update_protocolfunctions_clinic.pyModify protocol + regenerate events
backfill_protocol_eventsfunctions_clinic.pyRepair missing recurring events
sync_user_event_to_clinicfunctions_event_mirror.pyMirror client completions to clinic

Key Frontend Components

ComponentPlatformFilePurpose
AssignProtocolModalBasis Flow Webcomponents/protocol/AssignProtocolModal.tsxCreate/assign protocols
PlanTabBasis Flow Webapp/(main)/clients/[id]/components/PlanTab.tsxDisplay calendar + protocols
RouteHabitsBasis Hybridview/routes/habits/route_habits.dartList protocols + activities
RouteSummaryBasis Hybridview/routes/summary/route_summary.dartEvent detail + preset display
ProtocolsPageBasis Webapp/portal/protocols/page.tsxRead-only protocol list

Activity Types

TypeDescription
habitExerciseGeneral exercise
habitLowerBody / habitUpperBodyStrength training
habitZone2Zone 2 cardio
habitSupplementSupplement intake
habitMorningLightExposureLight therapy
habitCoffeeWait / habitCoffeeEndCaffeine timing
habitNSDRNon-Sleep Deep Rest
habitHotColdTherapySauna/cold plunge
habitBreathworkBreathing exercises
habitMealEndMeal timing

Curated Protocol Templates

File: hybrid/basisflow-web/lib/protocol-data.ts

Pre-built templates: Sleep Program, Fitness Program, Focus Program, Nutrition Program, Cardiac Rehab, Metabolic Health.

Activity Type Detection in Basis Flow Web

When protocol activities don't display in the ActivityDetailsDrawer, CHECK UI CONDITIONALS FIRST:

  1. Protocol activity types use habit* prefix (e.g., habitUpperBody, habitLowerBody)
  2. The isWorkoutLike() function in PlanTab.tsx must recognize these patterns
  3. Rendering conditions for presets must check for hasAnyPreset not just isWorkout && workoutData

Troubleshooting

IssueCauseSolution
Presets not showing in Basis HybridEvent missing habitId/protocolIdCheck backend writes these fields
Completion not syncing to staff viewMissing clinicId on eventEnsure event has clinicId
Protocol not appearing in client appProtocol doc missing clinicIdReassign or backfill clinicId
Duplicate activitiesEvents written but not deduplicatedCheck event ID generation

Protocol Biomarker Tracking

Biomarker tracking allows clinicians to monitor health metrics throughout a protocol's duration.

Flow

  1. Template Creation (Programs Tab) — Select biomarkers from BIOMARKER_CATALOG (lib/biomarker-catalog.ts), specify target direction
  2. Protocol Assignment (AssignProtocolModal) — Auto-calculate 30-day baselines from healthSummaries, manual entry for weight/waist/BP/clinical scores
  3. Progress Display (PlanTab) — BiomarkerProgressCard shows baseline -> current -> change, color-coded status
  4. Copilot Integration — "Ask Copilot About This Protocol" builds ProtocolCopilotContext

Key Files

FilePurpose
lib/biomarker-catalog.tsBiomarker definitions grouped by category
lib/biomarker-utils.tsBaseline calculation, progress assessment
lib/protocol-copilot-context.tsCopilot context builder for protocols
lib/protocol-data.tsBiomarkerBinding interface
components/protocol/BiomarkerSelector.tsxMulti-select grouped by category
components/protocol/BiomarkerProgressCard.tsxProgress visualization

BiomarkerBinding Interface

interface BiomarkerBinding {
code: string; // e.g., 'hrvs', 'rhr', 'labHbA1c'
displayName: string;
category: string; // cardiac, metabolic, labs, etc.
unit: string;
baselineValue?: number;
baselineDate?: string;
baselineSource?: 'auto_30d' | 'manual';
targetDirection?: 'increase' | 'decrease' | 'maintain';
targetValue?: number;
expectedOutcome?: {
deltaPctRange?: [number, number];
byWeek?: number;
};
}

Biomarker Categories

CategoryExamplesData Source
cardiacHRV, RHR, VO2 Max, Blood PressureWearable, Manual
metabolicGlucose (CGM), Fasting Glucose, HbA1cCGM, Lab
body_compositionWeight, Waist, BMI, Body Fat %Manual
activitySteps, Active Calories, Zone MinutesWearable
sleepSleep Duration, Sleep ScoreWearable
labsLipid Panel, CBC, Thyroid, VitaminsLab
manualMood, Energy, Pain, PHQ-9, GAD-7Manual Entry

Baseline Calculation

Automatic (30-day average): Query healthSummaries for last 30 days, match biomarker code to type/vt field, calculate mean.

Manual entry required for: Weight, waist circumference, blood pressure, clinical scores (PHQ-9, GAD-7, etc.)

Progress Assessment

Requires >=3 data points for valid assessment. Calculates % change from baseline and assesses against target direction.

Adherence Requirement

Biomarker analysis requires 80% protocol adherence to be meaningful. Display warning if adherence < 80%.