fhir-data-utils-ts 0.6.21 → 0.6.22

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/CHANGELOG.md CHANGED
@@ -5,6 +5,15 @@
5
5
  - Re-establish the public repository from a clean source snapshot while
6
6
  retaining this changelog as the package release record.
7
7
 
8
+ ## 0.6.22 - 2026-10-22
9
+
10
+ - Add FHIR-owned flat claims and a lossless R4 extension round trip for the
11
+ optional US Core Patient race and ethnicity extensions. Preserve OMB and
12
+ detailed Coding tokens separately from the required human-readable text;
13
+ derive the official `Patient.race` and `Patient.ethnicity` token-search
14
+ claims instead of accepting them from a browser; and do not expose these
15
+ jurisdictional fields as universal demographics or org.schema properties.
16
+
8
17
  ## 0.6.21 - 2026-10-21
9
18
 
10
19
  - Add the shared CommunicationRequest form, flat-claim and materialization
@@ -35,3 +35,4 @@ export * from './regulated-authorization-claims.js';
35
35
  export * from './service-request-claims.js';
36
36
  export * from './schedule-slot-claims.js';
37
37
  export * from './task-claims.js';
38
+ export * from './us-core-patient-demographics-claims.js';
@@ -35,3 +35,4 @@ export * from './regulated-authorization-claims.js';
35
35
  export * from './service-request-claims.js';
36
36
  export * from './schedule-slot-claims.js';
37
37
  export * from './task-claims.js';
38
+ export * from './us-core-patient-demographics-claims.js';
@@ -0,0 +1,19 @@
1
+ import type { ClaimSpec } from './claim-types.js';
2
+ /** Canonical claims for the optional US Core Patient race and ethnicity extensions. */
3
+ export declare const UsCorePatientDemographicClaim: Readonly<{
4
+ readonly RaceOmbCategory: "Patient.extension:us-core-race.extension:ombCategory";
5
+ readonly RaceDetailed: "Patient.extension:us-core-race.extension:detailed";
6
+ readonly RaceText: "Patient.extension:us-core-race.extension:text";
7
+ readonly EthnicityOmbCategory: "Patient.extension:us-core-ethnicity.extension:ombCategory";
8
+ readonly EthnicityDetailed: "Patient.extension:us-core-ethnicity.extension:detailed";
9
+ readonly EthnicityText: "Patient.extension:us-core-ethnicity.extension:text";
10
+ }>;
11
+ /** Derived claims backed by the two official optional US Core SearchParameters. */
12
+ export declare const UsCorePatientDemographicSearchClaim: Readonly<{
13
+ readonly Race: "Patient.race";
14
+ readonly Ethnicity: "Patient.ethnicity";
15
+ }>;
16
+ export type UsCorePatientDemographicClaimKey = typeof UsCorePatientDemographicClaim[keyof typeof UsCorePatientDemographicClaim];
17
+ export type UsCorePatientDemographicSearchClaimKey = typeof UsCorePatientDemographicSearchClaim[keyof typeof UsCorePatientDemographicSearchClaim];
18
+ export declare const UsCorePatientDemographicClaimSpecs: readonly ClaimSpec[];
19
+ export declare const UsCorePatientDemographicSearchClaimSpecs: readonly ClaimSpec[];
@@ -0,0 +1,26 @@
1
+ /** Canonical claims for the optional US Core Patient race and ethnicity extensions. */
2
+ export const UsCorePatientDemographicClaim = Object.freeze({
3
+ RaceOmbCategory: 'Patient.extension:us-core-race.extension:ombCategory',
4
+ RaceDetailed: 'Patient.extension:us-core-race.extension:detailed',
5
+ RaceText: 'Patient.extension:us-core-race.extension:text',
6
+ EthnicityOmbCategory: 'Patient.extension:us-core-ethnicity.extension:ombCategory',
7
+ EthnicityDetailed: 'Patient.extension:us-core-ethnicity.extension:detailed',
8
+ EthnicityText: 'Patient.extension:us-core-ethnicity.extension:text',
9
+ });
10
+ /** Derived claims backed by the two official optional US Core SearchParameters. */
11
+ export const UsCorePatientDemographicSearchClaim = Object.freeze({
12
+ Race: 'Patient.race',
13
+ Ethnicity: 'Patient.ethnicity',
14
+ });
15
+ export const UsCorePatientDemographicClaimSpecs = Object.freeze([
16
+ { key: UsCorePatientDemographicClaim.RaceOmbCategory, meaning: 'Comma-separated OMB race Coding tokens.', example: 'urn:oid:2.16.840.1.113883.6.238|2028-9' },
17
+ { key: UsCorePatientDemographicClaim.RaceDetailed, meaning: 'Comma-separated detailed race Coding tokens.', example: 'urn:oid:2.16.840.1.113883.6.238|2036-2' },
18
+ { key: UsCorePatientDemographicClaim.RaceText, meaning: 'Patient-supplied race text required when the US Core race extension is present.', example: 'Asian' },
19
+ { key: UsCorePatientDemographicClaim.EthnicityOmbCategory, meaning: 'OMB ethnicity Coding token.', example: 'urn:oid:2.16.840.1.113883.6.238|2135-2' },
20
+ { key: UsCorePatientDemographicClaim.EthnicityDetailed, meaning: 'Comma-separated detailed ethnicity Coding tokens.', example: 'urn:oid:2.16.840.1.113883.6.238|2148-5' },
21
+ { key: UsCorePatientDemographicClaim.EthnicityText, meaning: 'Patient-supplied ethnicity text required when the US Core ethnicity extension is present.', example: 'Hispanic or Latino' },
22
+ ]);
23
+ export const UsCorePatientDemographicSearchClaimSpecs = Object.freeze([
24
+ { key: UsCorePatientDemographicSearchClaim.Race, meaning: 'Derived token list for the official optional US Core race SearchParameter.', example: 'urn:oid:2.16.840.1.113883.6.238|2028-9' },
25
+ { key: UsCorePatientDemographicSearchClaim.Ethnicity, meaning: 'Derived token list for the official optional US Core ethnicity SearchParameter.', example: 'urn:oid:2.16.840.1.113883.6.238|2135-2' },
26
+ ]);
@@ -0,0 +1,29 @@
1
+ import type { UsCorePatientDemographicSearchClaimKey } from '../claims/us-core-patient-demographics-claims.js';
2
+ import type { FhirCoding, FhirResource } from '../fhir-types.js';
3
+ import type { FlatClaims } from './convert-shared.js';
4
+ export declare const UsCorePatientRaceExtensionUrl = "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race";
5
+ export declare const UsCorePatientEthnicityExtensionUrl = "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity";
6
+ export type UsCorePatientSearchParameterDefinition = Readonly<{
7
+ canonicalUrl: string;
8
+ code: 'race' | 'ethnicity';
9
+ base: 'Patient';
10
+ type: 'token';
11
+ expression: string;
12
+ claim: UsCorePatientDemographicSearchClaimKey;
13
+ sourceClaims: readonly string[];
14
+ multipleOr: true;
15
+ multipleAnd: true;
16
+ }>;
17
+ export declare const UsCorePatientDemographicSearchParameters: readonly UsCorePatientSearchParameterDefinition[];
18
+ export type FhirExtension = Readonly<{
19
+ url: string;
20
+ extension?: readonly FhirExtension[];
21
+ valueCoding?: FhirCoding;
22
+ valueString?: string;
23
+ }>;
24
+ /** Materializes only the optional US Core extensions enabled by canonical claims. */
25
+ export declare function usCorePatientDemographicsFlatToFhirR4Extensions(claims: FlatClaims): readonly FhirExtension[];
26
+ /** Reads the two governed US Core extensions without treating them as universal demographics. */
27
+ export declare function usCorePatientDemographicsFhirR4ToFlat(resource: FhirResource): FlatClaims;
28
+ /** Derives the official token-search claims; callers never submit these independently. */
29
+ export declare function deriveUsCorePatientDemographicSearchClaims(claims: FlatClaims): FlatClaims;
@@ -0,0 +1,114 @@
1
+ import { UsCorePatientDemographicClaim, UsCorePatientDemographicSearchClaim } from '../claims/us-core-patient-demographics-claims.js';
2
+ import { codingListFromCsv, codingListToCsv } from './convert-shared.js';
3
+ export const UsCorePatientRaceExtensionUrl = 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-race';
4
+ export const UsCorePatientEthnicityExtensionUrl = 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity';
5
+ export const UsCorePatientDemographicSearchParameters = Object.freeze([
6
+ Object.freeze({
7
+ canonicalUrl: 'http://hl7.org/fhir/us/core/SearchParameter/us-core-race',
8
+ code: 'race', base: 'Patient', type: 'token',
9
+ expression: `Patient.extension.where(url = '${UsCorePatientRaceExtensionUrl}').extension.value.code`,
10
+ claim: UsCorePatientDemographicSearchClaim.Race,
11
+ sourceClaims: Object.freeze([UsCorePatientDemographicClaim.RaceOmbCategory, UsCorePatientDemographicClaim.RaceDetailed]),
12
+ multipleOr: true, multipleAnd: true,
13
+ }),
14
+ Object.freeze({
15
+ canonicalUrl: 'http://hl7.org/fhir/us/core/SearchParameter/us-core-ethnicity',
16
+ code: 'ethnicity', base: 'Patient', type: 'token',
17
+ expression: `Patient.extension.where(url = '${UsCorePatientEthnicityExtensionUrl}').extension.value.code`,
18
+ claim: UsCorePatientDemographicSearchClaim.Ethnicity,
19
+ sourceClaims: Object.freeze([UsCorePatientDemographicClaim.EthnicityOmbCategory, UsCorePatientDemographicClaim.EthnicityDetailed]),
20
+ multipleOr: true, multipleAnd: true,
21
+ }),
22
+ ]);
23
+ /** Materializes only the optional US Core extensions enabled by canonical claims. */
24
+ export function usCorePatientDemographicsFlatToFhirR4Extensions(claims) {
25
+ return [
26
+ buildExtension({
27
+ url: UsCorePatientRaceExtensionUrl,
28
+ omb: claims[UsCorePatientDemographicClaim.RaceOmbCategory],
29
+ detailed: claims[UsCorePatientDemographicClaim.RaceDetailed],
30
+ text: claims[UsCorePatientDemographicClaim.RaceText],
31
+ textError: 'us_core_patient_race_text_required',
32
+ }),
33
+ buildExtension({
34
+ url: UsCorePatientEthnicityExtensionUrl,
35
+ omb: claims[UsCorePatientDemographicClaim.EthnicityOmbCategory],
36
+ detailed: claims[UsCorePatientDemographicClaim.EthnicityDetailed],
37
+ text: claims[UsCorePatientDemographicClaim.EthnicityText],
38
+ textError: 'us_core_patient_ethnicity_text_required',
39
+ singleOmbCategory: true,
40
+ }),
41
+ ].filter((extension) => Boolean(extension));
42
+ }
43
+ /** Reads the two governed US Core extensions without treating them as universal demographics. */
44
+ export function usCorePatientDemographicsFhirR4ToFlat(resource) {
45
+ if (resource.resourceType !== 'Patient')
46
+ throw new TypeError('us_core_patient_resource_required');
47
+ const extensions = Array.isArray(resource.extension) ? resource.extension : [];
48
+ return {
49
+ ...readExtension(extensions, UsCorePatientRaceExtensionUrl, {
50
+ omb: UsCorePatientDemographicClaim.RaceOmbCategory,
51
+ detailed: UsCorePatientDemographicClaim.RaceDetailed,
52
+ text: UsCorePatientDemographicClaim.RaceText,
53
+ textError: 'us_core_patient_race_text_required',
54
+ }),
55
+ ...readExtension(extensions, UsCorePatientEthnicityExtensionUrl, {
56
+ omb: UsCorePatientDemographicClaim.EthnicityOmbCategory,
57
+ detailed: UsCorePatientDemographicClaim.EthnicityDetailed,
58
+ text: UsCorePatientDemographicClaim.EthnicityText,
59
+ textError: 'us_core_patient_ethnicity_text_required',
60
+ singleOmbCategory: true,
61
+ }),
62
+ };
63
+ }
64
+ /** Derives the official token-search claims; callers never submit these independently. */
65
+ export function deriveUsCorePatientDemographicSearchClaims(claims) {
66
+ return {
67
+ [UsCorePatientDemographicSearchClaim.Race]: mergeTokenClaims(claims[UsCorePatientDemographicClaim.RaceOmbCategory], claims[UsCorePatientDemographicClaim.RaceDetailed]),
68
+ [UsCorePatientDemographicSearchClaim.Ethnicity]: mergeTokenClaims(claims[UsCorePatientDemographicClaim.EthnicityOmbCategory], claims[UsCorePatientDemographicClaim.EthnicityDetailed]),
69
+ };
70
+ }
71
+ function buildExtension(input) {
72
+ const omb = codingListFromCsv(input.omb) ?? [];
73
+ const detailed = codingListFromCsv(input.detailed) ?? [];
74
+ const text = String(input.text || '').trim();
75
+ if (!omb.length && !detailed.length && !text)
76
+ return undefined;
77
+ if (!text)
78
+ throw new TypeError(input.textError);
79
+ if (input.singleOmbCategory && omb.length > 1)
80
+ throw new TypeError('us_core_patient_ethnicity_omb_category_cardinality');
81
+ return Object.freeze({
82
+ url: input.url,
83
+ extension: Object.freeze([
84
+ ...omb.map((valueCoding) => Object.freeze({ url: 'ombCategory', valueCoding })),
85
+ ...detailed.map((valueCoding) => Object.freeze({ url: 'detailed', valueCoding })),
86
+ Object.freeze({ url: 'text', valueString: text }),
87
+ ]),
88
+ });
89
+ }
90
+ function readExtension(extensions, url, keys) {
91
+ const matches = extensions.filter((extension) => extension.url === url);
92
+ if (!matches.length)
93
+ return {};
94
+ if (matches.length > 1)
95
+ throw new TypeError('us_core_patient_extension_cardinality');
96
+ const children = matches[0]?.extension ?? [];
97
+ const omb = children.filter((extension) => extension.url === 'ombCategory' && extension.valueCoding).map((extension) => extension.valueCoding);
98
+ const detailed = children.filter((extension) => extension.url === 'detailed' && extension.valueCoding).map((extension) => extension.valueCoding);
99
+ const texts = children.filter((extension) => extension.url === 'text' && extension.valueString).map((extension) => extension.valueString.trim()).filter(Boolean);
100
+ if (texts.length !== 1)
101
+ throw new TypeError(keys.textError);
102
+ if (keys.singleOmbCategory && omb.length > 1)
103
+ throw new TypeError('us_core_patient_ethnicity_omb_category_cardinality');
104
+ return {
105
+ [keys.omb]: codingListToCsv(omb),
106
+ [keys.detailed]: codingListToCsv(detailed),
107
+ [keys.text]: texts[0],
108
+ };
109
+ }
110
+ function mergeTokenClaims(...values) {
111
+ const tokens = values.flatMap((value) => String(value || '').split(',')).map((value) => value.trim()).filter(Boolean);
112
+ const unique = [...new Set(tokens)];
113
+ return unique.length ? unique.join(',') : undefined;
114
+ }
@@ -32,3 +32,4 @@ export * from './convert-related-person.js';
32
32
  export * from './convert-service-request.js';
33
33
  export * from './convert-schedule-slot.js';
34
34
  export * from './materialize-claims-first-resource.js';
35
+ export * from './convert-us-core-patient-demographics.js';
@@ -32,3 +32,4 @@ export * from './convert-related-person.js';
32
32
  export * from './convert-service-request.js';
33
33
  export * from './convert-schedule-slot.js';
34
34
  export * from './materialize-claims-first-resource.js';
35
+ export * from './convert-us-core-patient-demographics.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fhir-data-utils-ts",
3
- "version": "0.6.21",
3
+ "version": "0.6.22",
4
4
  "description": "FHIR-version-neutral flat-claim and SearchParameter data utilities",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",