fhir-data-utils-ts 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: govern-fhir-observation-components
3
+ description: Implement or audit neutral FHIR Observation claim catalogs, vital-sign terminology, component serialization, reduced component indexes, historical bp aliases, and R4/R5 projection adapters. Use when changing Observation.component, blood pressure, assessment scales, device readings, flat claims, portal or assistant vital-sign builders, or migrating code from gdc-common-utils-ts or uhc-fhir-utils-typescript.
4
+ ---
5
+
6
+ # Govern FHIR Observation Components
7
+
8
+ ## Establish the boundary
9
+
10
+ 1. Treat native FHIR import/export as an adapter boundary. Keep flat claims as
11
+ the version-neutral internal contract.
12
+ 2. Read `src/observation-claims.ts`, `src/vital-sign-observations.ts`, their
13
+ tests, and `docs/UHC_FHIR_UTILS_MIGRATION.md` before changing behavior.
14
+ 3. Treat every `gdc-*` repository and `uhc-fhir-utils-typescript` as read-only
15
+ compatibility evidence unless the user explicitly changes that scope.
16
+ 4. Keep product policies in SOS, Vet or UHC packages. This package owns only
17
+ neutral FHIR data semantics.
18
+
19
+ ## Preserve the component contract
20
+
21
+ - Model blood pressure as one native FHIR Observation with systolic and
22
+ diastolic `component[]` entries. Do not export those components as three
23
+ independent native Observations.
24
+ - Encode aligned repeatable component claims with
25
+ `encodeObservationClaimList`. Emit JSON arrays because flat values are
26
+ strings and CSV is ambiguous for displays and text. Keep numeric component
27
+ values as strings inside those JSON arrays for compatibility with the
28
+ deployed flat-claim converter; parse numbers only at typed boundaries.
29
+ - Accept historical CSV only through `decodeObservationClaimList`; never emit
30
+ it from a new writer.
31
+ - Derive reduced component index entries generically by code, value and unit.
32
+ They carry their parent reference and ordinal, but no `Observation.status`
33
+ and no invented persistent identifier.
34
+ - Never introduce scale-specific numeric claims. In particular,
35
+ `Observation.bp-systolic-number` and `Observation.bp-diastolic-number` are
36
+ read-only migration aliases. Apply the same rule to APGAR, Glasgow and other
37
+ composite observations.
38
+ - Keep canonical component arrays on the parent as the reconstruction source.
39
+ A persistence adapter may preserve historical child identifiers or apply an
40
+ explicitly versioned identity policy; a neutral helper must not improvise
41
+ one.
42
+
43
+ ## Reuse governed names
44
+
45
+ - Import every Observation claim key from `ObservationClaim`.
46
+ - Import terminology from `ObservationCategoryCodes`, `VitalSignsCodes` and
47
+ `VitalSignsUnits`.
48
+ - Let `buildVitalSignObservation` author canonical code/component displays and
49
+ the optional `Observation.user-selected` flag; consumers must not rebuild
50
+ those fields.
51
+ - Add a missing neutral constant here once and consume it downstream. Do not
52
+ copy `Observation.*`, LOINC, UCUM or category strings into portals,
53
+ assistants, tests or snippets.
54
+ - Keep displays separate from `system|code` tokens and local-language text.
55
+
56
+ ## Change with TDD
57
+
58
+ 1. Begin every new or modified test file with:
59
+ `// Flow contract: reuse shared test fixtures and canonical types; do not introduce duplicated literals.`
60
+ 2. Add the smallest failing test and retain its intended red result.
61
+ 3. Prove canonical JSON writing, legacy reading, generic indexing, absence of
62
+ `bp-*` output and one-Observation/two-component R4 and R5 projection.
63
+ 4. Add negative assertions against raw claim literals outside the owning
64
+ catalog and against invented persistent component identifiers.
65
+ 5. Run Node 24, `npm ci`, `npm run check` and `npm pack --dry-run`.
66
+
67
+ ## Promote consumers
68
+
69
+ Release bottom-up. Publish this package first, install its exact registry
70
+ version in the lowest consumer, and run that consumer's complete local matrix.
71
+ Never commit `file:`, Git, workspace or tarball dependencies. Portals and
72
+ assistants should call shared builders or use governed constants; they must not
73
+ assemble component arrays with copied claim literals.
74
+
75
+ Update JSDoc, high-level docs, tests, snippets and this skill together whenever
76
+ the contract changes.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Govern FHIR Observation Components"
3
+ short_description: "Preserve canonical Observation component and index contracts"
4
+ default_prompt: "Use $govern-fhir-observation-components to implement or audit FHIR Observation flat claims, component indexes, vital signs, or R4/R5 projections without custom per-scale claims."
@@ -0,0 +1,57 @@
1
+ ---
2
+ name: preserve-fhir-flat-coding-review
3
+ description: Preserve the neutral flat-claim contract for imported local clinical text, terminology proposals and confirmed codings. Use when changing DataConv, Vet or UHC ingestion, ResearchSubject batches, contained clinical resources, coding-review UI/API contracts, examples, fixtures or tests.
4
+ ---
5
+
6
+ # Preserve FHIR Flat Coding Review
7
+
8
+ Keep imported local text, proposed terminology and confirmed coding as distinct
9
+ states. Reuse the public builders and fixtures from `fhir-data-utils-ts`; do not
10
+ redeclare governed claim paths, code systems, UUIDs or example values.
11
+
12
+ ## Canonical contract
13
+
14
+ - Store source wording as the resource's canonical code-like local-text claim,
15
+ such as `<ResourceType>.<code-field>-text`. Never serialize `coding-input:*`.
16
+ - Put `meta.codingProposals[]` on the same contained clinical resource as
17
+ `meta.claims`. Both are siblings. Never aggregate proposals into
18
+ `ResearchSubject.meta`.
19
+ - Each proposal `field` names the coded target claim. Confirmation writes
20
+ `system|code` to that target and the terminology display to `-display`; it
21
+ must preserve the imported `-text` value.
22
+ - A localized generated label may use a language-qualified `-text_<BCP47>`
23
+ companion. It must not overwrite the imported local text before explicit
24
+ review.
25
+ - In a DIDComm-style batch, traverse
26
+ `body.data[].resource.contained[].meta.codingProposals[]`. In a native FHIR
27
+ Bundle projection, traverse `entry[].resource.contained[]`. Never invent
28
+ `entry[].data[]`.
29
+ - Multiple imported rows may resolve to one stable UUID ResearchSubject. Keep
30
+ every proposal on the clinical resource generated from its source row.
31
+
32
+ ## Workflow
33
+
34
+ 1. Import `deriveCodingLocalTextClaim`, `deriveCodingDisplayClaim`,
35
+ `buildFlatClaimCodingReviewResource`, `applyFlatClaimCodingSelection` and
36
+ `CODING_REVIEW_EXAMPLES` from `fhir-data-utils-ts`.
37
+ 2. Write the smallest failing contract test first. Begin its suite with a flow
38
+ contract comment.
39
+ 3. Build the resource with source `-text` plus a resource-scoped proposal.
40
+ 4. Confirm a candidate only through the shared selection helper or an adapter
41
+ proven equivalent by contract tests.
42
+ 5. Test the real DataConv or product boundary, then update JSDoc, SYNC and
43
+ changelog together.
44
+ 6. Use local tarballs for the consumer matrix. Merge validated branches to
45
+ `main`, publish bottom-up, reinstall exact registry versions and only then
46
+ promote to staging.
47
+
48
+ ## Boundaries
49
+
50
+ - This is FHIR-neutral shared data behavior. It belongs in
51
+ `fhir-data-utils-ts`, not SOSChain, Vet, UHC or frozen `gdc-*` packages.
52
+ - DataConv applies the contract during import. Vet and UHC consume the same
53
+ contract and may add sector policy without forking its claim shape.
54
+ - Do not put HTTP clients, gateway policy, product authorization or terminology
55
+ catalogs in this package.
56
+ - Tests and examples import package-owned fixtures/builders. Do not scatter raw
57
+ claim names, code systems, codes, identifiers or UUIDs through consumers.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Preserve FHIR Flat Coding Review"
3
+ short_description: "Keep local text and coding proposals canonical"
4
+ default_prompt: "Use $preserve-fhir-flat-coding-review to implement or audit flat-claim coding review without duplicated literals."
package/CHANGELOG.md ADDED
@@ -0,0 +1,35 @@
1
+ # Changelog
2
+
3
+ ## 0.2.3 - 2026-09-21
4
+
5
+ - Preserve the deployed flat-claim component number wire contract by encoding
6
+ aligned numeric values as JSON string arrays while parsing them as numbers
7
+ at typed and native-FHIR boundaries.
8
+
9
+ ## 0.2.2 - 2026-09-21
10
+
11
+ - Make the neutral vital-sign builder own canonical code displays, component
12
+ displays and the optional user-selected provenance claim so portals and
13
+ assistants no longer assemble those Observation fields from literals.
14
+
15
+ ## 0.2.1 - 2026-09-21
16
+
17
+ - Add the repository skill that governs Observation component authoring,
18
+ compatibility, generic indexing, R4/R5 projection and consumer promotion.
19
+
20
+ ## 0.2.0 - 2026-09-21
21
+
22
+ - Consolidate the canonical Observation claim names, vital-sign terminology,
23
+ JSON repeatable-component encoding, generic component index projection and
24
+ explicit R4/R5 projection boundary. Historical component CSV and `bp-*`
25
+ aliases remain read-only compatibility inputs.
26
+ - Record the reuse/rewrite/retire audit of the original
27
+ `uhc-fhir-utils-typescript` architecture without adding its obsolete product,
28
+ transport, SHA-1, DICOM or build dependencies to the neutral package.
29
+ - Add the neutral contained-resource coding-review contract: canonical imported
30
+ `*-text`, resource-scoped `meta.codingProposals[]`, batch traversal, candidate
31
+ confirmation and reusable governed fixtures for DataConv, Vet and UHC.
32
+ - Add channel-neutral FHIR-like vital-sign Observation builders and lossless device-batch normalization while preserving device source without inventing professional attestation.
33
+ - Introduce product-neutral flat-claim resource graphs, parent-owned
34
+ `contained-reference-list`, narrative `language`/`xhtml`, and governed
35
+ SearchParameter definitions.
package/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # fhir-data-utils-ts
2
+
3
+ FHIR-neutral data contracts for flat claims, governed SearchParameter names,
4
+ resource graphs and explicit R4/R5/R6 projection adapters. This package has no
5
+ network clients and no dependency on GDC, SOS, Vet or UHC packages.
6
+
7
+ Release order is local-first: local tarball consumers, complete local matrix,
8
+ merge and push `main`, publish bottom-up, install exact registry versions, then
9
+ staging. Never commit `file:`, Git or workspace dependencies.
10
+
11
+ ## Where this package sits
12
+
13
+ ```text
14
+ fhir-data-utils-ts
15
+ └─ FHIR SearchParameters, flat-claim graphs and R4/R5/R6 projection rules
16
+ ↓
17
+ sos-data-utils-ts
18
+ └─ cross-sector clinical catalogs and shared SOS/Vet/UHC data contracts
19
+ ├─ vet-data-utils-ts: animal species and veterinary policy
20
+ └─ uhc-data-utils-ts: human-health policy
21
+
22
+ terminology-api-node-ts reads the neutral FHIR and shared SOS contracts.
23
+ It never depends on Vet or UHC product packages.
24
+ ```
25
+
26
+ FHIR resource graphs use independent entries. A parent links children only
27
+ through `<ResourceType>.contained-reference-list`; children do not carry
28
+ `is-contained` or `contained-parent-reference`. Narrative source is preserved
29
+ as `<ResourceType>.language`, `.narrative-status` and `.xhtml`. Native FHIR is
30
+ materialized only by an explicit R4/R5/R6 import or export adapter.
31
+
32
+ ## Observation components
33
+
34
+ Repeatable `Observation.component[]` values are encoded as aligned JSON arrays
35
+ inside string-valued flat claims. Historical CSV and blood-pressure `bp-*`
36
+ aliases are read-only compatibility inputs. Generic reduced component rows may
37
+ be derived for indexing, but they are not independent native FHIR resources
38
+ and carry no top-level `Observation.status`.
39
+
40
+ `buildVitalSignObservation` also owns canonical displays and the optional
41
+ `Observation.user-selected` provenance flag. Consumers add their envelope and
42
+ section context but do not reconstruct Observation component claims.
43
+
44
+ See [the UHC FHIR utilities migration](docs/UHC_FHIR_UTILS_MIGRATION.md) for the
45
+ reuse/rewrite/retire inventory and the rule for moving frozen contracts without
46
+ changing any `gdc-*` repository.
47
+
48
+ ## Coding review for imported clinical text
49
+
50
+ DataConv, Vet and UHC share one neutral review contract from
51
+ `fhir-data-utils-ts/coding-review-flat-claims`:
52
+
53
+ - imported wording is the canonical `<ResourceType>.<code-field>-text` claim;
54
+ - `meta.codingProposals[]` is a sibling of `meta.claims` on that same contained
55
+ clinical resource, never on `ResearchSubject.meta`;
56
+ - a proposal targets the coded claim, such as `Condition.code` or
57
+ `Immunization.vaccination-code`;
58
+ - accepting a candidate adds `system|code` and the official `-display`, while
59
+ retaining the imported local `-text` value;
60
+ - language-qualified generated local labels use `-text_<BCP47>` and do not
61
+ replace imported text until review explicitly chooses that operation.
62
+
63
+ The package exports builders, traversal/confirmation helpers and
64
+ `CODING_REVIEW_EXAMPLES`. Tests and consumers reuse them instead of duplicating
65
+ claim strings, terminology identifiers, codes or UUIDs.
@@ -0,0 +1,134 @@
1
+ export type FlatClaimCodingCandidate = Readonly<{
2
+ id: string;
3
+ system: string;
4
+ code: string;
5
+ display: string;
6
+ localText?: string;
7
+ }>;
8
+ export type FlatClaimCodingProposal = Readonly<{
9
+ id: string;
10
+ status: 'proposed' | 'accepted' | 'rejected' | 'deferred';
11
+ field: string;
12
+ inputText: string;
13
+ language: string;
14
+ candidates: readonly FlatClaimCodingCandidate[];
15
+ selectedCandidateId?: string;
16
+ }>;
17
+ export type FlatClaimCodingReviewResource = Readonly<{
18
+ resourceType: string;
19
+ id: string;
20
+ meta: Readonly<{
21
+ claims: Readonly<Record<string, string>>;
22
+ codingProposals?: readonly FlatClaimCodingProposal[];
23
+ }>;
24
+ }>;
25
+ export type ResearchSubjectCodingReviewEntry = Readonly<{
26
+ resource: Readonly<{
27
+ resourceType: 'ResearchSubject';
28
+ id: string;
29
+ meta: Readonly<{
30
+ claims: Readonly<Record<string, string>>;
31
+ codingProposals?: never;
32
+ }>;
33
+ contained: readonly FlatClaimCodingReviewResource[];
34
+ }>;
35
+ }>;
36
+ export type FhirLikeCodingReviewBatch = Readonly<{
37
+ resourceType: 'Bundle';
38
+ type: 'batch';
39
+ data: readonly ResearchSubjectCodingReviewEntry[];
40
+ }>;
41
+ export type ContainedCodingReviewItem = Readonly<{
42
+ researchSubjectId: string;
43
+ resource: FlatClaimCodingReviewResource;
44
+ proposal: FlatClaimCodingProposal;
45
+ }>;
46
+ /** Return the canonical local-text companion for a code-like flat claim. */
47
+ export declare function deriveCodingLocalTextClaim(targetClaim: string): string;
48
+ /** Return the official-display companion for a code-like flat claim. */
49
+ export declare function deriveCodingDisplayClaim(targetClaim: string): string;
50
+ /**
51
+ * Build a reviewable contained resource.
52
+ *
53
+ * Imported wording remains the canonical local `*-text` flat claim. The
54
+ * proposal targets the code-like claim and is a sibling of `meta.claims`; it
55
+ * never becomes a claim and is never copied to ResearchSubject metadata.
56
+ */
57
+ export declare function buildFlatClaimCodingReviewResource(input: Readonly<{
58
+ resourceType: string;
59
+ resourceId: string;
60
+ targetClaim: string;
61
+ localText: string;
62
+ language: string;
63
+ baseClaims: Readonly<Record<string, string>>;
64
+ proposalId: string;
65
+ candidates: readonly FlatClaimCodingCandidate[];
66
+ }>): FlatClaimCodingReviewResource;
67
+ /** Traverse every ResearchSubject entry and return resource-scoped proposals. */
68
+ export declare function collectBatchCodingReviewItems(batch: FhirLikeCodingReviewBatch): readonly ContainedCodingReviewItem[];
69
+ /**
70
+ * Confirm one terminology candidate on the resource that owns the proposal.
71
+ * The coded claim and official display are added while the imported local
72
+ * `*-text` claim remains untouched for provenance and multilingual search.
73
+ */
74
+ export declare function applyFlatClaimCodingSelection(resource: FlatClaimCodingReviewResource, proposalId: string, candidateId: string): FlatClaimCodingReviewResource;
75
+ /** Package-owned reusable fixture; tests and consumer examples must import it. */
76
+ export declare const CODING_REVIEW_EXAMPLES: Readonly<{
77
+ condition: Readonly<{
78
+ input: Readonly<{
79
+ resourceType: "Condition";
80
+ resourceId: "00000000-0000-4000-8000-000000000102";
81
+ targetClaim: "Condition.code";
82
+ localText: "otitis";
83
+ language: "es-ES";
84
+ baseClaims: Readonly<{
85
+ 'Condition.identifier': "00000000-0000-4000-8000-000000000102";
86
+ 'Condition.subject': "ResearchSubject/00000000-0000-4000-8000-000000000101";
87
+ }>;
88
+ proposalId: "condition-code-proposal-example";
89
+ candidates: readonly Readonly<{
90
+ id: "condition-candidate-example";
91
+ system: "http://snomed.info/sct";
92
+ code: "129127001";
93
+ display: "Otitis externa";
94
+ }>[];
95
+ }>;
96
+ resource: Readonly<{
97
+ resourceType: string;
98
+ id: string;
99
+ meta: Readonly<{
100
+ claims: Readonly<Record<string, string>>;
101
+ codingProposals?: readonly FlatClaimCodingProposal[];
102
+ }>;
103
+ }>;
104
+ expectedAcceptedClaims: Readonly<{
105
+ [x: string]: string;
106
+ "Condition.code": "http://snomed.info/sct|129127001";
107
+ }>;
108
+ expectedAcceptedProposalStatus: "accepted";
109
+ }>;
110
+ batch: Readonly<{
111
+ resourceType: "Bundle";
112
+ type: "batch";
113
+ data: readonly Readonly<{
114
+ resource: Readonly<{
115
+ resourceType: "ResearchSubject";
116
+ id: "00000000-0000-4000-8000-000000000101";
117
+ meta: Readonly<{
118
+ claims: Readonly<{
119
+ 'ResearchSubject.identifier': "urn:uuid:00000000-0000-4000-8000-000000000101";
120
+ }>;
121
+ }>;
122
+ contained: readonly Readonly<{
123
+ resourceType: string;
124
+ id: string;
125
+ meta: Readonly<{
126
+ claims: Readonly<Record<string, string>>;
127
+ codingProposals?: readonly FlatClaimCodingProposal[];
128
+ }>;
129
+ }>[];
130
+ }>;
131
+ }>[];
132
+ }>;
133
+ expectedReviewIdentities: readonly (readonly string[])[];
134
+ }>;
@@ -0,0 +1,160 @@
1
+ import { buildFlatClaimResourceEntry } from './flat-claim-resource-graph.js';
2
+ /** Return the canonical local-text companion for a code-like flat claim. */
3
+ export function deriveCodingLocalTextClaim(targetClaim) {
4
+ return `${requiredCodingTarget(targetClaim)}-text`;
5
+ }
6
+ /** Return the official-display companion for a code-like flat claim. */
7
+ export function deriveCodingDisplayClaim(targetClaim) {
8
+ return `${requiredCodingTarget(targetClaim)}-display`;
9
+ }
10
+ /**
11
+ * Build a reviewable contained resource.
12
+ *
13
+ * Imported wording remains the canonical local `*-text` flat claim. The
14
+ * proposal targets the code-like claim and is a sibling of `meta.claims`; it
15
+ * never becomes a claim and is never copied to ResearchSubject metadata.
16
+ */
17
+ export function buildFlatClaimCodingReviewResource(input) {
18
+ const targetClaim = requiredCodingTarget(input.targetClaim);
19
+ const prefix = `${input.resourceType}.`;
20
+ if (!targetClaim.startsWith(prefix))
21
+ throw new TypeError('coding_target_resource_type_mismatch');
22
+ const entry = buildFlatClaimResourceEntry({
23
+ entryId: input.resourceId,
24
+ resourceType: input.resourceType,
25
+ claims: {
26
+ ...input.baseClaims,
27
+ [deriveCodingLocalTextClaim(targetClaim)]: requiredText(input.localText, 'coding_local_text_required'),
28
+ [`${input.resourceType}.language`]: requiredText(input.language, 'coding_language_required'),
29
+ },
30
+ });
31
+ const proposal = Object.freeze({
32
+ id: requiredText(input.proposalId, 'coding_proposal_id_required'),
33
+ status: 'proposed',
34
+ field: targetClaim,
35
+ inputText: input.localText.trim(),
36
+ language: input.language.trim(),
37
+ candidates: Object.freeze(input.candidates.map(candidate => Object.freeze({ ...candidate }))),
38
+ });
39
+ return Object.freeze({
40
+ resourceType: entry.resourceType,
41
+ id: entry.entryId,
42
+ meta: Object.freeze({ claims: entry.claims, codingProposals: Object.freeze([proposal]) }),
43
+ });
44
+ }
45
+ /** Traverse every ResearchSubject entry and return resource-scoped proposals. */
46
+ export function collectBatchCodingReviewItems(batch) {
47
+ const items = [];
48
+ for (const entry of batch.data) {
49
+ for (const resource of entry.resource.contained) {
50
+ for (const proposal of resource.meta.codingProposals ?? []) {
51
+ items.push(Object.freeze({ researchSubjectId: entry.resource.id, resource, proposal }));
52
+ }
53
+ }
54
+ }
55
+ return Object.freeze(items);
56
+ }
57
+ /**
58
+ * Confirm one terminology candidate on the resource that owns the proposal.
59
+ * The coded claim and official display are added while the imported local
60
+ * `*-text` claim remains untouched for provenance and multilingual search.
61
+ */
62
+ export function applyFlatClaimCodingSelection(resource, proposalId, candidateId) {
63
+ const proposals = resource.meta.codingProposals ?? [];
64
+ const proposalIndex = proposals.findIndex(proposal => proposal.id === proposalId);
65
+ if (proposalIndex < 0)
66
+ throw new TypeError('coding_proposal_not_found');
67
+ const proposal = proposals[proposalIndex];
68
+ const candidate = proposal.candidates.find(item => item.id === candidateId);
69
+ if (!candidate)
70
+ throw new TypeError('coding_candidate_not_found');
71
+ const updatedProposal = Object.freeze({
72
+ ...proposal,
73
+ status: 'accepted',
74
+ selectedCandidateId: candidate.id,
75
+ });
76
+ const updatedProposals = proposals.map((item, index) => (index === proposalIndex ? updatedProposal : item));
77
+ return Object.freeze({
78
+ ...resource,
79
+ meta: Object.freeze({
80
+ claims: Object.freeze({
81
+ ...resource.meta.claims,
82
+ [proposal.field]: `${candidate.system}|${candidate.code}`,
83
+ [deriveCodingDisplayClaim(proposal.field)]: candidate.display,
84
+ }),
85
+ codingProposals: Object.freeze(updatedProposals),
86
+ }),
87
+ });
88
+ }
89
+ const RESEARCH_SUBJECT_EXAMPLE_ID = '00000000-0000-4000-8000-000000000101';
90
+ const CONDITION_EXAMPLE_ID = '00000000-0000-4000-8000-000000000102';
91
+ const CONDITION_PROPOSAL_EXAMPLE_ID = 'condition-code-proposal-example';
92
+ const CONDITION_CANDIDATE_EXAMPLE_ID = 'condition-candidate-example';
93
+ const CONDITION_EXAMPLE_INPUT = Object.freeze({
94
+ resourceType: 'Condition',
95
+ resourceId: CONDITION_EXAMPLE_ID,
96
+ targetClaim: 'Condition.code',
97
+ localText: 'otitis',
98
+ language: 'es-ES',
99
+ baseClaims: Object.freeze({
100
+ 'Condition.identifier': CONDITION_EXAMPLE_ID,
101
+ 'Condition.subject': `ResearchSubject/${RESEARCH_SUBJECT_EXAMPLE_ID}`,
102
+ }),
103
+ proposalId: CONDITION_PROPOSAL_EXAMPLE_ID,
104
+ candidates: Object.freeze([
105
+ Object.freeze({
106
+ id: CONDITION_CANDIDATE_EXAMPLE_ID,
107
+ system: 'http://snomed.info/sct',
108
+ code: '129127001',
109
+ display: 'Otitis externa',
110
+ }),
111
+ ]),
112
+ });
113
+ const CONDITION_EXAMPLE_RESOURCE = buildFlatClaimCodingReviewResource(CONDITION_EXAMPLE_INPUT);
114
+ const CONDITION_EXAMPLE_CANDIDATE = CONDITION_EXAMPLE_INPUT.candidates[0];
115
+ const CONDITION_EXPECTED_ACCEPTED_CLAIMS = Object.freeze({
116
+ ...CONDITION_EXAMPLE_RESOURCE.meta.claims,
117
+ [CONDITION_EXAMPLE_INPUT.targetClaim]: `${CONDITION_EXAMPLE_CANDIDATE.system}|${CONDITION_EXAMPLE_CANDIDATE.code}`,
118
+ [deriveCodingDisplayClaim(CONDITION_EXAMPLE_INPUT.targetClaim)]: CONDITION_EXAMPLE_CANDIDATE.display,
119
+ });
120
+ /** Package-owned reusable fixture; tests and consumer examples must import it. */
121
+ export const CODING_REVIEW_EXAMPLES = Object.freeze({
122
+ condition: Object.freeze({
123
+ input: CONDITION_EXAMPLE_INPUT,
124
+ resource: CONDITION_EXAMPLE_RESOURCE,
125
+ expectedAcceptedClaims: CONDITION_EXPECTED_ACCEPTED_CLAIMS,
126
+ expectedAcceptedProposalStatus: 'accepted',
127
+ }),
128
+ batch: Object.freeze({
129
+ resourceType: 'Bundle',
130
+ type: 'batch',
131
+ data: Object.freeze([
132
+ Object.freeze({
133
+ resource: Object.freeze({
134
+ resourceType: 'ResearchSubject',
135
+ id: RESEARCH_SUBJECT_EXAMPLE_ID,
136
+ meta: Object.freeze({
137
+ claims: Object.freeze({ 'ResearchSubject.identifier': `urn:uuid:${RESEARCH_SUBJECT_EXAMPLE_ID}` }),
138
+ }),
139
+ contained: Object.freeze([CONDITION_EXAMPLE_RESOURCE]),
140
+ }),
141
+ }),
142
+ ]),
143
+ }),
144
+ expectedReviewIdentities: Object.freeze([
145
+ Object.freeze([RESEARCH_SUBJECT_EXAMPLE_ID, CONDITION_EXAMPLE_ID, CONDITION_PROPOSAL_EXAMPLE_ID]),
146
+ ]),
147
+ });
148
+ function requiredCodingTarget(value) {
149
+ const target = requiredText(value, 'coding_target_claim_required');
150
+ if (!/^[A-Z][A-Za-z0-9]+\.(?:code|[a-z][a-z0-9-]*-code)$/.test(target)) {
151
+ throw new TypeError('coding_target_claim_invalid');
152
+ }
153
+ return target;
154
+ }
155
+ function requiredText(value, error) {
156
+ const normalized = value.trim();
157
+ if (!normalized)
158
+ throw new TypeError(error);
159
+ return normalized;
160
+ }
@@ -0,0 +1,48 @@
1
+ /** A canonical flat-claims resource entry; `entryId` is graph identity, never a business identifier. */
2
+ export type FlatClaimResourceEntry = Readonly<{
3
+ entryId: string;
4
+ reference: string;
5
+ resourceType: string;
6
+ claims: Readonly<Record<string, string>>;
7
+ }>;
8
+ /** Version-neutral source for FHIR `Resource.language` and `DomainResource.text`. */
9
+ export type FlatClaimNarrative = Readonly<{
10
+ language: string;
11
+ status: 'generated' | 'extensions' | 'additional' | 'empty';
12
+ xhtml: string;
13
+ }>;
14
+ /** Builds one independent resource entry whose claims all belong to its resource type. */
15
+ export declare function buildFlatClaimResourceEntry(input: Readonly<{
16
+ entryId: string;
17
+ resourceType: string;
18
+ claims: Readonly<Record<string, string>>;
19
+ }>): FlatClaimResourceEntry;
20
+ /**
21
+ * Links contained or referenced resources from the parent flat entry.
22
+ *
23
+ * The comma-separated `<ParentType>.contained-reference-list` is the only
24
+ * canonical graph edge. A child remains an independent entry and therefore
25
+ * never receives `is-contained` or `contained-parent-reference` claims.
26
+ */
27
+ export declare function linkFlatClaimResourceEntries(entries: readonly FlatClaimResourceEntry[], link: Readonly<{
28
+ parentEntryId: string;
29
+ childEntryIds: readonly string[];
30
+ }>): readonly FlatClaimResourceEntry[];
31
+ /** Adds language and XHTML narrative source without materializing native FHIR. */
32
+ export declare function withFlatClaimNarrative(entry: FlatClaimResourceEntry, narrative: FlatClaimNarrative): FlatClaimResourceEntry;
33
+ /** Reads legacy graphs without perpetuating child-owned containment state. */
34
+ export declare function normalizeLegacyContainedClaims(entry: FlatClaimResourceEntry): FlatClaimResourceEntry;
35
+ /**
36
+ * Builds one DocumentReference flat entry for a PDF, JPEG or PNG.
37
+ * Link one or many such entries from any clinical parent with
38
+ * `linkFlatClaimResourceEntries`; attachment bytes are not duplicated.
39
+ */
40
+ export declare function buildDocumentReferenceFlatEntry(input: Readonly<{
41
+ entryId: string;
42
+ identifier: string;
43
+ contentType: 'application/pdf' | 'image/jpeg' | 'image/png';
44
+ contentHash: string;
45
+ title?: string;
46
+ url?: string;
47
+ dataBase64?: string;
48
+ }>): FlatClaimResourceEntry;