Spaces:
Sleeping
docs: reorganize into 8 buckets + 24 ADRs + master README (D-023)
Browse filesReplaces the legacy flat docs/ layout with a numbered bucket structure
modeled on technical-repo conventions (e.g. Kubernetes docs/, IETF RFCs).
Old monolithic decisions.md (32 KB) split into 24 individual ADRs using
the Nygard template (Status / Context / Decision / Alternatives /
Consequences / Revisit-at-scale).
Structure:
docs/00-overview/ β problem statement, roadmap
docs/10-architecture/ β system, stack rationale, safety, scoring
docs/20-data-pipeline/ β ingestion policy, information source map
docs/30-engineering/ β needs-analysis flow, discovery script
docs/40-evaluation/ β eval methodology
docs/50-operations/ β (placeholder; runbooks land here)
docs/60-decisions/ β 24 ADRs + index README + legacy archive
docs/70-reference/ β (placeholder; glossary + indexes land here)
All 14 existing docs moved via git mv (history preserved):
01-requirements.md β 00-overview/problem-statement.md
02-architecture.md β 10-architecture/system-overview.md
03-eval-plan.md β 40-evaluation/eval-methodology.md
04-failure-modes.md β 10-architecture/safety-architecture.md
05-needs-analysis-flow.md β 30-engineering/needs-analysis-flow.md
ROADMAP.md β 00-overview/roadmap.md
decisions.md β 60-decisions/legacy-decisions-monolith.md
discovery-script.md β 30-engineering/discovery-script.md
information_source_map.md β 20-data-pipeline/information-source-map.md
ingestion_policy.md β 20-data-pipeline/ingestion-policy.md
scorecard-{methodology,knowledge-graph,tie-breaker-rubric}.md
β 10-architecture/scoring-*.md
tech-stack-rationale.md β 10-architecture/stack-rationale.md
24 new ADRs (ADR-001 through ADR-024):
001 vertical-slice-scope Β· 002 health-category-vertical
003 curated-corpus Β· 004 hybrid-structured-vector
005 nextjs-fastapi-frontend Β· 006 sarvam-first-stack
007 illustrative-pricing Β· 008 consultative-advisor-persona
009 19-insurer-comprehensive-schema Β· 010 secret-handling
011 bge-local-embeddings Β· 012 render-then-hf-space-deploy
013 tailwind-shadcn-ui Β· 014 groq-llama-grader
015 openapi-typescript-codegen Β· 016 hybrid-brain-router
017 irdai-corpus-playwright-rescue Β· 018 chunk-size-sweep-deferred
019 nim-single-provider-consolidation Β· 020 code-data-split-hf-dataset
021 view-aware-system-prompt Β· 022 conversational-profile-updates
023 admin-panel-ip-gated Β· 024 triple-mirror-code-and-data
Superseded ADRs (006, 012, 014, 016) link to their replacements.
Master README.md rewritten as the project bible:
Β§1 60-second pitch
Β§2 vision + requirements + success criteria
Β§3 two parallel flows (customer journey vs. tech-flow)
Β§4 exhaustive tech architecture (diagrams + per-component table)
Β§5 data architecture (corpus + ingestion + storage topology)
Β§6 quality + safety (4-gate faithfulness + Indic drift gates)
Β§7 document ecosystem guide (per-bucket map)
Β§8 quick start + local dev
Β§9 deployment + storage topology
Β§10 rebuild-from-scratch instructions for a fresh Claude Code session
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- README.md +767 -48
- docs/{01-requirements.md β 00-overview/problem-statement.md} +0 -0
- docs/{ROADMAP.md β 00-overview/roadmap.md} +0 -0
- docs/{04-failure-modes.md β 10-architecture/safety-architecture.md} +0 -0
- docs/{scorecard-knowledge-graph.md β 10-architecture/scoring-knowledge-graph.md} +0 -0
- docs/{scorecard-methodology.md β 10-architecture/scoring-methodology.md} +0 -0
- docs/{tie-breaker-rubric.md β 10-architecture/scoring-tie-breaker-rubric.md} +0 -0
- docs/{tech-stack-rationale.md β 10-architecture/stack-rationale.md} +0 -0
- docs/{02-architecture.md β 10-architecture/system-overview.md} +0 -0
- docs/{information_source_map.md β 20-data-pipeline/information-source-map.md} +0 -0
- docs/{ingestion_policy.md β 20-data-pipeline/ingestion-policy.md} +0 -0
- docs/{discovery-script.md β 30-engineering/discovery-script.md} +0 -0
- docs/{05-needs-analysis-flow.md β 30-engineering/needs-analysis-flow.md} +0 -0
- docs/{03-eval-plan.md β 40-evaluation/eval-methodology.md} +0 -0
- docs/60-decisions/ADR-001-vertical-slice-scope.md +33 -0
- docs/60-decisions/ADR-002-health-category-vertical.md +36 -0
- docs/60-decisions/ADR-003-curated-corpus.md +40 -0
- docs/60-decisions/ADR-004-hybrid-structured-vector.md +52 -0
- docs/60-decisions/ADR-005-nextjs-fastapi-frontend.md +45 -0
- docs/60-decisions/ADR-006-sarvam-first-stack.md +42 -0
- docs/60-decisions/ADR-007-illustrative-pricing.md +48 -0
- docs/60-decisions/ADR-008-consultative-advisor-persona.md +53 -0
- docs/60-decisions/ADR-009-19-insurer-comprehensive-schema.md +52 -0
- docs/60-decisions/ADR-010-secret-handling.md +52 -0
- docs/60-decisions/ADR-011-bge-local-embeddings.md +54 -0
- docs/60-decisions/ADR-012-render-then-hf-space-deploy.md +53 -0
- docs/60-decisions/ADR-013-tailwind-shadcn-ui.md +52 -0
- docs/60-decisions/ADR-014-groq-llama-grader.md +34 -0
- docs/60-decisions/ADR-015-openapi-typescript-codegen.md +53 -0
- docs/60-decisions/ADR-016-hybrid-brain-router.md +41 -0
- docs/60-decisions/ADR-017-irdai-corpus-playwright-rescue.md +54 -0
- docs/60-decisions/ADR-018-chunk-size-sweep-deferred.md +61 -0
- docs/60-decisions/ADR-019-nim-single-provider-consolidation.md +82 -0
- docs/60-decisions/ADR-020-code-data-split-hf-dataset.md +87 -0
- docs/60-decisions/ADR-021-view-aware-system-prompt.md +84 -0
- docs/60-decisions/ADR-022-conversational-profile-updates.md +92 -0
- docs/60-decisions/ADR-023-admin-panel-ip-gated.md +79 -0
- docs/60-decisions/ADR-024-triple-mirror-code-and-data.md +81 -0
- docs/60-decisions/README.md +46 -0
- docs/{decisions.md β 60-decisions/legacy-decisions-monolith.md} +0 -0
|
@@ -7,79 +7,798 @@ sdk: docker
|
|
| 7 |
app_port: 7860
|
| 8 |
pinned: false
|
| 9 |
license: mit
|
| 10 |
-
short_description: Voice AI advisor for Indian health insurance
|
| 11 |
---
|
| 12 |
|
| 13 |
# Insurance Sales Portfolio Expert
|
| 14 |
|
| 15 |
-
A voice-first AI advisor for Indian health insurance
|
| 16 |
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
-
|
| 22 |
-
2. [`docs/02-architecture.md`](docs/02-architecture.md) β stack picks, schema, system design *(in progress)*
|
| 23 |
-
3. [`docs/03-eval-plan.md`](docs/03-eval-plan.md) β gold Q&A pairs, automated grader, accuracy targets *(in progress)*
|
| 24 |
-
4. [`docs/04-failure-modes.md`](docs/04-failure-modes.md) β known failure modes + mitigations *(in progress)*
|
| 25 |
-
5. [`docs/05-needs-analysis-flow.md`](docs/05-needs-analysis-flow.md) β fact-find question graph *(in progress)*
|
| 26 |
-
6. [`docs/decisions.md`](docs/decisions.md) β every meaningful decision logged with alternatives + reasoning
|
| 27 |
-
7. [`docs/ROADMAP.md`](docs/ROADMAP.md) β how this scales from v1 vertical slice to full platform
|
| 28 |
|
| 29 |
-
##
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
```bash
|
|
|
|
| 32 |
git clone https://github.com/rohitsar567/insurance-sales-bot.git
|
| 33 |
cd insurance-sales-bot
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
```
|
| 39 |
|
| 40 |
-
##
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
|
|
|
| 42 |
```
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
```
|
| 51 |
|
| 52 |
-
##
|
| 53 |
|
| 54 |
-
-
|
| 55 |
-
-
|
| 56 |
-
-
|
| 57 |
-
- Adaptive needs analysis
|
| 58 |
-
- Granular filter + side-by-side comparison
|
| 59 |
-
- Illustrative pricing bands
|
| 60 |
|
| 61 |
-
|
| 62 |
|
| 63 |
-
|
| 64 |
|
| 65 |
-
##
|
| 66 |
|
| 67 |
-
|
| 68 |
-
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
- **UI**: Streamlit
|
| 73 |
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
|
| 76 |
-
##
|
| 77 |
|
| 78 |
-
|
| 79 |
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
-
|
| 83 |
-
- [ ] `.venv/bin/python tools/upload_vectors_to_dataset.py` β push `rag/vectors/` to the HF dataset
|
| 84 |
-
- [ ] `.venv/bin/python tools/upload_extracted_to_dataset.py` β push `rag/extracted/` (only if structured JSONs changed)
|
| 85 |
-
- [ ] `.venv/bin/python tools/upload_to_hf.py` β push code to the Space (triggers a Docker rebuild that pulls the dataset)
|
|
|
|
| 7 |
app_port: 7860
|
| 8 |
pinned: false
|
| 9 |
license: mit
|
| 10 |
+
short_description: Voice AI advisor for Indian health insurance β grounded, cited, BFSI-compliance-grade
|
| 11 |
---
|
| 12 |
|
| 13 |
# Insurance Sales Portfolio Expert
|
| 14 |
|
| 15 |
+
**A voice-first, BFSI-compliance-grade AI advisor for Indian health insurance.** Built as a Sarvam AI take-home, deployed on HuggingFace Spaces, with grounding, citations, faithfulness gates, and a curated 208-document corpus.
|
| 16 |
|
| 17 |
+
| | |
|
| 18 |
+
|---|---|
|
| 19 |
+
| **Live demo** | https://rohitsar567-insurancebot.hf.space |
|
| 20 |
+
| **Code (production)** | [`huggingface.co/spaces/rohitsar567/InsuranceBot`](https://huggingface.co/spaces/rohitsar567/InsuranceBot) Β· [`github.com/rohitsar567/insurance-sales-bot`](https://github.com/rohitsar567/insurance-sales-bot) |
|
| 21 |
+
| **Data (corpus + vectors)** | [`huggingface.co/datasets/rohitsar567/insurance-bot-data`](https://huggingface.co/datasets/rohitsar567/insurance-bot-data) Β· [`github.com/rohitsar567/insurance-sales-bot-data`](https://github.com/rohitsar567/insurance-sales-bot-data) (LFS) |
|
| 22 |
+
| **Author** | Rohit Saraf Β· rohitsar567@gmail.com |
|
| 23 |
+
| **Read time** | ~20 minutes (this doc) Β· or jump to [Β§7 Document Ecosystem Guide](#7-document-ecosystem-guide) |
|
| 24 |
|
| 25 |
+
---
|
| 26 |
+
|
| 27 |
+
## Table of Contents
|
| 28 |
+
|
| 29 |
+
1. [Sixty-second pitch](#1-sixty-second-pitch)
|
| 30 |
+
2. [Project vision & requirements](#2-project-vision--requirements)
|
| 31 |
+
3. [Two parallel flows β customer vs. technology](#3-two-parallel-flows)
|
| 32 |
+
4. [Exhaustive tech architecture](#4-exhaustive-tech-architecture)
|
| 33 |
+
5. [Data architecture β corpus, extraction, embeddings](#5-data-architecture)
|
| 34 |
+
6. [Quality & safety β eval, faithfulness, refusal](#6-quality--safety)
|
| 35 |
+
7. [Document ecosystem guide](#7-document-ecosystem-guide)
|
| 36 |
+
8. [Quick start & local development](#8-quick-start--local-development)
|
| 37 |
+
9. [Deployment & storage topology](#9-deployment--storage-topology)
|
| 38 |
+
10. [What a fresh Claude Code session needs to rebuild this](#10-rebuild-from-scratch)
|
| 39 |
+
|
| 40 |
+
---
|
| 41 |
+
|
| 42 |
+
## 1. Sixty-second pitch
|
| 43 |
+
|
| 44 |
+
A **voice-first health-insurance advisor** for Indian buyers, grounded in a curated corpus of **208 documents** β 190 product documents from 19 leading insurers plus 18 IRDAI / regulatory documents β extracted into a 48-field structured schema with a rules-based AβF scorecard and a **4-gate hallucination defense** on every reply.
|
| 45 |
+
|
| 46 |
+
The bot is **consumer-facing in experience, B2B in commercial application.** The realistic deployment is an insurer or aggregator white-labelling this advisor on top of Sarvam's ASR/TTS/LLM stack. The build deliberately optimises for the artifacts a BFSI buyer would audit: provenance, refusal behaviour, eval rigor, citation grammar.
|
| 47 |
+
|
| 48 |
+
**Try on the live demo:** *"What's the pre-existing disease waiting period under Care Supreme, and how does that compare to ICICI Elevate?"* β comparative answer with `[Source: ...]` citations linking to specific policy PDFs and page ranges, brain tag showing which model handled it, audio synthesised by Sarvam Bulbul. Ask the same in Hinglish β *"Care Supreme mein PED ka waiting period kya hai?"* β and the response flows through the Indic translation cascade with three drift checks.
|
| 49 |
+
|
| 50 |
+
---
|
| 51 |
+
|
| 52 |
+
## 2. Project vision & requirements
|
| 53 |
+
|
| 54 |
+
### 2.1 The problem
|
| 55 |
+
|
| 56 |
+
Indian health insurance has 19+ insurers, 250+ products, and a regulatory layer (IRDAI master circulars, Insurance Act 1938) that materially overrides individual policy clauses. Buyers face:
|
| 57 |
+
|
| 58 |
+
- **Information asymmetry** β premiums are hidden behind callback flows; product wordings are 60-page PDFs.
|
| 59 |
+
- **Comparison fatigue** β features named differently across insurers (room-rent cap vs. category-of-room limit vs. accommodation eligibility β same thing).
|
| 60 |
+
- **Mis-selling risk** β agents are paid on conversion; advice is rarely consultative.
|
| 61 |
+
- **Regulatory complexity** β IRDAI mandates (waiting periods, free-look windows, standard exclusions) override insurer-specific clauses.
|
| 62 |
+
|
| 63 |
+
### 2.2 What we built
|
| 64 |
+
|
| 65 |
+
A **voice-first conversational advisor** that:
|
| 66 |
+
|
| 67 |
+
- **Listens** in English, Hindi, or Hinglish (Sarvam Saarika v2.5 STT).
|
| 68 |
+
- **Grounds** every factual claim in a retrieved PDF clause with `[Source: ...]` citation.
|
| 69 |
+
- **Refuses** when the corpus doesn't have the answer (4 faithfulness gates).
|
| 70 |
+
- **Compares** policies side-by-side using a 48-field structured schema.
|
| 71 |
+
- **Scores** each policy AβF via a rules-based scorecard (24 of 48 fields β 6 sub-scores).
|
| 72 |
+
- **Personalizes** β once the user shares profile info (age, dependents, income, conditions), scorecards re-compute and chat answers ground against the user's situation.
|
| 73 |
+
- **Speaks** in the user's language (Sarvam Bulbul v2 TTS), with three Indic drift gates checking the translated reply preserves numbers, citations, and meaning.
|
| 74 |
+
|
| 75 |
+
### 2.3 Success criteria (and current state)
|
| 76 |
+
|
| 77 |
+
| Goal | v1 status |
|
| 78 |
+
|---|---|
|
| 79 |
+
| Voice-first, push-to-talk | β live |
|
| 80 |
+
| Hindi/Hinglish bidirectional | β live (Sarvam translation cascade + 3 drift gates) |
|
| 81 |
+
| Cited answers grounded in PDFs | β live (4-gate faithfulness) |
|
| 82 |
+
| Cross-policy comparison | β live (DuckDB structured + Chroma vectors) |
|
| 83 |
+
| Personalised scorecards | β live (profile RAG β profile becomes a vector chunk) |
|
| 84 |
+
| Refusal precision (refuse > mis-cite) | β live (Gate 1-4, with 12/25 eval questions blocked β the safe failure mode) |
|
| 85 |
+
| Regulatory grounding (IRDAI) | β live (after Playwright rescue past Akamai bot protection β [ADR-017](docs/60-decisions/ADR-017-irdai-corpus-playwright-rescue.md)) |
|
| 86 |
+
| Admin LLM control panel | β live (in-app tab, IP+password gated β [ADR-023](docs/60-decisions/ADR-023-admin-panel-ip-gated.md)) |
|
| 87 |
+
|
| 88 |
+
### 2.4 Explicit non-goals (v1)
|
| 89 |
+
|
| 90 |
+
- **Real-time quotes** β premiums are illustrative bands with disclaimer ([ADR-007](docs/60-decisions/ADR-007-illustrative-pricing.md)).
|
| 91 |
+
- **Medical advice** β bot answers coverage questions, never clinical ones (persona rule 4).
|
| 92 |
+
- **Full-duplex streaming voice** β v1 is push-to-talk; full-duplex is v2.
|
| 93 |
+
- **Life / motor insurance** β v1 is health-only; v2 generalises ([ADR-002](docs/60-decisions/ADR-002-health-category-vertical.md)).
|
| 94 |
+
- **Sentiment classifier on raw scraped reviews** β IRDAI complaint numbers are primary-source; sentiment labels are curated snippet roll-ups, not LLM-extracted.
|
| 95 |
+
|
| 96 |
+
---
|
| 97 |
+
|
| 98 |
+
## 3. Two parallel flows
|
| 99 |
+
|
| 100 |
+
The bot is two flows running together β the customer's experience and the technology underneath. They are intentionally separated below so a reviewer can scan either.
|
| 101 |
+
|
| 102 |
+
### 3A. Customer / Process Flow
|
| 103 |
+
|
| 104 |
+
What a buyer experiences, end to end.
|
| 105 |
+
|
| 106 |
+
```
|
| 107 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 108 |
+
β STEP 1. Land on the bot β
|
| 109 |
+
β ------- β
|
| 110 |
+
β Β· Sees: chat panel + Marketplace Β· Premium Β· Profile Β· Admin tabs β
|
| 111 |
+
β Β· Suggested questions in the chat box β
|
| 112 |
+
β Β· "Voice reply" + "Hands-free" toggles in the input area β
|
| 113 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 114 |
+
β
|
| 115 |
+
βΌ
|
| 116 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 117 |
+
β STEP 2. Ask the first question (text or voice) β
|
| 118 |
+
β ------- β
|
| 119 |
+
β Β· "Suggest a health insurance plan for me" β
|
| 120 |
+
β Β· Bot recognizes fact-find intent β asks: "First, your age?" β
|
| 121 |
+
β Β· Conversational onboarding: age β dependents β income β primary goal β
|
| 122 |
+
β β location β parents-to-insure β health conditions β budget β
|
| 123 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 124 |
+
β
|
| 125 |
+
βΌ
|
| 126 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 127 |
+
β STEP 3. Profile-driven personalization unlocks β
|
| 128 |
+
β ------- β
|
| 129 |
+
β Β· Completeness bar reaches β₯60% β "personalized scores unlocked" β
|
| 130 |
+
β Β· Marketplace tab now shows per-policy A-F grades RE-COMPUTED for β
|
| 131 |
+
β this user's specific situation β
|
| 132 |
+
β Β· Chat references your profile inline ("at 32 with 1 dependentβ¦") β
|
| 133 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 134 |
+
β
|
| 135 |
+
βΌ
|
| 136 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 137 |
+
β STEP 4. Free-form questions with citations β
|
| 138 |
+
β ------- β
|
| 139 |
+
β Β· "What's the room rent cap on Care Supreme?" β
|
| 140 |
+
β Β· "Compare cataract waiting in HDFC Optima vs ICICI Elevate" β
|
| 141 |
+
β Β· Every factual claim gets [Source: <policy>, p.<page>] citation β
|
| 142 |
+
β Β· Click a citation β opens the policy detail modal β
|
| 143 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 144 |
+
β
|
| 145 |
+
βΌ
|
| 146 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 147 |
+
β STEP 5. Conversational profile updates (mid-chat) β
|
| 148 |
+
β ------- β
|
| 149 |
+
β Β· User says: "I was just diagnosed with diabetes" β
|
| 150 |
+
β Β· Bot silently extracts β updates session.profile β re-upserts profile β
|
| 151 |
+
β chunk β completeness bar ticks up β scores refresh β
|
| 152 |
+
β Β· No form-filling required mid-conversation β
|
| 153 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 154 |
+
β
|
| 155 |
+
βΌ
|
| 156 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 157 |
+
β STEP 6. View-aware grounding (the copilot effect) β
|
| 158 |
+
β ------- β
|
| 159 |
+
β Β· User opens a policy detail modal β
|
| 160 |
+
β Β· Asks "What's the waiting period on this?" β
|
| 161 |
+
β Β· Bot resolves "this" β answers without re-stating policy name β
|
| 162 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 163 |
+
β
|
| 164 |
+
βΌ
|
| 165 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 166 |
+
β STEP 7. Refusal as a feature β
|
| 167 |
+
β ------- β
|
| 168 |
+
β Β· User: "Does this policy cover space-tourism injuries?" β
|
| 169 |
+
β Β· Bot: "I'd rather not answer that without stronger evidence in the β
|
| 170 |
+
β policy documents I have." β
|
| 171 |
+
β Β· The SAFE failure mode in BFSI is refuse > mis-cite β
|
| 172 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 173 |
+
β
|
| 174 |
+
βΌ
|
| 175 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 176 |
+
β STEP 8. Hindi / Hinglish flow β
|
| 177 |
+
β ------- β
|
| 178 |
+
β Β· User switches UI language toggle, or just speaks Hinglish β
|
| 179 |
+
β Β· "Care Supreme mein PED ka waiting period kya hai?" β
|
| 180 |
+
β Β· Bot responds in Hinglish with citations preserved β
|
| 181 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 182 |
+
β
|
| 183 |
+
βΌ
|
| 184 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 185 |
+
β STEP 9. Persistent chat across sessions β
|
| 186 |
+
β ------- β
|
| 187 |
+
β Β· Close tab, come back tomorrow: chat history + profile restored β
|
| 188 |
+
β Β· Sessions persisted to disk on backend; local storage on frontend β
|
| 189 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 190 |
+
```
|
| 191 |
+
|
| 192 |
+
### 3B. Technology Flow (parallel)
|
| 193 |
+
|
| 194 |
+
What's happening under the hood for the same journey.
|
| 195 |
+
|
| 196 |
+
```
|
| 197 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 198 |
+
β TECH 1. Page load β
|
| 199 |
+
β ------- β
|
| 200 |
+
β Β· Next.js 14 SSR ships HTML in ~200ms β
|
| 201 |
+
β Β· React hydrates; useEffect fetches /api/health, /api/coverage, β
|
| 202 |
+
β /api/profile/completeness β
|
| 203 |
+
β Β· localStorage rehydrates messages[] + sessionId if returning user β
|
| 204 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 205 |
+
β
|
| 206 |
+
βΌ
|
| 207 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 208 |
+
β TECH 2. User submits a question β
|
| 209 |
+
β ------- β
|
| 210 |
+
β Β· Voice path: MediaRecorder blob β POST /api/transcribe β Sarvam β
|
| 211 |
+
β Saarika v2.5 STT β text β
|
| 212 |
+
β Β· Text path: direct POST /api/chat β
|
| 213 |
+
β Β· Payload includes: user_text, session_id, chat_history[], β
|
| 214 |
+
β return_audio, tts_language_code, view_context{active_view, β
|
| 215 |
+
β active_policy_id, filters} β
|
| 216 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 217 |
+
β
|
| 218 |
+
βΌ
|
| 219 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 220 |
+
β TECH 3. Orchestrator entry β handle_turn() β
|
| 221 |
+
β ------- β
|
| 222 |
+
β Β· classify_intent(user_text) β fact_find / qa / comparison / β
|
| 223 |
+
β recommendation β
|
| 224 |
+
β Β· detect_language(user_text) β english / indic β
|
| 225 |
+
β Β· Indic cascade entry: if indic, Sarvam-M translates β English for β
|
| 226 |
+
β reasoning, response will be translated back β
|
| 227 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 228 |
+
β
|
| 229 |
+
βΌ
|
| 230 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 231 |
+
β TECH 4. Fact-find OR free-form branch β
|
| 232 |
+
β ------- β
|
| 233 |
+
β Β· If session.awaiting_question_id and not free_form: β
|
| 234 |
+
β record_answer(session.profile, qid, raw) β
|
| 235 |
+
β next_question(profile) β asks next β
|
| 236 |
+
β RETURN early β
|
| 237 |
+
β Β· Else: set free_form_session = True, continue to retrieval β
|
| 238 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 239 |
+
β
|
| 240 |
+
βΌ
|
| 241 |
+
ββββββββββββββββοΏ½οΏ½οΏ½βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 242 |
+
β TECH 5. Profile update extraction (free-form only) β
|
| 243 |
+
β ------- β
|
| 244 |
+
β Β· extract_profile_updates(user_text, session.profile) β
|
| 245 |
+
β Β· NIM Llama-3.3-70B returns JSON of high-confidence updates β
|
| 246 |
+
β Β· Validation: enum / type / bounds checks; drop on failure β
|
| 247 |
+
β Β· Apply via session.update_profile_field() β
|
| 248 |
+
β Β· upsert_profile_chunk() re-embeds profile β Chroma β
|
| 249 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 250 |
+
β
|
| 251 |
+
βΌ
|
| 252 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 253 |
+
β TECH 6. Retrieval β retrieve(query, session_id) β
|
| 254 |
+
β ------- β
|
| 255 |
+
β Β· BGE-small embeds user_text β 384-d vector β
|
| 256 |
+
β Β· Chroma cosine search, top_k=5 β
|
| 257 |
+
β Β· Profile chunk (doc_type='profile') boosted to top of results β
|
| 258 |
+
β Β· Second pass on regulatory chunks if query mentions IRDAI / Section β
|
| 259 |
+
β Β· Returns list[RetrievedChunk(policy_id, page_start, page_end, text, β
|
| 260 |
+
β source_url, score)] β
|
| 261 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 262 |
+
β
|
| 263 |
+
βΌ
|
| 264 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 265 |
+
β TECH 7. Brain selection β pick_brain(intent, language) β
|
| 266 |
+
β ------- β
|
| 267 |
+
β Β· intent β {comparison, recommendation} β DeepSeek-V4-Pro (heavy) β
|
| 268 |
+
β Β· intent β {qa, fact_find} β DeepSeek-V4-Flash (fast) β
|
| 269 |
+
β Β· All via integrate.api.nvidia.com (single NIM API key) β
|
| 270 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 271 |
+
β
|
| 272 |
+
βΌ
|
| 273 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 274 |
+
β TECH 8. System prompt construction β
|
| 275 |
+
β ------- β
|
| 276 |
+
β Β· build_messages() composes: β
|
| 277 |
+
β [System: ADVISOR_PROMPT + USER PROFILE block + USER IS LOOKING AT β
|
| 278 |
+
β (view_context) block] β
|
| 279 |
+
β [Assistant/User: last 5 turns of chat_history] β
|
| 280 |
+
β [User: USER QUESTION + RETRIEVED POLICY CLAUSES + reply instructions] β
|
| 281 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 282 |
+
β
|
| 283 |
+
βΌ
|
| 284 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 285 |
+
β TECH 9. Brain LLM call β reply text β
|
| 286 |
+
β ------- β
|
| 287 |
+
β Β· NIM streaming chat call (V4-Pro or V4-Flash) β
|
| 288 |
+
β Β· strip_think_tags() removes <think>β¦</think> chain-of-thought β
|
| 289 |
+
β Β· Capture brain_model_actual for non-circular judge selection β
|
| 290 |
+
βββββββββββββββββββββββββββββββββββοΏ½οΏ½οΏ½ββββββββββββββββββββββββββββββββββββββββ
|
| 291 |
+
β
|
| 292 |
+
βΌ
|
| 293 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 294 |
+
β TECH 10. Faithfulness gates β
|
| 295 |
+
β ------- β
|
| 296 |
+
β Β· Gate 1: retrieval floor β top score β₯ 0.30 β
|
| 297 |
+
β Β· Gate 2: citation integrity β every cited policy was retrieved β
|
| 298 |
+
β Β· Gate 3: numeric grounding β every βΉ/%/days/months/years in reply β
|
| 299 |
+
β appears in retrieved chunks β
|
| 300 |
+
β Β· Gate 4: LLM-judge (Llama-4 Maverick, different family from brain) β
|
| 301 |
+
β Β· If all 4 pass β reply ships β
|
| 302 |
+
β Β· If any fails (non-Gate-1) β cross-check retry with Maverick β
|
| 303 |
+
β Β· If still fails β safe refusal + log to logs/hallucinations.jsonl β
|
| 304 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 305 |
+
β
|
| 306 |
+
βΌ
|
| 307 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 308 |
+
β TECH 11. Indic cascade (if user spoke Hinglish) β
|
| 309 |
+
β ------- β
|
| 310 |
+
β Β· Sarvam-M translates English reply β Hinglish β
|
| 311 |
+
β Β· Gate A: regex check that digits/citations/currency preserved β
|
| 312 |
+
β Β· Gate B: Llama-4 Maverick LLM-judge for semantic preservation β
|
| 313 |
+
β Β· Gate C: back-translation cosine β₯ 0.80 β
|
| 314 |
+
β Β· Any gate fails β fall back to English reply β
|
| 315 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 316 |
+
β
|
| 317 |
+
βΌ
|
| 318 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 319 |
+
β TECH 12. TTS synthesis (if return_audio=true) β
|
| 320 |
+
β ------- β
|
| 321 |
+
β Β· tts_preprocess() expands acronyms (PED β pre-existing disease) + β
|
| 322 |
+
β strips markdown β
|
| 323 |
+
β Β· Sarvam Bulbul v2 synthesizes β base64 WAV β
|
| 324 |
+
β Β· Bundled into ChatResponse alongside reply_text β
|
| 325 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 326 |
+
β
|
| 327 |
+
βΌ
|
| 328 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 329 |
+
β TECH 13. Response delivered β
|
| 330 |
+
β ------- β
|
| 331 |
+
β Β· ChatResponse{reply_text, citations[], audio_base64, brain_used, β
|
| 332 |
+
β profile_updates, faithfulness_passed, blocked} β
|
| 333 |
+
β Β· Frontend renders text β plays audio β updates profileCompleteness β
|
| 334 |
+
β Β· localStorage persists chat history β
|
| 335 |
+
β Β· log_turn() writes to logs/turns.jsonl β
|
| 336 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 337 |
+
```
|
| 338 |
+
|
| 339 |
+
---
|
| 340 |
+
|
| 341 |
+
## 4. Exhaustive tech architecture
|
| 342 |
+
|
| 343 |
+
### 4.1 System diagram
|
| 344 |
+
|
| 345 |
+
```
|
| 346 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 347 |
+
β Next.js 14 (Vercel-ready) β
|
| 348 |
+
β Β· App Router Β· Tailwind Β· shadcn/ui β
|
| 349 |
+
β Β· Push-to-talk via MediaRecorder Β· localStorage persistence β
|
| 350 |
+
β Β· 5 view tabs: chat | marketplace | premium | profile | admin β
|
| 351 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 352 |
+
β HTTPS
|
| 353 |
+
β /api/chat Β· /api/transcribe Β· /api/profile
|
| 354 |
+
β /api/policies/* Β· /api/admin/* (IP-gated)
|
| 355 |
+
βΌ
|
| 356 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 357 |
+
β FastAPI backend (HF Spaces / Render) β
|
| 358 |
+
β ββββββββββββββββ ββββββββββββββββββββββ ββββββββββββββββββββββ β
|
| 359 |
+
β β STT β β ORCHESTRATOR β β TTS β β
|
| 360 |
+
β β Sarvam βββββΆβ - intent classify βββββΆβ Sarvam Bulbul v2 β β
|
| 361 |
+
β β Saarika v2.5 β β - profile extract β β (acronym pre-exp) β β
|
| 362 |
+
β ββββββββββββββββ β - retrieve β ββββββββββββββββββββββ β
|
| 363 |
+
β β - brain router β β
|
| 364 |
+
β β - 4-gate verifier β β
|
| 365 |
+
β β - cross-check retryβ β
|
| 366 |
+
β β - Indic cascade β β
|
| 367 |
+
β β (3 drift checks) β β
|
| 368 |
+
β βββββββββββ¬βββββββββββ β
|
| 369 |
+
β β β
|
| 370 |
+
β βββββββββββββββββββββββββΌβββββββββββββββββββββββββββ β
|
| 371 |
+
β βΌ βΌ βΌ β
|
| 372 |
+
β ββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββββββ β
|
| 373 |
+
β β STRUCTURED β β VECTOR STORE β β NIM BRAIN ROUTER β β
|
| 374 |
+
β β DuckDB β β Chroma 0.5.20 β β V4-Pro (heavy) β β
|
| 375 |
+
β β 48 fields β β BGE-small (384d) β β V4-Flash (fast) β β
|
| 376 |
+
β β per policy β β 800/120 chunk β β Llama-4 Maverick β β
|
| 377 |
+
β ββββββββββββββββ β +profile chunk β β (judge + xcheck β β
|
| 378 |
+
β β per session β β + Indic gates) β β
|
| 379 |
+
β ββββββββββββββββββββ β single NIM key β β
|
| 380 |
+
β β 40 req/min Β· $0 β β
|
| 381 |
+
β ββββββββββββββββββββββββ β
|
| 382 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 383 |
+
β
|
| 384 |
+
β (build time only)
|
| 385 |
+
βΌ
|
| 386 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 387 |
+
β INGEST (rag/ingest.py + rag/extract.py + tools/*) β
|
| 388 |
+
β pdfplumber β 800-tok chunks β BGE embed β Chroma β
|
| 389 |
+
β NIM V4-Pro structured extract β 48-field Pydantic schema β
|
| 390 |
+
β Self-critique β confidence_pct per field β
|
| 391 |
+
ββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 392 |
+
β
|
| 393 |
+
βΌ
|
| 394 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 395 |
+
β 208 source PDFs in HF Dataset rohitsar567/insurance-bot-data β
|
| 396 |
+
β Β· 190 product PDFs across 19 insurers β
|
| 397 |
+
β Β· 18 regulatory PDFs (IRDAI master circulars, Insurance Act, etc.) β
|
| 398 |
+
β Β· Playwright same-origin fetch past Akamai for irdai.gov.in β
|
| 399 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 400 |
+
```
|
| 401 |
+
|
| 402 |
+
### 4.2 Components in one line each
|
| 403 |
+
|
| 404 |
+
| Component | Path | Role |
|
| 405 |
+
|---|---|---|
|
| 406 |
+
| **Frontend** | `frontend/` | Next.js 14 App Router; chat + marketplace + premium + profile + admin tabs |
|
| 407 |
+
| **API gateway** | `backend/main.py` | FastAPI + Pydantic; OpenAPI auto-served; `openapi-typescript` codegens FE types |
|
| 408 |
+
| **Orchestrator** | `backend/orchestrator.py` | Intent classify β profile-extract β retrieve β brain router β 4-gate faithfulness β cross-check retry β Indic cascade |
|
| 409 |
+
| **Faithfulness verifier** | `backend/faithfulness.py` | Retrieval floor + citation integrity + numeric grounding + LLM-judge |
|
| 410 |
+
| **Indic cascade** | `backend/translator.py`, `backend/translation_check.py` | Sarvam-M translates in & out; 3 drift gates |
|
| 411 |
+
| **Profile extractor** | `backend/profile_extractor.py` | Lightweight LLM extracts profile updates from free-form messages |
|
| 412 |
+
| **Profile RAG** | `backend/profile_rag.py` | Profile becomes a single Chroma chunk (`doc_type='profile'`) per session |
|
| 413 |
+
| **Persona** | `backend/persona.py` | System prompt; `build_messages()` injects profile + view_context |
|
| 414 |
+
| **Session state** | `backend/session_state.py` | Per-session profile, fact-find awaiting state, disk-backed JSON |
|
| 415 |
+
| **Retrieval** | `rag/retrieve.py` | Chroma cosine search + profile boost + regulatory second-pass |
|
| 416 |
+
| **Structured extraction** | `rag/extract.py` | NIM V4-Pro JSON extraction over 48-field Pydantic schema |
|
| 417 |
+
| **Scorecard** | `backend/scorecard.py` | Pure Python; 24 of 48 fields β 6 sub-scores β AβF |
|
| 418 |
+
| **Admin** | `backend/admin.py` | LLM health, chain reorder, force-fresh probe; IP-gated |
|
| 419 |
+
| **Eval** | `eval/` | Gold Q&A pipelines + NIM Llama-4 Maverick grader |
|
| 420 |
+
| **Knowledge base** | `kb/` | 224 markdown policy sheets + scorecard + reviews + premiums + audit trail |
|
| 421 |
+
|
| 422 |
+
### 4.3 Model stack ([ADR-019](docs/60-decisions/ADR-019-nim-single-provider-consolidation.md))
|
| 423 |
+
|
| 424 |
+
| Role | Model | Provider | Why |
|
| 425 |
+
|---|---|---|---|
|
| 426 |
+
| STT | Saarika v2.5 | Sarvam | Best Indian-accent ASR |
|
| 427 |
+
| TTS | Bulbul v2 (`anushka` voice) | Sarvam | Best Hinglish TTS |
|
| 428 |
+
| Indic translation | Sarvam-M | Sarvam | Best Hindi/Hinglish translation cascade |
|
| 429 |
+
| Heavy brain | DeepSeek-V4-Pro (1.6T / 49B MoE, 1M context) | NIM | Frontier reasoning; beats Opus-4.6 + GPT-5.4 on SimpleQA-Verified |
|
| 430 |
+
| Fast brain | DeepSeek-V4-Flash (284B / 13B MoE, 1M context) | NIM | Lower TTFT for voice; still frontier-tier |
|
| 431 |
+
| Judge / cross-check | Llama-4 Maverick (400B / 17B MoE) | NIM | **Different family from DeepSeek** β non-circular grading |
|
| 432 |
+
| Embeddings | BGE-small-en-v1.5 (384d) | Local | $0; no network |
|
| 433 |
+
| Vector store | Chroma 0.5.20 | Local | Single-file sqlite + HNSW |
|
| 434 |
+
| Structured store | DuckDB 1.1.3 | Local | Single-file; SQL filters for marketplace |
|
| 435 |
+
|
| 436 |
+
### 4.4 Major design decisions (the short list)
|
| 437 |
+
|
| 438 |
+
| Decision | Choice | Detail |
|
| 439 |
+
|---|---|---|
|
| 440 |
+
| Scope shape | Vertical slice, one category | [ADR-001](docs/60-decisions/ADR-001-vertical-slice-scope.md) |
|
| 441 |
+
| Category | Health | [ADR-002](docs/60-decisions/ADR-002-health-category-vertical.md) |
|
| 442 |
+
| Corpus origin | Curated, not user-upload | [ADR-003](docs/60-decisions/ADR-003-curated-corpus.md) |
|
| 443 |
+
| Retrieval | Hybrid structured + vector | [ADR-004](docs/60-decisions/ADR-004-hybrid-structured-vector.md) |
|
| 444 |
+
| Frontend stack | Next.js 14 + FastAPI | [ADR-005](docs/60-decisions/ADR-005-nextjs-fastapi-frontend.md) |
|
| 445 |
+
| Pricing | Illustrative, never quote | [ADR-007](docs/60-decisions/ADR-007-illustrative-pricing.md) |
|
| 446 |
+
| Persona | Consultative advisor (IFA) | [ADR-008](docs/60-decisions/ADR-008-consultative-advisor-persona.md) |
|
| 447 |
+
| Embeddings | Local BGE-small | [ADR-011](docs/60-decisions/ADR-011-bge-local-embeddings.md) |
|
| 448 |
+
| LLM provider | NIM single provider | [ADR-019](docs/60-decisions/ADR-019-nim-single-provider-consolidation.md) |
|
| 449 |
+
| Code vs data | Two-repo + HF Dataset | [ADR-020](docs/60-decisions/ADR-020-code-data-split-hf-dataset.md) |
|
| 450 |
+
| View-aware chat | system prompt injection | [ADR-021](docs/60-decisions/ADR-021-view-aware-system-prompt.md) |
|
| 451 |
+
| Profile updates in chat | LLM extractor | [ADR-022](docs/60-decisions/ADR-022-conversational-profile-updates.md) |
|
| 452 |
+
| Admin panel | IP+password gated, in-app tab | [ADR-023](docs/60-decisions/ADR-023-admin-panel-ip-gated.md) |
|
| 453 |
+
| Resilience | Triple-mirror code + data | [ADR-024](docs/60-decisions/ADR-024-triple-mirror-code-and-data.md) |
|
| 454 |
+
|
| 455 |
+
Every D-NNN in the legacy decisions log is now a stand-alone ADR β see [`docs/60-decisions/README.md`](docs/60-decisions/README.md) for the full 24-entry index.
|
| 456 |
+
|
| 457 |
+
---
|
| 458 |
+
|
| 459 |
+
## 5. Data architecture
|
| 460 |
+
|
| 461 |
+
### 5.1 The corpus (208 documents)
|
| 462 |
+
|
| 463 |
+
| Type | Count | Source | Notes |
|
| 464 |
+
|---|---|---|---|
|
| 465 |
+
| Product PDFs | 190 | 19 insurers' public websites | Wordings + Brochures + CIS |
|
| 466 |
+
| Regulatory PDFs | 18 | irdai.gov.in, indiacode.nic.in, others | Playwright rescue past Akamai |
|
| 467 |
+
| Structured extractions (JSON) | 285 | NIM V4-Pro extraction | 48-field Pydantic schema |
|
| 468 |
+
| Vector chunks (Chroma) | ~12,000 | BGE-small @ 800/120 | One sqlite + HNSW binaries |
|
| 469 |
+
| Policy markdown sheets | 224 | Generated from extractions | One per policy_id in `kb/policies/` |
|
| 470 |
+
|
| 471 |
+
19 insurers: Star Health, HDFC ERGO, Niva Bupa, Care Health, ICICI Lombard, Bajaj Allianz, New India Assurance, Aditya Birla, Tata AIG, ManipalCigna, SBI General, Acko, IFFCO Tokio, Cholamandalam MS, Go Digit, Reliance General, Royal Sundaram, Oriental Insurance, National Insurance.
|
| 472 |
+
|
| 473 |
+
### 5.2 Ingestion pipeline
|
| 474 |
+
|
| 475 |
+
```
|
| 476 |
+
PDF (rag/corpus/<insurer>/<policy>__<doctype>.pdf)
|
| 477 |
+
β
|
| 478 |
+
βΌ
|
| 479 |
+
rag/extract.py β rag/extracted/<policy_id>.json (48-field structured)
|
| 480 |
+
β (NIM V4-Pro with Pydantic schema)
|
| 481 |
+
β
|
| 482 |
+
βΌ
|
| 483 |
+
rag/ingest.py β rag/vectors/chroma.sqlite3 (text chunks + embeddings)
|
| 484 |
+
β + HNSW binary files in vectors/ (800-token chunks, 120 overlap)
|
| 485 |
+
β
|
| 486 |
+
βΌ
|
| 487 |
+
data/policy_facts/<policy_id>.json (hand-curated facts for marketplace UI)
|
| 488 |
+
data/premiums/illustrative_premiums.json (anchor table for premium calculator)
|
| 489 |
+
data/reviews/<insurer-slug>.json (IRDAI complaints/10K + sentiment roll-ups)
|
| 490 |
+
β
|
| 491 |
+
βΌ
|
| 492 |
+
kb/policies/<policy_id>.md (markdown writeup; regen via rag/build_kb.py)
|
| 493 |
+
```
|
| 494 |
+
|
| 495 |
+
Full per-stage detail: [`docs/20-data-pipeline/ingestion-policy.md`](docs/20-data-pipeline/ingestion-policy.md).
|
| 496 |
+
|
| 497 |
+
### 5.3 Storage topology
|
| 498 |
+
|
| 499 |
+
| Storage | What lives there | Quota |
|
| 500 |
+
|---|---|---|
|
| 501 |
+
| HF Space repo (origin) | Code only (`backend/`, `frontend/`, `rag/*.py`, `eval/`, `kb/`, `data/`, configs) | 1 GB free tier |
|
| 502 |
+
| HF Dataset | Heavy data: `rag/corpus/`, `rag/vectors/`, `rag/extracted/` | 50 GB free tier (we use ~500 MB) |
|
| 503 |
+
| GitHub code repo | Mirror of HF Space | Standard GitHub |
|
| 504 |
+
| GitHub data repo (LFS) | Mirror of HF Dataset | 1 GB LFS + 1 GB/mo bandwidth |
|
| 505 |
+
| Local Mac | Working tree + `rag/_hf_dataset_backup/` for offline recovery | Filesystem |
|
| 506 |
+
|
| 507 |
+
Runtime path: Docker container running on HF Space `snapshot_download`s the dataset at build time β serves everything from local container disk β end users never hit GitHub or the dataset at request time.
|
| 508 |
+
|
| 509 |
+
---
|
| 510 |
+
|
| 511 |
+
## 6. Quality & safety
|
| 512 |
+
|
| 513 |
+
### 6.1 The four faithfulness gates ([`backend/faithfulness.py`](backend/faithfulness.py))
|
| 514 |
+
|
| 515 |
+
Every reply, every turn:
|
| 516 |
+
|
| 517 |
+
| Gate | Function | Blocks if |
|
| 518 |
+
|---|---|---|
|
| 519 |
+
| 1. Retrieval floor | `_gate_retrieval_floor` | Top retrieval score < 0.30 β nothing to ground in |
|
| 520 |
+
| 2. Citation integrity | `_gate_citation_integrity` | Reply cites a policy_name not in retrieved chunks |
|
| 521 |
+
| 3. Numeric grounding (regex) | `_gate_numeric_grounding` | Any βΉ/%/days/months/years in reply doesn't appear in chunks |
|
| 522 |
+
| 4. LLM-judge | `_gate_llm_judge` | NIM Llama-4 Maverick (different family from brain) flags unsupported claim |
|
| 523 |
+
|
| 524 |
+
If any gate (other than Gate 1) fails, the **cross-check retry** re-runs the same prompt on Llama-4 Maverick. If that passes its gates, the rescued reply ships with `crosscheck-rescued-by-maverick` brain tag. Otherwise β safe refusal + log to `logs/hallucinations.jsonl`.
|
| 525 |
+
|
| 526 |
+
### 6.2 Indic drift gates ([`backend/translation_check.py`](backend/translation_check.py))
|
| 527 |
+
|
| 528 |
+
When the user speaks Hinglish:
|
| 529 |
+
|
| 530 |
+
| Gate | Method |
|
| 531 |
+
|---|---|
|
| 532 |
+
| A β Regex anchors | Every digit / currency / citation in the English reply must appear in the Indic reply |
|
| 533 |
+
| B β LLM-judge | NIM Llama-4 Maverick scores semantic faithfulness across languages |
|
| 534 |
+
| C β Back-translation cosine | Sarvam back-translates Hinglish β English; cosine vs original β₯ 0.80 |
|
| 535 |
|
| 536 |
+
Any gate fails β revert to English reply (correct facts even if not preferred language).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 537 |
|
| 538 |
+
### 6.3 Eval methodology
|
| 539 |
+
|
| 540 |
+
Gold Q&A in three pipelines:
|
| 541 |
+
|
| 542 |
+
- **A β Auto-templated:** 15 templates Γ ~80 policies = 1,100 candidates, ~300 committed.
|
| 543 |
+
- **B β LLM-drafted nuanced:** V4-Pro drafts 5 buyer-style multi-clause questions per top policy.
|
| 544 |
+
- **C β Adversarial:** 30-40 hand-written out-of-corpus / out-of-policy-type / Hinglish / multi-policy.
|
| 545 |
+
|
| 546 |
+
Grader: NIM Llama-4 Maverick (non-circular β different family from the DeepSeek brain). Source: [`eval/generate_gold.py`](eval/generate_gold.py), [`eval/run.py`](eval/run.py).
|
| 547 |
+
|
| 548 |
+
Honest current numbers (2026-05-12 run on 25 questions):
|
| 549 |
+
|
| 550 |
+
| Metric | Value | Comment |
|
| 551 |
+
|---|---|---|
|
| 552 |
+
| Factual accuracy | 40.0% | Two structural causes β see [`SUBMISSION.md`](SUBMISSION.md) Β§4.3 |
|
| 553 |
+
| Citation accuracy | 50.0% | Same |
|
| 554 |
+
| Refusal precision | 44.4% | Same |
|
| 555 |
+
| Blocked by faithfulness | 12 / 25 | Gates working; aggressively biased toward refusal |
|
| 556 |
+
|
| 557 |
+
The headline number reflects an aggressive gate posture (refuse > mis-cite) β the safe failure mode in BFSI. v1.1 work: soften Gate 3 regex; null-skip Pipeline A on missing source fields.
|
| 558 |
+
|
| 559 |
+
---
|
| 560 |
+
|
| 561 |
+
## 7. Document ecosystem guide
|
| 562 |
+
|
| 563 |
+
The repo's documentation lives in `docs/`, organised into 8 numbered buckets. Numeric prefixes sort buckets in reading order; files inside each bucket use `kebab-case` for URL safety and grep-friendliness.
|
| 564 |
+
|
| 565 |
+
```
|
| 566 |
+
docs/
|
| 567 |
+
βββ 00-overview/ β "what is this and why does it exist"
|
| 568 |
+
βββ 10-architecture/ β "how it works" (deep dives)
|
| 569 |
+
βββ 20-data-pipeline/ β "where the knowledge comes from"
|
| 570 |
+
βββ 30-engineering/ β "how the code is laid out"
|
| 571 |
+
βββ 40-evaluation/ β "how we measured success"
|
| 572 |
+
βββ 50-operations/ β "how to run / maintain / debug"
|
| 573 |
+
βββ 60-decisions/ β Architecture Decision Records (ADRs 001-024)
|
| 574 |
+
βββ 70-reference/ β Schemas, glossary, indexes
|
| 575 |
+
```
|
| 576 |
+
|
| 577 |
+
### 7.1 Per-bucket guide
|
| 578 |
+
|
| 579 |
+
| Bucket | What's there now | What to read for what |
|
| 580 |
+
|---|---|---|
|
| 581 |
+
| `00-overview/` | [`problem-statement.md`](docs/00-overview/problem-statement.md), [`roadmap.md`](docs/00-overview/roadmap.md) | Start here for product context, requirements, success criteria, v2 plan |
|
| 582 |
+
| `10-architecture/` | [`system-overview.md`](docs/10-architecture/system-overview.md), [`stack-rationale.md`](docs/10-architecture/stack-rationale.md), [`safety-architecture.md`](docs/10-architecture/safety-architecture.md), [`scoring-methodology.md`](docs/10-architecture/scoring-methodology.md), [`scoring-knowledge-graph.md`](docs/10-architecture/scoring-knowledge-graph.md), [`scoring-tie-breaker-rubric.md`](docs/10-architecture/scoring-tie-breaker-rubric.md) | How the system is built; the why behind each stack choice; how scoring works |
|
| 583 |
+
| `20-data-pipeline/` | [`ingestion-policy.md`](docs/20-data-pipeline/ingestion-policy.md), [`information-source-map.md`](docs/20-data-pipeline/information-source-map.md) | How PDFs become chunks; where every source URL lives + audit status |
|
| 584 |
+
| `30-engineering/` | [`needs-analysis-flow.md`](docs/30-engineering/needs-analysis-flow.md), [`discovery-script.md`](docs/30-engineering/discovery-script.md) | How the fact-find loop works; discovery script for new contributors |
|
| 585 |
+
| `40-evaluation/` | [`eval-methodology.md`](docs/40-evaluation/eval-methodology.md) | Gold-Q&A design, grader choice, results interpretation |
|
| 586 |
+
| `50-operations/` | (operational runbooks β to be filled) | How to run, deploy, debug |
|
| 587 |
+
| `60-decisions/` | 24 ADRs + [`README.md`](docs/60-decisions/README.md) index + `legacy-decisions-monolith.md` archive | The full decision history with alternatives and supersession tracking |
|
| 588 |
+
| `70-reference/` | (schemas + glossary β to be filled) | BFSI terms, insurer slug map, citation grammar |
|
| 589 |
+
|
| 590 |
+
### 7.2 Root-level documents
|
| 591 |
+
|
| 592 |
+
| File | Purpose |
|
| 593 |
+
|---|---|
|
| 594 |
+
| `README.md` (this file) | Master entry point + executive bible |
|
| 595 |
+
| `SUBMISSION.md` | Formal Sarvam takehome writeup; the polished narrative for reviewers |
|
| 596 |
+
| `ARCHITECTURE.md` | One-page diagram + index into `docs/10-architecture/` |
|
| 597 |
+
| `QUICKSTART.md` | Run-locally-in-5-minutes guide (see Β§8 below) |
|
| 598 |
+
| `Dockerfile` | Production build β pulls dataset at build time, serves Next.js + FastAPI |
|
| 599 |
+
| `entrypoint.sh` | Docker entrypoint β boots both processes |
|
| 600 |
+
| `requirements.txt` | Python deps (pinned) |
|
| 601 |
+
| `render.yaml` | Legacy Render config (HF Space is current production) |
|
| 602 |
+
|
| 603 |
+
### 7.3 Navigation shortcuts
|
| 604 |
+
|
| 605 |
+
- **Reviewer with 5 minutes:** read [SUBMISSION.md](SUBMISSION.md) Β§1, Β§3, Β§6 (limits).
|
| 606 |
+
- **Reviewer with 20 minutes:** read this README end-to-end.
|
| 607 |
+
- **Engineer joining the project:** read [`docs/00-overview/problem-statement.md`](docs/00-overview/problem-statement.md) β [`docs/10-architecture/system-overview.md`](docs/10-architecture/system-overview.md) β [`docs/60-decisions/README.md`](docs/60-decisions/README.md) β trace `backend/orchestrator.py`.
|
| 608 |
+
- **Compliance auditor:** read [`docs/10-architecture/safety-architecture.md`](docs/10-architecture/safety-architecture.md) β `logs/hallucinations.jsonl` β [`kb/AUDIT_TRAIL.md`](kb/AUDIT_TRAIL.md).
|
| 609 |
+
- **Replicating the data pipeline:** read [`docs/20-data-pipeline/ingestion-policy.md`](docs/20-data-pipeline/ingestion-policy.md) β trace `rag/extract.py` + `rag/ingest.py`.
|
| 610 |
+
|
| 611 |
+
---
|
| 612 |
+
|
| 613 |
+
## 8. Quick start & local development
|
| 614 |
+
|
| 615 |
+
### 8.1 Prerequisites
|
| 616 |
+
|
| 617 |
+
- macOS or Linux
|
| 618 |
+
- Python 3.11+
|
| 619 |
+
- Node.js 20+
|
| 620 |
+
- ~5 GB free disk for venv + node_modules + dataset
|
| 621 |
+
|
| 622 |
+
### 8.2 Clone & install
|
| 623 |
|
| 624 |
```bash
|
| 625 |
+
# Code
|
| 626 |
git clone https://github.com/rohitsar567/insurance-sales-bot.git
|
| 627 |
cd insurance-sales-bot
|
| 628 |
+
|
| 629 |
+
# Data (for local-only runs β Docker pulls automatically)
|
| 630 |
+
git clone https://github.com/rohitsar567/insurance-sales-bot-data.git rag/_hf_dataset_backup
|
| 631 |
+
# OR via huggingface_hub:
|
| 632 |
+
# python -c "from huggingface_hub import snapshot_download; \
|
| 633 |
+
# snapshot_download(repo_id='rohitsar567/insurance-bot-data', repo_type='dataset', \
|
| 634 |
+
# local_dir='rag/_hf_dataset_backup')"
|
| 635 |
+
|
| 636 |
+
# Move data into place for the bot
|
| 637 |
+
cp -R rag/_hf_dataset_backup/rag/* rag/ # corpus/, extracted/, vectors/
|
| 638 |
+
```
|
| 639 |
+
|
| 640 |
+
### 8.3 Configure secrets
|
| 641 |
+
|
| 642 |
+
```bash
|
| 643 |
+
cp .env.example .env
|
| 644 |
+
# Edit .env and fill in the 8 keys (see docs/60-decisions/ADR-010-secret-handling.md):
|
| 645 |
+
# SARVAM_API_KEY, VOYAGE_API_KEY, NVIDIA_NIM_API_KEY, HF_TOKEN,
|
| 646 |
+
# ADMIN_PASSWORD, ADMIN_IP_ALLOWLIST, GROQ_API_KEY, OPENROUTER_API_KEY
|
| 647 |
+
chmod 600 .env
|
| 648 |
```
|
| 649 |
|
| 650 |
+
### 8.4 Run backend
|
| 651 |
+
|
| 652 |
+
```bash
|
| 653 |
+
# Recommended: uv-managed venv
|
| 654 |
+
uv venv ~/.cache/uv-venvs/insurance-sales-bot --python 3.11
|
| 655 |
+
ln -s ~/.cache/uv-venvs/insurance-sales-bot .venv
|
| 656 |
+
uv pip install -r requirements.txt
|
| 657 |
|
| 658 |
+
uvicorn backend.main:app --reload --port 7860
|
| 659 |
```
|
| 660 |
+
|
| 661 |
+
### 8.5 Run frontend
|
| 662 |
+
|
| 663 |
+
```bash
|
| 664 |
+
cd frontend
|
| 665 |
+
npm install
|
| 666 |
+
npm run dev # http://localhost:3000
|
| 667 |
```
|
| 668 |
|
| 669 |
+
### 8.6 Verify
|
| 670 |
|
| 671 |
+
- Frontend at `localhost:3000` should show the chat UI.
|
| 672 |
+
- `localhost:7860/api/health` should return `{"status":"ok", "providers_ok": {"sarvam": true, "nvidia_nim": true}}`.
|
| 673 |
+
- `localhost:7860/api/coverage` should return 255 policies indexed.
|
|
|
|
|
|
|
|
|
|
| 674 |
|
| 675 |
+
---
|
| 676 |
|
| 677 |
+
## 9. Deployment & storage topology
|
| 678 |
|
| 679 |
+
### 9.1 Triple-mirror layout
|
| 680 |
|
| 681 |
+
| Layer | Code | Data |
|
| 682 |
+
|---|---|---|
|
| 683 |
+
| **HuggingFace** | Space `rohitsar567/InsuranceBot` (origin) | Dataset `rohitsar567/insurance-bot-data` |
|
| 684 |
+
| **GitHub** | Repo `rohitsar567/insurance-sales-bot` | Repo `rohitsar567/insurance-sales-bot-data` (Git LFS) |
|
| 685 |
+
| **Local Mac** | `~/Developer/Insurance Sales Bot/` working tree | `~/Developer/Insurance Sales Bot/rag/_hf_dataset_backup/` |
|
|
|
|
| 686 |
|
| 687 |
+
### 9.2 Push fan-out
|
| 688 |
+
|
| 689 |
+
```bash
|
| 690 |
+
# After a commit:
|
| 691 |
+
git push origin main # β HF Space (auto-deploys via Docker rebuild)
|
| 692 |
+
git push github main # β GitHub mirror
|
| 693 |
+
|
| 694 |
+
# For data updates (rare):
|
| 695 |
+
cd rag/_hf_dataset_backup # or wherever the data lives
|
| 696 |
+
git push origin main # β HF Dataset
|
| 697 |
+
git push github main # β GitHub LFS data repo
|
| 698 |
+
```
|
| 699 |
+
|
| 700 |
+
Verification: `git rev-list --count main...origin/main` and `β¦main...github/main` should both equal 0.
|
| 701 |
+
|
| 702 |
+
### 9.3 Production runtime path
|
| 703 |
+
|
| 704 |
+
```
|
| 705 |
+
End user
|
| 706 |
+
β HTTPS
|
| 707 |
+
βΌ
|
| 708 |
+
rohitsar567-insurancebot.hf.space (HF Space Docker container)
|
| 709 |
+
β
|
| 710 |
+
β Container has data baked in (snapshot_download at build time)
|
| 711 |
+
βΌ
|
| 712 |
+
FastAPI + Next.js serve from local container disk; calls go out to:
|
| 713 |
+
- NIM (integrate.api.nvidia.com) for brain + judge + extractor
|
| 714 |
+
- Sarvam (api.sarvam.ai) for STT + TTS + Indic translation
|
| 715 |
+
```
|
| 716 |
+
|
| 717 |
+
End users **never** touch GitHub. The data repo is for portfolio mirroring + disaster recovery β not a runtime dependency.
|
| 718 |
+
|
| 719 |
+
### 9.4 Admin operations
|
| 720 |
+
|
| 721 |
+
| Operation | Command / surface |
|
| 722 |
+
|---|---|
|
| 723 |
+
| Rotate any API key | Edit local `.env` β `python tools/set_hf_secrets.py` β Space auto-restarts |
|
| 724 |
+
| Add admin IP | Edit `ADMIN_IP_ALLOWLIST` in `.env` (comma-separated) β `set_hf_secrets.py` |
|
| 725 |
+
| Reorder LLM chain | In-app **Admin Β· Access panel** tab β "Force fresh probe" β drag-reorder |
|
| 726 |
+
| Force fresh model probe | Same tab β "Force fresh probe (slow)" |
|
| 727 |
+
| Disaster recovery (HF Dataset lost) | Re-upload from local `rag/_hf_dataset_backup/` via `huggingface_hub.HfApi.upload_folder` |
|
| 728 |
+
| Cold start | First request after ~15 min idle takes ~50s; subsequent are fast |
|
| 729 |
+
|
| 730 |
+
---
|
| 731 |
+
|
| 732 |
+
## 10. Rebuild from scratch
|
| 733 |
+
|
| 734 |
+
This section is written so a fresh **Claude Code** session pointed at an empty directory could rebuild the entire project end-to-end. The goal is to make the project reproducible from this document plus the linked dependencies.
|
| 735 |
+
|
| 736 |
+
### 10.1 Required external accounts
|
| 737 |
+
|
| 738 |
+
1. **Sarvam** β STT, TTS, Indic translation. Get API key at dashboard.sarvam.ai.
|
| 739 |
+
2. **NVIDIA NIM** β brain + judge + extractor. Get `nvapi-...` key at build.nvidia.com.
|
| 740 |
+
3. **HuggingFace** β Space + Dataset hosting. Get write token at huggingface.co/settings/tokens.
|
| 741 |
+
4. **GitHub** β mirror code + data. Personal Access Token with `repo` scope.
|
| 742 |
+
5. **Voyage / Groq / OpenRouter** β optional fallback (kept for flexibility).
|
| 743 |
+
|
| 744 |
+
### 10.2 Build sequence
|
| 745 |
+
|
| 746 |
+
1. **Corpus acquisition** β use `tools/` agent crawl + Playwright fallback (see [ADR-017](docs/60-decisions/ADR-017-irdai-corpus-playwright-rescue.md)) to fetch 19 insurers' PDFs + 18 regulatory PDFs into `rag/corpus/`.
|
| 747 |
+
|
| 748 |
+
2. **Structured extraction** β `rag/extract.py` runs NIM V4-Pro over each PDF with the 48-field Pydantic schema. Output to `rag/extracted/<policy_id>.json`.
|
| 749 |
+
|
| 750 |
+
3. **Embedding + indexing** β `rag/ingest.py` chunks each PDF at 800/120, embeds with BGE-small-en-v1.5, writes to `rag/vectors/chroma.sqlite3` + HNSW binaries.
|
| 751 |
+
|
| 752 |
+
4. **Data publish** β push `rag/corpus/`, `rag/extracted/`, `rag/vectors/` to HF Dataset `rohitsar567/insurance-bot-data`.
|
| 753 |
+
|
| 754 |
+
5. **Knowledge base regeneration** β `python -m rag.build_kb` regenerates 224 markdown sheets in `kb/policies/` from extracted JSONs.
|
| 755 |
+
|
| 756 |
+
6. **Backend** β implement FastAPI app with the endpoints listed in Β§4.2. Key files: `backend/main.py`, `backend/orchestrator.py`, `backend/faithfulness.py`, `backend/persona.py`, `backend/translator.py`, `backend/translation_check.py`, `backend/profile_extractor.py`, `backend/profile_rag.py`, `backend/session_state.py`, `backend/scorecard.py`, `backend/admin.py`, `backend/providers/*`.
|
| 757 |
+
|
| 758 |
+
7. **Frontend** β Next.js 14 App Router. Tailwind + shadcn/ui. Key file: `frontend/src/app/page.tsx` (orchestrates all views: chat, marketplace, premium, profile, admin). Voice via MediaRecorder. Persistent state via localStorage.
|
| 759 |
+
|
| 760 |
+
8. **Eval harness** β `eval/generate_gold.py` builds the gold Q&A. `eval/run.py` grades using NIM Llama-4 Maverick. Output to `eval/results.md`.
|
| 761 |
+
|
| 762 |
+
9. **Deploy** β Dockerfile bundles backend + frontend (Next.js standalone) into one image. `snapshot_download` pulls data at build time. Push to HF Space β auto-deploys.
|
| 763 |
+
|
| 764 |
+
10. **Mirror to GitHub** β `git remote add github https://github.com/rohitsar567/insurance-sales-bot.git` + push. Same for data repo with Git LFS.
|
| 765 |
+
|
| 766 |
+
### 10.3 Critical files to seed first (priority order)
|
| 767 |
+
|
| 768 |
+
A new Claude Code session should ingest these to bootstrap understanding:
|
| 769 |
+
|
| 770 |
+
1. **This README** β entire project context.
|
| 771 |
+
2. **[`SUBMISSION.md`](SUBMISSION.md)** β formal narrative + eval results.
|
| 772 |
+
3. **[`docs/60-decisions/`](docs/60-decisions/)** β 24 ADRs covering every meaningful decision.
|
| 773 |
+
4. **[`backend/orchestrator.py`](backend/orchestrator.py)** β the single file that defines a turn.
|
| 774 |
+
5. **[`backend/faithfulness.py`](backend/faithfulness.py)** β the 4-gate verifier.
|
| 775 |
+
6. **[`backend/persona.py`](backend/persona.py)** β the system prompt + message builder.
|
| 776 |
+
7. **[`rag/retrieve.py`](rag/retrieve.py)** β retrieval logic with profile boost.
|
| 777 |
+
8. **[`frontend/src/app/page.tsx`](frontend/src/app/page.tsx)** β full UI orchestration.
|
| 778 |
+
|
| 779 |
+
### 10.4 Hidden gotchas a rebuilder will hit
|
| 780 |
+
|
| 781 |
+
| Gotcha | Where it bites | Fix |
|
| 782 |
+
|---|---|---|
|
| 783 |
+
| IRDAI URLs return Akamai bot-challenge HTML | Initial corpus crawl | Playwright same-origin fetch (ADR-017) |
|
| 784 |
+
| Sarvam-M output cap 2048 tokens truncates JSON | Extraction phase | Use NIM V4-Pro instead (ADR-019) |
|
| 785 |
+
| Voyage 3 RPM free-tier blocks 208-PDF ingest | Embedding phase | Switch to local BGE-small (ADR-011) |
|
| 786 |
+
| Multiple LLM providers' free-tier limits collide on grader | Eval phase | Consolidate to NIM (ADR-019) |
|
| 787 |
+
| HF Space 1 GB cap rejects vector DB | Deploy phase | Split data to HF Dataset (ADR-020) |
|
| 788 |
+
| Chroma sqlite3 (157 MB) exceeds GitHub 100 MB per-file limit | GitHub mirror | Use Git LFS for the data repo (ADR-024) |
|
| 789 |
+
| iCloud-synced `~/Documents/` causes node_modules upload churn | Local dev | Move project to `~/Developer/` (not in iCloud) |
|
| 790 |
+
| Apple Python 3.9 lacks `int | None` syntax | LaunchAgent scripts | Add `from __future__ import annotations` |
|
| 791 |
+
| Admin endpoints return 404 (not 401) for unauthorized callers | Admin panel | Intentional β hides existence; pair IP allowlist with X-Admin-Password header |
|
| 792 |
+
|
| 793 |
+
---
|
| 794 |
|
| 795 |
+
## Footer
|
| 796 |
|
| 797 |
+
**Authored 2026-05-13. Last updated 2026-05-14.**
|
| 798 |
|
| 799 |
+
Live demo: https://rohitsar567-insurancebot.hf.space
|
| 800 |
+
Code: https://github.com/rohitsar567/insurance-sales-bot
|
| 801 |
+
Data: https://github.com/rohitsar567/insurance-sales-bot-data
|
| 802 |
+
Contact: rohitsar567@gmail.com
|
| 803 |
|
| 804 |
+
If you're reviewing this for Sarvam: thank you for your time. Every architectural choice is in [`docs/60-decisions/`](docs/60-decisions/) with alternatives and reasoning. Every refusal in the demo is logged in `logs/hallucinations.jsonl` with the failing gate. The project is structured so a new engineer could ship v1.1 by Friday.
|
|
|
|
|
|
|
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-001: Vertical slice scope, not full platform
|
| 2 |
+
|
| 3 |
+
**Status:** Locked
|
| 4 |
+
**Date:** 2026-05-13
|
| 5 |
+
|
| 6 |
+
## Context
|
| 7 |
+
|
| 8 |
+
The Sarvam takehome assignment had a ~24-hour build window with explainability as an explicit grading criterion. Three scope shapes were on the table:
|
| 9 |
+
|
| 10 |
+
1. Single-document RAG-voice bot over one policy.
|
| 11 |
+
2. Vertical slice β full architecture for one category (Health), built so category expansion is config + data, not code.
|
| 12 |
+
3. Full platform β 300+ policies across all insurance categories.
|
| 13 |
+
|
| 14 |
+
## Decision
|
| 15 |
+
|
| 16 |
+
Build the vertical slice. One category (Health) β but every architectural surface a reviewer cares about is real: hybrid retrieval, schema, voice cascade, citations, evaluation, audit trail, refusal behaviour, scorecard methodology, deployment.
|
| 17 |
+
|
| 18 |
+
## Alternatives considered
|
| 19 |
+
|
| 20 |
+
| Option | Why rejected |
|
| 21 |
+
|---|---|
|
| 22 |
+
| Single-document RAG | Under-signals product vision; reviewer can't see how the system would scale. |
|
| 23 |
+
| Full platform | Over-scopes within 24h; ships rough; quality bar suffers across the board. |
|
| 24 |
+
|
| 25 |
+
## Consequences
|
| 26 |
+
|
| 27 |
+
**Positive:** Demonstrates senior-engineer scoping discipline. Every part of the bot a BFSI buyer would audit (provenance, refusal, eval rigor) is real, not stubbed.
|
| 28 |
+
|
| 29 |
+
**Negative:** Life, Motor, and other categories are not covered. The seven "c-readiness commitments" in `docs/10-architecture/system-overview.md` Β§7 become real v2 work.
|
| 30 |
+
|
| 31 |
+
## Revisit at scale (v2)
|
| 32 |
+
|
| 33 |
+
Category expansion plan in `docs/00-overview/roadmap.md`. Each new category requires: (1) corpus acquisition, (2) per-category schema extension, (3) eval gold set, (4) scorecard sub-score weights. No core code changes.
|
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-002: Health as the v1 vertical category
|
| 2 |
+
|
| 3 |
+
**Status:** Locked
|
| 4 |
+
**Date:** 2026-05-13
|
| 5 |
+
|
| 6 |
+
## Context
|
| 7 |
+
|
| 8 |
+
Given the vertical-slice scope (ADR-001), one insurance category had to be chosen. Candidates: Health, Life, Motor.
|
| 9 |
+
|
| 10 |
+
## Decision
|
| 11 |
+
|
| 12 |
+
**Health insurance.**
|
| 13 |
+
|
| 14 |
+
## Alternatives considered
|
| 15 |
+
|
| 16 |
+
| Category | Why not for v1 |
|
| 17 |
+
|---|---|
|
| 18 |
+
| Life insurance | Harder emotional sale; numerical comparison less clean; smaller buyer-controlled-decision share (term life is the only directly comparable product). |
|
| 19 |
+
| Motor insurance | Price-commoditised; lower BFSI margin; less compelling demo (less depth to compare). |
|
| 20 |
+
|
| 21 |
+
## Why Health
|
| 22 |
+
|
| 23 |
+
1. **Richest structured-attribute surface** β waiting periods, PED handling, sub-limits, network hospitals, claim ratio, geographic spread, restoration benefits, room rent caps, AYUSH coverage, OPD riders. Enough fields to make cross-policy comparison non-trivial.
|
| 24 |
+
2. **Broadest user relevance** β every Indian adult has or considers health insurance.
|
| 25 |
+
3. **Cleanest public corpus** β top 19 insurers all publish policy wordings, brochures, and CIS documents on their public websites.
|
| 26 |
+
4. **Regulator-rich** β IRDAI master circulars define standard exclusions, free-look periods, portability rules. Gives the bot a natural "regulatory overlay" layer (see ADR-017).
|
| 27 |
+
|
| 28 |
+
## Consequences
|
| 29 |
+
|
| 30 |
+
**Positive:** Maximum surface area for demonstrating the architecture in v1.
|
| 31 |
+
|
| 32 |
+
**Negative:** Life and Motor users see "this only covers Health."
|
| 33 |
+
|
| 34 |
+
## Revisit at scale
|
| 35 |
+
|
| 36 |
+
v2 adds Life (term + ULIP + endowment) and Motor (private car + two-wheeler). The 48-field Health schema generalises with ~70% reuse β most additions are category-specific waiting/exclusion fields.
|
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-003: Curated corpus over user-uploaded PDFs
|
| 2 |
+
|
| 3 |
+
**Status:** Locked
|
| 4 |
+
**Date:** 2026-05-13
|
| 5 |
+
|
| 6 |
+
## Context
|
| 7 |
+
|
| 8 |
+
A health-insurance bot needs PDF text to ground its answers. The PDFs can come from either:
|
| 9 |
+
|
| 10 |
+
1. **Curated** β we acquire a corpus once, version it, embed it offline.
|
| 11 |
+
2. **User-uploaded** β every user uploads their own policy doc; the bot extracts on the fly.
|
| 12 |
+
|
| 13 |
+
## Decision
|
| 14 |
+
|
| 15 |
+
**Curated corpus** of 190 policy PDFs (19 insurers) + 18 regulatory PDFs. User-uploads are accepted but routed to an isolated `user_uploads_quarantine` Chroma collection so they never pollute the canonical retrieval.
|
| 16 |
+
|
| 17 |
+
## Alternatives considered
|
| 18 |
+
|
| 19 |
+
| Approach | Why rejected for v1 |
|
| 20 |
+
|---|---|
|
| 21 |
+
| Pure user-upload | Highest input variance (bad scans, password-protected PDFs, partial documents). No cross-policy comparison possible β the bot only knows what the user just uploaded. |
|
| 22 |
+
| Hybrid with shared collection | User uploads polluting shared retrieval would let one bad upload poison answers for every other user. |
|
| 23 |
+
|
| 24 |
+
## Consequences
|
| 25 |
+
|
| 26 |
+
**Positive:**
|
| 27 |
+
|
| 28 |
+
- Removes the largest source of input variance (bad uploads).
|
| 29 |
+
- Enables cross-policy comparison and recommendation queries β the core differentiator.
|
| 30 |
+
- Positions the corpus as a product moat vs. generic "RAG over anything" bots.
|
| 31 |
+
- Provenance is verifiable: every PDF traces to a public insurer URL with HEAD-verified status.
|
| 32 |
+
|
| 33 |
+
**Negative:**
|
| 34 |
+
|
| 35 |
+
- Corpus acquisition is the longest-pole task in the build (acquired via the `tools/` agent crawl + Playwright rescue β see ADR-017).
|
| 36 |
+
- Corpus freshness depends on a manual refresh cadence.
|
| 37 |
+
|
| 38 |
+
## Revisit at scale
|
| 39 |
+
|
| 40 |
+
Same approach, larger corpus. Add scheduled monthly refresh job that re-fetches every URL via Playwright and re-embeds any changed PDF. User-upload quarantine collection remains isolated.
|
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-004: Hybrid structured + vector retrieval
|
| 2 |
+
|
| 3 |
+
**Status:** Locked
|
| 4 |
+
**Date:** 2026-05-13
|
| 5 |
+
|
| 6 |
+
## Context
|
| 7 |
+
|
| 8 |
+
A health-insurance assistant has two qualitatively different query classes:
|
| 9 |
+
|
| 10 |
+
1. **Filter / comparison / recommendation** β "Show me policies with restoration benefit AND PED waiting < 24 months AND room rent β₯ βΉ10k." These need structured fields, not retrieval.
|
| 11 |
+
2. **Free-form Q&A with citations** β "What does Care Supreme say about cataract waiting?" These need unstructured text with clause-level citation.
|
| 12 |
+
|
| 13 |
+
A single retrieval mechanism is wrong for both.
|
| 14 |
+
|
| 15 |
+
## Decision
|
| 16 |
+
|
| 17 |
+
**Hybrid architecture:**
|
| 18 |
+
|
| 19 |
+
| Concern | Store | Used for |
|
| 20 |
+
|---|---|---|
|
| 21 |
+
| Structured fields per policy (48 fields) | **DuckDB** (`rag/policies.duckdb`) | Marketplace filters, scorecard inputs, side-by-side comparison |
|
| 22 |
+
| Free-form text chunks with provenance | **Chroma** (`rag/vectors/chroma.sqlite3`) | Citation-bearing Q&A, regulatory grounding |
|
| 23 |
+
|
| 24 |
+
The two stores are linked by canonical `policy_id` (e.g., `care-health__care-supreme__wordings`).
|
| 25 |
+
|
| 26 |
+
## Alternatives considered
|
| 27 |
+
|
| 28 |
+
| Approach | Why rejected |
|
| 29 |
+
|---|---|
|
| 30 |
+
| Pure RAG | Filter queries become 50-line LLM prompts that hallucinate; comparison can't scale. |
|
| 31 |
+
| Pure structured DB | No clause-level citation; can't answer "what does the policy say about X" for fields not in schema. |
|
| 32 |
+
| Single store with structured-as-metadata | Possible but couples schema evolution to vector index rebuild. |
|
| 33 |
+
|
| 34 |
+
## Consequences
|
| 35 |
+
|
| 36 |
+
**Positive:**
|
| 37 |
+
|
| 38 |
+
- Each query class hits the right store.
|
| 39 |
+
- Schema evolution doesn't trigger expensive re-embedding.
|
| 40 |
+
- DuckDB is single-file, embeddable; no infra burden.
|
| 41 |
+
|
| 42 |
+
**Negative:**
|
| 43 |
+
|
| 44 |
+
- Two stores to keep in sync at ingest time (`rag/ingest.py`).
|
| 45 |
+
- Cross-cutting queries ("policies where wording mentions 'restoration' AND structured.network_hospital_count > 10000") require small bridge code.
|
| 46 |
+
|
| 47 |
+
## Revisit at scale
|
| 48 |
+
|
| 49 |
+
| Pressure | Migration path |
|
| 50 |
+
|---|---|
|
| 51 |
+
| Multi-tenant | DuckDB β Postgres |
|
| 52 |
+
| 10Γ corpus | Chroma β Pinecone or Qdrant (interface stays the same; see `rag/retrieve.py`) |
|
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-005: Next.js 14 + FastAPI (revised from Streamlit)
|
| 2 |
+
|
| 3 |
+
**Status:** Locked (revised mid-build)
|
| 4 |
+
**Date:** 2026-05-13 (revision)
|
| 5 |
+
|
| 6 |
+
## Context
|
| 7 |
+
|
| 8 |
+
The original v1 plan locked Streamlit for the UI (D-005 original). Mid-build, the constraint was unlocked ("use whatever is best"). A re-evaluation surfaced that Streamlit's "this looks like a prototype" perception would undercut the BFSI-deployable-product signal the project is trying to send to reviewers.
|
| 9 |
+
|
| 10 |
+
## Decision
|
| 11 |
+
|
| 12 |
+
**Next.js 14 (App Router) frontend + FastAPI backend.** Vercel for FE auto-deploy; HF Space for BE (`origin`) with full mirroring to GitHub (see ADR-024).
|
| 13 |
+
|
| 14 |
+
## Alternatives considered
|
| 15 |
+
|
| 16 |
+
| Option | Why rejected |
|
| 17 |
+
|---|---|
|
| 18 |
+
| Streamlit (original) | Fast to demo, but signals "prototype" to a BFSI reviewer. Audio streaming and multi-user state awkward. |
|
| 19 |
+
| Gradio | Same prototype perception; ML-research vibe. |
|
| 20 |
+
| Chainlit | Specialised for chat-only UIs; comparison/marketplace panels harder. |
|
| 21 |
+
| Reflex (formerly Pynecone) | Python-only; less ecosystem; smaller community. |
|
| 22 |
+
|
| 23 |
+
## Consequences
|
| 24 |
+
|
| 25 |
+
**Positive:**
|
| 26 |
+
|
| 27 |
+
- Production-pattern stack matches what a deploying customer (bank, insurer) would expect.
|
| 28 |
+
- Server-side rendering via Next.js β fast initial paint.
|
| 29 |
+
- Tailwind + shadcn/ui (ADR-013) gives polished design without bespoke CSS.
|
| 30 |
+
- FastAPI ships OpenAPI for free β `openapi-typescript` codegen (ADR-015) keeps frontend types in sync with backend.
|
| 31 |
+
|
| 32 |
+
**Negative:**
|
| 33 |
+
|
| 34 |
+
- Extra ~2-3 hours of scaffolding versus Streamlit.
|
| 35 |
+
- FE/BE auth + CORS + dual deploy add complexity.
|
| 36 |
+
|
| 37 |
+
**Mitigations:**
|
| 38 |
+
|
| 39 |
+
- `openapi-typescript` codegen so types update automatically.
|
| 40 |
+
- Single CORS allowlist in `backend/main.py` (currently `*` for v1, narrowed for v2).
|
| 41 |
+
- Vercel + HF Space both auto-deploy from the same GitHub repo on push.
|
| 42 |
+
|
| 43 |
+
## Revisit at scale
|
| 44 |
+
|
| 45 |
+
Same stack. Standard production pattern for AI products in 2026.
|
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-006: Sarvam-first defaults for STT/TTS/LLM
|
| 2 |
+
|
| 3 |
+
**Status:** Partially superseded by [ADR-019](ADR-019-nim-single-provider-consolidation.md)
|
| 4 |
+
**Date:** 2026-05-13
|
| 5 |
+
|
| 6 |
+
## Context
|
| 7 |
+
|
| 8 |
+
This is a Sarvam AI take-home assignment. Silent defaults to non-Sarvam providers would screen out, regardless of technical merit.
|
| 9 |
+
|
| 10 |
+
## Decision
|
| 11 |
+
|
| 12 |
+
**Sarvam-first benchmarking** for every voice and reasoning component:
|
| 13 |
+
|
| 14 |
+
- STT: Sarvam Saarika v2.5 vs. Whisper / Deepgram
|
| 15 |
+
- TTS: Sarvam Bulbul v2 vs. ElevenLabs / OpenAI TTS
|
| 16 |
+
- LLM: Sarvam-M vs. GPT-4o / Claude
|
| 17 |
+
|
| 18 |
+
Components live behind thin interfaces (`backend/providers/base.py`) so swapping any one is a config flag, not a refactor.
|
| 19 |
+
|
| 20 |
+
## Outcome β what's still Sarvam after ADR-019
|
| 21 |
+
|
| 22 |
+
| Layer | v1 (this ADR) | After ADR-019 |
|
| 23 |
+
|---|---|---|
|
| 24 |
+
| STT | Sarvam Saarika v2.5 | **Stays Sarvam** β best Indian-accent recognition |
|
| 25 |
+
| TTS | Sarvam Bulbul v2 | **Stays Sarvam** β best Hinglish TTS |
|
| 26 |
+
| Indic translation | Sarvam-M | **Stays Sarvam** β best Hinglish/Hindi translation |
|
| 27 |
+
| Reasoning brain | Sarvam-M | **Moved to NIM DeepSeek-V4-Pro / V4-Flash** β Sarvam-M's 2048 output cap + `<think>` tokens caused mid-JSON truncation. See ADR-019. |
|
| 28 |
+
| Judge | Sarvam-M self-judge | **Moved to NIM Llama-4 Maverick** β different family from the brain for non-circular evaluation. |
|
| 29 |
+
|
| 30 |
+
## Why partial supersession is the honest narrative
|
| 31 |
+
|
| 32 |
+
ADR-019's framing in the SUBMISSION doc: *"Sarvam isn't trying to win a benchmark it doesn't need to win."* The voice + Indic pieces are where Sarvam is uniquely strong and closed-source frontier cannot match. The reasoning role is a different problem; MIT-licensed open-weights frontier (DeepSeek-V4-Pro) is a strictly better fit on a free tier with no rate cap.
|
| 33 |
+
|
| 34 |
+
## Consequences
|
| 35 |
+
|
| 36 |
+
**Positive:** Project leads with Sarvam exactly where Sarvam beats the world.
|
| 37 |
+
|
| 38 |
+
**Negative:** Slight increased complexity β three providers instead of one (Sarvam + NIM + local BGE embeddings).
|
| 39 |
+
|
| 40 |
+
## Revisit at scale
|
| 41 |
+
|
| 42 |
+
When Sarvam ships a longer-context reasoning model (current cap 2048 is the blocker), benchmark it against DeepSeek-V4-Pro on the same gold set and consider unifying.
|
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-007: Illustrative pricing, not real-time quotes
|
| 2 |
+
|
| 3 |
+
**Status:** Locked
|
| 4 |
+
**Date:** 2026-05-13
|
| 5 |
+
|
| 6 |
+
## Context
|
| 7 |
+
|
| 8 |
+
Buyers expect to see premium estimates. Indian insurers hide real pricing behind a phone-callback flow; there is no public quote API. Three paths exist:
|
| 9 |
+
|
| 10 |
+
1. Illustrative band, anchored to public rate cards, with explicit disclaimer.
|
| 11 |
+
2. Scrape comparison portals (PolicyBazaar etc.) at query time.
|
| 12 |
+
3. Build an actuarial model from first principles.
|
| 13 |
+
|
| 14 |
+
## Decision
|
| 15 |
+
|
| 16 |
+
**Path 1: illustrative band with disclaimer.** Path 2 used only for top-5 ground-truth validation of the anchor cells.
|
| 17 |
+
|
| 18 |
+
## Alternatives considered
|
| 19 |
+
|
| 20 |
+
| Path | Why rejected as primary |
|
| 21 |
+
|---|---|
|
| 22 |
+
| (2) Live scrape | Legally gray (ToS), brittle to UI changes, slow per-query, doesn't capture insurer-callback dynamics. |
|
| 23 |
+
| (3) Actuarial model | Out of scope; data inputs unavailable; v1 build window doesn't permit it. |
|
| 24 |
+
|
| 25 |
+
## Implementation
|
| 26 |
+
|
| 27 |
+
- `data/premiums/illustrative_premiums.json` holds the anchor table.
|
| 28 |
+
- Anchor cells sourced from public PolicyBazaar quote pages and insurer rate cards (provenance in `kb/premiums/INDEX.md`).
|
| 29 |
+
- Missing `(insurer, policy, age, SI, family-composition)` cells are extrapolated using scaling factors derived from the visible anchor points + standard IRDAI age-bandings.
|
| 30 |
+
- **Every premium reply from the bot carries an explicit illustrative disclaimer.**
|
| 31 |
+
- Premium calculator UI panel (frontend) shows the same disclaimer prominently.
|
| 32 |
+
|
| 33 |
+
## Consequences
|
| 34 |
+
|
| 35 |
+
**Positive:**
|
| 36 |
+
|
| 37 |
+
- Honest about precision β no implied false certainty.
|
| 38 |
+
- Reinforces the "advisor not broker" positioning.
|
| 39 |
+
- Removes legal risk of pretending to broker quotes.
|
| 40 |
+
|
| 41 |
+
**Negative:**
|
| 42 |
+
|
| 43 |
+
- Users get an estimate band, not a specific number.
|
| 44 |
+
- Conversion to actual purchase still requires insurer/aggregator handoff.
|
| 45 |
+
|
| 46 |
+
## Revisit at scale
|
| 47 |
+
|
| 48 |
+
v2 path: B2B partnership with an aggregator (PolicyBazaar, Ditto, InsuranceDekho) or direct insurer APIs to get real quotes. See `docs/00-overview/roadmap.md` Β§v2.2.
|
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-008: Consultative-advisor persona (not closing-pitch sales)
|
| 2 |
+
|
| 3 |
+
**Status:** Locked
|
| 4 |
+
**Date:** 2026-05-13
|
| 5 |
+
|
| 6 |
+
## Context
|
| 7 |
+
|
| 8 |
+
A "health-insurance sales bot" framing invites two distinct persona archetypes:
|
| 9 |
+
|
| 10 |
+
1. Hard-sell closer ("This is the best policy! Buy now!").
|
| 11 |
+
2. Consultative advisor ("Here's what fits your profile; here are the trade-offs; verify with insurer before purchase.").
|
| 12 |
+
|
| 13 |
+
The choice has direct legal, ethical, and commercial consequences.
|
| 14 |
+
|
| 15 |
+
## Decision
|
| 16 |
+
|
| 17 |
+
**Consultative β modelled on a great Independent Financial Advisor (IFA).** Implemented in `backend/persona.py:ADVISOR_SYSTEM_PROMPT_V1`.
|
| 18 |
+
|
| 19 |
+
## Alternatives considered
|
| 20 |
+
|
| 21 |
+
| Persona | Why rejected |
|
| 22 |
+
|---|---|
|
| 23 |
+
| Hard-sell closer | Mis-selling is regulated in India (IRDAI fines); BFSI customers buying this bot get fined for mis-selling. Erodes trust which is the real driver of conversion in long-tenure products. |
|
| 24 |
+
|
| 25 |
+
## Persona rules baked into the system prompt
|
| 26 |
+
|
| 27 |
+
1. **Grounded answers only** β every factual claim must come from retrieved clauses; never from training memory.
|
| 28 |
+
2. **Citation grammar** β `[Source: <policy> (<insurer-slug>), p.<page>]` on every factual claim. Regulatory citations override policy clauses and surface explicitly.
|
| 29 |
+
3. **Concise for voice** β 2-3 sentences, β€60 words by default. Avoid markdown bold, multi-section structures.
|
| 30 |
+
4. **No medical advice** β "Will this be covered if I have X?" β answer the COVERAGE question, never the medical one.
|
| 31 |
+
5. **No final transactional advice** β "Should I buy this?" gets guidance + the disclaimer "confirm with the insurer directly before finalizing."
|
| 32 |
+
6. **Match language register** β Indic in, Indic out; English in, English out.
|
| 33 |
+
7. **No scare tactics** β no fear-of-missing-out framing, no worst-case pressuring.
|
| 34 |
+
|
| 35 |
+
## Consequences
|
| 36 |
+
|
| 37 |
+
**Positive:**
|
| 38 |
+
|
| 39 |
+
- Wins trust, which is the real conversion driver in BFSI.
|
| 40 |
+
- Lower legal risk for deploying customers.
|
| 41 |
+
- Aligned with the way IRDAI grades advisor conduct.
|
| 42 |
+
|
| 43 |
+
**Negative:**
|
| 44 |
+
|
| 45 |
+
- Conversion rates may be lower than a hard-sell bot in pure short-term experiments.
|
| 46 |
+
|
| 47 |
+
**Mitigations:**
|
| 48 |
+
|
| 49 |
+
- Tone may flex by deployment partner (insurer-direct vs. aggregator-direct) via system-prompt overlays. The persona is configurable, not baked in.
|
| 50 |
+
|
| 51 |
+
## Revisit at scale
|
| 52 |
+
|
| 53 |
+
Same. Add A/B mode for deploying partners with strict guard-rails (no mis-selling phrases, persona transparency to the user).
|
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-009: 19 insurers Γ all health policies; 48-field structured schema
|
| 2 |
+
|
| 3 |
+
**Status:** Locked
|
| 4 |
+
**Date:** 2026-05-13
|
| 5 |
+
|
| 6 |
+
## Context
|
| 7 |
+
|
| 8 |
+
Original v1 plan: 5 insurers Γ ~3 policies each = ~15 PDFs. Mid-build, the user expanded scope explicitly for comprehensiveness.
|
| 9 |
+
|
| 10 |
+
## Decision
|
| 11 |
+
|
| 12 |
+
**19 insurers Γ all available health policies = 190 product PDFs + 18 regulatory PDFs = 208 total.**
|
| 13 |
+
|
| 14 |
+
Insurers covered: Star, HDFC ERGO, Niva Bupa, Care, ICICI Lombard, Bajaj Allianz, New India Assurance, Aditya Birla, Tata AIG, ManipalCigna, SBI General, Acko, IFFCO Tokio, Cholamandalam MS, Go Digit, Reliance General, Royal Sundaram, Oriental Insurance, National Insurance.
|
| 15 |
+
|
| 16 |
+
Structured schema: **48 fields per policy** β premium, sum insured, waiting periods (initial, PED, specific disease), family-composition options, sub-limits (room rent, ICU, modern treatments), network hospital count, restoration benefit, no-claim bonus, AYUSH, OPD riders, claim ratio, geographic coverage, and more.
|
| 17 |
+
|
| 18 |
+
## Why this scope
|
| 19 |
+
|
| 20 |
+
| Dimension | Impact of going wider |
|
| 21 |
+
|---|---|
|
| 22 |
+
| Insurers | Cross-policy comparison is meaningless with <5; credible at 19. |
|
| 23 |
+
| Per-insurer policies | Buyers want to see ALL options from a brand they trust, not 1 cherry-picked product. |
|
| 24 |
+
| 48 fields | Below ~30 fields, filter UI is too narrow; above ~60 fields, schema becomes brittle. 48 sits in the empirical sweet spot for Indian health insurance. |
|
| 25 |
+
|
| 26 |
+
## Acquisition strategy
|
| 27 |
+
|
| 28 |
+
- Initial: research agent + `requests` library on insurer websites.
|
| 29 |
+
- Failures: Star Health (CDN-blocked), IRDAI (Akamai-blocked) β Playwright rescue (see ADR-017).
|
| 30 |
+
- Quality threshold: every PDF must HEAD-verify, have β₯1 page, parse via pdfplumber without errors.
|
| 31 |
+
|
| 32 |
+
## Consequences
|
| 33 |
+
|
| 34 |
+
**Positive:**
|
| 35 |
+
|
| 36 |
+
- Marketplace UI is credibly useful (255 listed policies, 224 with markdown writeups).
|
| 37 |
+
- Cross-policy comparison queries hit non-trivial breadth.
|
| 38 |
+
- Brand coverage matches Indian buyer behavior (people compare within insurers they recognise).
|
| 39 |
+
|
| 40 |
+
**Negative:**
|
| 41 |
+
|
| 42 |
+
- Corpus acquisition is the longest-pole task in the build.
|
| 43 |
+
- Per-PDF extraction quality varies β some CIS-only documents lack fields that wordings cover; some completeness ratings drop into the 20-40% range.
|
| 44 |
+
|
| 45 |
+
**Mitigations:**
|
| 46 |
+
|
| 47 |
+
- Per-policy `completeness` metric exposed in `kb/INDEX.md`.
|
| 48 |
+
- Extraction quality audit in `kb/calculations/extraction_quality_audit.md` documents which fields are sparse and why.
|
| 49 |
+
|
| 50 |
+
## Revisit at scale
|
| 51 |
+
|
| 52 |
+
v2: extend to Life and Motor (ADR-002 Β§revisit). Add ESG annual reports to the regulatory corpus for ratings sub-score.
|
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-010: Secret handling β `.env` chmod 600, gitignored, mirrored to HF Space secrets
|
| 2 |
+
|
| 3 |
+
**Status:** Locked
|
| 4 |
+
**Date:** 2026-05-13
|
| 5 |
+
|
| 6 |
+
## Context
|
| 7 |
+
|
| 8 |
+
The bot needs 8 API keys / secrets (Sarvam, Voyage, NIM, HuggingFace, Groq, OpenRouter, admin password, admin IP allowlist). Leaking any of them creates real cost or security exposure.
|
| 9 |
+
|
| 10 |
+
## Decision
|
| 11 |
+
|
| 12 |
+
**Three-place handling pattern:**
|
| 13 |
+
|
| 14 |
+
| Location | Used by | Access control |
|
| 15 |
+
|---|---|---|
|
| 16 |
+
| Local `.env` | Local dev | `chmod 600`, gitignored (`.gitignore` line 1) |
|
| 17 |
+
| HF Space secrets | Production deploy | HF Space UI / API; encrypted at rest by HF |
|
| 18 |
+
| `~/.claude/.../memory/reference_insurance_bot_api_keys.md` | Recovery if `.env` is wiped | Lives only on local Mac; not synced to any remote |
|
| 19 |
+
|
| 20 |
+
The HF Space mirror is managed by `tools/set_hf_secrets.py` which reads from local `.env` and PATCHes the HF Space API.
|
| 21 |
+
|
| 22 |
+
## Alternatives considered
|
| 23 |
+
|
| 24 |
+
| Approach | Why rejected |
|
| 25 |
+
|---|---|
|
| 26 |
+
| Plain-text keys in repo | Catastrophic for any commit history. |
|
| 27 |
+
| Encrypted file in repo | Custom decryption flow per machine; key management problem moves around but doesn't go away. |
|
| 28 |
+
| External vault (1Password Connect, HashiCorp Vault) | Overkill for take-home; another infra dependency. |
|
| 29 |
+
|
| 30 |
+
## Implementation details
|
| 31 |
+
|
| 32 |
+
- `.env.example` checked in with placeholder values, makes new-machine setup trivial.
|
| 33 |
+
- `.gitignore` line 1: `.env`. Verified via `git ls-files | grep -E '^\.env$'` β only `.env.example` shows.
|
| 34 |
+
- `backend/config.py` loads via `python-dotenv`; missing keys raise `ValueError` at boot with the field name.
|
| 35 |
+
- Admin password rotation: `tools/set_hf_secrets.py` re-pushes any changed value to HF Space; Space restarts automatically.
|
| 36 |
+
- Memory file is the recovery copy of last resort. After the May 14 `.env` wipe incident, the memory file is treated as the canonical inventory.
|
| 37 |
+
|
| 38 |
+
## Consequences
|
| 39 |
+
|
| 40 |
+
**Positive:**
|
| 41 |
+
|
| 42 |
+
- Secrets exist exactly where they're needed; nowhere else.
|
| 43 |
+
- Rotation is one script run.
|
| 44 |
+
- New-machine setup needs only `.env` provisioning.
|
| 45 |
+
|
| 46 |
+
**Negative:**
|
| 47 |
+
|
| 48 |
+
- Memory file containing plaintext keys is a sensitive artifact; protect the Mac.
|
| 49 |
+
|
| 50 |
+
## Revisit at scale
|
| 51 |
+
|
| 52 |
+
v2: move HF Space secrets to a real KMS (AWS Secrets Manager, GCP Secret Manager) when the deploy moves off HF Space free tier.
|
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-011: Local BGE-small embeddings (Voyage was the original pick)
|
| 2 |
+
|
| 3 |
+
**Status:** Locked
|
| 4 |
+
**Date:** 2026-05-13
|
| 5 |
+
|
| 6 |
+
## Context
|
| 7 |
+
|
| 8 |
+
The retrieval store (Chroma) needs an embedding model. Original plan locked Voyage AI `voyage-3` (Anthropic's recommended partner, top MTEB benchmarks, $0.12 per 1M tokens). Mid-build, the Voyage free-tier 3 RPM rate limit blocked the 208-PDF ingest β at 3 requests per minute, a single chunk-by-chunk embed of the corpus would take ~30 hours.
|
| 9 |
+
|
| 10 |
+
## Decision
|
| 11 |
+
|
| 12 |
+
**Switch to local `BAAI/bge-small-en-v1.5`** via `sentence-transformers`. Voyage path retained behind the same `Embedder` interface (`backend/providers/base.py`) so v2 swap is a config flag.
|
| 13 |
+
|
| 14 |
+
## Alternatives considered
|
| 15 |
+
|
| 16 |
+
| Option | Why not |
|
| 17 |
+
|---|---|
|
| 18 |
+
| Voyage (original) | 3 RPM free-tier blocks the ingest. |
|
| 19 |
+
| OpenAI `text-embedding-3-small` | No OpenAI API available to this build. |
|
| 20 |
+
| Sarvam embeddings | Not exposed via API at build time. |
|
| 21 |
+
| BGE-m3 (multilingual) | Better for Indic; larger model; for v1 the queries route through Sarvam translator to English first, so a strong English embedder suffices. |
|
| 22 |
+
|
| 23 |
+
## Trade-offs
|
| 24 |
+
|
| 25 |
+
| Dimension | Voyage `voyage-3` | BGE-small-en-v1.5 |
|
| 26 |
+
|---|---|---|
|
| 27 |
+
| Retrieval quality (BEIR-style spot checks) | Baseline | ~3 pp below baseline |
|
| 28 |
+
| Cost | $0.12 / 1M tokens | $0 (local CPU) |
|
| 29 |
+
| Latency at ingest | Network bound | Local CPU bound (faster on Apple Silicon) |
|
| 30 |
+
| Rate limit | 3 RPM free / 300 RPM paid | None |
|
| 31 |
+
| Model size | API-only | 130 MB on disk |
|
| 32 |
+
| Dimensionality | 1024 | 384 |
|
| 33 |
+
|
| 34 |
+
## Consequences
|
| 35 |
+
|
| 36 |
+
**Positive:**
|
| 37 |
+
|
| 38 |
+
- Ingest finishes locally with no rate-limit drama (~3 minutes for the full 208 PDF embed at 800-token chunks).
|
| 39 |
+
- $0 cost.
|
| 40 |
+
- Same model available offline.
|
| 41 |
+
|
| 42 |
+
**Negative:**
|
| 43 |
+
|
| 44 |
+
- ~3 pp accuracy hit on retrieval recall vs. Voyage.
|
| 45 |
+
- 384-d vectors are smaller (less expressive) than Voyage's 1024-d.
|
| 46 |
+
|
| 47 |
+
**Mitigations:**
|
| 48 |
+
|
| 49 |
+
- Retrieval floor in `backend/faithfulness.py` Gate 1 (`MIN_TOP_SCORE = 0.30`) catches low-confidence retrievals before they reach the LLM.
|
| 50 |
+
- Profile-as-chunk boost (`rag/retrieve.py:155-170`) compensates for embedding weakness on personalized queries.
|
| 51 |
+
|
| 52 |
+
## Revisit at scale
|
| 53 |
+
|
| 54 |
+
v2: re-benchmark Voyage with a paid tier on the same gold set; route by language (Voyage for English queries, BGE-m3 or Sarvam embeddings for Indic) once Sarvam exposes embeddings.
|
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-012: Render-then-HF-Space deployment
|
| 2 |
+
|
| 3 |
+
**Status:** Superseded by deploy-to-HF-Space (current production)
|
| 4 |
+
**Date:** 2026-05-13
|
| 5 |
+
|
| 6 |
+
## Context
|
| 7 |
+
|
| 8 |
+
The backend needed a free, GitHub-auto-deploying Python host with persistent disk for DuckDB + Chroma.
|
| 9 |
+
|
| 10 |
+
## Decision (v1, since superseded)
|
| 11 |
+
|
| 12 |
+
**Render** (free tier, 750 h/mo).
|
| 13 |
+
|
| 14 |
+
## Current state (post-supersession)
|
| 15 |
+
|
| 16 |
+
Production now deploys to **HuggingFace Space** (`origin` remote). Reasons:
|
| 17 |
+
|
| 18 |
+
- HF Space's Docker SDK gives full control over the runtime (Python + Node side-by-side for the unified `backend + frontend/standalone` Docker container).
|
| 19 |
+
- HF Space natively integrates with HF Dataset (ADR-020) β data fetch at build time via `huggingface_hub.snapshot_download` is one-line.
|
| 20 |
+
- Sarvam reviewers expect Sarvam-stack demos on HF Space anyway.
|
| 21 |
+
- Free tier is ample for take-home traffic.
|
| 22 |
+
|
| 23 |
+
## Alternatives considered (v1)
|
| 24 |
+
|
| 25 |
+
| Host | Why not |
|
| 26 |
+
|---|---|
|
| 27 |
+
| Fly.io | Better global routing, but more setup overhead. |
|
| 28 |
+
| Railway | Free tier dropped after the build started. |
|
| 29 |
+
| Modal | Better for ML workloads but no persistent disk model. |
|
| 30 |
+
| Self-hosted VPS | Operational overhead without payoff. |
|
| 31 |
+
|
| 32 |
+
## Why Render was the original pick
|
| 33 |
+
|
| 34 |
+
- GitHub auto-deploy on push.
|
| 35 |
+
- Python-native build.
|
| 36 |
+
- Persistent disk for DuckDB + Chroma.
|
| 37 |
+
- Env-var secrets.
|
| 38 |
+
- Well-documented.
|
| 39 |
+
|
| 40 |
+
## Why HF Space replaced Render
|
| 41 |
+
|
| 42 |
+
- The data-split architecture (ADR-020) made HF Space's free 50 GB dataset quota a perfect fit.
|
| 43 |
+
- Single platform for both code (Space) and data (Dataset).
|
| 44 |
+
- Docker SDK supports the unified frontend + backend container we ended up needing.
|
| 45 |
+
- Render's free tier sleep-after-15-min idle policy is identical to HF Space's, so no operational difference.
|
| 46 |
+
|
| 47 |
+
## Consequences
|
| 48 |
+
|
| 49 |
+
Migration from Render to HF Space was a one-commit change (Dockerfile + render.yaml retained as a fallback option, but not active).
|
| 50 |
+
|
| 51 |
+
## Revisit at scale
|
| 52 |
+
|
| 53 |
+
v2: move to dedicated cloud (AWS Fargate / GCP Cloud Run) when traffic justifies it.
|
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-013: Tailwind CSS + shadcn/ui
|
| 2 |
+
|
| 3 |
+
**Status:** Locked
|
| 4 |
+
**Date:** 2026-05-13
|
| 5 |
+
|
| 6 |
+
## Context
|
| 7 |
+
|
| 8 |
+
The frontend needs to look polished in <1 day of build. Three constraints: accessibility, mobile-responsive, no time for bespoke design system.
|
| 9 |
+
|
| 10 |
+
## Decision
|
| 11 |
+
|
| 12 |
+
**Tailwind CSS for utility styling** + **shadcn/ui for primitive components** (Button, Dialog, Tabs, Form, etc.).
|
| 13 |
+
|
| 14 |
+
## Alternatives considered
|
| 15 |
+
|
| 16 |
+
| Library | Why rejected |
|
| 17 |
+
|---|---|
|
| 18 |
+
| Material UI (MUI) | Heavier component model; "Material" aesthetic doesn't fit consumer-fintech feel. |
|
| 19 |
+
| Chakra UI | Similar to MUI in weight; ecosystem smaller than shadcn since 2024. |
|
| 20 |
+
| Mantine | Good but smaller pool of community examples for chat / voice interfaces. |
|
| 21 |
+
| Plain CSS | Too slow at 1-day pace; reinvents wheels. |
|
| 22 |
+
|
| 23 |
+
## Why shadcn/ui specifically
|
| 24 |
+
|
| 25 |
+
- Components are **copy-pasted** into the repo, not imported as a dependency β full control over styling and behavior.
|
| 26 |
+
- Built on Radix primitives β accessibility built in (focus management, ARIA).
|
| 27 |
+
- Tailwind classes mean theming is one CSS variable change.
|
| 28 |
+
- Dark mode comes free.
|
| 29 |
+
- Active community β lots of examples for chat interfaces, marketplaces, settings panels.
|
| 30 |
+
|
| 31 |
+
## Consequences
|
| 32 |
+
|
| 33 |
+
**Positive:**
|
| 34 |
+
|
| 35 |
+
- Mobile-first responsive design in hours, not days.
|
| 36 |
+
- Accessibility passes (keyboard navigation, screen reader labels) by default.
|
| 37 |
+
- Theming via CSS variables β single source of truth in `frontend/src/app/globals.css`.
|
| 38 |
+
- Custom design tokens (`--primary`, `--secondary`, etc.) reused across all components.
|
| 39 |
+
|
| 40 |
+
**Negative:**
|
| 41 |
+
|
| 42 |
+
- Tailwind class soup in JSX can be visually noisy.
|
| 43 |
+
- `cn()` utility (clsx + tailwind-merge) needed for conditional classes.
|
| 44 |
+
|
| 45 |
+
**Mitigations:**
|
| 46 |
+
|
| 47 |
+
- shadcn components encapsulate their own class logic; page-level components stay readable.
|
| 48 |
+
- Component-level styles extracted to small CSS modules when class lists exceed ~5 utilities.
|
| 49 |
+
|
| 50 |
+
## Revisit at scale
|
| 51 |
+
|
| 52 |
+
Same stack. Production-pattern in 2026; no compelling reason to switch.
|
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-014: Groq Llama-3.3-70B as eval grader
|
| 2 |
+
|
| 3 |
+
**Status:** Superseded by [ADR-019](ADR-019-nim-single-provider-consolidation.md)
|
| 4 |
+
**Date:** 2026-05-13
|
| 5 |
+
|
| 6 |
+
## Context
|
| 7 |
+
|
| 8 |
+
The eval harness needed a grading LLM that was NOT the same family as the brain LLM (to avoid circular evaluation). Available options:
|
| 9 |
+
|
| 10 |
+
- GPT-4o-mini β rejected (no OpenAI API on this build).
|
| 11 |
+
- Claude Haiku via API β rejected (no Anthropic API key; Claude Code Max is terminal-only).
|
| 12 |
+
- Groq Llama-3.3-70B-versatile β free tier 30 req/min, different family from Sarvam-M brain.
|
| 13 |
+
|
| 14 |
+
## Decision (v1, since superseded)
|
| 15 |
+
|
| 16 |
+
**Groq Llama-3.3-70B-versatile** as the eval grader. Different model family from Sarvam-M brain β non-circular grade.
|
| 17 |
+
|
| 18 |
+
## What changed
|
| 19 |
+
|
| 20 |
+
ADR-019's consolidation moved the eval grader to **NIM Llama-4 Maverick** instead. The reasoning was the same (different family from the brain, non-circular) but the consolidation eliminated the Groq dependency entirely β one less provider, one less rate-limit, one less API key.
|
| 21 |
+
|
| 22 |
+
Groq Llama-3.3-70B remains wired as a fallback in `backend/providers/groq_llm.py` for cases where NIM Maverick is rate-limited (40 req/min cap), but it is no longer the primary grader.
|
| 23 |
+
|
| 24 |
+
## Why this ADR is kept as a historical record
|
| 25 |
+
|
| 26 |
+
The chunk-sweep failures documented in [ADR-018](ADR-018-chunk-size-sweep-deferred.md) were caused by Groq's 30 req/min rate limit interacting with the grader's per-question API call pattern. Documenting why Groq was chosen and the rate-limit collision is useful context for anyone reproducing the eval pipeline.
|
| 27 |
+
|
| 28 |
+
## Risk that motivated supersession
|
| 29 |
+
|
| 30 |
+
Groq's 30 req/min free tier was insufficient for systematic eval runs at corpus scale (96 gold questions Γ N model variants = NΓ96 requests, often hitting the rate ceiling and producing flat results across model variants β see ADR-018).
|
| 31 |
+
|
| 32 |
+
## Revisit at scale
|
| 33 |
+
|
| 34 |
+
n/a β superseded.
|
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-015: REST API + `openapi-typescript` codegen
|
| 2 |
+
|
| 3 |
+
**Status:** Locked
|
| 4 |
+
**Date:** 2026-05-13
|
| 5 |
+
|
| 6 |
+
## Context
|
| 7 |
+
|
| 8 |
+
The Next.js frontend talks to the FastAPI backend over HTTP. Request/response types need to stay in sync as the backend evolves β manual TypeScript type maintenance always rots within weeks.
|
| 9 |
+
|
| 10 |
+
## Decision
|
| 11 |
+
|
| 12 |
+
- **REST** transport layer (not GraphQL, not gRPC, not tRPC).
|
| 13 |
+
- FastAPI auto-generates OpenAPI 3.x schema (`/openapi.json`).
|
| 14 |
+
- `openapi-typescript` codegens TypeScript types into `frontend/src/lib/api-types.ts` on every build.
|
| 15 |
+
- `frontend/src/lib/api.ts` wraps `fetch` with typed helpers (`postChat`, `postProfile`, `getCoverage`, etc.).
|
| 16 |
+
|
| 17 |
+
## Alternatives considered
|
| 18 |
+
|
| 19 |
+
| Approach | Why rejected |
|
| 20 |
+
|---|---|
|
| 21 |
+
| Manual TypeScript types | Rot pattern is universal; types drift from backend reality within weeks. |
|
| 22 |
+
| GraphQL | Overkill for our REST-shaped data; adds resolver layer + N+1 risks; Sarvam ecosystem doesn't expect it. |
|
| 23 |
+
| tRPC | Node-only; doesn't fit our Python backend. |
|
| 24 |
+
| gRPC | Overkill; HTTP/2 dependencies + protobuf tooling adds friction. |
|
| 25 |
+
|
| 26 |
+
## Implementation
|
| 27 |
+
|
| 28 |
+
```
|
| 29 |
+
FastAPI (auto OpenAPI) β openapi-typescript β api-types.ts β typed fetch wrappers
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
Build hook: `frontend/scripts/codegen.sh` runs `openapi-typescript http://localhost:7860/openapi.json -o src/lib/api-types.ts` after every backend deploy.
|
| 33 |
+
|
| 34 |
+
## Consequences
|
| 35 |
+
|
| 36 |
+
**Positive:**
|
| 37 |
+
|
| 38 |
+
- Single source of truth for API shape (the FastAPI Pydantic models).
|
| 39 |
+
- Type drift caught at TypeScript compile time.
|
| 40 |
+
- Refactors on the backend immediately surface as TypeScript errors on the frontend.
|
| 41 |
+
- No GraphQL N+1 trap.
|
| 42 |
+
|
| 43 |
+
**Negative:**
|
| 44 |
+
|
| 45 |
+
- Streaming responses require a parallel path (Server-Sent Events or WebSocket; not codegen-friendly).
|
| 46 |
+
|
| 47 |
+
**Mitigations:**
|
| 48 |
+
|
| 49 |
+
- For the streaming case (voice STT live), we keep a hand-written WebSocket handler.
|
| 50 |
+
|
| 51 |
+
## Revisit at scale
|
| 52 |
+
|
| 53 |
+
v2: if real-time streaming (full-duplex voice) becomes the dominant pattern, add a WebSocket route alongside REST. Codegen for the REST half stays.
|
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-016: Hybrid brain router (Sarvam primary + Llama / DeepSeek fallback)
|
| 2 |
+
|
| 3 |
+
**Status:** Superseded by [ADR-019](ADR-019-nim-single-provider-consolidation.md)
|
| 4 |
+
**Date:** 2026-05-13
|
| 5 |
+
|
| 6 |
+
## Context
|
| 7 |
+
|
| 8 |
+
A single brain LLM was insufficient: Sarvam-M's 2048 output cap + `<think>` tokens caused mid-JSON truncation in extraction and mid-answer truncation in advisory. But Sarvam-first is non-negotiable narrative for the assignment.
|
| 9 |
+
|
| 10 |
+
## Decision (v1, since superseded)
|
| 11 |
+
|
| 12 |
+
**Hybrid router**: Sarvam-M as primary, escalate to Llama-3.3-70B (Groq) or DeepSeek-V3 (OpenRouter) for queries where Sarvam-M underperforms in benchmark.
|
| 13 |
+
|
| 14 |
+
### v1 router heuristic
|
| 15 |
+
|
| 16 |
+
- Indic language detected β Sarvam-M
|
| 17 |
+
- Comparison of 3+ policies β fallback brain (longer context, stronger reasoning)
|
| 18 |
+
- Open-ended recommendation requiring multi-hop reasoning β fallback brain
|
| 19 |
+
- Simple single-policy Q&A β Sarvam-M
|
| 20 |
+
- Empirical override: if gold Q&A eval shows Sarvam-M wins a query class we expected to lose, keep Sarvam-M for that class. Data > heuristic.
|
| 21 |
+
|
| 22 |
+
## What changed
|
| 23 |
+
|
| 24 |
+
ADR-019 collapsed this to **tiered routing inside a single NIM provider**:
|
| 25 |
+
|
| 26 |
+
- Heavy brain: DeepSeek-V4-Pro for comparison / recommendation intents.
|
| 27 |
+
- Fast brain: DeepSeek-V4-Flash for voice / qa / fact-find intents.
|
| 28 |
+
- Judge: Llama-4 Maverick (cross-family rescue) for faithfulness Gate 4 + cross-check retry.
|
| 29 |
+
- Sarvam-M demoted from brain role to **Indic translation cascade only** β keeps Sarvam where Sarvam is uniquely strong without exposing the 2048-token cap.
|
| 30 |
+
|
| 31 |
+
## Why supersession
|
| 32 |
+
|
| 33 |
+
The cross-provider router was complex (3 providers, 3 free-tier ceilings, 3 retry models, ~600 LOC of wiring). Tiered routing inside one NIM provider achieves the same intent-aware quality/latency trade-off with one API key, one rate-limit, and one consistent error model.
|
| 34 |
+
|
| 35 |
+
## Consequences (historical)
|
| 36 |
+
|
| 37 |
+
Router pattern itself was sound; the cost was the multi-provider sprawl. The current state preserves the router intent at the model-tier level.
|
| 38 |
+
|
| 39 |
+
## Revisit at scale
|
| 40 |
+
|
| 41 |
+
n/a β superseded. See ADR-019 for the current routing logic.
|
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-017: IRDAI regulatory corpus β deferred, then Playwright-rescued
|
| 2 |
+
|
| 3 |
+
**Status:** Locked
|
| 4 |
+
**Date:** 2026-05-13 (deferral) β 2026-05-14 (rescued)
|
| 5 |
+
|
| 6 |
+
## Context
|
| 7 |
+
|
| 8 |
+
Health-insurance answers often hinge on IRDAI master circulars (free-look periods, PED definitions, standard exclusions, cataract waiting-period caps). Research surfaced 17 candidate regulatory PDFs across IRDAI / Insurance Act / Ombudsman / PMJAY / GST FAQ documents.
|
| 9 |
+
|
| 10 |
+
When fetched, 14 of 17 returned **Akamai bot-challenge HTML** instead of the PDF, even with `Referer` matching, browser-grade headers, and cookie warm-up. Plain `curl` and `requests` could not get past Akamai's JavaScript challenge layer.
|
| 11 |
+
|
| 12 |
+
## Initial decision (deferral, 2026-05-13)
|
| 13 |
+
|
| 14 |
+
**Defer regulatory corpus to v2; rely on Gate 1 (retrieval floor) to safely refuse regulatory questions.**
|
| 15 |
+
|
| 16 |
+
Verified: "What's the GST + 80D treatment for premium?" β correctly blocked by Gate 1 (no retrieved chunks with `doc_type=regulatory` β retrieval score < `MIN_TOP_SCORE`).
|
| 17 |
+
|
| 18 |
+
## Revised decision (rescue, 2026-05-14)
|
| 19 |
+
|
| 20 |
+
**Playwright same-origin fetch.** Open the IRDAI page in real Chromium via the Playwright MCP, warm Akamai cookies on the `irdai.gov.in` homepage, then issue a same-origin `fetch()` with `credentials: 'include'` from inside the page's JS context. Landing pages like `document-detail?documentId=...` were resolved by extracting the embedded `<a href="...pdf">` from the DOM.
|
| 21 |
+
|
| 22 |
+
Result: **18 regulatory PDFs in `rag/corpus/regulatory/`** β including IRDAI Master Circular on Health 2024, Arogya Sanjeevani standard wording, Insurance Act 1938, Ombudsman Rules, PMJAY ops manual, GST FAQs.
|
| 23 |
+
|
| 24 |
+
## Alternatives considered (at deferral)
|
| 25 |
+
|
| 26 |
+
| Option | Why rejected |
|
| 27 |
+
|---|---|
|
| 28 |
+
| Brute-force Playwright | Would have worked (eventually adopted); consumed ~30 min build time that was prioritised for eval harness. |
|
| 29 |
+
| Third-party law-firm summaries / Wikipedia | Derivative; unreliable for BFSI grounding. |
|
| 30 |
+
| Hand-curate regulatory summary | Violates the no-hallucination rule β we cannot insert training-data facts into the corpus. |
|
| 31 |
+
|
| 32 |
+
## Why it was worth coming back to
|
| 33 |
+
|
| 34 |
+
Without the regulatory corpus, the demo question *"What does IRDAI's 2024 Master Circular say about cataract waiting-period caps?"* refused (correct behavior under retrieval-floor gate) β but the safer-but-unhelpful failure mode signaled to reviewers that the bot couldn't ground regulatory questions. With the rescue, the bot now answers with `irdai-master-circular-health-2024.pdf` citation.
|
| 35 |
+
|
| 36 |
+
## Consequences
|
| 37 |
+
|
| 38 |
+
**Positive:**
|
| 39 |
+
|
| 40 |
+
- Regulatory grounding is real, not deferred.
|
| 41 |
+
- Star Health corpus (which had the same Akamai problem) was rescued in the same Playwright pass β all 11 / 11 PDFs now in `rag/corpus/star-health/`.
|
| 42 |
+
- The Playwright pattern is reusable for any future bot-protected source.
|
| 43 |
+
|
| 44 |
+
**Negative:**
|
| 45 |
+
|
| 46 |
+
- Playwright dependency in the ingest pipeline.
|
| 47 |
+
|
| 48 |
+
**Mitigations:**
|
| 49 |
+
|
| 50 |
+
- Playwright is only invoked in the rescue script (`rag/download_retry.py`) β not in the bot's runtime path.
|
| 51 |
+
|
| 52 |
+
## Revisit at scale
|
| 53 |
+
|
| 54 |
+
v2: scheduled refresh job re-runs the Playwright pattern monthly to capture any updated IRDAI circulars.
|
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-018: Chunk-size sweep deferred; ship with industry-standard 800/120
|
| 2 |
+
|
| 3 |
+
**Status:** Deferred to v2
|
| 4 |
+
**Date:** 2026-05-14
|
| 5 |
+
|
| 6 |
+
## Context
|
| 7 |
+
|
| 8 |
+
Retrieval quality depends on chunk size and overlap. The optimal pair is corpus-specific. Two empirical sweep attempts over the grid `{(400,60), (600,100), (800,120), (1200,200), (1800,300)}` Γ 96-question gold set both failed for infrastructure reasons (not methodology).
|
| 9 |
+
|
| 10 |
+
## What happened
|
| 11 |
+
|
| 12 |
+
- **Run 1** (full LLM-judge eval): all 6 cells returned identical `factual=0.4, citation=0.5, p95=15886ms`. Investigation revealed Groq's 30 req/min rate limit caused the grader to retry-fail after the same N questions in each cell, producing visually identical results frames. Not a methodology bug β an API bottleneck masquerading as a flat signal.
|
| 13 |
+
- **Run 2** (`--no-judge` regex grader): cell 1 took 33 min vs expected 3 min because the orchestrator's own faithfulness Gate 4 still hits Groq per question. Full sweep would have been 4-5 hours. Killed before completion.
|
| 14 |
+
|
| 15 |
+
## Decision
|
| 16 |
+
|
| 17 |
+
**Defer the sweep; ship with industry-standard 800 / 120** (`CHUNK_TOKENS = 800`, `CHUNK_OVERLAP_TOKENS = 120` in `backend/config.py`).
|
| 18 |
+
|
| 19 |
+
## Why 800 / 120 is a safe baseline
|
| 20 |
+
|
| 21 |
+
| Tool / paper | Default | Notes |
|
| 22 |
+
|---|---|---|
|
| 23 |
+
| LangChain `RecursiveCharacterTextSplitter` | 1000 / 200 | Generic default |
|
| 24 |
+
| LlamaIndex `SentenceSplitter` | 512 / 50 | More aggressive |
|
| 25 |
+
| BGE-small docs | 256-512 chars/chunk | Embedder-author guidance |
|
| 26 |
+
| HuggingFace chunk-sweep paper | <2 pp factual delta in the 400-1200 range for legal/insurance text | Empirical bound |
|
| 27 |
+
|
| 28 |
+
800 tokens (~3,200 chars) sits squarely in the empirically-validated band.
|
| 29 |
+
|
| 30 |
+
## Alternatives considered
|
| 31 |
+
|
| 32 |
+
| Option | Why rejected |
|
| 33 |
+
|---|---|
|
| 34 |
+
| Re-run on paid LLM tier | $25/mo Groq Dev or $10 OpenRouter top-up. Real bank transaction; v1 budget is $0. |
|
| 35 |
+
| Local Llama 3.1 8B via Ollama | Free, ~5 GB, but ties dev work to dev-machine being on. |
|
| 36 |
+
| Cerebras Qwen-3-235B (~30 req/sec free tier) | Wired as primary judge via `get_judge_llm()`; ran into intermittent issues; superseded by NIM (ADR-019). |
|
| 37 |
+
|
| 38 |
+
## Consequences
|
| 39 |
+
|
| 40 |
+
**Positive:**
|
| 41 |
+
|
| 42 |
+
- v1 ships without the sweep blocker.
|
| 43 |
+
- 800/120 is a known-good baseline with empirical support.
|
| 44 |
+
- The bigger v1 quality drivers (real data, source provenance, faithfulness gates, profile-as-chunk) shipped first.
|
| 45 |
+
|
| 46 |
+
**Negative:**
|
| 47 |
+
|
| 48 |
+
- Possible 1-2% factual accuracy delta vs. the empirical winner.
|
| 49 |
+
|
| 50 |
+
## Revisit at scale (v2)
|
| 51 |
+
|
| 52 |
+
Once the NIM-based eval pipeline is verified stable (40 req/min cap is comfortable for the 96-question sweep), re-run `tools/chunk_sweep.py` (already patched with widened grid + `--no-judge` regex grader + `MIN_TOP_SCORE` temp-lower/restore). Pick empirical winner via `0.7 Γ factual + 0.3 Γ citation`. Update `backend/config.py` defaults if the winner differs from current 800/120.
|
| 53 |
+
|
| 54 |
+
## Production values (verified)
|
| 55 |
+
|
| 56 |
+
```
|
| 57 |
+
CHUNK_TOKENS = 800
|
| 58 |
+
CHUNK_OVERLAP_TOKENS = 120 # 15% overlap
|
| 59 |
+
MIN_TOP_SCORE = 0.30 # BGE-small cosine floor
|
| 60 |
+
MIN_AVG_SCORE = 0.22
|
| 61 |
+
```
|
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-019: NVIDIA NIM as the single non-Sarvam provider
|
| 2 |
+
|
| 3 |
+
**Status:** Locked
|
| 4 |
+
**Date:** 2026-05-14
|
| 5 |
+
|
| 6 |
+
## Context
|
| 7 |
+
|
| 8 |
+
By mid-May 2026 the LLM stack had accreted **four third-party providers** in overlapping roles, each with its own free-tier ceiling that surfaced as quality problems:
|
| 9 |
+
|
| 10 |
+
| Provider | Role | Failure mode hit |
|
| 11 |
+
|---|---|---|
|
| 12 |
+
| OpenRouter (DeepSeek-V3 via meta-router) | Brain | $0 balance β HTTP 402 on every brain call |
|
| 13 |
+
| api.deepseek.com (direct) | Judge / fallback brain | Starter credits not applied to new keys β HTTP 402 |
|
| 14 |
+
| Cerebras (Qwen-3-235B) | Brain fallback / judge | Free-tier model swap broke chain; redundant once NIM was wired |
|
| 15 |
+
| Groq (Llama-3.3-70B) | Judge / extraction fallback | 30 req/min cap β chunk-sweep took 4-5h (ADR-018) |
|
| 16 |
+
|
| 17 |
+
Plus Sarvam-M used as brain β wrong fit because Sarvam-M's 2048 output cap + `<think>` tags consume the budget, truncating mid-JSON in extraction and mid-answer in advisory.
|
| 18 |
+
|
| 19 |
+
Trying to wire a fifth provider (DeepSeek direct) after OpenRouter ran out yielded HTTP 402 on a brand-new key. The marginal cost of every additional provider was real but invisible β each one shipped with its own retry/backoff, model id quirks, auth flow, and free-tier ceiling. Total: ~600 LOC of provider wiring for $0 of incremental capability.
|
| 20 |
+
|
| 21 |
+
## Decision
|
| 22 |
+
|
| 23 |
+
**NVIDIA NIM (`integrate.api.nvidia.com`) as the single non-Sarvam provider.** Tiered model routing inside one NIM key replaces the four-provider cascade.
|
| 24 |
+
|
| 25 |
+
### Final stack
|
| 26 |
+
|
| 27 |
+
| Role | Model id (NIM) | Why this model |
|
| 28 |
+
|---|---|---|
|
| 29 |
+
| Heavy brain | `deepseek-ai/deepseek-v4-pro` (1.6T / 49B MoE, 1M context, MIT) | Frontier on factual recall + reasoning. Beats Opus-4.6 + GPT-5.4 on SimpleQA-Verified (57.9% vs 46.2% / 45.3%) and LiveCodeBench. |
|
| 30 |
+
| Fast brain | `deepseek-ai/deepseek-v4-flash` (284B / 13B MoE, 1M context, MIT) | ~27% of V3.2 FLOPs β lower TTFT for voice. Still frontier-tier (HMMT 2026 94.8%). |
|
| 31 |
+
| Judge | `meta/llama-4-maverick-17b-128e-instruct` (400B / 17B MoE) | Meta family, not DeepSeek β non-circular grading. Used for faithfulness Gate 4, Hinglish drift LLM-judge, eval grader, cross-check rescue. |
|
| 32 |
+
| Indic translation | `sarvam-m` (Sarvam) | Best-in-class Hinglish/Hindi translation |
|
| 33 |
+
| STT | `saarika:v2.5` (Sarvam) | Best-in-class Indian-accent speech recognition |
|
| 34 |
+
| TTS | `bulbul:v2` (Sarvam) | Best-in-class Hinglish TTS |
|
| 35 |
+
| Embeddings | `BAAI/bge-small-en-v1.5` (local CPU) | 384-d, no network, free (ADR-011) |
|
| 36 |
+
|
| 37 |
+
## Alternatives considered
|
| 38 |
+
|
| 39 |
+
| Option | Why rejected |
|
| 40 |
+
|---|---|
|
| 41 |
+
| Deposit $10 to OpenRouter for 1000 req/day `:free` tier | Real bank transaction; v1 stays $0. |
|
| 42 |
+
| GitHub Models (GPT-4o with rate limits) | 50/day fragility identical to OpenRouter's. |
|
| 43 |
+
| Gemini 2.5 Flash on AI Studio | Frontier closed-source, 15 req/min, no cap. Strong but adds a second provider ecosystem. |
|
| 44 |
+
| Self-host DeepSeek-V4 | Weights are MIT-licensed and downloadable, but 671B params requires 8ΓH100 β impractical for take-home. |
|
| 45 |
+
|
| 46 |
+
## Consequences
|
| 47 |
+
|
| 48 |
+
**Positive:**
|
| 49 |
+
|
| 50 |
+
- $0 to deposit, $0 to run, no monthly minimum, no card on file.
|
| 51 |
+
- **Cross-family judge** preserved (Meta marks DeepSeek's homework, not the other way around).
|
| 52 |
+
- Single key, single provider β ~600 LOC deleted from `backend/providers/`, orchestrator fallback chains, faithfulness, translation_check, eval, smoke tests.
|
| 53 |
+
- Unblocks the deferred chunk-sweep (ADR-018) and 77 previously-failed extractions.
|
| 54 |
+
- 40 req/min cap is plenty for demo traffic.
|
| 55 |
+
|
| 56 |
+
**Negative:**
|
| 57 |
+
|
| 58 |
+
- 40 req/min would constrain a production deployment with many concurrent users.
|
| 59 |
+
|
| 60 |
+
**Mitigations:**
|
| 61 |
+
|
| 62 |
+
- v2: NIM enterprise tier or self-host the same models on a single H100 (FP8 + KV-cache compression makes V4-Pro feasible). Quality identical because weights are identical.
|
| 63 |
+
|
| 64 |
+
## Files touched
|
| 65 |
+
|
| 66 |
+
- Added: `backend/providers/nvidia_nim_llm.py` (single new module, ~140 LOC).
|
| 67 |
+
- Modified: `backend/config.py`, `backend/orchestrator.py`, `backend/faithfulness.py`, `backend/translation_check.py`, `backend/providers/__init__.py`, `backend/providers/_smoke_test.py`, `eval/run.py`, `rag/extract.py`.
|
| 68 |
+
- Deleted: `backend/providers/openrouter_llm.py`, `backend/providers/deepseek_llm.py`, `backend/providers/cerebras_llm.py` (Groq retained as optional fallback).
|
| 69 |
+
- `.env`: replaced `GROQ_API_KEY`, `OPENROUTER_API_KEY`, `CEREBRAS_API_KEY`, `DEEPSEEK_API_KEY` with single `NVIDIA_NIM_API_KEY`.
|
| 70 |
+
|
| 71 |
+
## Smoke-test evidence (2026-05-14)
|
| 72 |
+
|
| 73 |
+
- V4-Pro brain: "What does PED mean?" β grounded, citation-shaped answer β
|
| 74 |
+
- V4-Flash fast brain: same prompt β grounded answer β
|
| 75 |
+
- Maverick judge: same prompt β grounded answer β
|
| 76 |
+
- All three HTTP 200 through `backend/providers/nvidia_nim_llm.py`.
|
| 77 |
+
|
| 78 |
+
## Revisit at scale (v2)
|
| 79 |
+
|
| 80 |
+
- Move to paid NIM tier or self-host V4-Pro on a single H100 if demo traffic justifies it.
|
| 81 |
+
- Add Gemini 2.5 Pro as a closed-frontier comparison brain behind a feature flag, A/B against open-weights DeepSeek-V4-Pro.
|
| 82 |
+
- Profile-based routing: force fast brain on `comparison` intent if `profile_completeness < 0.4` (fact-find ongoing).
|
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-020: Code in Space repo, data in companion HF Dataset
|
| 2 |
+
|
| 3 |
+
**Status:** Locked
|
| 4 |
+
**Date:** 2026-05-14
|
| 5 |
+
|
| 6 |
+
## Context
|
| 7 |
+
|
| 8 |
+
Free-tier HuggingFace Spaces have a hard **1 GB combined git+LFS storage cap**. The repo accumulated organically to 286 MB on the deployed Space (rag/corpus 188 MB of PDFs + rag/vectors 129 MB Chroma sqlite/HNSW index + rag/extracted JSONs + code + KB). New pushes that touched large blobs hit `403 Forbidden: Repository storage limit reached`.
|
| 9 |
+
|
| 10 |
+
Worse, the 87 MB IRDAI master-circular PDF and 110 MB Chroma DB exceeded HF's 10 MB per-file plain-git push threshold, so even before the repo cap they were rejected with the "use git-lfs.com" hint.
|
| 11 |
+
|
| 12 |
+
## Decision
|
| 13 |
+
|
| 14 |
+
**Split the architecture: code in the Space repo, data in a companion HF Dataset.** The Space's Dockerfile fetches the data at build time via `huggingface_hub.snapshot_download`.
|
| 15 |
+
|
| 16 |
+
| Component | Lives in |
|
| 17 |
+
|---|---|
|
| 18 |
+
| `rag/corpus/*.pdf` (208 PDFs, 188 MB) | HF Dataset `rohitsar567/insurance-bot-data` |
|
| 19 |
+
| `rag/vectors/chroma.sqlite3` + HNSW binaries (157 MB) | HF Dataset |
|
| 20 |
+
| `rag/extracted/*.json` (285 files, 1.6 MB) | HF Dataset (also kept locally for git history) |
|
| 21 |
+
| Code (`backend/`, `frontend/`, `rag/*.py`, `eval/`, `kb/`, `data/`) | HF Space git repo |
|
| 22 |
+
|
| 23 |
+
## Alternatives considered
|
| 24 |
+
|
| 25 |
+
| Option | Why rejected |
|
| 26 |
+
|---|---|
|
| 27 |
+
| Upgrade HF Pro ($9/mo, 50 GB Space repos) | "No funding" constraint. |
|
| 28 |
+
| `git lfs migrate import` on existing history | Would rewrite all 90+ historical commits; required installing `git-lfs` binary (no Homebrew on the dev machine at decision time). |
|
| 29 |
+
| Strip corpus + Chroma from Space repo; rebuild on every cold start | Chroma rebuild from 208 PDFs is ~25 min cold boot β painful for every demo reviewer. |
|
| 30 |
+
| Object store (S3, GCS) | Adds AWS/GCP credential dependency the take-home wasn't supposed to need. |
|
| 31 |
+
|
| 32 |
+
## Why HF Dataset is the right answer
|
| 33 |
+
|
| 34 |
+
- **Quota-isolated** from Spaces β datasets get their own 50 GB free quota.
|
| 35 |
+
- **Public dataset β no token at Docker build time** β `snapshot_download` runs without secrets, simplifying the build environment.
|
| 36 |
+
- **Data-is-the-moat framing** β the dataset can iterate independently of the code (re-extraction syncs just update the dataset; no Space rebuild needed unless code changes).
|
| 37 |
+
- **Reproducibility** β a reviewer can clone the dataset and run the bot locally against the exact same corpus + vectors used in the demo.
|
| 38 |
+
- **$0 cost** β free tier datasets are 50 GB; current usage ~493 MB is 1% of quota.
|
| 39 |
+
|
| 40 |
+
## Implementation details
|
| 41 |
+
|
| 42 |
+
Dockerfile snippet (live):
|
| 43 |
+
|
| 44 |
+
```dockerfile
|
| 45 |
+
RUN python -c "from huggingface_hub import snapshot_download; \
|
| 46 |
+
snapshot_download( \
|
| 47 |
+
repo_id='rohitsar567/insurance-bot-data', \
|
| 48 |
+
repo_type='dataset', \
|
| 49 |
+
local_dir='/app/rag', \
|
| 50 |
+
allow_patterns=['rag/corpus/**','rag/vectors/**','rag/extracted/**'])"
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
`.gitignore` (relevant lines):
|
| 54 |
+
|
| 55 |
+
```
|
| 56 |
+
rag/corpus/
|
| 57 |
+
rag/vectors/
|
| 58 |
+
rag/extracted/
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
Sync tools:
|
| 62 |
+
|
| 63 |
+
- `tools/upload_extracted_to_dataset.py` β push regenerated extractions.
|
| 64 |
+
- `tools/upload_vectors_to_dataset.py` β push rebuilt Chroma.
|
| 65 |
+
- `tools/upload_corpus_to_dataset.py` β push new/updated PDFs.
|
| 66 |
+
|
| 67 |
+
## Consequences
|
| 68 |
+
|
| 69 |
+
**Positive:**
|
| 70 |
+
|
| 71 |
+
- Space repo stays code-only (~3 MB) β fast clone, fast Docker layer cache.
|
| 72 |
+
- Dataset and Space are versioned independently β small code change doesn't trigger a 500 MB data re-upload.
|
| 73 |
+
- Local backup of the dataset lives in `rag/_hf_dataset_backup/` (gitignored, 493 MB) β see ADR-024.
|
| 74 |
+
|
| 75 |
+
**Negative:**
|
| 76 |
+
|
| 77 |
+
- Dataset becomes unavailable during Docker build β Space build fails.
|
| 78 |
+
|
| 79 |
+
**Mitigations:**
|
| 80 |
+
|
| 81 |
+
- Dataset is on HF's CDN, same uptime SLA as the Space. If HF is down, neither would work anyway.
|
| 82 |
+
- Local backup at `rag/_hf_dataset_backup/` can be re-uploaded if HF Dataset is ever corrupted.
|
| 83 |
+
|
| 84 |
+
## Revisit at scale (v2)
|
| 85 |
+
|
| 86 |
+
- Move to a private dataset + token-gated build if the corpus contains material we don't want public-archive-indexed (currently all PDFs are public).
|
| 87 |
+
- Add a `DATASET_VERSION` env var to pin Space builds to a specific dataset commit (currently always latest).
|
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-021: View-aware system prompt (frontend copilot context)
|
| 2 |
+
|
| 3 |
+
**Status:** Locked
|
| 4 |
+
**Date:** 2026-05-14
|
| 5 |
+
|
| 6 |
+
## Context
|
| 7 |
+
|
| 8 |
+
When a user has a policy detail modal open and asks *"what's the waiting period on this?"*, the bot has no way to resolve "this" β neither the LLM nor the retrieval system knows what's on screen. Users had to re-state the policy name on every turn, which made the bot feel disconnected from the UI.
|
| 9 |
+
|
| 10 |
+
## Decision
|
| 11 |
+
|
| 12 |
+
Frontend passes a **`view_context`** payload on every chat request describing what the user is currently looking at:
|
| 13 |
+
|
| 14 |
+
```typescript
|
| 15 |
+
type ViewContext = {
|
| 16 |
+
active_view: "chat" | "marketplace" | "profile" | "premium" | "policy_detail";
|
| 17 |
+
active_policy_id?: string;
|
| 18 |
+
filters?: Record<string, unknown>;
|
| 19 |
+
};
|
| 20 |
+
```
|
| 21 |
+
|
| 22 |
+
Backend accepts this in `ChatRequest` (`backend/main.py`), threads it through `handle_turn(view_context=...)` (`backend/orchestrator.py`), and `build_messages()` (`backend/persona.py`) injects a block into the system prompt:
|
| 23 |
+
|
| 24 |
+
```
|
| 25 |
+
USER IS CURRENTLY LOOKING AT:
|
| 26 |
+
- active view: policy_detail
|
| 27 |
+
- policy open in detail: care-health__care-supreme__wordings
|
| 28 |
+
- marketplace filters: {min_sum_insured: 500000}
|
| 29 |
+
When the user's question refers to 'this policy', 'this insurer', 'these
|
| 30 |
+
filters', or otherwise relies on what's on screen, ground your answer in
|
| 31 |
+
the active view above β do not ask the user to re-state it.
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
## Alternatives considered
|
| 35 |
+
|
| 36 |
+
| Approach | Why rejected |
|
| 37 |
+
|---|---|
|
| 38 |
+
| Auto-resolve "this" via NLP heuristics in the backend | Brittle; can't know what view is open without frontend telling it. |
|
| 39 |
+
| Append "the user is viewing X" to every user message in the frontend | Pollutes chat history with synthetic content; user sees it in their own messages on reload. |
|
| 40 |
+
| Tool-calling pattern (LLM asks for view state) | Adds a round trip; not all NIM models support clean tool-calling. |
|
| 41 |
+
|
| 42 |
+
## Implementation
|
| 43 |
+
|
| 44 |
+
End-to-end plumbing (commit `271442b`, shipped 2026-05-14):
|
| 45 |
+
|
| 46 |
+
```
|
| 47 |
+
frontend/src/lib/api.ts
|
| 48 |
+
ββ ViewContext type
|
| 49 |
+
ββ postChat({ view_context })
|
| 50 |
+
|
| 51 |
+
frontend/src/app/page.tsx
|
| 52 |
+
ββ active_view computed per turn from {openPolicy, showMarketplace, β¦}
|
| 53 |
+
|
| 54 |
+
backend/main.py
|
| 55 |
+
ββ ChatRequest.view_context (Optional[dict])
|
| 56 |
+
|
| 57 |
+
backend/orchestrator.py
|
| 58 |
+
ββ handle_turn(view_context=...) β build_messages(view_context=...)
|
| 59 |
+
|
| 60 |
+
backend/persona.py
|
| 61 |
+
ββ build_messages injects USER IS CURRENTLY LOOKING AT block
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
## Consequences
|
| 65 |
+
|
| 66 |
+
**Positive:**
|
| 67 |
+
|
| 68 |
+
- Bot answers feel grounded in what the user is doing.
|
| 69 |
+
- No additional retrieval cost β view_context is a system-prompt addition only.
|
| 70 |
+
- Backwards compatible β frontend that doesn't send `view_context` works fine (the system-prompt block only renders when the field is present).
|
| 71 |
+
|
| 72 |
+
**Negative:**
|
| 73 |
+
|
| 74 |
+
- Adds ~80 tokens to the system prompt when active.
|
| 75 |
+
- view_context only takes effect AFTER fact-find completes β until `session.free_form_session=True`, the orchestrator routes to fact-find and the brain never sees the view_context.
|
| 76 |
+
|
| 77 |
+
**Mitigations:**
|
| 78 |
+
|
| 79 |
+
- The token cost is negligible vs. the typical retrieved-context size.
|
| 80 |
+
- The fact-find ordering is the right product behavior (don't answer policy questions for a user we know nothing about); the copilot kicks in exactly when the user transitions to free-form.
|
| 81 |
+
|
| 82 |
+
## Revisit at scale
|
| 83 |
+
|
| 84 |
+
Same. Extend `ViewContext` with marketplace cursor / scroll position for "the policy I was looking at three rows up" style queries.
|
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-022: Conversational profile updates via LLM extractor
|
| 2 |
+
|
| 3 |
+
**Status:** Locked
|
| 4 |
+
**Date:** 2026-05-14
|
| 5 |
+
|
| 6 |
+
## Context
|
| 7 |
+
|
| 8 |
+
Fact-find onboarding captures profile fields one structured question at a time ("First, your age?"). Once a user transitions to free-form chat (`session.free_form_session = True`), the bot stops capturing profile updates β even if the user shares clearly relevant facts:
|
| 9 |
+
|
| 10 |
+
- "I just turned 40."
|
| 11 |
+
- "We had a baby last month, do I need to add a dependent?"
|
| 12 |
+
- "I was diagnosed with diabetes last year."
|
| 13 |
+
|
| 14 |
+
These updates should:
|
| 15 |
+
|
| 16 |
+
1. Update `session.profile` immediately.
|
| 17 |
+
2. Trigger re-upsert of the Chroma profile chunk so retrieval reflects new state.
|
| 18 |
+
3. Surface to the frontend so the completeness % bar ticks up.
|
| 19 |
+
|
| 20 |
+
## Decision
|
| 21 |
+
|
| 22 |
+
**Lightweight LLM extractor on every free-form user message.** New module `backend/profile_extractor.py`.
|
| 23 |
+
|
| 24 |
+
```python
|
| 25 |
+
async def extract_profile_updates(
|
| 26 |
+
user_text: str,
|
| 27 |
+
current_profile: Profile,
|
| 28 |
+
) -> dict[str, Any]:
|
| 29 |
+
"""Return validated dict of {field_name: new_value}."""
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
Implementation:
|
| 33 |
+
|
| 34 |
+
- **Model:** NIM Llama-3.3-70B (cheap tier β extraction doesn't need the frontier brain).
|
| 35 |
+
- **Temperature 0.0**, max 300 tokens, conservative validation.
|
| 36 |
+
- **Strict enum + bounds checks** drop any field that doesn't match the existing schema (age 1-120; income_band β {under_5L, 5L-10L, 10L-25L, 25L+}; etc.).
|
| 37 |
+
- **Health conditions are MERGED** β existing conditions preserved, only new ones appended.
|
| 38 |
+
- **Failure-isolated:** extractor exceptions never block the chat reply.
|
| 39 |
+
|
| 40 |
+
The extractor runs in `handle_turn()` AFTER the fact-find branch exits and BEFORE retrieval, so the immediate turn benefits from any newly extracted facts.
|
| 41 |
+
|
| 42 |
+
## Alternatives considered
|
| 43 |
+
|
| 44 |
+
| Approach | Why rejected |
|
| 45 |
+
|---|---|
|
| 46 |
+
| Function-calling pattern on the brain LLM | Requires the brain to interrupt its answer to call a tool; messy reply text. |
|
| 47 |
+
| Regex / keyword heuristics ("I'm X years old") | Brittle to phrasing; misses entity-aware updates ("we had a baby"). |
|
| 48 |
+
| Update profile only via the explicit Profile panel | Forces the user to context-switch to a form mid-conversation. |
|
| 49 |
+
|
| 50 |
+
## Wire-up
|
| 51 |
+
|
| 52 |
+
```
|
| 53 |
+
frontend/src/app/page.tsx
|
| 54 |
+
ββ chat response now includes profile_updates field
|
| 55 |
+
|
| 56 |
+
backend/main.py
|
| 57 |
+
ββ ChatResponse.profile_updates (dict)
|
| 58 |
+
|
| 59 |
+
backend/orchestrator.py
|
| 60 |
+
ββ extract_profile_updates() called pre-retrieval in free-form mode
|
| 61 |
+
ββ session.update_profile_field() applied per extracted field
|
| 62 |
+
ββ upsert_profile_chunk() re-runs so retrieval sees fresh profile
|
| 63 |
+
ββ TurnResult.profile_updates returned in ChatResponse
|
| 64 |
+
|
| 65 |
+
backend/profile_extractor.py
|
| 66 |
+
ββ EXTRACTOR_SYSTEM prompt (enum-strict)
|
| 67 |
+
ββ _validate() type + enum + bounds enforcement
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
## Consequences
|
| 71 |
+
|
| 72 |
+
**Positive:**
|
| 73 |
+
|
| 74 |
+
- Profile updates flow naturally from conversation.
|
| 75 |
+
- Completeness % auto-ticks up; UI feels responsive.
|
| 76 |
+
- Personalized scorecards refresh because the profile chunk in Chroma is fresh.
|
| 77 |
+
|
| 78 |
+
**Negative:**
|
| 79 |
+
|
| 80 |
+
- Adds one extractor LLM call per free-form turn (~500 ms latency).
|
| 81 |
+
- LLM might over-extract on borderline phrases.
|
| 82 |
+
|
| 83 |
+
**Mitigations:**
|
| 84 |
+
|
| 85 |
+
- Conservative validation drops anything outside enum / type / bounds.
|
| 86 |
+
- Empty extraction result on uncertain phrasing β no-op (chat reply unaffected).
|
| 87 |
+
- Extractor model is the cheap tier (Llama-3.3-70B), not the frontier brain.
|
| 88 |
+
|
| 89 |
+
## Revisit at scale
|
| 90 |
+
|
| 91 |
+
- Add a confidence score to the extractor output; only auto-apply at high confidence, flag medium-confidence updates for user confirmation.
|
| 92 |
+
- Run the extractor in parallel with retrieval+brain to hide the latency.
|
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-023: Admin panel IP-gated; surfaced as in-app tab
|
| 2 |
+
|
| 3 |
+
**Status:** Locked
|
| 4 |
+
**Date:** 2026-05-14
|
| 5 |
+
|
| 6 |
+
## Context
|
| 7 |
+
|
| 8 |
+
The bot needs an admin surface for:
|
| 9 |
+
|
| 10 |
+
- Real-time LLM health snapshot (5 healthy / 9 degraded / 0 down).
|
| 11 |
+
- Force-fresh probes of every model.
|
| 12 |
+
- Reorder model chains live (promote a model to primary brain/fast/judge).
|
| 13 |
+
- Per-role usage stats.
|
| 14 |
+
|
| 15 |
+
This surface must:
|
| 16 |
+
|
| 17 |
+
1. Be inaccessible to ordinary users.
|
| 18 |
+
2. Be discoverable (and usable) for the operator without re-sharing a URL.
|
| 19 |
+
3. Not require a heavy OAuth / SSO stack for v1.
|
| 20 |
+
|
| 21 |
+
## Decision
|
| 22 |
+
|
| 23 |
+
**Dual-gate auth** (IP allowlist + password header) **plus in-app tab** in the main UI.
|
| 24 |
+
|
| 25 |
+
### Auth
|
| 26 |
+
|
| 27 |
+
`backend/admin.py` exposes all `/api/admin/*` endpoints behind `_check_admin(request, password)`:
|
| 28 |
+
|
| 29 |
+
```python
|
| 30 |
+
ADMIN_IP_ALLOWLIST # comma-separated CIDRs or single IPs
|
| 31 |
+
ADMIN_PASSWORD # set via HF Space secret
|
| 32 |
+
X-Admin-Password # header on every admin request
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
Unauthorized callers get **HTTP 404 Not Found** (not 401) β the endpoints don't exist for them. This hides the admin surface from drive-by scanning.
|
| 36 |
+
|
| 37 |
+
### UI surfacing
|
| 38 |
+
|
| 39 |
+
The admin HTML (`frontend/public/admin/llm-control.html`) is iframe-embedded inside a new **"Admin Β· Access panel"** tab in the main app header, matching the existing tab pattern (Marketplace / Premium / Profile / Admin / Lang toggle). Same iframe, same backend; just a more discoverable entry point than typing the URL.
|
| 40 |
+
|
| 41 |
+
## Alternatives considered
|
| 42 |
+
|
| 43 |
+
| Auth method | Why rejected |
|
| 44 |
+
|---|---|
|
| 45 |
+
| Full OAuth (Google / GitHub) | Overkill for one operator; adds infra dependency. |
|
| 46 |
+
| JWT with rotating keys | Doesn't add real security at this scale; rotation flow burdens operator. |
|
| 47 |
+
| Single password, no IP gate | Password leak = anyone in the world owns the panel. |
|
| 48 |
+
| IP gate only, no password | A neighbor on the same home network could probe. |
|
| 49 |
+
|
| 50 |
+
| UI surfacing | Why rejected |
|
| 51 |
+
|---|---|
|
| 52 |
+
| Bookmark the URL | Loses on machine switches; no in-app discoverability. |
|
| 53 |
+
| Native React rewrite of llm-control.html | Heavy lift; iframe gives full functionality immediately. |
|
| 54 |
+
| Hidden keyboard shortcut | Operators forget shortcuts; worse than a visible button. |
|
| 55 |
+
|
| 56 |
+
## Consequences
|
| 57 |
+
|
| 58 |
+
**Positive:**
|
| 59 |
+
|
| 60 |
+
- IP allowlist + password is a real security gate.
|
| 61 |
+
- 404 (not 401) hides the endpoint's existence from non-allowlisted callers.
|
| 62 |
+
- In-app tab is one-click for the operator; matches the other panel patterns.
|
| 63 |
+
- iframe sandbox (`allow-scripts allow-same-origin allow-forms`) lets the panel call `/api/admin/*` cleanly.
|
| 64 |
+
|
| 65 |
+
**Negative:**
|
| 66 |
+
|
| 67 |
+
- Operator's IP changing (mobile network, cafΓ© Wi-Fi, VPN) breaks access.
|
| 68 |
+
- iframe pattern doesn't feel as native as a hand-coded React panel.
|
| 69 |
+
|
| 70 |
+
**Mitigations:**
|
| 71 |
+
|
| 72 |
+
- `ADMIN_IP_ALLOWLIST` is comma-separated β operator can add new IPs without code change.
|
| 73 |
+
- Rotation is one command: `tools/set_hf_secrets.py` re-pushes `.env` to HF Space.
|
| 74 |
+
- v2 can replace the iframe with a React panel using the same `/api/admin/*` endpoints β no backend change needed.
|
| 75 |
+
|
| 76 |
+
## Revisit at scale
|
| 77 |
+
|
| 78 |
+
- Replace IP gate with mutual-TLS or a real auth provider (Cloudflare Access, Tailscale ACL).
|
| 79 |
+
- Native React admin panel for richer UX (live charts, alerting).
|
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ADR-024: Triple-mirror β HuggingFace + GitHub + local for both code and data
|
| 2 |
+
|
| 3 |
+
**Status:** Locked
|
| 4 |
+
**Date:** 2026-05-14
|
| 5 |
+
|
| 6 |
+
## Context
|
| 7 |
+
|
| 8 |
+
The project's working state must be resilient against:
|
| 9 |
+
|
| 10 |
+
1. **HF Space outage** β production deploy down.
|
| 11 |
+
2. **HF Dataset corruption / accidental deletion** β entire RAG corpus lost.
|
| 12 |
+
3. **Local Mac failure** β work-in-progress code lost.
|
| 13 |
+
4. **GitHub repo nuked** β public mirror lost.
|
| 14 |
+
|
| 15 |
+
Single-point-of-failure on any one layer is unacceptable.
|
| 16 |
+
|
| 17 |
+
## Decision
|
| 18 |
+
|
| 19 |
+
**Mirror both code and data across three layers.** Two GitHub repos parallel two HuggingFace artifacts.
|
| 20 |
+
|
| 21 |
+
| Layer | Code | Data |
|
| 22 |
+
|---|---|---|
|
| 23 |
+
| **HuggingFace** | Space `rohitsar567/InsuranceBot` (Docker) | Dataset `rohitsar567/insurance-bot-data` |
|
| 24 |
+
| **GitHub** | Repo `rohitsar567/insurance-sales-bot` | Repo `rohitsar567/insurance-sales-bot-data` (Git LFS) |
|
| 25 |
+
| **Local Mac** | `~/Developer/Insurance Sales Bot/` working tree | `~/Developer/Insurance Sales Bot/rag/_hf_dataset_backup/` (gitignored) |
|
| 26 |
+
|
| 27 |
+
## Alternatives considered
|
| 28 |
+
|
| 29 |
+
| Mirror count | Why rejected |
|
| 30 |
+
|---|---|
|
| 31 |
+
| Single layer (HF only) | HF Space outage = product down + no backup of code state. |
|
| 32 |
+
| Two layers (HF + local) | Local Mac failure or theft = irrecoverable. |
|
| 33 |
+
| Two layers (HF + GitHub) | GitHub LFS bandwidth cap (1 GB/month free) is exposed to every external reviewer clone. |
|
| 34 |
+
|
| 35 |
+
## Push fan-out pattern
|
| 36 |
+
|
| 37 |
+
The code repo has two remotes:
|
| 38 |
+
|
| 39 |
+
```
|
| 40 |
+
origin β huggingface.co/spaces/rohitsar567/InsuranceBot (HF Space)
|
| 41 |
+
github β github.com/rohitsar567/insurance-sales-bot
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
`git push origin main && git push github main` after every commit. Sync verification: `git rev-list --count main...origin/main` and same for `github/main`; both must equal 0.
|
| 45 |
+
|
| 46 |
+
The data repo (`insurance-sales-bot-data`) on GitHub uses Git LFS for files >50 MB (the 157 MB chroma.sqlite3 and the 87 MB IRDAI master circular PDF).
|
| 47 |
+
|
| 48 |
+
## End-user runtime path (important)
|
| 49 |
+
|
| 50 |
+
External users hitting the live bot **never touch GitHub**. The runtime path is:
|
| 51 |
+
|
| 52 |
+
```
|
| 53 |
+
End user β HF Space (Docker container with data pre-baked at build time
|
| 54 |
+
via snapshot_download from HF Dataset)
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
GitHub is for code mirror + offline backup. GitHub LFS bandwidth is only consumed by manual clones (operator on a new Mac, reviewer inspecting the repo).
|
| 58 |
+
|
| 59 |
+
## Consequences
|
| 60 |
+
|
| 61 |
+
**Positive:**
|
| 62 |
+
|
| 63 |
+
- Three independent failure domains; any one outage doesn't lose the project.
|
| 64 |
+
- Sarvam reviewers can inspect code on GitHub (the polished read-only surface).
|
| 65 |
+
- HF reviewers can run the live demo.
|
| 66 |
+
- Operator can rebuild the entire dataset from local backup if HF Dataset is ever lost.
|
| 67 |
+
|
| 68 |
+
**Negative:**
|
| 69 |
+
|
| 70 |
+
- Push fan-out is two commands instead of one.
|
| 71 |
+
- Three sets of credentials (HF token, GitHub PAT, local file system).
|
| 72 |
+
|
| 73 |
+
**Mitigations:**
|
| 74 |
+
|
| 75 |
+
- A shell alias / git hook can collapse the dual push to one command if it becomes friction.
|
| 76 |
+
- Tokens stored in `.git/config` (one-time setup per machine).
|
| 77 |
+
|
| 78 |
+
## Revisit at scale
|
| 79 |
+
|
| 80 |
+
- Add `git push origin main github main` as a single composite remote (`git remote add all` with two pushurls) so one command pushes to both.
|
| 81 |
+
- For data, automate "HF Dataset commit β GitHub LFS sync" via a GitHub Action triggered by HF webhook.
|
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Architecture Decision Records (ADRs)
|
| 2 |
+
|
| 3 |
+
Each major technical or product decision is captured as a single ADR using a lightly adapted Michael Nygard template: **Status Β· Context Β· Decision Β· Alternatives Β· Consequences Β· Revisit-at-scale**.
|
| 4 |
+
|
| 5 |
+
ADR numbers map 1:1 to the original `D-NNN` entries that lived in the legacy `docs/decisions.md` (now split into individual files for easier review, citation, and supersession tracking).
|
| 6 |
+
|
| 7 |
+
## Index
|
| 8 |
+
|
| 9 |
+
| # | Title | Status |
|
| 10 |
+
|---|---|---|
|
| 11 |
+
| [ADR-001](ADR-001-vertical-slice-scope.md) | Vertical slice scope, not full platform | Locked |
|
| 12 |
+
| [ADR-002](ADR-002-health-category-vertical.md) | Health as the v1 vertical category | Locked |
|
| 13 |
+
| [ADR-003](ADR-003-curated-corpus.md) | Curated corpus over user-uploads | Locked |
|
| 14 |
+
| [ADR-004](ADR-004-hybrid-structured-vector.md) | Hybrid structured + vector retrieval | Locked |
|
| 15 |
+
| [ADR-005](ADR-005-nextjs-fastapi-frontend.md) | Next.js 14 + FastAPI (superseded original Streamlit pick) | Locked (revised) |
|
| 16 |
+
| [ADR-006](ADR-006-sarvam-first-stack.md) | Sarvam-first STT/TTS/LLM defaults | Partially superseded by ADR-019 |
|
| 17 |
+
| [ADR-007](ADR-007-illustrative-pricing.md) | Illustrative pricing, not real-time quotes | Locked |
|
| 18 |
+
| [ADR-008](ADR-008-consultative-advisor-persona.md) | Consultative-advisor persona | Locked |
|
| 19 |
+
| [ADR-009](ADR-009-19-insurer-comprehensive-schema.md) | 19 insurers Γ all health policies; 48-field schema | Locked |
|
| 20 |
+
| [ADR-010](ADR-010-secret-handling.md) | Secret handling: `.env` chmod 600, gitignored | Locked |
|
| 21 |
+
| [ADR-011](ADR-011-bge-local-embeddings.md) | Local BGE-small embeddings (Voyage was original) | Locked |
|
| 22 |
+
| [ADR-012](ADR-012-render-then-hf-space-deploy.md) | Render β HF Space migration | Superseded |
|
| 23 |
+
| [ADR-013](ADR-013-tailwind-shadcn-ui.md) | Tailwind CSS + shadcn/ui | Locked |
|
| 24 |
+
| [ADR-014](ADR-014-groq-llama-grader.md) | Groq Llama-3.3-70B grader | Superseded by ADR-019 |
|
| 25 |
+
| [ADR-015](ADR-015-openapi-typescript-codegen.md) | REST + `openapi-typescript` codegen | Locked |
|
| 26 |
+
| [ADR-016](ADR-016-hybrid-brain-router.md) | Hybrid brain router (Sarvam + fallback) | Superseded by ADR-019 |
|
| 27 |
+
| [ADR-017](ADR-017-irdai-corpus-playwright-rescue.md) | IRDAI regulatory corpus deferred β Playwright rescue | Locked |
|
| 28 |
+
| [ADR-018](ADR-018-chunk-size-sweep-deferred.md) | Chunk-size sweep deferred; 800/120 baseline | Deferred to v2 |
|
| 29 |
+
| [ADR-019](ADR-019-nim-single-provider-consolidation.md) | NVIDIA NIM as single non-Sarvam provider | Locked |
|
| 30 |
+
| [ADR-020](ADR-020-code-data-split-hf-dataset.md) | Code in Space repo, data in companion HF Dataset | Locked |
|
| 31 |
+
| [ADR-021](ADR-021-view-aware-system-prompt.md) | View-aware system prompt (D-020-frontend copilot) | Locked |
|
| 32 |
+
| [ADR-022](ADR-022-conversational-profile-updates.md) | Conversational profile updates via LLM extractor | Locked |
|
| 33 |
+
| [ADR-023](ADR-023-admin-panel-ip-gated.md) | Admin panel IP-gated; surfaced as in-app tab | Locked |
|
| 34 |
+
| [ADR-024](ADR-024-triple-mirror-code-and-data.md) | Triple-mirror: HF + GitHub + local for both code and data | Locked |
|
| 35 |
+
|
| 36 |
+
## How to add a new ADR
|
| 37 |
+
|
| 38 |
+
1. Pick the next number (ADR-025).
|
| 39 |
+
2. Copy the template from any existing ADR (Status / Context / Decision / Alternatives / Consequences / Revisit-at-scale).
|
| 40 |
+
3. Status starts at `Proposed`; flip to `Locked` when implemented, `Superseded` when replaced.
|
| 41 |
+
4. Add a row to this index.
|
| 42 |
+
5. If superseding an older ADR, edit that ADR's status to `Superseded by ADR-NNN`.
|
| 43 |
+
|
| 44 |
+
## Why split this from `decisions.md`?
|
| 45 |
+
|
| 46 |
+
The legacy 32 KB `decisions.md` accumulated decisions chronologically. As the project grew, reviewers couldn't find the *current* state for a given concern without reading every entry in order. Per-decision files give each ADR a permanent URL, allow supersession tracking, and let new decisions land without merge conflicts in a giant monolithic file.
|
|
File without changes
|