# Project export: Quitecare

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2026
- Tagline: Quietcare — quiet until it counts.
- Devpost: https://devpost.com/software/quitecare
- GitHub: https://github.com/SashaSkind/Quietcare
- Video: https://www.youtube.com/embed/vqaRzVSESPE?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — Sasha (43 commits)

## Devpost submission (written by the team)

### Inspiration

Millions of older adults live alone, and the scariest moments — a fall, a stroke, choking, wandering at night — often happen with no one around to notice. Existing medical-alert devices rely on the person pressing a button, which fails exactly when they can't. But the opposite extreme — a camera feed or an app that pings family every time grandpa drops a spoon — gets muted within a week. We wanted a companion that quietly watches for trouble, checks in like a caring human would, and reaches a real person only when it actually matters — without turning someone's home into a 24/7 surveillance feed.

### What it does

QuietCare is an always-on elderly-safety companion built around one idea: stay silent through the noise of daily life, and speak up only in a real emergency. "Your phone doesn't blow up every time grandpa drops a spoon — only when something's actually wrong." A phone on a lanyard runs cheap on-device sensing (accelerometer + a rolling audio buffer) and, when something looks wrong, streams a short event to the cloud over a WebSocket. Two cooperating Claude agents take over: elder-agent reasons about the event, runs a gentle spoken check-in ("Margaret, are you okay?"), and fuses the signals — trigger type, what it hears back, whether they responded at all, and non-speech sounds like a thud or a scream — into a decision to resolve or escalate. caretaker-agent picks up escalations over a message bus and alerts the human caretaker via SMS or a voice call, and can even handle everyday errands like a prescription refill, or answer a casual "how's mom today?" text. The experience is deliberately lopsided: the elder is asked nothing and the caretaker hears nothing — until the one moment that counts. It handles falls, inactivity (a possible silent emergency like a stroke), and geofence breaches (wandering), with higher urgency at night.

### How we built it

Client: Expo / React Native (TypeScript) with expo-sensors for fall detection, a rolling on-device audio buffer, a single front-camera snapshot on trigger, and a resilient auto-reconnecting WebSocket. Backend: Python + FastAPI exposing a WebSocket (/ws) and /health. Two Claude agents run a provider-agnostic tool-use loop. Services: Claude (reasoning, via the PaleBlueDot router), Deepgram (STT/TTS), Twilio (SMS/voice), BAND (agent message bus), Browserbase (cloud browser for errands), Redis (memory), Sentry (monitoring). Safety core: an explicit escalation state machine enforces invariants — the LLM decides what to do, but the code decides what's allowed. Mock-by-default: every provider falls back to a deterministic mock when its API key is absent, so the whole loop runs end-to-end with zero credentials.

### Challenges we ran into

Avoiding both false alarms and missed emergencies. An unanswered check-in could mean "they're fine and walked away" or "they're unconscious." We solved this by fusing multiple signals — trigger source, transcript, silence, and acoustic distress tags — rather than trusting any one. Silence after a fall became one of our strongest signals, since the worst emergencies are exactly the ones where the person can't speak. Letting an AI act without letting it act dangerously. We separated decision-making (the LLM) from enforcement (a deterministic state machine), and hard-gated 911 behind explicit human confirmation. Always-on without being invasive. We buffer audio on-device and only send the seconds before a trigger, never a continuous stream. Demoability. Building real telephony, STT, and agents that also run fully mocked with no keys took careful interface design.

### Accomplishments we're proud of

A genuine two-agent system that cooperates over a real message bus — one agent that knows the elder, one that represents the family. 911 is unreachable without a human — a safety guarantee enforced in code, not just a prompt. Dignity-first design: a gentle check-in before any alarm, privacy by architecture, and a device that asks the elder to learn nothing. Runs end-to-end with zero credentials thanks to mock fallbacks.

### What we learned

For high-stakes AI, the most important code is the part that constrains the model, not the part that prompts it. Multi-signal fusion beats any single sensor for safety decisions. Privacy is an architecture choice — buffer vs. upload, keys server-side, no raw media in telemetry — not a checkbox.

### What's next

for QuietCare Real on-device fall-detection ML (CNN-LSTM on SisFall) to replace the threshold heuristic. A richer caretaker dashboard with wellness trends and incident history. Medication reminders and adherence tracking to make it useful every day, not just in emergencies. Native Apple Watch fall-API integration and on-device speech to cut cloud dependence further.

## README (from the GitHub repository)

# Quietcare

**An ambient AI safety companion for elderly care.** _Cal Hacks 2026 · June 20–21_

> "Your phone doesn't blow up every time grandpa drops a spoon — only when something is actually wrong."

