rohitsar567 Claude Opus 4.7 (1M context) commited on
Commit
f93292f
·
1 Parent(s): 75b229d

fix(fact-find): KI-012/013/014 — flow corrections from user testing

Browse files

KI-012 (P0) — Bot stuck in fact_find_complete readback loop
After fact-find completed, the bot kept emitting the same readback
summary instead of answering user's policy questions. Audit transcript
showed P002 wasted 19 of 30 turns echoing the profile back.
Fix: set session.free_form_session=True on completion + flush.

KI-013 (P0) — Bot recommends senior-citizen policy to non-seniors
Real user testing: vague opener → bot retrieved + pitched "Care
Senior" (senior-only policy) without ANY fact-find. Intent classifier
routed to recommendation, bypassing fact-find.
Fix: orchestrator forces fact-find when profile is empty (no age,
dependents, or income captured yet), regardless of intent.

KI-014 (P1) — "family" auto-mapped to self+spouse+kids
Real user testing: user said "family" → bot assumed self+spouse+kids.
User actually meant parents + siblings (joint family).
Fix: VAGUE_TERMS list in keyword normalizer returns None for unclear
terms ("family", "everyone", "whole family", "joint family") forcing
LLM normalizer or re-ask clarification. Disambiguated phrases like
"family — me and my wife" still parse correctly.

KI-015 (P1) — Age in readback mismatched user's stated age
Real user testing: user said 31, readback said 30. Documented in
known-issues.md for follow-up. Fix plan involves adding an explicit
profile-confirmation step before transitioning to recommendations.

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

backend/fact_find_normalizer.py CHANGED
@@ -144,6 +144,16 @@ def _keyword_normalize(question_id: str, raw_text: str) -> Any:
144
  s = raw_text.lower()
145
 
146
  if question_id == "dependents":
 
 
 
 
 
 
 
 
 
 
147
  if any(k in s for k in ["spouse", "wife", "husband"]) and "kid" in s and "parent" in s:
148
  return "self+spouse+kids+parents"
149
  if any(k in s for k in ["spouse", "wife", "husband"]) and "kid" in s:
 
144
  s = raw_text.lower()
145
 
146
  if question_id == "dependents":
147
+ # KI-014 — vague terms like "family" / "everyone" must NOT auto-map.
148
+ # User testing surfaced: user said "family" → bot assumed self+spouse+kids
149
+ # → wrong (user may have meant parents, siblings, joint-family etc.).
150
+ # Returning None here forces the LLM normalizer (or re-ask) to clarify.
151
+ VAGUE_TERMS = ["family", "everyone", "all of us", "everybody", "whole family", "joint family"]
152
+ if any(v in s for v in VAGUE_TERMS) and not any(
153
+ k in s for k in ["spouse", "wife", "husband", "kid", "child", "parent"]
154
+ ):
155
+ return None # force clarification
156
+
157
  if any(k in s for k in ["spouse", "wife", "husband"]) and "kid" in s and "parent" in s:
158
  return "self+spouse+kids+parents"
159
  if any(k in s for k in ["spouse", "wife", "husband"]) and "kid" in s:
