rohitsar567 Claude Opus 4.7 (1M context) commited on
Commit
3dab53c
·
1 Parent(s): f81328f

feat(audit): 100-persona multi-scenario simulation framework

Browse files

Three-stage audit harness for the non-speech parts of the bot:

tools/audit/personas.py 100 personas (10 archetypes × 10 demographics).
Cross-product engineered so each persona is a
unique (archetype, demo, style) triple.

tools/audit/flows.py 30-turn flow per persona: opening + 9 fact-find
answers + 10 free-form policy Qs (anchored on
persona concerns) + 5 edge cases (retries,
view-shift, mid-profile-update, view-aware) +
5 adversarial (OOC, multi-policy, regulatory,
closing-resistance, summary).

tools/audit/run_audit.py Concurrent async runner with NIM rate-limit
guard (1.8s/dispatch × 4 workers = 35 req/min,
under NIM's 40/min cap). Resumable: completed
persona transcripts land in
audit_results/<run_id>/transcripts/ and skip
on restart. 5xx retries with exponential
backoff (3s, 8s, 18s).

tools/audit/analyze.py Scans transcripts → markdown report covering
refusal rate, brain routing, intent dist,
profile-field capture, faithfulness fails,
latency p50/p95/p99, worst-refusers and
worst-errored personas, per-archetype +
per-style breakdowns.

Run for the full 100×30 audit:
python tools/audit/run_audit.py --workers 4 --delay 1.8
python tools/audit/analyze.py

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

tools/audit/analyze.py ADDED
@@ -0,0 +1,305 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Analyze audit transcripts → produce a markdown report.
2
+
3
+ Looks across all transcripts in audit_results/<run_id>/transcripts/ for:
4
+ - completion rates (turns reached / 30)
5
+ - error / timeout rates (HTTP 5xx, network failures)
6
+ - refusal rate (blocked=true or faithfulness_passed=false)
7
+ - profile-completeness progression (which fact-find fields landed)
8
+ - brain routing distribution (V4-Pro / V4-Flash / Maverick cross-check)
9
+ - intent classification distribution
10
+ - citation density (avg citations per non-blocked reply)
11
+ - latency p50 / p95
12
+ - failure-pattern clusters (recurring failure modes by archetype / style)
13
+ - stuck-in-fact-find: where the bot kept re-asking the same question
14
+
15
+ Output: audit_results/<run_id>/report.md
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import argparse
20
+ import collections
21
+ import json
22
+ import re
23
+ import statistics
24
+ from pathlib import Path
25
+ from typing import Any
26
+
27
+ RESULTS_ROOT = Path(__file__).resolve().parent.parent.parent / "audit_results"
28
+
29
+
30
+ def _load_transcripts(run_dir: Path) -> list[dict[str, Any]]:
31
+ out = []
32
+ for f in sorted((run_dir / "transcripts").glob("*.json")):
33
+ try:
34
+ out.append(json.loads(f.read_text()))
35
+ except Exception as e:
36
+ print(f" warn: failed to read {f.name}: {e}")
37
+ return out
38
+
39
+
40
+ def _percentile(xs: list[float], p: float) -> float | None:
41
+ if not xs:
42
+ return None
43
+ xs = sorted(xs)
44
+ k = int(round((p / 100) * (len(xs) - 1)))
45
+ return xs[k]
46
+
47
+
48
+ def _summarize_brain(turns: list[dict]) -> dict[str, int]:
49
+ out: dict[str, int] = collections.Counter()
50
+ for t in turns:
51
+ b = t.get("brain_used")
52
+ if b:
53
+ # Coarse-grain: just the prefix before :: or +
54
+ key = re.split(r"[:+]", b, maxsplit=1)[0]
55
+ out[key] += 1
56
+ return dict(out)
57
+
58
+
59
+ def _intent_dist(turns: list[dict]) -> dict[str, int]:
60
+ return dict(collections.Counter(t.get("intent") for t in turns if t.get("intent")))
61
+
62
+
63
+ def _profile_progress(turns: list[dict]) -> dict[str, Any]:
64
+ """How many distinct profile fields ended up captured across all turns?
65
+ Returns a dict with the final set + the last turn at which each appeared
66
+ so we can see if updates flowed through `profile_updates`.
67
+ """
68
+ captured: dict[str, int] = {} # field → first turn it appeared
69
+ for t in turns:
70
+ pu = t.get("profile_updates") or {}
71
+ for k in pu:
72
+ captured.setdefault(k, t.get("turn", 0))
73
+ return {"captured_fields": captured, "fields_captured": len(captured)}
74
+
75
+
76
+ def _stuck_in_factfind(turns: list[dict]) -> int:
77
+ """Count turns where the brain stayed in needs_finder::* across N+ turns."""
78
+ nf_turns = [t for t in turns if (t.get("brain_used") or "").startswith("needs_finder")]
79
+ return len(nf_turns)
80
+
81
+
82
+ def _refusals(turns: list[dict]) -> int:
83
+ return sum(1 for t in turns if t.get("blocked"))
84
+
85
+
86
+ def _faithfulness_fails(turns: list[dict]) -> int:
87
+ return sum(1 for t in turns if t.get("faithfulness_passed") is False)
88
+
89
+
90
+ def _reask_clarify_count(turns: list[dict]) -> int:
91
+ return sum(1 for t in turns if "reask_clarify" in (t.get("brain_used") or ""))
92
+
93
+
94
+ def _citation_density(turns: list[dict]) -> float | None:
95
+ counts = [len(t.get("citations") or []) for t in turns if not t.get("blocked") and t.get("citations") is not None]
96
+ if not counts:
97
+ return None
98
+ return round(sum(counts) / len(counts), 2)
99
+
100
+
101
+ def _errors(turns: list[dict]) -> int:
102
+ return sum(1 for t in turns if t.get("error"))
103
+
104
+
105
+ def analyze_one(transcript: dict[str, Any]) -> dict[str, Any]:
106
+ persona = transcript["persona"]
107
+ turns = transcript.get("turns", [])
108
+ latencies = [t.get("latency_ms") for t in turns if isinstance(t.get("latency_ms"), int)]
109
+ return {
110
+ "persona_id": persona["persona_id"],
111
+ "archetype": persona["archetype"],
112
+ "style": persona["style"],
113
+ "name": persona["name"],
114
+ "completed_turns": len(turns),
115
+ "errors": _errors(turns),
116
+ "refusals": _refusals(turns),
117
+ "faithfulness_fails": _faithfulness_fails(turns),
118
+ "reask_clarify": _reask_clarify_count(turns),
119
+ "stuck_in_factfind": _stuck_in_factfind(turns),
120
+ "brain_dist": _summarize_brain(turns),
121
+ "intent_dist": _intent_dist(turns),
122
+ "profile_progress": _profile_progress(turns),
123
+ "citation_density": _citation_density(turns),
124
+ "latency_p50_ms": _percentile(latencies, 50),
125
+ "latency_p95_ms": _percentile(latencies, 95),
126
+ }
127
+
128
+
129
+ def build_report(run_dir: Path) -> Path:
130
+ transcripts = _load_transcripts(run_dir)
131
+ if not transcripts:
132
+ print(f"no transcripts in {run_dir}/transcripts/")
133
+ return run_dir / "report.md"
134
+
135
+ per_persona = [analyze_one(t) for t in transcripts]
136
+
137
+ n = len(per_persona)
138
+ total_turns = sum(p["completed_turns"] for p in per_persona)
139
+ total_errors = sum(p["errors"] for p in per_persona)
140
+ total_refusals = sum(p["refusals"] for p in per_persona)
141
+ total_ffails = sum(p["faithfulness_fails"] for p in per_persona)
142
+ total_reask = sum(p["reask_clarify"] for p in per_persona)
143
+ avg_completed = round(total_turns / n, 1) if n else 0
144
+
145
+ all_latencies = []
146
+ for t in transcripts:
147
+ for turn in t.get("turns", []):
148
+ if isinstance(turn.get("latency_ms"), int):
149
+ all_latencies.append(turn["latency_ms"])
150
+
151
+ # Aggregate brain + intent
152
+ agg_brain: dict[str, int] = collections.Counter()
153
+ agg_intent: dict[str, int] = collections.Counter()
154
+ for p in per_persona:
155
+ for k, v in p["brain_dist"].items():
156
+ agg_brain[k] += v
157
+ for k, v in p["intent_dist"].items():
158
+ agg_intent[k] += v
159
+
160
+ # By archetype
161
+ by_arch: dict[str, list[dict]] = collections.defaultdict(list)
162
+ for p in per_persona:
163
+ by_arch[p["archetype"]].append(p)
164
+
165
+ # By style
166
+ by_style: dict[str, list[dict]] = collections.defaultdict(list)
167
+ for p in per_persona:
168
+ by_style[p["style"]].append(p)
169
+
170
+ # Profile-field coverage across all personas
171
+ field_counts: dict[str, int] = collections.Counter()
172
+ for p in per_persona:
173
+ for k in p["profile_progress"]["captured_fields"]:
174
+ field_counts[k] += 1
175
+
176
+ # Identify worst performers
177
+ worst_refusers = sorted(per_persona, key=lambda p: p["refusals"], reverse=True)[:10]
178
+ worst_errors = sorted(per_persona, key=lambda p: p["errors"], reverse=True)[:10]
179
+
180
+ lines: list[str] = []
181
+ lines.append(f"# Bot Audit Report")
182
+ lines.append(f"")
183
+ lines.append(f"_Run directory: `{run_dir.name}`_")
184
+ lines.append(f"_Generated automatically by `tools/audit/analyze.py`_")
185
+ lines.append(f"")
186
+ lines.append(f"## 1. Run summary")
187
+ lines.append(f"")
188
+ lines.append(f"| Metric | Value |")
189
+ lines.append(f"|---|---|")
190
+ lines.append(f"| Personas completed | **{n}** of 100 |")
191
+ lines.append(f"| Total turns executed | **{total_turns}** of {n*30} expected |")
192
+ lines.append(f"| Avg completed turns / persona | {avg_completed} |")
193
+ lines.append(f"| Errors (HTTP / timeout / network) | {total_errors} ({total_errors/max(total_turns,1)*100:.1f}%) |")
194
+ lines.append(f"| Refusals (blocked=true) | {total_refusals} ({total_refusals/max(total_turns,1)*100:.1f}%) |")
195
+ lines.append(f"| Faithfulness gate fails | {total_ffails} |")
196
+ lines.append(f"| Fact-find re-ask events | {total_reask} |")
197
+ if all_latencies:
198
+ lines.append(f"| Latency p50 | {_percentile(all_latencies, 50):.0f} ms |")
199
+ lines.append(f"| Latency p95 | {_percentile(all_latencies, 95):.0f} ms |")
200
+ lines.append(f"| Latency p99 | {_percentile(all_latencies, 99):.0f} ms |")
201
+ lines.append(f"")
202
+
203
+ lines.append(f"## 2. Brain routing")
204
+ lines.append(f"")
205
+ lines.append(f"| Brain | Turns |")
206
+ lines.append(f"|---|---:|")
207
+ for k, v in sorted(agg_brain.items(), key=lambda x: -x[1]):
208
+ lines.append(f"| `{k}` | {v} |")
209
+ lines.append(f"")
210
+
211
+ lines.append(f"## 3. Intent distribution")
212
+ lines.append(f"")
213
+ lines.append(f"| Intent | Turns |")
214
+ lines.append(f"|---|---:|")
215
+ for k, v in sorted(agg_intent.items(), key=lambda x: -x[1]):
216
+ lines.append(f"| `{k}` | {v} |")
217
+ lines.append(f"")
218
+
219
+ lines.append(f"## 4. Profile capture (across all personas)")
220
+ lines.append(f"")
221
+ lines.append(f"How many personas got each field captured at least once during the audit:")
222
+ lines.append(f"")
223
+ lines.append(f"| Field | Personas hit |")
224
+ lines.append(f"|---|---:|")
225
+ for k, v in sorted(field_counts.items(), key=lambda x: -x[1]):
226
+ lines.append(f"| `{k}` | {v} / {n} |")
227
+ lines.append(f"")
228
+
229
+ lines.append(f"## 5. Refusal + faithfulness by archetype")
230
+ lines.append(f"")
231
+ lines.append(f"| Archetype | Personas | Avg refusals/persona | Faithfulness fails |")
232
+ lines.append(f"|---|---:|---:|---:|")
233
+ for arch, plist in sorted(by_arch.items()):
234
+ if not plist:
235
+ continue
236
+ avg_ref = sum(p["refusals"] for p in plist) / len(plist)
237
+ ffs = sum(p["faithfulness_fails"] for p in plist)
238
+ lines.append(f"| `{arch}` | {len(plist)} | {avg_ref:.1f} | {ffs} |")
239
+ lines.append(f"")
240
+
241
+ lines.append(f"## 6. Refusal + faithfulness by conversational style")
242
+ lines.append(f"")
243
+ lines.append(f"| Style | Personas | Avg refusals/persona | Faithfulness fails |")
244
+ lines.append(f"|---|---:|---:|---:|")
245
+ for style, plist in sorted(by_style.items()):
246
+ if not plist:
247
+ continue
248
+ avg_ref = sum(p["refusals"] for p in plist) / len(plist)
249
+ ffs = sum(p["faithfulness_fails"] for p in plist)
250
+ lines.append(f"| `{style}` | {len(plist)} | {avg_ref:.1f} | {ffs} |")
251
+ lines.append(f"")
252
+
253
+ lines.append(f"## 7. Worst refusers (10 personas with most refusals)")
254
+ lines.append(f"")
255
+ lines.append(f"| Persona | Name | Archetype | Style | Refusals | Errors |")
256
+ lines.append(f"|---|---|---|---|---:|---:|")
257
+ for p in worst_refusers:
258
+ lines.append(f"| `{p['persona_id']}` | {p['name']} | {p['archetype']} | {p['style']} | {p['refusals']} | {p['errors']} |")
259
+ lines.append(f"")
260
+
261
+ lines.append(f"## 8. Worst error-affected (10 personas with most HTTP errors)")
262
+ lines.append(f"")
263
+ lines.append(f"| Persona | Name | Archetype | Style | Errors | Completed turns |")
264
+ lines.append(f"|---|---|---|---|---:|---:|")
265
+ for p in worst_errors:
266
+ lines.append(f"| `{p['persona_id']}` | {p['name']} | {p['archetype']} | {p['style']} | {p['errors']} | {p['completed_turns']} |")
267
+ lines.append(f"")
268
+
269
+ lines.append(f"## 9. Per-persona one-liner")
270
+ lines.append(f"")
271
+ lines.append(f"| ID | Name | Archetype | Style | Done | Errs | Refusals | Reask | Citations/reply |")
272
+ lines.append(f"|---|---|---|---|---:|---:|---:|---:|---:|")
273
+ for p in per_persona:
274
+ cd = p["citation_density"]
275
+ cd_str = f"{cd:.1f}" if cd is not None else "—"
276
+ lines.append(
277
+ f"| `{p['persona_id']}` | {p['name']} | {p['archetype']} | {p['style']} | "
278
+ f"{p['completed_turns']} | {p['errors']} | {p['refusals']} | {p['reask_clarify']} | {cd_str} |"
279
+ )
280
+ lines.append(f"")
281
+
282
+ report_path = run_dir / "report.md"
283
+ report_path.write_text("\n".join(lines))
284
+ print(f"wrote {report_path}")
285
+ return report_path
286
+
287
+
288
+ def main() -> None:
289
+ parser = argparse.ArgumentParser()
290
+ parser.add_argument("--run-id", default=None, help="audit_results subdir to analyze (default: latest)")
291
+ args = parser.parse_args()
292
+
293
+ if args.run_id:
294
+ run_dir = RESULTS_ROOT / args.run_id
295
+ else:
296
+ runs = sorted([d for d in RESULTS_ROOT.iterdir() if d.is_dir()], key=lambda d: d.stat().st_mtime)
297
+ if not runs:
298
+ print("no audit runs found in audit_results/")
299
+ return
300
+ run_dir = runs[-1]
301
+ build_report(run_dir)
302
+
303
+
304
+ if __name__ == "__main__":
305
+ main()
tools/audit/flows.json ADDED
The diff for this file is too large to render. See raw diff
 
tools/audit/flows.py ADDED
@@ -0,0 +1,279 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Generate 30-turn conversational flows for each persona.
2
+
3
+ Flow shape per persona (30 turns total):
4
+ Phase 1 — Opening ( 1 turn)
5
+ Phase 2 — Fact-find answers ( 9 turns)
6
+ Phase 3 — Free-form policy Qs (10 turns)
7
+ Phase 4 — Edge-case probes ( 5 turns)
8
+ Phase 5 — Adversarial + close ( 5 turns)
9
+
10
+ Each turn is a single `user_text` string the audit runner will POST to
11
+ /api/chat. The persona's `style` and `lang` control surface variation
12
+ (terse vs verbose, English vs Hinglish, hedge-heavy vs clean).
13
+
14
+ Output: tools/audit/flows.json (dict[persona_id, list[str]]).
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ import random
20
+ from pathlib import Path
21
+ from typing import Any
22
+
23
+ # Anchor questions per concern — driving what each free-form turn asks.
24
+ CONCERN_QS = {
25
+ "coverage_breadth": ["What does Care Supreme cover for hospital expenses?",
26
+ "Does Star Comprehensive include AYUSH treatment?"],
27
+ "premium_value": ["What's the cheapest ₹10L sum insured option?",
28
+ "Compare premiums on Optima Secure vs Niva Bupa ReAssure"],
29
+ "claim_settlement": ["What's the claim settlement ratio for HDFC ERGO?",
30
+ "Which insurer has the lowest claim rejection rate?"],
31
+ "sum_insured_size": ["Should I buy ₹10L or ₹25L sum insured?",
32
+ "Does restoration benefit fully reset the sum insured?"],
33
+ "ped_waiting": ["What's the PED waiting period under Care Supreme?",
34
+ "Does any policy have a shorter PED waiting than 36 months?"],
35
+ "restoration_benefit": ["Which policies offer unlimited restoration?",
36
+ "When does the restoration benefit kick in?"],
37
+ "parents_age_max": ["Up to what age can I add my parents?",
38
+ "What's the renewal age cap on senior-citizen plans?"],
39
+ "specific_disease_waiting": ["What's the waiting period for cataract surgery?",
40
+ "Does any plan have a shorter cancer waiting period?"],
41
+ "sub_limits": ["Does Care Supreme cap room rent?",
42
+ "What's the ICU sub-limit on Optima Secure?"],
43
+ "network_hospitals": ["How many cashless hospitals does Niva Bupa have in Bangalore?",
44
+ "Does Bajaj Allianz have empanelled hospitals in Delhi?"],
45
+ "no_claim_bonus": ["What's the NCB rate on Optima Restore?",
46
+ "Does NCB compound year-over-year?"],
47
+ "exclusions": ["What's permanently excluded under Care Supreme?",
48
+ "Are pregnancy complications covered?"],
49
+ "free_look": ["How many days is the free-look period?",
50
+ "Can I cancel within the free-look and get a full refund?"],
51
+ "irdai_mandate": ["What does IRDAI's 2024 master circular say about cataract caps?",
52
+ "Is there an IRDAI rule on premium revision frequency?"],
53
+ "tax_treatment": ["How much premium can I claim under Section 80D?",
54
+ "Is the premium for parents above 60 separately deductible?"],
55
+ "ombudsman": ["How do I file an ombudsman complaint?",
56
+ "What's the turnaround for an ombudsman decision?"],
57
+ "reviews": ["What do customers say about ICICI Lombard claim settlement?",
58
+ "How does Star Health rate on Reddit?"],
59
+ "regulatory_overlay": ["Does IRDAI override a policy's claim-time disclosure rule?",
60
+ "What's the standard pre-existing-condition definition per IRDAI?"],
61
+ "section_80d": ["Maximum 80D deduction if I'm 45 and parents are 70?",
62
+ "Does preventive health check-up qualify for 80D?"],
63
+ "premium_band": ["What's a typical premium for ₹15L at age 35?",
64
+ "How much more does ₹25L cost vs ₹10L for a 40-year-old?"],
65
+ "language_switch": ["What is the room rent cap?",
66
+ "रूम रेंट कैप क्या है?"],
67
+ }
68
+
69
+ # Edge-case templates — applied to every persona regardless of archetype.
70
+ EDGE_CASES = [
71
+ "Open the marketplace.", # view-context shift
72
+ "Try again", # retry intent (Bug C)
73
+ "I forgot to mention — I was just diagnosed with diabetes", # mid-flow profile update
74
+ "What's the room rent cap on this?", # view-aware reference
75
+ "Just say it again please", # another retry phrasing
76
+ ]
77
+
78
+ # Adversarial probes — tests refusal, regulatory grounding, OOC behavior.
79
+ ADVERSARIAL = [
80
+ "Does this policy cover space tourism injuries?", # OOC — refuse cleanly
81
+ "Should I buy this policy?", # close-resistance
82
+ "What does IRDAI say about my situation?", # regulatory grounding
83
+ "Compare Star Comprehensive, Care Supreme, and Optima Secure on PED waiting", # multi-policy
84
+ "give me a quick summary of what we discussed", # close
85
+ ]
86
+
87
+
88
+ def _opener(p: dict[str, Any]) -> str:
89
+ arch = p["archetype"]
90
+ style = p["style"]
91
+ base = {
92
+ "first_buyer": "I'm looking to buy my first health insurance policy.",
93
+ "upgrader": "I have a ₹5L cover and want to upgrade.",
94
+ "senior_care": "I need to insure my parents — they're around 75.",
95
+ "comparer": "Help me compare HDFC Optima Secure, Care Supreme, and ICICI Elevate.",
96
+ "anxious": "I'm worried insurers will deny claims later. Where do I start?",
97
+ "savvy": "I want a plan with strong restoration and ≥4Cr lifetime renewability.",
98
+ "tax_planner": "I want to maximise my 80D deduction while getting good cover.",
99
+ "low_trust": "Honest question — which insurer actually pays claims fast?",
100
+ "code_switcher": "Hi, mujhe health insurance chahiye, can you help?",
101
+ "specific_condition": f"I have {p['health_conditions'][0] if p['health_conditions'] else 'a condition'}; what should I look at?",
102
+ }[arch]
103
+ return _stylize(base, style, p["style_hedges"])
104
+
105
+
106
+ def _factfind_answers(p: dict[str, Any]) -> list[str]:
107
+ """9 ordered answers matching the fact-find graph: age, dependents,
108
+ income, existing_cover, primary_goal, location, parents, conditions, budget."""
109
+ age = p["age"]
110
+ deps = p["dependents"]
111
+ inc = {"under_5L": "under 5 lakh", "5L-10L": "around 8 lakh", "10L-25L": "around 18 lakh", "25L+": "more than 25 lakh"}[p["income_band"]]
112
+ cover = "no existing cover" if p["existing_cover_inr"] == 0 else "₹5 lakh from work"
113
+ goal = {"first_buy": "this is my first policy", "upgrade": "upgrading existing cover", "compare_specific": "comparing specific plans", "tax_planning": "mainly for tax planning"}[p["primary_goal"]]
114
+ loc = {"metro": "Bangalore", "tier1": "Pune", "tier2": "Indore", "tier3": "Bhilai"}[p["location_tier"]]
115
+ parents = "yes, both parents" if p["parents_to_insure"] else "no, just me / family"
116
+ cond = ", ".join(p["health_conditions"]) if p["health_conditions"] else "none"
117
+ budget = {"under_15k": "under 15 thousand a year", "15k_30k": "15-30k", "30k_60k": "30-60k", "60k+": "more than 60k"}[p["budget_band"]]
118
+
119
+ raws = [
120
+ f"{age}",
121
+ _deps_to_natural(deps),
122
+ inc,
123
+ cover,
124
+ goal,
125
+ loc,
126
+ parents,
127
+ cond,
128
+ budget,
129
+ ]
130
+ return [_stylize(r, p["style"], p["style_hedges"]) for r in raws]
131
+
132
+
133
+ def _deps_to_natural(deps: str) -> str:
134
+ return {
135
+ "self": "just me",
136
+ "self+spouse": "me and my wife",
137
+ "self+spouse+kids": "me, wife, and two kids",
138
+ "self+parents": "me and my parents",
139
+ "self+spouse+kids+parents": "me, wife, kids, and parents",
140
+ }.get(deps, deps)
141
+
142
+
143
+ def _freeform_qs(p: dict[str, Any]) -> list[str]:
144
+ """10 policy Qs anchored on the persona's concerns."""
145
+ out: list[str] = []
146
+ concerns = p["anchor_concerns"]
147
+ # Two Qs per anchor concern + filler from neighbouring concerns
148
+ for c in concerns:
149
+ for q in CONCERN_QS.get(c, []):
150
+ out.append(_stylize(q, p["style"], p["style_hedges"]))
151
+ if len(out) >= 6:
152
+ break
153
+ if len(out) >= 6:
154
+ break
155
+ # Fill to 10 with neutral concerns
156
+ filler_pool = ["coverage_breadth", "premium_value", "ped_waiting", "free_look", "exclusions"]
157
+ rng = random.Random(p["persona_id"]) # deterministic per persona
158
+ while len(out) < 10:
159
+ pool_c = rng.choice(filler_pool)
160
+ candidates = CONCERN_QS.get(pool_c, [])
161
+ if candidates:
162
+ out.append(_stylize(rng.choice(candidates), p["style"], p["style_hedges"]))
163
+ return out[:10]
164
+
165
+
166
+ def _edge_cases(p: dict[str, Any]) -> list[str]:
167
+ return [_stylize(e, p["style"], p["style_hedges"]) for e in EDGE_CASES]
168
+
169
+
170
+ def _adversarial(p: dict[str, Any]) -> list[str]:
171
+ return [_stylize(a, p["style"], p["style_hedges"]) for a in ADVERSARIAL]
172
+
173
+
174
+ # ----------------------------------------------------------------------------
175
+ # Style transforms
176
+ # ----------------------------------------------------------------------------
177
+
178
+ _HINGLISH_MAP = {
179
+ "policy": "policy", "insurance": "insurance", # English loan-words stay
180
+ "cover": "cover", "premium": "premium",
181
+ "I": "main", "have": "hai", "want": "chahiye", "need": "chahiye",
182
+ "what": "kya", "is": "hai", "the": "wo", "should": "karu",
183
+ "first": "pehla", "buy": "buy", "tell": "batao", "me": "mujhe",
184
+ "show": "dikhao", "compare": "compare kar do",
185
+ }
186
+
187
+
188
+ def _to_hinglish(text: str) -> str:
189
+ words = text.split()
190
+ out = []
191
+ for w in words:
192
+ bare = w.strip(".,?!:").lower()
193
+ if bare in _HINGLISH_MAP:
194
+ out.append(_HINGLISH_MAP[bare] + w[len(bare):])
195
+ else:
196
+ out.append(w)
197
+ return " ".join(out)
198
+
199
+
200
+ def _to_hindi_devanagari(text: str) -> str:
201
+ """Light transliteration sample — keeps policy/insurance words in English
202
+ but flavours basic phrases. Real Hindi-primary users speak this way."""
203
+ mappings = [
204
+ ("I want", "मुझे चाहिए"),
205
+ ("I have", "मेरे पास है"),
206
+ ("Please tell me", "मुझे बताइए"),
207
+ ("What is", "क्या है"),
208
+ ("policy", "policy"),
209
+ ("compare", "तुलना करें"),
210
+ ("waiting period", "waiting period"),
211
+ ]
212
+ out = text
213
+ for src, dst in mappings:
214
+ out = out.replace(src, dst)
215
+ return out
216
+
217
+
218
+ def _stylize(text: str, style: str, hedges: list[str]) -> str:
219
+ if style == "terse":
220
+ words = text.split()
221
+ return " ".join(words[:7]) + ("." if not words[0].endswith("?") else "")
222
+ if style == "verbose":
223
+ prefix = (hedges[0] if hedges else "") + "so basically, "
224
+ suffix = ", let me know what you think"
225
+ return prefix + text.lower() + suffix
226
+ if style == "hinglish":
227
+ return _to_hinglish(text)
228
+ if style == "hindi_primary":
229
+ return _to_hindi_devanagari(text)
230
+ if style == "formal_en":
231
+ return text # already formal English
232
+ if style == "casual_en":
233
+ return (hedges[0] if hedges else "") + text.lower()
234
+ if style == "anxious_q":
235
+ return (hedges[0] if hedges else "") + text + " — is that right?"
236
+ if style == "numbers_heavy":
237
+ return text # natural form often has numbers
238
+ if style == "stream":
239
+ return (hedges[0] if hedges else "") + text + " " + (hedges[-1] if hedges else "") + "also what else should i ask?"
240
+ if style == "tester":
241
+ return text + " (and don't make up the answer)"
242
+ return text
243
+
244
+
245
+ # ----------------------------------------------------------------------------
246
+ # Composer
247
+ # ----------------------------------------------------------------------------
248
+
249
+ def build_flow(p: dict[str, Any]) -> list[str]:
250
+ flow: list[str] = []
251
+ flow.append(_opener(p)) # 1 opening
252
+ flow.extend(_factfind_answers(p)) # 9 fact-find
253
+ flow.extend(_freeform_qs(p)) # 10 free-form
254
+ flow.extend(_edge_cases(p)) # 5 edge cases
255
+ flow.extend(_adversarial(p)) # 5 adversarial + close
256
+ assert len(flow) == 30, f"flow length {len(flow)} != 30 for {p['persona_id']}"
257
+ return flow
258
+
259
+
260
+ def main() -> None:
261
+ root = Path(__file__).resolve().parent
262
+ personas = json.loads((root / "personas.json").read_text())
263
+ out: dict[str, list[str]] = {}
264
+ for p in personas:
265
+ out[p["persona_id"]] = build_flow(p)
266
+ out_path = root / "flows.json"
267
+ out_path.write_text(json.dumps(out, indent=2, ensure_ascii=False))
268
+ total_turns = sum(len(v) for v in out.values())
269
+ print(f"wrote {out_path} ({len(out)} personas, {total_turns} total turns)")
270
+ # Show one sample flow
271
+ sample_id = "P001"
272
+ print(f"\n=== Sample flow for {sample_id} ({personas[0]['archetype_label']}, style={personas[0]['style']}) ===")
273
+ for i, turn in enumerate(out[sample_id][:5], 1):
274
+ print(f" {i}. {turn}")
275
+ print(" ...")
276
+
277
+
278
+ if __name__ == "__main__":
279
+ main()
tools/audit/personas.json ADDED
@@ -0,0 +1,2943 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "persona_id": "P001",
4
+ "name": "Aarav Sharma",
5
+ "archetype": "first_buyer",
6
+ "archetype_label": "First-time health insurance buyer",
7
+ "primary_goal": "first_buy",
8
+ "anchor_concerns": [
9
+ "coverage_breadth",
10
+ "premium_value",
11
+ "claim_settlement"
12
+ ],
13
+ "age": 24,
14
+ "dependents": "self",
15
+ "income_band": "under_5L",
16
+ "location_tier": "metro",
17
+ "marital_stage": "single",
18
+ "existing_cover_inr": 0,
19
+ "health_conditions": [],
20
+ "parents_to_insure": false,
21
+ "parents_age_max": null,
22
+ "parents_has_ped": false,
23
+ "budget_band": "under_15k",
24
+ "style": "terse",
25
+ "style_label": "Terse — 3-7 word answers",
26
+ "lang": "en",
27
+ "style_hedges": []
28
+ },
29
+ {
30
+ "persona_id": "P002",
31
+ "name": "Diya Joshi",
32
+ "archetype": "first_buyer",
33
+ "archetype_label": "First-time health insurance buyer",
34
+ "primary_goal": "first_buy",
35
+ "anchor_concerns": [
36
+ "coverage_breadth",
37
+ "premium_value",
38
+ "claim_settlement"
39
+ ],
40
+ "age": 28,
41
+ "dependents": "self",
42
+ "income_band": "5L-10L",
43
+ "location_tier": "metro",
44
+ "marital_stage": "single",
45
+ "existing_cover_inr": 0,
46
+ "health_conditions": [],
47
+ "parents_to_insure": false,
48
+ "parents_age_max": null,
49
+ "parents_has_ped": false,
50
+ "budget_band": "15k_30k",
51
+ "style": "verbose",
52
+ "style_label": "Verbose — paragraphs of context",
53
+ "lang": "en",
54
+ "style_hedges": [
55
+ "um, ",
56
+ "well, ",
57
+ "you know, "
58
+ ]
59
+ },
60
+ {
61
+ "persona_id": "P003",
62
+ "name": "Rohan Banerjee",
63
+ "archetype": "first_buyer",
64
+ "archetype_label": "First-time health insurance buyer",
65
+ "primary_goal": "first_buy",
66
+ "anchor_concerns": [
67
+ "coverage_breadth",
68
+ "premium_value",
69
+ "claim_settlement"
70
+ ],
71
+ "age": 31,
72
+ "dependents": "self+spouse",
73
+ "income_band": "10L-25L",
74
+ "location_tier": "metro",
75
+ "marital_stage": "married",
76
+ "existing_cover_inr": 0,
77
+ "health_conditions": [],
78
+ "parents_to_insure": false,
79
+ "parents_age_max": null,
80
+ "parents_has_ped": false,
81
+ "budget_band": "30k_60k",
82
+ "style": "hinglish",
83
+ "style_label": "English with Hindi words sprinkled",
84
+ "lang": "hinglish",
85
+ "style_hedges": []
86
+ },
87
+ {
88
+ "persona_id": "P004",
89
+ "name": "Ananya Iyer",
90
+ "archetype": "first_buyer",
91
+ "archetype_label": "First-time health insurance buyer",
92
+ "primary_goal": "first_buy",
93
+ "anchor_concerns": [
94
+ "coverage_breadth",
95
+ "premium_value",
96
+ "claim_settlement"
97
+ ],
98
+ "age": 34,
99
+ "dependents": "self+spouse+kids",
100
+ "income_band": "10L-25L",
101
+ "location_tier": "tier1",
102
+ "marital_stage": "married_kids",
103
+ "existing_cover_inr": 0,
104
+ "health_conditions": [],
105
+ "parents_to_insure": false,
106
+ "parents_age_max": null,
107
+ "parents_has_ped": false,
108
+ "budget_band": "30k_60k",
109
+ "style": "formal_en",
110
+ "style_label": "Formal English, full sentences",
111
+ "lang": "en",
112
+ "style_hedges": []
113
+ },
114
+ {
115
+ "persona_id": "P005",
116
+ "name": "Vikram Khan",
117
+ "archetype": "first_buyer",
118
+ "archetype_label": "First-time health insurance buyer",
119
+ "primary_goal": "first_buy",
120
+ "anchor_concerns": [
121
+ "coverage_breadth",
122
+ "premium_value",
123
+ "claim_settlement"
124
+ ],
125
+ "age": 38,
126
+ "dependents": "self+spouse+kids",
127
+ "income_band": "25L+",
128
+ "location_tier": "metro",
129
+ "marital_stage": "married_kids",
130
+ "existing_cover_inr": 0,
131
+ "health_conditions": [],
132
+ "parents_to_insure": false,
133
+ "parents_age_max": null,
134
+ "parents_has_ped": false,
135
+ "budget_band": "60k+",
136
+ "style": "casual_en",
137
+ "style_label": "Casual English with typos and lowercase",
138
+ "lang": "en",
139
+ "style_hedges": [
140
+ "btw ",
141
+ "lol ",
142
+ "tbh "
143
+ ]
144
+ },
145
+ {
146
+ "persona_id": "P006",
147
+ "name": "Priya Kapoor",
148
+ "archetype": "first_buyer",
149
+ "archetype_label": "First-time health insurance buyer",
150
+ "primary_goal": "first_buy",
151
+ "anchor_concerns": [
152
+ "coverage_breadth",
153
+ "premium_value",
154
+ "claim_settlement"
155
+ ],
156
+ "age": 42,
157
+ "dependents": "self+spouse+kids+parents",
158
+ "income_band": "25L+",
159
+ "location_tier": "metro",
160
+ "marital_stage": "sandwich_gen",
161
+ "existing_cover_inr": 0,
162
+ "health_conditions": [],
163
+ "parents_to_insure": true,
164
+ "parents_age_max": 75,
165
+ "parents_has_ped": false,
166
+ "budget_band": "60k+",
167
+ "style": "hindi_primary",
168
+ "style_label": "Mostly Hindi (Devanagari letters)",
169
+ "lang": "hi",
170
+ "style_hedges": []
171
+ },
172
+ {
173
+ "persona_id": "P007",
174
+ "name": "Karthik Mehta",
175
+ "archetype": "first_buyer",
176
+ "archetype_label": "First-time health insurance buyer",
177
+ "primary_goal": "first_buy",
178
+ "anchor_concerns": [
179
+ "coverage_breadth",
180
+ "premium_value",
181
+ "claim_settlement"
182
+ ],
183
+ "age": 47,
184
+ "dependents": "self+spouse+parents",
185
+ "income_band": "10L-25L",
186
+ "location_tier": "tier1",
187
+ "marital_stage": "married_no_kids",
188
+ "existing_cover_inr": 0,
189
+ "health_conditions": [],
190
+ "parents_to_insure": true,
191
+ "parents_age_max": 75,
192
+ "parents_has_ped": false,
193
+ "budget_band": "30k_60k",
194
+ "style": "anxious_q",
195
+ "style_label": "Asks lots of follow-up questions",
196
+ "lang": "en",
197
+ "style_hedges": [
198
+ "but ",
199
+ "wait, ",
200
+ "what about "
201
+ ]
202
+ },
203
+ {
204
+ "persona_id": "P008",
205
+ "name": "Meera Pillai",
206
+ "archetype": "first_buyer",
207
+ "archetype_label": "First-time health insurance buyer",
208
+ "primary_goal": "first_buy",
209
+ "anchor_concerns": [
210
+ "coverage_breadth",
211
+ "premium_value",
212
+ "claim_settlement"
213
+ ],
214
+ "age": 52,
215
+ "dependents": "self+spouse",
216
+ "income_band": "10L-25L",
217
+ "location_tier": "tier2",
218
+ "marital_stage": "empty_nester",
219
+ "existing_cover_inr": 0,
220
+ "health_conditions": [
221
+ "hypertension"
222
+ ],
223
+ "parents_to_insure": false,
224
+ "parents_age_max": null,
225
+ "parents_has_ped": false,
226
+ "budget_band": "30k_60k",
227
+ "style": "numbers_heavy",
228
+ "style_label": "Quotes specific numbers and policies",
229
+ "lang": "en",
230
+ "style_hedges": []
231
+ },
232
+ {
233
+ "persona_id": "P009",
234
+ "name": "Saif Nair",
235
+ "archetype": "first_buyer",
236
+ "archetype_label": "First-time health insurance buyer",
237
+ "primary_goal": "first_buy",
238
+ "anchor_concerns": [
239
+ "coverage_breadth",
240
+ "premium_value",
241
+ "claim_settlement"
242
+ ],
243
+ "age": 58,
244
+ "dependents": "self+spouse",
245
+ "income_band": "5L-10L",
246
+ "location_tier": "tier2",
247
+ "marital_stage": "near_retire",
248
+ "existing_cover_inr": 0,
249
+ "health_conditions": [
250
+ "hypertension"
251
+ ],
252
+ "parents_to_insure": false,
253
+ "parents_age_max": null,
254
+ "parents_has_ped": false,
255
+ "budget_band": "15k_30k",
256
+ "style": "stream",
257
+ "style_label": "Stream-of-consciousness, rambles",
258
+ "lang": "en",
259
+ "style_hedges": [
260
+ "uh ",
261
+ "hmm ",
262
+ "and ",
263
+ "also "
264
+ ]
265
+ },
266
+ {
267
+ "persona_id": "P010",
268
+ "name": "Ishita Reddy",
269
+ "archetype": "first_buyer",
270
+ "archetype_label": "First-time health insurance buyer",
271
+ "primary_goal": "first_buy",
272
+ "anchor_concerns": [
273
+ "coverage_breadth",
274
+ "premium_value",
275
+ "claim_settlement"
276
+ ],
277
+ "age": 63,
278
+ "dependents": "self+spouse",
279
+ "income_band": "under_5L",
280
+ "location_tier": "tier3",
281
+ "marital_stage": "retired",
282
+ "existing_cover_inr": 0,
283
+ "health_conditions": [
284
+ "hypertension"
285
+ ],
286
+ "parents_to_insure": false,
287
+ "parents_age_max": null,
288
+ "parents_has_ped": false,
289
+ "budget_band": "under_15k",
290
+ "style": "tester",
291
+ "style_label": "Tries to trip up the bot",
292
+ "lang": "en",
293
+ "style_hedges": []
294
+ },
295
+ {
296
+ "persona_id": "P011",
297
+ "name": "Diya Reddy",
298
+ "archetype": "upgrader",
299
+ "archetype_label": "Existing-cover upgrader",
300
+ "primary_goal": "upgrade",
301
+ "anchor_concerns": [
302
+ "sum_insured_size",
303
+ "ped_waiting",
304
+ "restoration_benefit"
305
+ ],
306
+ "age": 24,
307
+ "dependents": "self",
308
+ "income_band": "under_5L",
309
+ "location_tier": "metro",
310
+ "marital_stage": "single",
311
+ "existing_cover_inr": 500000,
312
+ "health_conditions": [],
313
+ "parents_to_insure": false,
314
+ "parents_age_max": null,
315
+ "parents_has_ped": false,
316
+ "budget_band": "under_15k",
317
+ "style": "verbose",
318
+ "style_label": "Verbose — paragraphs of context",
319
+ "lang": "en",
320
+ "style_hedges": [
321
+ "um, ",
322
+ "well, ",
323
+ "you know, "
324
+ ]
325
+ },
326
+ {
327
+ "persona_id": "P012",
328
+ "name": "Rohan Sharma",
329
+ "archetype": "upgrader",
330
+ "archetype_label": "Existing-cover upgrader",
331
+ "primary_goal": "upgrade",
332
+ "anchor_concerns": [
333
+ "sum_insured_size",
334
+ "ped_waiting",
335
+ "restoration_benefit"
336
+ ],
337
+ "age": 28,
338
+ "dependents": "self",
339
+ "income_band": "5L-10L",
340
+ "location_tier": "metro",
341
+ "marital_stage": "single",
342
+ "existing_cover_inr": 500000,
343
+ "health_conditions": [],
344
+ "parents_to_insure": false,
345
+ "parents_age_max": null,
346
+ "parents_has_ped": false,
347
+ "budget_band": "15k_30k",
348
+ "style": "hinglish",
349
+ "style_label": "English with Hindi words sprinkled",
350
+ "lang": "hinglish",
351
+ "style_hedges": []
352
+ },
353
+ {
354
+ "persona_id": "P013",
355
+ "name": "Ananya Joshi",
356
+ "archetype": "upgrader",
357
+ "archetype_label": "Existing-cover upgrader",
358
+ "primary_goal": "upgrade",
359
+ "anchor_concerns": [
360
+ "sum_insured_size",
361
+ "ped_waiting",
362
+ "restoration_benefit"
363
+ ],
364
+ "age": 31,
365
+ "dependents": "self+spouse",
366
+ "income_band": "10L-25L",
367
+ "location_tier": "metro",
368
+ "marital_stage": "married",
369
+ "existing_cover_inr": 500000,
370
+ "health_conditions": [],
371
+ "parents_to_insure": false,
372
+ "parents_age_max": null,
373
+ "parents_has_ped": false,
374
+ "budget_band": "30k_60k",
375
+ "style": "formal_en",
376
+ "style_label": "Formal English, full sentences",
377
+ "lang": "en",
378
+ "style_hedges": []
379
+ },
380
+ {
381
+ "persona_id": "P014",
382
+ "name": "Vikram Banerjee",
383
+ "archetype": "upgrader",
384
+ "archetype_label": "Existing-cover upgrader",
385
+ "primary_goal": "upgrade",
386
+ "anchor_concerns": [
387
+ "sum_insured_size",
388
+ "ped_waiting",
389
+ "restoration_benefit"
390
+ ],
391
+ "age": 34,
392
+ "dependents": "self+spouse+kids",
393
+ "income_band": "10L-25L",
394
+ "location_tier": "tier1",
395
+ "marital_stage": "married_kids",
396
+ "existing_cover_inr": 500000,
397
+ "health_conditions": [],
398
+ "parents_to_insure": false,
399
+ "parents_age_max": null,
400
+ "parents_has_ped": false,
401
+ "budget_band": "30k_60k",
402
+ "style": "casual_en",
403
+ "style_label": "Casual English with typos and lowercase",
404
+ "lang": "en",
405
+ "style_hedges": [
406
+ "btw ",
407
+ "lol ",
408
+ "tbh "
409
+ ]
410
+ },
411
+ {
412
+ "persona_id": "P015",
413
+ "name": "Priya Iyer",
414
+ "archetype": "upgrader",
415
+ "archetype_label": "Existing-cover upgrader",
416
+ "primary_goal": "upgrade",
417
+ "anchor_concerns": [
418
+ "sum_insured_size",
419
+ "ped_waiting",
420
+ "restoration_benefit"
421
+ ],
422
+ "age": 38,
423
+ "dependents": "self+spouse+kids",
424
+ "income_band": "25L+",
425
+ "location_tier": "metro",
426
+ "marital_stage": "married_kids",
427
+ "existing_cover_inr": 500000,
428
+ "health_conditions": [],
429
+ "parents_to_insure": false,
430
+ "parents_age_max": null,
431
+ "parents_has_ped": false,
432
+ "budget_band": "60k+",
433
+ "style": "hindi_primary",
434
+ "style_label": "Mostly Hindi (Devanagari letters)",
435
+ "lang": "hi",
436
+ "style_hedges": []
437
+ },
438
+ {
439
+ "persona_id": "P016",
440
+ "name": "Karthik Khan",
441
+ "archetype": "upgrader",
442
+ "archetype_label": "Existing-cover upgrader",
443
+ "primary_goal": "upgrade",
444
+ "anchor_concerns": [
445
+ "sum_insured_size",
446
+ "ped_waiting",
447
+ "restoration_benefit"
448
+ ],
449
+ "age": 42,
450
+ "dependents": "self+spouse+kids+parents",
451
+ "income_band": "25L+",
452
+ "location_tier": "metro",
453
+ "marital_stage": "sandwich_gen",
454
+ "existing_cover_inr": 500000,
455
+ "health_conditions": [],
456
+ "parents_to_insure": true,
457
+ "parents_age_max": 75,
458
+ "parents_has_ped": false,
459
+ "budget_band": "60k+",
460
+ "style": "anxious_q",
461
+ "style_label": "Asks lots of follow-up questions",
462
+ "lang": "en",
463
+ "style_hedges": [
464
+ "but ",
465
+ "wait, ",
466
+ "what about "
467
+ ]
468
+ },
469
+ {
470
+ "persona_id": "P017",
471
+ "name": "Meera Kapoor",
472
+ "archetype": "upgrader",
473
+ "archetype_label": "Existing-cover upgrader",
474
+ "primary_goal": "upgrade",
475
+ "anchor_concerns": [
476
+ "sum_insured_size",
477
+ "ped_waiting",
478
+ "restoration_benefit"
479
+ ],
480
+ "age": 47,
481
+ "dependents": "self+spouse+parents",
482
+ "income_band": "10L-25L",
483
+ "location_tier": "tier1",
484
+ "marital_stage": "married_no_kids",
485
+ "existing_cover_inr": 500000,
486
+ "health_conditions": [],
487
+ "parents_to_insure": true,
488
+ "parents_age_max": 75,
489
+ "parents_has_ped": false,
490
+ "budget_band": "30k_60k",
491
+ "style": "numbers_heavy",
492
+ "style_label": "Quotes specific numbers and policies",
493
+ "lang": "en",
494
+ "style_hedges": []
495
+ },
496
+ {
497
+ "persona_id": "P018",
498
+ "name": "Saif Mehta",
499
+ "archetype": "upgrader",
500
+ "archetype_label": "Existing-cover upgrader",
501
+ "primary_goal": "upgrade",
502
+ "anchor_concerns": [
503
+ "sum_insured_size",
504
+ "ped_waiting",
505
+ "restoration_benefit"
506
+ ],
507
+ "age": 52,
508
+ "dependents": "self+spouse",
509
+ "income_band": "10L-25L",
510
+ "location_tier": "tier2",
511
+ "marital_stage": "empty_nester",
512
+ "existing_cover_inr": 500000,
513
+ "health_conditions": [
514
+ "hypertension"
515
+ ],
516
+ "parents_to_insure": false,
517
+ "parents_age_max": null,
518
+ "parents_has_ped": false,
519
+ "budget_band": "30k_60k",
520
+ "style": "stream",
521
+ "style_label": "Stream-of-consciousness, rambles",
522
+ "lang": "en",
523
+ "style_hedges": [
524
+ "uh ",
525
+ "hmm ",
526
+ "and ",
527
+ "also "
528
+ ]
529
+ },
530
+ {
531
+ "persona_id": "P019",
532
+ "name": "Ishita Pillai",
533
+ "archetype": "upgrader",
534
+ "archetype_label": "Existing-cover upgrader",
535
+ "primary_goal": "upgrade",
536
+ "anchor_concerns": [
537
+ "sum_insured_size",
538
+ "ped_waiting",
539
+ "restoration_benefit"
540
+ ],
541
+ "age": 58,
542
+ "dependents": "self+spouse",
543
+ "income_band": "5L-10L",
544
+ "location_tier": "tier2",
545
+ "marital_stage": "near_retire",
546
+ "existing_cover_inr": 500000,
547
+ "health_conditions": [
548
+ "hypertension"
549
+ ],
550
+ "parents_to_insure": false,
551
+ "parents_age_max": null,
552
+ "parents_has_ped": false,
553
+ "budget_band": "15k_30k",
554
+ "style": "tester",
555
+ "style_label": "Tries to trip up the bot",
556
+ "lang": "en",
557
+ "style_hedges": []
558
+ },
559
+ {
560
+ "persona_id": "P020",
561
+ "name": "Aarav Nair",
562
+ "archetype": "upgrader",
563
+ "archetype_label": "Existing-cover upgrader",
564
+ "primary_goal": "upgrade",
565
+ "anchor_concerns": [
566
+ "sum_insured_size",
567
+ "ped_waiting",
568
+ "restoration_benefit"
569
+ ],
570
+ "age": 63,
571
+ "dependents": "self+spouse",
572
+ "income_band": "under_5L",
573
+ "location_tier": "tier3",
574
+ "marital_stage": "retired",
575
+ "existing_cover_inr": 500000,
576
+ "health_conditions": [
577
+ "hypertension"
578
+ ],
579
+ "parents_to_insure": false,
580
+ "parents_age_max": null,
581
+ "parents_has_ped": false,
582
+ "budget_band": "under_15k",
583
+ "style": "terse",
584
+ "style_label": "Terse — 3-7 word answers",
585
+ "lang": "en",
586
+ "style_hedges": []
587
+ },
588
+ {
589
+ "persona_id": "P021",
590
+ "name": "Rohan Nair",
591
+ "archetype": "senior_care",
592
+ "archetype_label": "Buying for senior parents",
593
+ "primary_goal": "first_buy",
594
+ "anchor_concerns": [
595
+ "parents_age_max",
596
+ "ped_waiting",
597
+ "specific_disease_waiting"
598
+ ],
599
+ "age": 24,
600
+ "dependents": "self",
601
+ "income_band": "under_5L",
602
+ "location_tier": "metro",
603
+ "marital_stage": "single",
604
+ "existing_cover_inr": 0,
605
+ "health_conditions": [],
606
+ "parents_to_insure": true,
607
+ "parents_age_max": 75,
608
+ "parents_has_ped": true,
609
+ "budget_band": "under_15k",
610
+ "style": "hinglish",
611
+ "style_label": "English with Hindi words sprinkled",
612
+ "lang": "hinglish",
613
+ "style_hedges": []
614
+ },
615
+ {
616
+ "persona_id": "P022",
617
+ "name": "Ananya Reddy",
618
+ "archetype": "senior_care",
619
+ "archetype_label": "Buying for senior parents",
620
+ "primary_goal": "first_buy",
621
+ "anchor_concerns": [
622
+ "parents_age_max",
623
+ "ped_waiting",
624
+ "specific_disease_waiting"
625
+ ],
626
+ "age": 28,
627
+ "dependents": "self",
628
+ "income_band": "5L-10L",
629
+ "location_tier": "metro",
630
+ "marital_stage": "single",
631
+ "existing_cover_inr": 0,
632
+ "health_conditions": [],
633
+ "parents_to_insure": true,
634
+ "parents_age_max": 75,
635
+ "parents_has_ped": true,
636
+ "budget_band": "15k_30k",
637
+ "style": "formal_en",
638
+ "style_label": "Formal English, full sentences",
639
+ "lang": "en",
640
+ "style_hedges": []
641
+ },
642
+ {
643
+ "persona_id": "P023",
644
+ "name": "Vikram Sharma",
645
+ "archetype": "senior_care",
646
+ "archetype_label": "Buying for senior parents",
647
+ "primary_goal": "first_buy",
648
+ "anchor_concerns": [
649
+ "parents_age_max",
650
+ "ped_waiting",
651
+ "specific_disease_waiting"
652
+ ],
653
+ "age": 31,
654
+ "dependents": "self+spouse",
655
+ "income_band": "10L-25L",
656
+ "location_tier": "metro",
657
+ "marital_stage": "married",
658
+ "existing_cover_inr": 0,
659
+ "health_conditions": [],
660
+ "parents_to_insure": true,
661
+ "parents_age_max": 75,
662
+ "parents_has_ped": true,
663
+ "budget_band": "30k_60k",
664
+ "style": "casual_en",
665
+ "style_label": "Casual English with typos and lowercase",
666
+ "lang": "en",
667
+ "style_hedges": [
668
+ "btw ",
669
+ "lol ",
670
+ "tbh "
671
+ ]
672
+ },
673
+ {
674
+ "persona_id": "P024",
675
+ "name": "Priya Joshi",
676
+ "archetype": "senior_care",
677
+ "archetype_label": "Buying for senior parents",
678
+ "primary_goal": "first_buy",
679
+ "anchor_concerns": [
680
+ "parents_age_max",
681
+ "ped_waiting",
682
+ "specific_disease_waiting"
683
+ ],
684
+ "age": 34,
685
+ "dependents": "self+spouse+kids",
686
+ "income_band": "10L-25L",
687
+ "location_tier": "tier1",
688
+ "marital_stage": "married_kids",
689
+ "existing_cover_inr": 0,
690
+ "health_conditions": [],
691
+ "parents_to_insure": true,
692
+ "parents_age_max": 75,
693
+ "parents_has_ped": true,
694
+ "budget_band": "30k_60k",
695
+ "style": "hindi_primary",
696
+ "style_label": "Mostly Hindi (Devanagari letters)",
697
+ "lang": "hi",
698
+ "style_hedges": []
699
+ },
700
+ {
701
+ "persona_id": "P025",
702
+ "name": "Karthik Banerjee",
703
+ "archetype": "senior_care",
704
+ "archetype_label": "Buying for senior parents",
705
+ "primary_goal": "first_buy",
706
+ "anchor_concerns": [
707
+ "parents_age_max",
708
+ "ped_waiting",
709
+ "specific_disease_waiting"
710
+ ],
711
+ "age": 38,
712
+ "dependents": "self+spouse+kids",
713
+ "income_band": "25L+",
714
+ "location_tier": "metro",
715
+ "marital_stage": "married_kids",
716
+ "existing_cover_inr": 0,
717
+ "health_conditions": [],
718
+ "parents_to_insure": true,
719
+ "parents_age_max": 75,
720
+ "parents_has_ped": true,
721
+ "budget_band": "60k+",
722
+ "style": "anxious_q",
723
+ "style_label": "Asks lots of follow-up questions",
724
+ "lang": "en",
725
+ "style_hedges": [
726
+ "but ",
727
+ "wait, ",
728
+ "what about "
729
+ ]
730
+ },
731
+ {
732
+ "persona_id": "P026",
733
+ "name": "Meera Iyer",
734
+ "archetype": "senior_care",
735
+ "archetype_label": "Buying for senior parents",
736
+ "primary_goal": "first_buy",
737
+ "anchor_concerns": [
738
+ "parents_age_max",
739
+ "ped_waiting",
740
+ "specific_disease_waiting"
741
+ ],
742
+ "age": 42,
743
+ "dependents": "self+spouse+kids+parents",
744
+ "income_band": "25L+",
745
+ "location_tier": "metro",
746
+ "marital_stage": "sandwich_gen",
747
+ "existing_cover_inr": 0,
748
+ "health_conditions": [],
749
+ "parents_to_insure": true,
750
+ "parents_age_max": 75,
751
+ "parents_has_ped": true,
752
+ "budget_band": "60k+",
753
+ "style": "numbers_heavy",
754
+ "style_label": "Quotes specific numbers and policies",
755
+ "lang": "en",
756
+ "style_hedges": []
757
+ },
758
+ {
759
+ "persona_id": "P027",
760
+ "name": "Saif Khan",
761
+ "archetype": "senior_care",
762
+ "archetype_label": "Buying for senior parents",
763
+ "primary_goal": "first_buy",
764
+ "anchor_concerns": [
765
+ "parents_age_max",
766
+ "ped_waiting",
767
+ "specific_disease_waiting"
768
+ ],
769
+ "age": 47,
770
+ "dependents": "self+spouse+parents",
771
+ "income_band": "10L-25L",
772
+ "location_tier": "tier1",
773
+ "marital_stage": "married_no_kids",
774
+ "existing_cover_inr": 0,
775
+ "health_conditions": [],
776
+ "parents_to_insure": true,
777
+ "parents_age_max": 75,
778
+ "parents_has_ped": true,
779
+ "budget_band": "30k_60k",
780
+ "style": "stream",
781
+ "style_label": "Stream-of-consciousness, rambles",
782
+ "lang": "en",
783
+ "style_hedges": [
784
+ "uh ",
785
+ "hmm ",
786
+ "and ",
787
+ "also "
788
+ ]
789
+ },
790
+ {
791
+ "persona_id": "P028",
792
+ "name": "Ishita Kapoor",
793
+ "archetype": "senior_care",
794
+ "archetype_label": "Buying for senior parents",
795
+ "primary_goal": "first_buy",
796
+ "anchor_concerns": [
797
+ "parents_age_max",
798
+ "ped_waiting",
799
+ "specific_disease_waiting"
800
+ ],
801
+ "age": 52,
802
+ "dependents": "self+spouse",
803
+ "income_band": "10L-25L",
804
+ "location_tier": "tier2",
805
+ "marital_stage": "empty_nester",
806
+ "existing_cover_inr": 0,
807
+ "health_conditions": [],
808
+ "parents_to_insure": true,
809
+ "parents_age_max": 75,
810
+ "parents_has_ped": true,
811
+ "budget_band": "30k_60k",
812
+ "style": "tester",
813
+ "style_label": "Tries to trip up the bot",
814
+ "lang": "en",
815
+ "style_hedges": []
816
+ },
817
+ {
818
+ "persona_id": "P029",
819
+ "name": "Aarav Mehta",
820
+ "archetype": "senior_care",
821
+ "archetype_label": "Buying for senior parents",
822
+ "primary_goal": "first_buy",
823
+ "anchor_concerns": [
824
+ "parents_age_max",
825
+ "ped_waiting",
826
+ "specific_disease_waiting"
827
+ ],
828
+ "age": 58,
829
+ "dependents": "self+spouse",
830
+ "income_band": "5L-10L",
831
+ "location_tier": "tier2",
832
+ "marital_stage": "near_retire",
833
+ "existing_cover_inr": 0,
834
+ "health_conditions": [],
835
+ "parents_to_insure": true,
836
+ "parents_age_max": 75,
837
+ "parents_has_ped": true,
838
+ "budget_band": "15k_30k",
839
+ "style": "terse",
840
+ "style_label": "Terse — 3-7 word answers",
841
+ "lang": "en",
842
+ "style_hedges": []
843
+ },
844
+ {
845
+ "persona_id": "P030",
846
+ "name": "Diya Pillai",
847
+ "archetype": "senior_care",
848
+ "archetype_label": "Buying for senior parents",
849
+ "primary_goal": "first_buy",
850
+ "anchor_concerns": [
851
+ "parents_age_max",
852
+ "ped_waiting",
853
+ "specific_disease_waiting"
854
+ ],
855
+ "age": 63,
856
+ "dependents": "self+spouse",
857
+ "income_band": "under_5L",
858
+ "location_tier": "tier3",
859
+ "marital_stage": "retired",
860
+ "existing_cover_inr": 0,
861
+ "health_conditions": [],
862
+ "parents_to_insure": true,
863
+ "parents_age_max": 75,
864
+ "parents_has_ped": true,
865
+ "budget_band": "under_15k",
866
+ "style": "verbose",
867
+ "style_label": "Verbose — paragraphs of context",
868
+ "lang": "en",
869
+ "style_hedges": [
870
+ "um, ",
871
+ "well, ",
872
+ "you know, "
873
+ ]
874
+ },
875
+ {
876
+ "persona_id": "P031",
877
+ "name": "Ananya Pillai",
878
+ "archetype": "comparer",
879
+ "archetype_label": "Comparing 2-3 specific policies",
880
+ "primary_goal": "compare_specific",
881
+ "anchor_concerns": [
882
+ "sub_limits",
883
+ "network_hospitals",
884
+ "no_claim_bonus"
885
+ ],
886
+ "age": 24,
887
+ "dependents": "self",
888
+ "income_band": "under_5L",
889
+ "location_tier": "metro",
890
+ "marital_stage": "single",
891
+ "existing_cover_inr": 500000,
892
+ "health_conditions": [],
893
+ "parents_to_insure": false,
894
+ "parents_age_max": null,
895
+ "parents_has_ped": false,
896
+ "budget_band": "under_15k",
897
+ "style": "formal_en",
898
+ "style_label": "Formal English, full sentences",
899
+ "lang": "en",
900
+ "style_hedges": []
901
+ },
902
+ {
903
+ "persona_id": "P032",
904
+ "name": "Vikram Nair",
905
+ "archetype": "comparer",
906
+ "archetype_label": "Comparing 2-3 specific policies",
907
+ "primary_goal": "compare_specific",
908
+ "anchor_concerns": [
909
+ "sub_limits",
910
+ "network_hospitals",
911
+ "no_claim_bonus"
912
+ ],
913
+ "age": 28,
914
+ "dependents": "self",
915
+ "income_band": "5L-10L",
916
+ "location_tier": "metro",
917
+ "marital_stage": "single",
918
+ "existing_cover_inr": 500000,
919
+ "health_conditions": [],
920
+ "parents_to_insure": false,
921
+ "parents_age_max": null,
922
+ "parents_has_ped": false,
923
+ "budget_band": "15k_30k",
924
+ "style": "casual_en",
925
+ "style_label": "Casual English with typos and lowercase",
926
+ "lang": "en",
927
+ "style_hedges": [
928
+ "btw ",
929
+ "lol ",
930
+ "tbh "
931
+ ]
932
+ },
933
+ {
934
+ "persona_id": "P033",
935
+ "name": "Priya Reddy",
936
+ "archetype": "comparer",
937
+ "archetype_label": "Comparing 2-3 specific policies",
938
+ "primary_goal": "compare_specific",
939
+ "anchor_concerns": [
940
+ "sub_limits",
941
+ "network_hospitals",
942
+ "no_claim_bonus"
943
+ ],
944
+ "age": 31,
945
+ "dependents": "self+spouse",
946
+ "income_band": "10L-25L",
947
+ "location_tier": "metro",
948
+ "marital_stage": "married",
949
+ "existing_cover_inr": 500000,
950
+ "health_conditions": [],
951
+ "parents_to_insure": false,
952
+ "parents_age_max": null,
953
+ "parents_has_ped": false,
954
+ "budget_band": "30k_60k",
955
+ "style": "hindi_primary",
956
+ "style_label": "Mostly Hindi (Devanagari letters)",
957
+ "lang": "hi",
958
+ "style_hedges": []
959
+ },
960
+ {
961
+ "persona_id": "P034",
962
+ "name": "Karthik Sharma",
963
+ "archetype": "comparer",
964
+ "archetype_label": "Comparing 2-3 specific policies",
965
+ "primary_goal": "compare_specific",
966
+ "anchor_concerns": [
967
+ "sub_limits",
968
+ "network_hospitals",
969
+ "no_claim_bonus"
970
+ ],
971
+ "age": 34,
972
+ "dependents": "self+spouse+kids",
973
+ "income_band": "10L-25L",
974
+ "location_tier": "tier1",
975
+ "marital_stage": "married_kids",
976
+ "existing_cover_inr": 500000,
977
+ "health_conditions": [],
978
+ "parents_to_insure": false,
979
+ "parents_age_max": null,
980
+ "parents_has_ped": false,
981
+ "budget_band": "30k_60k",
982
+ "style": "anxious_q",
983
+ "style_label": "Asks lots of follow-up questions",
984
+ "lang": "en",
985
+ "style_hedges": [
986
+ "but ",
987
+ "wait, ",
988
+ "what about "
989
+ ]
990
+ },
991
+ {
992
+ "persona_id": "P035",
993
+ "name": "Meera Joshi",
994
+ "archetype": "comparer",
995
+ "archetype_label": "Comparing 2-3 specific policies",
996
+ "primary_goal": "compare_specific",
997
+ "anchor_concerns": [
998
+ "sub_limits",
999
+ "network_hospitals",
1000
+ "no_claim_bonus"
1001
+ ],
1002
+ "age": 38,
1003
+ "dependents": "self+spouse+kids",
1004
+ "income_band": "25L+",
1005
+ "location_tier": "metro",
1006
+ "marital_stage": "married_kids",
1007
+ "existing_cover_inr": 500000,
1008
+ "health_conditions": [],
1009
+ "parents_to_insure": false,
1010
+ "parents_age_max": null,
1011
+ "parents_has_ped": false,
1012
+ "budget_band": "60k+",
1013
+ "style": "numbers_heavy",
1014
+ "style_label": "Quotes specific numbers and policies",
1015
+ "lang": "en",
1016
+ "style_hedges": []
1017
+ },
1018
+ {
1019
+ "persona_id": "P036",
1020
+ "name": "Saif Banerjee",
1021
+ "archetype": "comparer",
1022
+ "archetype_label": "Comparing 2-3 specific policies",
1023
+ "primary_goal": "compare_specific",
1024
+ "anchor_concerns": [
1025
+ "sub_limits",
1026
+ "network_hospitals",
1027
+ "no_claim_bonus"
1028
+ ],
1029
+ "age": 42,
1030
+ "dependents": "self+spouse+kids+parents",
1031
+ "income_band": "25L+",
1032
+ "location_tier": "metro",
1033
+ "marital_stage": "sandwich_gen",
1034
+ "existing_cover_inr": 500000,
1035
+ "health_conditions": [],
1036
+ "parents_to_insure": true,
1037
+ "parents_age_max": 75,
1038
+ "parents_has_ped": false,
1039
+ "budget_band": "60k+",
1040
+ "style": "stream",
1041
+ "style_label": "Stream-of-consciousness, rambles",
1042
+ "lang": "en",
1043
+ "style_hedges": [
1044
+ "uh ",
1045
+ "hmm ",
1046
+ "and ",
1047
+ "also "
1048
+ ]
1049
+ },
1050
+ {
1051
+ "persona_id": "P037",
1052
+ "name": "Ishita Iyer",
1053
+ "archetype": "comparer",
1054
+ "archetype_label": "Comparing 2-3 specific policies",
1055
+ "primary_goal": "compare_specific",
1056
+ "anchor_concerns": [
1057
+ "sub_limits",
1058
+ "network_hospitals",
1059
+ "no_claim_bonus"
1060
+ ],
1061
+ "age": 47,
1062
+ "dependents": "self+spouse+parents",
1063
+ "income_band": "10L-25L",
1064
+ "location_tier": "tier1",
1065
+ "marital_stage": "married_no_kids",
1066
+ "existing_cover_inr": 500000,
1067
+ "health_conditions": [],
1068
+ "parents_to_insure": true,
1069
+ "parents_age_max": 75,
1070
+ "parents_has_ped": false,
1071
+ "budget_band": "30k_60k",
1072
+ "style": "tester",
1073
+ "style_label": "Tries to trip up the bot",
1074
+ "lang": "en",
1075
+ "style_hedges": []
1076
+ },
1077
+ {
1078
+ "persona_id": "P038",
1079
+ "name": "Aarav Khan",
1080
+ "archetype": "comparer",
1081
+ "archetype_label": "Comparing 2-3 specific policies",
1082
+ "primary_goal": "compare_specific",
1083
+ "anchor_concerns": [
1084
+ "sub_limits",
1085
+ "network_hospitals",
1086
+ "no_claim_bonus"
1087
+ ],
1088
+ "age": 52,
1089
+ "dependents": "self+spouse",
1090
+ "income_band": "10L-25L",
1091
+ "location_tier": "tier2",
1092
+ "marital_stage": "empty_nester",
1093
+ "existing_cover_inr": 500000,
1094
+ "health_conditions": [
1095
+ "hypertension"
1096
+ ],
1097
+ "parents_to_insure": false,
1098
+ "parents_age_max": null,
1099
+ "parents_has_ped": false,
1100
+ "budget_band": "30k_60k",
1101
+ "style": "terse",
1102
+ "style_label": "Terse — 3-7 word answers",
1103
+ "lang": "en",
1104
+ "style_hedges": []
1105
+ },
1106
+ {
1107
+ "persona_id": "P039",
1108
+ "name": "Diya Kapoor",
1109
+ "archetype": "comparer",
1110
+ "archetype_label": "Comparing 2-3 specific policies",
1111
+ "primary_goal": "compare_specific",
1112
+ "anchor_concerns": [
1113
+ "sub_limits",
1114
+ "network_hospitals",
1115
+ "no_claim_bonus"
1116
+ ],
1117
+ "age": 58,
1118
+ "dependents": "self+spouse",
1119
+ "income_band": "5L-10L",
1120
+ "location_tier": "tier2",
1121
+ "marital_stage": "near_retire",
1122
+ "existing_cover_inr": 500000,
1123
+ "health_conditions": [
1124
+ "hypertension"
1125
+ ],
1126
+ "parents_to_insure": false,
1127
+ "parents_age_max": null,
1128
+ "parents_has_ped": false,
1129
+ "budget_band": "15k_30k",
1130
+ "style": "verbose",
1131
+ "style_label": "Verbose — paragraphs of context",
1132
+ "lang": "en",
1133
+ "style_hedges": [
1134
+ "um, ",
1135
+ "well, ",
1136
+ "you know, "
1137
+ ]
1138
+ },
1139
+ {
1140
+ "persona_id": "P040",
1141
+ "name": "Rohan Mehta",
1142
+ "archetype": "comparer",
1143
+ "archetype_label": "Comparing 2-3 specific policies",
1144
+ "primary_goal": "compare_specific",
1145
+ "anchor_concerns": [
1146
+ "sub_limits",
1147
+ "network_hospitals",
1148
+ "no_claim_bonus"
1149
+ ],
1150
+ "age": 63,
1151
+ "dependents": "self+spouse",
1152
+ "income_band": "under_5L",
1153
+ "location_tier": "tier3",
1154
+ "marital_stage": "retired",
1155
+ "existing_cover_inr": 500000,
1156
+ "health_conditions": [
1157
+ "hypertension"
1158
+ ],
1159
+ "parents_to_insure": false,
1160
+ "parents_age_max": null,
1161
+ "parents_has_ped": false,
1162
+ "budget_band": "under_15k",
1163
+ "style": "hinglish",
1164
+ "style_label": "English with Hindi words sprinkled",
1165
+ "lang": "hinglish",
1166
+ "style_hedges": []
1167
+ },
1168
+ {
1169
+ "persona_id": "P041",
1170
+ "name": "Vikram Mehta",
1171
+ "archetype": "anxious",
1172
+ "archetype_label": "Anxious skeptic — claim-denial fears",
1173
+ "primary_goal": "first_buy",
1174
+ "anchor_concerns": [
1175
+ "claim_settlement",
1176
+ "exclusions",
1177
+ "free_look"
1178
+ ],
1179
+ "age": 24,
1180
+ "dependents": "self",
1181
+ "income_band": "under_5L",
1182
+ "location_tier": "metro",
1183
+ "marital_stage": "single",
1184
+ "existing_cover_inr": 0,
1185
+ "health_conditions": [],
1186
+ "parents_to_insure": false,
1187
+ "parents_age_max": null,
1188
+ "parents_has_ped": false,
1189
+ "budget_band": "under_15k",
1190
+ "style": "casual_en",
1191
+ "style_label": "Casual English with typos and lowercase",
1192
+ "lang": "en",
1193
+ "style_hedges": [
1194
+ "btw ",
1195
+ "lol ",
1196
+ "tbh "
1197
+ ]
1198
+ },
1199
+ {
1200
+ "persona_id": "P042",
1201
+ "name": "Priya Pillai",
1202
+ "archetype": "anxious",
1203
+ "archetype_label": "Anxious skeptic — claim-denial fears",
1204
+ "primary_goal": "first_buy",
1205
+ "anchor_concerns": [
1206
+ "claim_settlement",
1207
+ "exclusions",
1208
+ "free_look"
1209
+ ],
1210
+ "age": 28,
1211
+ "dependents": "self",
1212
+ "income_band": "5L-10L",
1213
+ "location_tier": "metro",
1214
+ "marital_stage": "single",
1215
+ "existing_cover_inr": 0,
1216
+ "health_conditions": [],
1217
+ "parents_to_insure": false,
1218
+ "parents_age_max": null,
1219
+ "parents_has_ped": false,
1220
+ "budget_band": "15k_30k",
1221
+ "style": "hindi_primary",
1222
+ "style_label": "Mostly Hindi (Devanagari letters)",
1223
+ "lang": "hi",
1224
+ "style_hedges": []
1225
+ },
1226
+ {
1227
+ "persona_id": "P043",
1228
+ "name": "Karthik Nair",
1229
+ "archetype": "anxious",
1230
+ "archetype_label": "Anxious skeptic — claim-denial fears",
1231
+ "primary_goal": "first_buy",
1232
+ "anchor_concerns": [
1233
+ "claim_settlement",
1234
+ "exclusions",
1235
+ "free_look"
1236
+ ],
1237
+ "age": 31,
1238
+ "dependents": "self+spouse",
1239
+ "income_band": "10L-25L",
1240
+ "location_tier": "metro",
1241
+ "marital_stage": "married",
1242
+ "existing_cover_inr": 0,
1243
+ "health_conditions": [],
1244
+ "parents_to_insure": false,
1245
+ "parents_age_max": null,
1246
+ "parents_has_ped": false,
1247
+ "budget_band": "30k_60k",
1248
+ "style": "anxious_q",
1249
+ "style_label": "Asks lots of follow-up questions",
1250
+ "lang": "en",
1251
+ "style_hedges": [
1252
+ "but ",
1253
+ "wait, ",
1254
+ "what about "
1255
+ ]
1256
+ },
1257
+ {
1258
+ "persona_id": "P044",
1259
+ "name": "Meera Reddy",
1260
+ "archetype": "anxious",
1261
+ "archetype_label": "Anxious skeptic — claim-denial fears",
1262
+ "primary_goal": "first_buy",
1263
+ "anchor_concerns": [
1264
+ "claim_settlement",
1265
+ "exclusions",
1266
+ "free_look"
1267
+ ],
1268
+ "age": 34,
1269
+ "dependents": "self+spouse+kids",
1270
+ "income_band": "10L-25L",
1271
+ "location_tier": "tier1",
1272
+ "marital_stage": "married_kids",
1273
+ "existing_cover_inr": 0,
1274
+ "health_conditions": [],
1275
+ "parents_to_insure": false,
1276
+ "parents_age_max": null,
1277
+ "parents_has_ped": false,
1278
+ "budget_band": "30k_60k",
1279
+ "style": "numbers_heavy",
1280
+ "style_label": "Quotes specific numbers and policies",
1281
+ "lang": "en",
1282
+ "style_hedges": []
1283
+ },
1284
+ {
1285
+ "persona_id": "P045",
1286
+ "name": "Saif Sharma",
1287
+ "archetype": "anxious",
1288
+ "archetype_label": "Anxious skeptic — claim-denial fears",
1289
+ "primary_goal": "first_buy",
1290
+ "anchor_concerns": [
1291
+ "claim_settlement",
1292
+ "exclusions",
1293
+ "free_look"
1294
+ ],
1295
+ "age": 38,
1296
+ "dependents": "self+spouse+kids",
1297
+ "income_band": "25L+",
1298
+ "location_tier": "metro",
1299
+ "marital_stage": "married_kids",
1300
+ "existing_cover_inr": 0,
1301
+ "health_conditions": [],
1302
+ "parents_to_insure": false,
1303
+ "parents_age_max": null,
1304
+ "parents_has_ped": false,
1305
+ "budget_band": "60k+",
1306
+ "style": "stream",
1307
+ "style_label": "Stream-of-consciousness, rambles",
1308
+ "lang": "en",
1309
+ "style_hedges": [
1310
+ "uh ",
1311
+ "hmm ",
1312
+ "and ",
1313
+ "also "
1314
+ ]
1315
+ },
1316
+ {
1317
+ "persona_id": "P046",
1318
+ "name": "Ishita Joshi",
1319
+ "archetype": "anxious",
1320
+ "archetype_label": "Anxious skeptic — claim-denial fears",
1321
+ "primary_goal": "first_buy",
1322
+ "anchor_concerns": [
1323
+ "claim_settlement",
1324
+ "exclusions",
1325
+ "free_look"
1326
+ ],
1327
+ "age": 42,
1328
+ "dependents": "self+spouse+kids+parents",
1329
+ "income_band": "25L+",
1330
+ "location_tier": "metro",
1331
+ "marital_stage": "sandwich_gen",
1332
+ "existing_cover_inr": 0,
1333
+ "health_conditions": [],
1334
+ "parents_to_insure": true,
1335
+ "parents_age_max": 75,
1336
+ "parents_has_ped": false,
1337
+ "budget_band": "60k+",
1338
+ "style": "tester",
1339
+ "style_label": "Tries to trip up the bot",
1340
+ "lang": "en",
1341
+ "style_hedges": []
1342
+ },
1343
+ {
1344
+ "persona_id": "P047",
1345
+ "name": "Aarav Banerjee",
1346
+ "archetype": "anxious",
1347
+ "archetype_label": "Anxious skeptic — claim-denial fears",
1348
+ "primary_goal": "first_buy",
1349
+ "anchor_concerns": [
1350
+ "claim_settlement",
1351
+ "exclusions",
1352
+ "free_look"
1353
+ ],
1354
+ "age": 47,
1355
+ "dependents": "self+spouse+parents",
1356
+ "income_band": "10L-25L",
1357
+ "location_tier": "tier1",
1358
+ "marital_stage": "married_no_kids",
1359
+ "existing_cover_inr": 0,
1360
+ "health_conditions": [],
1361
+ "parents_to_insure": true,
1362
+ "parents_age_max": 75,
1363
+ "parents_has_ped": false,
1364
+ "budget_band": "30k_60k",
1365
+ "style": "terse",
1366
+ "style_label": "Terse — 3-7 word answers",
1367
+ "lang": "en",
1368
+ "style_hedges": []
1369
+ },
1370
+ {
1371
+ "persona_id": "P048",
1372
+ "name": "Diya Iyer",
1373
+ "archetype": "anxious",
1374
+ "archetype_label": "Anxious skeptic — claim-denial fears",
1375
+ "primary_goal": "first_buy",
1376
+ "anchor_concerns": [
1377
+ "claim_settlement",
1378
+ "exclusions",
1379
+ "free_look"
1380
+ ],
1381
+ "age": 52,
1382
+ "dependents": "self+spouse",
1383
+ "income_band": "10L-25L",
1384
+ "location_tier": "tier2",
1385
+ "marital_stage": "empty_nester",
1386
+ "existing_cover_inr": 0,
1387
+ "health_conditions": [
1388
+ "hypertension"
1389
+ ],
1390
+ "parents_to_insure": false,
1391
+ "parents_age_max": null,
1392
+ "parents_has_ped": false,
1393
+ "budget_band": "30k_60k",
1394
+ "style": "verbose",
1395
+ "style_label": "Verbose — paragraphs of context",
1396
+ "lang": "en",
1397
+ "style_hedges": [
1398
+ "um, ",
1399
+ "well, ",
1400
+ "you know, "
1401
+ ]
1402
+ },
1403
+ {
1404
+ "persona_id": "P049",
1405
+ "name": "Rohan Khan",
1406
+ "archetype": "anxious",
1407
+ "archetype_label": "Anxious skeptic — claim-denial fears",
1408
+ "primary_goal": "first_buy",
1409
+ "anchor_concerns": [
1410
+ "claim_settlement",
1411
+ "exclusions",
1412
+ "free_look"
1413
+ ],
1414
+ "age": 58,
1415
+ "dependents": "self+spouse",
1416
+ "income_band": "5L-10L",
1417
+ "location_tier": "tier2",
1418
+ "marital_stage": "near_retire",
1419
+ "existing_cover_inr": 0,
1420
+ "health_conditions": [
1421
+ "hypertension"
1422
+ ],
1423
+ "parents_to_insure": false,
1424
+ "parents_age_max": null,
1425
+ "parents_has_ped": false,
1426
+ "budget_band": "15k_30k",
1427
+ "style": "hinglish",
1428
+ "style_label": "English with Hindi words sprinkled",
1429
+ "lang": "hinglish",
1430
+ "style_hedges": []
1431
+ },
1432
+ {
1433
+ "persona_id": "P050",
1434
+ "name": "Ananya Kapoor",
1435
+ "archetype": "anxious",
1436
+ "archetype_label": "Anxious skeptic — claim-denial fears",
1437
+ "primary_goal": "first_buy",
1438
+ "anchor_concerns": [
1439
+ "claim_settlement",
1440
+ "exclusions",
1441
+ "free_look"
1442
+ ],
1443
+ "age": 63,
1444
+ "dependents": "self+spouse",
1445
+ "income_band": "under_5L",
1446
+ "location_tier": "tier3",
1447
+ "marital_stage": "retired",
1448
+ "existing_cover_inr": 0,
1449
+ "health_conditions": [
1450
+ "hypertension"
1451
+ ],
1452
+ "parents_to_insure": false,
1453
+ "parents_age_max": null,
1454
+ "parents_has_ped": false,
1455
+ "budget_band": "under_15k",
1456
+ "style": "formal_en",
1457
+ "style_label": "Formal English, full sentences",
1458
+ "lang": "en",
1459
+ "style_hedges": []
1460
+ },
1461
+ {
1462
+ "persona_id": "P051",
1463
+ "name": "Priya Kapoor",
1464
+ "archetype": "savvy",
1465
+ "archetype_label": "Financially literate, asks pointed questions",
1466
+ "primary_goal": "compare_specific",
1467
+ "anchor_concerns": [
1468
+ "irdai_mandate",
1469
+ "tax_treatment",
1470
+ "ombudsman"
1471
+ ],
1472
+ "age": 24,
1473
+ "dependents": "self",
1474
+ "income_band": "under_5L",
1475
+ "location_tier": "metro",
1476
+ "marital_stage": "single",
1477
+ "existing_cover_inr": 500000,
1478
+ "health_conditions": [],
1479
+ "parents_to_insure": false,
1480
+ "parents_age_max": null,
1481
+ "parents_has_ped": false,
1482
+ "budget_band": "under_15k",
1483
+ "style": "hindi_primary",
1484
+ "style_label": "Mostly Hindi (Devanagari letters)",
1485
+ "lang": "hi",
1486
+ "style_hedges": []
1487
+ },
1488
+ {
1489
+ "persona_id": "P052",
1490
+ "name": "Karthik Mehta",
1491
+ "archetype": "savvy",
1492
+ "archetype_label": "Financially literate, asks pointed questions",
1493
+ "primary_goal": "compare_specific",
1494
+ "anchor_concerns": [
1495
+ "irdai_mandate",
1496
+ "tax_treatment",
1497
+ "ombudsman"
1498
+ ],
1499
+ "age": 28,
1500
+ "dependents": "self",
1501
+ "income_band": "5L-10L",
1502
+ "location_tier": "metro",
1503
+ "marital_stage": "single",
1504
+ "existing_cover_inr": 500000,
1505
+ "health_conditions": [],
1506
+ "parents_to_insure": false,
1507
+ "parents_age_max": null,
1508
+ "parents_has_ped": false,
1509
+ "budget_band": "15k_30k",
1510
+ "style": "anxious_q",
1511
+ "style_label": "Asks lots of follow-up questions",
1512
+ "lang": "en",
1513
+ "style_hedges": [
1514
+ "but ",
1515
+ "wait, ",
1516
+ "what about "
1517
+ ]
1518
+ },
1519
+ {
1520
+ "persona_id": "P053",
1521
+ "name": "Meera Pillai",
1522
+ "archetype": "savvy",
1523
+ "archetype_label": "Financially literate, asks pointed questions",
1524
+ "primary_goal": "compare_specific",
1525
+ "anchor_concerns": [
1526
+ "irdai_mandate",
1527
+ "tax_treatment",
1528
+ "ombudsman"
1529
+ ],
1530
+ "age": 31,
1531
+ "dependents": "self+spouse",
1532
+ "income_band": "10L-25L",
1533
+ "location_tier": "metro",
1534
+ "marital_stage": "married",
1535
+ "existing_cover_inr": 500000,
1536
+ "health_conditions": [],
1537
+ "parents_to_insure": false,
1538
+ "parents_age_max": null,
1539
+ "parents_has_ped": false,
1540
+ "budget_band": "30k_60k",
1541
+ "style": "numbers_heavy",
1542
+ "style_label": "Quotes specific numbers and policies",
1543
+ "lang": "en",
1544
+ "style_hedges": []
1545
+ },
1546
+ {
1547
+ "persona_id": "P054",
1548
+ "name": "Saif Nair",
1549
+ "archetype": "savvy",
1550
+ "archetype_label": "Financially literate, asks pointed questions",
1551
+ "primary_goal": "compare_specific",
1552
+ "anchor_concerns": [
1553
+ "irdai_mandate",
1554
+ "tax_treatment",
1555
+ "ombudsman"
1556
+ ],
1557
+ "age": 34,
1558
+ "dependents": "self+spouse+kids",
1559
+ "income_band": "10L-25L",
1560
+ "location_tier": "tier1",
1561
+ "marital_stage": "married_kids",
1562
+ "existing_cover_inr": 500000,
1563
+ "health_conditions": [],
1564
+ "parents_to_insure": false,
1565
+ "parents_age_max": null,
1566
+ "parents_has_ped": false,
1567
+ "budget_band": "30k_60k",
1568
+ "style": "stream",
1569
+ "style_label": "Stream-of-consciousness, rambles",
1570
+ "lang": "en",
1571
+ "style_hedges": [
1572
+ "uh ",
1573
+ "hmm ",
1574
+ "and ",
1575
+ "also "
1576
+ ]
1577
+ },
1578
+ {
1579
+ "persona_id": "P055",
1580
+ "name": "Ishita Reddy",
1581
+ "archetype": "savvy",
1582
+ "archetype_label": "Financially literate, asks pointed questions",
1583
+ "primary_goal": "compare_specific",
1584
+ "anchor_concerns": [
1585
+ "irdai_mandate",
1586
+ "tax_treatment",
1587
+ "ombudsman"
1588
+ ],
1589
+ "age": 38,
1590
+ "dependents": "self+spouse+kids",
1591
+ "income_band": "25L+",
1592
+ "location_tier": "metro",
1593
+ "marital_stage": "married_kids",
1594
+ "existing_cover_inr": 500000,
1595
+ "health_conditions": [],
1596
+ "parents_to_insure": false,
1597
+ "parents_age_max": null,
1598
+ "parents_has_ped": false,
1599
+ "budget_band": "60k+",
1600
+ "style": "tester",
1601
+ "style_label": "Tries to trip up the bot",
1602
+ "lang": "en",
1603
+ "style_hedges": []
1604
+ },
1605
+ {
1606
+ "persona_id": "P056",
1607
+ "name": "Aarav Sharma",
1608
+ "archetype": "savvy",
1609
+ "archetype_label": "Financially literate, asks pointed questions",
1610
+ "primary_goal": "compare_specific",
1611
+ "anchor_concerns": [
1612
+ "irdai_mandate",
1613
+ "tax_treatment",
1614
+ "ombudsman"
1615
+ ],
1616
+ "age": 42,
1617
+ "dependents": "self+spouse+kids+parents",
1618
+ "income_band": "25L+",
1619
+ "location_tier": "metro",
1620
+ "marital_stage": "sandwich_gen",
1621
+ "existing_cover_inr": 500000,
1622
+ "health_conditions": [],
1623
+ "parents_to_insure": true,
1624
+ "parents_age_max": 75,
1625
+ "parents_has_ped": false,
1626
+ "budget_band": "60k+",
1627
+ "style": "terse",
1628
+ "style_label": "Terse — 3-7 word answers",
1629
+ "lang": "en",
1630
+ "style_hedges": []
1631
+ },
1632
+ {
1633
+ "persona_id": "P057",
1634
+ "name": "Diya Joshi",
1635
+ "archetype": "savvy",
1636
+ "archetype_label": "Financially literate, asks pointed questions",
1637
+ "primary_goal": "compare_specific",
1638
+ "anchor_concerns": [
1639
+ "irdai_mandate",
1640
+ "tax_treatment",
1641
+ "ombudsman"
1642
+ ],
1643
+ "age": 47,
1644
+ "dependents": "self+spouse+parents",
1645
+ "income_band": "10L-25L",
1646
+ "location_tier": "tier1",
1647
+ "marital_stage": "married_no_kids",
1648
+ "existing_cover_inr": 500000,
1649
+ "health_conditions": [],
1650
+ "parents_to_insure": true,
1651
+ "parents_age_max": 75,
1652
+ "parents_has_ped": false,
1653
+ "budget_band": "30k_60k",
1654
+ "style": "verbose",
1655
+ "style_label": "Verbose — paragraphs of context",
1656
+ "lang": "en",
1657
+ "style_hedges": [
1658
+ "um, ",
1659
+ "well, ",
1660
+ "you know, "
1661
+ ]
1662
+ },
1663
+ {
1664
+ "persona_id": "P058",
1665
+ "name": "Rohan Banerjee",
1666
+ "archetype": "savvy",
1667
+ "archetype_label": "Financially literate, asks pointed questions",
1668
+ "primary_goal": "compare_specific",
1669
+ "anchor_concerns": [
1670
+ "irdai_mandate",
1671
+ "tax_treatment",
1672
+ "ombudsman"
1673
+ ],
1674
+ "age": 52,
1675
+ "dependents": "self+spouse",
1676
+ "income_band": "10L-25L",
1677
+ "location_tier": "tier2",
1678
+ "marital_stage": "empty_nester",
1679
+ "existing_cover_inr": 500000,
1680
+ "health_conditions": [
1681
+ "hypertension"
1682
+ ],
1683
+ "parents_to_insure": false,
1684
+ "parents_age_max": null,
1685
+ "parents_has_ped": false,
1686
+ "budget_band": "30k_60k",
1687
+ "style": "hinglish",
1688
+ "style_label": "English with Hindi words sprinkled",
1689
+ "lang": "hinglish",
1690
+ "style_hedges": []
1691
+ },
1692
+ {
1693
+ "persona_id": "P059",
1694
+ "name": "Ananya Iyer",
1695
+ "archetype": "savvy",
1696
+ "archetype_label": "Financially literate, asks pointed questions",
1697
+ "primary_goal": "compare_specific",
1698
+ "anchor_concerns": [
1699
+ "irdai_mandate",
1700
+ "tax_treatment",
1701
+ "ombudsman"
1702
+ ],
1703
+ "age": 58,
1704
+ "dependents": "self+spouse",
1705
+ "income_band": "5L-10L",
1706
+ "location_tier": "tier2",
1707
+ "marital_stage": "near_retire",
1708
+ "existing_cover_inr": 500000,
1709
+ "health_conditions": [
1710
+ "hypertension"
1711
+ ],
1712
+ "parents_to_insure": false,
1713
+ "parents_age_max": null,
1714
+ "parents_has_ped": false,
1715
+ "budget_band": "15k_30k",
1716
+ "style": "formal_en",
1717
+ "style_label": "Formal English, full sentences",
1718
+ "lang": "en",
1719
+ "style_hedges": []
1720
+ },
1721
+ {
1722
+ "persona_id": "P060",
1723
+ "name": "Vikram Khan",
1724
+ "archetype": "savvy",
1725
+ "archetype_label": "Financially literate, asks pointed questions",
1726
+ "primary_goal": "compare_specific",
1727
+ "anchor_concerns": [
1728
+ "irdai_mandate",
1729
+ "tax_treatment",
1730
+ "ombudsman"
1731
+ ],
1732
+ "age": 63,
1733
+ "dependents": "self+spouse",
1734
+ "income_band": "under_5L",
1735
+ "location_tier": "tier3",
1736
+ "marital_stage": "retired",
1737
+ "existing_cover_inr": 500000,
1738
+ "health_conditions": [
1739
+ "hypertension"
1740
+ ],
1741
+ "parents_to_insure": false,
1742
+ "parents_age_max": null,
1743
+ "parents_has_ped": false,
1744
+ "budget_band": "under_15k",
1745
+ "style": "casual_en",
1746
+ "style_label": "Casual English with typos and lowercase",
1747
+ "lang": "en",
1748
+ "style_hedges": [
1749
+ "btw ",
1750
+ "lol ",
1751
+ "tbh "
1752
+ ]
1753
+ },
1754
+ {
1755
+ "persona_id": "P061",
1756
+ "name": "Karthik Khan",
1757
+ "archetype": "tax_planner",
1758
+ "archetype_label": "Tax-planning oriented",
1759
+ "primary_goal": "tax_planning",
1760
+ "anchor_concerns": [
1761
+ "section_80d",
1762
+ "premium_band",
1763
+ "sum_insured_size"
1764
+ ],
1765
+ "age": 24,
1766
+ "dependents": "self",
1767
+ "income_band": "under_5L",
1768
+ "location_tier": "metro",
1769
+ "marital_stage": "single",
1770
+ "existing_cover_inr": 0,
1771
+ "health_conditions": [],
1772
+ "parents_to_insure": false,
1773
+ "parents_age_max": null,
1774
+ "parents_has_ped": false,
1775
+ "budget_band": "under_15k",
1776
+ "style": "anxious_q",
1777
+ "style_label": "Asks lots of follow-up questions",
1778
+ "lang": "en",
1779
+ "style_hedges": [
1780
+ "but ",
1781
+ "wait, ",
1782
+ "what about "
1783
+ ]
1784
+ },
1785
+ {
1786
+ "persona_id": "P062",
1787
+ "name": "Meera Kapoor",
1788
+ "archetype": "tax_planner",
1789
+ "archetype_label": "Tax-planning oriented",
1790
+ "primary_goal": "tax_planning",
1791
+ "anchor_concerns": [
1792
+ "section_80d",
1793
+ "premium_band",
1794
+ "sum_insured_size"
1795
+ ],
1796
+ "age": 28,
1797
+ "dependents": "self",
1798
+ "income_band": "5L-10L",
1799
+ "location_tier": "metro",
1800
+ "marital_stage": "single",
1801
+ "existing_cover_inr": 0,
1802
+ "health_conditions": [],
1803
+ "parents_to_insure": false,
1804
+ "parents_age_max": null,
1805
+ "parents_has_ped": false,
1806
+ "budget_band": "15k_30k",
1807
+ "style": "numbers_heavy",
1808
+ "style_label": "Quotes specific numbers and policies",
1809
+ "lang": "en",
1810
+ "style_hedges": []
1811
+ },
1812
+ {
1813
+ "persona_id": "P063",
1814
+ "name": "Saif Mehta",
1815
+ "archetype": "tax_planner",
1816
+ "archetype_label": "Tax-planning oriented",
1817
+ "primary_goal": "tax_planning",
1818
+ "anchor_concerns": [
1819
+ "section_80d",
1820
+ "premium_band",
1821
+ "sum_insured_size"
1822
+ ],
1823
+ "age": 31,
1824
+ "dependents": "self+spouse",
1825
+ "income_band": "10L-25L",
1826
+ "location_tier": "metro",
1827
+ "marital_stage": "married",
1828
+ "existing_cover_inr": 0,
1829
+ "health_conditions": [],
1830
+ "parents_to_insure": false,
1831
+ "parents_age_max": null,
1832
+ "parents_has_ped": false,
1833
+ "budget_band": "30k_60k",
1834
+ "style": "stream",
1835
+ "style_label": "Stream-of-consciousness, rambles",
1836
+ "lang": "en",
1837
+ "style_hedges": [
1838
+ "uh ",
1839
+ "hmm ",
1840
+ "and ",
1841
+ "also "
1842
+ ]
1843
+ },
1844
+ {
1845
+ "persona_id": "P064",
1846
+ "name": "Ishita Pillai",
1847
+ "archetype": "tax_planner",
1848
+ "archetype_label": "Tax-planning oriented",
1849
+ "primary_goal": "tax_planning",
1850
+ "anchor_concerns": [
1851
+ "section_80d",
1852
+ "premium_band",
1853
+ "sum_insured_size"
1854
+ ],
1855
+ "age": 34,
1856
+ "dependents": "self+spouse+kids",
1857
+ "income_band": "10L-25L",
1858
+ "location_tier": "tier1",
1859
+ "marital_stage": "married_kids",
1860
+ "existing_cover_inr": 0,
1861
+ "health_conditions": [],
1862
+ "parents_to_insure": false,
1863
+ "parents_age_max": null,
1864
+ "parents_has_ped": false,
1865
+ "budget_band": "30k_60k",
1866
+ "style": "tester",
1867
+ "style_label": "Tries to trip up the bot",
1868
+ "lang": "en",
1869
+ "style_hedges": []
1870
+ },
1871
+ {
1872
+ "persona_id": "P065",
1873
+ "name": "Aarav Nair",
1874
+ "archetype": "tax_planner",
1875
+ "archetype_label": "Tax-planning oriented",
1876
+ "primary_goal": "tax_planning",
1877
+ "anchor_concerns": [
1878
+ "section_80d",
1879
+ "premium_band",
1880
+ "sum_insured_size"
1881
+ ],
1882
+ "age": 38,
1883
+ "dependents": "self+spouse+kids",
1884
+ "income_band": "25L+",
1885
+ "location_tier": "metro",
1886
+ "marital_stage": "married_kids",
1887
+ "existing_cover_inr": 0,
1888
+ "health_conditions": [],
1889
+ "parents_to_insure": false,
1890
+ "parents_age_max": null,
1891
+ "parents_has_ped": false,
1892
+ "budget_band": "60k+",
1893
+ "style": "terse",
1894
+ "style_label": "Terse — 3-7 word answers",
1895
+ "lang": "en",
1896
+ "style_hedges": []
1897
+ },
1898
+ {
1899
+ "persona_id": "P066",
1900
+ "name": "Diya Reddy",
1901
+ "archetype": "tax_planner",
1902
+ "archetype_label": "Tax-planning oriented",
1903
+ "primary_goal": "tax_planning",
1904
+ "anchor_concerns": [
1905
+ "section_80d",
1906
+ "premium_band",
1907
+ "sum_insured_size"
1908
+ ],
1909
+ "age": 42,
1910
+ "dependents": "self+spouse+kids+parents",
1911
+ "income_band": "25L+",
1912
+ "location_tier": "metro",
1913
+ "marital_stage": "sandwich_gen",
1914
+ "existing_cover_inr": 0,
1915
+ "health_conditions": [],
1916
+ "parents_to_insure": true,
1917
+ "parents_age_max": 75,
1918
+ "parents_has_ped": false,
1919
+ "budget_band": "60k+",
1920
+ "style": "verbose",
1921
+ "style_label": "Verbose — paragraphs of context",
1922
+ "lang": "en",
1923
+ "style_hedges": [
1924
+ "um, ",
1925
+ "well, ",
1926
+ "you know, "
1927
+ ]
1928
+ },
1929
+ {
1930
+ "persona_id": "P067",
1931
+ "name": "Rohan Sharma",
1932
+ "archetype": "tax_planner",
1933
+ "archetype_label": "Tax-planning oriented",
1934
+ "primary_goal": "tax_planning",
1935
+ "anchor_concerns": [
1936
+ "section_80d",
1937
+ "premium_band",
1938
+ "sum_insured_size"
1939
+ ],
1940
+ "age": 47,
1941
+ "dependents": "self+spouse+parents",
1942
+ "income_band": "10L-25L",
1943
+ "location_tier": "tier1",
1944
+ "marital_stage": "married_no_kids",
1945
+ "existing_cover_inr": 0,
1946
+ "health_conditions": [],
1947
+ "parents_to_insure": true,
1948
+ "parents_age_max": 75,
1949
+ "parents_has_ped": false,
1950
+ "budget_band": "30k_60k",
1951
+ "style": "hinglish",
1952
+ "style_label": "English with Hindi words sprinkled",
1953
+ "lang": "hinglish",
1954
+ "style_hedges": []
1955
+ },
1956
+ {
1957
+ "persona_id": "P068",
1958
+ "name": "Ananya Joshi",
1959
+ "archetype": "tax_planner",
1960
+ "archetype_label": "Tax-planning oriented",
1961
+ "primary_goal": "tax_planning",
1962
+ "anchor_concerns": [
1963
+ "section_80d",
1964
+ "premium_band",
1965
+ "sum_insured_size"
1966
+ ],
1967
+ "age": 52,
1968
+ "dependents": "self+spouse",
1969
+ "income_band": "10L-25L",
1970
+ "location_tier": "tier2",
1971
+ "marital_stage": "empty_nester",
1972
+ "existing_cover_inr": 0,
1973
+ "health_conditions": [
1974
+ "hypertension"
1975
+ ],
1976
+ "parents_to_insure": false,
1977
+ "parents_age_max": null,
1978
+ "parents_has_ped": false,
1979
+ "budget_band": "30k_60k",
1980
+ "style": "formal_en",
1981
+ "style_label": "Formal English, full sentences",
1982
+ "lang": "en",
1983
+ "style_hedges": []
1984
+ },
1985
+ {
1986
+ "persona_id": "P069",
1987
+ "name": "Vikram Banerjee",
1988
+ "archetype": "tax_planner",
1989
+ "archetype_label": "Tax-planning oriented",
1990
+ "primary_goal": "tax_planning",
1991
+ "anchor_concerns": [
1992
+ "section_80d",
1993
+ "premium_band",
1994
+ "sum_insured_size"
1995
+ ],
1996
+ "age": 58,
1997
+ "dependents": "self+spouse",
1998
+ "income_band": "5L-10L",
1999
+ "location_tier": "tier2",
2000
+ "marital_stage": "near_retire",
2001
+ "existing_cover_inr": 0,
2002
+ "health_conditions": [
2003
+ "hypertension"
2004
+ ],
2005
+ "parents_to_insure": false,
2006
+ "parents_age_max": null,
2007
+ "parents_has_ped": false,
2008
+ "budget_band": "15k_30k",
2009
+ "style": "casual_en",
2010
+ "style_label": "Casual English with typos and lowercase",
2011
+ "lang": "en",
2012
+ "style_hedges": [
2013
+ "btw ",
2014
+ "lol ",
2015
+ "tbh "
2016
+ ]
2017
+ },
2018
+ {
2019
+ "persona_id": "P070",
2020
+ "name": "Priya Iyer",
2021
+ "archetype": "tax_planner",
2022
+ "archetype_label": "Tax-planning oriented",
2023
+ "primary_goal": "tax_planning",
2024
+ "anchor_concerns": [
2025
+ "section_80d",
2026
+ "premium_band",
2027
+ "sum_insured_size"
2028
+ ],
2029
+ "age": 63,
2030
+ "dependents": "self+spouse",
2031
+ "income_band": "under_5L",
2032
+ "location_tier": "tier3",
2033
+ "marital_stage": "retired",
2034
+ "existing_cover_inr": 0,
2035
+ "health_conditions": [
2036
+ "hypertension"
2037
+ ],
2038
+ "parents_to_insure": false,
2039
+ "parents_age_max": null,
2040
+ "parents_has_ped": false,
2041
+ "budget_band": "under_15k",
2042
+ "style": "hindi_primary",
2043
+ "style_label": "Mostly Hindi (Devanagari letters)",
2044
+ "lang": "hi",
2045
+ "style_hedges": []
2046
+ },
2047
+ {
2048
+ "persona_id": "P071",
2049
+ "name": "Meera Iyer",
2050
+ "archetype": "low_trust",
2051
+ "archetype_label": "Low trust in insurers; needs proof",
2052
+ "primary_goal": "first_buy",
2053
+ "anchor_concerns": [
2054
+ "claim_settlement",
2055
+ "reviews",
2056
+ "regulatory_overlay"
2057
+ ],
2058
+ "age": 24,
2059
+ "dependents": "self",
2060
+ "income_band": "under_5L",
2061
+ "location_tier": "metro",
2062
+ "marital_stage": "single",
2063
+ "existing_cover_inr": 0,
2064
+ "health_conditions": [],
2065
+ "parents_to_insure": false,
2066
+ "parents_age_max": null,
2067
+ "parents_has_ped": false,
2068
+ "budget_band": "under_15k",
2069
+ "style": "numbers_heavy",
2070
+ "style_label": "Quotes specific numbers and policies",
2071
+ "lang": "en",
2072
+ "style_hedges": []
2073
+ },
2074
+ {
2075
+ "persona_id": "P072",
2076
+ "name": "Saif Khan",
2077
+ "archetype": "low_trust",
2078
+ "archetype_label": "Low trust in insurers; needs proof",
2079
+ "primary_goal": "first_buy",
2080
+ "anchor_concerns": [
2081
+ "claim_settlement",
2082
+ "reviews",
2083
+ "regulatory_overlay"
2084
+ ],
2085
+ "age": 28,
2086
+ "dependents": "self",
2087
+ "income_band": "5L-10L",
2088
+ "location_tier": "metro",
2089
+ "marital_stage": "single",
2090
+ "existing_cover_inr": 0,
2091
+ "health_conditions": [],
2092
+ "parents_to_insure": false,
2093
+ "parents_age_max": null,
2094
+ "parents_has_ped": false,
2095
+ "budget_band": "15k_30k",
2096
+ "style": "stream",
2097
+ "style_label": "Stream-of-consciousness, rambles",
2098
+ "lang": "en",
2099
+ "style_hedges": [
2100
+ "uh ",
2101
+ "hmm ",
2102
+ "and ",
2103
+ "also "
2104
+ ]
2105
+ },
2106
+ {
2107
+ "persona_id": "P073",
2108
+ "name": "Ishita Kapoor",
2109
+ "archetype": "low_trust",
2110
+ "archetype_label": "Low trust in insurers; needs proof",
2111
+ "primary_goal": "first_buy",
2112
+ "anchor_concerns": [
2113
+ "claim_settlement",
2114
+ "reviews",
2115
+ "regulatory_overlay"
2116
+ ],
2117
+ "age": 31,
2118
+ "dependents": "self+spouse",
2119
+ "income_band": "10L-25L",
2120
+ "location_tier": "metro",
2121
+ "marital_stage": "married",
2122
+ "existing_cover_inr": 0,
2123
+ "health_conditions": [],
2124
+ "parents_to_insure": false,
2125
+ "parents_age_max": null,
2126
+ "parents_has_ped": false,
2127
+ "budget_band": "30k_60k",
2128
+ "style": "tester",
2129
+ "style_label": "Tries to trip up the bot",
2130
+ "lang": "en",
2131
+ "style_hedges": []
2132
+ },
2133
+ {
2134
+ "persona_id": "P074",
2135
+ "name": "Aarav Mehta",
2136
+ "archetype": "low_trust",
2137
+ "archetype_label": "Low trust in insurers; needs proof",
2138
+ "primary_goal": "first_buy",
2139
+ "anchor_concerns": [
2140
+ "claim_settlement",
2141
+ "reviews",
2142
+ "regulatory_overlay"
2143
+ ],
2144
+ "age": 34,
2145
+ "dependents": "self+spouse+kids",
2146
+ "income_band": "10L-25L",
2147
+ "location_tier": "tier1",
2148
+ "marital_stage": "married_kids",
2149
+ "existing_cover_inr": 0,
2150
+ "health_conditions": [],
2151
+ "parents_to_insure": false,
2152
+ "parents_age_max": null,
2153
+ "parents_has_ped": false,
2154
+ "budget_band": "30k_60k",
2155
+ "style": "terse",
2156
+ "style_label": "Terse — 3-7 word answers",
2157
+ "lang": "en",
2158
+ "style_hedges": []
2159
+ },
2160
+ {
2161
+ "persona_id": "P075",
2162
+ "name": "Diya Pillai",
2163
+ "archetype": "low_trust",
2164
+ "archetype_label": "Low trust in insurers; needs proof",
2165
+ "primary_goal": "first_buy",
2166
+ "anchor_concerns": [
2167
+ "claim_settlement",
2168
+ "reviews",
2169
+ "regulatory_overlay"
2170
+ ],
2171
+ "age": 38,
2172
+ "dependents": "self+spouse+kids",
2173
+ "income_band": "25L+",
2174
+ "location_tier": "metro",
2175
+ "marital_stage": "married_kids",
2176
+ "existing_cover_inr": 0,
2177
+ "health_conditions": [],
2178
+ "parents_to_insure": false,
2179
+ "parents_age_max": null,
2180
+ "parents_has_ped": false,
2181
+ "budget_band": "60k+",
2182
+ "style": "verbose",
2183
+ "style_label": "Verbose — paragraphs of context",
2184
+ "lang": "en",
2185
+ "style_hedges": [
2186
+ "um, ",
2187
+ "well, ",
2188
+ "you know, "
2189
+ ]
2190
+ },
2191
+ {
2192
+ "persona_id": "P076",
2193
+ "name": "Rohan Nair",
2194
+ "archetype": "low_trust",
2195
+ "archetype_label": "Low trust in insurers; needs proof",
2196
+ "primary_goal": "first_buy",
2197
+ "anchor_concerns": [
2198
+ "claim_settlement",
2199
+ "reviews",
2200
+ "regulatory_overlay"
2201
+ ],
2202
+ "age": 42,
2203
+ "dependents": "self+spouse+kids+parents",
2204
+ "income_band": "25L+",
2205
+ "location_tier": "metro",
2206
+ "marital_stage": "sandwich_gen",
2207
+ "existing_cover_inr": 0,
2208
+ "health_conditions": [],
2209
+ "parents_to_insure": true,
2210
+ "parents_age_max": 75,
2211
+ "parents_has_ped": false,
2212
+ "budget_band": "60k+",
2213
+ "style": "hinglish",
2214
+ "style_label": "English with Hindi words sprinkled",
2215
+ "lang": "hinglish",
2216
+ "style_hedges": []
2217
+ },
2218
+ {
2219
+ "persona_id": "P077",
2220
+ "name": "Ananya Reddy",
2221
+ "archetype": "low_trust",
2222
+ "archetype_label": "Low trust in insurers; needs proof",
2223
+ "primary_goal": "first_buy",
2224
+ "anchor_concerns": [
2225
+ "claim_settlement",
2226
+ "reviews",
2227
+ "regulatory_overlay"
2228
+ ],
2229
+ "age": 47,
2230
+ "dependents": "self+spouse+parents",
2231
+ "income_band": "10L-25L",
2232
+ "location_tier": "tier1",
2233
+ "marital_stage": "married_no_kids",
2234
+ "existing_cover_inr": 0,
2235
+ "health_conditions": [],
2236
+ "parents_to_insure": true,
2237
+ "parents_age_max": 75,
2238
+ "parents_has_ped": false,
2239
+ "budget_band": "30k_60k",
2240
+ "style": "formal_en",
2241
+ "style_label": "Formal English, full sentences",
2242
+ "lang": "en",
2243
+ "style_hedges": []
2244
+ },
2245
+ {
2246
+ "persona_id": "P078",
2247
+ "name": "Vikram Sharma",
2248
+ "archetype": "low_trust",
2249
+ "archetype_label": "Low trust in insurers; needs proof",
2250
+ "primary_goal": "first_buy",
2251
+ "anchor_concerns": [
2252
+ "claim_settlement",
2253
+ "reviews",
2254
+ "regulatory_overlay"
2255
+ ],
2256
+ "age": 52,
2257
+ "dependents": "self+spouse",
2258
+ "income_band": "10L-25L",
2259
+ "location_tier": "tier2",
2260
+ "marital_stage": "empty_nester",
2261
+ "existing_cover_inr": 0,
2262
+ "health_conditions": [
2263
+ "hypertension"
2264
+ ],
2265
+ "parents_to_insure": false,
2266
+ "parents_age_max": null,
2267
+ "parents_has_ped": false,
2268
+ "budget_band": "30k_60k",
2269
+ "style": "casual_en",
2270
+ "style_label": "Casual English with typos and lowercase",
2271
+ "lang": "en",
2272
+ "style_hedges": [
2273
+ "btw ",
2274
+ "lol ",
2275
+ "tbh "
2276
+ ]
2277
+ },
2278
+ {
2279
+ "persona_id": "P079",
2280
+ "name": "Priya Joshi",
2281
+ "archetype": "low_trust",
2282
+ "archetype_label": "Low trust in insurers; needs proof",
2283
+ "primary_goal": "first_buy",
2284
+ "anchor_concerns": [
2285
+ "claim_settlement",
2286
+ "reviews",
2287
+ "regulatory_overlay"
2288
+ ],
2289
+ "age": 58,
2290
+ "dependents": "self+spouse",
2291
+ "income_band": "5L-10L",
2292
+ "location_tier": "tier2",
2293
+ "marital_stage": "near_retire",
2294
+ "existing_cover_inr": 0,
2295
+ "health_conditions": [
2296
+ "hypertension"
2297
+ ],
2298
+ "parents_to_insure": false,
2299
+ "parents_age_max": null,
2300
+ "parents_has_ped": false,
2301
+ "budget_band": "15k_30k",
2302
+ "style": "hindi_primary",
2303
+ "style_label": "Mostly Hindi (Devanagari letters)",
2304
+ "lang": "hi",
2305
+ "style_hedges": []
2306
+ },
2307
+ {
2308
+ "persona_id": "P080",
2309
+ "name": "Karthik Banerjee",
2310
+ "archetype": "low_trust",
2311
+ "archetype_label": "Low trust in insurers; needs proof",
2312
+ "primary_goal": "first_buy",
2313
+ "anchor_concerns": [
2314
+ "claim_settlement",
2315
+ "reviews",
2316
+ "regulatory_overlay"
2317
+ ],
2318
+ "age": 63,
2319
+ "dependents": "self+spouse",
2320
+ "income_band": "under_5L",
2321
+ "location_tier": "tier3",
2322
+ "marital_stage": "retired",
2323
+ "existing_cover_inr": 0,
2324
+ "health_conditions": [
2325
+ "hypertension"
2326
+ ],
2327
+ "parents_to_insure": false,
2328
+ "parents_age_max": null,
2329
+ "parents_has_ped": false,
2330
+ "budget_band": "under_15k",
2331
+ "style": "anxious_q",
2332
+ "style_label": "Asks lots of follow-up questions",
2333
+ "lang": "en",
2334
+ "style_hedges": [
2335
+ "but ",
2336
+ "wait, ",
2337
+ "what about "
2338
+ ]
2339
+ },
2340
+ {
2341
+ "persona_id": "P081",
2342
+ "name": "Saif Banerjee",
2343
+ "archetype": "code_switcher",
2344
+ "archetype_label": "Switches English ↔ Hinglish mid-flow",
2345
+ "primary_goal": "first_buy",
2346
+ "anchor_concerns": [
2347
+ "language_switch",
2348
+ "premium_band",
2349
+ "coverage_breadth"
2350
+ ],
2351
+ "age": 24,
2352
+ "dependents": "self",
2353
+ "income_band": "under_5L",
2354
+ "location_tier": "metro",
2355
+ "marital_stage": "single",
2356
+ "existing_cover_inr": 500000,
2357
+ "health_conditions": [],
2358
+ "parents_to_insure": false,
2359
+ "parents_age_max": null,
2360
+ "parents_has_ped": false,
2361
+ "budget_band": "under_15k",
2362
+ "style": "stream",
2363
+ "style_label": "Stream-of-consciousness, rambles",
2364
+ "lang": "en",
2365
+ "style_hedges": [
2366
+ "uh ",
2367
+ "hmm ",
2368
+ "and ",
2369
+ "also "
2370
+ ]
2371
+ },
2372
+ {
2373
+ "persona_id": "P082",
2374
+ "name": "Ishita Iyer",
2375
+ "archetype": "code_switcher",
2376
+ "archetype_label": "Switches English ↔ Hinglish mid-flow",
2377
+ "primary_goal": "first_buy",
2378
+ "anchor_concerns": [
2379
+ "language_switch",
2380
+ "premium_band",
2381
+ "coverage_breadth"
2382
+ ],
2383
+ "age": 28,
2384
+ "dependents": "self",
2385
+ "income_band": "5L-10L",
2386
+ "location_tier": "metro",
2387
+ "marital_stage": "single",
2388
+ "existing_cover_inr": 500000,
2389
+ "health_conditions": [],
2390
+ "parents_to_insure": false,
2391
+ "parents_age_max": null,
2392
+ "parents_has_ped": false,
2393
+ "budget_band": "15k_30k",
2394
+ "style": "tester",
2395
+ "style_label": "Tries to trip up the bot",
2396
+ "lang": "en",
2397
+ "style_hedges": []
2398
+ },
2399
+ {
2400
+ "persona_id": "P083",
2401
+ "name": "Aarav Khan",
2402
+ "archetype": "code_switcher",
2403
+ "archetype_label": "Switches English ↔ Hinglish mid-flow",
2404
+ "primary_goal": "first_buy",
2405
+ "anchor_concerns": [
2406
+ "language_switch",
2407
+ "premium_band",
2408
+ "coverage_breadth"
2409
+ ],
2410
+ "age": 31,
2411
+ "dependents": "self+spouse",
2412
+ "income_band": "10L-25L",
2413
+ "location_tier": "metro",
2414
+ "marital_stage": "married",
2415
+ "existing_cover_inr": 500000,
2416
+ "health_conditions": [],
2417
+ "parents_to_insure": false,
2418
+ "parents_age_max": null,
2419
+ "parents_has_ped": false,
2420
+ "budget_band": "30k_60k",
2421
+ "style": "terse",
2422
+ "style_label": "Terse — 3-7 word answers",
2423
+ "lang": "en",
2424
+ "style_hedges": []
2425
+ },
2426
+ {
2427
+ "persona_id": "P084",
2428
+ "name": "Diya Kapoor",
2429
+ "archetype": "code_switcher",
2430
+ "archetype_label": "Switches English ↔ Hinglish mid-flow",
2431
+ "primary_goal": "first_buy",
2432
+ "anchor_concerns": [
2433
+ "language_switch",
2434
+ "premium_band",
2435
+ "coverage_breadth"
2436
+ ],
2437
+ "age": 34,
2438
+ "dependents": "self+spouse+kids",
2439
+ "income_band": "10L-25L",
2440
+ "location_tier": "tier1",
2441
+ "marital_stage": "married_kids",
2442
+ "existing_cover_inr": 500000,
2443
+ "health_conditions": [],
2444
+ "parents_to_insure": false,
2445
+ "parents_age_max": null,
2446
+ "parents_has_ped": false,
2447
+ "budget_band": "30k_60k",
2448
+ "style": "verbose",
2449
+ "style_label": "Verbose — paragraphs of context",
2450
+ "lang": "en",
2451
+ "style_hedges": [
2452
+ "um, ",
2453
+ "well, ",
2454
+ "you know, "
2455
+ ]
2456
+ },
2457
+ {
2458
+ "persona_id": "P085",
2459
+ "name": "Rohan Mehta",
2460
+ "archetype": "code_switcher",
2461
+ "archetype_label": "Switches English ↔ Hinglish mid-flow",
2462
+ "primary_goal": "first_buy",
2463
+ "anchor_concerns": [
2464
+ "language_switch",
2465
+ "premium_band",
2466
+ "coverage_breadth"
2467
+ ],
2468
+ "age": 38,
2469
+ "dependents": "self+spouse+kids",
2470
+ "income_band": "25L+",
2471
+ "location_tier": "metro",
2472
+ "marital_stage": "married_kids",
2473
+ "existing_cover_inr": 500000,
2474
+ "health_conditions": [],
2475
+ "parents_to_insure": false,
2476
+ "parents_age_max": null,
2477
+ "parents_has_ped": false,
2478
+ "budget_band": "60k+",
2479
+ "style": "hinglish",
2480
+ "style_label": "English with Hindi words sprinkled",
2481
+ "lang": "hinglish",
2482
+ "style_hedges": []
2483
+ },
2484
+ {
2485
+ "persona_id": "P086",
2486
+ "name": "Ananya Pillai",
2487
+ "archetype": "code_switcher",
2488
+ "archetype_label": "Switches English ↔ Hinglish mid-flow",
2489
+ "primary_goal": "first_buy",
2490
+ "anchor_concerns": [
2491
+ "language_switch",
2492
+ "premium_band",
2493
+ "coverage_breadth"
2494
+ ],
2495
+ "age": 42,
2496
+ "dependents": "self+spouse+kids+parents",
2497
+ "income_band": "25L+",
2498
+ "location_tier": "metro",
2499
+ "marital_stage": "sandwich_gen",
2500
+ "existing_cover_inr": 500000,
2501
+ "health_conditions": [],
2502
+ "parents_to_insure": true,
2503
+ "parents_age_max": 75,
2504
+ "parents_has_ped": false,
2505
+ "budget_band": "60k+",
2506
+ "style": "formal_en",
2507
+ "style_label": "Formal English, full sentences",
2508
+ "lang": "en",
2509
+ "style_hedges": []
2510
+ },
2511
+ {
2512
+ "persona_id": "P087",
2513
+ "name": "Vikram Nair",
2514
+ "archetype": "code_switcher",
2515
+ "archetype_label": "Switches English ↔ Hinglish mid-flow",
2516
+ "primary_goal": "first_buy",
2517
+ "anchor_concerns": [
2518
+ "language_switch",
2519
+ "premium_band",
2520
+ "coverage_breadth"
2521
+ ],
2522
+ "age": 47,
2523
+ "dependents": "self+spouse+parents",
2524
+ "income_band": "10L-25L",
2525
+ "location_tier": "tier1",
2526
+ "marital_stage": "married_no_kids",
2527
+ "existing_cover_inr": 500000,
2528
+ "health_conditions": [],
2529
+ "parents_to_insure": true,
2530
+ "parents_age_max": 75,
2531
+ "parents_has_ped": false,
2532
+ "budget_band": "30k_60k",
2533
+ "style": "casual_en",
2534
+ "style_label": "Casual English with typos and lowercase",
2535
+ "lang": "en",
2536
+ "style_hedges": [
2537
+ "btw ",
2538
+ "lol ",
2539
+ "tbh "
2540
+ ]
2541
+ },
2542
+ {
2543
+ "persona_id": "P088",
2544
+ "name": "Priya Reddy",
2545
+ "archetype": "code_switcher",
2546
+ "archetype_label": "Switches English ↔ Hinglish mid-flow",
2547
+ "primary_goal": "first_buy",
2548
+ "anchor_concerns": [
2549
+ "language_switch",
2550
+ "premium_band",
2551
+ "coverage_breadth"
2552
+ ],
2553
+ "age": 52,
2554
+ "dependents": "self+spouse",
2555
+ "income_band": "10L-25L",
2556
+ "location_tier": "tier2",
2557
+ "marital_stage": "empty_nester",
2558
+ "existing_cover_inr": 500000,
2559
+ "health_conditions": [
2560
+ "hypertension"
2561
+ ],
2562
+ "parents_to_insure": false,
2563
+ "parents_age_max": null,
2564
+ "parents_has_ped": false,
2565
+ "budget_band": "30k_60k",
2566
+ "style": "hindi_primary",
2567
+ "style_label": "Mostly Hindi (Devanagari letters)",
2568
+ "lang": "hi",
2569
+ "style_hedges": []
2570
+ },
2571
+ {
2572
+ "persona_id": "P089",
2573
+ "name": "Karthik Sharma",
2574
+ "archetype": "code_switcher",
2575
+ "archetype_label": "Switches English ↔ Hinglish mid-flow",
2576
+ "primary_goal": "first_buy",
2577
+ "anchor_concerns": [
2578
+ "language_switch",
2579
+ "premium_band",
2580
+ "coverage_breadth"
2581
+ ],
2582
+ "age": 58,
2583
+ "dependents": "self+spouse",
2584
+ "income_band": "5L-10L",
2585
+ "location_tier": "tier2",
2586
+ "marital_stage": "near_retire",
2587
+ "existing_cover_inr": 500000,
2588
+ "health_conditions": [
2589
+ "hypertension"
2590
+ ],
2591
+ "parents_to_insure": false,
2592
+ "parents_age_max": null,
2593
+ "parents_has_ped": false,
2594
+ "budget_band": "15k_30k",
2595
+ "style": "anxious_q",
2596
+ "style_label": "Asks lots of follow-up questions",
2597
+ "lang": "en",
2598
+ "style_hedges": [
2599
+ "but ",
2600
+ "wait, ",
2601
+ "what about "
2602
+ ]
2603
+ },
2604
+ {
2605
+ "persona_id": "P090",
2606
+ "name": "Meera Joshi",
2607
+ "archetype": "code_switcher",
2608
+ "archetype_label": "Switches English ↔ Hinglish mid-flow",
2609
+ "primary_goal": "first_buy",
2610
+ "anchor_concerns": [
2611
+ "language_switch",
2612
+ "premium_band",
2613
+ "coverage_breadth"
2614
+ ],
2615
+ "age": 63,
2616
+ "dependents": "self+spouse",
2617
+ "income_band": "under_5L",
2618
+ "location_tier": "tier3",
2619
+ "marital_stage": "retired",
2620
+ "existing_cover_inr": 500000,
2621
+ "health_conditions": [
2622
+ "hypertension"
2623
+ ],
2624
+ "parents_to_insure": false,
2625
+ "parents_age_max": null,
2626
+ "parents_has_ped": false,
2627
+ "budget_band": "under_15k",
2628
+ "style": "numbers_heavy",
2629
+ "style_label": "Quotes specific numbers and policies",
2630
+ "lang": "en",
2631
+ "style_hedges": []
2632
+ },
2633
+ {
2634
+ "persona_id": "P091",
2635
+ "name": "Ishita Joshi",
2636
+ "archetype": "specific_condition",
2637
+ "archetype_label": "Has a specific condition driving the buy",
2638
+ "primary_goal": "upgrade",
2639
+ "anchor_concerns": [
2640
+ "ped_waiting",
2641
+ "specific_disease_waiting",
2642
+ "sub_limits"
2643
+ ],
2644
+ "age": 24,
2645
+ "dependents": "self",
2646
+ "income_band": "under_5L",
2647
+ "location_tier": "metro",
2648
+ "marital_stage": "single",
2649
+ "existing_cover_inr": 500000,
2650
+ "health_conditions": [
2651
+ "asthma"
2652
+ ],
2653
+ "parents_to_insure": false,
2654
+ "parents_age_max": null,
2655
+ "parents_has_ped": false,
2656
+ "budget_band": "under_15k",
2657
+ "style": "tester",
2658
+ "style_label": "Tries to trip up the bot",
2659
+ "lang": "en",
2660
+ "style_hedges": []
2661
+ },
2662
+ {
2663
+ "persona_id": "P092",
2664
+ "name": "Aarav Banerjee",
2665
+ "archetype": "specific_condition",
2666
+ "archetype_label": "Has a specific condition driving the buy",
2667
+ "primary_goal": "upgrade",
2668
+ "anchor_concerns": [
2669
+ "ped_waiting",
2670
+ "specific_disease_waiting",
2671
+ "sub_limits"
2672
+ ],
2673
+ "age": 28,
2674
+ "dependents": "self",
2675
+ "income_band": "5L-10L",
2676
+ "location_tier": "metro",
2677
+ "marital_stage": "single",
2678
+ "existing_cover_inr": 500000,
2679
+ "health_conditions": [
2680
+ "asthma"
2681
+ ],
2682
+ "parents_to_insure": false,
2683
+ "parents_age_max": null,
2684
+ "parents_has_ped": false,
2685
+ "budget_band": "15k_30k",
2686
+ "style": "terse",
2687
+ "style_label": "Terse — 3-7 word answers",
2688
+ "lang": "en",
2689
+ "style_hedges": []
2690
+ },
2691
+ {
2692
+ "persona_id": "P093",
2693
+ "name": "Diya Iyer",
2694
+ "archetype": "specific_condition",
2695
+ "archetype_label": "Has a specific condition driving the buy",
2696
+ "primary_goal": "upgrade",
2697
+ "anchor_concerns": [
2698
+ "ped_waiting",
2699
+ "specific_disease_waiting",
2700
+ "sub_limits"
2701
+ ],
2702
+ "age": 31,
2703
+ "dependents": "self+spouse",
2704
+ "income_band": "10L-25L",
2705
+ "location_tier": "metro",
2706
+ "marital_stage": "married",
2707
+ "existing_cover_inr": 500000,
2708
+ "health_conditions": [
2709
+ "asthma"
2710
+ ],
2711
+ "parents_to_insure": false,
2712
+ "parents_age_max": null,
2713
+ "parents_has_ped": false,
2714
+ "budget_band": "30k_60k",
2715
+ "style": "verbose",
2716
+ "style_label": "Verbose — paragraphs of context",
2717
+ "lang": "en",
2718
+ "style_hedges": [
2719
+ "um, ",
2720
+ "well, ",
2721
+ "you know, "
2722
+ ]
2723
+ },
2724
+ {
2725
+ "persona_id": "P094",
2726
+ "name": "Rohan Khan",
2727
+ "archetype": "specific_condition",
2728
+ "archetype_label": "Has a specific condition driving the buy",
2729
+ "primary_goal": "upgrade",
2730
+ "anchor_concerns": [
2731
+ "ped_waiting",
2732
+ "specific_disease_waiting",
2733
+ "sub_limits"
2734
+ ],
2735
+ "age": 34,
2736
+ "dependents": "self+spouse+kids",
2737
+ "income_band": "10L-25L",
2738
+ "location_tier": "tier1",
2739
+ "marital_stage": "married_kids",
2740
+ "existing_cover_inr": 500000,
2741
+ "health_conditions": [
2742
+ "asthma"
2743
+ ],
2744
+ "parents_to_insure": false,
2745
+ "parents_age_max": null,
2746
+ "parents_has_ped": false,
2747
+ "budget_band": "30k_60k",
2748
+ "style": "hinglish",
2749
+ "style_label": "English with Hindi words sprinkled",
2750
+ "lang": "hinglish",
2751
+ "style_hedges": []
2752
+ },
2753
+ {
2754
+ "persona_id": "P095",
2755
+ "name": "Ananya Kapoor",
2756
+ "archetype": "specific_condition",
2757
+ "archetype_label": "Has a specific condition driving the buy",
2758
+ "primary_goal": "upgrade",
2759
+ "anchor_concerns": [
2760
+ "ped_waiting",
2761
+ "specific_disease_waiting",
2762
+ "sub_limits"
2763
+ ],
2764
+ "age": 38,
2765
+ "dependents": "self+spouse+kids",
2766
+ "income_band": "25L+",
2767
+ "location_tier": "metro",
2768
+ "marital_stage": "married_kids",
2769
+ "existing_cover_inr": 500000,
2770
+ "health_conditions": [
2771
+ "hypertension"
2772
+ ],
2773
+ "parents_to_insure": false,
2774
+ "parents_age_max": null,
2775
+ "parents_has_ped": false,
2776
+ "budget_band": "60k+",
2777
+ "style": "formal_en",
2778
+ "style_label": "Formal English, full sentences",
2779
+ "lang": "en",
2780
+ "style_hedges": []
2781
+ },
2782
+ {
2783
+ "persona_id": "P096",
2784
+ "name": "Vikram Mehta",
2785
+ "archetype": "specific_condition",
2786
+ "archetype_label": "Has a specific condition driving the buy",
2787
+ "primary_goal": "upgrade",
2788
+ "anchor_concerns": [
2789
+ "ped_waiting",
2790
+ "specific_disease_waiting",
2791
+ "sub_limits"
2792
+ ],
2793
+ "age": 42,
2794
+ "dependents": "self+spouse+kids+parents",
2795
+ "income_band": "25L+",
2796
+ "location_tier": "metro",
2797
+ "marital_stage": "sandwich_gen",
2798
+ "existing_cover_inr": 500000,
2799
+ "health_conditions": [
2800
+ "hypertension"
2801
+ ],
2802
+ "parents_to_insure": true,
2803
+ "parents_age_max": 75,
2804
+ "parents_has_ped": false,
2805
+ "budget_band": "60k+",
2806
+ "style": "casual_en",
2807
+ "style_label": "Casual English with typos and lowercase",
2808
+ "lang": "en",
2809
+ "style_hedges": [
2810
+ "btw ",
2811
+ "lol ",
2812
+ "tbh "
2813
+ ]
2814
+ },
2815
+ {
2816
+ "persona_id": "P097",
2817
+ "name": "Priya Pillai",
2818
+ "archetype": "specific_condition",
2819
+ "archetype_label": "Has a specific condition driving the buy",
2820
+ "primary_goal": "upgrade",
2821
+ "anchor_concerns": [
2822
+ "ped_waiting",
2823
+ "specific_disease_waiting",
2824
+ "sub_limits"
2825
+ ],
2826
+ "age": 47,
2827
+ "dependents": "self+spouse+parents",
2828
+ "income_band": "10L-25L",
2829
+ "location_tier": "tier1",
2830
+ "marital_stage": "married_no_kids",
2831
+ "existing_cover_inr": 500000,
2832
+ "health_conditions": [
2833
+ "hypertension"
2834
+ ],
2835
+ "parents_to_insure": true,
2836
+ "parents_age_max": 75,
2837
+ "parents_has_ped": false,
2838
+ "budget_band": "30k_60k",
2839
+ "style": "hindi_primary",
2840
+ "style_label": "Mostly Hindi (Devanagari letters)",
2841
+ "lang": "hi",
2842
+ "style_hedges": []
2843
+ },
2844
+ {
2845
+ "persona_id": "P098",
2846
+ "name": "Karthik Nair",
2847
+ "archetype": "specific_condition",
2848
+ "archetype_label": "Has a specific condition driving the buy",
2849
+ "primary_goal": "upgrade",
2850
+ "anchor_concerns": [
2851
+ "ped_waiting",
2852
+ "specific_disease_waiting",
2853
+ "sub_limits"
2854
+ ],
2855
+ "age": 52,
2856
+ "dependents": "self+spouse",
2857
+ "income_band": "10L-25L",
2858
+ "location_tier": "tier2",
2859
+ "marital_stage": "empty_nester",
2860
+ "existing_cover_inr": 500000,
2861
+ "health_conditions": [
2862
+ "diabetes",
2863
+ "hypertension"
2864
+ ],
2865
+ "parents_to_insure": false,
2866
+ "parents_age_max": null,
2867
+ "parents_has_ped": false,
2868
+ "budget_band": "30k_60k",
2869
+ "style": "anxious_q",
2870
+ "style_label": "Asks lots of follow-up questions",
2871
+ "lang": "en",
2872
+ "style_hedges": [
2873
+ "but ",
2874
+ "wait, ",
2875
+ "what about "
2876
+ ]
2877
+ },
2878
+ {
2879
+ "persona_id": "P099",
2880
+ "name": "Meera Reddy",
2881
+ "archetype": "specific_condition",
2882
+ "archetype_label": "Has a specific condition driving the buy",
2883
+ "primary_goal": "upgrade",
2884
+ "anchor_concerns": [
2885
+ "ped_waiting",
2886
+ "specific_disease_waiting",
2887
+ "sub_limits"
2888
+ ],
2889
+ "age": 58,
2890
+ "dependents": "self+spouse",
2891
+ "income_band": "5L-10L",
2892
+ "location_tier": "tier2",
2893
+ "marital_stage": "near_retire",
2894
+ "existing_cover_inr": 500000,
2895
+ "health_conditions": [
2896
+ "diabetes",
2897
+ "hypertension"
2898
+ ],
2899
+ "parents_to_insure": false,
2900
+ "parents_age_max": null,
2901
+ "parents_has_ped": false,
2902
+ "budget_band": "15k_30k",
2903
+ "style": "numbers_heavy",
2904
+ "style_label": "Quotes specific numbers and policies",
2905
+ "lang": "en",
2906
+ "style_hedges": []
2907
+ },
2908
+ {
2909
+ "persona_id": "P100",
2910
+ "name": "Saif Sharma",
2911
+ "archetype": "specific_condition",
2912
+ "archetype_label": "Has a specific condition driving the buy",
2913
+ "primary_goal": "upgrade",
2914
+ "anchor_concerns": [
2915
+ "ped_waiting",
2916
+ "specific_disease_waiting",
2917
+ "sub_limits"
2918
+ ],
2919
+ "age": 63,
2920
+ "dependents": "self+spouse",
2921
+ "income_band": "under_5L",
2922
+ "location_tier": "tier3",
2923
+ "marital_stage": "retired",
2924
+ "existing_cover_inr": 500000,
2925
+ "health_conditions": [
2926
+ "diabetes",
2927
+ "hypertension"
2928
+ ],
2929
+ "parents_to_insure": false,
2930
+ "parents_age_max": null,
2931
+ "parents_has_ped": false,
2932
+ "budget_band": "under_15k",
2933
+ "style": "stream",
2934
+ "style_label": "Stream-of-consciousness, rambles",
2935
+ "lang": "en",
2936
+ "style_hedges": [
2937
+ "uh ",
2938
+ "hmm ",
2939
+ "and ",
2940
+ "also "
2941
+ ]
2942
+ }
2943
+ ]
tools/audit/personas.py ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Generate 100 diverse personas for end-to-end bot audit.
2
+
3
+ Each persona is the product of:
4
+ - 10 archetypes (intent shape: first-buyer, upgrader, senior-care, …)
5
+ - 10 demographic profiles (age × dependents × income × city tier)
6
+ - 1 conversational style (deterministic-pick from a curated list)
7
+
8
+ The cross-product is engineered so that no two personas have the same
9
+ (archetype, demo, style) triple. Each persona is stable across runs (the
10
+ order is index-based), so report diffs reveal regressions rather than
11
+ shuffle noise.
12
+
13
+ Run as a script:
14
+ python tools/audit/personas.py
15
+ → writes tools/audit/personas.json (100 entries)
16
+
17
+ Read from another script:
18
+ from tools.audit.personas import generate
19
+ personas = generate() # → list[dict]
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import json
24
+ from pathlib import Path
25
+ from typing import Any
26
+
27
+ # ----------------------------------------------------------------------------
28
+ # Archetypes — the user's INTENT shape. Drives flow design downstream.
29
+ # ----------------------------------------------------------------------------
30
+ ARCHETYPES = [
31
+ {
32
+ "id": "first_buyer",
33
+ "label": "First-time health insurance buyer",
34
+ "primary_goal": "first_buy",
35
+ "anchor_concerns": ["coverage_breadth", "premium_value", "claim_settlement"],
36
+ },
37
+ {
38
+ "id": "upgrader",
39
+ "label": "Existing-cover upgrader",
40
+ "primary_goal": "upgrade",
41
+ "anchor_concerns": ["sum_insured_size", "ped_waiting", "restoration_benefit"],
42
+ },
43
+ {
44
+ "id": "senior_care",
45
+ "label": "Buying for senior parents",
46
+ "primary_goal": "first_buy",
47
+ "anchor_concerns": ["parents_age_max", "ped_waiting", "specific_disease_waiting"],
48
+ },
49
+ {
50
+ "id": "comparer",
51
+ "label": "Comparing 2-3 specific policies",
52
+ "primary_goal": "compare_specific",
53
+ "anchor_concerns": ["sub_limits", "network_hospitals", "no_claim_bonus"],
54
+ },
55
+ {
56
+ "id": "anxious",
57
+ "label": "Anxious skeptic — claim-denial fears",
58
+ "primary_goal": "first_buy",
59
+ "anchor_concerns": ["claim_settlement", "exclusions", "free_look"],
60
+ },
61
+ {
62
+ "id": "savvy",
63
+ "label": "Financially literate, asks pointed questions",
64
+ "primary_goal": "compare_specific",
65
+ "anchor_concerns": ["irdai_mandate", "tax_treatment", "ombudsman"],
66
+ },
67
+ {
68
+ "id": "tax_planner",
69
+ "label": "Tax-planning oriented",
70
+ "primary_goal": "tax_planning",
71
+ "anchor_concerns": ["section_80d", "premium_band", "sum_insured_size"],
72
+ },
73
+ {
74
+ "id": "low_trust",
75
+ "label": "Low trust in insurers; needs proof",
76
+ "primary_goal": "first_buy",
77
+ "anchor_concerns": ["claim_settlement", "reviews", "regulatory_overlay"],
78
+ },
79
+ {
80
+ "id": "code_switcher",
81
+ "label": "Switches English ↔ Hinglish mid-flow",
82
+ "primary_goal": "first_buy",
83
+ "anchor_concerns": ["language_switch", "premium_band", "coverage_breadth"],
84
+ },
85
+ {
86
+ "id": "specific_condition",
87
+ "label": "Has a specific condition driving the buy",
88
+ "primary_goal": "upgrade",
89
+ "anchor_concerns": ["ped_waiting", "specific_disease_waiting", "sub_limits"],
90
+ },
91
+ ]
92
+
93
+ # ----------------------------------------------------------------------------
94
+ # Demographic variations — 10 stable cells across age × family × income × city.
95
+ # Each cell is a different life situation; together they span ~70% of Indian
96
+ # middle-class health-insurance buyers.
97
+ # ----------------------------------------------------------------------------
98
+ DEMOGRAPHICS = [
99
+ {"age": 24, "dependents": "self", "income_band": "under_5L", "location_tier": "metro", "marital": "single"},
100
+ {"age": 28, "dependents": "self", "income_band": "5L-10L", "location_tier": "metro", "marital": "single"},
101
+ {"age": 31, "dependents": "self+spouse", "income_band": "10L-25L", "location_tier": "metro", "marital": "married"},
102
+ {"age": 34, "dependents": "self+spouse+kids", "income_band": "10L-25L", "location_tier": "tier1", "marital": "married_kids"},
103
+ {"age": 38, "dependents": "self+spouse+kids", "income_band": "25L+", "location_tier": "metro", "marital": "married_kids"},
104
+ {"age": 42, "dependents": "self+spouse+kids+parents", "income_band": "25L+", "location_tier": "metro", "marital": "sandwich_gen"},
105
+ {"age": 47, "dependents": "self+spouse+parents", "income_band": "10L-25L", "location_tier": "tier1", "marital": "married_no_kids"},
106
+ {"age": 52, "dependents": "self+spouse", "income_band": "10L-25L", "location_tier": "tier2", "marital": "empty_nester"},
107
+ {"age": 58, "dependents": "self+spouse", "income_band": "5L-10L", "location_tier": "tier2", "marital": "near_retire"},
108
+ {"age": 63, "dependents": "self+spouse", "income_band": "under_5L", "location_tier": "tier3", "marital": "retired"},
109
+ ]
110
+
111
+ # ----------------------------------------------------------------------------
112
+ # Conversational styles — how the persona TALKS, independent of what they
113
+ # want. The 10 styles cycle so each (archetype × demo) cell gets a different
114
+ # voice across the 100 personas.
115
+ # ----------------------------------------------------------------------------
116
+ STYLES = [
117
+ {"id": "terse", "label": "Terse — 3-7 word answers", "lang": "en", "hedges": []},
118
+ {"id": "verbose", "label": "Verbose — paragraphs of context", "lang": "en", "hedges": ["um, ", "well, ", "you know, "]},
119
+ {"id": "hinglish", "label": "English with Hindi words sprinkled", "lang": "hinglish", "hedges": []},
120
+ {"id": "formal_en", "label": "Formal English, full sentences", "lang": "en", "hedges": []},
121
+ {"id": "casual_en", "label": "Casual English with typos and lowercase", "lang": "en", "hedges": ["btw ", "lol ", "tbh "]},
122
+ {"id": "hindi_primary","label": "Mostly Hindi (Devanagari letters)", "lang": "hi", "hedges": []},
123
+ {"id": "anxious_q", "label": "Asks lots of follow-up questions", "lang": "en", "hedges": ["but ", "wait, ", "what about "]},
124
+ {"id": "numbers_heavy","label": "Quotes specific numbers and policies", "lang": "en", "hedges": []},
125
+ {"id": "stream", "label": "Stream-of-consciousness, rambles", "lang": "en", "hedges": ["uh ", "hmm ", "and ", "also "]},
126
+ {"id": "tester", "label": "Tries to trip up the bot", "lang": "en", "hedges": []},
127
+ ]
128
+
129
+ # ----------------------------------------------------------------------------
130
+ # Synthetic name pool — 10 first names × 10 surnames covers our 100 personas
131
+ # without recycling the same identity. Names are common Indian names with a
132
+ # light regional spread (north + south + west).
133
+ # ----------------------------------------------------------------------------
134
+ FIRST_NAMES = ["Aarav", "Diya", "Rohan", "Ananya", "Vikram", "Priya", "Karthik", "Meera", "Saif", "Ishita"]
135
+ SURNAMES = ["Sharma", "Iyer", "Mehta", "Reddy", "Banerjee", "Kapoor", "Nair", "Joshi", "Khan", "Pillai"]
136
+
137
+ # Health condition presets keyed by archetype + age bucket.
138
+ def _condition_for(archetype_id: str, age: int) -> list[str]:
139
+ if archetype_id == "specific_condition":
140
+ if age < 35:
141
+ return ["asthma"]
142
+ if age < 50:
143
+ return ["hypertension"]
144
+ return ["diabetes", "hypertension"]
145
+ if archetype_id == "senior_care":
146
+ return [] # parents' conditions handled separately in flow
147
+ if age >= 50:
148
+ return ["hypertension"] # age-bucket baseline
149
+ return []
150
+
151
+
152
+ def generate() -> list[dict[str, Any]]:
153
+ personas: list[dict[str, Any]] = []
154
+ pid = 1
155
+ for ai, arch in enumerate(ARCHETYPES):
156
+ for di, demo in enumerate(DEMOGRAPHICS):
157
+ style = STYLES[(ai + di) % len(STYLES)] # rotates so styles spread evenly across archetypes
158
+ name_first = FIRST_NAMES[(ai + di) % len(FIRST_NAMES)]
159
+ name_last = SURNAMES[(ai * 3 + di * 7) % len(SURNAMES)]
160
+ persona = {
161
+ "persona_id": f"P{pid:03d}",
162
+ "name": f"{name_first} {name_last}",
163
+ "archetype": arch["id"],
164
+ "archetype_label": arch["label"],
165
+ "primary_goal": arch["primary_goal"],
166
+ "anchor_concerns": arch["anchor_concerns"],
167
+ "age": demo["age"],
168
+ "dependents": demo["dependents"],
169
+ "income_band": demo["income_band"],
170
+ "location_tier": demo["location_tier"],
171
+ "marital_stage": demo["marital"],
172
+ "existing_cover_inr": 0 if arch["id"] in ("first_buyer", "senior_care", "anxious", "tax_planner", "low_trust") else 500_000,
173
+ "health_conditions": _condition_for(arch["id"], demo["age"]),
174
+ "parents_to_insure": arch["id"] == "senior_care" or "parents" in demo["dependents"],
175
+ "parents_age_max": 75 if arch["id"] == "senior_care" or "parents" in demo["dependents"] else None,
176
+ "parents_has_ped": arch["id"] == "senior_care",
177
+ "budget_band": {
178
+ "under_5L": "under_15k",
179
+ "5L-10L": "15k_30k",
180
+ "10L-25L": "30k_60k",
181
+ "25L+": "60k+",
182
+ }[demo["income_band"]],
183
+ "style": style["id"],
184
+ "style_label": style["label"],
185
+ "lang": style["lang"],
186
+ "style_hedges": style["hedges"],
187
+ }
188
+ personas.append(persona)
189
+ pid += 1
190
+ return personas
191
+
192
+
193
+ def main() -> None:
194
+ personas = generate()
195
+ out = Path(__file__).resolve().parent / "personas.json"
196
+ out.write_text(json.dumps(personas, indent=2, ensure_ascii=False))
197
+ print(f"wrote {out} ({len(personas)} personas)")
198
+ # Quick distribution sanity check
199
+ by_arch: dict[str, int] = {}
200
+ by_style: dict[str, int] = {}
201
+ for p in personas:
202
+ by_arch[p["archetype"]] = by_arch.get(p["archetype"], 0) + 1
203
+ by_style[p["style"]] = by_style.get(p["style"], 0) + 1
204
+ print(f" archetypes (each should be 10): {by_arch}")
205
+ print(f" styles (each should be 10): {by_style}")
206
+
207
+
208
+ if __name__ == "__main__":
209
+ main()
tools/audit/run_audit.py ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Run the audit — walks each persona's 30-turn flow against the live API.
2
+
3
+ Design notes:
4
+ - Resumable: completed personas land in audit_results/<run_id>/transcripts/
5
+ as soon as they finish; if re-run, already-saved IDs are skipped.
6
+ - Rate-limited: NIM's 40 req/min cap is the bottleneck. Each /api/chat
7
+ triggers ~1-3 NIM calls (brain + sometimes judge + sometimes cross-check).
8
+ We use a serial loop with adaptive sleep, plus a soft retry on 5xx.
9
+ - Concurrent: WORKERS workers pull personas off a shared queue. The global
10
+ NIM cap is enforced via the per-request sleep so concurrency × dispatch
11
+ rate stays under the cap.
12
+ - Each turn captures: reply_text, brain_used, intent, citations,
13
+ profile_updates, faithfulness_passed, blocked, latency_ms.
14
+
15
+ Usage:
16
+ python tools/audit/run_audit.py [--max-personas N] [--workers W] [--base URL]
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import argparse
21
+ import asyncio
22
+ import json
23
+ import time
24
+ import uuid
25
+ from pathlib import Path
26
+ from typing import Any
27
+
28
+ import httpx
29
+
30
+ DEFAULT_BASE = "https://rohitsar567-insurancebot.hf.space"
31
+ ROOT = Path(__file__).resolve().parent
32
+ RESULTS_ROOT = ROOT.parent.parent / "audit_results"
33
+
34
+
35
+ async def post_chat(
36
+ client: httpx.AsyncClient,
37
+ base: str,
38
+ user_text: str,
39
+ session_id: str,
40
+ chat_history: list[dict],
41
+ sema: asyncio.Semaphore,
42
+ delay_s: float,
43
+ ) -> dict[str, Any]:
44
+ """One /api/chat call. Returns raw response dict + latency_ms.
45
+
46
+ On HTTP 5xx, retries up to 3x with exponential backoff (3s, 8s, 18s).
47
+ """
48
+ payload = {
49
+ "user_text": user_text,
50
+ "session_id": session_id,
51
+ "chat_history": chat_history,
52
+ "profile": {},
53
+ "return_audio": False,
54
+ "tts_language_code": "en-IN",
55
+ }
56
+ backoff = [3.0, 8.0, 18.0]
57
+ for attempt in range(len(backoff) + 1):
58
+ async with sema:
59
+ await asyncio.sleep(delay_s) # global rate-limit token
60
+ t0 = time.monotonic()
61
+ try:
62
+ resp = await client.post(f"{base}/api/chat", json=payload, timeout=60.0)
63
+ except (httpx.ConnectError, httpx.ReadTimeout, httpx.WriteTimeout, httpx.RemoteProtocolError) as e:
64
+ if attempt < len(backoff):
65
+ await asyncio.sleep(backoff[attempt])
66
+ continue
67
+ return {"error": f"{type(e).__name__}: {e}", "latency_ms": int((time.monotonic() - t0) * 1000)}
68
+ elapsed = int((time.monotonic() - t0) * 1000)
69
+ if resp.status_code >= 500 and attempt < len(backoff):
70
+ await asyncio.sleep(backoff[attempt])
71
+ continue
72
+ if resp.status_code != 200:
73
+ return {
74
+ "error": f"http_{resp.status_code}",
75
+ "body": resp.text[:500],
76
+ "latency_ms": elapsed,
77
+ }
78
+ data = resp.json()
79
+ data["_latency_ms"] = elapsed
80
+ return data
81
+ return {"error": "retries_exhausted"}
82
+
83
+
84
+ async def run_persona(
85
+ client: httpx.AsyncClient,
86
+ base: str,
87
+ persona: dict[str, Any],
88
+ flow: list[str],
89
+ out_dir: Path,
90
+ sema: asyncio.Semaphore,
91
+ delay_s: float,
92
+ log_lock: asyncio.Lock,
93
+ ) -> None:
94
+ out_file = out_dir / f"{persona['persona_id']}.json"
95
+ if out_file.exists():
96
+ async with log_lock:
97
+ print(f" skip {persona['persona_id']} (already done)")
98
+ return
99
+
100
+ session_id = f"audit_{persona['persona_id']}_{uuid.uuid4().hex[:6]}"
101
+ history: list[dict] = []
102
+ transcript: list[dict] = []
103
+
104
+ for turn_idx, user_text in enumerate(flow, 1):
105
+ resp = await post_chat(client, base, user_text, session_id, history, sema, delay_s)
106
+ if "error" in resp:
107
+ transcript.append({
108
+ "turn": turn_idx,
109
+ "user_text": user_text,
110
+ "error": resp.get("error"),
111
+ "body": resp.get("body"),
112
+ "latency_ms": resp.get("latency_ms") or resp.get("_latency_ms"),
113
+ })
114
+ # Don't break — keep going so we get partial transcript for analysis.
115
+ continue
116
+ reply_text = resp.get("reply_text", "")
117
+ transcript.append({
118
+ "turn": turn_idx,
119
+ "user_text": user_text,
120
+ "reply_text": reply_text,
121
+ "brain_used": resp.get("brain_used"),
122
+ "intent": resp.get("intent"),
123
+ "language": resp.get("language"),
124
+ "citations": resp.get("citations", []),
125
+ "profile_updates": resp.get("profile_updates", {}),
126
+ "faithfulness_passed": resp.get("faithfulness_passed"),
127
+ "faithfulness_reasons": resp.get("faithfulness_reasons", []),
128
+ "blocked": resp.get("blocked"),
129
+ "latency_ms": resp.get("_latency_ms"),
130
+ "session_id": resp.get("session_id"),
131
+ })
132
+ history.append({"role": "user", "content": user_text})
133
+ history.append({"role": "assistant", "content": reply_text})
134
+
135
+ payload = {
136
+ "persona": persona,
137
+ "session_id": session_id,
138
+ "turns": transcript,
139
+ "completed_turns": len(transcript),
140
+ "errors": sum(1 for t in transcript if t.get("error")),
141
+ }
142
+ out_file.write_text(json.dumps(payload, indent=2, ensure_ascii=False))
143
+ async with log_lock:
144
+ ok = sum(1 for t in transcript if not t.get("error"))
145
+ print(f" done {persona['persona_id']:<5} | {ok}/{len(transcript)} ok | "
146
+ f"refusals={sum(1 for t in transcript if t.get('blocked'))} | "
147
+ f"out={out_file.name}")
148
+
149
+
150
+ async def worker(
151
+ worker_id: int,
152
+ queue: asyncio.Queue,
153
+ base: str,
154
+ out_dir: Path,
155
+ sema: asyncio.Semaphore,
156
+ delay_s: float,
157
+ log_lock: asyncio.Lock,
158
+ ) -> None:
159
+ async with httpx.AsyncClient() as client:
160
+ while True:
161
+ item = await queue.get()
162
+ if item is None:
163
+ queue.task_done()
164
+ break
165
+ persona, flow = item
166
+ try:
167
+ await run_persona(client, base, persona, flow, out_dir, sema, delay_s, log_lock)
168
+ except Exception as e:
169
+ async with log_lock:
170
+ print(f" ERR {persona['persona_id']}: {type(e).__name__}: {e}")
171
+ queue.task_done()
172
+
173
+
174
+ async def main_async(args: argparse.Namespace) -> None:
175
+ personas = json.loads((ROOT / "personas.json").read_text())
176
+ flows = json.loads((ROOT / "flows.json").read_text())
177
+
178
+ if args.max_personas:
179
+ personas = personas[: args.max_personas]
180
+
181
+ run_id = args.run_id or time.strftime("%Y%m%d_%H%M%S")
182
+ out_dir = RESULTS_ROOT / run_id / "transcripts"
183
+ out_dir.mkdir(parents=True, exist_ok=True)
184
+ summary_file = RESULTS_ROOT / run_id / "summary.json"
185
+ print(f"=== audit run_id={run_id} ===")
186
+ print(f" base: {args.base}")
187
+ print(f" personas: {len(personas)}")
188
+ print(f" total turns: {sum(len(flows[p['persona_id']]) for p in personas)}")
189
+ print(f" workers: {args.workers}")
190
+ print(f" delay_s: {args.delay} (per dispatch)")
191
+ print(f" out: {out_dir}")
192
+
193
+ sema = asyncio.Semaphore(args.workers)
194
+ queue: asyncio.Queue = asyncio.Queue()
195
+ for p in personas:
196
+ await queue.put((p, flows[p["persona_id"]]))
197
+ # Sentinel to stop workers
198
+ for _ in range(args.workers):
199
+ await queue.put(None)
200
+
201
+ log_lock = asyncio.Lock()
202
+ started = time.time()
203
+ workers = [
204
+ asyncio.create_task(worker(i, queue, args.base, out_dir, sema, args.delay, log_lock))
205
+ for i in range(args.workers)
206
+ ]
207
+ await queue.join()
208
+ await asyncio.gather(*workers)
209
+ elapsed = time.time() - started
210
+
211
+ completed = sorted(out_dir.glob("*.json"))
212
+ summary_file.write_text(json.dumps({
213
+ "run_id": run_id,
214
+ "base": args.base,
215
+ "personas_requested": len(personas),
216
+ "personas_completed": len(completed),
217
+ "elapsed_seconds": int(elapsed),
218
+ "workers": args.workers,
219
+ "delay_s": args.delay,
220
+ "transcripts_dir": str(out_dir),
221
+ }, indent=2))
222
+ print(f"\n=== done in {elapsed:.0f}s ===")
223
+ print(f" completed: {len(completed)} / {len(personas)} personas")
224
+ print(f" summary: {summary_file}")
225
+
226
+
227
+ def main() -> None:
228
+ parser = argparse.ArgumentParser()
229
+ parser.add_argument("--base", default=DEFAULT_BASE)
230
+ parser.add_argument("--max-personas", type=int, default=None)
231
+ parser.add_argument("--workers", type=int, default=4, help="concurrent personas in flight")
232
+ parser.add_argument("--delay", type=float, default=1.8, help="seconds between dispatches per worker")
233
+ parser.add_argument("--run-id", default=None)
234
+ args = parser.parse_args()
235
+ asyncio.run(main_async(args))
236
+
237
+
238
+ if __name__ == "__main__":
239
+ main()