SilverGait - System Architecture
Detailed node-level specs, state schemas, and database design for the two-graph LangGraph system.
Assessment Graph
Six-node pipeline that scores raw health inputs, classifies frailty tier, and conditionally updates care plans. All nodes are deterministic - zero LLM calls.
score_katz() → 0–6, score_cfs() → 1–9, score_sppb() → 0–12. Trigger-dependent: onboarding skips SPPB, assessment skips Katz re-compute.classify_frailty(cfs, katz, sppb) - pure rule-based logic → robust / pre_frail / frail / severely_frail. Generates templated risk_explanation.frailty_evaluations. Returns "changed" or "unchanged" to route subsequent nodes.content_library.py: exercise, sleep, education, monitoring. Supersedes old active plans in DB.tier_decline (urgent if frail/severely_frail) or tier_improvement (info). No alert on first evaluation.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
Chat Graph
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.
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.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
Deterministic Services
Core services that underpin both graphs. Zero LLM calls - all logic is rule-based or database-driven.
- 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
- EXERCISE_PLANS[tier]
- DEFICIT_EXERCISES[deficit]
- SLEEP_CONTENT[risk_level]
- EDUCATION_CONTENT[topic][tier]
- MONITORING_TEMPLATES[tier]
- 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
User Journey
Six distinct phases, each mapped to specific graph triggers and LLM call counts.
/api/users/{id}/health-snapshot → Assessment Graph (trigger="onboarding"). Score → Classify → Update Plans → Persist./api/exercises/complete.update_profile tool. Triggers Assessment Graph (trigger="profile_update"). Re-runs scoring + classification with updated health answers. May change tier and supersede care plans.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 |
Database Schema
SQLite via SQLAlchemy async (aiosqlite). Append-only design for full auditability.
gender, language, created_at, onboarded_at
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
test_type, completed_tests,
balance_score, gait_score, chair_stand_score,
sppb_total, confidence, issues,
recommendations, pose_metrics
health_snapshot_id, assessment_id,
cfs_score, katz_total, sppb_total,
frailty_tier, risk_explanation,
tier_changed, previous_tier
plan_type, content, created_at,
status, superseded_by_id, trigger
graph_type, trigger, input_summary,
output_summary, nodes_executed, elapsed_seconds
exercise_id, completed,
duration_seconds, reps, form_score, logged_at
content, tool_calls, language, timestamp
alerts: id, user_id, timestamp,
alert_type, severity, message, source, read
Key Design Decisions
- 1health_snapshots is append-only - never UPDATE, always INSERT. Enables diffs, trends, and full audit trail across sessions.
- 2frailty_evaluations links its inputs - FK to both health_snapshot_id and assessment_id provides complete input traceability for each evaluation.
- 3care_plans have lifecycle - status transitions from active → superseded, with superseded_by_id pointer to the replacement plan.
- 4care_plans content is NOT LLM-generated - selected from content_library.py (expert-reviewed, curated content). LLM sub-agents personalize on top of these templates.
- 5agent_runs tracks graph executions - graph_type, trigger, nodes executed, elapsed time. Essential for debugging and auditability.
- 6alerts have source field - distinguishes assessment_graph vs chat_safety_gate vs system alerts for triage and display logic.
Event Triggers
Every graph execution is event-driven. Each trigger maps to a specific API endpoint and graph.