@fuego-systems/core 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constants.d.ts +89 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +97 -0
- package/dist/constants.js.map +1 -0
- package/dist/db/types.d.ts +49 -0
- package/dist/db/types.d.ts.map +1 -0
- package/dist/db/types.js +7 -0
- package/dist/db/types.js.map +1 -0
- package/dist/fhir/helpers.d.ts +4 -0
- package/dist/fhir/helpers.d.ts.map +1 -0
- package/dist/fhir/helpers.js +7 -0
- package/dist/fhir/helpers.js.map +1 -0
- package/dist/fhir/index.d.ts +3 -0
- package/dist/fhir/index.d.ts.map +1 -0
- package/dist/fhir/index.js +19 -0
- package/dist/fhir/index.js.map +1 -0
- package/dist/fhir/transaction-utils.d.ts +22 -0
- package/dist/fhir/transaction-utils.d.ts.map +1 -0
- package/dist/fhir/transaction-utils.js +156 -0
- package/dist/fhir/transaction-utils.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/scheduling/appointment.d.ts +15 -0
- package/dist/scheduling/appointment.d.ts.map +1 -0
- package/dist/scheduling/appointment.js +338 -0
- package/dist/scheduling/appointment.js.map +1 -0
- package/dist/scheduling/extension.d.ts +58 -0
- package/dist/scheduling/extension.d.ts.map +1 -0
- package/dist/scheduling/extension.js +3 -0
- package/dist/scheduling/extension.js.map +1 -0
- package/dist/scheduling/slot.d.ts +15 -0
- package/dist/scheduling/slot.d.ts.map +1 -0
- package/dist/scheduling/slot.js +291 -0
- package/dist/scheduling/slot.js.map +1 -0
- package/dist/scheduling/time-span.d.ts +26 -0
- package/dist/scheduling/time-span.d.ts.map +1 -0
- package/dist/scheduling/time-span.js +143 -0
- package/dist/scheduling/time-span.js.map +1 -0
- package/dist/search/alias-builder.d.ts +23 -0
- package/dist/search/alias-builder.d.ts.map +1 -0
- package/dist/search/alias-builder.js +126 -0
- package/dist/search/alias-builder.js.map +1 -0
- package/dist/search/normalizer.d.ts +25 -0
- package/dist/search/normalizer.d.ts.map +1 -0
- package/dist/search/normalizer.js +81 -0
- package/dist/search/normalizer.js.map +1 -0
- package/dist/test/factories.d.ts +10 -0
- package/dist/test/factories.d.ts.map +1 -0
- package/dist/test/factories.js +68 -0
- package/dist/test/factories.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +33 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findAvailableSlots = findAvailableSlots;
|
|
4
|
+
exports.isExtension = isExtension;
|
|
5
|
+
exports.isResourceReference = isResourceReference;
|
|
6
|
+
exports.codeableConceptEqual = codeableConceptEqual;
|
|
7
|
+
const core_1 = require("@medplum/core");
|
|
8
|
+
const date_fns_1 = require("date-fns");
|
|
9
|
+
const utc_1 = require("@date-fns/utc");
|
|
10
|
+
const time_span_1 = require("./time-span");
|
|
11
|
+
const constants_1 = require("../constants");
|
|
12
|
+
class Procedure {
|
|
13
|
+
constructor(activityDefintion) {
|
|
14
|
+
this.activityDefintion = activityDefintion;
|
|
15
|
+
this.extractShedulingParameters(activityDefintion.extension);
|
|
16
|
+
}
|
|
17
|
+
get serviceType() {
|
|
18
|
+
return this.activityDefintion.code || {};
|
|
19
|
+
}
|
|
20
|
+
get duration() {
|
|
21
|
+
var _a;
|
|
22
|
+
let duration = durationToMilliseconds((_a = this.activityDefintion.timingDuration) !== null && _a !== void 0 ? _a : {});
|
|
23
|
+
let buffer = 0;
|
|
24
|
+
for (const parameter of this._schedulingParameters()) {
|
|
25
|
+
if (parameter.url === 'duration') {
|
|
26
|
+
duration = durationToMilliseconds(parameter.valueDuration);
|
|
27
|
+
}
|
|
28
|
+
else if (parameter.url === 'bufferBefore') {
|
|
29
|
+
buffer += durationToMilliseconds(parameter.valueDuration);
|
|
30
|
+
}
|
|
31
|
+
else if (parameter.url === 'bufferAfter') {
|
|
32
|
+
buffer += durationToMilliseconds(parameter.valueDuration);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return duration + buffer;
|
|
36
|
+
}
|
|
37
|
+
get alignment() {
|
|
38
|
+
let interval = 0;
|
|
39
|
+
let offset = 0;
|
|
40
|
+
for (const parameter of this._schedulingParameters()) {
|
|
41
|
+
if (parameter.url === 'alignmentInterval') {
|
|
42
|
+
interval = durationToMilliseconds(parameter.valueDuration);
|
|
43
|
+
}
|
|
44
|
+
else if (parameter.url === 'alignmentOffset') {
|
|
45
|
+
offset = durationToMilliseconds(parameter.valueDuration);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (interval <= 0) {
|
|
49
|
+
interval = 1;
|
|
50
|
+
}
|
|
51
|
+
return { interval, offset };
|
|
52
|
+
}
|
|
53
|
+
*availability(start, end) {
|
|
54
|
+
for (const parameter of this._schedulingParameters()) {
|
|
55
|
+
if (parameter.url === 'availability') {
|
|
56
|
+
yield* iterateTimeSpans(parameter.valueTiming, start, end);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
extractShedulingParameters(extensions) {
|
|
61
|
+
if (!extensions) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
let candidate;
|
|
65
|
+
for (const extension of extensions) {
|
|
66
|
+
if (isExtension(constants_1.SCHEDULING_PARAMETERS_EXTENSION_URL, extension)) {
|
|
67
|
+
const serviceTypeParameter = extension.extension.find(parameter => parameter.url === 'serviceType');
|
|
68
|
+
if (!serviceTypeParameter) {
|
|
69
|
+
if (!candidate) {
|
|
70
|
+
candidate = extension;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else if (codeableConceptEqual(this.serviceType, serviceTypeParameter.valueCodeableConcept)) {
|
|
74
|
+
candidate = extension;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (candidate) {
|
|
79
|
+
this.schedulingParameters = candidate;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
*_schedulingParameters() {
|
|
83
|
+
if (this.schedulingParameters) {
|
|
84
|
+
for (const parameter of this.schedulingParameters.extension) {
|
|
85
|
+
yield parameter;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function findAvailableSlots(medplum, options) {
|
|
91
|
+
const mainProcedure = new Procedure(options.mainActivityDefinition);
|
|
92
|
+
const procedures = [mainProcedure, ...options.addonActivityDefinitions.map(activity => new Procedure(activity))];
|
|
93
|
+
const start = new utc_1.UTCDate(options.start);
|
|
94
|
+
const end = options.end && new utc_1.UTCDate(options.end);
|
|
95
|
+
const result = [];
|
|
96
|
+
for (const device of options.devices) {
|
|
97
|
+
const schedules = await medplum.searchResources('Schedule', { active: true, actor: (0, core_1.getReferenceString)(device) });
|
|
98
|
+
const deviceSchedule = schedules.find(schedule => schedule.actor.length === 1);
|
|
99
|
+
if (!deviceSchedule) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
for (const procedure of procedures) {
|
|
103
|
+
procedure.extractShedulingParameters(deviceSchedule.extension);
|
|
104
|
+
}
|
|
105
|
+
const [practitionerDeviceSchedule, practitionerReference] = findScheduleWithActor('Practitioner', schedules);
|
|
106
|
+
if (practitionerDeviceSchedule && practitionerReference) {
|
|
107
|
+
for await (const page of medplum.searchResourcePages('Schedule', {
|
|
108
|
+
active: true,
|
|
109
|
+
actor: (0, core_1.getReferenceString)(practitionerReference)
|
|
110
|
+
})) {
|
|
111
|
+
for (const practitionerSchedule of page) {
|
|
112
|
+
if (practitionerSchedule.actor.length === 1) {
|
|
113
|
+
for (const procedure of procedures) {
|
|
114
|
+
procedure.extractShedulingParameters(practitionerSchedule.extension);
|
|
115
|
+
}
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
for (const procedure of procedures) {
|
|
121
|
+
procedure.extractShedulingParameters(practitionerDeviceSchedule.extension);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const duration = procedures.reduce((acc, procedure) => acc + procedure.duration, 0);
|
|
125
|
+
if (!duration) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
let availability = new time_span_1.TimeSpanArray();
|
|
129
|
+
for (const span of mainProcedure.availability(start, end)) {
|
|
130
|
+
availability.add(span);
|
|
131
|
+
}
|
|
132
|
+
availability = availability.consolidate();
|
|
133
|
+
const timeSpans = [];
|
|
134
|
+
const query = {
|
|
135
|
+
_sort: 'start',
|
|
136
|
+
start: 'le' + (0, date_fns_1.formatISO)((0, date_fns_1.endOfDay)(end !== null && end !== void 0 ? end : start)),
|
|
137
|
+
end: 'ge' + (0, date_fns_1.formatISO)((0, date_fns_1.startOfDay)(start)),
|
|
138
|
+
'schedule.active': true,
|
|
139
|
+
'schedule.actor': [
|
|
140
|
+
(0, core_1.getReferenceString)(device),
|
|
141
|
+
...(practitionerDeviceSchedule && practitionerReference ? [(0, core_1.getReferenceString)(practitionerReference)] : [])
|
|
142
|
+
].join(','),
|
|
143
|
+
status: 'busy,busy-unavailable,busy-tentative'
|
|
144
|
+
};
|
|
145
|
+
for await (const page of medplum.searchResourcePages('Slot', query)) {
|
|
146
|
+
for (const slot of page) {
|
|
147
|
+
const start = (0, date_fns_1.parseISO)(slot.start);
|
|
148
|
+
const end = (0, date_fns_1.parseISO)(slot.end);
|
|
149
|
+
timeSpans.push(new time_span_1.TimeSpan(start, end));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const reserved = new time_span_1.TimeSpanArray(timeSpans);
|
|
153
|
+
reserved.consolidate();
|
|
154
|
+
availability = availability.subtract(reserved);
|
|
155
|
+
for (const span of availability) {
|
|
156
|
+
for (const part of span.partition(duration, false, mainProcedure.alignment)) {
|
|
157
|
+
result.push({
|
|
158
|
+
resource: {
|
|
159
|
+
resourceType: 'Slot',
|
|
160
|
+
status: 'free',
|
|
161
|
+
start: part.start.toISOString(),
|
|
162
|
+
end: part.end.toISOString(),
|
|
163
|
+
schedule: practitionerDeviceSchedule
|
|
164
|
+
? (0, core_1.createReference)(practitionerDeviceSchedule)
|
|
165
|
+
: (0, core_1.createReference)(deviceSchedule)
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
resourceType: 'Bundle',
|
|
173
|
+
type: 'searchset',
|
|
174
|
+
entry: result
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function isExtension(url, extension) {
|
|
178
|
+
return extension.url === url;
|
|
179
|
+
}
|
|
180
|
+
function isResourceReference(resourceType, reference) {
|
|
181
|
+
var _a;
|
|
182
|
+
return ((_a = reference.reference) === null || _a === void 0 ? void 0 : _a.startsWith(resourceType + '/')) || false;
|
|
183
|
+
}
|
|
184
|
+
function codeableConceptEqual(a, b) {
|
|
185
|
+
if (!a.coding || !b.coding) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
return a.coding.some(coding => {
|
|
189
|
+
var _a;
|
|
190
|
+
return (_a = b.coding) === null || _a === void 0 ? void 0 : _a.some(other => other.system === coding.system && other.code === coding.code);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
function findScheduleWithActor(resourceType, schedules) {
|
|
194
|
+
for (const schedule of schedules) {
|
|
195
|
+
const actor = schedule.actor.find(actor => isResourceReference(resourceType, actor));
|
|
196
|
+
if (actor) {
|
|
197
|
+
return [schedule, actor];
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return [undefined, undefined];
|
|
201
|
+
}
|
|
202
|
+
function* iterateTimeSpans(timing, start, end) {
|
|
203
|
+
var _a, _b, _c, _d, _e;
|
|
204
|
+
if (!((_a = timing.repeat) === null || _a === void 0 ? void 0 : _a.timeOfDay) || !((_b = timing.repeat) === null || _b === void 0 ? void 0 : _b.duration) || !((_c = timing.repeat) === null || _c === void 0 ? void 0 : _c.durationUnit)) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (!end) {
|
|
208
|
+
end = start;
|
|
209
|
+
}
|
|
210
|
+
const duration = (_d = timing.repeat.durationMax) !== null && _d !== void 0 ? _d : timing.repeat.duration;
|
|
211
|
+
const bounds = timing.repeat.boundsPeriod;
|
|
212
|
+
if (bounds) {
|
|
213
|
+
const boundInterval = {
|
|
214
|
+
start: bounds.start ? (0, date_fns_1.parseISO)(bounds.start) : start,
|
|
215
|
+
end: bounds.end ? (0, date_fns_1.parseISO)(bounds.end) : end
|
|
216
|
+
};
|
|
217
|
+
start = (0, date_fns_1.clamp)(start, boundInterval);
|
|
218
|
+
end = (0, date_fns_1.clamp)(end, boundInterval);
|
|
219
|
+
}
|
|
220
|
+
for (let current = start; (0, date_fns_1.isBefore)(current, end) || (0, date_fns_1.isEqual)(current, end); current = (0, date_fns_1.addDays)(current, 1)) {
|
|
221
|
+
const dow = dayOfWeek(current);
|
|
222
|
+
if (!((_e = timing.repeat.dayOfWeek) === null || _e === void 0 ? void 0 : _e.includes(dow))) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
for (const tod of timing.repeat.timeOfDay) {
|
|
226
|
+
const openingDate = parseTime(tod, current);
|
|
227
|
+
let closingDate = openingDate;
|
|
228
|
+
switch (timing.repeat.durationUnit) {
|
|
229
|
+
case 's':
|
|
230
|
+
closingDate = (0, date_fns_1.addSeconds)(openingDate, duration);
|
|
231
|
+
break;
|
|
232
|
+
case 'min':
|
|
233
|
+
closingDate = (0, date_fns_1.addMinutes)(openingDate, duration);
|
|
234
|
+
break;
|
|
235
|
+
case 'h':
|
|
236
|
+
closingDate = (0, date_fns_1.addHours)(openingDate, duration);
|
|
237
|
+
break;
|
|
238
|
+
case 'd':
|
|
239
|
+
closingDate = (0, date_fns_1.addDays)(openingDate, duration);
|
|
240
|
+
break;
|
|
241
|
+
case 'wk':
|
|
242
|
+
closingDate = (0, date_fns_1.addWeeks)(openingDate, duration);
|
|
243
|
+
break;
|
|
244
|
+
case 'mo':
|
|
245
|
+
closingDate = (0, date_fns_1.addMonths)(openingDate, duration);
|
|
246
|
+
break;
|
|
247
|
+
case 'a':
|
|
248
|
+
closingDate = (0, date_fns_1.addYears)(openingDate, duration);
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
if ((0, date_fns_1.isSameDay)(openingDate, closingDate) && (0, date_fns_1.isBefore)(openingDate, closingDate)) {
|
|
252
|
+
yield new time_span_1.TimeSpan(openingDate, closingDate);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function durationToMilliseconds(duration) {
|
|
258
|
+
if (!isUcumUnit(duration.unit, duration.system)) {
|
|
259
|
+
return 0;
|
|
260
|
+
}
|
|
261
|
+
let value = duration.value || 0;
|
|
262
|
+
/* eslint-disable no-fallthrough */
|
|
263
|
+
switch (duration.unit) {
|
|
264
|
+
case 'a':
|
|
265
|
+
value *= 365;
|
|
266
|
+
case 'mo':
|
|
267
|
+
value *= 30;
|
|
268
|
+
case 'wk':
|
|
269
|
+
value *= 7;
|
|
270
|
+
case 'd':
|
|
271
|
+
value *= 24;
|
|
272
|
+
case 'h':
|
|
273
|
+
value *= 60;
|
|
274
|
+
case 'min':
|
|
275
|
+
value *= 60;
|
|
276
|
+
case 's':
|
|
277
|
+
value *= 1000;
|
|
278
|
+
}
|
|
279
|
+
/* eslint-enable no-fallthrough */
|
|
280
|
+
return value;
|
|
281
|
+
}
|
|
282
|
+
function isUcumUnit(unit, system) {
|
|
283
|
+
return !!unit && (!system || system === core_1.UCUM);
|
|
284
|
+
}
|
|
285
|
+
function dayOfWeek(date) {
|
|
286
|
+
return (0, date_fns_1.format)(date, 'iii').toLowerCase();
|
|
287
|
+
}
|
|
288
|
+
function parseTime(time, referenceDate) {
|
|
289
|
+
return (0, date_fns_1.parse)(time, 'HH:mm:ss', referenceDate);
|
|
290
|
+
}
|
|
291
|
+
//# sourceMappingURL=slot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slot.js","sourceRoot":"","sources":["../../src/scheduling/slot.ts"],"names":[],"mappings":"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","sourcesContent":["import { createReference, getReferenceString, MedplumClient, UCUM } from '@medplum/core'\nimport {\n ActivityDefinition,\n Bundle,\n BundleEntry,\n CodeableConcept,\n Device,\n Duration,\n Extension,\n ExtractResource,\n Reference,\n ResourceType,\n Schedule,\n Slot,\n Timing\n} from '@medplum/fhirtypes'\nimport {\n addDays,\n addHours,\n addMinutes,\n addMonths,\n addSeconds,\n addWeeks,\n addYears,\n clamp,\n endOfDay,\n format,\n formatISO,\n isBefore,\n isEqual,\n isSameDay,\n parse,\n parseISO,\n startOfDay\n} from 'date-fns'\nimport { UTCDate } from '@date-fns/utc'\nimport { TimeSpan, TimeSpanArray } from './time-span'\nimport { SCHEDULING_PARAMETERS_EXTENSION_URL } from '../constants'\nimport { ExtensionUrl, ExtractExtension, SchedulingParameter, SchedulingParametersExtension } from './extension'\n\nexport interface FindAvailableSlotsArguments {\n mainActivityDefinition: ActivityDefinition\n addonActivityDefinitions: ActivityDefinition[]\n devices: Device[]\n start: Date\n end?: Date\n}\n\nclass Procedure {\n activityDefintion: ActivityDefinition\n schedulingParameters?: SchedulingParametersExtension\n\n constructor (activityDefintion: ActivityDefinition) {\n this.activityDefintion = activityDefintion\n this.extractShedulingParameters(activityDefintion.extension)\n }\n\n get serviceType (): CodeableConcept {\n return this.activityDefintion.code || {}\n }\n\n get duration (): number {\n let duration = durationToMilliseconds(this.activityDefintion.timingDuration ?? {})\n let buffer = 0\n\n for (const parameter of this._schedulingParameters()) {\n if (parameter.url === 'duration') {\n duration = durationToMilliseconds(parameter.valueDuration)\n } else if (parameter.url === 'bufferBefore') {\n buffer += durationToMilliseconds(parameter.valueDuration)\n } else if (parameter.url === 'bufferAfter') {\n buffer += durationToMilliseconds(parameter.valueDuration)\n }\n }\n\n return duration + buffer\n }\n\n get alignment (): { interval: number, offset: number } {\n let interval = 0\n let offset = 0\n\n for (const parameter of this._schedulingParameters()) {\n if (parameter.url === 'alignmentInterval') {\n interval = durationToMilliseconds(parameter.valueDuration)\n } else if (parameter.url === 'alignmentOffset') {\n offset = durationToMilliseconds(parameter.valueDuration)\n }\n }\n\n if (interval <= 0) {\n interval = 1\n }\n\n return { interval, offset }\n }\n\n * availability (start: Date, end?: Date): Generator<TimeSpan> {\n for (const parameter of this._schedulingParameters()) {\n if (parameter.url === 'availability') {\n yield * iterateTimeSpans(parameter.valueTiming, start, end)\n }\n }\n }\n\n extractShedulingParameters (extensions?: Extension[]): void {\n if (!extensions) {\n return\n }\n\n let candidate: SchedulingParametersExtension | undefined\n\n for (const extension of extensions) {\n if (isExtension(SCHEDULING_PARAMETERS_EXTENSION_URL, extension)) {\n const serviceTypeParameter = extension.extension.find(parameter => parameter.url === 'serviceType')\n\n if (!serviceTypeParameter) {\n if (!candidate) {\n candidate = extension\n }\n } else if (codeableConceptEqual(this.serviceType, serviceTypeParameter.valueCodeableConcept)) {\n candidate = extension\n }\n }\n }\n\n if (candidate) {\n this.schedulingParameters = candidate\n }\n }\n\n * _schedulingParameters (): Generator<SchedulingParameter> {\n if (this.schedulingParameters) {\n for (const parameter of this.schedulingParameters.extension) {\n yield parameter\n }\n }\n }\n}\n\nexport async function findAvailableSlots (medplum: MedplumClient, options: FindAvailableSlotsArguments): Promise<Bundle<Slot>> {\n const mainProcedure = new Procedure(options.mainActivityDefinition)\n const procedures = [mainProcedure, ...options.addonActivityDefinitions.map(activity => new Procedure(activity))]\n const start = new UTCDate(options.start)\n const end = options.end && new UTCDate(options.end)\n\n const result: BundleEntry<Slot>[] = []\n\n for (const device of options.devices) {\n const schedules = await medplum.searchResources('Schedule', { active: true, actor: getReferenceString(device) })\n const deviceSchedule = schedules.find(schedule => schedule.actor.length === 1)\n\n if (!deviceSchedule) {\n continue\n }\n\n for (const procedure of procedures) {\n procedure.extractShedulingParameters(deviceSchedule.extension)\n }\n\n const [practitionerDeviceSchedule, practitionerReference] = findScheduleWithActor('Practitioner', schedules)\n\n if (practitionerDeviceSchedule && practitionerReference) {\n for await (const page of medplum.searchResourcePages('Schedule', {\n active: true,\n actor: getReferenceString(practitionerReference)\n })) {\n for (const practitionerSchedule of page) {\n if (practitionerSchedule.actor.length === 1) {\n for (const procedure of procedures) {\n procedure.extractShedulingParameters(practitionerSchedule.extension)\n }\n\n break\n }\n }\n }\n\n for (const procedure of procedures) {\n procedure.extractShedulingParameters(practitionerDeviceSchedule.extension)\n }\n }\n\n const duration = procedures.reduce((acc, procedure) => acc + procedure.duration, 0)\n\n if (!duration) {\n continue\n }\n\n let availability = new TimeSpanArray()\n\n for (const span of mainProcedure.availability(start, end)) {\n availability.add(span)\n }\n\n availability = availability.consolidate()\n const timeSpans: TimeSpan[] = []\n\n const query = {\n _sort: 'start',\n start: 'le' + formatISO(endOfDay(end ?? start)),\n end: 'ge' + formatISO(startOfDay(start)),\n 'schedule.active': true,\n 'schedule.actor': [\n getReferenceString(device),\n ...(practitionerDeviceSchedule && practitionerReference ? [getReferenceString(practitionerReference)] : [])].join(','),\n status: 'busy,busy-unavailable,busy-tentative'\n }\n\n for await (const page of medplum.searchResourcePages('Slot', query)) {\n for (const slot of page) {\n const start = parseISO(slot.start)\n const end = parseISO(slot.end)\n timeSpans.push(new TimeSpan(start, end))\n }\n }\n\n const reserved = new TimeSpanArray(timeSpans)\n reserved.consolidate()\n availability = availability.subtract(reserved)\n\n for (const span of availability) {\n for (const part of span.partition(duration, false, mainProcedure.alignment)) {\n result.push({\n resource: {\n resourceType: 'Slot',\n status: 'free',\n start: part.start.toISOString(),\n end: part.end.toISOString(),\n schedule: practitionerDeviceSchedule\n ? createReference(practitionerDeviceSchedule as Schedule)\n : createReference(deviceSchedule)\n }\n })\n }\n }\n }\n\n return {\n resourceType: 'Bundle',\n type: 'searchset',\n entry: result\n }\n}\n\nexport function isExtension<U extends ExtensionUrl> (url: U, extension: Extension): extension is ExtractExtension<U> {\n return extension.url === url\n}\n\nexport function isResourceReference<RT extends ResourceType> (\n resourceType: RT,\n reference: Reference): reference is Reference<ExtractResource<RT>> {\n return reference.reference?.startsWith(resourceType + '/') || false\n}\n\nexport function codeableConceptEqual (a: CodeableConcept, b: CodeableConcept): boolean {\n if (!a.coding || !b.coding) {\n return false\n }\n\n return a.coding.some(\n coding => b.coding?.some(\n other => other.system === coding.system && other.code === coding.code))\n}\n\nfunction findScheduleWithActor<RT extends ResourceType, T extends Schedule> (resourceType: RT, schedules: T[]): [T, Reference<ExtractResource<RT>>] | [undefined, undefined] {\n for (const schedule of schedules) {\n const actor = schedule.actor.find(actor => isResourceReference(resourceType, actor))\n\n if (actor) {\n return [schedule, actor]\n }\n }\n\n return [undefined, undefined]\n}\n\nfunction * iterateTimeSpans (timing: Timing, start: Date, end?: Date): Generator<TimeSpan> {\n if (!timing.repeat?.timeOfDay || !timing.repeat?.duration || !timing.repeat?.durationUnit) {\n return\n }\n\n if (!end) {\n end = start\n }\n\n const duration = timing.repeat.durationMax ?? timing.repeat.duration\n const bounds = timing.repeat.boundsPeriod\n\n if (bounds) {\n const boundInterval = {\n start: bounds.start ? parseISO(bounds.start) : start,\n end: bounds.end ? parseISO(bounds.end) : end\n }\n\n start = clamp(start, boundInterval)\n end = clamp(end, boundInterval)\n }\n\n for (let current = start; isBefore(current, end) || isEqual(current, end); current = addDays(current, 1)) {\n const dow = dayOfWeek(current)\n\n if (!timing.repeat.dayOfWeek?.includes(dow)) {\n continue\n }\n\n for (const tod of timing.repeat.timeOfDay) {\n const openingDate = parseTime(tod, current)\n let closingDate = openingDate\n\n switch (timing.repeat.durationUnit) {\n case 's':\n closingDate = addSeconds(openingDate, duration)\n break\n case 'min':\n closingDate = addMinutes(openingDate, duration)\n break\n case 'h':\n closingDate = addHours(openingDate, duration)\n break\n case 'd':\n closingDate = addDays(openingDate, duration)\n break\n case 'wk':\n closingDate = addWeeks(openingDate, duration)\n break\n case 'mo':\n closingDate = addMonths(openingDate, duration)\n break\n case 'a':\n closingDate = addYears(openingDate, duration)\n break\n }\n\n if (isSameDay(openingDate, closingDate) && isBefore(openingDate, closingDate)) {\n yield new TimeSpan(openingDate, closingDate)\n }\n }\n }\n}\n\nfunction durationToMilliseconds (duration: Duration): number {\n if (!isUcumUnit(duration.unit, duration.system)) {\n return 0\n }\n\n let value = duration.value || 0\n\n /* eslint-disable no-fallthrough */\n switch (duration.unit) {\n case 'a':\n value *= 365\n case 'mo':\n value *= 30\n case 'wk':\n value *= 7\n case 'd':\n value *= 24\n case 'h':\n value *= 60\n case 'min':\n value *= 60\n case 's':\n value *= 1000\n }\n /* eslint-enable no-fallthrough */\n\n return value\n}\n\nfunction isUcumUnit (unit?: string, system?: string): unit is 's' | 'min' | 'h' | 'd' | 'wk' | 'mo' | 'a' {\n return !!unit && (!system || system === UCUM)\n}\n\ntype Day = 'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat'\n\nfunction dayOfWeek (date: Date): Day {\n return format(date, 'iii').toLowerCase() as Day\n}\n\nfunction parseTime (time: string, referenceDate: Date): Date {\n return parse(time, 'HH:mm:ss', referenceDate)\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface AlignmentOptions {
|
|
2
|
+
interval?: number;
|
|
3
|
+
offset?: number;
|
|
4
|
+
origin?: Date;
|
|
5
|
+
}
|
|
6
|
+
export declare class TimeSpan {
|
|
7
|
+
#private;
|
|
8
|
+
constructor(start: Date, end: Date);
|
|
9
|
+
get start(): Date;
|
|
10
|
+
get end(): Date;
|
|
11
|
+
includes(date: Date): boolean;
|
|
12
|
+
intersects(other: TimeSpan): boolean;
|
|
13
|
+
extend(date: Date): void;
|
|
14
|
+
partition(duration: number, includeRemainder?: boolean, alignment?: AlignmentOptions): Generator<TimeSpan>;
|
|
15
|
+
}
|
|
16
|
+
export declare class TimeSpanArray {
|
|
17
|
+
#private;
|
|
18
|
+
constructor(timeSpans?: Iterable<TimeSpan>);
|
|
19
|
+
get length(): number;
|
|
20
|
+
[Symbol.iterator](): Generator<TimeSpan>;
|
|
21
|
+
get(index: number): TimeSpan;
|
|
22
|
+
add(item: TimeSpan): void;
|
|
23
|
+
consolidate(): TimeSpanArray;
|
|
24
|
+
subtract(otherTimeSpans: TimeSpanArray): TimeSpanArray;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=time-span.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time-span.d.ts","sourceRoot":"","sources":["../../src/scheduling/time-span.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,IAAI,CAAA;CACd;AAED,qBAAa,QAAQ;;gBAIN,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI;IAKnC,IAAI,KAAK,IAAK,IAAI,CAEjB;IAED,IAAI,GAAG,IAAK,IAAI,CAEf;IAED,QAAQ,CAAE,IAAI,EAAE,IAAI,GAAG,OAAO;IAI9B,UAAU,CAAE,KAAK,EAAE,QAAQ,GAAG,OAAO;IAIrC,MAAM,CAAE,IAAI,EAAE,IAAI,GAAG,IAAI;IAIvB,SAAS,CAAE,QAAQ,EAAE,MAAM,EAAE,gBAAgB,UAAQ,EAAE,SAAS,GAAE,gBAAqB,GAAG,SAAS,CAAC,QAAQ,CAAC;CAgBhH;AAED,qBAAa,aAAa;;gBAGX,SAAS,GAAE,QAAQ,CAAC,QAAQ,CAAM;IAI/C,IAAI,MAAM,IAAK,MAAM,CAEpB;IAEC,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAK,SAAS,CAAC,QAAQ,CAAC;IAM3C,GAAG,CAAE,KAAK,EAAE,MAAM,GAAG,QAAQ;IAI7B,GAAG,CAAE,IAAI,EAAE,QAAQ,GAAG,IAAI;IAsB1B,WAAW,IAAK,aAAa;IAqB7B,QAAQ,CAAE,cAAc,EAAE,aAAa,GAAG,aAAa;CAkCxD"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
3
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
4
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
5
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
6
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
7
|
+
};
|
|
8
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
9
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
10
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
|
+
};
|
|
13
|
+
var _TimeSpan_start, _TimeSpan_end, _TimeSpanArray_timeSpans;
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.TimeSpanArray = exports.TimeSpan = void 0;
|
|
16
|
+
const date_fns_1 = require("date-fns");
|
|
17
|
+
class TimeSpan {
|
|
18
|
+
constructor(start, end) {
|
|
19
|
+
_TimeSpan_start.set(this, void 0);
|
|
20
|
+
_TimeSpan_end.set(this, void 0);
|
|
21
|
+
__classPrivateFieldSet(this, _TimeSpan_start, start, "f");
|
|
22
|
+
__classPrivateFieldSet(this, _TimeSpan_end, end, "f");
|
|
23
|
+
}
|
|
24
|
+
get start() {
|
|
25
|
+
return __classPrivateFieldGet(this, _TimeSpan_start, "f");
|
|
26
|
+
}
|
|
27
|
+
get end() {
|
|
28
|
+
return __classPrivateFieldGet(this, _TimeSpan_end, "f");
|
|
29
|
+
}
|
|
30
|
+
includes(date) {
|
|
31
|
+
return (0, date_fns_1.isWithinInterval)(date, this);
|
|
32
|
+
}
|
|
33
|
+
intersects(other) {
|
|
34
|
+
return (0, date_fns_1.areIntervalsOverlapping)(this, other);
|
|
35
|
+
}
|
|
36
|
+
extend(date) {
|
|
37
|
+
__classPrivateFieldSet(this, _TimeSpan_end, (0, date_fns_1.max)([date, __classPrivateFieldGet(this, _TimeSpan_end, "f")]), "f");
|
|
38
|
+
}
|
|
39
|
+
*partition(duration, includeRemainder = false, alignment = {}) {
|
|
40
|
+
const { interval, offset, origin } = { interval: 1, offset: 0, origin: (0, date_fns_1.startOfDay)(__classPrivateFieldGet(this, _TimeSpan_start, "f")), ...alignment };
|
|
41
|
+
let start = alignDate(origin, interval, offset, __classPrivateFieldGet(this, _TimeSpan_start, "f"));
|
|
42
|
+
let end = (0, date_fns_1.addMilliseconds)(start, duration);
|
|
43
|
+
while ((0, date_fns_1.isBefore)(end, __classPrivateFieldGet(this, _TimeSpan_end, "f")) || (0, date_fns_1.isEqual)(end, __classPrivateFieldGet(this, _TimeSpan_end, "f"))) {
|
|
44
|
+
yield new TimeSpan(start, end);
|
|
45
|
+
start = alignDate(origin, interval, offset, end);
|
|
46
|
+
end = (0, date_fns_1.addMilliseconds)(start, duration);
|
|
47
|
+
}
|
|
48
|
+
if ((0, date_fns_1.isBefore)(start, __classPrivateFieldGet(this, _TimeSpan_end, "f")) && includeRemainder) {
|
|
49
|
+
yield new TimeSpan(start, __classPrivateFieldGet(this, _TimeSpan_end, "f"));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.TimeSpan = TimeSpan;
|
|
54
|
+
_TimeSpan_start = new WeakMap(), _TimeSpan_end = new WeakMap();
|
|
55
|
+
class TimeSpanArray {
|
|
56
|
+
constructor(timeSpans = []) {
|
|
57
|
+
_TimeSpanArray_timeSpans.set(this, void 0);
|
|
58
|
+
__classPrivateFieldSet(this, _TimeSpanArray_timeSpans, Array.from(timeSpans), "f");
|
|
59
|
+
}
|
|
60
|
+
get length() {
|
|
61
|
+
return __classPrivateFieldGet(this, _TimeSpanArray_timeSpans, "f").length;
|
|
62
|
+
}
|
|
63
|
+
*[(_TimeSpanArray_timeSpans = new WeakMap(), Symbol.iterator)]() {
|
|
64
|
+
for (const item of __classPrivateFieldGet(this, _TimeSpanArray_timeSpans, "f")) {
|
|
65
|
+
yield item;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
get(index) {
|
|
69
|
+
return __classPrivateFieldGet(this, _TimeSpanArray_timeSpans, "f")[index];
|
|
70
|
+
}
|
|
71
|
+
add(item) {
|
|
72
|
+
const timeSpans = __classPrivateFieldGet(this, _TimeSpanArray_timeSpans, "f");
|
|
73
|
+
// Insert time span ordered by start date for easier consolidation
|
|
74
|
+
// of overlapping spans
|
|
75
|
+
if (timeSpans.length) {
|
|
76
|
+
for (let i = timeSpans.length - 1; i >= 0; i--) {
|
|
77
|
+
const other = timeSpans[i];
|
|
78
|
+
if ((0, date_fns_1.isAfter)(item.start, other.start)) {
|
|
79
|
+
timeSpans.splice(i + 1, 0, item);
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
else if (i === 0) {
|
|
83
|
+
timeSpans.splice(0, 0, item);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
timeSpans.push(item);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// The function assumes that the time spans are sorted by start date
|
|
92
|
+
consolidate() {
|
|
93
|
+
const result = [];
|
|
94
|
+
for (const span of __classPrivateFieldGet(this, _TimeSpanArray_timeSpans, "f")) {
|
|
95
|
+
if (!result.length) {
|
|
96
|
+
result.push(span);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
const last = result[result.length - 1];
|
|
100
|
+
if (span.intersects(last)) {
|
|
101
|
+
last.extend(span.end);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
result.push(span);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return new TimeSpanArray(result);
|
|
109
|
+
}
|
|
110
|
+
// Both arrays need to be consolidated before calling subtract
|
|
111
|
+
subtract(otherTimeSpans) {
|
|
112
|
+
const result = [];
|
|
113
|
+
let j = 0;
|
|
114
|
+
for (const span of __classPrivateFieldGet(this, _TimeSpanArray_timeSpans, "f")) {
|
|
115
|
+
let start = span.start;
|
|
116
|
+
while (j < otherTimeSpans.length && (0, date_fns_1.isBefore)(otherTimeSpans.get(j).end, span.start)) {
|
|
117
|
+
j++;
|
|
118
|
+
}
|
|
119
|
+
while (j < otherTimeSpans.length && (0, date_fns_1.isBefore)(otherTimeSpans.get(j).start, span.end)) {
|
|
120
|
+
const other = otherTimeSpans.get(j);
|
|
121
|
+
if ((0, date_fns_1.isAfter)(other.start, start)) {
|
|
122
|
+
result.push(new TimeSpan(start, (0, date_fns_1.min)([other.start, span.end])));
|
|
123
|
+
}
|
|
124
|
+
start = (0, date_fns_1.max)([start, other.end]);
|
|
125
|
+
if ((0, date_fns_1.isAfter)(other.end, span.end)) {
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
j++;
|
|
129
|
+
}
|
|
130
|
+
if ((0, date_fns_1.isBefore)(start, span.end)) {
|
|
131
|
+
result.push(new TimeSpan(start, span.end));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return new TimeSpanArray(result);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
exports.TimeSpanArray = TimeSpanArray;
|
|
138
|
+
function alignDate(origin, interval, offset, start) {
|
|
139
|
+
const originTime = origin.getTime();
|
|
140
|
+
const wholeIntervals = Math.ceil((start.getTime() - originTime) / interval);
|
|
141
|
+
return new Date(originTime + wholeIntervals * interval + offset);
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=time-span.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time-span.js","sourceRoot":"","sources":["../../src/scheduling/time-span.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n addMilliseconds,\n areIntervalsOverlapping,\n isAfter,\n isBefore,\n isEqual,\n isWithinInterval,\n max,\n min,\n startOfDay\n} from 'date-fns'\n\nexport interface AlignmentOptions {\n interval?: number\n offset?: number\n origin?: Date\n}\n\nexport class TimeSpan {\n #start: Date\n #end: Date\n\n constructor (start: Date, end: Date) {\n this.#start = start\n this.#end = end\n }\n\n get start (): Date {\n return this.#start\n }\n\n get end (): Date {\n return this.#end\n }\n\n includes (date: Date): boolean {\n return isWithinInterval(date, this)\n }\n\n intersects (other: TimeSpan): boolean {\n return areIntervalsOverlapping(this, other)\n }\n\n extend (date: Date): void {\n this.#end = max([date, this.#end])\n }\n\n * partition (duration: number, includeRemainder = false, alignment: AlignmentOptions = {}): Generator<TimeSpan> {\n const { interval, offset, origin } = { interval: 1, offset: 0, origin: startOfDay(this.#start), ...alignment }\n\n let start = alignDate(origin, interval, offset, this.#start)\n let end = addMilliseconds(start, duration)\n\n while (isBefore(end, this.#end) || isEqual(end, this.#end)) {\n yield new TimeSpan(start, end)\n start = alignDate(origin, interval, offset, end)\n end = addMilliseconds(start, duration)\n }\n\n if (isBefore(start, this.#end) && includeRemainder) {\n yield new TimeSpan(start, this.#end)\n }\n }\n}\n\nexport class TimeSpanArray {\n #timeSpans: TimeSpan[]\n\n constructor (timeSpans: Iterable<TimeSpan> = []) {\n this.#timeSpans = Array.from(timeSpans)\n }\n\n get length (): number {\n return this.#timeSpans.length\n }\n\n * [Symbol.iterator] (): Generator<TimeSpan> {\n for (const item of this.#timeSpans) {\n yield item\n }\n }\n\n get (index: number): TimeSpan {\n return this.#timeSpans[index]\n }\n\n add (item: TimeSpan): void {\n const timeSpans = this.#timeSpans\n\n // Insert time span ordered by start date for easier consolidation\n // of overlapping spans\n if (timeSpans.length) {\n for (let i = timeSpans.length - 1; i >= 0; i--) {\n const other = timeSpans[i]\n\n if (isAfter(item.start, other.start)) {\n timeSpans.splice(i + 1, 0, item)\n break\n } else if (i === 0) {\n timeSpans.splice(0, 0, item)\n }\n }\n } else {\n timeSpans.push(item)\n }\n }\n\n // The function assumes that the time spans are sorted by start date\n consolidate (): TimeSpanArray {\n const result: TimeSpan[] = []\n\n for (const span of this.#timeSpans) {\n if (!result.length) {\n result.push(span)\n } else {\n const last = result[result.length - 1]\n\n if (span.intersects(last)) {\n last.extend(span.end)\n } else {\n result.push(span)\n }\n }\n }\n\n return new TimeSpanArray(result)\n }\n\n // Both arrays need to be consolidated before calling subtract\n subtract (otherTimeSpans: TimeSpanArray): TimeSpanArray {\n const result: TimeSpan[] = []\n let j = 0\n\n for (const span of this.#timeSpans) {\n let start = span.start\n\n while (j < otherTimeSpans.length && isBefore(otherTimeSpans.get(j).end, span.start)) {\n j++\n }\n\n while (j < otherTimeSpans.length && isBefore(otherTimeSpans.get(j).start, span.end)) {\n const other = otherTimeSpans.get(j)\n\n if (isAfter(other.start, start)) {\n result.push(new TimeSpan(start, min([other.start, span.end])))\n }\n\n start = max([start, other.end])\n\n if (isAfter(other.end, span.end)) {\n break\n }\n\n j++\n }\n\n if (isBefore(start, span.end)) {\n result.push(new TimeSpan(start, span.end))\n }\n }\n\n return new TimeSpanArray(result)\n }\n}\n\nfunction alignDate (origin: Date, interval: number, offset: number, start: Date): Date {\n const originTime = origin.getTime()\n const wholeIntervals = Math.ceil((start.getTime() - originTime) / interval)\n return new Date(originTime + wholeIntervals * interval + offset)\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { HealthcareService } from '@medplum/fhirtypes';
|
|
2
|
+
import type { AliasRow } from '../db/types';
|
|
3
|
+
/**
|
|
4
|
+
* FHIR identifier system used by the search index to store and extract service codes.
|
|
5
|
+
*
|
|
6
|
+
* Note: this value may differ from SERVICE_CODES_SYSTEM in constants.ts which is
|
|
7
|
+
* used by the admin UI. Use this constant whenever interacting with the search index.
|
|
8
|
+
*/
|
|
9
|
+
export declare const SEARCH_SERVICE_CODES_SYSTEM = "http://fuego.health/fhir/service-codes";
|
|
10
|
+
/**
|
|
11
|
+
* Extract custom search aliases from the HS_SEARCH_ALIASES_EXTENSION_URL extension.
|
|
12
|
+
* Returns an empty array when none are present.
|
|
13
|
+
* @param hs - HealthcareService resource to read.
|
|
14
|
+
* @returns Non-empty extension alias strings.
|
|
15
|
+
*/
|
|
16
|
+
export declare function extractSearchAliases(hs: HealthcareService): string[];
|
|
17
|
+
/**
|
|
18
|
+
* Build all alias rows for a single HealthcareService resource.
|
|
19
|
+
* @param hs - Source HealthcareService resource.
|
|
20
|
+
* @returns Alias rows ready to persist in the search index.
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildAliases(hs: HealthcareService): AliasRow[];
|
|
23
|
+
//# sourceMappingURL=alias-builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alias-builder.d.ts","sourceRoot":"","sources":["../../src/search/alias-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAC3D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAI3C;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,2CAA2C,CAAA;AA0BnF;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAE,EAAE,EAAE,iBAAiB,GAAG,MAAM,EAAE,CAOrE;AAmBD;;;;GAIG;AACH,wBAAgB,YAAY,CAAE,EAAE,EAAE,iBAAiB,GAAG,QAAQ,EAAE,CA6D/D"}
|