Elderly people living alone face a dangerous gap: when something goes wrong — a
fall, a stroke, choking, sudden confusion — help often arrives far too late.
Quietcare watches ambiently, runs a calm spoken check-in when something looks
off, and loops in the right person **fast, without false alarms**.

---

## How it works

A phone app watches motion + audio cheaply on-device. It continuously streams
short audio segments as `audio_probe` frames — the backend tags the **YAMNet
audio scene** and watches for a wake signal — and the elder can **hold to talk**
to send a `voice_conversation` clip that the backend transcribes, answers with
the conversation agent, and speaks back. When something looks wrong it streams a
trigger event over the same WebSocket. The backend then runs **two cooperating
Claude agents**:

- **elder-agent** — reasons about the event, runs a spoken voice check-in, fuses
  the signals (trigger source + transcript + whether the elder responded + any
  non-speech acoustic distress), and decides to **resolve** or **escalate**.
- **caretaker-agent** — triages an escalation off the message bus and alerts the
  human caretaker via SMS / voice call, and can request **human-authorized 911**.

```
 phone (client)              cloud (server)                          humans
 ┌──────────────┐  WS /ws   ┌───────────────┐   BAND bus  ┌────────────────┐
 │ fall detect  │ ───────►  │  elder-agent  │ ──@mention─►│ caretaker-agent│
 │ audio buffer │  trigger  │  (Claude)     │             │   (Claude)     │
 │ check-in I/O │ ◄───────  │  voice + ML   │             │  Twilio SMS/call│
 └──────────────┘  speak/   └───────────────┘             └───────┬────────┘
                   listen                                          │ gated
                                                                   ▼
                                                        human-authorized 911
```

Everything runs **end-to-end with zero credentials** — every external provider
sits behind an interface with an automatic **mock fallback**.

---

## Repository layout

| Path | What's there |
| --- | --- |
| [`server/`](server/README.md) | FastAPI backend, the two Claude agents, all providers, escalation FSM. |
| [`client/`](client/README.md) | Expo (React Native) device app: on-device fall detection, rolling audio buffer, check-in I/O, camera snapshot. |
| [`server/app/band_mesh/`](server/app/band_mesh) | Standalone BAND agent daemons (elder + caretaker) for the full `@mention` mesh. |
| [`shared/protocol.md`](shared/protocol.md) | The WebSocket protocol v1 contract shared by client + server. |
| [`PROJECT.md`](PROJECT.md) | Full project brief, pitch notes, and sponsor rationale. |
| `design-videos/` | UI mocks + walkthrough recordings. |

---

## Quickstart

### Backend (all-mock, no keys)

```bash
cd server
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env            # all keys may stay blank

# terminal 1 — run the server
uvicorn app.main:app --host 0.0.0.0 --port 8080

# terminal 2 — drive the full loop
python mock/send_trigger.py --scenario emergency   # -> escalation + caretaker alert
python mock/send_trigger.py --scenario fine        # -> resolves silently
```

`GET http://localhost:8080/health` reports which provider impls are active
(`mock` vs real).

### Client (device app)

See [`client/README.md`](client/README.md) for the Expo dev-build + Android
install steps. The client holds **no API keys** and talks to the backend purely
over the WebSocket contract.

---

## Safety model

The **LLM decides** transitions (via which tools it calls); the **code enforces**
them and the hard invariants:

- **911 is never reachable without explicit human confirmation.** `escalate_911`
  is gated in `state_machine.gate_911(human_confirmed=…)`, re-sanctioned by a
  policy gate, and dispatch only fires on a token-verified human approval.
- **Every escalation passes through a check-in first**, unless the trigger is an
  unambiguous hard fall.
- **Agents never autonomously dial emergency services** — they can only *request*
  human authorization.

```
idle -> triggered -> checking_in -> (resolved | escalating)
                  \-> escalating              (only if hard_fall)
escalating -> caretaker_notified -> (human_ack | 911_gated)
```

---

## Sponsor / integration stack

Every integration is wired behind an interface with an automatic mock fallback.

| Provider | Layer | Role |
| --- | --- | --- |
| **PaleBlueDot** (TokenRouter) | Inference | Anthropic-compatible gateway serving **Claude**; direct Anthropic is the fallback. |
| **BAND** | Inter-agent bus | Carries escalations between the elder- and caretaker-agents; powers the full `@mention` mesh. |
| **Deepgram** | Voice I/O | Real STT (`nova-2`) + TTS (`aura-asteria-en`) for the spoken check-in. |
| **YAMNet** (TFLite) | Audio-scene ML | Non-speech distress tagging (thud / scream / glass) fused into the decision. |
| **Redis** | Memory | Elder profile, meds, history, recent events. |
| **Twilio** | Emergency channel | Real SMS / voice calls to the caretaker + gated 911 dispatch + inbound "how's mom?" recap. |
| **Browserbase** | Everyday-care | Off-critical-path automation (e.g. medication refill) via a cloud browser. |
| **Arize AX** | Evals / tracing | OpenTelemetry tracing of the agent tool-use loop and every Claude call. |
| **ArmorIQ** | Security posture | MCP-endpoint vulnerability scanning (SAFE-MCP). |
| **Sentry** | Monitoring | Error/perf monitoring on backend + client. |

