@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,286 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildPracticaMappingConceptMap = buildPracticaMappingConceptMap;
4
+ exports.readPracticaMappingRules = readPracticaMappingRules;
5
+ exports.matchPracticaMapping = matchPracticaMapping;
6
+ exports.computePatientAge = computePatientAge;
7
+ const constants_1 = require("./constants");
8
+ // ─── ConceptMap serialization ────────────────────────────────────────────────
9
+ /**
10
+ * Builds the singleton práctica-mapping ConceptMap from a list of rules.
11
+ * Rule order is preserved (it is the final precedence tie-break).
12
+ *
13
+ * @param rules - the mapping rules to serialize
14
+ * @param existingId - optional resource id to reuse when updating
15
+ * @returns the ConceptMap resource ready to be saved
16
+ */
17
+ function buildPracticaMappingConceptMap(rules, existingId) {
18
+ var _a;
19
+ const elementsBySource = new Map();
20
+ for (const rule of rules) {
21
+ let element = elementsBySource.get(rule.sourcePraId);
22
+ if (!element) {
23
+ element = { code: String(rule.sourcePraId), target: [] };
24
+ elementsBySource.set(rule.sourcePraId, element);
25
+ }
26
+ (_a = element.target) === null || _a === void 0 ? void 0 : _a.push(buildTarget(rule));
27
+ }
28
+ const conceptMap = {
29
+ resourceType: 'ConceptMap',
30
+ url: constants_1.PRACTICA_MAPPING_CONCEPT_MAP_URL,
31
+ status: 'active',
32
+ name: 'PracticaMapping',
33
+ title: 'Mapeo de prácticas por cobertura, edad y sexo',
34
+ meta: {
35
+ tag: [{ system: constants_1.BILLING_CODES_TAG_SYSTEM, code: constants_1.PRACTICA_MAPPING_TAG_CODE }]
36
+ },
37
+ group: [
38
+ {
39
+ source: constants_1.EGES_PRACTICA_SYSTEM,
40
+ target: constants_1.EGES_PRACTICA_SYSTEM,
41
+ element: Array.from(elementsBySource.values())
42
+ }
43
+ ]
44
+ };
45
+ if (existingId) {
46
+ conceptMap.id = existingId;
47
+ }
48
+ return conceptMap;
49
+ }
50
+ /**
51
+ * Reads the mapping rules out of the práctica-mapping ConceptMap, preserving
52
+ * element/target order. Targets with a non-numeric code or an unreadable
53
+ * condition extension are skipped.
54
+ *
55
+ * @param conceptMap - the mapping registry ConceptMap (may be undefined)
56
+ * @returns the parsed rules (empty when the registry is absent or empty)
57
+ */
58
+ function readPracticaMappingRules(conceptMap) {
59
+ var _a, _b, _c;
60
+ const rules = [];
61
+ for (const group of (_a = conceptMap === null || conceptMap === void 0 ? void 0 : conceptMap.group) !== null && _a !== void 0 ? _a : []) {
62
+ if (group.source !== constants_1.EGES_PRACTICA_SYSTEM || group.target !== constants_1.EGES_PRACTICA_SYSTEM) {
63
+ continue;
64
+ }
65
+ for (const element of (_b = group.element) !== null && _b !== void 0 ? _b : []) {
66
+ const sourcePraId = parsePraId(element.code);
67
+ if (sourcePraId === undefined) {
68
+ continue;
69
+ }
70
+ for (const target of (_c = element.target) !== null && _c !== void 0 ? _c : []) {
71
+ const targetPraId = parsePraId(target.code);
72
+ if (targetPraId === undefined) {
73
+ continue;
74
+ }
75
+ rules.push({
76
+ sourcePraId,
77
+ targetPraId,
78
+ condition: readCondition(target),
79
+ ...(target.display !== undefined ? { display: target.display } : {})
80
+ });
81
+ }
82
+ }
83
+ }
84
+ return rules;
85
+ }
86
+ // ─── Matching ────────────────────────────────────────────────────────────────
87
+ /**
88
+ * Finds the winning rule for a source práctica under the given context.
89
+ *
90
+ * Precedence among matching rules:
91
+ * 1. Most matched condition dimensions wins.
92
+ * 2. Insurance tier breaks ties: a plan-tier match beats an obra-social-tier match.
93
+ * 3. Registry order breaks remaining ties (first rule wins).
94
+ *
95
+ * @param rules - all registry rules (in registry order)
96
+ * @param praId - the source pra_id being resolved
97
+ * @param context - the patient-side facts to match against
98
+ * @returns the winning match, or undefined when no rule applies (identity)
99
+ */
100
+ function matchPracticaMapping(rules, praId, context) {
101
+ let winner;
102
+ for (const rule of rules) {
103
+ if (rule.sourcePraId !== praId) {
104
+ continue;
105
+ }
106
+ const matchedConditions = matchConditions(rule.condition, context);
107
+ if (!matchedConditions) {
108
+ continue;
109
+ }
110
+ if (!winner || beats(matchedConditions, winner.matchedConditions)) {
111
+ winner = { rule, matchedConditions };
112
+ }
113
+ }
114
+ return winner;
115
+ }
116
+ /**
117
+ * Evaluates a rule's conditions against the context.
118
+ *
119
+ * @param condition - the rule's conditions
120
+ * @param context - the patient-side facts
121
+ * @returns the matched dimensions, or undefined when any condition fails
122
+ */
123
+ function matchConditions(condition, context) {
124
+ const matched = [];
125
+ if (condition.insuranceRef !== undefined) {
126
+ if (condition.insuranceRef === context.insurancePlanRef) {
127
+ matched.push('insurance-plan');
128
+ }
129
+ else if (condition.insuranceRef === context.insuranceOrgRef) {
130
+ matched.push('insurance-org');
131
+ }
132
+ else {
133
+ return undefined;
134
+ }
135
+ }
136
+ if (condition.ageRange !== undefined) {
137
+ if (context.age === undefined || !isAgeInRange(context.age, condition.ageRange)) {
138
+ return undefined;
139
+ }
140
+ matched.push('age');
141
+ }
142
+ if (condition.sex !== undefined) {
143
+ if (condition.sex !== context.sex) {
144
+ return undefined;
145
+ }
146
+ matched.push('sex');
147
+ }
148
+ return matched;
149
+ }
150
+ /**
151
+ * Whether match `a` takes precedence over match `b`: more matched dimensions
152
+ * first, then plan tier over obra-social tier. Equal specificity keeps `b`
153
+ * (registry order).
154
+ *
155
+ * @param a - the challenger's matched dimensions
156
+ * @param b - the current winner's matched dimensions
157
+ * @returns whether `a` wins over `b`
158
+ */
159
+ function beats(a, b) {
160
+ if (a.length !== b.length) {
161
+ return a.length > b.length;
162
+ }
163
+ return a.includes('insurance-plan') && b.includes('insurance-org');
164
+ }
165
+ /**
166
+ * Computes a patient's exact age (completed years, months, and days) at the
167
+ * given date, so `ageRange` bounds in any unit compare correctly.
168
+ *
169
+ * @param birthDate - ISO date string (`YYYY-MM-DD`)
170
+ * @param at - the reference date (defaults to now)
171
+ * @returns the age, or undefined for an unparsable or future birth date
172
+ */
173
+ function computePatientAge(birthDate, at = new Date()) {
174
+ const parsed = new Date(birthDate);
175
+ if (isNaN(parsed.getTime()) || parsed.getTime() > at.getTime()) {
176
+ return undefined;
177
+ }
178
+ let months = (at.getUTCFullYear() - parsed.getUTCFullYear()) * 12 + (at.getUTCMonth() - parsed.getUTCMonth());
179
+ if (at.getUTCDate() < parsed.getUTCDate()) {
180
+ months--;
181
+ }
182
+ if (months < 0) {
183
+ months = 0;
184
+ }
185
+ const days = Math.floor((at.getTime() - parsed.getTime()) / 86400000);
186
+ return { years: Math.floor(months / 12), months, days };
187
+ }
188
+ // ─── Internal helpers ───────────────────────────────────────────────────────
189
+ /**
190
+ * Normalizes a bound Quantity's unit to years / months / days. Accepts UCUM
191
+ * codes (`a`, `mo`, `d`) plus common Spanish/English unit strings; absent or
192
+ * unknown units default to **years** (the pre-units rule format).
193
+ *
194
+ * @param quantity - a Range bound
195
+ * @param quantity.code - the bound's UCUM code, when present
196
+ * @param quantity.unit - the bound's display unit, used as fallback
197
+ * @returns the normalized age unit
198
+ */
199
+ function boundAgeUnit(quantity) {
200
+ var _a, _b;
201
+ const raw = ((_b = (_a = quantity.code) !== null && _a !== void 0 ? _a : quantity.unit) !== null && _b !== void 0 ? _b : '').toLowerCase();
202
+ if (raw === 'mo' || raw.startsWith('mes') || raw.startsWith('month')) {
203
+ return 'mo';
204
+ }
205
+ if (raw === 'd' || raw.startsWith('día') || raw.startsWith('dia') || raw.startsWith('day')) {
206
+ return 'd';
207
+ }
208
+ return 'a';
209
+ }
210
+ function ageInUnit(age, unit) {
211
+ if (unit === 'mo') {
212
+ return age.months;
213
+ }
214
+ if (unit === 'd') {
215
+ return age.days;
216
+ }
217
+ return age.years;
218
+ }
219
+ /**
220
+ * Inclusive range check with per-bound units: each bound is compared against
221
+ * the patient's completed age in that bound's own unit, so mixed ranges like
222
+ * "desde 6 meses hasta 2 años" work as written.
223
+ *
224
+ * @param age - the patient's exact age
225
+ * @param range - the rule's ageRange
226
+ * @returns whether the age satisfies both bounds
227
+ */
228
+ function isAgeInRange(age, range) {
229
+ var _a, _b;
230
+ if (((_a = range.low) === null || _a === void 0 ? void 0 : _a.value) !== undefined && ageInUnit(age, boundAgeUnit(range.low)) < range.low.value) {
231
+ return false;
232
+ }
233
+ if (((_b = range.high) === null || _b === void 0 ? void 0 : _b.value) !== undefined && ageInUnit(age, boundAgeUnit(range.high)) > range.high.value) {
234
+ return false;
235
+ }
236
+ return true;
237
+ }
238
+ function parsePraId(code) {
239
+ if (!code) {
240
+ return undefined;
241
+ }
242
+ const parsed = parseInt(code, 10);
243
+ return isNaN(parsed) ? undefined : parsed;
244
+ }
245
+ function buildTarget(rule) {
246
+ const conditionExtensions = [];
247
+ if (rule.condition.insuranceRef !== undefined) {
248
+ conditionExtensions.push({ url: 'insurance', valueReference: { reference: rule.condition.insuranceRef } });
249
+ }
250
+ if (rule.condition.ageRange !== undefined) {
251
+ conditionExtensions.push({ url: 'ageRange', valueRange: rule.condition.ageRange });
252
+ }
253
+ if (rule.condition.sex !== undefined) {
254
+ conditionExtensions.push({ url: 'sex', valueCode: rule.condition.sex });
255
+ }
256
+ const target = {
257
+ code: String(rule.targetPraId),
258
+ equivalence: 'equivalent'
259
+ };
260
+ if (rule.display !== undefined) {
261
+ target.display = rule.display;
262
+ }
263
+ if (conditionExtensions.length > 0) {
264
+ target.extension = [
265
+ {
266
+ url: constants_1.PRACTICA_MAPPING_CONDITION_EXTENSION_URL,
267
+ extension: conditionExtensions
268
+ }
269
+ ];
270
+ }
271
+ return target;
272
+ }
273
+ function readCondition(target) {
274
+ var _a, _b, _c, _d, _e, _f;
275
+ const container = (_a = target.extension) === null || _a === void 0 ? void 0 : _a.find((e) => e.url === constants_1.PRACTICA_MAPPING_CONDITION_EXTENSION_URL);
276
+ const subs = (_b = container === null || container === void 0 ? void 0 : container.extension) !== null && _b !== void 0 ? _b : [];
277
+ const insuranceRef = (_d = (_c = subs.find((e) => e.url === 'insurance')) === null || _c === void 0 ? void 0 : _c.valueReference) === null || _d === void 0 ? void 0 : _d.reference;
278
+ const ageRange = (_e = subs.find((e) => e.url === 'ageRange')) === null || _e === void 0 ? void 0 : _e.valueRange;
279
+ const sex = (_f = subs.find((e) => e.url === 'sex')) === null || _f === void 0 ? void 0 : _f.valueCode;
280
+ return {
281
+ ...(insuranceRef !== undefined ? { insuranceRef } : {}),
282
+ ...(ageRange !== undefined ? { ageRange } : {}),
283
+ ...(sex !== undefined ? { sex } : {})
284
+ };
285
+ }
286
+ //# sourceMappingURL=practica-mapping.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"practica-mapping.js","sourceRoot":"","sources":["../src/practica-mapping.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ConceptMap, ConceptMapGroupElement, ConceptMapGroupElementTarget, Extension, Range } from '@medplum/fhirtypes'\nimport {\n BILLING_CODES_TAG_SYSTEM,\n EGES_PRACTICA_SYSTEM,\n PRACTICA_MAPPING_CONCEPT_MAP_URL,\n PRACTICA_MAPPING_CONDITION_EXTENSION_URL,\n PRACTICA_MAPPING_TAG_CODE\n} from './constants'\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** FHIR administrative gender codes accepted by mapping conditions. */\nexport type PracticaMappingSex = 'male' | 'female' | 'other' | 'unknown'\n\n/**\n * Conditions under which a mapping rule applies. All present conditions must\n * match (logical AND); a rule with no conditions is an unconditional override.\n */\nexport interface PracticaMappingCondition {\n /**\n * Reference to an `InsurancePlan` (plan tier) or a payer `Organization`\n * (obra social tier), e.g. `InsurancePlan/123` / `Organization/45`.\n */\n readonly insuranceRef?: string\n /**\n * Patient age range; bounds inclusive, either side may be open. Each bound\n * Quantity may carry a UCUM code (`a` years, `mo` months, `d` days) —\n * absent unit means years — and each bound is evaluated in its own unit.\n */\n readonly ageRange?: Range\n /** FHIR administrative gender. */\n readonly sex?: PracticaMappingSex\n}\n\n/** One práctica → práctica substitution rule from the mapping registry. */\nexport interface PracticaMappingRule {\n /** EGES pra_id the rule applies to. */\n readonly sourcePraId: number\n /** EGES pra_id substituted when the rule matches. */\n readonly targetPraId: number\n readonly condition: PracticaMappingCondition\n /** Optional human label for the admin editor. */\n readonly display?: string\n}\n\n/** Units accepted on `ageRange` bounds (UCUM): years, months, days. */\nexport type PracticaAgeUnit = 'a' | 'mo' | 'd'\n\n/**\n * The patient's exact age at resolution time, floored per calendar unit, so\n * `ageRange` bounds expressed in days, months, or years all compare exactly.\n */\nexport interface PatientAge {\n /** Completed calendar years. */\n readonly years: number\n /** Completed calendar months. */\n readonly months: number\n /** Completed days. */\n readonly days: number\n}\n\n/**\n * The patient-side facts a rule's conditions are matched against. Resolved\n * once per booking (see `resolvePracticaMappingContext`).\n */\nexport interface PracticaMappingContext {\n /** `InsurancePlan/{id}` reference of the patient's plan, when known. */\n readonly insurancePlanRef?: string\n /** `Organization/{id}` reference of the plan's payer, when known. */\n readonly insuranceOrgRef?: string\n /** Patient age at resolution time, when known. */\n readonly age?: PatientAge\n readonly sex?: PracticaMappingSex\n}\n\n/** Which condition dimensions a matching rule satisfied (for audit/UI). */\nexport type PracticaMappingMatchedCondition = 'insurance-plan' | 'insurance-org' | 'age' | 'sex'\n\n/** A matched rule plus the dimensions it matched on. */\nexport interface PracticaMappingMatch {\n readonly rule: PracticaMappingRule\n readonly matchedConditions: readonly PracticaMappingMatchedCondition[]\n}\n\n// ─── ConceptMap serialization ────────────────────────────────────────────────\n\n/**\n * Builds the singleton práctica-mapping ConceptMap from a list of rules.\n * Rule order is preserved (it is the final precedence tie-break).\n *\n * @param rules - the mapping rules to serialize\n * @param existingId - optional resource id to reuse when updating\n * @returns the ConceptMap resource ready to be saved\n */\nexport function buildPracticaMappingConceptMap (\n rules: readonly PracticaMappingRule[],\n existingId?: string\n): ConceptMap {\n const elementsBySource = new Map<number, ConceptMapGroupElement>()\n for (const rule of rules) {\n let element = elementsBySource.get(rule.sourcePraId)\n if (!element) {\n element = { code: String(rule.sourcePraId), target: [] }\n elementsBySource.set(rule.sourcePraId, element)\n }\n element.target?.push(buildTarget(rule))\n }\n\n const conceptMap: ConceptMap = {\n resourceType: 'ConceptMap',\n url: PRACTICA_MAPPING_CONCEPT_MAP_URL,\n status: 'active',\n name: 'PracticaMapping',\n title: 'Mapeo de prácticas por cobertura, edad y sexo',\n meta: {\n tag: [{ system: BILLING_CODES_TAG_SYSTEM, code: PRACTICA_MAPPING_TAG_CODE }]\n },\n group: [\n {\n source: EGES_PRACTICA_SYSTEM,\n target: EGES_PRACTICA_SYSTEM,\n element: Array.from(elementsBySource.values())\n }\n ]\n }\n\n if (existingId) {\n conceptMap.id = existingId\n }\n\n return conceptMap\n}\n\n/**\n * Reads the mapping rules out of the práctica-mapping ConceptMap, preserving\n * element/target order. Targets with a non-numeric code or an unreadable\n * condition extension are skipped.\n *\n * @param conceptMap - the mapping registry ConceptMap (may be undefined)\n * @returns the parsed rules (empty when the registry is absent or empty)\n */\nexport function readPracticaMappingRules (conceptMap: ConceptMap | undefined): PracticaMappingRule[] {\n const rules: PracticaMappingRule[] = []\n for (const group of conceptMap?.group ?? []) {\n if (group.source !== EGES_PRACTICA_SYSTEM || group.target !== EGES_PRACTICA_SYSTEM) {\n continue\n }\n for (const element of group.element ?? []) {\n const sourcePraId = parsePraId(element.code)\n if (sourcePraId === undefined) {\n continue\n }\n for (const target of element.target ?? []) {\n const targetPraId = parsePraId(target.code)\n if (targetPraId === undefined) {\n continue\n }\n rules.push({\n sourcePraId,\n targetPraId,\n condition: readCondition(target),\n ...(target.display !== undefined ? { display: target.display } : {})\n })\n }\n }\n }\n return rules\n}\n\n// ─── Matching ────────────────────────────────────────────────────────────────\n\n/**\n * Finds the winning rule for a source práctica under the given context.\n *\n * Precedence among matching rules:\n * 1. Most matched condition dimensions wins.\n * 2. Insurance tier breaks ties: a plan-tier match beats an obra-social-tier match.\n * 3. Registry order breaks remaining ties (first rule wins).\n *\n * @param rules - all registry rules (in registry order)\n * @param praId - the source pra_id being resolved\n * @param context - the patient-side facts to match against\n * @returns the winning match, or undefined when no rule applies (identity)\n */\nexport function matchPracticaMapping (\n rules: readonly PracticaMappingRule[],\n praId: number,\n context: PracticaMappingContext\n): PracticaMappingMatch | undefined {\n let winner: PracticaMappingMatch | undefined\n\n for (const rule of rules) {\n if (rule.sourcePraId !== praId) {\n continue\n }\n const matchedConditions = matchConditions(rule.condition, context)\n if (!matchedConditions) {\n continue\n }\n if (!winner || beats(matchedConditions, winner.matchedConditions)) {\n winner = { rule, matchedConditions }\n }\n }\n\n return winner\n}\n\n/**\n * Evaluates a rule's conditions against the context.\n *\n * @param condition - the rule's conditions\n * @param context - the patient-side facts\n * @returns the matched dimensions, or undefined when any condition fails\n */\nfunction matchConditions (\n condition: PracticaMappingCondition,\n context: PracticaMappingContext\n): PracticaMappingMatchedCondition[] | undefined {\n const matched: PracticaMappingMatchedCondition[] = []\n\n if (condition.insuranceRef !== undefined) {\n if (condition.insuranceRef === context.insurancePlanRef) {\n matched.push('insurance-plan')\n } else if (condition.insuranceRef === context.insuranceOrgRef) {\n matched.push('insurance-org')\n } else {\n return undefined\n }\n }\n\n if (condition.ageRange !== undefined) {\n if (context.age === undefined || !isAgeInRange(context.age, condition.ageRange)) {\n return undefined\n }\n matched.push('age')\n }\n\n if (condition.sex !== undefined) {\n if (condition.sex !== context.sex) {\n return undefined\n }\n matched.push('sex')\n }\n\n return matched\n}\n\n/**\n * Whether match `a` takes precedence over match `b`: more matched dimensions\n * first, then plan tier over obra-social tier. Equal specificity keeps `b`\n * (registry order).\n *\n * @param a - the challenger's matched dimensions\n * @param b - the current winner's matched dimensions\n * @returns whether `a` wins over `b`\n */\nfunction beats (\n a: readonly PracticaMappingMatchedCondition[],\n b: readonly PracticaMappingMatchedCondition[]\n): boolean {\n if (a.length !== b.length) {\n return a.length > b.length\n }\n return a.includes('insurance-plan') && b.includes('insurance-org')\n}\n\n/**\n * Computes a patient's exact age (completed years, months, and days) at the\n * given date, so `ageRange` bounds in any unit compare correctly.\n *\n * @param birthDate - ISO date string (`YYYY-MM-DD`)\n * @param at - the reference date (defaults to now)\n * @returns the age, or undefined for an unparsable or future birth date\n */\nexport function computePatientAge (birthDate: string, at: Date = new Date()): PatientAge | undefined {\n const parsed = new Date(birthDate)\n if (isNaN(parsed.getTime()) || parsed.getTime() > at.getTime()) {\n return undefined\n }\n let months = (at.getUTCFullYear() - parsed.getUTCFullYear()) * 12 + (at.getUTCMonth() - parsed.getUTCMonth())\n if (at.getUTCDate() < parsed.getUTCDate()) {\n months--\n }\n if (months < 0) {\n months = 0\n }\n const days = Math.floor((at.getTime() - parsed.getTime()) / 86_400_000)\n return { years: Math.floor(months / 12), months, days }\n}\n\n// ─── Internal helpers ───────────────────────────────────────────────────────\n\n/**\n * Normalizes a bound Quantity's unit to years / months / days. Accepts UCUM\n * codes (`a`, `mo`, `d`) plus common Spanish/English unit strings; absent or\n * unknown units default to **years** (the pre-units rule format).\n *\n * @param quantity - a Range bound\n * @param quantity.code - the bound's UCUM code, when present\n * @param quantity.unit - the bound's display unit, used as fallback\n * @returns the normalized age unit\n */\nfunction boundAgeUnit (quantity: { code?: string, unit?: string }): PracticaAgeUnit {\n const raw = (quantity.code ?? quantity.unit ?? '').toLowerCase()\n if (raw === 'mo' || raw.startsWith('mes') || raw.startsWith('month')) {\n return 'mo'\n }\n if (raw === 'd' || raw.startsWith('día') || raw.startsWith('dia') || raw.startsWith('day')) {\n return 'd'\n }\n return 'a'\n}\n\nfunction ageInUnit (age: PatientAge, unit: PracticaAgeUnit): number {\n if (unit === 'mo') {\n return age.months\n }\n if (unit === 'd') {\n return age.days\n }\n return age.years\n}\n\n/**\n * Inclusive range check with per-bound units: each bound is compared against\n * the patient's completed age in that bound's own unit, so mixed ranges like\n * \"desde 6 meses hasta 2 años\" work as written.\n *\n * @param age - the patient's exact age\n * @param range - the rule's ageRange\n * @returns whether the age satisfies both bounds\n */\nfunction isAgeInRange (age: PatientAge, range: Range): boolean {\n if (range.low?.value !== undefined && ageInUnit(age, boundAgeUnit(range.low)) < range.low.value) {\n return false\n }\n if (range.high?.value !== undefined && ageInUnit(age, boundAgeUnit(range.high)) > range.high.value) {\n return false\n }\n return true\n}\n\nfunction parsePraId (code: string | undefined): number | undefined {\n if (!code) {\n return undefined\n }\n const parsed = parseInt(code, 10)\n return isNaN(parsed) ? undefined : parsed\n}\n\nfunction buildTarget (rule: PracticaMappingRule): ConceptMapGroupElementTarget {\n const conditionExtensions: Extension[] = []\n if (rule.condition.insuranceRef !== undefined) {\n conditionExtensions.push({ url: 'insurance', valueReference: { reference: rule.condition.insuranceRef } })\n }\n if (rule.condition.ageRange !== undefined) {\n conditionExtensions.push({ url: 'ageRange', valueRange: rule.condition.ageRange })\n }\n if (rule.condition.sex !== undefined) {\n conditionExtensions.push({ url: 'sex', valueCode: rule.condition.sex })\n }\n\n const target: ConceptMapGroupElementTarget = {\n code: String(rule.targetPraId),\n equivalence: 'equivalent'\n }\n if (rule.display !== undefined) {\n target.display = rule.display\n }\n if (conditionExtensions.length > 0) {\n target.extension = [\n {\n url: PRACTICA_MAPPING_CONDITION_EXTENSION_URL,\n extension: conditionExtensions\n }\n ]\n }\n return target\n}\n\nfunction readCondition (target: ConceptMapGroupElementTarget): PracticaMappingCondition {\n const container = target.extension?.find((e) => e.url === PRACTICA_MAPPING_CONDITION_EXTENSION_URL)\n const subs = container?.extension ?? []\n const insuranceRef = subs.find((e) => e.url === 'insurance')?.valueReference?.reference\n const ageRange = subs.find((e) => e.url === 'ageRange')?.valueRange\n const sex = subs.find((e) => e.url === 'sex')?.valueCode as PracticaMappingSex | undefined\n return {\n ...(insuranceRef !== undefined ? { insuranceRef } : {}),\n ...(ageRange !== undefined ? { ageRange } : {}),\n ...(sex !== undefined ? { sex } : {})\n }\n}\n"]}
@@ -0,0 +1,117 @@
1
+ import type { MedplumClient } from '@medplum/core';
2
+ import type { Duration, PlanDefinition } from '@medplum/fhirtypes';
3
+ import { type ProtocolSelection } from './plan-definition-group';
4
+ import { type PracticaMappingContext, type PracticaMappingMatchedCondition, type PracticaMappingRule, type PracticaMappingSex } from './practica-mapping';
5
+ /**
6
+ * Patient facts that parameterize práctica resolution. All fields optional —
7
+ * a missing field simply never matches the corresponding rule condition.
8
+ * Insurance is the EGES `obraSocialPlanId` (there is no Coverage resource);
9
+ * the resolver maps it to the migrated `InsurancePlan` / payer `Organization`.
10
+ */
11
+ export interface PatientContext {
12
+ /** EGES obra social plan id (`osp_id`), as carried by the booking. */
13
+ readonly insurancePlanId?: number;
14
+ /** ISO date (`YYYY-MM-DD`). */
15
+ readonly birthDate?: string;
16
+ readonly sex?: PracticaMappingSex;
17
+ }
18
+ /** One práctica in the effective set. */
19
+ export interface EffectivePractica {
20
+ /** EGES pra_id sent to the middleware. */
21
+ readonly praId: number;
22
+ /** `SERVICE_TYPE_SYSTEM` code, when the order-set PD carries one. */
23
+ readonly serviceType?: string;
24
+ readonly duration?: Duration;
25
+ }
26
+ /** Audit record of one applied mapping substitution. */
27
+ export interface PracticaSubstitution {
28
+ readonly from: number;
29
+ readonly to: number;
30
+ readonly matchedConditions: readonly PracticaMappingMatchedCondition[];
31
+ }
32
+ /**
33
+ * The full effective set of EGES práctica ids for one study selection —
34
+ * the single source of truth carried through availability search and
35
+ * appointment creation.
36
+ */
37
+ export interface EffectivePracticaSet {
38
+ /** The principal práctica (tipo 1). */
39
+ readonly principal: EffectivePractica;
40
+ /** Patient-toggled adicionales (tipo 2). */
41
+ readonly adicionales: readonly EffectivePractica[];
42
+ /** Mandatory adicionales (tipo 3): order-set defaults + variant overrides. */
43
+ readonly mandatory: readonly number[];
44
+ /** Every mapping substitution applied while resolving the set. */
45
+ readonly substitutions: readonly PracticaSubstitution[];
46
+ }
47
+ export interface ResolveEffectivePracticasParams {
48
+ /** The HealthcareService the patient picked. */
49
+ readonly hsId: string;
50
+ /**
51
+ * The protocol-group selection (variant + toggled addons). Required when
52
+ * the HS protocol is a protocol-group; ignored for single-variant studies.
53
+ */
54
+ readonly selection?: ProtocolSelection;
55
+ /** Patient facts for the mapping rules; omit to resolve without mapping. */
56
+ readonly context?: PatientContext;
57
+ }
58
+ /**
59
+ * Loads the práctica-mapping rules from the singleton ConceptMap.
60
+ *
61
+ * @param medplum - Medplum client
62
+ * @returns the registry rules, or `[]` when no registry exists
63
+ */
64
+ export declare function loadPracticaMappingRules(medplum: MedplumClient): Promise<PracticaMappingRule[]>;
65
+ /**
66
+ * Resolves the patient context into the references the mapping rules match
67
+ * against: the EGES plan id becomes the migrated `InsurancePlan` reference
68
+ * (matched by `EGES_OBRA_SOCIAL_PLAN_SYSTEM` identifier) and, via `ownedBy`,
69
+ * its payer `Organization` reference.
70
+ *
71
+ * @param medplum - Medplum client
72
+ * @param context - the raw patient context (may be undefined)
73
+ * @returns the resolved mapping context (empty when no context was given)
74
+ */
75
+ export declare function resolvePracticaMappingContext(medplum: MedplumClient, context: PatientContext | undefined): Promise<PracticaMappingContext>;
76
+ /**
77
+ * Resolves a study selection into the effective set of EGES práctica ids,
78
+ * applying the práctica-mapping registry (patient insurance / age / sex).
79
+ *
80
+ * This is the single canonical resolver behind both the availability search
81
+ * (via the extended `$resolve-protocol` operation) and appointment creation
82
+ * (in-process in the `create-appointment` bot):
83
+ *
84
+ * 1. `HealthcareService` → `hs-protocol` → protocol PD.
85
+ * 2. Protocol-group: `resolveProtocolSelection` yields the main + tipo-2
86
+ * addon order-set refs. Single-variant: the PD itself is the order-set.
87
+ * 3. Mapping applies at **order-set granularity, before expansion**: a
88
+ * substituted principal contributes the *target* order-set's service type,
89
+ * duration and tipo-3 defaults.
90
+ * 4. Tipo-3 = (effective order-set defaults ∪ variant `'add'` overrides)
91
+ * \ `'remove'` overrides, then id-level mapping on each.
92
+ *
93
+ * Substitution is single-pass: a target pra_id is never re-mapped.
94
+ *
95
+ * @param medplum - Medplum client
96
+ * @param params - the study selection + patient context
97
+ * @returns the effective práctica set
98
+ * @throws when the HS has no protocol, the selection is missing for a
99
+ * protocol-group, or a mapping target's order-set PD does not exist
100
+ */
101
+ export declare function resolveEffectivePracticas(medplum: MedplumClient, params: ResolveEffectivePracticasParams): Promise<EffectivePracticaSet>;
102
+ /**
103
+ * Extracts the EGES pra_id from a canonical of the form
104
+ * `${EGES_PRACTICA_SYSTEM}/{pra_id}`.
105
+ *
106
+ * @param canonical - the canonical URL
107
+ * @returns the pra_id, or undefined when absent or non-numeric
108
+ */
109
+ export declare function praIdFromCanonical(canonical: string | undefined): number | undefined;
110
+ /**
111
+ * Extracts the EGES pra_id from an order-set PD's identifiers.
112
+ *
113
+ * @param pd - an order-set PlanDefinition
114
+ * @returns the pra_id, or undefined when the identifier is absent/non-numeric
115
+ */
116
+ export declare function getOrderSetPraId(pd: PlanDefinition): number | undefined;
117
+ //# sourceMappingURL=practica-resolution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"practica-resolution.d.ts","sourceRoot":"","sources":["../src/practica-resolution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAwB,MAAM,oBAAoB,CAAA;AAQxF,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAIL,KAAK,sBAAsB,EAC3B,KAAK,+BAA+B,EACpC,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACxB,MAAM,oBAAoB,CAAA;AAI3B;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,sEAAsE;IACtE,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;IACjC,+BAA+B;IAC/B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,GAAG,CAAC,EAAE,kBAAkB,CAAA;CAClC;AAED,yCAAyC;AACzC,MAAM,WAAW,iBAAiB;IAChC,0CAA0C;IAC1C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,qEAAqE;IACrE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAA;CAC7B;AAED,wDAAwD;AACxD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,iBAAiB,EAAE,SAAS,+BAA+B,EAAE,CAAA;CACvE;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,uCAAuC;IACvC,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAA;IACrC,4CAA4C;IAC5C,QAAQ,CAAC,WAAW,EAAE,SAAS,iBAAiB,EAAE,CAAA;IAClD,8EAA8E;IAC9E,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAA;IACrC,kEAAkE;IAClE,QAAQ,CAAC,aAAa,EAAE,SAAS,oBAAoB,EAAE,CAAA;CACxD;AAED,MAAM,WAAW,+BAA+B;IAC9C,gDAAgD;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,iBAAiB,CAAA;IACtC,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,CAAC,EAAE,cAAc,CAAA;CAClC;AAID;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAGtG;AAED;;;;;;;;;GASG;AACH,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,cAAc,GAAG,SAAS,GAClC,OAAO,CAAC,sBAAsB,CAAC,CA0BjC;AAID;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,+BAA+B,GACtC,OAAO,CAAC,oBAAoB,CAAC,CAyF/B;AAID;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAE,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAMrF;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAE,EAAE,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAOxE"}