backend/orchestrator.py CHANGED
@@ -165,7 +165,22 @@ async def handle_turn(
165
  session = get_session(session_id or "anonymous")
166
 
167
  in_fact_find_continuation = bool(session.awaiting_question_id) and not session.free_form_session
168
- treat_as_fact_find = (intent == "fact_find" and not session.free_form_session) or in_fact_find_continuation
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
  if treat_as_fact_find:
171
  # If we were awaiting an answer, normalize + record it before picking next Q.
 
165
  session = get_session(session_id or "anonymous")
166
 
167
  in_fact_find_continuation = bool(session.awaiting_question_id) and not session.free_form_session
168
+ # KI-013 if the user has NO profile fields yet, FORCE fact-find regardless
169
+ # of intent classifier. Real user testing surfaced: a vague opener
170
+ # ("I want health insurance") got classified as "recommendation" and the
171
+ # bot retrieved "Care Senior" (a senior-citizen-only policy) and pitched it.
172
+ # Bot should never recommend without knowing the user's age / dependents /
173
+ # conditions / budget. Force fact-find until ≥1 profile field is set.
174
+ profile_is_empty = (
175
+ session.profile.age is None
176
+ and session.profile.dependents is None
177
+ and session.profile.income_band is None
178
+ )
179
+ treat_as_fact_find = (
180
+ (intent == "fact_find" and not session.free_form_session)
181
+ or in_fact_find_continuation
182
+ or (profile_is_empty and not session.free_form_session)
183
+ )
184
 
185
  if treat_as_fact_find:
186
  # If we were awaiting an answer, normalize + record it before picking next Q.
docs/40-evaluation/known-issues.md CHANGED
@@ -254,3 +254,55 @@ hits this.
254
  `session.free_form_session = True` and flush to disk. Subsequent turns
255
  skip the fact-find branch entirely and go through retrieval + brain
256
  as intended.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  `session.free_form_session = True` and flush to disk. Subsequent turns
255
  skip the fact-find branch entirely and go through retrieval + brain
256
  as intended.
257
+
258
+ ### KI-013 — Bot recommends policies on vague openers without fact-find — **FIXED in next commit**
259
+
260
+ **Severity:** P0
261
+ **Source:** `backend/orchestrator.py` intent → fact_find gating
262
+ **Discovered:** Real user testing 2026-05-14
263
+
264
+ User gave a vague opening ("I want a health policy") and bot
265
+ immediately retrieved + pitched "Care Senior" — a senior-citizen-only
266
+ policy. User is not a senior. The intent classifier routed the message
267
+ to "recommendation" / "qa", not to fact_find. The orchestrator then
268
+ went straight to retrieval + brain → bot recommended whatever scored
269
+ highest, regardless of user demographics.
270
+
271
+ **Fix:** Force fact-find whenever profile is empty (no age, no
272
+ dependents, no income_band). Regardless of what the intent classifier
273
+ says. Bot will now always start with "First, your age?" before any
274
+ recommendation.
275
+
276
+ ### KI-014 — Vague dependents term "family" auto-mapped to self+spouse+kids — **FIXED in next commit**
277
+
278
+ **Severity:** P1
279
+ **Source:** `backend/fact_find_normalizer.py` keyword fast-path
280
+ **Discovered:** Real user testing 2026-05-14
281
+
282
+ User said "family" as their dependents answer. Bot assumed
283
+ "self+spouse+kids". User had intended their joint family (parents +
284
+ siblings). All subsequent recommendations were wrong.
285
+
286
+ **Fix:** Add VAGUE_TERMS list (`family`, `everyone`, `joint family`,
287
+ etc.) that explicitly returns None from the keyword fast-path,
288
+ forcing either the LLM normalizer (which is more nuanced) or a re-ask
289
+ clarifier. Phrases like "family — me and my wife" still parse
290
+ correctly because the disambiguating words come through.
291
+
292
+ ### KI-015 — Age in readback summary doesn't match user's stated age
293
+
294
+ **Severity:** P1
295
+ **Source:** Possibly `backend/needs_finder.py::record_answer` for age,
296
+ or LLM readback hallucination
297
+ **Discovered:** Real user testing 2026-05-14
298
+
299
+ User said "31" but bot's readback summary said "30". Possible causes:
300
+ (a) User's earlier answer contained "30" that the int parser caught
301
+ first; (b) The bot is using the LLM to generate the readback and the
302
+ LLM is hallucinating numeric values.
303
+
304
+ **Fix plan:** Add a CONFIRMATION step before the bot transitions to
305
+ free-form recommendations. After fact-find readback, the bot should
306
+ ask "Does this all look right? Reply 'yes' or correct anything that's
307
+ off." Then proceed only if user confirms. Also: log the raw fact-find
308
+ inputs vs the captured profile so we can debug mismatches.