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.
@@ -0,0 +1,108 @@
1
+ const LEGACY_CONTAINMENT_SUFFIXES = Object.freeze(['.is-contained', '.contained-parent-reference']);
2
+ /** Builds one independent resource entry whose claims all belong to its resource type. */
3
+ export function buildFlatClaimResourceEntry(input) {
4
+ const entryId = requiredToken(input.entryId, 'flat_claim_entry_id_required');
5
+ const resourceType = requiredResourceType(input.resourceType);
6
+ const prefix = `${resourceType}.`;
7
+ const claims = {};
8
+ for (const [claim, value] of Object.entries(input.claims)) {
9
+ if (!claim.startsWith(prefix))
10
+ throw new TypeError('flat_claim_resource_type_mismatch');
11
+ if (typeof value !== 'string')
12
+ throw new TypeError('flat_claim_value_must_be_string');
13
+ claims[claim] = value.trim();
14
+ }
15
+ return Object.freeze({ entryId, reference: `${resourceType}/${entryId}`, resourceType, claims: Object.freeze(claims) });
16
+ }
17
+ /**
18
+ * Links contained or referenced resources from the parent flat entry.
19
+ *
20
+ * The comma-separated `<ParentType>.contained-reference-list` is the only
21
+ * canonical graph edge. A child remains an independent entry and therefore
22
+ * never receives `is-contained` or `contained-parent-reference` claims.
23
+ */
24
+ export function linkFlatClaimResourceEntries(entries, link) {
25
+ const byId = new Map(entries.map(entry => [entry.entryId, entry]));
26
+ const parent = byId.get(link.parentEntryId);
27
+ if (!parent)
28
+ throw new TypeError('flat_claim_entry_not_found');
29
+ const children = link.childEntryIds.map(entryId => {
30
+ const child = byId.get(entryId);
31
+ if (!child)
32
+ throw new TypeError('flat_claim_entry_not_found');
33
+ if (child.entryId === parent.entryId)
34
+ throw new TypeError('flat_claim_self_reference');
35
+ return child;
36
+ });
37
+ const claimName = `${parent.resourceType}.contained-reference-list`;
38
+ const references = uniqueCsv([...splitCsv(parent.claims[claimName]), ...children.map(child => child.reference)]);
39
+ return Object.freeze(entries.map(entry => entry.entryId === parent.entryId
40
+ ? buildFlatClaimResourceEntry({
41
+ entryId: entry.entryId,
42
+ resourceType: entry.resourceType,
43
+ claims: { ...entry.claims, [claimName]: references.join(',') },
44
+ })
45
+ : entry));
46
+ }
47
+ /** Adds language and XHTML narrative source without materializing native FHIR. */
48
+ export function withFlatClaimNarrative(entry, narrative) {
49
+ const prefix = entry.resourceType;
50
+ return buildFlatClaimResourceEntry({
51
+ entryId: entry.entryId,
52
+ resourceType: entry.resourceType,
53
+ claims: {
54
+ ...entry.claims,
55
+ [`${prefix}.language`]: requiredToken(narrative.language, 'flat_claim_language_required'),
56
+ [`${prefix}.narrative-status`]: narrative.status,
57
+ [`${prefix}.xhtml`]: requiredText(narrative.xhtml, 'flat_claim_xhtml_required'),
58
+ },
59
+ });
60
+ }
61
+ /** Reads legacy graphs without perpetuating child-owned containment state. */
62
+ export function normalizeLegacyContainedClaims(entry) {
63
+ const claims = Object.fromEntries(Object.entries(entry.claims).filter(([claim]) => !LEGACY_CONTAINMENT_SUFFIXES.some(suffix => claim.endsWith(suffix))));
64
+ return buildFlatClaimResourceEntry({ entryId: entry.entryId, resourceType: entry.resourceType, claims });
65
+ }
66
+ /**
67
+ * Builds one DocumentReference flat entry for a PDF, JPEG or PNG.
68
+ * Link one or many such entries from any clinical parent with
69
+ * `linkFlatClaimResourceEntries`; attachment bytes are not duplicated.
70
+ */
71
+ export function buildDocumentReferenceFlatEntry(input) {
72
+ const claims = {
73
+ 'DocumentReference.identifier': requiredToken(input.identifier, 'document_reference_identifier_required'),
74
+ 'DocumentReference.content-type': input.contentType,
75
+ 'DocumentReference.contenthash': requiredToken(input.contentHash, 'document_reference_contenthash_required'),
76
+ };
77
+ if (input.title?.trim())
78
+ claims['DocumentReference.title'] = input.title.trim();
79
+ if (input.url?.trim())
80
+ claims['DocumentReference.url'] = input.url.trim();
81
+ if (input.dataBase64?.trim())
82
+ claims['DocumentReference.data'] = input.dataBase64.trim();
83
+ return buildFlatClaimResourceEntry({ entryId: input.entryId, resourceType: 'DocumentReference', claims });
84
+ }
85
+ function splitCsv(value) {
86
+ return value?.split(',').map(item => item.trim()).filter(Boolean) ?? [];
87
+ }
88
+ function uniqueCsv(values) {
89
+ return [...new Set(values.map(value => value.trim()).filter(Boolean))];
90
+ }
91
+ function requiredToken(value, error) {
92
+ const normalized = value.trim();
93
+ if (!normalized || normalized.includes(','))
94
+ throw new TypeError(error);
95
+ return normalized;
96
+ }
97
+ function requiredText(value, error) {
98
+ const normalized = value.trim();
99
+ if (!normalized)
100
+ throw new TypeError(error);
101
+ return normalized;
102
+ }
103
+ function requiredResourceType(value) {
104
+ const normalized = requiredToken(value, 'flat_claim_resource_type_required');
105
+ if (!/^[A-Z][A-Za-z0-9]+$/.test(normalized))
106
+ throw new TypeError('flat_claim_resource_type_invalid');
107
+ return normalized;
108
+ }
@@ -0,0 +1,5 @@
1
+ export * from './flat-claim-resource-graph.js';
2
+ export * from './coding-review-flat-claims.js';
3
+ export * from './search-parameters.js';
4
+ export * from './observation-claims.js';
5
+ export * from './vital-sign-observations.js';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export * from './flat-claim-resource-graph.js';
2
+ export * from './coding-review-flat-claims.js';
3
+ export * from './search-parameters.js';
4
+ export * from './observation-claims.js';
5
+ export * from './vital-sign-observations.js';
@@ -0,0 +1,132 @@
1
+ /** Canonical code-system URIs used by neutral Observation contracts. */
2
+ export declare const FhirCodeSystem: Readonly<{
3
+ readonly Loinc: "http://loinc.org";
4
+ readonly Ucum: "http://unitsofmeasure.org";
5
+ readonly ObservationCategory: "http://terminology.hl7.org/CodeSystem/observation-category";
6
+ }>;
7
+ export type CodingDescriptor = Readonly<{
8
+ system: string;
9
+ code: string;
10
+ display?: string;
11
+ claim: string;
12
+ }>;
13
+ /** Canonical HL7 Observation category descriptors. */
14
+ export declare const ObservationCategoryCodes: Readonly<{
15
+ readonly SocialHistory: Readonly<{
16
+ system: string;
17
+ code: string;
18
+ display?: string;
19
+ claim: string;
20
+ }>;
21
+ readonly VitalSigns: Readonly<{
22
+ system: string;
23
+ code: string;
24
+ display?: string;
25
+ claim: string;
26
+ }>;
27
+ readonly Imaging: Readonly<{
28
+ system: string;
29
+ code: string;
30
+ display?: string;
31
+ claim: string;
32
+ }>;
33
+ readonly Laboratory: Readonly<{
34
+ system: string;
35
+ code: string;
36
+ display?: string;
37
+ claim: string;
38
+ }>;
39
+ readonly Procedure: Readonly<{
40
+ system: string;
41
+ code: string;
42
+ display?: string;
43
+ claim: string;
44
+ }>;
45
+ readonly Survey: Readonly<{
46
+ system: string;
47
+ code: string;
48
+ display?: string;
49
+ claim: string;
50
+ }>;
51
+ readonly Exam: Readonly<{
52
+ system: string;
53
+ code: string;
54
+ display?: string;
55
+ claim: string;
56
+ }>;
57
+ readonly Therapy: Readonly<{
58
+ system: string;
59
+ code: string;
60
+ display?: string;
61
+ claim: string;
62
+ }>;
63
+ readonly Activity: Readonly<{
64
+ system: string;
65
+ code: string;
66
+ display?: string;
67
+ claim: string;
68
+ }>;
69
+ }>;
70
+ /**
71
+ * Canonical flat claim keys for Observation authoring and projection.
72
+ *
73
+ * The `component-*` and `bp-*` keys are read-only migration inputs. They are
74
+ * not FHIR SearchParameters and must never be emitted by a current writer.
75
+ */
76
+ export declare const ObservationClaim: Readonly<{
77
+ readonly BasedOn: "Observation.based-on";
78
+ readonly Category: "Observation.category";
79
+ readonly CodeSystem: "Observation.code-system";
80
+ readonly CodeValue: "Observation.code-value";
81
+ readonly CodeText: "Observation.code-text";
82
+ readonly CodeTextLocal: "Observation.code-text";
83
+ readonly CodeDisplay: "Observation.code-display";
84
+ readonly Code: "Observation.code";
85
+ readonly Date: "Observation.date";
86
+ readonly Device: "Observation.device";
87
+ readonly Encounter: "Observation.encounter";
88
+ readonly Focus: "Observation.focus";
89
+ readonly HasMember: "Observation.has-member";
90
+ readonly ComponentTags: "Observation.component-tags";
91
+ readonly ComponentCodeValues: "Observation.component-code-values";
92
+ readonly ComponentNames: "Observation.component-names";
93
+ readonly Identifier: "Observation.identifier";
94
+ readonly Language: "Observation.language";
95
+ readonly Method: "Observation.method";
96
+ readonly Patient: "Observation.patient";
97
+ readonly Performer: "Observation.performer";
98
+ readonly Specimen: "Observation.specimen";
99
+ readonly Status: "Observation.status";
100
+ readonly Subject: "Observation.subject";
101
+ readonly ValueConcept: "Observation.value-concept";
102
+ readonly ValueConceptSystem: "Observation.value-concept-system";
103
+ readonly ValueConceptValue: "Observation.value-concept-value";
104
+ readonly ValueConceptText: "Observation.value-concept-text";
105
+ readonly ValueConceptDisplay: "Observation.value-concept-display";
106
+ readonly ValueDate: "Observation.value-date";
107
+ readonly ValueQuantityComparator: "Observation.value-quantity-comparator";
108
+ readonly ValueQuantityNumber: "Observation.value-quantity-number";
109
+ readonly ValueQuantityUnit: "Observation.value-quantity-unit";
110
+ readonly ReferenceRangeLowNumber: "Observation.reference-range-low-number";
111
+ readonly ReferenceRangeHighNumber: "Observation.reference-range-high-number";
112
+ readonly ReferenceRangeUnit: "Observation.reference-range-unit";
113
+ readonly ReferenceRangeText: "Observation.reference-range-text";
114
+ readonly ComponentCode: "Observation.component-code";
115
+ readonly ComponentCodeDisplay: "Observation.component-code-display";
116
+ readonly ComponentValueQuantityNumber: "Observation.component-value-quantity-number";
117
+ readonly ComponentValueQuantityUnit: "Observation.component-value-quantity-unit";
118
+ readonly ScoreTotalNumber: "Observation.score-total-number";
119
+ readonly BloodPressureSystolicNumber: "Observation.bp-systolic-number";
120
+ readonly BloodPressureDiastolicNumber: "Observation.bp-diastolic-number";
121
+ readonly ValueString: "Observation.value-string";
122
+ readonly Note: "Observation.note";
123
+ readonly EffectiveDateTime: "Observation.effective-datetime";
124
+ /** Custom provenance flag for an explicitly entered or selected result. */
125
+ readonly UserSelected: "Observation.user-selected";
126
+ }>;
127
+ export type ObservationClaimKey = typeof ObservationClaim[keyof typeof ObservationClaim];
128
+ /**
129
+ * Reads historical JSON-array and comma-separated component claims.
130
+ * Compatibility input is normalized in memory and never emitted again.
131
+ */
132
+ export declare function decodeObservationClaimList(value: string | undefined): readonly unknown[];
@@ -0,0 +1,96 @@
1
+ /** Canonical code-system URIs used by neutral Observation contracts. */
2
+ export const FhirCodeSystem = Object.freeze({
3
+ Loinc: 'http://loinc.org',
4
+ Ucum: 'http://unitsofmeasure.org',
5
+ ObservationCategory: 'http://terminology.hl7.org/CodeSystem/observation-category',
6
+ });
7
+ function defineCoding(system, code, display) {
8
+ return Object.freeze({ system, code, display, claim: `${system}|${code}` });
9
+ }
10
+ /** Canonical HL7 Observation category descriptors. */
11
+ export const ObservationCategoryCodes = Object.freeze({
12
+ SocialHistory: defineCoding(FhirCodeSystem.ObservationCategory, 'social-history', 'Social History'),
13
+ VitalSigns: defineCoding(FhirCodeSystem.ObservationCategory, 'vital-signs', 'Vital Signs'),
14
+ Imaging: defineCoding(FhirCodeSystem.ObservationCategory, 'imaging', 'Imaging'),
15
+ Laboratory: defineCoding(FhirCodeSystem.ObservationCategory, 'laboratory', 'Laboratory'),
16
+ Procedure: defineCoding(FhirCodeSystem.ObservationCategory, 'procedure', 'Procedure'),
17
+ Survey: defineCoding(FhirCodeSystem.ObservationCategory, 'survey', 'Survey'),
18
+ Exam: defineCoding(FhirCodeSystem.ObservationCategory, 'exam', 'Exam'),
19
+ Therapy: defineCoding(FhirCodeSystem.ObservationCategory, 'therapy', 'Therapy'),
20
+ Activity: defineCoding(FhirCodeSystem.ObservationCategory, 'activity', 'Activity'),
21
+ });
22
+ /**
23
+ * Canonical flat claim keys for Observation authoring and projection.
24
+ *
25
+ * The `component-*` and `bp-*` keys are read-only migration inputs. They are
26
+ * not FHIR SearchParameters and must never be emitted by a current writer.
27
+ */
28
+ export const ObservationClaim = Object.freeze({
29
+ BasedOn: 'Observation.based-on',
30
+ Category: 'Observation.category',
31
+ CodeSystem: 'Observation.code-system',
32
+ CodeValue: 'Observation.code-value',
33
+ CodeText: 'Observation.code-text',
34
+ CodeTextLocal: 'Observation.code-text',
35
+ CodeDisplay: 'Observation.code-display',
36
+ Code: 'Observation.code',
37
+ Date: 'Observation.date',
38
+ Device: 'Observation.device',
39
+ Encounter: 'Observation.encounter',
40
+ Focus: 'Observation.focus',
41
+ HasMember: 'Observation.has-member',
42
+ ComponentTags: 'Observation.component-tags',
43
+ ComponentCodeValues: 'Observation.component-code-values',
44
+ ComponentNames: 'Observation.component-names',
45
+ Identifier: 'Observation.identifier',
46
+ Language: 'Observation.language',
47
+ Method: 'Observation.method',
48
+ Patient: 'Observation.patient',
49
+ Performer: 'Observation.performer',
50
+ Specimen: 'Observation.specimen',
51
+ Status: 'Observation.status',
52
+ Subject: 'Observation.subject',
53
+ ValueConcept: 'Observation.value-concept',
54
+ ValueConceptSystem: 'Observation.value-concept-system',
55
+ ValueConceptValue: 'Observation.value-concept-value',
56
+ ValueConceptText: 'Observation.value-concept-text',
57
+ ValueConceptDisplay: 'Observation.value-concept-display',
58
+ ValueDate: 'Observation.value-date',
59
+ ValueQuantityComparator: 'Observation.value-quantity-comparator',
60
+ ValueQuantityNumber: 'Observation.value-quantity-number',
61
+ ValueQuantityUnit: 'Observation.value-quantity-unit',
62
+ ReferenceRangeLowNumber: 'Observation.reference-range-low-number',
63
+ ReferenceRangeHighNumber: 'Observation.reference-range-high-number',
64
+ ReferenceRangeUnit: 'Observation.reference-range-unit',
65
+ ReferenceRangeText: 'Observation.reference-range-text',
66
+ ComponentCode: 'Observation.component-code',
67
+ ComponentCodeDisplay: 'Observation.component-code-display',
68
+ ComponentValueQuantityNumber: 'Observation.component-value-quantity-number',
69
+ ComponentValueQuantityUnit: 'Observation.component-value-quantity-unit',
70
+ ScoreTotalNumber: 'Observation.score-total-number',
71
+ BloodPressureSystolicNumber: 'Observation.bp-systolic-number',
72
+ BloodPressureDiastolicNumber: 'Observation.bp-diastolic-number',
73
+ ValueString: 'Observation.value-string',
74
+ Note: 'Observation.note',
75
+ EffectiveDateTime: 'Observation.effective-datetime',
76
+ /** Custom provenance flag for an explicitly entered or selected result. */
77
+ UserSelected: 'Observation.user-selected',
78
+ });
79
+ /**
80
+ * Reads historical JSON-array and comma-separated component claims.
81
+ * Compatibility input is normalized in memory and never emitted again.
82
+ */
83
+ export function decodeObservationClaimList(value) {
84
+ if (!value)
85
+ return Object.freeze([]);
86
+ const normalized = value.trim();
87
+ if (!normalized)
88
+ return Object.freeze([]);
89
+ if (normalized.startsWith('[')) {
90
+ const parsed = JSON.parse(normalized);
91
+ if (!Array.isArray(parsed))
92
+ throw new TypeError('observation_claim_list_invalid');
93
+ return Object.freeze(parsed);
94
+ }
95
+ return Object.freeze(normalized.split(',').map(item => item.trim()));
96
+ }
@@ -0,0 +1,18 @@
1
+ export type FlatSearchParameterType = 'token' | 'reference' | 'date' | 'string' | 'number' | 'quantity' | 'uri' | 'composite' | 'special';
2
+ export type FlatSearchParameterDefinition = Readonly<{
3
+ claim: `${string}.${string}`;
4
+ resourceType: string;
5
+ elementPath: string;
6
+ type: FlatSearchParameterType;
7
+ }>;
8
+ /**
9
+ * Defines one canonical flat claim backed by a standard FHIR SearchParameter.
10
+ * Channels send `claim`; compatibility `resourceType` and `field` coordinates
11
+ * are derived from this governed definition and are never accepted as policy.
12
+ */
13
+ export declare function defineFlatSearchParameter(input: FlatSearchParameterDefinition): FlatSearchParameterDefinition;
14
+ /** Derives compatibility coordinates from governed policy, never from channel input. */
15
+ export declare function legacyFieldCoordinates(definition: FlatSearchParameterDefinition): Readonly<{
16
+ resourceType: string;
17
+ field: string;
18
+ }>;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Defines one canonical flat claim backed by a standard FHIR SearchParameter.
3
+ * Channels send `claim`; compatibility `resourceType` and `field` coordinates
4
+ * are derived from this governed definition and are never accepted as policy.
5
+ */
6
+ export function defineFlatSearchParameter(input) {
7
+ const resourceType = requiredResourceType(input.resourceType);
8
+ const claim = required(input.claim, 'flat_search_parameter_claim_required');
9
+ const elementPath = required(input.elementPath, 'flat_search_parameter_element_path_required');
10
+ if (!claim.startsWith(`${resourceType}.`) || !elementPath.startsWith(`${resourceType}.`)) {
11
+ throw new TypeError('flat_search_parameter_resource_type_mismatch');
12
+ }
13
+ return Object.freeze({ ...input, claim, resourceType, elementPath });
14
+ }
15
+ /** Derives compatibility coordinates from governed policy, never from channel input. */
16
+ export function legacyFieldCoordinates(definition) {
17
+ const prefix = `${definition.resourceType}.`;
18
+ const field = definition.elementPath.slice(prefix.length);
19
+ if (!field)
20
+ throw new TypeError('flat_search_parameter_element_path_invalid');
21
+ return Object.freeze({ resourceType: definition.resourceType, field });
22
+ }
23
+ function required(value, error) {
24
+ const normalized = value.trim();
25
+ if (!normalized)
26
+ throw new TypeError(error);
27
+ return normalized;
28
+ }
29
+ function requiredResourceType(value) {
30
+ const normalized = required(value, 'flat_search_parameter_resource_type_required');
31
+ if (!/^[A-Z][A-Za-z0-9]+$/.test(normalized))
32
+ throw new TypeError('flat_search_parameter_resource_type_invalid');
33
+ return normalized;
34
+ }
@@ -0,0 +1,250 @@
1
+ import { type FlatClaimResourceEntry } from './flat-claim-resource-graph.js';
2
+ export { ObservationCategoryCodes, ObservationClaim } from './observation-claims.js';
3
+ /** Canonical LOINC descriptors for supported vital signs. */
4
+ export declare const VitalSignsCodes: Readonly<{
5
+ readonly BodyWeight: Readonly<{
6
+ system: string;
7
+ code: string;
8
+ display?: string;
9
+ claim: string;
10
+ }>;
11
+ readonly HeartRate: Readonly<{
12
+ system: string;
13
+ code: string;
14
+ display?: string;
15
+ claim: string;
16
+ }>;
17
+ readonly BloodPressure: Readonly<{
18
+ system: string;
19
+ code: string;
20
+ display?: string;
21
+ claim: string;
22
+ }>;
23
+ readonly SystolicBloodPressure: Readonly<{
24
+ system: string;
25
+ code: string;
26
+ display?: string;
27
+ claim: string;
28
+ }>;
29
+ readonly DiastolicBloodPressure: Readonly<{
30
+ system: string;
31
+ code: string;
32
+ display?: string;
33
+ claim: string;
34
+ }>;
35
+ readonly BodyTemperature: Readonly<{
36
+ system: string;
37
+ code: string;
38
+ display?: string;
39
+ claim: string;
40
+ }>;
41
+ readonly OxygenSaturation: Readonly<{
42
+ system: string;
43
+ code: string;
44
+ display?: string;
45
+ claim: string;
46
+ }>;
47
+ readonly RespiratoryRate: Readonly<{
48
+ system: string;
49
+ code: string;
50
+ display?: string;
51
+ claim: string;
52
+ }>;
53
+ }>;
54
+ /** Canonical UCUM descriptors for supported vital signs. */
55
+ export declare const VitalSignsUnits: Readonly<{
56
+ readonly BeatsPerMinute: Readonly<{
57
+ system: string;
58
+ code: string;
59
+ display?: string;
60
+ claim: string;
61
+ }>;
62
+ readonly MillimeterOfMercury: Readonly<{
63
+ system: string;
64
+ code: string;
65
+ display?: string;
66
+ claim: string;
67
+ }>;
68
+ readonly Celsius: Readonly<{
69
+ system: string;
70
+ code: string;
71
+ display?: string;
72
+ claim: string;
73
+ }>;
74
+ readonly Percent: Readonly<{
75
+ system: string;
76
+ code: string;
77
+ display?: string;
78
+ claim: string;
79
+ }>;
80
+ readonly Kilogram: Readonly<{
81
+ system: string;
82
+ code: string;
83
+ display?: string;
84
+ claim: string;
85
+ }>;
86
+ }>;
87
+ /** Governed FHIR/LOINC tokens used by every channel that captures vital signs. */
88
+ export declare const VitalSignCode: Readonly<{
89
+ readonly bloodPressure: string;
90
+ readonly systolic: string;
91
+ readonly diastolic: string;
92
+ readonly temperature: string;
93
+ readonly heartRate: string;
94
+ readonly oxygenSaturation: string;
95
+ readonly respiratoryRate: string;
96
+ readonly bodyWeight: string;
97
+ }>;
98
+ /** UCUM-compatible unit spellings persisted in flat claims. */
99
+ export declare const VitalSignUnit: Readonly<{
100
+ readonly bloodPressure: string;
101
+ readonly temperature: string;
102
+ readonly heartRate: string;
103
+ readonly oxygenSaturation: string;
104
+ readonly respiratoryRate: string;
105
+ readonly bodyWeight: string;
106
+ }>;
107
+ export type VitalSignKind = 'blood-pressure' | 'temperature' | 'heart-rate' | 'oxygen-saturation' | 'respiratory-rate' | 'body-weight';
108
+ export type VitalSignObservationStatus = 'preliminary' | 'final';
109
+ type CommonMeasurement = Readonly<{
110
+ entryId: string;
111
+ subjectReference: string;
112
+ kind: VitalSignKind;
113
+ measuredAt: string;
114
+ status: VitalSignObservationStatus;
115
+ deviceReference?: string;
116
+ note?: string;
117
+ userSelected?: boolean;
118
+ }>;
119
+ export type VitalSignMeasurement = CommonMeasurement & (Readonly<{
120
+ kind: 'blood-pressure';
121
+ systolic: number;
122
+ diastolic: number;
123
+ }> | Readonly<{
124
+ kind: Exclude<VitalSignKind, 'blood-pressure'>;
125
+ value: number;
126
+ }>);
127
+ /**
128
+ * Canonical claims-first representation of one native FHIR Observation.
129
+ *
130
+ * Component values are independent reduced Observation entries so each one
131
+ * can be indexed with the ordinary Observation claim catalog. The primary
132
+ * entry owns the only relationship through `Observation.has-member`.
133
+ */
134
+ export type VitalSignObservationGraph = Readonly<{
135
+ primary: FlatClaimResourceEntry;
136
+ components: readonly FlatClaimResourceEntry[];
137
+ entries: readonly FlatClaimResourceEntry[];
138
+ }>;
139
+ /**
140
+ * Builds one timestamped Observation. The device is retained as source; this
141
+ * helper deliberately never creates a professional performer or attester.
142
+ */
143
+ export declare function buildVitalSignObservation(input: VitalSignMeasurement): VitalSignObservationGraph;
144
+ export type ObservationComponentQuantity = Readonly<{
145
+ code: string;
146
+ display?: string;
147
+ value: number;
148
+ unit: string;
149
+ }>;
150
+ /** Restores component values from the canonical graph or legacy scalar/array claims. */
151
+ export declare function parseObservationComponents(source: VitalSignObservationGraph | Readonly<Record<string, string>>): readonly ObservationComponentQuantity[];
152
+ export type ObservationComponentIndexEntry = Readonly<{
153
+ parentReference: string;
154
+ componentIndex: number;
155
+ claims: Readonly<Record<string, string>>;
156
+ }>;
157
+ /**
158
+ * Creates generic reduced rows for component-level indexes.
159
+ *
160
+ * These are internal index projections, not independent native FHIR
161
+ * Observations. The missing status deliberately keeps them out of normal
162
+ * top-level Observation results.
163
+ */
164
+ export declare function materializeObservationComponentIndexEntries(entry: VitalSignObservationGraph | FlatClaimResourceEntry): readonly ObservationComponentIndexEntry[];
165
+ type NativeCoding = Readonly<{
166
+ system?: string;
167
+ code: string;
168
+ display?: string;
169
+ }>;
170
+ type NativeVitalSignObservation = Readonly<{
171
+ resourceType: 'Observation';
172
+ id: string;
173
+ status: string;
174
+ category: readonly Readonly<{
175
+ coding: readonly NativeCoding[];
176
+ }>[];
177
+ code: Readonly<{
178
+ coding: readonly NativeCoding[];
179
+ }>;
180
+ subject: Readonly<{
181
+ reference: string;
182
+ }>;
183
+ effectiveDateTime: string;
184
+ device?: Readonly<{
185
+ reference: string;
186
+ }>;
187
+ component?: readonly Readonly<{
188
+ code: Readonly<{
189
+ coding: readonly NativeCoding[];
190
+ }>;
191
+ valueQuantity: Readonly<{
192
+ value: number;
193
+ unit: string;
194
+ system?: string;
195
+ code: string;
196
+ }>;
197
+ }>[];
198
+ valueQuantity?: Readonly<{
199
+ value: number;
200
+ unit: string;
201
+ system?: string;
202
+ code: string;
203
+ }>;
204
+ }>;
205
+ /** Projects the neutral flat contract at the explicit FHIR R4 boundary. */
206
+ export declare function projectVitalSignObservationR4(entry: VitalSignObservationGraph | FlatClaimResourceEntry): NativeVitalSignObservation;
207
+ /** Projects the neutral flat contract at the explicit FHIR R5 boundary. */
208
+ export declare function projectVitalSignObservationR5(entry: VitalSignObservationGraph | FlatClaimResourceEntry): NativeVitalSignObservation;
209
+ export type VitalSignDeviceReading = Readonly<{
210
+ entryId: string;
211
+ kind: 'blood-pressure';
212
+ measuredAt: string;
213
+ systolic: number;
214
+ diastolic: number;
215
+ note?: string;
216
+ }> | Readonly<{
217
+ entryId: string;
218
+ kind: Exclude<VitalSignKind, 'blood-pressure'>;
219
+ measuredAt: string;
220
+ value: number;
221
+ note?: string;
222
+ }>;
223
+ /** Converts a device/API batch to independent preliminary Observations without aggregation. */
224
+ export declare function normalizeVitalSignDeviceBatch(input: Readonly<{
225
+ subjectReference: string;
226
+ deviceReference: string;
227
+ readings: readonly VitalSignDeviceReading[];
228
+ }>): readonly VitalSignObservationGraph[];
229
+ /** Reusable neutral fixtures; consumers and tests must not duplicate wire examples. */
230
+ export declare const VITAL_SIGN_EXAMPLES: Readonly<{
231
+ subjectReference: "Patient/example-subject";
232
+ deviceReference: "Device/example-home-monitor";
233
+ bloodPressure: Readonly<{
234
+ entryId: "bp-example-1";
235
+ measuredAt: "2026-09-21T08:15:00-07:00";
236
+ systolic: 121;
237
+ diastolic: 79;
238
+ }>;
239
+ heartRateBatch: readonly (Readonly<{
240
+ entryId: "hr-example-1";
241
+ kind: "heart-rate";
242
+ measuredAt: "2026-09-20T08:00:00Z";
243
+ value: 68;
244
+ }> | Readonly<{
245
+ entryId: "hr-example-2";
246
+ kind: "heart-rate";
247
+ measuredAt: "2026-09-21T08:00:00Z";
248
+ value: 72;
249
+ }>)[];
250
+ }>;