@peerbits/fhir-validator 1.0.0 → 1.0.1
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/README.md +3 -1
- package/dist/base-definitions/claim-response.d.ts +5 -0
- package/dist/base-definitions/claim-response.js +186 -0
- package/dist/base-definitions/claim.d.ts +5 -0
- package/dist/base-definitions/claim.js +240 -0
- package/dist/base-definitions/condition.d.ts +5 -0
- package/dist/base-definitions/condition.js +78 -0
- package/dist/base-definitions/coverage.d.ts +5 -0
- package/dist/base-definitions/coverage.js +109 -0
- package/dist/base-definitions/encounter.d.ts +5 -0
- package/dist/base-definitions/encounter.js +137 -0
- package/dist/base-definitions/observation.d.ts +5 -0
- package/dist/base-definitions/observation.js +213 -0
- package/dist/base-definitions/patient.d.ts +5 -0
- package/dist/base-definitions/patient.js +163 -0
- package/dist/coding.d.ts +17 -0
- package/dist/coding.js +139 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +22 -0
- package/dist/profiles/us-core-observation-vitals.d.ts +11 -0
- package/dist/profiles/us-core-observation-vitals.js +22 -0
- package/dist/profiles/us-core-patient.d.ts +10 -0
- package/dist/profiles/us-core-patient.js +21 -0
- package/dist/reference-rules.d.ts +14 -0
- package/dist/reference-rules.js +139 -0
- package/dist/types.d.ts +45 -0
- package/dist/types.js +5 -0
- package/dist/validate.d.ts +21 -0
- package/dist/validate.js +201 -0
- package/package.json +3 -2
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { validateCodeableConcept, validateCoding } from "../coding.js";
|
|
2
|
+
import { validateReference } from "../reference-rules.js";
|
|
3
|
+
const VALID_ENCOUNTER_STATUSES = [
|
|
4
|
+
"planned",
|
|
5
|
+
"arrived",
|
|
6
|
+
"triaged",
|
|
7
|
+
"in-progress",
|
|
8
|
+
"onleave",
|
|
9
|
+
"finished",
|
|
10
|
+
"cancelled",
|
|
11
|
+
"entered-in-error",
|
|
12
|
+
"unknown",
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* Validates base structural rules for FHIR R4 Encounter resource.
|
|
16
|
+
*/
|
|
17
|
+
export function validateEncounter(resource, path = "Encounter") {
|
|
18
|
+
const issues = [];
|
|
19
|
+
if (resource.resourceType !== "Encounter") {
|
|
20
|
+
issues.push({
|
|
21
|
+
severity: "error",
|
|
22
|
+
path: `${path}.resourceType`,
|
|
23
|
+
code: "invalid-resource-type",
|
|
24
|
+
message: `Expected resourceType 'Encounter', found '${String(resource.resourceType)}'.`,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
// status (Required)
|
|
28
|
+
if (resource.status === undefined || resource.status === null || resource.status === "") {
|
|
29
|
+
issues.push({
|
|
30
|
+
severity: "error",
|
|
31
|
+
path: `${path}.status`,
|
|
32
|
+
code: "required",
|
|
33
|
+
message: `Missing required 'status' in '${path}'.`,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
else if (typeof resource.status !== "string" || !VALID_ENCOUNTER_STATUSES.includes(resource.status)) {
|
|
37
|
+
issues.push({
|
|
38
|
+
severity: "error",
|
|
39
|
+
path: `${path}.status`,
|
|
40
|
+
code: "invalid-value",
|
|
41
|
+
message: `Invalid Encounter status '${String(resource.status)}'. Allowed values: ${VALID_ENCOUNTER_STATUSES.join(", ")}.`,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// class (Required Coding / Coding-like object)
|
|
45
|
+
if (resource.class === undefined || resource.class === null) {
|
|
46
|
+
issues.push({
|
|
47
|
+
severity: "error",
|
|
48
|
+
path: `${path}.class`,
|
|
49
|
+
code: "required",
|
|
50
|
+
message: `Missing required 'class' in '${path}'.`,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
// In FHIR R4, Encounter.class is a Coding
|
|
55
|
+
issues.push(...validateCoding(resource.class, `${path}.class`));
|
|
56
|
+
}
|
|
57
|
+
// subject (Reference: Patient | Group)
|
|
58
|
+
if (resource.subject !== undefined) {
|
|
59
|
+
issues.push(...validateReference(resource.subject, `${path}.subject`, ["Patient", "Group"]));
|
|
60
|
+
}
|
|
61
|
+
// period (Period)
|
|
62
|
+
if (resource.period !== undefined) {
|
|
63
|
+
if (typeof resource.period !== "object" || resource.period === null || Array.isArray(resource.period)) {
|
|
64
|
+
issues.push({
|
|
65
|
+
severity: "error",
|
|
66
|
+
path: `${path}.period`,
|
|
67
|
+
code: "invalid-structure",
|
|
68
|
+
message: `Expected '${path}.period' to be an object.`,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
const p = resource.period;
|
|
73
|
+
if (p.start !== undefined && typeof p.start !== "string") {
|
|
74
|
+
issues.push({
|
|
75
|
+
severity: "error",
|
|
76
|
+
path: `${path}.period.start`,
|
|
77
|
+
code: "invalid-type",
|
|
78
|
+
message: `Expected '${path}.period.start' to be a string.`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (p.end !== undefined && typeof p.end !== "string") {
|
|
82
|
+
issues.push({
|
|
83
|
+
severity: "error",
|
|
84
|
+
path: `${path}.period.end`,
|
|
85
|
+
code: "invalid-type",
|
|
86
|
+
message: `Expected '${path}.period.end' to be a string.`,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// participant
|
|
92
|
+
if (resource.participant !== undefined) {
|
|
93
|
+
if (!Array.isArray(resource.participant)) {
|
|
94
|
+
issues.push({
|
|
95
|
+
severity: "error",
|
|
96
|
+
path: `${path}.participant`,
|
|
97
|
+
code: "invalid-type",
|
|
98
|
+
message: `Expected '${path}.participant' to be an array.`,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
resource.participant.forEach((part, idx) => {
|
|
103
|
+
const partPath = `${path}.participant[${idx}]`;
|
|
104
|
+
if (typeof part !== "object" || part === null || Array.isArray(part)) {
|
|
105
|
+
issues.push({
|
|
106
|
+
severity: "error",
|
|
107
|
+
path: partPath,
|
|
108
|
+
code: "invalid-structure",
|
|
109
|
+
message: `Expected '${partPath}' to be an object.`,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const p = part;
|
|
114
|
+
if (p.individual !== undefined) {
|
|
115
|
+
issues.push(...validateReference(p.individual, `${partPath}.individual`, ["Practitioner", "PractitionerRole", "RelatedPerson"]));
|
|
116
|
+
}
|
|
117
|
+
if (p.type !== undefined && Array.isArray(p.type)) {
|
|
118
|
+
p.type.forEach((t, tIdx) => {
|
|
119
|
+
issues.push(...validateCodeableConcept(t, `${partPath}.type[${tIdx}]`));
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// serviceProvider (Reference: Organization)
|
|
127
|
+
if (resource.serviceProvider !== undefined) {
|
|
128
|
+
issues.push(...validateReference(resource.serviceProvider, `${path}.serviceProvider`, ["Organization"]));
|
|
129
|
+
}
|
|
130
|
+
// type (CodeableConcept[])
|
|
131
|
+
if (resource.type !== undefined && Array.isArray(resource.type)) {
|
|
132
|
+
resource.type.forEach((t, idx) => {
|
|
133
|
+
issues.push(...validateCodeableConcept(t, `${path}.type[${idx}]`));
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return issues;
|
|
137
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { validateCodeableConcept } from "../coding.js";
|
|
2
|
+
import { validateReference } from "../reference-rules.js";
|
|
3
|
+
const VALID_OBSERVATION_STATUSES = [
|
|
4
|
+
"registered",
|
|
5
|
+
"preliminary",
|
|
6
|
+
"final",
|
|
7
|
+
"amended",
|
|
8
|
+
"corrected",
|
|
9
|
+
"cancelled",
|
|
10
|
+
"entered-in-error",
|
|
11
|
+
"unknown",
|
|
12
|
+
];
|
|
13
|
+
/**
|
|
14
|
+
* Validates base structural rules for FHIR R4 Observation resource.
|
|
15
|
+
*/
|
|
16
|
+
export function validateObservation(resource, path = "Observation") {
|
|
17
|
+
const issues = [];
|
|
18
|
+
if (resource.resourceType !== "Observation") {
|
|
19
|
+
issues.push({
|
|
20
|
+
severity: "error",
|
|
21
|
+
path: `${path}.resourceType`,
|
|
22
|
+
code: "invalid-resource-type",
|
|
23
|
+
message: `Expected resourceType 'Observation', found '${String(resource.resourceType)}'.`,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
// status (Required)
|
|
27
|
+
if (resource.status === undefined || resource.status === null || resource.status === "") {
|
|
28
|
+
issues.push({
|
|
29
|
+
severity: "error",
|
|
30
|
+
path: `${path}.status`,
|
|
31
|
+
code: "required",
|
|
32
|
+
message: `Missing required 'status' in '${path}'.`,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
else if (typeof resource.status !== "string" || !VALID_OBSERVATION_STATUSES.includes(resource.status)) {
|
|
36
|
+
issues.push({
|
|
37
|
+
severity: "error",
|
|
38
|
+
path: `${path}.status`,
|
|
39
|
+
code: "invalid-value",
|
|
40
|
+
message: `Invalid Observation status '${String(resource.status)}'. Allowed values: ${VALID_OBSERVATION_STATUSES.join(", ")}.`,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
// code (Required CodeableConcept)
|
|
44
|
+
if (resource.code === undefined || resource.code === null) {
|
|
45
|
+
issues.push({
|
|
46
|
+
severity: "error",
|
|
47
|
+
path: `${path}.code`,
|
|
48
|
+
code: "required",
|
|
49
|
+
message: `Missing required 'code' in '${path}'.`,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
issues.push(...validateCodeableConcept(resource.code, `${path}.code`, { requireCoding: true }));
|
|
54
|
+
}
|
|
55
|
+
// category (CodeableConcept[])
|
|
56
|
+
if (resource.category !== undefined) {
|
|
57
|
+
if (!Array.isArray(resource.category)) {
|
|
58
|
+
issues.push({
|
|
59
|
+
severity: "error",
|
|
60
|
+
path: `${path}.category`,
|
|
61
|
+
code: "invalid-type",
|
|
62
|
+
message: `Expected '${path}.category' to be an array, got ${typeof resource.category}.`,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
resource.category.forEach((cat, idx) => {
|
|
67
|
+
issues.push(...validateCodeableConcept(cat, `${path}.category[${idx}]`));
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// subject (Reference: Patient | Group | Device | Location)
|
|
72
|
+
if (resource.subject !== undefined) {
|
|
73
|
+
issues.push(...validateReference(resource.subject, `${path}.subject`, ["Patient", "Group", "Device", "Location"]));
|
|
74
|
+
}
|
|
75
|
+
// encounter (Reference: Encounter)
|
|
76
|
+
if (resource.encounter !== undefined) {
|
|
77
|
+
issues.push(...validateReference(resource.encounter, `${path}.encounter`, ["Encounter"]));
|
|
78
|
+
}
|
|
79
|
+
// performer (Reference[])
|
|
80
|
+
if (resource.performer !== undefined) {
|
|
81
|
+
if (!Array.isArray(resource.performer)) {
|
|
82
|
+
issues.push({
|
|
83
|
+
severity: "error",
|
|
84
|
+
path: `${path}.performer`,
|
|
85
|
+
code: "invalid-type",
|
|
86
|
+
message: `Expected '${path}.performer' to be an array, got ${typeof resource.performer}.`,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
resource.performer.forEach((perf, idx) => {
|
|
91
|
+
issues.push(...validateReference(perf, `${path}.performer[${idx}]`, ["Practitioner", "PractitionerRole", "Organization", "CareTeam", "Patient", "RelatedPerson"]));
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// valueQuantity
|
|
96
|
+
if (resource.valueQuantity !== undefined) {
|
|
97
|
+
if (typeof resource.valueQuantity !== "object" || resource.valueQuantity === null || Array.isArray(resource.valueQuantity)) {
|
|
98
|
+
issues.push({
|
|
99
|
+
severity: "error",
|
|
100
|
+
path: `${path}.valueQuantity`,
|
|
101
|
+
code: "invalid-structure",
|
|
102
|
+
message: `Expected '${path}.valueQuantity' to be an object.`,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
const q = resource.valueQuantity;
|
|
107
|
+
if (q.value !== undefined && typeof q.value !== "number") {
|
|
108
|
+
issues.push({
|
|
109
|
+
severity: "error",
|
|
110
|
+
path: `${path}.valueQuantity.value`,
|
|
111
|
+
code: "invalid-type",
|
|
112
|
+
message: `Expected '${path}.valueQuantity.value' to be a number, got ${typeof q.value}.`,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
if (q.unit !== undefined && typeof q.unit !== "string") {
|
|
116
|
+
issues.push({
|
|
117
|
+
severity: "error",
|
|
118
|
+
path: `${path}.valueQuantity.unit`,
|
|
119
|
+
code: "invalid-type",
|
|
120
|
+
message: `Expected '${path}.valueQuantity.unit' to be a string.`,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (q.system !== undefined && typeof q.system !== "string") {
|
|
124
|
+
issues.push({
|
|
125
|
+
severity: "error",
|
|
126
|
+
path: `${path}.valueQuantity.system`,
|
|
127
|
+
code: "invalid-type",
|
|
128
|
+
message: `Expected '${path}.valueQuantity.system' to be a string.`,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if (q.code !== undefined && typeof q.code !== "string") {
|
|
132
|
+
issues.push({
|
|
133
|
+
severity: "error",
|
|
134
|
+
path: `${path}.valueQuantity.code`,
|
|
135
|
+
code: "invalid-type",
|
|
136
|
+
message: `Expected '${path}.valueQuantity.code' to be a string.`,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// valueCodeableConcept
|
|
142
|
+
if (resource.valueCodeableConcept !== undefined) {
|
|
143
|
+
issues.push(...validateCodeableConcept(resource.valueCodeableConcept, `${path}.valueCodeableConcept`));
|
|
144
|
+
}
|
|
145
|
+
// component (Array of components)
|
|
146
|
+
if (resource.component !== undefined) {
|
|
147
|
+
if (!Array.isArray(resource.component)) {
|
|
148
|
+
issues.push({
|
|
149
|
+
severity: "error",
|
|
150
|
+
path: `${path}.component`,
|
|
151
|
+
code: "invalid-type",
|
|
152
|
+
message: `Expected '${path}.component' to be an array, got ${typeof resource.component}.`,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
resource.component.forEach((comp, idx) => {
|
|
157
|
+
const compPath = `${path}.component[${idx}]`;
|
|
158
|
+
if (typeof comp !== "object" || comp === null || Array.isArray(comp)) {
|
|
159
|
+
issues.push({
|
|
160
|
+
severity: "error",
|
|
161
|
+
path: compPath,
|
|
162
|
+
code: "invalid-structure",
|
|
163
|
+
message: `Expected '${compPath}' to be an object.`,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
const c = comp;
|
|
168
|
+
if (c.code === undefined || c.code === null) {
|
|
169
|
+
issues.push({
|
|
170
|
+
severity: "error",
|
|
171
|
+
path: `${compPath}.code`,
|
|
172
|
+
code: "required",
|
|
173
|
+
message: `Missing required 'code' in '${compPath}'.`,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
issues.push(...validateCodeableConcept(c.code, `${compPath}.code`, { requireCoding: true }));
|
|
178
|
+
}
|
|
179
|
+
if (c.valueQuantity !== undefined) {
|
|
180
|
+
const q = c.valueQuantity;
|
|
181
|
+
if (q && typeof q.value !== "number" && q.value !== undefined) {
|
|
182
|
+
issues.push({
|
|
183
|
+
severity: "error",
|
|
184
|
+
path: `${compPath}.valueQuantity.value`,
|
|
185
|
+
code: "invalid-type",
|
|
186
|
+
message: `Expected '${compPath}.valueQuantity.value' to be a number.`,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (c.valueCodeableConcept !== undefined) {
|
|
191
|
+
issues.push(...validateCodeableConcept(c.valueCodeableConcept, `${compPath}.valueCodeableConcept`));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// hasMember / derivedFrom
|
|
198
|
+
if (resource.hasMember !== undefined) {
|
|
199
|
+
if (Array.isArray(resource.hasMember)) {
|
|
200
|
+
resource.hasMember.forEach((hm, idx) => {
|
|
201
|
+
issues.push(...validateReference(hm, `${path}.hasMember[${idx}]`, ["Observation", "QuestionnaireResponse", "MolecularSequence"]));
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (resource.derivedFrom !== undefined) {
|
|
206
|
+
if (Array.isArray(resource.derivedFrom)) {
|
|
207
|
+
resource.derivedFrom.forEach((df, idx) => {
|
|
208
|
+
issues.push(...validateReference(df, `${path}.derivedFrom[${idx}]`, ["DocumentReference", "ImagingStudy", "Media", "QuestionnaireResponse", "Observation", "MolecularSequence"]));
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return issues;
|
|
213
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { validateCodeableConcept } from "../coding.js";
|
|
2
|
+
import { validateReference } from "../reference-rules.js";
|
|
3
|
+
const VALID_GENDERS = ["male", "female", "other", "unknown"];
|
|
4
|
+
const DATE_REGEX = /^\d{4}(-\d{2}(-\d{2})?)?$/;
|
|
5
|
+
/**
|
|
6
|
+
* Validates base structural rules for FHIR R4 Patient resource.
|
|
7
|
+
*/
|
|
8
|
+
export function validatePatient(resource, path = "Patient") {
|
|
9
|
+
const issues = [];
|
|
10
|
+
if (resource.resourceType !== "Patient") {
|
|
11
|
+
issues.push({
|
|
12
|
+
severity: "error",
|
|
13
|
+
path: `${path}.resourceType`,
|
|
14
|
+
code: "invalid-resource-type",
|
|
15
|
+
message: `Expected resourceType 'Patient', found '${String(resource.resourceType)}'.`,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
// active
|
|
19
|
+
if (resource.active !== undefined && typeof resource.active !== "boolean") {
|
|
20
|
+
issues.push({
|
|
21
|
+
severity: "error",
|
|
22
|
+
path: `${path}.active`,
|
|
23
|
+
code: "invalid-type",
|
|
24
|
+
message: `Expected '${path}.active' to be a boolean, got ${typeof resource.active}.`,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
// gender
|
|
28
|
+
if (resource.gender !== undefined) {
|
|
29
|
+
if (typeof resource.gender !== "string" || !VALID_GENDERS.includes(resource.gender)) {
|
|
30
|
+
issues.push({
|
|
31
|
+
severity: "error",
|
|
32
|
+
path: `${path}.gender`,
|
|
33
|
+
code: "invalid-value",
|
|
34
|
+
message: `Invalid gender '${String(resource.gender)}'. Allowed values: ${VALID_GENDERS.join(", ")}.`,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// birthDate
|
|
39
|
+
if (resource.birthDate !== undefined) {
|
|
40
|
+
if (typeof resource.birthDate !== "string" || !DATE_REGEX.test(resource.birthDate)) {
|
|
41
|
+
issues.push({
|
|
42
|
+
severity: "error",
|
|
43
|
+
path: `${path}.birthDate`,
|
|
44
|
+
code: "invalid-format",
|
|
45
|
+
message: `Expected '${path}.birthDate' to match YYYY, YYYY-MM, or YYYY-MM-DD format.`,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// name (HumanName[])
|
|
50
|
+
if (resource.name !== undefined) {
|
|
51
|
+
if (!Array.isArray(resource.name)) {
|
|
52
|
+
issues.push({
|
|
53
|
+
severity: "error",
|
|
54
|
+
path: `${path}.name`,
|
|
55
|
+
code: "invalid-type",
|
|
56
|
+
message: `Expected '${path}.name' to be an array, got ${typeof resource.name}.`,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
resource.name.forEach((n, idx) => {
|
|
61
|
+
const namePath = `${path}.name[${idx}]`;
|
|
62
|
+
if (typeof n !== "object" || n === null) {
|
|
63
|
+
issues.push({
|
|
64
|
+
severity: "error",
|
|
65
|
+
path: namePath,
|
|
66
|
+
code: "invalid-structure",
|
|
67
|
+
message: `Expected '${namePath}' to be a HumanName object.`,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
const hn = n;
|
|
72
|
+
if (hn.family !== undefined && typeof hn.family !== "string") {
|
|
73
|
+
issues.push({
|
|
74
|
+
severity: "error",
|
|
75
|
+
path: `${namePath}.family`,
|
|
76
|
+
code: "invalid-type",
|
|
77
|
+
message: `Expected '${namePath}.family' to be a string.`,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
if (hn.given !== undefined && (!Array.isArray(hn.given) || hn.given.some((g) => typeof g !== "string"))) {
|
|
81
|
+
issues.push({
|
|
82
|
+
severity: "error",
|
|
83
|
+
path: `${namePath}.given`,
|
|
84
|
+
code: "invalid-type",
|
|
85
|
+
message: `Expected '${namePath}.given' to be an array of strings.`,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// identifier (Identifier[])
|
|
93
|
+
if (resource.identifier !== undefined) {
|
|
94
|
+
if (!Array.isArray(resource.identifier)) {
|
|
95
|
+
issues.push({
|
|
96
|
+
severity: "error",
|
|
97
|
+
path: `${path}.identifier`,
|
|
98
|
+
code: "invalid-type",
|
|
99
|
+
message: `Expected '${path}.identifier' to be an array, got ${typeof resource.identifier}.`,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
resource.identifier.forEach((ident, idx) => {
|
|
104
|
+
const idPath = `${path}.identifier[${idx}]`;
|
|
105
|
+
if (typeof ident !== "object" || ident === null) {
|
|
106
|
+
issues.push({
|
|
107
|
+
severity: "error",
|
|
108
|
+
path: idPath,
|
|
109
|
+
code: "invalid-structure",
|
|
110
|
+
message: `Expected '${idPath}' to be an Identifier object.`,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
const idObj = ident;
|
|
115
|
+
if (idObj.system !== undefined && typeof idObj.system !== "string") {
|
|
116
|
+
issues.push({
|
|
117
|
+
severity: "error",
|
|
118
|
+
path: `${idPath}.system`,
|
|
119
|
+
code: "invalid-type",
|
|
120
|
+
message: `Expected '${idPath}.system' to be a string.`,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (idObj.value !== undefined && typeof idObj.value !== "string") {
|
|
124
|
+
issues.push({
|
|
125
|
+
severity: "error",
|
|
126
|
+
path: `${idPath}.value`,
|
|
127
|
+
code: "invalid-type",
|
|
128
|
+
message: `Expected '${idPath}.value' to be a string.`,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if (idObj.type !== undefined) {
|
|
132
|
+
issues.push(...validateCodeableConcept(idObj.type, `${idPath}.type`));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// generalPractitioner (Reference[])
|
|
139
|
+
if (resource.generalPractitioner !== undefined) {
|
|
140
|
+
if (!Array.isArray(resource.generalPractitioner)) {
|
|
141
|
+
issues.push({
|
|
142
|
+
severity: "error",
|
|
143
|
+
path: `${path}.generalPractitioner`,
|
|
144
|
+
code: "invalid-type",
|
|
145
|
+
message: `Expected '${path}.generalPractitioner' to be an array.`,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
resource.generalPractitioner.forEach((gp, idx) => {
|
|
150
|
+
issues.push(...validateReference(gp, `${path}.generalPractitioner[${idx}]`, ["Organization", "Practitioner", "PractitionerRole"]));
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// managingOrganization (Reference)
|
|
155
|
+
if (resource.managingOrganization !== undefined) {
|
|
156
|
+
issues.push(...validateReference(resource.managingOrganization, `${path}.managingOrganization`, ["Organization"]));
|
|
157
|
+
}
|
|
158
|
+
// maritalStatus (CodeableConcept)
|
|
159
|
+
if (resource.maritalStatus !== undefined) {
|
|
160
|
+
issues.push(...validateCodeableConcept(resource.maritalStatus, `${path}.maritalStatus`));
|
|
161
|
+
}
|
|
162
|
+
return issues;
|
|
163
|
+
}
|
package/dist/coding.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ValidationIssue } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Validates the structural shape of a FHIR Coding element.
|
|
4
|
+
* Checks for non-empty string code and valid URI system format.
|
|
5
|
+
*/
|
|
6
|
+
export declare function validateCoding(coding: unknown, path: string, options?: {
|
|
7
|
+
requireSystem?: boolean;
|
|
8
|
+
requireCode?: boolean;
|
|
9
|
+
}): ValidationIssue[];
|
|
10
|
+
/**
|
|
11
|
+
* Validates the structural shape of a FHIR CodeableConcept element.
|
|
12
|
+
* Checks for coding array structure and validates individual Coding elements.
|
|
13
|
+
*/
|
|
14
|
+
export declare function validateCodeableConcept(concept: unknown, path: string, options?: {
|
|
15
|
+
requireCoding?: boolean;
|
|
16
|
+
minCodings?: number;
|
|
17
|
+
}): ValidationIssue[];
|
package/dist/coding.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates the structural shape of a FHIR Coding element.
|
|
3
|
+
* Checks for non-empty string code and valid URI system format.
|
|
4
|
+
*/
|
|
5
|
+
export function validateCoding(coding, path, options) {
|
|
6
|
+
const issues = [];
|
|
7
|
+
if (!coding || typeof coding !== "object" || Array.isArray(coding)) {
|
|
8
|
+
issues.push({
|
|
9
|
+
severity: "error",
|
|
10
|
+
path,
|
|
11
|
+
code: "invalid-structure",
|
|
12
|
+
message: `Expected '${path}' to be a Coding object, got ${Array.isArray(coding) ? "array" : typeof coding}.`,
|
|
13
|
+
});
|
|
14
|
+
return issues;
|
|
15
|
+
}
|
|
16
|
+
const c = coding;
|
|
17
|
+
const requireSystem = options?.requireSystem ?? true;
|
|
18
|
+
const requireCode = options?.requireCode ?? true;
|
|
19
|
+
if (requireCode) {
|
|
20
|
+
if (c.code === undefined || c.code === null || c.code === "") {
|
|
21
|
+
issues.push({
|
|
22
|
+
severity: "error",
|
|
23
|
+
path: `${path}.code`,
|
|
24
|
+
code: "required",
|
|
25
|
+
message: `Missing required 'code' in '${path}'.`,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
else if (typeof c.code !== "string") {
|
|
29
|
+
issues.push({
|
|
30
|
+
severity: "error",
|
|
31
|
+
path: `${path}.code`,
|
|
32
|
+
code: "invalid-type",
|
|
33
|
+
message: `Expected 'code' in '${path}' to be a string, got ${typeof c.code}.`,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else if (c.code !== undefined && typeof c.code !== "string") {
|
|
38
|
+
issues.push({
|
|
39
|
+
severity: "error",
|
|
40
|
+
path: `${path}.code`,
|
|
41
|
+
code: "invalid-type",
|
|
42
|
+
message: `Expected 'code' in '${path}' to be a string, got ${typeof c.code}.`,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
if (requireSystem) {
|
|
46
|
+
if (c.system === undefined || c.system === null || c.system === "") {
|
|
47
|
+
issues.push({
|
|
48
|
+
severity: "error",
|
|
49
|
+
path: `${path}.system`,
|
|
50
|
+
code: "required",
|
|
51
|
+
message: `Missing required 'system' URI in '${path}'.`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
else if (typeof c.system !== "string") {
|
|
55
|
+
issues.push({
|
|
56
|
+
severity: "error",
|
|
57
|
+
path: `${path}.system`,
|
|
58
|
+
code: "invalid-type",
|
|
59
|
+
message: `Expected 'system' in '${path}' to be a string URI, got ${typeof c.system}.`,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else if (c.system !== undefined && typeof c.system !== "string") {
|
|
64
|
+
issues.push({
|
|
65
|
+
severity: "error",
|
|
66
|
+
path: `${path}.system`,
|
|
67
|
+
code: "invalid-type",
|
|
68
|
+
message: `Expected 'system' in '${path}' to be a string URI, got ${typeof c.system}.`,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
if (c.display !== undefined && typeof c.display !== "string") {
|
|
72
|
+
issues.push({
|
|
73
|
+
severity: "error",
|
|
74
|
+
path: `${path}.display`,
|
|
75
|
+
code: "invalid-type",
|
|
76
|
+
message: `Expected 'display' in '${path}' to be a string, got ${typeof c.display}.`,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return issues;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Validates the structural shape of a FHIR CodeableConcept element.
|
|
83
|
+
* Checks for coding array structure and validates individual Coding elements.
|
|
84
|
+
*/
|
|
85
|
+
export function validateCodeableConcept(concept, path, options) {
|
|
86
|
+
const issues = [];
|
|
87
|
+
if (!concept || typeof concept !== "object" || Array.isArray(concept)) {
|
|
88
|
+
issues.push({
|
|
89
|
+
severity: "error",
|
|
90
|
+
path,
|
|
91
|
+
code: "invalid-structure",
|
|
92
|
+
message: `Expected '${path}' to be a CodeableConcept object, got ${Array.isArray(concept) ? "array" : typeof concept}.`,
|
|
93
|
+
});
|
|
94
|
+
return issues;
|
|
95
|
+
}
|
|
96
|
+
const cc = concept;
|
|
97
|
+
const requireCoding = options?.requireCoding ?? false;
|
|
98
|
+
const minCodings = options?.minCodings ?? (requireCoding ? 1 : 0);
|
|
99
|
+
if (cc.coding !== undefined) {
|
|
100
|
+
if (!Array.isArray(cc.coding)) {
|
|
101
|
+
issues.push({
|
|
102
|
+
severity: "error",
|
|
103
|
+
path: `${path}.coding`,
|
|
104
|
+
code: "invalid-type",
|
|
105
|
+
message: `Expected '${path}.coding' to be an array, got ${typeof cc.coding}.`,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
if (cc.coding.length < minCodings) {
|
|
110
|
+
issues.push({
|
|
111
|
+
severity: "error",
|
|
112
|
+
path: `${path}.coding`,
|
|
113
|
+
code: "cardinality",
|
|
114
|
+
message: `Expected at least ${minCodings} coding(s) in '${path}.coding', found ${cc.coding.length}.`,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
cc.coding.forEach((item, idx) => {
|
|
118
|
+
issues.push(...validateCoding(item, `${path}.coding[${idx}]`));
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else if (requireCoding || minCodings > 0) {
|
|
123
|
+
issues.push({
|
|
124
|
+
severity: "error",
|
|
125
|
+
path: `${path}.coding`,
|
|
126
|
+
code: "required",
|
|
127
|
+
message: `Missing required 'coding' array in '${path}'.`,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
if (cc.text !== undefined && typeof cc.text !== "string") {
|
|
131
|
+
issues.push({
|
|
132
|
+
severity: "error",
|
|
133
|
+
path: `${path}.text`,
|
|
134
|
+
code: "invalid-type",
|
|
135
|
+
message: `Expected 'text' in '${path}' to be a string, got ${typeof cc.text}.`,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return issues;
|
|
139
|
+
}
|