Skip to content

PR3-G | Bridge cascade target display_name lost after lookup (oclmap) #2536

@paynejd

Description

@paynejd

Part of the Unified Mapper Model PR3 close-out. Tracked as PR3-G in ocl-online-docs/mapper/unified-mapper-model.md.

Summary

After PR2b (unified-model read-flip) and PR3-D1 (LOINC critical-path payload fixes), AI Assistant recommendable_concepts[*] entries for bridge cascade targets are missing display_name (and the rest of the enriched fields — names, concept_class, datatype, rerank_score) in a significant fraction of cases, even when the lookup repo returned 200 OK for the cascade target.

The thin entries have only 3 keys (evidence, concept_key, canonical_reference) while fully-enriched entries have 8 keys (adds display_name, names, concept_class, datatype, rerank_score).

This causes the LLM to reason over a degraded payload — postcoordination clusters and stem codes both end up with no display name, so the model can't compare semantic content and falls back to code-only reasoning.

Scope clarification — distinct from postcoord lookup-404 issue

PR3-G is the lookup-OK case (162 instances in run-2 evidence below). A separate bug (postcoord URL double-encoding causing 404s — mapper-roadmap.md Phase 0 #14) accounts for an additional 41 instances with the same surface symptom. The two are independent; PR3-G can't fix the lookup-404 case and vice versa.

Evidence — 2026-05-21 ICD-11 IMO Deep Analysis (project 126, 100 rows)

Bucket Instances Unique codes Diagnosis
Lookup 200, but `display_name` missing in AI payload 162 162 PR3-G — this ticket.
Lookup 4xx (postcoord URL encoding 404s) 41 34 Separate — `mapper-roadmap` Phase 0 #14.
No lookup attempted 0 0 n/a

Paired test case within a single row (run-2 `prompt-executions/3710.json`, row 1 "Need for vaccination"):

  • QC02.Z — bridge response `cascade_target_concept_name: null`, lookup 200 (returns `'Need for immunization against certain specified single infectious diseases, unspecified'`), AI payload entry has full enrichment ✓
  • QC01.2 — bridge response `cascade_target_concept_name: null`, lookup 200 (returns `'Need for immunization against rabies'`), AI payload entry has only `{evidence, concept_key, canonical_reference}` ❌

Both took identical code paths. The difference is somewhere between lookup-success and AI-payload-build, intermittently affecting some keys but not others.

Architectural framing (per @paynejd 2026-05-23)

For ICD-11 projects, the bridge response carrying `cascade_target_concept_name: null` is expected — ICD-11-WHO is a metadata-only target repo, so cascade-target names aren't populated on the CIEL→ICD-11 mappings. The custom `lookup_config` repo (here: `/orgs/OpenMRS-OCL-Squad/sources/ICD-11-WHO-Mapper/`) is the authoritative source for `display_name` and other concept fields. PR3-G must ensure lookup-enriched ConceptDefinitions reach the AI payload without being dropped.

For LOINC projects, the bridge response itself typically carries `cascade_target_concept_name` (LOINC target repo has full concept data), so the bug primarily affects the AI-payload-build step when lookup is the data source.

Static analysis ruled out

The single-write paths look correct:

  • `mergeIntoRowMatchState` rank guard (MapProject.jsx:378): strict `>` prevents pending stubs from overwriting full entries.
  • `writeConceptCachePatch` (MapProject.jsx:3308): spreads lookup response `data` after `existing`, so `display_name` lands.
  • `ensureLoaded` in-flight dedup (MapProject.jsx:3356): synchronous `forEach` works.
  • `buildV2RecommendationPayload` read (MapProject.jsx:3864): reads `conceptCacheRef.current[concept_key]` directly; no transformation strips `display_name`.
  • Concept-key construction: same `resolveReference` + `makeConceptKey` chain on both bridge-stub and lookup write paths.

Two suspect inter-render interactions

Static analysis can't conclusively distinguish these — needs runtime instrumentation to confirm.

(A) State-sync effect at MapProject.jsx:1738:

```js
React.useEffect(() => { conceptCacheRef.current = conceptCache; }, [conceptCache]);
```

If any code path calls `setConceptCache(value)` where `value` was built from a stale closure of `conceptCache` (React state), this effect copies the stale state back into the ref, wiping out any synchronous `writeConceptCachePatch` enrichments in between.

(B) Legacy URL-keyed `setConceptCache` at MapProject.jsx:2407:

```js
setConceptCache({...conceptCache, [url]: res})
```

Spreads `conceptCache` (closure-captured React state) and adds a URL-keyed entry. Combined with (A), could wipe in-flight concept-key-keyed enrichments.

Investigation plan

  1. Ship a small instrumentation PR (localStorage-gated debug logging on every `conceptCache[<target_key>]` write). Land first; not the fix.
  2. Reproduce locally on project 126 (or a small ICD-11 fixture). Capture the QC01.2 write trace.
  3. Confirm which of (A), (B), or a third path is wiping the enrichment.
  4. Ship the targeted fix as a follow-up PR. Either bump `lookupStatusRank` to factor `display_name` presence, or refactor the ref/state sync to not copy stale state back.

Acceptance criteria

  • Instrumentation PR merged (this work).
  • Root cause documented in a comment on this ticket.
  • Fix PR merged, closing this ticket.
  • Regression test added: ICD-11 fixture where QC01.2 and QC02.Z must both have `display_name` populated in the AI payload entry.
  • LOINC fixture unaffected (bridge-response carries `cascade_target_concept_name`).
  • Postcoord 404 case (PL11.4/PK8Y) unchanged — that's Dynamic collection references based on attribute value #14's job.

Related

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions