InsureGuide - Healthcare Navigator

Multilingual insurance navigation for DC-area immigrants - in your language, at your pace.


Inspiration

DC-area immigrants face overlapping programs and status-dependent rules, with information scattered across English-only sites and time-sensitive policy - Alliance moratoriums, shifting FPL thresholds, federal changes landing mid-year. We designed a system where language, voice, and visible structure replace opaque PDFs, because trust and clarity are as important as raw eligibility.


What It Does

InsureGuide is a multilingual web app for Washington DC immigrants featuring:

  • Voice-guided enrollment flows (ElevenLabs + VAPI)
  • An onboarding eligibility decision tree
  • A dashboard with a 3D WorldTree visualization
  • Live voice form-fill for the DC Medicaid / IMA combined application
  • Service location cards with map deep links
  • Public-charge reassurance baked into every language's i18n copy

The WorldTree is not decorative. Static 3D layout data (programs as branches, service sites as leaves) is joined to live eligibility from GET /api/eligibility, so branch and leaf styling reflect per-program pass/fail from the canonical rules engine in real time.


How We Built It

Platform and stack

Next.js 16 (App Router), Bun + Turborepo, Supabase (auth + Postgres + pgvector), Tailwind v4 with MD3-style tokens, i18next (8 languages), ElevenLabs (@elevenlabs/react), and VAPI webhooks with server tools under /api/tools/*.


The 3D WorldTree - React Three Fiber + Three.js

Rendering uses @react-three/fiber for the scene and @react-three/drei for helpers (Line, Text, Billboard). All branch geometry is extruded along parametric curves using THREE.TubeGeometry.


Trunk - multi-strand helical wire bundle

The trunk runs from bottom \(\mathbf{p}{\text{bottom}} = (0,\, -3,\, 0)\) to top \(\mathbf{p}{\text{top}} = (0,\, 2,\, 0)\). Seven strands (\(k = 0, \ldots, 6\)) are each a subclass of THREE.Curve<Vector3>, parameterized by \(t \in \bigl[0,\, 1\bigr]\).

Height (linear lerp along \(y\)):

$$ y(t) = (1 - t)\, y_{\text{bottom}} + t\, y_{\text{top}} $$

Helix phase for strand \(k\):

$$ \phi_k = \frac{2\pi k}{7} $$

Polar angle (\(T = 3.5\) full turns):

$$ \theta(t) = 2\pi T\, t + \phi_k $$

Convergence radius (strands collapse to the crown over the final 10 % of height):

$$ r(t) = r_{\text{base}} \cdot \begin{cases} 1 & t \leq 0.9 \ 1 - \frac{t - 0.9}{0.1} & t > 0.9 \end{cases} $$

Optional wobble (visual strand separation):

$$ r_{\text{wobble}}(t) = r(t) + \epsilon \sin(8\pi t + \phi_k) $$

Final strand position in the horizontal plane:

$$ x(t) = r_{\text{wobble}}(t)\,\cos(\theta(t)), \qquad z(t) = r_{\text{wobble}}(t)\,\sin(\theta(t)) $$

Each strand is meshed as THREE.TubeGeometry(curve, 120, rTube, 6, false).


Main branches - pentagonal symmetry

Five program families are placed at equal angular spacing around the trunk. Branch tip \(i\) sits at azimuth \(\theta_i\):

$$ \theta_i = \frac{2\pi i}{5}, \qquad i \in {0,\, 1,\, 2,\, 3,\, 4} $$

In world coordinates, with horizontal reach \(r_{\text{branch}}\) and vertical lift \(y_{\text{branch}}\):

$$ \mathbf{p}{\text{tip},\, i} = \left( r{\text{branch}} \sin\theta_i,\;\; y_{\text{branch}},\;\; r_{\text{branch}} \cos\theta_i \right) $$


Branch curves - Catmull-Rom splines

Each branch is a THREE.CatmullRomCurve3 through three world-space control points:

$$ \text{trunk top} \;\rightarrow\; \mathbf{p}{\text{mid}} \;\rightarrow\; \mathbf{p}{\text{tip},\, i} $$

where the midpoint \(\mathbf{p}{\text{mid}}\) is scaled toward the trunk at approximately \(0.4\, r{\text{branch}}\) for smooth organic curvature. This yields \(C^1\)-continuous geometry from only three control points per branch - ideal for rapid iteration.


Finger branches - procedural sub-geometry

From each program tip, 6 finger branches fan out. Lengths follow a deterministic pattern to avoid uniformity without requiring a full L-system:

$$ \ell_i = 0.6 + 0.3 \sin(1.7\, i), \qquad i \in {0, \ldots, 5} $$

Sub-branches recurse once per finger, producing a fractal-like silhouette under full version control.


Leaf placement - cylindrical offsets from branch tip

Each leaf is offset from its program's tip in cylindrical coordinates. For azimuth angle \(\alpha\), radial reach \(\rho\), and vertical offset \(\Delta y\):

$$ \mathbf{p}{\text{leaf}} = \mathbf{p}{\text{tip}} + \left( \rho \sin\alpha,\;\; \Delta y,\;\; \rho \cos\alpha \right) $$


Eligibility overlay

useEligibilityTree fetches GET /api/eligibility, builds a Map<programId, EligibilityResult>, and merges each static NODES entry with the API row for its programId. A program branch is marked eligible if any leaf within it is eligible, so the 3D scene reflects exactly the same canonical router as the REST API. Visual state and policy correctness share a single source of truth.


Eligibility engine - policy as code

The core eligibility computation lives in lib/eligibility/. All thresholds are encoded deterministically in TypeScript - not inferred by an LLM.

Federal Poverty Level percentage (from fpl.ts and fpl-table.json):

Let \(H\) be household size (clamped to \(\bigl[1,\, 99\bigr]\)) and \(A(H)\) be the annual FPL dollars for \(H\) persons from the HHS table. For monthly income \(M\):

$$ \text{incomePctFPL} = \left\lfloor \frac{12M}{A(H)} \times 100 \right\rceil $$

The 2026 HHS formula for households beyond 8 persons applies an additional-person increment \(\delta\):

$$ A(H) = A(8) + (H - 8)\,\delta, \qquad H > 8 $$

Alliance eligibility gate (encodes the October 2025 moratorium):

$$ \text{alliance_eligible} = (\text{age} \leq 25) \;\wedge\; (\text{incomePctFPL} \leq 138) \;\wedge\; \neg\,\text{has_medicare} \;\wedge\; \text{dc_resident} $$

Per-status eligibility vector across all five programs:

$$ \mathbf{e}(\text{status}) = \begin{bmatrix} \text{alliance} \ \text{healthy_dc} \ \text{medicaid} \ \text{emergency_medicaid} \ \text{marketplace} \end{bmatrix} \in {0,\, 1}^5 $$

For example, a TPS holder yields \(\mathbf{e} = \bigl[1,\, 1,\, 0,\, 1,\, 1\bigr]^\top\); an undocumented adult 26+ yields \(\mathbf{e} = \bigl[0,\, 0,\, 0,\, 1,\, 0\bigr]^\top\).


Voice, forms, and dual paths

Web (browser): ElevenLabs signed URL + clientTools (fillField, getFormProgress, completeForm) update React state and issue debounced PUT /api/forms/[formId].

Phone (VAPI): Server tools under /api/tools/* mirror all profile and form operations without requiring a browser session - callers can fill the DC Medicaid application entirely by voice.


RAG - optional retrieval layer (ADR-002)

An offline Python pipeline (trafilatura + pdfplumber + LangChain RecursiveCharacterTextSplitter) ingests policy documents, embeds with Gemini text-embedding-004 (768-dim), and stores chunks in Supabase. Retrieval uses filtered cosine similarity:

$$ \text{similarity}(q,\, c) = 1 - \frac{q \cdot c}{|q| \, |c|} $$

Before semantic ranking, a chunk is only eligible if its program tags \(\mathcal{P}\) intersect the caller's matched-program set \(\mathcal{U}\), and its status tags \(\mathcal{S}\) intersect the caller's RAG status tags \(\mathcal{R}\). Equivalently:

$$ \mathcal{P} \cap \mathcal{U} \neq \emptyset \quad \text{and} \quad \mathcal{S} \cap \mathcal{R} \neq \emptyset $$

Context ordering boosts urgency-3 (time-sensitive policy) chunks regardless of similarity rank:

$$ \text{context_order} = \mathrm{sort_by}(-u,\; -\text{similarity}) $$

Per ADR-002, LangChain is used only at ingest for chunking; all request-time retrieval is hand-written TypeScript against the match_chunks RPC.


Challenges We Ran Into

3D + accessibility: Balancing bloom and emissive trunk glow with readability across devices; reduceMotion and high-contrast accessibility flags gate animation intensity.

Geometry vs. semantics: The tree's layout is authored; eligibility is computed. Keeping programId consistent across treeLayout.ts, the API, and program-rules.ts prevents visually correct but semantically wrong leaf states.

Policy as code: Expressing moratoriums, FPL gates, and the full immigration taxonomy in TypeScript - rather than hoping an LLM infers them, is non-negotiable for correctness.

Scope: A voice-first product meant not shipping a streaming chat page. RAG stays on the telephony and tools paths only.


Accomplishments We're Proud Of

  • A visually distinctive trunk: parametric helices + Catmull-Rom branches + procedural finger geometry - fully readable in treeLayout.ts and compelling on screen
  • A single source of truth for eligibility: the WorldTree consumes the same GET /api/eligibility endpoint as every other UI
  • An end-to-end hackathon path: marketing site → profile → eligibility POST → dashboard embed → voice form-fill pipeline

What We Learned

Separate layout math from policy math. Helix parameters and spline control points are presentation concerns. FPL percentages and immigration taxonomy rules are correctness concerns. Conflating the two creates bugs that are hard to locate.

Parametric curves are version-control-friendly. Curve.getPoint(t) geometry is just numbers in a .ts file - diffable, reviewable, and iteration-fast for a hackathon.

Catmull-Rom gives smooth branches from few control points. Three world-space points per branch, no hand-authoring of tangents.


What's Next

  • Maryland and Virginia coverage - Prince George's County and Arlington immigrant populations
  • Household / multi-person eligibility flows
  • Recertification SMS/email reminders (60 days before annual renewal)
  • Navigator dashboard for human case workers to pick up where the app left off
  • Corpus staleness monitoring - the Alliance rules change again October 1, 2026; coverage ends for all adults 21+ on October 1, 2027

Built at BitCamp 2026. Every formula in this submission — from \(\theta_i = 2\pi i / 5\) to \(\text{incomePctFPL} = \left\lfloor \frac{12M}{A(H)} \times 100 \right\rceil\) — runs live in the app.

Built With

Share this project:

Updates