> **Secrets** live only in `server/.env` / `client/.env` (gitignored) and the
> BAND daemon `agent_config.yaml`. Never commit keys. See `server/.env.example`
> for the full list.

---

## Verification scripts

The backend ships live diagnostics under `server/scripts/`:

- `check_keys.py` / `check_remaining_sponsors.py` — live connectivity for every provider.
- `verify_audio.py` / `verify_yamnet.py` — Deepgram STT↔TTS round-trip + YAMNet classification.
- `verify_app_hook.py` / `mesh_demo_kickoff.py` — drive the app→elder→caretaker BAND mesh end-to-end.
- `verify_emergency_call.py` — places a real gated emergency-dispatch call (to a number you control).
- `verify_observability.py` / `probe_armoriq.py` — Arize tracing + ArmorIQ security-scan checks.
- `fake_phone.py` — simulates the phone over the WebSocket, sending a real spoken clip through the `voice_conversation` STT→agent→TTS loop.
- `fetch_yamnet.py` — downloads the YAMNet model into `server/models/`.

Run the test suite with `python -m unittest discover -s tests` from `server/`.


## Detected evidence (automated analysis)

Indexed codebase: 123 recognized source files, 604 KB.
- FastAPI (technology) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- TypeScript (language) — detected in the code
- Anthropic (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (120 of 134)

```
agents/plans/backend.md
client/.env.example
client/.gitignore
client/app.json
client/App.native.tsx
client/App.tsx
client/App.web.tsx
client/babel.config.js
client/eas.json
client/index.ts
client/jest.config.js
client/metro.config.js
client/mock/package.json
client/mock/server.js
client/ON_DEVICE_FALL_MODEL.md
client/package.json
client/README.md
client/scripts/gen-audio.js
client/scripts/record-design.js
client/src/app/LoginScreen.tsx
client/src/app/RootNavigator.tsx
client/src/app/session.ts
client/src/assets/sampleAudio.ts
client/src/audio/audioManager.ts
client/src/camera/CameraCapture.tsx
client/src/camera/cameraManager.ts
client/src/caretaker/AgentFlowScreen.tsx
client/src/caretaker/api.ts
client/src/caretaker/CaretakerDashboard.tsx
client/src/caretaker/ElderPickerScreen.tsx
client/src/components/DebugLog.tsx
client/src/components/StatusBanner.tsx
client/src/config.ts
client/src/design/DemoScreen.tsx
client/src/design/FallPrompt.tsx
client/src/design/Orb.tsx
client/src/design/theme.ts
client/src/design/types.ts
client/src/design/useDemoMachine.ts
client/src/elder/ElderScreen.tsx
client/src/elder/useElderWebSocket.ts
client/src/elder/useFallSensor.ts
client/src/hooks/useQuietcare.ts
client/src/sensors/accelerometer.ts
client/src/sensors/detectFall.test.ts
client/src/sensors/detectFall.ts
client/src/sensors/fallModel.test.ts
client/src/sensors/fallModel.ts
client/src/sensors/geofence.test.ts
client/src/sensors/geofence.ts
client/src/sensors/inactivity.test.ts
client/src/sensors/inactivity.ts
client/src/sentry.ts
client/src/types.ts
client/src/ws/WebSocketClient.ts
client/tsconfig.json
design-videos/README.md
PROJECT.md
README.md
server/.env.example
server/.gitignore
server/agent_config.example.yaml
server/app/__init__.py
server/app/agents/__init__.py
server/app/agents/base.py
server/app/agents/caretaker.py
server/app/agents/elder.py
server/app/auth.py
server/app/band_mesh/__init__.py
server/app/band_mesh/_runner.py
server/app/band_mesh/caretaker.py
server/app/band_mesh/elder.py
server/app/caretaker_query.py
server/app/config.py
server/app/confirmations.py
server/app/elder_conversation.py
server/app/escalation_flow.py
server/app/main.py
server/app/medications.py
server/app/observability.py
server/app/protocol.py
server/app/providers/__init__.py
server/app/providers/audio_scene.py
server/app/providers/browser.py
server/app/providers/bus.py
server/app/providers/factory.py
server/app/providers/llm.py
server/app/providers/memory.py
server/app/providers/policy_gate.py
server/app/providers/security_scan.py
server/app/providers/telephony.py
server/app/providers/voice.py
server/app/sentry_init.py
server/app/session.py
server/app/state_machine.py
server/app/wellness.py
server/mock/send_trigger.py
server/README.md
server/requirements-optional.txt
server/requirements.txt
server/scripts/check_keys.py
server/scripts/check_remaining_sponsors.py
server/scripts/demo_add_to_cart.py
server/scripts/fake_phone.py
server/scripts/fetch_yamnet.py
server/scripts/fetch_yamnet.sh
server/scripts/mesh_demo_kickoff.py
server/scripts/probe_armoriq.py
server/scripts/verify_app_hook.py
server/scripts/verify_audio.py
server/scripts/verify_emergency_call.py
server/scripts/verify_observability.py
server/scripts/verify_yamnet.py
server/tests/test_agents.py
server/tests/test_audio_scene.py
server/tests/test_caretaker_sms.py
server/tests/test_confirmations.py
server/tests/test_elder_conversation.py
server/tests/test_everyday_care.py
server/tests/test_medications.py
[14 more files omitted for size]
```

### Dependencies

- client/mock/package.json: ws@^8.18.0
- client/package.json: @babel/core@^7.25.2, @expo/metro-runtime@~6.1.2, @expo/ngrok@^4.1.3, @sentry/react-native@~7.2.0, @types/jest@^29.5.14, @types/react@~19.1.10, babel-preset-expo@~54.0.10, expo@^54.0.0, expo-asset@~12.0.13, expo-av@~16.0.8, expo-camera@~17.0.10, expo-dev-client@~6.0.21, expo-file-system@~19.0.23, expo-location@~19.0.8, expo-sensors@~15.0.8, expo-speech@~14.0.8, expo-status-bar@~3.0.9, jest@^29.7.0, playwright@^1.61.0, react@19.1.0, react-dom@19.1.0, react-native@0.81.5, react-native-fast-tflite@^1.6.0, react-native-web@^0.21.0, ts-jest@^29.4.11, typescript@~5.9.2
- server/requirements.txt: fastapi@>=0.115,<1.0, pydantic@>=2.8,<3.0, pydantic-settings@>=2.4,<3.0, python-dotenv@>=1.0,<2.0, python-multipart@>=0.0.9, uvicorn[standard]@>=0.30,<1.0

### Recent commits (newest first)

- feat(voice): add hold-to-talk voice conversation with backend STT and capture diagnostics
- docs: note audio-probe + hold-to-talk voice flow and complete verification-scripts list
- docs(server): document YAMNet audio-scene provider and audio_probe/voice_conversation handlers
- docs(client): refresh README for SDK 54, voice assistant, audio probe, mic meter
- Use WebSocket for elder flow
- Improve elder wake word audio UI
- chore: verify yamnet inference
- feat: add always-on elder voice agent
- chore(client): add @expo/ngrok for expo tunnel (any-network QR)
- feat(checkin): hybrid voice + tap response
- docs: add root README (project overview, architecture, quickstart, stack)
- feat(mobile): caretaker elder picker + add-medication modal, voice test, design assets
- fix(audio): make YAMNet paths portable (repo-relative default)
- Merge design-c-halo: SDK 54 upgrade, in-app role-based demo (login -> caretaker dashboard | elder Halo with real fall detection + spoken check-in); remove web dashboard
- remove standalone web dashboard (prioritize iOS; in-app RN caretaker dashboard remains)
- client: speak the fall check-in (expo-speech)
- test(telephony): add live emergency-dispatch call verifier
- client: in-app role-based demo (login -> caretaker dashboard | elder Halo) + real fall detection
- test(audio): add live verifier for Deepgram STT/TTS + YAMNet
- feat(band): full @mention mesh + app-as-elder escalation hook

## Key source files (fetched from GitHub, selected and truncated for size)

### ui_plan.md

```markdown
# Quietcare Caretaker Dashboard — UI Plan

A web dashboard for family caretakers to see how their loved one is doing,
manage everyday care (medications, wellness, errands), and handle the rare
emergency. It is a thin, read-mostly layer over the existing FastAPI backend —
all real logic (safety FSM, escalation gate, voice loop) stays server-side.

## Goals

- **Reassurance first.** The default view answers "is mom okay?" in one glance.
- **Everyday value, not just emergencies.** Surface medications, wellness
  trends, and the two-way "call me" bridge so the product earns daily use.
- **Human-in-the-loop for high stakes.** 911 dispatch always requires an
  explicit, audited confirmation click.

## Tech stack

- **React + Vite + TypeScript** (SPA).
- **TailwindCSS** + **shadcn/ui** components, **Lucide** icons.
- **TanStack Query** for data fetching/caching + polling.
- **Recharts** for wellness/adherence visualizations.
- Auth: an admin token (`X-Admin-Token`) held in memory for privileged writes
  (create elder, set medications). Read endpoints are open in the demo.

## Backend endpoints consumed

All already implemented in `server/app/main.py`:

| Concern | Method + Path |
| --- | --- |
| List residents | `GET /elders` |
| Resident profile | `GET /elders/{id}` |
| Event history | `GET /elders/{id}/events?kind=&limit=` |
| Warm recap | `GET /elders/{id}/summary?question=` |
| Weekly wellness | `GET /elders/{id}/wellness?days=7` |
| Medication schedule | `GET` / `PUT /elders/{id}/medications` |
| Manual reminder | `POST /elders/{id}/medications/remind` |
| Adherence stats | `GET /elders/{id}/adherence` |
| Prescription refill | `POST /elders/{id}/refill` |
| Two-way call bridge | `POST /elders/{id}/call-bridge` |
| Pending 911 confirm | `GET /incidents/{id}/confirmation` |
| Approve/reject 911 | `POST /incidents/{id}/confirm_911` |
| Create resident | `POST /elders` (admin) |
| Live integration status | `GET /health` (real vs mock per provider) |
| MCP security scan | `POST /admin/security-scan` (admin, ArmorIQ) |
| Live updates | `WS /ws` (status/incident stream, future) |

> **Refill is a real computer-use action.** `POST /elders/{id}/refill` hands the
> task to Browserbase + Playwright and returns a `task` object with
> `replay_url`, `session_id`, and `detail`. The UI should surface the
> `replay_url` as a "Watch the agent" link so caretakers (and judges) can see
> the automation recording. The same provider also handles add-to-cart errands.

## Screens

### 1. Residents overview (`/`)
- Card grid from `GET /elders` + each `GET /elders/{id}`.
- Each card: name, connection/last-seen, a status pill (All good / Checking in /
  Alerting), and today's medication adherence dot.
- A red banner appears for any resident with a pending 911 confirmation.

### 2. Resident detail (`/elders/:id`)
Tabs:
- **Today** — warm recap (`/summary`), latest incident, status pill, quick
  actions: **Call me** (`/call-bridge`), **Send recap**, **Refill** (on success,
  r
[truncated — 3292 more characters]
```

### shared/protocol.md

```markdown
# Quietcare — WebSocket Protocol v1 (shared contract)

> **Read-only reference for both `/client` and `/server`.** If this needs to
> change, stop and coordinate — editing it breaks the other half.

Client opens a WebSocket to the backend at `/ws`. Every message is a JSON text
frame with a `type` field. All audio is base64-encoded WAV or Opus.

## CLIENT → BACKEND

```json
{ "type": "register", "elder_id": "margaret-01" }

{
  "type": "trigger",
  "elder_id": "margaret-01",
  "ts": "2026-06-20T18:30:00Z",
  "trigger_source": "fall" | "audio_event" | "scheduled" | "manual" | "inactivity" | "geofence",
  "audio_clip_b64": "<base64 audio ~3-5s, or null>",
  "frame_b64": "<base64 jpeg, or null>",
  "device_state": { "battery": 0.82, "connectivity": "wifi" },
  "note": "<optional short human note, e.g. 'left safe zone'>",
  "location": { "lat": 37.7749, "lng": -122.4194 }
}

{
  "type": "audio_response",
  "elder_id": "margaret-01",
  "ts": "...",
  "prompt_id": "<echo the prompt_id from the listen message>",
  "audio_clip_b64": "<base64 audio>"
}

{ "type": "heartbeat", "elder_id": "margaret-01", "ts": "...", "device_state": { } }
```

## BACKEND → CLIENT

```json
{ "type": "speak", "prompt_id": "p1", "audio_b64": "<base64 audio>", "text": "Margaret, are you okay?" }

{ "type": "listen", "prompt_id": "p1", "duration_ms": 6000 }

{ "type": "status", "state": "idle" | "checking_in" | "escalating" | "resolved" }

{ "type": "ack", "received": "trigger" }
```

## Rules both sides obey

- A check-in is always: backend sends `speak`, then `listen`; client plays the
  audio, records `duration_ms`, replies with `audio_response` carrying the same
  `prompt_id`.
- **All STT/TTS happens on the backend** (Deepgram). The client only plays and
  records audio — it never transcribes or synthesizes.
- **The client holds NO API keys.** Anthropic, Deepgram, Twilio, BAND, Redis live
  on the backend only.
- The WebSocket URL is configurable on the client via `EXPO_PUBLIC_WS_URL`.

```

### server/requirements.txt

```
# Core runtime — required to run the backend with all-mock providers (no keys).
fastapi>=0.115,<1.0
uvicorn[standard]>=0.30,<1.0
pydantic>=2.8,<3.0
pydantic-settings>=2.4,<3.0
python-dotenv>=1.0,<2.0
python-multipart>=0.0.9  # Twilio inbound SMS webhook (form parsing)

# Real providers are OPTIONAL and lazy-imported; install them only to enable
# real integrations:  pip install -r requirements-optional.txt

```

### client/package.json

```
{
  "name": "quietcare-client",
  "version": "0.1.0",
  "main": "index.ts",
  "scripts": {
    "start": "expo start --dev-client",
    "android": "expo run:android",
    "ios": "expo run:ios",
    "web": "expo start --web",
    "mock": "node mock/server.js",
    "gen:audio": "node scripts/gen-audio.js",
    "typecheck": "tsc --noEmit",
    "test": "jest"
  },
  "dependencies": {
    "@sentry/react-native": "~7.2.0",
    "babel-preset-expo": "~54.0.10",
    "expo": "^54.0.0",
    "expo-asset": "~12.0.13",
    "expo-av": "~16.0.8",
    "expo-camera": "~17.0.10",
    "expo-dev-client": "~6.0.21",
    "expo-file-system": "~19.0.23",
    "expo-location": "~19.0.8",
    "expo-sensors": "~15.0.8",
    "expo-speech": "~14.0.8",
    "expo-status-bar": "~3.0.9",
    "react": "19.1.0",
    "react-dom": "19.1.0",
    "react-native": "0.81.5",
    "react-native-fast-tflite": "^1.6.0",
    "react-native-web": "^0.21.0"
  },
  "devDependencies": {
    "@babel/core": "^7.25.2",
    "@expo/metro-runtime": "~6.1.2",
    "@expo/ngrok": "^4.1.3",
    "@types/jest": "^29.5.14",
    "@types/react": "~19.1.10",
    "jest": "^29.7.0",
    "playwright": "^1.61.0",
    "ts-jest": "^29.4.11",
    "typescript": "~5.9.2"
  },
  "private": true
}

```

### client/mock/package.json

```
{
  "name": "quietcare-mock-backend",
  "version": "0.1.0",
  "private": true,
  "description": "Throwaway WebSocket test harness for the Quietcare client (client-only, not the real backend).",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "ws": "^8.18.0"
  }
}

```

### client/index.ts

```typescript
import { registerRootComponent } from 'expo';
import App from './App';

// registerRootComponent calls AppRegistry.registerComponent('main', () => App)
// and ensures the environment is set up appropriately for the dev build.
registerRootComponent(App);

```

### client/App.tsx

```typescript
import React from 'react';
import {
  Pressable,
  SafeAreaView,
  StatusBar,
  StyleSheet,
  Text,
  View,
} from 'react-native';
import { initSentry, Sentry } from './src/sentry';
import { useQuietcare } from './src/hooks/useQuietcare';
import { StatusBanner } from './src/components/StatusBanner';
import { DebugLog } from './src/components/DebugLog';
import { CameraCapture } from './src/camera/CameraCapture';

initSentry();

function App() {
  const { status, connection, accelMagnitude, logs, simulateFall } =
    useQuietcare();

  return (
    <SafeAreaView style={styles.safe}>
      <StatusBar barStyle="light-content" />
      <CameraCapture />
      <View style={styles.container}>
        <View style={styles.header}>
          <Text style={styles.title}>Quietcare</Text>
          <Text style={styles.meta}>
            ws: {connection} · |a|: {accelMagnitude.toFixed(2)} g
          </Text>
        </View>

        <StatusBanner status={status} />

        <Pressable
          style={({ pressed }) => [styles.button, pressed && styles.buttonPressed]}
          onPress={simulateFall}
        >
          <Text style={styles.buttonText}>Simulate Fall</Text>
        </Pressable>

        <DebugLog logs={logs} />
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  safe: {
    flex: 1,
    backgroundColor: '#0f172a',
  },
  container: {
    flex: 1,
    padding: 16,
    gap: 16,
  },
  header: {
    alignItems: 'center',
  },
  title: {
    color: '#f8fafc',
    fontSize: 22,
    fontWeight: '800',
  },
  meta: {
    color: '#94a3b8',
    fontSize: 13,
    marginTop: 2,
  },
  button: {
    backgroundColor: '#dc2626',
    paddingVertical: 24,
    borderRadius: 16,
    alignItems: 'center',
  },
  buttonPressed: {
    opacity: 0.8,
  },
  buttonText: {
    color: '#ffffff',
    fontSize: 24,
    fontWeight: '800',
  },
});

// Sentry.wrap enables native crash + error reporting around the root component.
export default Sentry.wrap(App);

```

### client/mock/server.js

```javascript
/* eslint-disable */
// Throwaway mock backend for testing the Quietcare CLIENT in isolation.
// This is NOT the real server (no agents, Deepgram, Twilio, Redis). It only
// speaks enough of protocol v1 to exercise the client end-to-end.
//
// Run: npm run mock   (from /client)  ->  ws://<host>:8080/ws

const http = require('http');
const fs = require('fs');
const path = require('path');
const { WebSocketServer } = require('ws');

const PORT = process.env.PORT ? Number(process.env.PORT) : 8080;

// Reuse the bundled sample tone as the "speak" audio so we don't ship a second clip.
function loadSampleAudioB64() {
  try {
    const tsPath = path.join(__dirname, '..', 'src', 'assets', 'sampleAudio.ts');
    const src = fs.readFileSync(tsPath, 'utf8');
    const match = src.match(/SAMPLE_AUDIO_B64 = '([^']+)'/);
    return match ? match[1] : '';
  } catch (err) {
    console.warn('Could not load sample audio:', err.message);
    return '';
  }
}

const SPEAK_AUDIO_B64 = loadSampleAudioB64();

const server = http.createServer();
const wss = new WebSocketServer({ server, path: '/ws' });

let promptCounter = 0;

function send(ws, obj) {
  const json = JSON.stringify(obj);
  ws.send(json);
  console.log('  -> sent', summarize(obj));
}

function summarize(obj) {
  const clone = { ...obj };
  for (const k of ['audio_b64', 'audio_clip_b64', 'frame_b64']) {
    if (clone[k] != null) clone[k] = `<${String(clone[k]).length} b64 chars>`;
  }
  return JSON.stringify(clone);
}

wss.on('connection', (ws) => {
  console.log('client connected');

  ws.on('message', (data) => {
    let msg;
    try {
      msg = JSON.parse(data.toString());
    } catch {
      console.log('  <- bad JSON');
      return;
    }
    console.log('  <- recv', summarize(msg));

    switch (msg.type) {
      case 'register':
        console.log(`registered elder: ${msg.elder_id}`);
        send(ws, { type: 'status', state: 'idle' });
        break;

      case 'heartbeat':
        // No-op; just observe.
        break;

      case 'trigger': {
        send(ws, { type: 'ack', received: 'trigger' });
        send(ws, { type: 'status', state: 'checking_in' });

        const prompt_id = `p${++promptCounter}`;
        // A check-in is always: speak, then listen.
        send(ws, {
          type: 'speak',
          prompt_id,
          audio_b64: SPEAK_AUDIO_B64,
          text: 'Margaret, are you okay?',
        });
        setTimeout(() => {
          send(ws, { type: 'listen', prompt_id, duration_ms: 4000 });
        }, 1500);
        break;
      }

      case 'audio_response': {
        const bytes = msg.audio_clip_b64 ? msg.audio_clip_b64.length : 0;
        console.log(
          `received audio_response for ${msg.prompt_id} (${bytes} b64 chars)`,
        );
        send(ws, { type: 'status', state: 'resolved' });
        setTimeout(() => send(ws, { type: 'status', state: 'idle' }), 1500);
        break;
      }

      default:
        console.log('  (unhandled type)', msg.type);
    }
  });

  ws.on('close', () => console.log('client disconnected'));
});

server.listen(PORT, () => {
  console.log(`Quietcare MOCK backend listening on ws://0.0.0.0:${PORT}/ws`);
  console.log('(throwaway test harness — not the real server)');
});

```

### server/app/main.py

```python
"""FastAPI app: GET /health and WS /ws implementing the protocol v1 server side."""

from __future__ import annotations

import asyncio
import json
import logging
from contextlib import asynccontextmanager

from typing import Optional

from fastapi import (
    FastAPI,
    Form,
    Header,
    HTTPException,
    Response,
    WebSocket,
    WebSocketDisconnect,
)
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel

from .auth import provision_device, verify_device
from .caretaker_query import (
    handle_inbound_sms,
    prompt_elder_to_call,
    summarize_for_caretaker,
)
from .config import settings
from .confirmations import ConfirmationRegistry
from .medications import (
    MedicationService,
    adherence_summary,
    run_medication_reminder,
)
from .elder_conversation import handle_elder_conversation, wants_attention
from .escalation_flow import call_caretaker_with_emergency_fallback
from .observability import init_tracing
from .wellness import summarize_wellness
from .protocol import (
    AudioProbeMessage,
    AudioProbeResultMessage,
    AudioResponseMessage,
    RegisterMessage,
    TriggerMessage,
    VoiceConversationMessage,
    VoiceConversationReplyMessage,
    parse_client_message,
)
from .providers.factory import build_providers
from .sentry_init import capture, init_sentry
from .session import (
    CaretakerService,
    ElderSession,
    SessionRegistry,
    confirm_911,
    handle_trigger,
)

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)
logger = logging.getLogger("quietcare.main")


