Inspiration

Vietnam's wellness app market is dominated by rule-based chatbots. None of them speak Vietnamese natively as a coach, none read wearable data, and none close the loop back into the insurance products people already pay for. Meanwhile the global reference models — Discovery Vitality in South Africa, John Hancock Vitality in the US — have shown that a shared-value loop (healthy behavior earns real premium reductions) changes member outcomes and insurer economics at the same time.

Qwen Build AI Day was the forcing function. Qwen's multilingual LLM plus vision capability made a Vietnamese-first nutrition coach tractable in a single sprint — a combination that wasn't realistic with English-centric models a year ago. We built SeeIt as an AI-powered digital health ecosystem aimed at a real partner (Shinhan Life Vietnam), not a toy demo.

What it does

SeeIt stitches three pillars together in one account:

  • Health tracking — JWT-authenticated patient profiles, Garmin FIT file upload, and a dashboard with 7-day sparklines for Body Battery, HRV, resting heart rate, sleep, and steps. Activities are scored into streaks and points.
  • AI coaching (Qwen-powered) — Snap a meal photo and get Vietnamese nutrition advice that cites your current metrics ("RHR elevated today → try lower-sodium phở"). Get a daily wellness brief. Run a streaming symptom-triage chat that classifies urgency and drops directly into an auto-generated e-prescription.
  • Insurance loop — Points roll up into Bronze/Silver/Gold/Platinum tiers. The policy card shows a live discount calculation against the base premium. One-tap claim submit posts a fake SHL claim and credits hospital cash back into the account.

The 60-second demo path is the whole product in one breath: Garmin upload → dashboard hydrates → food photo → Vietnamese coach → triage chat → auto-prescription → claim submit → tier discount updates.

How we built it

Stack. FastAPI + SQLAlchemy 2.0 async + PostgreSQL + Alembic on the backend; Vite + React 18 + TypeScript + Tailwind + shadcn/ui + Recharts on the frontend; scikit-learn for the health-risk classifier; Docker Compose for local orchestration.

Qwen model routing. We deliberately didn't default to one model. Routing lives in backend/app/services/qwen_prompts.py:

  • qwen-vl-plus for food-photo vision (F3)
  • qwen-max for triage reasoning and prescription extraction (F5, F6)
  • qwen-plus for narratives, daily briefs, streak nudges, CTA copy, tier explainers, and policy card copy (F2, F4, F7–F11)

HealthContextBuilder. The single highest-leverage abstraction in the app. It assembles the patient profile + last 7 days of wearable metrics + active risk flags into a reusable context block that gets injected into every Qwen call. One change to the builder upgrades F3, F4, F5, and F7 simultaneously.

QwenClient. Async httpx wrapper with retries on 5xx, 30-second timeout, and SSE streaming for the triage chat. Vision calls go through a separate path because VL-Plus has different constraints from the reasoning models.

AI cache. A TTL-indexed Postgres table dedupes expensive LLM calls for F7 (daily nudge), F8 (CTA copy), F10 (tier progress), and F11 (policy explainer). Cached at the feature level, not the raw-prompt level — this was the biggest demo-day latency win.

Atomic SQL for points. F7 uses points_total = points_total + ? in a single UPDATE so concurrent Garmin imports can't race each other.

Wave-based delivery. W1 Foundation → W2 Data + Food Coach hero → W3 Triage/Rx/Claim → W4 Polish (Fitbit-inspired UI, ML risk model, insurance/rewards screens). Each wave had a demoable gate before the next started; W3 also shipped a QA sub-track (backend/tests/test_w3_e2e.py) running e2e happy-path tests against a mocked Qwen client.

Challenges we ran into

  • Qwen-VL-Plus doesn't accept response_format: json_object. We had to split F3 into a two-call pattern: a vision pass to extract food items + nutrition, then a reasoning pass to structure the JSON and generate the Vietnamese advice on top of the patient context.
  • Garmin FIT deduplication. Overlapping exports produced duplicate timestamps. Fixed with a unique index on (user_id, metric_type, recorded_at) and upsert-on-conflict on ingest.
  • Streaming triage with context injection. SSE over WebSocket to stay stateless; the challenge was threading HealthContextBuilder output into the opening system prompt without blowing the context window on longer conversations.
  • Multilingual prompt hygiene. Vietnamese persona + English clinical terms + metric units in one turn. Solved with a canonical VN base system prompt concatenated with per-feature instructions — every feature inherits the same voice.
  • Async boundary bugs. SQLAlchemy 2.0 async sessions + FastAPI dependency injection + JWT auth produced a few "session closed" incidents until we got the per-request session scoping right.
  • Server-side PDF generation. Evaluated reportlab vs fpdf2 for the e-prescription export from Qwen-extracted JSON; simpler layout made fpdf2 the right call.

Accomplishments that we're proud of

  • Real Garmin data, real Qwen calls end-to-end. Nothing stubbed for the stage demo.
  • We actually closed the loop. Wearable metric → AI advice → triage → prescription → claim → premium discount, all inside one session. This is the piece competitors don't have.
  • Solo build, 4 days, 11 features shipped — F1 through F11 — with a dedicated QA wave running mocked-Qwen e2e tests.
  • ML alongside LLM. A scikit-learn classifier (backend/ml/health_risk_model.joblib + feature_config.json) runs next to the LLM, not in place of it. Prompt engineering alone wasn't the whole answer.

What we learned

  • Model routing beats model size. Picking VL-Plus for vision, Max for reasoning, and Plus for prose per-call outperformed defaulting every feature to the biggest model.
  • A shared context builder is the highest-leverage abstraction in an LLM app. Upgrading the patient context block lifts every feature at once.
  • Cache at the feature level, not the prompt level. Raw-prompt caching missed too often; feature-level caching (F7 nudge, F11 policy copy) hit consistently.
  • Wave gates prevented scope creep. Each wave had to be demoable before the next one started, which kept the 11-feature scope from collapsing mid-sprint.

What's next for SeeIt

  • CI Radar — continuous underwriting. Surface changing risk signals (RHR drift, sleep debt, HRV decline) back into policy pricing in near-real-time instead of once a year.
  • Family Care Graph. Multi-user households with shared policies and caregiver views for elderly parents.
  • Qwen fine-tune on Vietnamese clinical triage transcripts to shrink the Max dependency on F5 and F6 and drop latency.
  • Apple Health + Google Fit adapters alongside the Garmin FIT ingest path.
  • Clinician-facing dashboard to close the telemedicine handoff — the symptom triage eventually needs a human on the other end.
  • Live Shinhan Life pilot. Move F11 from a static policy card to a real underwriting API integration and run it with real members.

Built With

Share this project:

Updates