@fuego-systems/core 0.1.14 → 0.1.16

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.
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadPracticaMappingRules = loadPracticaMappingRules;
4
+ exports.resolvePracticaMappingContext = resolvePracticaMappingContext;
5
+ exports.resolveEffectivePracticas = resolveEffectivePracticas;
6
+ exports.praIdFromCanonical = praIdFromCanonical;
7
+ exports.getOrderSetPraId = getOrderSetPraId;
8
+ const constants_1 = require("./constants");
9
+ const plan_definition_group_1 = require("./plan-definition-group");
10
+ const practica_mapping_1 = require("./practica-mapping");
11
+ // ─── Registry / context loading ─────────────────────────────────────────────
12
+ /**
13
+ * Loads the práctica-mapping rules from the singleton ConceptMap.
14
+ *
15
+ * @param medplum - Medplum client
16
+ * @returns the registry rules, or `[]` when no registry exists
17
+ */
18
+ async function loadPracticaMappingRules(medplum) {
19
+ const conceptMap = await medplum.searchOne('ConceptMap', `url=${constants_1.PRACTICA_MAPPING_CONCEPT_MAP_URL}`);
20
+ return (0, practica_mapping_1.readPracticaMappingRules)(conceptMap);
21
+ }
22
+ /**
23
+ * Resolves the patient context into the references the mapping rules match
24
+ * against: the EGES plan id becomes the migrated `InsurancePlan` reference
25
+ * (matched by `EGES_OBRA_SOCIAL_PLAN_SYSTEM` identifier) and, via `ownedBy`,
26
+ * its payer `Organization` reference.
27
+ *
28
+ * @param medplum - Medplum client
29
+ * @param context - the raw patient context (may be undefined)
30
+ * @returns the resolved mapping context (empty when no context was given)
31
+ */
32
+ async function resolvePracticaMappingContext(medplum, context) {
33
+ var _a;
34
+ if (!context) {
35
+ return {};
36
+ }
37
+ let insurancePlanRef;
38
+ let insuranceOrgRef;
39
+ if (context.insurancePlanId !== undefined) {
40
+ const plan = await medplum.searchOne('InsurancePlan', `identifier=${constants_1.EGES_OBRA_SOCIAL_PLAN_SYSTEM}|${context.insurancePlanId}`);
41
+ if (plan === null || plan === void 0 ? void 0 : plan.id) {
42
+ insurancePlanRef = `InsurancePlan/${plan.id}`;
43
+ insuranceOrgRef = (_a = plan.ownedBy) === null || _a === void 0 ? void 0 : _a.reference;
44
+ }
45
+ }
46
+ const age = context.birthDate !== undefined ? (0, practica_mapping_1.computePatientAge)(context.birthDate) : undefined;
47
+ return {
48
+ ...(insurancePlanRef !== undefined ? { insurancePlanRef } : {}),
49
+ ...(insuranceOrgRef !== undefined ? { insuranceOrgRef } : {}),
50
+ ...(age !== undefined ? { age } : {}),
51
+ ...(context.sex !== undefined ? { sex: context.sex } : {})
52
+ };
53
+ }
54
+ // ─── Resolution ─────────────────────────────────────────────────────────────
55
+ /**
56
+ * Resolves a study selection into the effective set of EGES práctica ids,
57
+ * applying the práctica-mapping registry (patient insurance / age / sex).
58
+ *
59
+ * This is the single canonical resolver behind both the availability search
60
+ * (via the extended `$resolve-protocol` operation) and appointment creation
61
+ * (in-process in the `create-appointment` bot):
62
+ *
63
+ * 1. `HealthcareService` → `hs-protocol` → protocol PD.
64
+ * 2. Protocol-group: `resolveProtocolSelection` yields the main + tipo-2
65
+ * addon order-set refs. Single-variant: the PD itself is the order-set.
66
+ * 3. Mapping applies at **order-set granularity, before expansion**: a
67
+ * substituted principal contributes the *target* order-set's service type,
68
+ * duration and tipo-3 defaults.
69
+ * 4. Tipo-3 = (effective order-set defaults ∪ variant `'add'` overrides)
70
+ * \ `'remove'` overrides, then id-level mapping on each.
71
+ *
72
+ * Substitution is single-pass: a target pra_id is never re-mapped.
73
+ *
74
+ * @param medplum - Medplum client
75
+ * @param params - the study selection + patient context
76
+ * @returns the effective práctica set
77
+ * @throws when the HS has no protocol, the selection is missing for a
78
+ * protocol-group, or a mapping target's order-set PD does not exist
79
+ */
80
+ async function resolveEffectivePracticas(medplum, params) {
81
+ var _a, _b, _c, _d, _e, _f, _g;
82
+ const hs = await medplum.readResource('HealthcareService', params.hsId);
83
+ const pdRef = (_c = (_b = (_a = hs.extension) === null || _a === void 0 ? void 0 : _a.find((e) => e.url === constants_1.HS_PROTOCOL_EXTENSION_URL)) === null || _b === void 0 ? void 0 : _b.valueReference) === null || _c === void 0 ? void 0 : _c.reference;
84
+ if (!(pdRef === null || pdRef === void 0 ? void 0 : pdRef.startsWith('PlanDefinition/'))) {
85
+ throw new Error(`HealthcareService/${params.hsId} has no hs-protocol PlanDefinition reference`);
86
+ }
87
+ const protocolPd = await medplum.readResource('PlanDefinition', pdRef.split('/')[1]);
88
+ const rules = await loadPracticaMappingRules(medplum);
89
+ const mappingContext = rules.length > 0
90
+ ? await resolvePracticaMappingContext(medplum, params.context)
91
+ : {};
92
+ const substitutions = [];
93
+ const substitute = (praId) => {
94
+ const match = (0, practica_mapping_1.matchPracticaMapping)(rules, praId, mappingContext);
95
+ if (!match) {
96
+ return praId;
97
+ }
98
+ substitutions.push({
99
+ from: praId,
100
+ to: match.rule.targetPraId,
101
+ matchedConditions: match.matchedConditions
102
+ });
103
+ return match.rule.targetPraId;
104
+ };
105
+ let mainOrderSet;
106
+ let mainDuration;
107
+ let addonRefs = [];
108
+ let variantAction;
109
+ if ((0, plan_definition_group_1.isProtocolGroup)(protocolPd)) {
110
+ if (!params.selection) {
111
+ throw new Error('A protocol selection is required for a protocol-group HealthcareService');
112
+ }
113
+ const resolved = (0, plan_definition_group_1.resolveProtocolSelection)(protocolPd, params.selection);
114
+ variantAction = (_d = protocolPd.action) === null || _d === void 0 ? void 0 : _d.find((a) => { var _a; return a.id === ((_a = params.selection) === null || _a === void 0 ? void 0 : _a.actionId); });
115
+ mainOrderSet = await readOrderSet(medplum, resolved.main.protocolRef);
116
+ mainDuration = resolved.main.duration;
117
+ addonRefs = resolved.addons.map((a) => ({ ref: a.protocolRef, duration: a.duration }));
118
+ }
119
+ else {
120
+ mainOrderSet = protocolPd;
121
+ }
122
+ // Principal: substitute at order-set granularity (swap before expansion).
123
+ const mainPraId = requireOrderSetPraId(mainOrderSet);
124
+ const effectiveMainPraId = substitute(mainPraId);
125
+ const effectiveMainOrderSet = effectiveMainPraId === mainPraId
126
+ ? mainOrderSet
127
+ : await readOrderSetByPraId(medplum, effectiveMainPraId);
128
+ const principal = {
129
+ praId: effectiveMainPraId,
130
+ ...buildOrderSetFacts(effectiveMainOrderSet, effectiveMainPraId === mainPraId ? mainDuration : undefined)
131
+ };
132
+ // Tipo-2 adicionales: same order-set-level substitution per addon.
133
+ const adicionales = [];
134
+ for (const addon of addonRefs) {
135
+ const orderSet = await readOrderSet(medplum, addon.ref);
136
+ const praId = requireOrderSetPraId(orderSet);
137
+ const effectivePraId = substitute(praId);
138
+ const effectiveOrderSet = effectivePraId === praId
139
+ ? orderSet
140
+ : await readOrderSetByPraId(medplum, effectivePraId);
141
+ adicionales.push({
142
+ praId: effectivePraId,
143
+ ...buildOrderSetFacts(effectiveOrderSet, effectivePraId === praId ? addon.duration : undefined)
144
+ });
145
+ }
146
+ // Tipo-3: defaults come from the *effective* principal order-set, then the
147
+ // selected variant's add/remove overrides, then id-level mapping.
148
+ const defaultRefs = ((_g = (_f = (_e = effectiveMainOrderSet.action) === null || _e === void 0 ? void 0 : _e.find((a) => a.id === 'addons')) === null || _f === void 0 ? void 0 : _f.action) !== null && _g !== void 0 ? _g : [])
149
+ .filter((addon) => addon.requiredBehavior === 'must' && addon.definitionCanonical)
150
+ .map((addon) => addon.definitionCanonical);
151
+ const mandatoryRefs = (0, plan_definition_group_1.resolveVariantMandatoryAddons)(defaultRefs, variantAction);
152
+ const mandatory = [];
153
+ for (const ref of mandatoryRefs) {
154
+ const praId = praIdFromCanonical(ref);
155
+ if (praId !== undefined) {
156
+ mandatory.push(substitute(praId));
157
+ }
158
+ }
159
+ return { principal, adicionales, mandatory, substitutions };
160
+ }
161
+ // ─── Internal helpers ───────────────────────────────────────────────────────
162
+ /**
163
+ * Extracts the EGES pra_id from a canonical of the form
164
+ * `${EGES_PRACTICA_SYSTEM}/{pra_id}`.
165
+ *
166
+ * @param canonical - the canonical URL
167
+ * @returns the pra_id, or undefined when absent or non-numeric
168
+ */
169
+ function praIdFromCanonical(canonical) {
170
+ if (!(canonical === null || canonical === void 0 ? void 0 : canonical.startsWith(`${constants_1.EGES_PRACTICA_SYSTEM}/`))) {
171
+ return undefined;
172
+ }
173
+ const praId = parseInt(canonical.slice(constants_1.EGES_PRACTICA_SYSTEM.length + 1), 10);
174
+ return isNaN(praId) ? undefined : praId;
175
+ }
176
+ /**
177
+ * Extracts the EGES pra_id from an order-set PD's identifiers.
178
+ *
179
+ * @param pd - an order-set PlanDefinition
180
+ * @returns the pra_id, or undefined when the identifier is absent/non-numeric
181
+ */
182
+ function getOrderSetPraId(pd) {
183
+ var _a, _b;
184
+ const value = (_b = (_a = pd.identifier) === null || _a === void 0 ? void 0 : _a.find((i) => i.system === constants_1.EGES_PRACTICA_SYSTEM)) === null || _b === void 0 ? void 0 : _b.value;
185
+ if (value === undefined) {
186
+ return undefined;
187
+ }
188
+ const praId = parseInt(value, 10);
189
+ return isNaN(praId) ? undefined : praId;
190
+ }
191
+ function requireOrderSetPraId(pd) {
192
+ var _a;
193
+ const praId = getOrderSetPraId(pd);
194
+ if (praId === undefined) {
195
+ throw new Error(`PlanDefinition/${(_a = pd.id) !== null && _a !== void 0 ? _a : '?'} carries no EGES práctica identifier`);
196
+ }
197
+ return praId;
198
+ }
199
+ /**
200
+ * Reads an order-set PD referenced by either a `PlanDefinition/{id}` local
201
+ * reference or a `${EGES_PRACTICA_SYSTEM}/{pra_id}` canonical.
202
+ *
203
+ * @param medplum - Medplum client
204
+ * @param ref - the order-set reference
205
+ * @returns the order-set PlanDefinition
206
+ */
207
+ async function readOrderSet(medplum, ref) {
208
+ if (ref.startsWith('PlanDefinition/')) {
209
+ return medplum.readResource('PlanDefinition', ref.split('/')[1]);
210
+ }
211
+ const praId = praIdFromCanonical(ref);
212
+ if (praId !== undefined) {
213
+ return readOrderSetByPraId(medplum, praId);
214
+ }
215
+ throw new Error(`Unsupported order-set reference "${ref}"`);
216
+ }
217
+ async function readOrderSetByPraId(medplum, praId) {
218
+ const pd = await medplum.searchOne('PlanDefinition', `identifier=${constants_1.EGES_PRACTICA_SYSTEM}|${praId}`);
219
+ if (!pd) {
220
+ throw new Error(`No order-set PlanDefinition found for EGES práctica ${praId}`);
221
+ }
222
+ return pd;
223
+ }
224
+ /**
225
+ * Service type + duration facts for one effective práctica. `explicitDuration`
226
+ * (from the protocol-group action) wins when the práctica was not substituted;
227
+ * a substituted práctica falls back to the target order-set's own main-action
228
+ * duration.
229
+ *
230
+ * @param pd - the effective order-set PlanDefinition
231
+ * @param explicitDuration - protocol-group action duration, when applicable
232
+ * @returns the service type and duration facts (fields omitted when unknown)
233
+ */
234
+ function buildOrderSetFacts(pd, explicitDuration) {
235
+ var _a, _b, _c, _d;
236
+ const serviceType = (_b = (_a = pd.identifier) === null || _a === void 0 ? void 0 : _a.find((i) => i.system === constants_1.SERVICE_TYPE_SYSTEM)) === null || _b === void 0 ? void 0 : _b.value;
237
+ const duration = explicitDuration !== null && explicitDuration !== void 0 ? explicitDuration : (_d = (_c = pd.action) === null || _c === void 0 ? void 0 : _c.find((a) => a.id === 'main')) === null || _d === void 0 ? void 0 : _d.timingDuration;
238
+ return {
239
+ ...(serviceType !== undefined ? { serviceType } : {}),
240
+ ...(duration !== undefined ? { duration } : {})
241
+ };
242
+ }
243
+ //# sourceMappingURL=practica-resolution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"practica-resolution.js","sourceRoot":"","sources":["../src/practica-resolution.ts"],"names":[],"mappings":"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","sourcesContent":["import type { MedplumClient } from '@medplum/core'\nimport type { Duration, PlanDefinition, PlanDefinitionAction } from '@medplum/fhirtypes'\nimport {\n EGES_OBRA_SOCIAL_PLAN_SYSTEM,\n EGES_PRACTICA_SYSTEM,\n HS_PROTOCOL_EXTENSION_URL,\n PRACTICA_MAPPING_CONCEPT_MAP_URL,\n SERVICE_TYPE_SYSTEM\n} from './constants'\nimport {\n isProtocolGroup,\n resolveProtocolSelection,\n resolveVariantMandatoryAddons,\n type ProtocolSelection\n} from './plan-definition-group'\nimport {\n computePatientAge,\n matchPracticaMapping,\n readPracticaMappingRules,\n type PracticaMappingContext,\n type PracticaMappingMatchedCondition,\n type PracticaMappingRule,\n type PracticaMappingSex\n} from './practica-mapping'\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/**\n * Patient facts that parameterize práctica resolution. All fields optional —\n * a missing field simply never matches the corresponding rule condition.\n * Insurance is the EGES `obraSocialPlanId` (there is no Coverage resource);\n * the resolver maps it to the migrated `InsurancePlan` / payer `Organization`.\n */\nexport interface PatientContext {\n /** EGES obra social plan id (`osp_id`), as carried by the booking. */\n readonly insurancePlanId?: number\n /** ISO date (`YYYY-MM-DD`). */\n readonly birthDate?: string\n readonly sex?: PracticaMappingSex\n}\n\n/** One práctica in the effective set. */\nexport interface EffectivePractica {\n /** EGES pra_id sent to the middleware. */\n readonly praId: number\n /** `SERVICE_TYPE_SYSTEM` code, when the order-set PD carries one. */\n readonly serviceType?: string\n readonly duration?: Duration\n}\n\n/** Audit record of one applied mapping substitution. */\nexport interface PracticaSubstitution {\n readonly from: number\n readonly to: number\n readonly matchedConditions: readonly PracticaMappingMatchedCondition[]\n}\n\n/**\n * The full effective set of EGES práctica ids for one study selection —\n * the single source of truth carried through availability search and\n * appointment creation.\n */\nexport interface EffectivePracticaSet {\n /** The principal práctica (tipo 1). */\n readonly principal: EffectivePractica\n /** Patient-toggled adicionales (tipo 2). */\n readonly adicionales: readonly EffectivePractica[]\n /** Mandatory adicionales (tipo 3): order-set defaults + variant overrides. */\n readonly mandatory: readonly number[]\n /** Every mapping substitution applied while resolving the set. */\n readonly substitutions: readonly PracticaSubstitution[]\n}\n\nexport interface ResolveEffectivePracticasParams {\n /** The HealthcareService the patient picked. */\n readonly hsId: string\n /**\n * The protocol-group selection (variant + toggled addons). Required when\n * the HS protocol is a protocol-group; ignored for single-variant studies.\n */\n readonly selection?: ProtocolSelection\n /** Patient facts for the mapping rules; omit to resolve without mapping. */\n readonly context?: PatientContext\n}\n\n// ─── Registry / context loading ─────────────────────────────────────────────\n\n/**\n * Loads the práctica-mapping rules from the singleton ConceptMap.\n *\n * @param medplum - Medplum client\n * @returns the registry rules, or `[]` when no registry exists\n */\nexport async function loadPracticaMappingRules (medplum: MedplumClient): Promise<PracticaMappingRule[]> {\n const conceptMap = await medplum.searchOne('ConceptMap', `url=${PRACTICA_MAPPING_CONCEPT_MAP_URL}`)\n return readPracticaMappingRules(conceptMap)\n}\n\n/**\n * Resolves the patient context into the references the mapping rules match\n * against: the EGES plan id becomes the migrated `InsurancePlan` reference\n * (matched by `EGES_OBRA_SOCIAL_PLAN_SYSTEM` identifier) and, via `ownedBy`,\n * its payer `Organization` reference.\n *\n * @param medplum - Medplum client\n * @param context - the raw patient context (may be undefined)\n * @returns the resolved mapping context (empty when no context was given)\n */\nexport async function resolvePracticaMappingContext (\n medplum: MedplumClient,\n context: PatientContext | undefined\n): Promise<PracticaMappingContext> {\n if (!context) {\n return {}\n }\n\n let insurancePlanRef: string | undefined\n let insuranceOrgRef: string | undefined\n if (context.insurancePlanId !== undefined) {\n const plan = await medplum.searchOne(\n 'InsurancePlan',\n `identifier=${EGES_OBRA_SOCIAL_PLAN_SYSTEM}|${context.insurancePlanId}`\n )\n if (plan?.id) {\n insurancePlanRef = `InsurancePlan/${plan.id}`\n insuranceOrgRef = plan.ownedBy?.reference\n }\n }\n\n const age = context.birthDate !== undefined ? computePatientAge(context.birthDate) : undefined\n\n return {\n ...(insurancePlanRef !== undefined ? { insurancePlanRef } : {}),\n ...(insuranceOrgRef !== undefined ? { insuranceOrgRef } : {}),\n ...(age !== undefined ? { age } : {}),\n ...(context.sex !== undefined ? { sex: context.sex } : {})\n }\n}\n\n// ─── Resolution ─────────────────────────────────────────────────────────────\n\n/**\n * Resolves a study selection into the effective set of EGES práctica ids,\n * applying the práctica-mapping registry (patient insurance / age / sex).\n *\n * This is the single canonical resolver behind both the availability search\n * (via the extended `$resolve-protocol` operation) and appointment creation\n * (in-process in the `create-appointment` bot):\n *\n * 1. `HealthcareService` → `hs-protocol` → protocol PD.\n * 2. Protocol-group: `resolveProtocolSelection` yields the main + tipo-2\n * addon order-set refs. Single-variant: the PD itself is the order-set.\n * 3. Mapping applies at **order-set granularity, before expansion**: a\n * substituted principal contributes the *target* order-set's service type,\n * duration and tipo-3 defaults.\n * 4. Tipo-3 = (effective order-set defaults ∪ variant `'add'` overrides)\n * \\ `'remove'` overrides, then id-level mapping on each.\n *\n * Substitution is single-pass: a target pra_id is never re-mapped.\n *\n * @param medplum - Medplum client\n * @param params - the study selection + patient context\n * @returns the effective práctica set\n * @throws when the HS has no protocol, the selection is missing for a\n * protocol-group, or a mapping target's order-set PD does not exist\n */\nexport async function resolveEffectivePracticas (\n medplum: MedplumClient,\n params: ResolveEffectivePracticasParams\n): Promise<EffectivePracticaSet> {\n const hs = await medplum.readResource('HealthcareService', params.hsId)\n const pdRef = hs.extension?.find((e) => e.url === HS_PROTOCOL_EXTENSION_URL)?.valueReference?.reference\n if (!pdRef?.startsWith('PlanDefinition/')) {\n throw new Error(`HealthcareService/${params.hsId} has no hs-protocol PlanDefinition reference`)\n }\n const protocolPd = await medplum.readResource('PlanDefinition', pdRef.split('/')[1])\n\n const rules = await loadPracticaMappingRules(medplum)\n const mappingContext = rules.length > 0\n ? await resolvePracticaMappingContext(medplum, params.context)\n : {}\n\n const substitutions: PracticaSubstitution[] = []\n const substitute = (praId: number): number => {\n const match = matchPracticaMapping(rules, praId, mappingContext)\n if (!match) {\n return praId\n }\n substitutions.push({\n from: praId,\n to: match.rule.targetPraId,\n matchedConditions: match.matchedConditions\n })\n return match.rule.targetPraId\n }\n\n let mainOrderSet: PlanDefinition\n let mainDuration: Duration | undefined\n let addonRefs: { ref: string, duration?: Duration }[] = []\n let variantAction: PlanDefinitionAction | undefined\n\n if (isProtocolGroup(protocolPd)) {\n if (!params.selection) {\n throw new Error('A protocol selection is required for a protocol-group HealthcareService')\n }\n const resolved = resolveProtocolSelection(protocolPd, params.selection)\n variantAction = protocolPd.action?.find((a) => a.id === params.selection?.actionId)\n mainOrderSet = await readOrderSet(medplum, resolved.main.protocolRef)\n mainDuration = resolved.main.duration\n addonRefs = resolved.addons.map((a) => ({ ref: a.protocolRef, duration: a.duration }))\n } else {\n mainOrderSet = protocolPd\n }\n\n // Principal: substitute at order-set granularity (swap before expansion).\n const mainPraId = requireOrderSetPraId(mainOrderSet)\n const effectiveMainPraId = substitute(mainPraId)\n const effectiveMainOrderSet = effectiveMainPraId === mainPraId\n ? mainOrderSet\n : await readOrderSetByPraId(medplum, effectiveMainPraId)\n const principal: EffectivePractica = {\n praId: effectiveMainPraId,\n ...buildOrderSetFacts(\n effectiveMainOrderSet,\n effectiveMainPraId === mainPraId ? mainDuration : undefined\n )\n }\n\n // Tipo-2 adicionales: same order-set-level substitution per addon.\n const adicionales: EffectivePractica[] = []\n for (const addon of addonRefs) {\n const orderSet = await readOrderSet(medplum, addon.ref)\n const praId = requireOrderSetPraId(orderSet)\n const effectivePraId = substitute(praId)\n const effectiveOrderSet = effectivePraId === praId\n ? orderSet\n : await readOrderSetByPraId(medplum, effectivePraId)\n adicionales.push({\n praId: effectivePraId,\n ...buildOrderSetFacts(effectiveOrderSet, effectivePraId === praId ? addon.duration : undefined)\n })\n }\n\n // Tipo-3: defaults come from the *effective* principal order-set, then the\n // selected variant's add/remove overrides, then id-level mapping.\n const defaultRefs = (effectiveMainOrderSet.action?.find((a) => a.id === 'addons')?.action ?? [])\n .filter((addon) => addon.requiredBehavior === 'must' && addon.definitionCanonical)\n .map((addon) => addon.definitionCanonical as string)\n const mandatoryRefs = resolveVariantMandatoryAddons(defaultRefs, variantAction)\n const mandatory: number[] = []\n for (const ref of mandatoryRefs) {\n const praId = praIdFromCanonical(ref)\n if (praId !== undefined) {\n mandatory.push(substitute(praId))\n }\n }\n\n return { principal, adicionales, mandatory, substitutions }\n}\n\n// ─── Internal helpers ───────────────────────────────────────────────────────\n\n/**\n * Extracts the EGES pra_id from a canonical of the form\n * `${EGES_PRACTICA_SYSTEM}/{pra_id}`.\n *\n * @param canonical - the canonical URL\n * @returns the pra_id, or undefined when absent or non-numeric\n */\nexport function praIdFromCanonical (canonical: string | undefined): number | undefined {\n if (!canonical?.startsWith(`${EGES_PRACTICA_SYSTEM}/`)) {\n return undefined\n }\n const praId = parseInt(canonical.slice(EGES_PRACTICA_SYSTEM.length + 1), 10)\n return isNaN(praId) ? undefined : praId\n}\n\n/**\n * Extracts the EGES pra_id from an order-set PD's identifiers.\n *\n * @param pd - an order-set PlanDefinition\n * @returns the pra_id, or undefined when the identifier is absent/non-numeric\n */\nexport function getOrderSetPraId (pd: PlanDefinition): number | undefined {\n const value = pd.identifier?.find((i) => i.system === EGES_PRACTICA_SYSTEM)?.value\n if (value === undefined) {\n return undefined\n }\n const praId = parseInt(value, 10)\n return isNaN(praId) ? undefined : praId\n}\n\nfunction requireOrderSetPraId (pd: PlanDefinition): number {\n const praId = getOrderSetPraId(pd)\n if (praId === undefined) {\n throw new Error(`PlanDefinition/${pd.id ?? '?'} carries no EGES práctica identifier`)\n }\n return praId\n}\n\n/**\n * Reads an order-set PD referenced by either a `PlanDefinition/{id}` local\n * reference or a `${EGES_PRACTICA_SYSTEM}/{pra_id}` canonical.\n *\n * @param medplum - Medplum client\n * @param ref - the order-set reference\n * @returns the order-set PlanDefinition\n */\nasync function readOrderSet (medplum: MedplumClient, ref: string): Promise<PlanDefinition> {\n if (ref.startsWith('PlanDefinition/')) {\n return medplum.readResource('PlanDefinition', ref.split('/')[1])\n }\n const praId = praIdFromCanonical(ref)\n if (praId !== undefined) {\n return readOrderSetByPraId(medplum, praId)\n }\n throw new Error(`Unsupported order-set reference \"${ref}\"`)\n}\n\nasync function readOrderSetByPraId (medplum: MedplumClient, praId: number): Promise<PlanDefinition> {\n const pd = await medplum.searchOne('PlanDefinition', `identifier=${EGES_PRACTICA_SYSTEM}|${praId}`)\n if (!pd) {\n throw new Error(`No order-set PlanDefinition found for EGES práctica ${praId}`)\n }\n return pd\n}\n\n/**\n * Service type + duration facts for one effective práctica. `explicitDuration`\n * (from the protocol-group action) wins when the práctica was not substituted;\n * a substituted práctica falls back to the target order-set's own main-action\n * duration.\n *\n * @param pd - the effective order-set PlanDefinition\n * @param explicitDuration - protocol-group action duration, when applicable\n * @returns the service type and duration facts (fields omitted when unknown)\n */\nfunction buildOrderSetFacts (\n pd: PlanDefinition,\n explicitDuration: Duration | undefined\n): { serviceType?: string, duration?: Duration } {\n const serviceType = pd.identifier?.find((i) => i.system === SERVICE_TYPE_SYSTEM)?.value\n const duration = explicitDuration ?? pd.action?.find((a) => a.id === 'main')?.timingDuration\n return {\n ...(serviceType !== undefined ? { serviceType } : {}),\n ...(duration !== undefined ? { duration } : {})\n }\n}\n"]}