@asynccontextmanager
async def lifespan(app: FastAPI):
    init_sentry()
    # Enable Arize tracing before the Anthropic client is constructed so the
    # OpenInference instrumentation patches are in place for all LLM calls.
    init_tracing()
    providers = build_providers(settings)

    # Seed elder profile into real Redis if it's empty (no-op for mock memory,
    # which seeds itself; idempotent for Redis via seed()'s existence check).
    seed = getattr(providers.memory, "seed", None)
    if seed is not None:
        try:
            await seed()
        except Exception as exc:  # pragma: no cover - external store
            logger.warning("memory seed failed (%s); continuing", exc)

    registry = SessionRegistry()
    confirmations = ConfirmationRegistry()
    caretaker = CaretakerService(
        providers,
        registry,
        confirmations,
        auto_emergency_fallback=settings.auto_emergency_fallback,
        caretaker_ack_timeout_seconds=settings.caretaker_ack_timeout_seconds,
    )
    caretaker.attach()

    medications = MedicationService(providers, registry, settings)

    app.state.providers = providers
    app.state.registry = registry
    app.state.confirmations = confirmations
    app.state.caretaker = caretaker
    app.state.medications = medications
    app.state.bg_tasks = set()
    logger.info("Quietcare backend up. providers=%s", providers.summary())

    # Best-effort ArmorIQ posture scan of configured MCP endpoints at startup.
    for target in settings.scan_target_list:
        try:
            res = await providers.security_scan.scan(target)
            level = "warning" if res.severity_level not in ("safe", "unknown") else "info"
            getattr(logger, level)(
                "ArmorIQ scan %s: severity=%s score=%s mcp_endpoints=%s",
                target, res.severity_level, res.vulnerability_score, res.mcp_endpoints,
            )
        except Exception as exc:  # pragma: no cover - external
            logger.warning("startup security scan failed for %s (%s)", target, exc)

    # Background medication-reminder scheduler.
    med_task = asyncio.create_task(medications.run_forever())
    app.state.bg_tasks.add(med_task)
    yield
    med_task.cancel()
    logger.info("Quietcare backend shutting down.")


