InsuranceBot / docs /05-needs-analysis-flow.md
rohitsar567's picture
Deploy v1 โ€” single-Docker FastAPI + Next.js + RAG + voice + faithfulness
5dbdf6e verified
|
Raw
History Blame
8.81 kB
# 05 โ€” Needs Analysis Flow (Fact-Find)
| Field | Value |
| --- | --- |
| Project | Insurance Sales Portfolio Expert |
| Version | 0.1 |
| Date | 2026-05-13 |
| Implementation | `backend/needs_finder.py` |
## 0. Why an explicit graph (not "let the LLM figure it out")
A good Independent Financial Advisor opens with a stable, repeatable set of questions โ€” and conditionally deep-dives based on the buyer's signal. We replicate this with an **explicit question graph**, not an LLM that improvises every session.
Why:
1. **Auditable behavior.** A reviewer can see the graph, trace any session, and check why a question was (or wasn't) asked.
2. **Testable.** Pure functions of `Profile โ†’ Question`. Every branch can be unit-tested.
3. **Fail-soft.** Even if the LLM brain degrades, the question flow still works.
4. **Bilingual by construction.** Each node has English + Hindi prompts side by side.
## 1. The graph
```
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Q1: age (core) โ”‚
โ”‚ "What is your age?" โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Q2: dependents (core) โ”‚
โ”‚ "Who else do you cover?" โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Q3: income_band (core) โ”‚
โ”‚ "Annual income?" โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Q4: existing_cover (core) โ”‚
โ”‚ "Already have health ins?" โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Q5: primary_goal (core) โ”‚
โ”‚ "Why are you here?" โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Q6: location (core) โ”‚
โ”‚ "Which city / tier?" โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ–ผ
(conditional branches)
โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ–ผ โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Q7: parents_age (cond) โ”‚ โ”‚ Q8: health_conditions (always) โ”‚
โ”‚ asked IF dependents include โ”‚ โ”‚ "Any pre-existing condition โ”‚
โ”‚ 'parent' โ”‚ โ”‚ on your side?" โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ–ผ โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Q9: budget_band (core) โ”‚
โ”‚ "Premium budget?" โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Profile complete โ†’ readback + โ”‚
โ”‚ policy recommendation โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
```
## 2. Termination criteria
The graph emits `next_question = None` (i.e., ready to recommend) when all of:
1. All 6 core questions answered (age, dependents, income, existing_cover, primary_goal, location)
2. All applicable conditional questions answered (parents_age if dependents include parents)
3. health_conditions and budget_band answered
Or when the user sets `profile.free_form_session = True` โ€” the orchestrator skips fact-find and answers free questions directly.
## 3. Bilingual prompts
Every node has both `prompt_en` and `prompt_hi`. The orchestrator picks based on the user's detected language. Example:
| Node | English | Hindi |
| --- | --- | --- |
| age | "To start, what's your age?" | "เคถเฅเคฐเฅ‚ เค•เคฐเคคเฅ‡ เคนเฅˆเค‚ โ€” เค†เคชเค•เฅ€ เค‰เคฎเฅเคฐ เค•เฅเคฏเคพ เคนเฅˆ?" |
| dependents | "Who else do you want to coverโ€ฆ" | "เค†เคชเค•เฅ‡ เค…เคฒเคพเคตเคพ เค•เคฟเคธ-เค•เคฟเคธ เค•เฅ‹ cover เค•เคฐเคจเคพ เคนเฅˆโ€ฆ" |
| budget | "What annual premium budgetโ€ฆ" | "Premium เค•เฅ‡ เคฒเคฟเค เคธเคพเคฒเคพเคจเคพ เค•เคฟเคคเคจเคพ เค–เคฐเฅเคšโ€ฆ" |
This is what "Indic-native, not translated" (Doc 01 ยง5.3) looks like in practice โ€” the question is *authored* in both languages, not machine-translated at runtime.
## 4. Parsing user answers
Each node has a `parser` callable that normalizes the user's natural-language answer into a typed Profile field:
- `age`: `lambda s: int("".join(c for c in s if c.isdigit())[:3] or 0) or None`
- `dependents`: free-form string (will be normalized via LLM in v1.1)
- `income_band`: bucketed via keyword match ("5 lakh" โ†’ "under_5L")
When the parser returns `None`, we treat the question as unanswered and re-ask (v1 simply leaves the field as None and moves on โ€” re-ask is v1.1).
## 5. How the orchestrator uses this
```python
profile = Profile.from_session(session_id)
q = next_question(profile, language=detected_language)
if q is not None:
# Still in fact-find phase โ€” ask the next question
reply = q.prompt_hi if language == "indic" else q.prompt_en
profile.asked.append(q.id)
else:
# Profile complete โ€” proceed to retrieval + recommendation
chunks = retrieve(user_query, top_k=5)
reply = brain.generate(...)
```
The orchestrator can also let the user drive the conversation freely โ€” if the user immediately asks a specific question like "compare Star and HDFC ERGO," we set `free_form_session = True` and skip the fact-find.
## 6. What the user sees
```
Bot: "Hi, I'm your AI insurance advisor. To start, what's your age?"
User: "I'm 32"
Bot: "Got it. Who else do you want to cover โ€” just yourself, spouse, kids, or parents?"
User: "self + spouse, kids later"
Bot: "Roughly what's your annual income โ€” under 5 lakh, 5-10, 10-25, or 25+ lakh?"
User: "around 18 lakhs"
... [continues through the core 6] ...
Bot: "Here's what I've understood: 32 years old; covering self+spouse; income 10L-25L;
no existing cover; goal: first health policy; in a metro; budget 15K-30K.
Did I get that right?"
User: "yes"
Bot: "Based on that, three policies stand out: [...] โ€” let me explain why."
```
## 7. v2 enhancements
| # | Enhancement | Why |
| --- | --- | --- |
| 1 | LLM-parser for free-form answers ("I'm in my early 30s with 2 kids") | Handles natural-language replies instead of rigid bucket-matching |
| 2 | Re-ask on `None` parse | Robust to user confusion |
| 3 | Skip-confirm flow ("you can skip this โ€” say 'skip'") | Buyer autonomy |
| 4 | Save profile across sessions | Returning user picks up where they left off |
| 5 | Tone-match the user's energy (formal vs casual) | Conversation feels human |