vet-data-utils-ts 0.5.10 → 0.5.14
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.
|
@@ -23,6 +23,7 @@ export type ReusableOrganizationApplication = Readonly<{
|
|
|
23
23
|
regionalId: boolean;
|
|
24
24
|
subdivisionCode?: string;
|
|
25
25
|
officialLicense?: string;
|
|
26
|
+
officialPhone?: string;
|
|
26
27
|
legalRepresentative: Readonly<{
|
|
27
28
|
name: string;
|
|
28
29
|
email: string;
|
|
@@ -49,3 +50,5 @@ export declare function organizationServiceTypeForParticipationRoles(roles: read
|
|
|
49
50
|
* object and remain inside the BFF/high-level Node runtime.
|
|
50
51
|
*/
|
|
51
52
|
export declare function parseReusableOrganizationApplication(input: unknown): ReusableOrganizationApplication;
|
|
53
|
+
/** Normalizes an optional official organization telephone to E.164 using the selected jurisdiction calling code. */
|
|
54
|
+
export declare function normalizeOptionalOrganizationOfficialPhone(value: unknown, defaultCallingCode: unknown): string | undefined;
|
|
@@ -91,6 +91,7 @@ export function parseReusableOrganizationApplication(input) {
|
|
|
91
91
|
regionalId,
|
|
92
92
|
...(subdivisionCode ? { subdivisionCode } : {}),
|
|
93
93
|
...(optional(value.officialLicense) ? { officialLicense: optional(value.officialLicense) } : {}),
|
|
94
|
+
...(optional(value.officialPhone) ? { officialPhone: normalizeOptionalOrganizationOfficialPhone(value.officialPhone, value.defaultPhoneCallingCode) } : {}),
|
|
94
95
|
legalRepresentative: Object.freeze({
|
|
95
96
|
name: required(legalRepresentative.name, 'organization_legal_representative_name_required'),
|
|
96
97
|
email: legalEmail,
|
|
@@ -99,6 +100,24 @@ export function parseReusableOrganizationApplication(input) {
|
|
|
99
100
|
participationRoles: Object.freeze([...new Set(selectedRoles)]),
|
|
100
101
|
});
|
|
101
102
|
}
|
|
103
|
+
/** Normalizes an optional official organization telephone to E.164 using the selected jurisdiction calling code. */
|
|
104
|
+
export function normalizeOptionalOrganizationOfficialPhone(value, defaultCallingCode) {
|
|
105
|
+
const raw = optional(value);
|
|
106
|
+
if (!raw)
|
|
107
|
+
return undefined;
|
|
108
|
+
let compact = raw.replace(/^tel:/i, '').replace(/[\s().-]/g, '');
|
|
109
|
+
if (compact.startsWith('00'))
|
|
110
|
+
compact = `+${compact.slice(2)}`;
|
|
111
|
+
if (!compact.startsWith('+')) {
|
|
112
|
+
const callingCode = String(defaultCallingCode ?? '').trim().replace(/^00/, '+');
|
|
113
|
+
if (!/^\+[1-9]\d{0,2}$/.test(callingCode))
|
|
114
|
+
throw new TypeError('organization_official_phone_calling_code_required');
|
|
115
|
+
compact = `${callingCode}${compact.replace(/^0/, '')}`;
|
|
116
|
+
}
|
|
117
|
+
if (!/^\+[1-9]\d{7,14}$/.test(compact))
|
|
118
|
+
throw new TypeError('organization_official_phone_invalid');
|
|
119
|
+
return compact;
|
|
120
|
+
}
|
|
102
121
|
function record(value, error) {
|
|
103
122
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
104
123
|
throw new TypeError(error);
|
package/dist/scheduling.d.ts
CHANGED
|
@@ -115,9 +115,23 @@ export declare function buildVeterinaryAppointmentResponseResource(input: Readon
|
|
|
115
115
|
appointmentReference: string;
|
|
116
116
|
actorReference: string;
|
|
117
117
|
participantStatus: string;
|
|
118
|
+
respondedAt?: string;
|
|
118
119
|
proposedNewTime?: boolean;
|
|
119
120
|
start?: string;
|
|
120
121
|
end?: string;
|
|
122
|
+
notificationRecipientReference?: string;
|
|
123
|
+
notificationRecipientEmail?: string;
|
|
124
|
+
}>): ClaimsFirstResource;
|
|
125
|
+
/** Builds the FHIR-like Communication that an adapter may render as the additional English clinic email. */
|
|
126
|
+
export declare function buildVeterinaryAppointmentResponseNotificationResource(input: Readonly<{
|
|
127
|
+
id: string;
|
|
128
|
+
responseReference: string;
|
|
129
|
+
appointmentReference: string;
|
|
130
|
+
senderReference: string;
|
|
131
|
+
recipientReference: string;
|
|
132
|
+
participantStatus: string;
|
|
133
|
+
sentAt: string;
|
|
134
|
+
language: string;
|
|
121
135
|
}>): ClaimsFirstResource;
|
|
122
136
|
/** Builds a searchable claims-first FHIR R5 Appointment with a seven-day confirmation due time. */
|
|
123
137
|
export declare function buildVeterinaryAppointmentResource(input: Readonly<{
|
|
@@ -129,6 +143,9 @@ export declare function buildVeterinaryAppointmentResource(input: Readonly<{
|
|
|
129
143
|
actorReferences: readonly string[];
|
|
130
144
|
locationReference?: string;
|
|
131
145
|
confirmationDueAt?: string;
|
|
146
|
+
userSelected?: boolean;
|
|
147
|
+
clinicRecipientReference?: string;
|
|
148
|
+
clinicRecipientEmail?: string;
|
|
132
149
|
}>): ClaimsFirstResource;
|
|
133
150
|
/** Builds the durable web inbox notification when a schedule change displaces an appointment. */
|
|
134
151
|
export declare function buildVeterinaryAppointmentNotificationResource(input: Readonly<{
|
package/dist/scheduling.js
CHANGED
|
@@ -14,8 +14,8 @@ export const VeterinarySchedulingFlatClaimCatalog = Object.freeze({
|
|
|
14
14
|
Location: Object.freeze(['Location.identifier', 'Location.status', 'Location.mode', 'Location.name', 'Location.description', 'Location.position.latitude', 'Location.position.longitude', 'Location.position.altitude', 'Location.managingOrganization', 'Location.characteristic', 'VeterinaryLocation.photoUrl']),
|
|
15
15
|
Schedule: Object.freeze(['Schedule.identifier', 'Schedule.active', 'Schedule.name', 'Schedule.actor', 'Schedule.planningHorizon.start', 'Schedule.planningHorizon.end']),
|
|
16
16
|
Slot: Object.freeze(['Slot.identifier', 'Slot.schedule', 'Slot.status', 'Slot.start', 'Slot.end', 'Slot.service-category', 'Slot.service-type', 'Slot.specialty', 'VeterinarySlot.location', 'VeterinarySlot.timeZone']),
|
|
17
|
-
Appointment: Object.freeze(['Appointment.identifier', 'Appointment.status', 'Appointment.description', 'Appointment.note', 'Appointment.start', 'Appointment.end', 'Appointment.slot', 'Appointment.actor', 'Appointment.location', 'VeterinaryAppointment.confirmationDueAt', 'VeterinaryAppointment.reschedulingReason']),
|
|
18
|
-
AppointmentResponse: Object.freeze(['AppointmentResponse.identifier', 'AppointmentResponse.appointment', 'AppointmentResponse.actor', 'AppointmentResponse.participantStatus', 'AppointmentResponse.proposedNewTime', 'AppointmentResponse.start', 'AppointmentResponse.end']),
|
|
17
|
+
Appointment: Object.freeze(['Appointment.identifier', 'Appointment.status', 'Appointment.description', 'Appointment.note', 'Appointment.start', 'Appointment.end', 'Appointment.slot', 'Appointment.actor', 'Appointment.location', 'Appointment.user-selected', 'VeterinaryAppointment.confirmationDueAt', 'VeterinaryAppointment.reschedulingReason', 'VeterinaryAppointment.clinicRecipient', 'VeterinaryAppointment.clinicEmail']),
|
|
18
|
+
AppointmentResponse: Object.freeze(['AppointmentResponse.identifier', 'AppointmentResponse.appointment', 'AppointmentResponse.actor', 'AppointmentResponse.participantStatus', 'AppointmentResponse.proposedNewTime', 'AppointmentResponse.start', 'AppointmentResponse.end', 'VeterinaryAppointmentResponse.respondedAt', 'VeterinaryAppointmentResponse.recipient', 'VeterinaryAppointmentResponse.recipientEmail']),
|
|
19
19
|
});
|
|
20
20
|
/** Validates the claims-only wire shape and normalizes scalar builder values to indexed strings. */
|
|
21
21
|
export function normalizeVeterinarySchedulingFlatClaimsResource(input) {
|
|
@@ -177,7 +177,37 @@ export function buildVeterinaryAppointmentResponseResource(input) {
|
|
|
177
177
|
throw new Error('appointment_response_time_incomplete');
|
|
178
178
|
if (input.start && input.end && Date.parse(input.start) >= Date.parse(input.end))
|
|
179
179
|
throw new Error('appointment_response_time_invalid');
|
|
180
|
-
|
|
180
|
+
const respondedAt = input.respondedAt || new Date().toISOString();
|
|
181
|
+
if (!Number.isFinite(Date.parse(respondedAt)))
|
|
182
|
+
throw new Error('appointment_response_responded_at_invalid');
|
|
183
|
+
const hasRecipientReference = Boolean(input.notificationRecipientReference?.trim());
|
|
184
|
+
const hasRecipientEmail = Boolean(input.notificationRecipientEmail?.trim());
|
|
185
|
+
if (hasRecipientReference !== hasRecipientEmail)
|
|
186
|
+
throw new Error('appointment_response_notification_recipient_incomplete');
|
|
187
|
+
if (hasRecipientEmail && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(input.notificationRecipientEmail.trim()))
|
|
188
|
+
throw new Error('appointment_response_notification_email_invalid');
|
|
189
|
+
return claimsResource('AppointmentResponse', input.id, { 'AppointmentResponse.appointment': required(input.appointmentReference, 'appointment_reference_required'), 'AppointmentResponse.actor': required(input.actorReference, 'appointment_response_actor_required'), 'AppointmentResponse.participantStatus': input.participantStatus, 'VeterinaryAppointmentResponse.respondedAt': new Date(respondedAt).toISOString(), ...(hasRecipientReference ? { 'VeterinaryAppointmentResponse.recipient': input.notificationRecipientReference.trim(), 'VeterinaryAppointmentResponse.recipientEmail': input.notificationRecipientEmail.trim().toLowerCase() } : {}), ...(input.proposedNewTime === undefined ? {} : { 'AppointmentResponse.proposedNewTime': input.proposedNewTime }), ...(input.start ? { 'AppointmentResponse.start': input.start, 'AppointmentResponse.end': input.end } : {}) });
|
|
190
|
+
}
|
|
191
|
+
/** Builds the FHIR-like Communication that an adapter may render as the additional English clinic email. */
|
|
192
|
+
export function buildVeterinaryAppointmentResponseNotificationResource(input) {
|
|
193
|
+
if (input.language !== 'en')
|
|
194
|
+
throw new Error('appointment_response_notification_language_unsupported');
|
|
195
|
+
if (!Object.values(AppointmentResponseStatuses).includes(input.participantStatus))
|
|
196
|
+
throw new Error('appointment_response_status_invalid');
|
|
197
|
+
if (!Number.isFinite(Date.parse(input.sentAt)))
|
|
198
|
+
throw new Error('appointment_response_notification_sent_at_invalid');
|
|
199
|
+
const statusText = Object.freeze({ accepted: 'accepted', declined: 'declined', tentative: 'tentatively changed', 'needs-action': 'left awaiting action', 'entered-in-error': 'marked as entered in error' });
|
|
200
|
+
return claimsResource('Communication', input.id, {
|
|
201
|
+
'Communication.identifier': input.id,
|
|
202
|
+
'Communication.status': 'completed',
|
|
203
|
+
'Communication.category': 'http://terminology.hl7.org/CodeSystem/communication-category|notification',
|
|
204
|
+
'Communication.recipient': required(input.recipientReference, 'appointment_response_notification_recipient_required'),
|
|
205
|
+
'Communication.sender': required(input.senderReference, 'appointment_response_notification_sender_required'),
|
|
206
|
+
'Communication.sent': new Date(input.sentAt).toISOString(),
|
|
207
|
+
'Communication.topic': 'https://vetchain.app/fhir/CodeSystem/communication-topic|appointment-response',
|
|
208
|
+
'Communication.content-reference': Object.freeze([required(input.responseReference, 'appointment_response_reference_required'), required(input.appointmentReference, 'appointment_reference_required')]),
|
|
209
|
+
'Communication.text': `The appointment was ${statusText[input.participantStatus]}.`,
|
|
210
|
+
});
|
|
181
211
|
}
|
|
182
212
|
/** Builds a searchable claims-first FHIR R5 Appointment with a seven-day confirmation due time. */
|
|
183
213
|
export function buildVeterinaryAppointmentResource(input) {
|
|
@@ -187,19 +217,28 @@ export function buildVeterinaryAppointmentResource(input) {
|
|
|
187
217
|
const end = Date.parse(input.end);
|
|
188
218
|
if (!Number.isFinite(start) || !Number.isFinite(end) || start >= end)
|
|
189
219
|
throw new Error('appointment_time_invalid');
|
|
190
|
-
|
|
220
|
+
const userSelected = input.userSelected ?? false;
|
|
221
|
+
if (!input.slotReferences.length && !userSelected)
|
|
191
222
|
throw new Error('appointment_slot_required');
|
|
192
223
|
if (!input.actorReferences.length)
|
|
193
224
|
throw new Error('appointment_actor_required');
|
|
194
225
|
const confirmationDueAt = input.confirmationDueAt || new Date(start - 7 * 24 * 60 * 60 * 1000).toISOString();
|
|
195
226
|
if (!Number.isFinite(Date.parse(confirmationDueAt)) || Date.parse(confirmationDueAt) >= start)
|
|
196
227
|
throw new Error('appointment_confirmation_due_invalid');
|
|
228
|
+
const hasClinicRecipient = Boolean(input.clinicRecipientReference?.trim());
|
|
229
|
+
const hasClinicEmail = Boolean(input.clinicRecipientEmail?.trim());
|
|
230
|
+
if (hasClinicRecipient !== hasClinicEmail)
|
|
231
|
+
throw new Error('appointment_clinic_notification_recipient_incomplete');
|
|
232
|
+
if (hasClinicEmail && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(input.clinicRecipientEmail.trim()))
|
|
233
|
+
throw new Error('appointment_clinic_email_invalid');
|
|
197
234
|
return claimsResource('Appointment', input.id, {
|
|
198
235
|
'Appointment.status': input.status,
|
|
199
236
|
'Appointment.start': new Date(start).toISOString(),
|
|
200
237
|
'Appointment.end': new Date(end).toISOString(),
|
|
201
|
-
'Appointment.slot': Object.freeze([...input.slotReferences]),
|
|
238
|
+
...(input.slotReferences.length ? { 'Appointment.slot': Object.freeze([...input.slotReferences]) } : {}),
|
|
202
239
|
'Appointment.actor': Object.freeze([...input.actorReferences]),
|
|
240
|
+
'Appointment.user-selected': userSelected,
|
|
241
|
+
...(hasClinicRecipient ? { 'VeterinaryAppointment.clinicRecipient': input.clinicRecipientReference.trim(), 'VeterinaryAppointment.clinicEmail': input.clinicRecipientEmail.trim().toLowerCase() } : {}),
|
|
203
242
|
...(input.locationReference ? { 'Appointment.location': required(input.locationReference, 'appointment_location_required') } : {}),
|
|
204
243
|
'VeterinaryAppointment.confirmationDueAt': new Date(confirmationDueAt).toISOString(),
|
|
205
244
|
});
|