fhir-data-utils-ts 0.2.4
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.
- package/.codex/skills/govern-fhir-observation-components/SKILL.md +74 -0
- package/.codex/skills/govern-fhir-observation-components/agents/openai.yaml +4 -0
- package/.codex/skills/preserve-fhir-flat-coding-review/SKILL.md +57 -0
- package/.codex/skills/preserve-fhir-flat-coding-review/agents/openai.yaml +4 -0
- package/CHANGELOG.md +44 -0
- package/README.md +72 -0
- package/dist/coding-review-flat-claims.d.ts +134 -0
- package/dist/coding-review-flat-claims.js +160 -0
- package/dist/flat-claim-resource-graph.d.ts +48 -0
- package/dist/flat-claim-resource-graph.js +108 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/observation-claims.d.ts +132 -0
- package/dist/observation-claims.js +96 -0
- package/dist/search-parameters.d.ts +18 -0
- package/dist/search-parameters.js +34 -0
- package/dist/vital-sign-observations.d.ts +250 -0
- package/dist/vital-sign-observations.js +284 -0
- package/docs/UHC_FHIR_UTILS_MIGRATION.md +72 -0
- package/package.json +61 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: govern-fhir-observation-components
|
|
3
|
+
description: Implement or audit neutral FHIR Observation claim catalogs, vital-sign terminology, parent-owned component graphs, reduced component indexes, historical array and 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
|
+
- Build a flat resource graph. The primary Observation owns comma-separated
|
|
25
|
+
`Observation.has-member` references to reduced Observation entries.
|
|
26
|
+
- Give every reduced member its graph identifier, subject, category, date,
|
|
27
|
+
generic code, numeric value and unit. Omit `Observation.status` so normal
|
|
28
|
+
top-level listings ignore it while ordinary Observation indexes can query it.
|
|
29
|
+
- Never use `is-contained`, `contained-parent-reference` or
|
|
30
|
+
`contained-reference-list` for Observation components.
|
|
31
|
+
- Accept historical JSON arrays and CSV only through
|
|
32
|
+
`decodeObservationClaimList`; never emit either from a current writer.
|
|
33
|
+
- Never introduce scale-specific numeric claims. In particular,
|
|
34
|
+
`Observation.bp-systolic-number` and `Observation.bp-diastolic-number` are
|
|
35
|
+
read-only migration aliases. Apply the same rule to APGAR, Glasgow and other
|
|
36
|
+
composite observations.
|
|
37
|
+
- Reconstruct native `Observation.component[]` only at the explicit R4/R5
|
|
38
|
+
adapter boundary by resolving the primary entry's governed member graph.
|
|
39
|
+
|
|
40
|
+
## Reuse governed names
|
|
41
|
+
|
|
42
|
+
- Import every Observation claim key from `ObservationClaim`.
|
|
43
|
+
- Import terminology from `ObservationCategoryCodes`, `VitalSignsCodes` and
|
|
44
|
+
`VitalSignsUnits`.
|
|
45
|
+
- Let `buildVitalSignObservation` author the primary/member graph, canonical
|
|
46
|
+
displays and optional `Observation.user-selected` flag; consumers submit its
|
|
47
|
+
entries and must not rebuild those fields or relationships.
|
|
48
|
+
- Add a missing neutral constant here once and consume it downstream. Do not
|
|
49
|
+
copy `Observation.*`, LOINC, UCUM or category strings into portals,
|
|
50
|
+
assistants, tests or snippets.
|
|
51
|
+
- Keep displays separate from `system|code` tokens and local-language text.
|
|
52
|
+
|
|
53
|
+
## Change with TDD
|
|
54
|
+
|
|
55
|
+
1. Begin every new or modified test file with:
|
|
56
|
+
`// Flow contract: reuse shared test fixtures and canonical types; do not introduce duplicated literals.`
|
|
57
|
+
2. Add the smallest failing test and retain its intended red result.
|
|
58
|
+
3. Prove parent-owned `has-member`, independent component indexing, legacy
|
|
59
|
+
array reading, absence of `bp-*` and containment output, and a single native
|
|
60
|
+
Observation with two components in R4 and R5 projection.
|
|
61
|
+
4. Add negative assertions against raw claim literals outside the owning
|
|
62
|
+
catalog and against child-owned containment markers.
|
|
63
|
+
5. Run Node 24, `npm ci`, `npm run check` and `npm pack --dry-run`.
|
|
64
|
+
|
|
65
|
+
## Promote consumers
|
|
66
|
+
|
|
67
|
+
Release bottom-up. Publish this package first, install its exact registry
|
|
68
|
+
version in the lowest consumer, and run that consumer's complete local matrix.
|
|
69
|
+
Never commit `file:`, Git, workspace or tarball dependencies. Portals and
|
|
70
|
+
assistants should call shared builders or use governed constants; they submit
|
|
71
|
+
the returned entries and must not assemble component arrays or graph links.
|
|
72
|
+
|
|
73
|
+
Update JSDoc, high-level docs, tests, snippets and this skill together whenever
|
|
74
|
+
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.
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.4 - 2026-09-21
|
|
4
|
+
|
|
5
|
+
- Restore the canonical composite-Observation resource graph: the primary
|
|
6
|
+
Observation owns `has-member` references to independently indexable reduced
|
|
7
|
+
Observation entries, while R4/R5 projection reconstructs native
|
|
8
|
+
`component[]`.
|
|
9
|
+
- Keep JSON/CSV component arrays, `bp-*` aliases and deprecated child-owned
|
|
10
|
+
containment markers read-only; current writers emit none of them.
|
|
11
|
+
|
|
12
|
+
## 0.2.3 - 2026-09-21
|
|
13
|
+
|
|
14
|
+
- Preserve the deployed flat-claim component number wire contract by encoding
|
|
15
|
+
aligned numeric values as JSON string arrays while parsing them as numbers
|
|
16
|
+
at typed and native-FHIR boundaries.
|
|
17
|
+
|
|
18
|
+
## 0.2.2 - 2026-09-21
|
|
19
|
+
|
|
20
|
+
- Make the neutral vital-sign builder own canonical code displays, component
|
|
21
|
+
displays and the optional user-selected provenance claim so portals and
|
|
22
|
+
assistants no longer assemble those Observation fields from literals.
|
|
23
|
+
|
|
24
|
+
## 0.2.1 - 2026-09-21
|
|
25
|
+
|
|
26
|
+
- Add the repository skill that governs Observation component authoring,
|
|
27
|
+
compatibility, generic indexing, R4/R5 projection and consumer promotion.
|
|
28
|
+
|
|
29
|
+
## 0.2.0 - 2026-09-21
|
|
30
|
+
|
|
31
|
+
- Consolidate the canonical Observation claim names, vital-sign terminology,
|
|
32
|
+
JSON repeatable-component encoding, generic component index projection and
|
|
33
|
+
explicit R4/R5 projection boundary. Historical component CSV and `bp-*`
|
|
34
|
+
aliases remain read-only compatibility inputs.
|
|
35
|
+
- Record the reuse/rewrite/retire audit of the original
|
|
36
|
+
`uhc-fhir-utils-typescript` architecture without adding its obsolete product,
|
|
37
|
+
transport, SHA-1, DICOM or build dependencies to the neutral package.
|
|
38
|
+
- Add the neutral contained-resource coding-review contract: canonical imported
|
|
39
|
+
`*-text`, resource-scoped `meta.codingProposals[]`, batch traversal, candidate
|
|
40
|
+
confirmation and reusable governed fixtures for DataConv, Vet and UHC.
|
|
41
|
+
- Add channel-neutral FHIR-like vital-sign Observation builders and lossless device-batch normalization while preserving device source without inventing professional attestation.
|
|
42
|
+
- Introduce product-neutral flat-claim resource graphs, parent-owned
|
|
43
|
+
`contained-reference-list`, narrative `language`/`xhtml`, and governed
|
|
44
|
+
SearchParameter definitions.
|
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
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
|
+
Composite observations use a claims-first resource graph. The primary
|
|
35
|
+
Observation owns `Observation.has-member`; each component is an independent
|
|
36
|
+
reduced Observation entry using the ordinary code and quantity claims and no
|
|
37
|
+
top-level `Observation.status`. This allows every component to be indexed
|
|
38
|
+
without inventing scale-specific claim families. At the explicit FHIR export
|
|
39
|
+
boundary, the graph is projected back to one native Observation with
|
|
40
|
+
`component[]`.
|
|
41
|
+
|
|
42
|
+
Historical JSON/CSV `Observation.component-*` claims and blood-pressure `bp-*`
|
|
43
|
+
aliases are read-only compatibility inputs. Current writers emit none of them.
|
|
44
|
+
Components never use `is-contained`, `contained-parent-reference` or the
|
|
45
|
+
generic contained-resource link.
|
|
46
|
+
|
|
47
|
+
`buildVitalSignObservation` owns the graph, canonical displays and optional
|
|
48
|
+
`Observation.user-selected` provenance flag. Consumers submit its `entries`
|
|
49
|
+
without reconstructing component claims or relationships.
|
|
50
|
+
|
|
51
|
+
See [the UHC FHIR utilities migration](docs/UHC_FHIR_UTILS_MIGRATION.md) for the
|
|
52
|
+
reuse/rewrite/retire inventory and the rule for moving frozen contracts without
|
|
53
|
+
changing any `gdc-*` repository.
|
|
54
|
+
|
|
55
|
+
## Coding review for imported clinical text
|
|
56
|
+
|
|
57
|
+
DataConv, Vet and UHC share one neutral review contract from
|
|
58
|
+
`fhir-data-utils-ts/coding-review-flat-claims`:
|
|
59
|
+
|
|
60
|
+
- imported wording is the canonical `<ResourceType>.<code-field>-text` claim;
|
|
61
|
+
- `meta.codingProposals[]` is a sibling of `meta.claims` on that same contained
|
|
62
|
+
clinical resource, never on `ResearchSubject.meta`;
|
|
63
|
+
- a proposal targets the coded claim, such as `Condition.code` or
|
|
64
|
+
`Immunization.vaccination-code`;
|
|
65
|
+
- accepting a candidate adds `system|code` and the official `-display`, while
|
|
66
|
+
retaining the imported local `-text` value;
|
|
67
|
+
- language-qualified generated local labels use `-text_<BCP47>` and do not
|
|
68
|
+
replace imported text until review explicitly chooses that operation.
|
|
69
|
+
|
|
70
|
+
The package exports builders, traversal/confirmation helpers and
|
|
71
|
+
`CODING_REVIEW_EXAMPLES`. Tests and consumers reuse them instead of duplicating
|
|
72
|
+
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;
|