Technical Reference

SilverGait - System Architecture

Detailed node-level specs, state schemas, and database design for the two-graph LangGraph system.

01

Assessment Graph

backend/app/services/langgraph_agents/assessment_graph.py

Six-node pipeline that scores raw health inputs, classifies frailty tier, and conditionally updates care plans. All nodes are deterministic - zero LLM calls.

score_node deterministic
Runs score_katz() → 0–6, score_cfs() → 1–9, score_sppb() → 0–12. Trigger-dependent: onboarding skips SPPB, assessment skips Katz re-compute.
classify_node deterministic
Calls classify_frailty(cfs, katz, sppb) - pure rule-based logic → robust / pre_frail / frail / severely_frail. Generates templated risk_explanation.
tier_change_router decision
Conditional edge. Compares new tier to previous from frailty_evaluations. Returns "changed" or "unchanged" to route subsequent nodes.
update_plans_node deterministic
Only runs if tier changed. Selects care plans from content_library.py: exercise, sleep, education, monitoring. Supersedes old active plans in DB.
notify_node deterministic
Only runs if tier changed. Creates alerts: tier_decline (urgent if frail/severely_frail) or tier_improvement (info). No alert on first evaluation.
persist_node database
Always runs last. INSERT into frailty_evaluations, care_plans, and agent_runs. Health_snapshots inserted before graph entry.

Classification Rule

if cfs >= 7 or (katz <= 2 and sppb is not None and sppb <= 3):
    tier = "severely_frail"
elif cfs >= 5 or (katz <= 4 and sppb is not None and sppb <= 6):
    tier = "frail"
elif cfs >= 4 or (sppb is not None and sppb <= 9):
    tier = "pre_frail"
else:
    tier = "robust"

State Schema

class AssessmentState(TypedDict, total=False):
    user_id: str
    trigger: str              # onboarding | assessment | profile_update | biweekly_recheck
    language: str
    katz_answers: dict | None
    contributing: dict | None
    sppb_balance: int | None
    sppb_gait: int | None
    sppb_chair: int | None
    issues: list[str] | None
    katz_total: int | None
    cfs_score: int | None
    sppb_total: int | None
    frailty_tier: str | None
    risk_explanation: str | None
    previous_tier: str | None
    tier_changed: bool
    new_plans: list[dict]
    alerts: list[dict]
    health_snapshot_id: int | None
    assessment_id: int | None
    db: AsyncSession | None
02

Chat Graph

backend/app/services/langgraph_agents/chat_graph.py

Four-node pipeline triggered by each user chat message. Assembles full user context, invokes Gemini 2.5 Flash with native function calling, applies safety pattern matching, then persists.

context_assembly deterministic
Calls build_user_context() - 10+ DB queries across health_snapshots, frailty_evaluations, care_plans, assessments, exercise_logs, alerts. Serializes via to_system_prompt_context() into Gemini system prompt.
agent_node LLM
Model: gemini-2.5-flash (orchestrator). Loads last 10 chat messages for conversation history. Uses Gemini native function calling with TOOL_DECLARATIONS. If Gemini returns a function_call → executes sub-agent → second Gemini call to synthesize. 1–5 LLM calls total.
safety_gate deterministic
Pattern-matches user message for crisis signals. Appends safety info (does not replace response). Falls → alert. Chest pain / can't breathe → "Please call 995". Distress phrases → crisis helplines.
persist_node database
INSERT chat_messages for both user and assistant roles. Stores tool_calls JSON if tools were used during this turn.

Chat Tools - Gemini Function Calling

Tool Type Purpose LLM
get_exercise_plan() LLM sub-agent Exercise Agent → personalized 4-week progressive program based on tier/SPPB/deficits 1 Gemini Flash Lite
get_sleep_advice() LLM sub-agent Sleep Agent → CBT-I + sleep hygiene plan based on sleep risk level 1 Gemini Flash Lite
get_education(topic) LLM sub-agent Education Agent → frailty/balance/nutrition education tailored to tier 1 Gemini Flash Lite
analyze_trends() LLM sub-agent Monitoring Agent → health trend analysis + deterioration detection from DB data 1 Gemini Flash Lite
get_progress_summary() deterministic Computes SPPB/Katz trends, exercise streak from DB queries 0
alert_caregiver(message) deterministic INSERT alert record with severity=warning, notifies caregiver 0
navigate_to_page(page) deterministic Directs user to an app page (/check, /exercises, /progress, /sleep, /caregiver) 0

State Schema

class ChatState(TypedDict, total=False):
    user_id: str
    user_message: str
    language: str
    system_prompt: str | None
    user_context: UserContext | None
    agent_response: str | None
    tool_calls: list[dict] | None
    safety_alerts: list[dict]
    response_appendix: str | None
    db: AsyncSession | None
03

Deterministic Services

Core services that underpin both graphs. Zero LLM calls - all logic is rule-based or database-driven.

Scoring
services/scoring.py
  • score_katz(answers) → 0–6
  • score_cfs(katz_total) → 1–9
  • score_sppb(balance, gait, chair) → 0–12
  • classify_frailty(cfs, katz, sppb)
  • route_care(tier, risks) → list[str]
  • generate_narrative(tier, …) → str
Content Library
services/content_library.py
  • EXERCISE_PLANS[tier]
  • DEFICIT_EXERCISES[deficit]
  • SLEEP_CONTENT[risk_level]
  • EDUCATION_CONTENT[topic][tier]
  • MONITORING_TEMPLATES[tier]
UserContext
services/context.py
  • build_user_context(db, user_id)
  • to_system_prompt_context()
  • User identity + language
  • Latest snapshot, eval, plans
  • SPPB/Katz trends (last 5)
  • Exercise stats + alerts
04

User Journey

Six distinct phases, each mapped to specific graph triggers and LLM call counts.

Phase 1
Onboarding
0 LLM calls
Welcome → Name + language. Katz ADL: 6 yes/no questions with TTS/STT support. Contributing conditions: 4 three-option questions. POST /api/users/{id}/health-snapshot → Assessment Graph (trigger="onboarding"). Score → Classify → Update Plans → Persist.
Phase 2
First Assessment
1 LLM call
Video recording: balance → gait → chair stand sequence. Gemini 2.5 Flash Lite analyzes video → SPPB sub-scores. Assessment Graph (trigger="assessment") → complete tier with all three scores combined.
Phase 3
Daily Routine
1 LLM call
Morning: assessment → exercises from personalized plan. Assessment Graph runs each time; most days tier unchanged → short path (skips update_plans + notify). Exercises logged via /api/exercises/complete.
Phase 4
Chat
1–5 LLM calls
Chat Graph: context assembly → Gemini agent → safety gate → persist. Gemini native function calling dispatches to management sub-agents when user needs personalized exercise, sleep, or education content.
Phase 5
Profile Updates
0 LLM calls
Via UI or chat update_profile tool. Triggers Assessment Graph (trigger="profile_update"). Re-runs scoring + classification with updated health answers. May change tier and supersede care plans.
Phase 6
Caregiver View
0 LLM calls
CaregiverPage shows clinical detail: frailty tier, CFS, Katz total, SPPB sub-scores, risk explanation, active care plans, unread alerts. No sensitive medication info - referral pathway for clinical follow-up.
05

LLM Budget

Design philosophy: LLM only where reasoning is needed. Scoring, classification, routing, and plan selection are all deterministic.

Scenario Gemini Video Assessment Graph Chat Graph Total
Onboarding 0 0 0 0
Assessment (no tier change) 1 0 0 1
Assessment (tier change) 1 0 0 1
Chat turn (no tool) 0 0 1 1
Chat turn (with sub-agent) 0 0 2–3 2–3
Profile update (UI) 0 0 0 0
06

Database Schema

SQLite via SQLAlchemy async (aiosqlite). Append-only design for full auditability.

users
id, display_name, date_of_birth,
gender, language, created_at, onboarded_at
health_snapshots
id, user_id, captured_at, trigger,
katz_bathing, katz_dressing, katz_toileting,
katz_transferring, katz_continence, katz_feeding,
katz_total, cognitive_risk, mood_risk,
sleep_risk, social_isolation_risk,
cfs_score, cfs_label, notes
assessments
id, user_id, timestamp,
test_type, completed_tests,
balance_score, gait_score, chair_stand_score,
sppb_total, confidence, issues,
recommendations, pose_metrics
frailty_evaluations
id, user_id, timestamp, trigger,
health_snapshot_id, assessment_id,
cfs_score, katz_total, sppb_total,
frailty_tier, risk_explanation,
tier_changed, previous_tier
care_plans
id, user_id,
plan_type, content, created_at,
status, superseded_by_id, trigger
agent_runs
id, user_id, timestamp,
graph_type, trigger, input_summary,
output_summary, nodes_executed, elapsed_seconds
exercise_logs
id, user_id, date,
exercise_id, completed,
duration_seconds, reps, form_score, logged_at
chat_messages & alerts
chat_messages: id, user_id, role,
content, tool_calls, language, timestamp

alerts: id, user_id, timestamp,
alert_type, severity, message, source, read

Key Design Decisions

  1. 1
    health_snapshots is append-only - never UPDATE, always INSERT. Enables diffs, trends, and full audit trail across sessions.
  2. 2
    frailty_evaluations links its inputs - FK to both health_snapshot_id and assessment_id provides complete input traceability for each evaluation.
  3. 3
    care_plans have lifecycle - status transitions from active → superseded, with superseded_by_id pointer to the replacement plan.
  4. 4
    care_plans content is NOT LLM-generated - selected from content_library.py (expert-reviewed, curated content). LLM sub-agents personalize on top of these templates.
  5. 5
    agent_runs tracks graph executions - graph_type, trigger, nodes executed, elapsed time. Essential for debugging and auditability.
  6. 6
    alerts have source field - distinguishes assessment_graph vs chat_safety_gate vs system alerts for triage and display logic.
07

Event Triggers

Every graph execution is event-driven. Each trigger maps to a specific API endpoint and graph.

ONBOARDING COMPLETE
POST /api/users/{id}/health-snapshot
Assessment Graph (trigger=onboarding)
VIDEO ASSESSMENT
POST /api/assessment/analyze-stream
Assessment Graph (trigger=assessment)
PROFILE UPDATE
POST /api/users/{id}/health-snapshot
Assessment Graph (trigger=profile_update)
BIWEEKLY RECHECK
POST /api/users/{id}/health-snapshot
Assessment Graph (trigger=biweekly_recheck)
CHAT MESSAGE
POST /api/chat/stream
Chat Graph