app = FastAPI(title="Quietcare Backend", version="0.1.0", lifespan=lifespan)

# Permit cross-origin calls from the in-app caretaker dashboard / web preview.
# Dev-only wide-open policy; tighten for production.
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)


@app.get("/health")
async def health() -> dict[str, object]:
    providers = getattr(app.state, "providers", None)
    return {
        "status": "ok",
        "providers": providers.summary() if providers else {},
    }


class Confirm911Request(BaseModel):
    token: str
    approve: bool = True


@app.get("/incidents/{elder_id}/confirmation")
async def get_confirmation(elder_id: str) -> dict[str, object]:
    """Return the pending 911 confirmation status for an elder (no token)."""
    confirmations: ConfirmationRegistry = app.state.confirmations
    pc = confirmations.get(elder_id)
    if pc is None:
        raise HTTPException(status_code=404, detail="no confirmation for elder")
    return {"elder_id": elder_id, "status": pc.status, "reason": pc.reason}


@app.post("/incidents/{elder_id}/confirm_911")
async def post_confirm_911(elder_id: str, body: Confirm911Request) -> dict[str, object]:
    """Human approves/rejects emergency dispatch. The hard gate (token + FSM)
    is enforced inside confirm_911."""
    try:
        return await confirm_911(
            registry=app.state.registry,
            confirmations=app.state.confirmations,
            providers=app.state.providers,
            elder_id=elder_id,
            token=body.token,
            approve=body.approve,
        )
    except KeyError:
        raise HTTPException(status_code=404, detail="no pending confirmation")
    except PermissionError:
        raise HTTPException(status_code=403, detail="invalid confirmation token")
    except ValueError as exc:
        raise HTTPException(status_code=409, detail=str(exc))


class ElderCreate(BaseModel):
    elder_id: str
    name: str
    age: Optional[int] = None
    medications: list[str] = []
    conditions: 
[truncated — 18504 more characters]
```

### client/babel.config.js

```javascript
module.exports = function (api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
  };
};

```

[112 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]