Skip to content

Backend: orchestrator API, PRD-aligned scoring, multimodal fusion + safety#2

Open
jasonca2023 wants to merge 9 commits into
devfrom
feat/jason-api
Open

Backend: orchestrator API, PRD-aligned scoring, multimodal fusion + safety#2
jasonca2023 wants to merge 9 commits into
devfrom
feat/jason-api

Conversation

@jasonca2023

Copy link
Copy Markdown
Collaborator

Backend (orchestrator) — feat/jason-apidev

FastAPI orchestrator on :8001 + SQLite. Wesley's app and Dhruva's SMS bot both call this service; it calls signals (:8002), ML/TRIBE (:8003), and video (:8004), stores everything, and serves history/metrics/brain.

Endpoints

  • GET /health, GET /stimulus/today/{user_id}, GET /stimulus/all
  • POST /response/submit (app + sms) — orchestrates signals → ML → store → red alert
  • GET /score/{user_id}, GET /history/{user_id}, GET /metrics/{user_id}
  • GET /brain/{user_id} — TRIBE regions + plain-English explanations
  • POST /video/submit — fuses facial + voice into a burnout score

PRD alignment

  • Multimodal fusion (§4.5): breakdown = {imessage, typing, facial, voice, tribe}; video check-ins move the score (facial weighted highest).
  • Safety (§6): every RED result carries a human-support path (988, Crisis Text Line, campus counseling, trusted contact).
  • Brain (§5.2): plain-English, non-clinical region explanations.
  • Privacy (§8): raw video is never stored — only derived analysis.
  • Levels: green 0–29 / yellow 30–64 / red 65–100.

Resilience

Degrades gracefully: if signals / ML / video are offline, returns a fallback result (source=fallback) instead of erroring, so the backend runs standalone for the demo.

Contract

Updates shared/contract.json (owned here): stimulus shape, user_response (+source app|sms), burnout_result (+breakdown, +support), and the real signals endpoints (/alert/send, /register-phone).

Only backend/ + shared/ changes appear in this diff — Dhruva's signals work is already on dev.

🤖 Generated with Claude Code

jasonca2023 and others added 6 commits June 13, 2026 11:12
- FastAPI app on :8001 — /stimulus/send, /response/submit, /score, /brain, /history, /stimuli, /health

- Orchestrates signals (8002) + ML (8003); degrades to local fallback scoring when either is offline so the backend runs standalone

- SQLite via SQLAlchemy: users, sessions, score_records (+ raw signal history)

- shared/contract.json reconciled to the live signals stimulus shape; shared/stimuli.json canonical manifest (mirrors signals bank ids/baselines)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…eo endpoints

- Restructure to prefix-based routers: /stimulus, /response, /score, /brain, /history, /metrics, /video

- Contract v2: stimulus {type,url,category,prompt}; user_response +response_latency_ms +source(app|sms); burnout_result +breakdown{imessage,typing,facial,voice,tribe}

- New: GET /metrics (trend + latest breakdown + total), POST /video/submit (forwards to video svc :8004, VideoRecord table)

- Rename tribe_client -> ml_client (+get_baseline); add video_client; signal_client.send_alert matches Dhruva's live POST /alert (requires phone)

- /stimulus/today daily rotation; /score returns friendly default before first check-in; keeps graceful fallback when signals/ML/video offline

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…eo service

- /video/submit now reads top-level combined_score from the video service response and persists it (VideoRecord.combined_score)

- contract.json: video /analyze/video documented as -> { facial{facial_stress_score,...}, voice, combined_score }

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…hone) + /register-phone

- signal_client.send_alert now POSTs /alert/send {user_id, score, level, intervention} (phone removed; signals looks it up)

- add signal_client.register_phone -> POST /register-phone

- response.py fires the red alert without a phone gate (signals owns phone lookup)

- contract.json: document real signals endpoints (/alert/send, /register-phone, /send-checkin, /sms/webhook, /signals/{id}/history) and flat /analyze response

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…afety, brain explanations

- Multimodal fusion (PRD 4.5): /video/submit fuses facial+voice into a burnout score (facial weighted highest), stored source=video; breakdown surfaces facial/voice; /response/submit folds in latest video signals

- Safety (PRD 6): new safety.py enforces that every RED result carries a human-support path (988 + Crisis Text Line + campus counseling + trusted contact); applied in response + video routes and burnout_result.support

- Brain (PRD 5.2): /brain returns plain-English, non-clinical region explanations

- contract.json: burnout_result +support +source(app|sms|video); /video/submit returns burnout_result; raw video never stored (PRD 8)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @jasonca2023! 👋

Your private repo does not have access to Sourcery.

Please upgrade to continue using Sourcery ✨

jasonca2023 and others added 3 commits June 13, 2026 13:30
…stent /score default + SQLite busy timeout

QA pass (28/28 edge-case tests green):

- scoring helpers coerce None/strings and clamp out-of-range so the fallback path never 500s (negatives, huge values, null combined_signal_score, etc.)

- SAFETY: normalize_ml_result reconciles level with score — an invalid level derives from score, and a red-range score (>=65) is never downgraded, so the human-support path always attaches (PRD 6)

- /score default (pre-first-checkin) now returns the full burnout_result shape incl. support[] so the frontend never hits a missing key

- SQLite busy timeout (30s) so concurrent app + SMS writes don't error with 'database is locked'

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Found by a 30-way concurrent first-submit stress test (app + SMS hitting a brand-new user at once), which previously TIMED OUT every request:

- ensure_user did get-then-insert with no IntegrityError handling; a losing race left a failed transaction holding the SQLite write lock, stalling all other requests until timeout. Now catches IntegrityError, rolls back, and reuses the row the winner created.

- SQLite: enable WAL journal mode + busy_timeout=8s + synchronous=NORMAL, and a larger pool (20+30), so concurrent reads/writes don't block. .gitignore covers -wal/-shm sidecars.

Verified: 30x6 concurrent submits + 15 concurrent video = 0 failures; 28/28 regression still green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant