@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
package/README.md
CHANGED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { validateCodeableConcept } from "../coding.js";
|
|
2
|
+
import { validateReference } from "../reference-rules.js";
|
|
3
|
+
const VALID_CLAIM_RESPONSE_STATUSES = ["active", "cancelled", "draft", "entered-in-error"];
|
|
4
|
+
const VALID_CLAIM_RESPONSE_USES = ["claim", "preauthorization", "predetermination"];
|
|
5
|
+
const VALID_CLAIM_RESPONSE_OUTCOMES = ["queued", "complete", "error", "partial"];
|
|
6
|
+
/**
|
|
7
|
+
* Validates base structural rules for FHIR R4 ClaimResponse resource.
|
|
8
|
+
*/
|
|
9
|
+
export function validateClaimResponse(resource, path = "ClaimResponse") {
|
|
10
|
+
const issues = [];
|
|
11
|
+
if (resource.resourceType !== "ClaimResponse") {
|
|
12
|
+
issues.push({
|
|
13
|
+
severity: "error",
|
|
14
|
+
path: `${path}.resourceType`,
|
|
15
|
+
code: "invalid-resource-type",
|
|
16
|
+
message: `Expected resourceType 'ClaimResponse', found '${String(resource.resourceType)}'.`,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
// status (Required)
|
|
20
|
+
if (resource.status === undefined || resource.status === null || resource.status === "") {
|
|
21
|
+
issues.push({
|
|
22
|
+
severity: "error",
|
|
23
|
+
path: `${path}.status`,
|
|
24
|
+
code: "required",
|
|
25
|
+
message: `Missing required 'status' in '${path}'.`,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
else if (typeof resource.status !== "string" || !VALID_CLAIM_RESPONSE_STATUSES.includes(resource.status)) {
|
|
29
|
+
issues.push({
|
|
30
|
+
severity: "error",
|
|
31
|
+
path: `${path}.status`,
|
|
32
|
+
code: "invalid-value",
|
|
33
|
+
message: `Invalid ClaimResponse status '${String(resource.status)}'. Allowed values: ${VALID_CLAIM_RESPONSE_STATUSES.join(", ")}.`,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
// type (Required CodeableConcept)
|
|
37
|
+
if (resource.type === undefined || resource.type === null) {
|
|
38
|
+
issues.push({
|
|
39
|
+
severity: "error",
|
|
40
|
+
path: `${path}.type`,
|
|
41
|
+
code: "required",
|
|
42
|
+
message: `Missing required 'type' in '${path}'.`,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
issues.push(...validateCodeableConcept(resource.type, `${path}.type`));
|
|
47
|
+
}
|
|
48
|
+
// use (Required enum: claim | preauthorization | predetermination)
|
|
49
|
+
if (resource.use === undefined || resource.use === null || resource.use === "") {
|
|
50
|
+
issues.push({
|
|
51
|
+
severity: "error",
|
|
52
|
+
path: `${path}.use`,
|
|
53
|
+
code: "required",
|
|
54
|
+
message: `Missing required 'use' in '${path}'.`,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
else if (typeof resource.use !== "string" || !VALID_CLAIM_RESPONSE_USES.includes(resource.use)) {
|
|
58
|
+
issues.push({
|
|
59
|
+
severity: "error",
|
|
60
|
+
path: `${path}.use`,
|
|
61
|
+
code: "invalid-value",
|
|
62
|
+
message: `Invalid ClaimResponse use '${String(resource.use)}'. Allowed values: ${VALID_CLAIM_RESPONSE_USES.join(", ")}.`,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
// patient (Required Reference: Patient)
|
|
66
|
+
if (resource.patient === undefined || resource.patient === null) {
|
|
67
|
+
issues.push({
|
|
68
|
+
severity: "error",
|
|
69
|
+
path: `${path}.patient`,
|
|
70
|
+
code: "required",
|
|
71
|
+
message: `Missing required 'patient' in '${path}'.`,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
issues.push(...validateReference(resource.patient, `${path}.patient`, ["Patient"]));
|
|
76
|
+
}
|
|
77
|
+
// created (Required string dateTime)
|
|
78
|
+
if (resource.created === undefined || resource.created === null || resource.created === "") {
|
|
79
|
+
issues.push({
|
|
80
|
+
severity: "error",
|
|
81
|
+
path: `${path}.created`,
|
|
82
|
+
code: "required",
|
|
83
|
+
message: `Missing required 'created' in '${path}'.`,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
else if (typeof resource.created !== "string") {
|
|
87
|
+
issues.push({
|
|
88
|
+
severity: "error",
|
|
89
|
+
path: `${path}.created`,
|
|
90
|
+
code: "invalid-type",
|
|
91
|
+
message: `Expected '${path}.created' to be a string dateTime.`,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
// insurer (Required Reference: Organization)
|
|
95
|
+
if (resource.insurer === undefined || resource.insurer === null) {
|
|
96
|
+
issues.push({
|
|
97
|
+
severity: "error",
|
|
98
|
+
path: `${path}.insurer`,
|
|
99
|
+
code: "required",
|
|
100
|
+
message: `Missing required 'insurer' in '${path}'.`,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
issues.push(...validateReference(resource.insurer, `${path}.insurer`, ["Organization"]));
|
|
105
|
+
}
|
|
106
|
+
// outcome (Required enum: queued | complete | error | partial)
|
|
107
|
+
if (resource.outcome === undefined || resource.outcome === null || resource.outcome === "") {
|
|
108
|
+
issues.push({
|
|
109
|
+
severity: "error",
|
|
110
|
+
path: `${path}.outcome`,
|
|
111
|
+
code: "required",
|
|
112
|
+
message: `Missing required 'outcome' in '${path}'.`,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
else if (typeof resource.outcome !== "string" || !VALID_CLAIM_RESPONSE_OUTCOMES.includes(resource.outcome)) {
|
|
116
|
+
issues.push({
|
|
117
|
+
severity: "error",
|
|
118
|
+
path: `${path}.outcome`,
|
|
119
|
+
code: "invalid-value",
|
|
120
|
+
message: `Invalid ClaimResponse outcome '${String(resource.outcome)}'. Allowed values: ${VALID_CLAIM_RESPONSE_OUTCOMES.join(", ")}.`,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
// request (Reference: Claim)
|
|
124
|
+
if (resource.request !== undefined) {
|
|
125
|
+
issues.push(...validateReference(resource.request, `${path}.request`, ["Claim"]));
|
|
126
|
+
}
|
|
127
|
+
// requestor (Reference: Practitioner | PractitionerRole | Organization)
|
|
128
|
+
if (resource.requestor !== undefined) {
|
|
129
|
+
issues.push(...validateReference(resource.requestor, `${path}.requestor`, ["Practitioner", "PractitionerRole", "Organization"]));
|
|
130
|
+
}
|
|
131
|
+
// insurance (Array)
|
|
132
|
+
if (resource.insurance !== undefined) {
|
|
133
|
+
if (!Array.isArray(resource.insurance)) {
|
|
134
|
+
issues.push({
|
|
135
|
+
severity: "error",
|
|
136
|
+
path: `${path}.insurance`,
|
|
137
|
+
code: "invalid-type",
|
|
138
|
+
message: `Expected '${path}.insurance' to be an array.`,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
resource.insurance.forEach((ins, idx) => {
|
|
143
|
+
const insPath = `${path}.insurance[${idx}]`;
|
|
144
|
+
if (typeof ins !== "object" || ins === null || Array.isArray(ins)) {
|
|
145
|
+
issues.push({
|
|
146
|
+
severity: "error",
|
|
147
|
+
path: insPath,
|
|
148
|
+
code: "invalid-structure",
|
|
149
|
+
message: `Expected '${insPath}' to be an object.`,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
const obj = ins;
|
|
154
|
+
if (typeof obj.sequence !== "number") {
|
|
155
|
+
issues.push({
|
|
156
|
+
severity: "error",
|
|
157
|
+
path: `${insPath}.sequence`,
|
|
158
|
+
code: "required",
|
|
159
|
+
message: `Missing or invalid 'sequence' number in '${insPath}'.`,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (typeof obj.focal !== "boolean") {
|
|
163
|
+
issues.push({
|
|
164
|
+
severity: "error",
|
|
165
|
+
path: `${insPath}.focal`,
|
|
166
|
+
code: "required",
|
|
167
|
+
message: `Missing or invalid 'focal' boolean in '${insPath}'.`,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
if (obj.coverage === undefined || obj.coverage === null) {
|
|
171
|
+
issues.push({
|
|
172
|
+
severity: "error",
|
|
173
|
+
path: `${insPath}.coverage`,
|
|
174
|
+
code: "required",
|
|
175
|
+
message: `Missing required 'coverage' reference in '${insPath}'.`,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
issues.push(...validateReference(obj.coverage, `${insPath}.coverage`, ["Coverage"]));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return issues;
|
|
186
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { validateCodeableConcept } from "../coding.js";
|
|
2
|
+
import { validateReference } from "../reference-rules.js";
|
|
3
|
+
const VALID_CLAIM_STATUSES = ["active", "cancelled", "draft", "entered-in-error"];
|
|
4
|
+
const VALID_CLAIM_USES = ["claim", "preauthorization", "predetermination"];
|
|
5
|
+
/**
|
|
6
|
+
* Validates base structural rules for FHIR R4 Claim resource.
|
|
7
|
+
*/
|
|
8
|
+
export function validateClaim(resource, path = "Claim") {
|
|
9
|
+
const issues = [];
|
|
10
|
+
if (resource.resourceType !== "Claim") {
|
|
11
|
+
issues.push({
|
|
12
|
+
severity: "error",
|
|
13
|
+
path: `${path}.resourceType`,
|
|
14
|
+
code: "invalid-resource-type",
|
|
15
|
+
message: `Expected resourceType 'Claim', found '${String(resource.resourceType)}'.`,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
// status (Required)
|
|
19
|
+
if (resource.status === undefined || resource.status === null || resource.status === "") {
|
|
20
|
+
issues.push({
|
|
21
|
+
severity: "error",
|
|
22
|
+
path: `${path}.status`,
|
|
23
|
+
code: "required",
|
|
24
|
+
message: `Missing required 'status' in '${path}'.`,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
else if (typeof resource.status !== "string" || !VALID_CLAIM_STATUSES.includes(resource.status)) {
|
|
28
|
+
issues.push({
|
|
29
|
+
severity: "error",
|
|
30
|
+
path: `${path}.status`,
|
|
31
|
+
code: "invalid-value",
|
|
32
|
+
message: `Invalid Claim status '${String(resource.status)}'. Allowed values: ${VALID_CLAIM_STATUSES.join(", ")}.`,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
// type (Required CodeableConcept)
|
|
36
|
+
if (resource.type === undefined || resource.type === null) {
|
|
37
|
+
issues.push({
|
|
38
|
+
severity: "error",
|
|
39
|
+
path: `${path}.type`,
|
|
40
|
+
code: "required",
|
|
41
|
+
message: `Missing required 'type' in '${path}'.`,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
issues.push(...validateCodeableConcept(resource.type, `${path}.type`));
|
|
46
|
+
}
|
|
47
|
+
// use (Required enum: claim | preauthorization | predetermination)
|
|
48
|
+
if (resource.use === undefined || resource.use === null || resource.use === "") {
|
|
49
|
+
issues.push({
|
|
50
|
+
severity: "error",
|
|
51
|
+
path: `${path}.use`,
|
|
52
|
+
code: "required",
|
|
53
|
+
message: `Missing required 'use' in '${path}'.`,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
else if (typeof resource.use !== "string" || !VALID_CLAIM_USES.includes(resource.use)) {
|
|
57
|
+
issues.push({
|
|
58
|
+
severity: "error",
|
|
59
|
+
path: `${path}.use`,
|
|
60
|
+
code: "invalid-value",
|
|
61
|
+
message: `Invalid Claim use '${String(resource.use)}'. Allowed values: ${VALID_CLAIM_USES.join(", ")}.`,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
// patient (Required Reference: Patient)
|
|
65
|
+
if (resource.patient === undefined || resource.patient === null) {
|
|
66
|
+
issues.push({
|
|
67
|
+
severity: "error",
|
|
68
|
+
path: `${path}.patient`,
|
|
69
|
+
code: "required",
|
|
70
|
+
message: `Missing required 'patient' in '${path}'.`,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
issues.push(...validateReference(resource.patient, `${path}.patient`, ["Patient"]));
|
|
75
|
+
}
|
|
76
|
+
// created (Required string dateTime)
|
|
77
|
+
if (resource.created === undefined || resource.created === null || resource.created === "") {
|
|
78
|
+
issues.push({
|
|
79
|
+
severity: "error",
|
|
80
|
+
path: `${path}.created`,
|
|
81
|
+
code: "required",
|
|
82
|
+
message: `Missing required 'created' in '${path}'.`,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
else if (typeof resource.created !== "string") {
|
|
86
|
+
issues.push({
|
|
87
|
+
severity: "error",
|
|
88
|
+
path: `${path}.created`,
|
|
89
|
+
code: "invalid-type",
|
|
90
|
+
message: `Expected '${path}.created' to be a string dateTime.`,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
// provider (Required Reference: Practitioner | PractitionerRole | Organization)
|
|
94
|
+
if (resource.provider === undefined || resource.provider === null) {
|
|
95
|
+
issues.push({
|
|
96
|
+
severity: "error",
|
|
97
|
+
path: `${path}.provider`,
|
|
98
|
+
code: "required",
|
|
99
|
+
message: `Missing required 'provider' in '${path}'.`,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
issues.push(...validateReference(resource.provider, `${path}.provider`, ["Practitioner", "PractitionerRole", "Organization"]));
|
|
104
|
+
}
|
|
105
|
+
// priority (Required CodeableConcept)
|
|
106
|
+
if (resource.priority === undefined || resource.priority === null) {
|
|
107
|
+
issues.push({
|
|
108
|
+
severity: "error",
|
|
109
|
+
path: `${path}.priority`,
|
|
110
|
+
code: "required",
|
|
111
|
+
message: `Missing required 'priority' in '${path}'.`,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
issues.push(...validateCodeableConcept(resource.priority, `${path}.priority`));
|
|
116
|
+
}
|
|
117
|
+
// insurer (Reference: Organization)
|
|
118
|
+
if (resource.insurer !== undefined) {
|
|
119
|
+
issues.push(...validateReference(resource.insurer, `${path}.insurer`, ["Organization"]));
|
|
120
|
+
}
|
|
121
|
+
// facility (Reference: Location)
|
|
122
|
+
if (resource.facility !== undefined) {
|
|
123
|
+
issues.push(...validateReference(resource.facility, `${path}.facility`, ["Location"]));
|
|
124
|
+
}
|
|
125
|
+
// insurance (Required array, min 1)
|
|
126
|
+
if (resource.insurance === undefined || resource.insurance === null) {
|
|
127
|
+
issues.push({
|
|
128
|
+
severity: "error",
|
|
129
|
+
path: `${path}.insurance`,
|
|
130
|
+
code: "required",
|
|
131
|
+
message: `Missing required 'insurance' in '${path}'.`,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
else if (!Array.isArray(resource.insurance)) {
|
|
135
|
+
issues.push({
|
|
136
|
+
severity: "error",
|
|
137
|
+
path: `${path}.insurance`,
|
|
138
|
+
code: "invalid-type",
|
|
139
|
+
message: `Expected '${path}.insurance' to be an array, got ${typeof resource.insurance}.`,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
else if (resource.insurance.length === 0) {
|
|
143
|
+
issues.push({
|
|
144
|
+
severity: "error",
|
|
145
|
+
path: `${path}.insurance`,
|
|
146
|
+
code: "cardinality",
|
|
147
|
+
message: `Expected at least 1 insurance element in '${path}.insurance', found 0.`,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
resource.insurance.forEach((ins, idx) => {
|
|
152
|
+
const insPath = `${path}.insurance[${idx}]`;
|
|
153
|
+
if (typeof ins !== "object" || ins === null || Array.isArray(ins)) {
|
|
154
|
+
issues.push({
|
|
155
|
+
severity: "error",
|
|
156
|
+
path: insPath,
|
|
157
|
+
code: "invalid-structure",
|
|
158
|
+
message: `Expected '${insPath}' to be an object.`,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
const obj = ins;
|
|
163
|
+
if (typeof obj.sequence !== "number") {
|
|
164
|
+
issues.push({
|
|
165
|
+
severity: "error",
|
|
166
|
+
path: `${insPath}.sequence`,
|
|
167
|
+
code: "required",
|
|
168
|
+
message: `Missing or invalid 'sequence' number in '${insPath}'.`,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
if (typeof obj.focal !== "boolean") {
|
|
172
|
+
issues.push({
|
|
173
|
+
severity: "error",
|
|
174
|
+
path: `${insPath}.focal`,
|
|
175
|
+
code: "required",
|
|
176
|
+
message: `Missing or invalid 'focal' boolean in '${insPath}'.`,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (obj.coverage === undefined || obj.coverage === null) {
|
|
180
|
+
issues.push({
|
|
181
|
+
severity: "error",
|
|
182
|
+
path: `${insPath}.coverage`,
|
|
183
|
+
code: "required",
|
|
184
|
+
message: `Missing required 'coverage' reference in '${insPath}'.`,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
issues.push(...validateReference(obj.coverage, `${insPath}.coverage`, ["Coverage"]));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
// item (Array)
|
|
194
|
+
if (resource.item !== undefined) {
|
|
195
|
+
if (!Array.isArray(resource.item)) {
|
|
196
|
+
issues.push({
|
|
197
|
+
severity: "error",
|
|
198
|
+
path: `${path}.item`,
|
|
199
|
+
code: "invalid-type",
|
|
200
|
+
message: `Expected '${path}.item' to be an array, got ${typeof resource.item}.`,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
resource.item.forEach((it, idx) => {
|
|
205
|
+
const itPath = `${path}.item[${idx}]`;
|
|
206
|
+
if (typeof it !== "object" || it === null || Array.isArray(it)) {
|
|
207
|
+
issues.push({
|
|
208
|
+
severity: "error",
|
|
209
|
+
path: itPath,
|
|
210
|
+
code: "invalid-structure",
|
|
211
|
+
message: `Expected '${itPath}' to be an object.`,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
const itemObj = it;
|
|
216
|
+
if (typeof itemObj.sequence !== "number") {
|
|
217
|
+
issues.push({
|
|
218
|
+
severity: "error",
|
|
219
|
+
path: `${itPath}.sequence`,
|
|
220
|
+
code: "required",
|
|
221
|
+
message: `Missing or invalid 'sequence' number in '${itPath}'.`,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
if (itemObj.productOrService === undefined || itemObj.productOrService === null) {
|
|
225
|
+
issues.push({
|
|
226
|
+
severity: "error",
|
|
227
|
+
path: `${itPath}.productOrService`,
|
|
228
|
+
code: "required",
|
|
229
|
+
message: `Missing required 'productOrService' in '${itPath}'.`,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
issues.push(...validateCodeableConcept(itemObj.productOrService, `${itPath}.productOrService`));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return issues;
|
|
240
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { validateCodeableConcept } from "../coding.js";
|
|
2
|
+
import { validateReference } from "../reference-rules.js";
|
|
3
|
+
/**
|
|
4
|
+
* Validates base structural rules for FHIR R4 Condition resource.
|
|
5
|
+
*/
|
|
6
|
+
export function validateCondition(resource, path = "Condition") {
|
|
7
|
+
const issues = [];
|
|
8
|
+
if (resource.resourceType !== "Condition") {
|
|
9
|
+
issues.push({
|
|
10
|
+
severity: "error",
|
|
11
|
+
path: `${path}.resourceType`,
|
|
12
|
+
code: "invalid-resource-type",
|
|
13
|
+
message: `Expected resourceType 'Condition', found '${String(resource.resourceType)}'.`,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
// subject (Required Reference: Patient | Group)
|
|
17
|
+
if (resource.subject === undefined || resource.subject === null) {
|
|
18
|
+
issues.push({
|
|
19
|
+
severity: "error",
|
|
20
|
+
path: `${path}.subject`,
|
|
21
|
+
code: "required",
|
|
22
|
+
message: `Missing required 'subject' in '${path}'.`,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
issues.push(...validateReference(resource.subject, `${path}.subject`, ["Patient", "Group"]));
|
|
27
|
+
}
|
|
28
|
+
// clinicalStatus (CodeableConcept)
|
|
29
|
+
if (resource.clinicalStatus !== undefined) {
|
|
30
|
+
issues.push(...validateCodeableConcept(resource.clinicalStatus, `${path}.clinicalStatus`));
|
|
31
|
+
}
|
|
32
|
+
// verificationStatus (CodeableConcept)
|
|
33
|
+
if (resource.verificationStatus !== undefined) {
|
|
34
|
+
issues.push(...validateCodeableConcept(resource.verificationStatus, `${path}.verificationStatus`));
|
|
35
|
+
}
|
|
36
|
+
// category (CodeableConcept[])
|
|
37
|
+
if (resource.category !== undefined) {
|
|
38
|
+
if (!Array.isArray(resource.category)) {
|
|
39
|
+
issues.push({
|
|
40
|
+
severity: "error",
|
|
41
|
+
path: `${path}.category`,
|
|
42
|
+
code: "invalid-type",
|
|
43
|
+
message: `Expected '${path}.category' to be an array, got ${typeof resource.category}.`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
resource.category.forEach((cat, idx) => {
|
|
48
|
+
issues.push(...validateCodeableConcept(cat, `${path}.category[${idx}]`));
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// code (CodeableConcept)
|
|
53
|
+
if (resource.code !== undefined) {
|
|
54
|
+
issues.push(...validateCodeableConcept(resource.code, `${path}.code`));
|
|
55
|
+
}
|
|
56
|
+
// encounter (Reference: Encounter)
|
|
57
|
+
if (resource.encounter !== undefined) {
|
|
58
|
+
issues.push(...validateReference(resource.encounter, `${path}.encounter`, ["Encounter"]));
|
|
59
|
+
}
|
|
60
|
+
// recorder (Reference)
|
|
61
|
+
if (resource.recorder !== undefined) {
|
|
62
|
+
issues.push(...validateReference(resource.recorder, `${path}.recorder`, ["Practitioner", "PractitionerRole", "Patient", "RelatedPerson"]));
|
|
63
|
+
}
|
|
64
|
+
// asserter (Reference)
|
|
65
|
+
if (resource.asserter !== undefined) {
|
|
66
|
+
issues.push(...validateReference(resource.asserter, `${path}.asserter`, ["Practitioner", "PractitionerRole", "Patient", "RelatedPerson"]));
|
|
67
|
+
}
|
|
68
|
+
// recordedDate
|
|
69
|
+
if (resource.recordedDate !== undefined && typeof resource.recordedDate !== "string") {
|
|
70
|
+
issues.push({
|
|
71
|
+
severity: "error",
|
|
72
|
+
path: `${path}.recordedDate`,
|
|
73
|
+
code: "invalid-type",
|
|
74
|
+
message: `Expected '${path}.recordedDate' to be a string.`,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return issues;
|
|
78
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { validateCodeableConcept } from "../coding.js";
|
|
2
|
+
import { validateReference } from "../reference-rules.js";
|
|
3
|
+
const VALID_COVERAGE_STATUSES = ["active", "cancelled", "draft", "entered-in-error"];
|
|
4
|
+
/**
|
|
5
|
+
* Validates base structural rules for FHIR R4 Coverage resource.
|
|
6
|
+
*/
|
|
7
|
+
export function validateCoverage(resource, path = "Coverage") {
|
|
8
|
+
const issues = [];
|
|
9
|
+
if (resource.resourceType !== "Coverage") {
|
|
10
|
+
issues.push({
|
|
11
|
+
severity: "error",
|
|
12
|
+
path: `${path}.resourceType`,
|
|
13
|
+
code: "invalid-resource-type",
|
|
14
|
+
message: `Expected resourceType 'Coverage', found '${String(resource.resourceType)}'.`,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
// status (Required)
|
|
18
|
+
if (resource.status === undefined || resource.status === null || resource.status === "") {
|
|
19
|
+
issues.push({
|
|
20
|
+
severity: "error",
|
|
21
|
+
path: `${path}.status`,
|
|
22
|
+
code: "required",
|
|
23
|
+
message: `Missing required 'status' in '${path}'.`,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
else if (typeof resource.status !== "string" || !VALID_COVERAGE_STATUSES.includes(resource.status)) {
|
|
27
|
+
issues.push({
|
|
28
|
+
severity: "error",
|
|
29
|
+
path: `${path}.status`,
|
|
30
|
+
code: "invalid-value",
|
|
31
|
+
message: `Invalid Coverage status '${String(resource.status)}'. Allowed values: ${VALID_COVERAGE_STATUSES.join(", ")}.`,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
// beneficiary (Required Reference: Patient)
|
|
35
|
+
if (resource.beneficiary === undefined || resource.beneficiary === null) {
|
|
36
|
+
issues.push({
|
|
37
|
+
severity: "error",
|
|
38
|
+
path: `${path}.beneficiary`,
|
|
39
|
+
code: "required",
|
|
40
|
+
message: `Missing required 'beneficiary' in '${path}'.`,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
issues.push(...validateReference(resource.beneficiary, `${path}.beneficiary`, ["Patient"]));
|
|
45
|
+
}
|
|
46
|
+
// payor (Required Reference[]: Organization | Patient | RelatedPerson, min 1)
|
|
47
|
+
if (resource.payor === undefined || resource.payor === null) {
|
|
48
|
+
issues.push({
|
|
49
|
+
severity: "error",
|
|
50
|
+
path: `${path}.payor`,
|
|
51
|
+
code: "required",
|
|
52
|
+
message: `Missing required 'payor' in '${path}'.`,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
else if (!Array.isArray(resource.payor)) {
|
|
56
|
+
issues.push({
|
|
57
|
+
severity: "error",
|
|
58
|
+
path: `${path}.payor`,
|
|
59
|
+
code: "invalid-type",
|
|
60
|
+
message: `Expected '${path}.payor' to be an array, got ${typeof resource.payor}.`,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
else if (resource.payor.length === 0) {
|
|
64
|
+
issues.push({
|
|
65
|
+
severity: "error",
|
|
66
|
+
path: `${path}.payor`,
|
|
67
|
+
code: "cardinality",
|
|
68
|
+
message: `Expected at least 1 payor in '${path}.payor', found 0.`,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
resource.payor.forEach((p, idx) => {
|
|
73
|
+
issues.push(...validateReference(p, `${path}.payor[${idx}]`, ["Organization", "Patient", "RelatedPerson"]));
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
// subscriber (Reference: Patient | RelatedPerson)
|
|
77
|
+
if (resource.subscriber !== undefined) {
|
|
78
|
+
issues.push(...validateReference(resource.subscriber, `${path}.subscriber`, ["Patient", "RelatedPerson"]));
|
|
79
|
+
}
|
|
80
|
+
// subscriberId (string)
|
|
81
|
+
if (resource.subscriberId !== undefined && typeof resource.subscriberId !== "string") {
|
|
82
|
+
issues.push({
|
|
83
|
+
severity: "error",
|
|
84
|
+
path: `${path}.subscriberId`,
|
|
85
|
+
code: "invalid-type",
|
|
86
|
+
message: `Expected '${path}.subscriberId' to be a string.`,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// type (CodeableConcept)
|
|
90
|
+
if (resource.type !== undefined) {
|
|
91
|
+
issues.push(...validateCodeableConcept(resource.type, `${path}.type`));
|
|
92
|
+
}
|
|
93
|
+
// relationship (CodeableConcept)
|
|
94
|
+
if (resource.relationship !== undefined) {
|
|
95
|
+
issues.push(...validateCodeableConcept(resource.relationship, `${path}.relationship`));
|
|
96
|
+
}
|
|
97
|
+
// period (Period)
|
|
98
|
+
if (resource.period !== undefined) {
|
|
99
|
+
if (typeof resource.period !== "object" || resource.period === null || Array.isArray(resource.period)) {
|
|
100
|
+
issues.push({
|
|
101
|
+
severity: "error",
|
|
102
|
+
path: `${path}.period`,
|
|
103
|
+
code: "invalid-structure",
|
|
104
|
+
message: `Expected '${path}.period' to be an object.`,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return issues;
|
|
109
|
+
}
|