Unified mapper data model PR1: normalizers + parallel state#16
Unified mapper data model PR1: normalizers + parallel state#16
Conversation
…lizers + parallel state
Introduces the normalized candidate/concept entity model (plans/unified-mapper-model.md)
behind a feature flag, in parallel with the legacy allCandidates state. Reads still
come from legacy state — flipping reads is PR2.
- normalizers.js: pure normalizeAlgoResult / normalizeAlgorithmInvocation; resolves
canonical ConceptReference {url, code, version?} per algo's concept_identity config
(target_repo / bridge_repo / fixed); produces AlgorithmResponse + Candidate +
ConceptDefinition + ConceptRow with intra-invocation dedup.
- conceptKey.js: opaque pool key helpers (avoids the FHIR canonical |version
collision that would arise from concatenating url|code).
- __tests__/normalizers.test.js: 26 tests covering conceptKey roundtrip, standard /
scispacy / bridge / multi-cascade normalization, multi-algo convergence on shared
canonical reference, and intra-invocation dedup.
- algorithms.jsx: concept_identity block on ocl-search and ocl-semantic.
- MapProject.jsx: UNIFIED_MODEL_ENABLED feature flag (default OFF), parallel
rowMatchState hook + ref, mergeIntoRowMatchState callback, buildProjectContext
(reads repo.canonical_url or derives https://ns.openconceptlab.org{url}),
flag-gated normalize-and-merge in onResponse success and failure paths.
- package.json: npm test script using built-in node:test (no new test framework
dependency).
PR1 verified: npm test 26/26 passing, npm run eslint clean, npm run build succeeds.
With the flag off, runtime behavior is unchanged.
Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
| concept_rows: { ...prevRow.concept_rows }, | ||
| } | ||
|
|
||
| for(const cr of concept_rows) { |
There was a problem hiding this comment.
forEach is our convention. for feels dated
There was a problem hiding this comment.
Done in 10d0940 — converted to forEach. Also applied across the other for...of loops introduced in this PR for consistency.
| // richer (lookup_status='full') over stubs ('pending'/'partial'). | ||
| setConceptCache(prev => { | ||
| const next = { ...prev } | ||
| for(const def of concept_definitions) { |
| // Target repo canonical URL is read from repo metadata; if absent, derive | ||
| // 'https://ns.openconceptlab.org' + relative URL (per OCL canonical | ||
| // conventions — see plans/unified-mapper-model.md). | ||
| const buildProjectContext = () => { |
There was a problem hiding this comment.
this needs to be outside the scope of fetchAllCandidatesForRow
There was a problem hiding this comment.
Done in 10d0940 — hoisted to component scope as a React.useCallback next to mergeIntoRowMatchState, with deps [project, owner, repo, repoVersion]. Reusable from the PR2 read paths.
| mergeIntoRowMatchState(__row.__index, normalizeAlgorithmInvocation(null, { | ||
| algorithmId: algoId, | ||
| algorithmConfig: algoDef, | ||
| projectContext: buildProjectContext(), |
There was a problem hiding this comment.
buildProjectContext() can be extracted on top before if block and must be reused in both block if/else
There was a problem hiding this comment.
Done in 10d0940 — projectContext is now computed once at the top of onResponse (gated by UNIFIED_MODEL_ENABLED) and reused in both the failure and success branches.
| mergeIntoRowMatchState(__row.__index, normalizeAlgorithmInvocation(rowPayload, { | ||
| algorithmId: algoId, | ||
| algorithmConfig: algoDef, | ||
| projectContext: buildProjectContext(), |
There was a problem hiding this comment.
Done in 10d0940 — same hoisted projectContext const reused here.
| const defsByKey = new Map() | ||
| const rowsByKey = new Map() | ||
|
|
||
| for (const result of results) { |
There was a problem hiding this comment.
Done in 10d0940 — converted all four for...of loops in normalizers.js to forEach.
…t buildProjectContext Address Sunny's review comments on PR #16: - Convert all for...of loops introduced in this PR to forEach (project convention) - Hoist buildProjectContext from fetchAllCandidatesForRow to component-level useCallback - Compute projectContext once in onResponse and reuse across success/failure branches No behavior change. Feature flag remains OFF by default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
PR1 of the row-scoped, canonical-URL-identified candidate/concept refactor for OCL Mapper. Introduces the new entity model (AlgorithmResponse, Candidate, ConceptDefinition, ConceptRow) and the normalizer that produces it, behind a feature flag that is OFF by default. Reads still come from legacy
allCandidates— flipping reads is PR2.Spec: unified-mapper-model.md — full design rationale, key decisions, and implementation status table.
References OpenConceptLab/ocl_issues#2337 (does not yet close — PR1 of 3).
What's in this PR
New files:
src/components/map-projects/normalizers.js— purenormalizeAlgoResult/normalizeAlgorithmInvocation. Resolves canonicalConceptReference {url, code, version?}per algorithm'sconcept_identityconfig (target_repo/bridge_repo/fixed). Produces the four entities with intra-invocation dedup (richer ConceptDefinition wins).src/components/map-projects/conceptKey.js— opaque pool key helpers. Avoids the FHIR canonical|versioncollision that would arise from concatenatingurl|code.src/components/map-projects/__tests__/normalizers.test.js— 26 unit tests (Nodenode:test, no new framework dependency).Modified:
src/components/map-projects/algorithms.jsx—concept_identityblock onocl-searchandocl-semantic.src/components/map-projects/MapProject.jsx—UNIFIED_MODEL_ENABLEDfeature flag (default OFF), parallelrowMatchStatehook,mergeIntoRowMatchStatecallback,buildProjectContext(readsrepo.canonical_urlor deriveshttps://ns.openconceptlab.org{url}), flag-gated normalize-and-merge inonResponse(success + failure paths).package.json—npm testscript using built-innode --test.Verification
npm test→ 26/26 passingnpm run eslint→ cleannpm run build→ webpack compiled successfully (afternpm installto sync recent package updates)Test plan
npm install,npm test,npm run eslint,npm run buildall greenUNIFIED_MODEL_ENABLED = truelocally, run candidates, inspectrowMatchStatein React DevTools, capture one real$matchresponse and confirm shape matches the synthesized fixtures in the test file. Don't merge with the flag on.Deferred to follow-up PRs
Candidates.jsx,Concept.jsx,Score.jsx,SearchHighlightsDialog.jsx,AICandidatesAnalysis.jsx,MapButton.jsx); wire bridge and scispacy code paths into the normalizer; addcanonical_urlfield to custom-algo config UI; surface canonical context inConfigurationForm.jsx; rebuild$lookupasensureLoadedover$resolveReference; removematch_type.allCandidates; ship schema-v2 save format withnormalizeLegacy.jsfor backward-compatible load.🤖 Generated with Claude Code