rohitsar567 Claude Opus 4.7 (1M context) commited on
Commit
e525764
Β·
1 Parent(s): 208c839

fix(#57+#58+#59): canonical-resolve compare-card marketplace lookup

Browse files

One root cause, three symptoms on the HDFC ERGO Optima Restore compare
card. page.tsx policyById was keyed ONLY by exact marketplace policy_id;
the compare modal resolves the marketplace row by the CITED policy_id. A
recommendation can cite a doctype/variant/alias id that is not byte-equal
to the marketplace card's id (same canonical-identity class #40 solved
for grades) β†’ policyById[id] undefined β†’ the card silently degraded:
- #58 no network_list_url β†’ 'Hospitals list' link missing
- #57 no rationalised SI β†’ Cover amount fell back to 'As per policy schedule'
- #59 fewer populated fields β†’ shorter/asymmetric vs Niva Bupa & Star

Fix: build a canonical-resolving index (exact policy_id wins, plus
doctype-stripped product_key + normalised policy_name + aliases, never
overwriting a real id hit) and a _resolvePolicy(id,name) fallback chain;
PolicyCompareModal now passes c.policy_name as the canonical fallback.
Niva Bupa & Star already matched exactly so they were unaffected; HDFC
now resolves to its real marketplace card (link + SI + full fields).

tsc --noEmit clean; frontend build Compiled successfully.

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

frontend/src/app/page.tsx CHANGED
@@ -3877,14 +3877,44 @@ function CitedPolicyCards({
3877
  // same 4-stat grid + highlights that the marketplace cards show. When the
3878
  // marketplace hasn't loaded yet (or a cited policy isn't in the corpus),
3879
  // the modal's PolicyHighlights section silently skips.
 
 
 
 
 
 
 
 
 
 
 
 
 
3880
  const policyById: Record<string, MarketplacePolicy> = (() => {
3881
  const out: Record<string, MarketplacePolicy> = {};
3882
  if (!marketplace) return out;
 
 
 
3883
  for (const p of marketplace.policies) {
3884
- out[p.policy_id] = p;
 
 
 
 
 
 
 
3885
  }
3886
  return out;
3887
  })();
 
 
 
 
 
 
 
3888
 
3889
  // Translate the live UserProfile (chat-side) into the shapes the
3890
  // premium-bulk and scorecard-bulk endpoints expect. These two shapes are
@@ -4040,7 +4070,7 @@ function CitedPolicyCards({
4040
  policies={topPolicies}
4041
  onClose={() => setCompareOpen(false)}
4042
  profile={profile}
4043
- policyDataFor={(id) => policyById[id]}
4044
  renderPremiumFor={(policyId, policyName) => (
4045
  <PolicyPremiumWidget
4046
  policyId={policyId}
 
3877
  // same 4-stat grid + highlights that the marketplace cards show. When the
3878
  // marketplace hasn't loaded yet (or a cited policy isn't in the corpus),
3879
  // the modal's PolicyHighlights section silently skips.
3880
+ // Canonical-resolving index (#57/#58/#59 root cause): the compare modal
3881
+ // looks up the marketplace row by the CITED policy_id. A recommendation
3882
+ // can cite a doctype/variant/alias id that is not byte-equal to the
3883
+ // marketplace card's policy_id (same canonical-identity class #40 solved
3884
+ // for grades). An exact-only map then returns undefined β†’ the card loses
3885
+ // its Hospitals link, SI falls back to "As per policy schedule", and it
3886
+ // renders fewer fields (asymmetric). So we also register weaker canonical
3887
+ // keys: the doctype-stripped product_key, the normalised policy_name, and
3888
+ // every alias name β€” without ever overwriting a real exact-id hit.
3889
+ const _DOCT_RE = /__(wordings|brochure|cis|prospectus)$/;
3890
+ const _pkOf = (s: string) => (s || "").replace(_DOCT_RE, "");
3891
+ const _nmKey = (s: string) =>
3892
+ "nm:" + (s || "").trim().toLowerCase().replace(/\s+/g, " ");
3893
  const policyById: Record<string, MarketplacePolicy> = (() => {
3894
  const out: Record<string, MarketplacePolicy> = {};
3895
  if (!marketplace) return out;
3896
+ // Pass 1 β€” exact policy_id is the strongest key; set first.
3897
+ for (const p of marketplace.policies) out[p.policy_id] = p;
3898
+ // Pass 2 β€” weaker canonical keys, only when not already a real id hit.
3899
  for (const p of marketplace.policies) {
3900
+ const k = _pkOf(p.policy_id);
3901
+ if (k && !(k in out)) out[k] = p;
3902
+ const nk = _nmKey(p.policy_name);
3903
+ if (nk !== "nm:" && !(nk in out)) out[nk] = p;
3904
+ for (const a of p.aliases ?? []) {
3905
+ const ak = _nmKey(a);
3906
+ if (ak !== "nm:" && !(ak in out)) out[ak] = p;
3907
+ }
3908
  }
3909
  return out;
3910
  })();
3911
+ const _resolvePolicy = (
3912
+ id: string,
3913
+ name?: string,
3914
+ ): MarketplacePolicy | undefined =>
3915
+ policyById[id] ??
3916
+ policyById[_pkOf(id)] ??
3917
+ (name ? policyById[_nmKey(name)] : undefined);
3918
 
3919
  // Translate the live UserProfile (chat-side) into the shapes the
3920
  // premium-bulk and scorecard-bulk endpoints expect. These two shapes are
 
4070
  policies={topPolicies}
4071
  onClose={() => setCompareOpen(false)}
4072
  profile={profile}
4073
+ policyDataFor={(id, name) => _resolvePolicy(id, name)}
4074
  renderPremiumFor={(policyId, policyName) => (
4075
  <PolicyPremiumWidget
4076
  policyId={policyId}
frontend/src/components/PolicyCompareModal.tsx CHANGED
@@ -241,7 +241,15 @@ export type PolicyCompareModalProps = {
241
  // Optional resolver returning the marketplace MarketplacePolicy row for a
242
  // citation. Powers the new "POLICY HIGHLIGHTS" section (4-stat grid +
243
  // bullets). When undefined, the highlights section is skipped.
244
- policyDataFor?: (policyId: string) => MarketplacePolicy | undefined;
 
 
 
 
 
 
 
 
245
  // Hook for "Open in full marketplace" β€” defaults to no-op + closes modal.
246
  onOpenMarketplace?: () => void;
247
  };
@@ -428,7 +436,7 @@ export default function PolicyCompareModal({
428
  c.policy_id,
429
  c.policy_name,
430
  )}
431
- marketplacePolicy={policyDataFor?.(c.policy_id)}
432
  profile={_profile as SnapProfile}
433
  />
434
  </div>
 
241
  // Optional resolver returning the marketplace MarketplacePolicy row for a
242
  // citation. Powers the new "POLICY HIGHLIGHTS" section (4-stat grid +
243
  // bullets). When undefined, the highlights section is skipped.
244
+ // Resolve by cited policy_id; policyName is a canonical fallback so a
245
+ // doctype/variant/alias id (e.g. recommended `hdfc-ergo__optima-restore`
246
+ // vs marketplace card `..__brochure`) still resolves to its card β€”
247
+ // otherwise the card silently degrades (no Hospitals link, SI falls back
248
+ // to "As per policy schedule", fewer fields β†’ asymmetric). #57/#58/#59.
249
+ policyDataFor?: (
250
+ policyId: string,
251
+ policyName?: string,
252
+ ) => MarketplacePolicy | undefined;
253
  // Hook for "Open in full marketplace" β€” defaults to no-op + closes modal.
254
  onOpenMarketplace?: () => void;
255
  };
 
436
  c.policy_id,
437
  c.policy_name,
438
  )}
439
+ marketplacePolicy={policyDataFor?.(c.policy_id, c.policy_name)}
440
  profile={_profile as SnapProfile}
441
  />
442
  </div>