diff --git a/__tests__/corner_cases.test.tsx b/__tests__/corner_cases.test.tsx index 69569db1..bc6d9dab 100644 --- a/__tests__/corner_cases.test.tsx +++ b/__tests__/corner_cases.test.tsx @@ -86,20 +86,6 @@ describe("Corner cases", () => { }; let result = dosageUtils.fromDosageToText(dosage); - expect(result).toBe("at a rate of 3 pills per "); - }); - - test("maxDosePerPeriod - no denominator", () => { - const dosage: Dosage = { - maxDosePerPeriod: { - numerator: { - value: 3, - unit: "pills", - }, - }, - }; - - let result = dosageUtils.fromDosageToText(dosage); - expect(result).toBe("up to a maximum of 3 pills per "); + expect(result).toBe("at a rate of 3 pills"); }); }); diff --git a/__tests__/fromDosageToText/boundsRange.test.tsx b/__tests__/fromDosageToText/boundsRange.test.tsx index 941629da..a4f3d1f2 100644 --- a/__tests__/fromDosageToText/boundsRange.test.tsx +++ b/__tests__/fromDosageToText/boundsRange.test.tsx @@ -23,6 +23,19 @@ describe("fromDosageToText - boundsRange", () => { expect(result).toBe(""); }); + test("Empty boundsRange", () => { + const dosage: Dosage = { + timing: { + repeat: { + boundsRange: {}, + }, + }, + }; + + let result = dosageUtils.fromDosageToText(dosage); + expect(result).toBe(""); + }); + test("high and low", () => { const dosage: Dosage = { timing: { diff --git a/__tests__/fromDosageToText/doseRange.test.tsx b/__tests__/fromDosageToText/doseRange.test.tsx index 90e9096d..2022a480 100644 --- a/__tests__/fromDosageToText/doseRange.test.tsx +++ b/__tests__/fromDosageToText/doseRange.test.tsx @@ -23,6 +23,19 @@ describe("fromDosageToText - doseRange", () => { expect(result).toBe(""); }); + test("Empty doseRange", () => { + const dosage: Dosage = { + doseAndRate: [ + { + doseRange: {}, + }, + ], + }; + + let result = dosageUtils.fromDosageToText(dosage); + expect(result).toBe(""); + }); + test("high and low", () => { const dosage: Dosage = { doseAndRate: [ @@ -78,6 +91,26 @@ describe("fromDosageToText - doseRange", () => { }; let result = dosageUtils.fromDosageToText(dosage); - expect(result).toBe("3 pills"); + expect(result).toBe("at least 3 pills"); + }); + + test("No units", () => { + const dosage: Dosage = { + doseAndRate: [ + { + doseRange: { + low: { + value: 3, + }, + high: { + value: 5, + }, + }, + }, + ], + }; + + let result = dosageUtils.fromDosageToText(dosage); + expect(result).toBe("3 to 5"); }); }); diff --git a/__tests__/fromDosageToText/maxDosePerPeriod.test.tsx b/__tests__/fromDosageToText/maxDosePerPeriod.test.tsx index 77f4c746..551c0a49 100644 --- a/__tests__/fromDosageToText/maxDosePerPeriod.test.tsx +++ b/__tests__/fromDosageToText/maxDosePerPeriod.test.tsx @@ -42,21 +42,7 @@ describe("fromDosageToText - maxDosePerPeriod", () => { expect(result).toBe("up to a maximum of 3 pills every 5 hours"); }); - test("no numerator", () => { - const dosage: DosageR4 = { - maxDosePerPeriod: { - denominator: { - value: 5, - unit: "hours", - }, - }, - }; - - let result = dosageUtils.fromDosageToText(dosage); - expect(result).toBe("up to a maximum of 1 every 5 hours"); - }); - - test("no denominator value", () => { + test("denominator value equal to 1", () => { const dosage: DosageR4 = { maxDosePerPeriod: { numerator: { @@ -64,6 +50,7 @@ describe("fromDosageToText - maxDosePerPeriod", () => { unit: "pills", }, denominator: { + value: 1, unit: "hour", }, }, diff --git a/__tests__/fromDosageToText/rateRange.test.tsx b/__tests__/fromDosageToText/rateRange.test.tsx index 0b89f983..5022e3c9 100644 --- a/__tests__/fromDosageToText/rateRange.test.tsx +++ b/__tests__/fromDosageToText/rateRange.test.tsx @@ -23,6 +23,19 @@ describe("fromDosageToText - rateRange", () => { expect(result).toBe(""); }); + test("Empty rateRange", () => { + const dosage: Dosage = { + doseAndRate: [ + { + rateRange: {}, + }, + ], + }; + + let result = dosageUtils.fromDosageToText(dosage); + expect(result).toBe(""); + }); + test("high and low", () => { const dosage: Dosage = { doseAndRate: [ @@ -60,7 +73,7 @@ describe("fromDosageToText - rateRange", () => { }; let result = dosageUtils.fromDosageToText(dosage); - expect(result).toBe("at a rate of 5 pills"); + expect(result).toBe("at a rate of up to 5 pills"); }); test("low only", () => { @@ -78,6 +91,6 @@ describe("fromDosageToText - rateRange", () => { }; let result = dosageUtils.fromDosageToText(dosage); - expect(result).toBe("at a rate of 3 pills"); + expect(result).toBe("at a rate of at least 3 pills"); }); }); diff --git a/__tests__/fromDosageToText/rateRatio.test.tsx b/__tests__/fromDosageToText/rateRatio.test.tsx index 53814f02..cfe4d4fa 100644 --- a/__tests__/fromDosageToText/rateRatio.test.tsx +++ b/__tests__/fromDosageToText/rateRatio.test.tsx @@ -32,6 +32,19 @@ describe("fromDosageToText - rateRatio", () => { expect(result).toBe(""); }); + test("Empty rateRatio", () => { + const dosage: Dosage = { + doseAndRate: [ + { + rateRatio: {}, + }, + ], + }; + + let result = dosageUtils.fromDosageToText(dosage); + expect(result).toBe(""); + }); + test("numerator and denominator", () => { const dosage: Dosage = { doseAndRate: [ @@ -69,20 +82,19 @@ describe("fromDosageToText - rateRatio", () => { }; let result = dosageUtils.fromDosageToText(dosage); - expect(result).toBe("at a rate of 1 every 5 hours"); + expect(result).toBe("at a rate of every 5 hours"); }); - test("no denominator value", () => { + test("no unit", () => { const dosage: Dosage = { doseAndRate: [ { rateRatio: { numerator: { - value: 3, - unit: "pills", + value: 1, }, denominator: { - unit: "hour", + value: 128, }, }, }, @@ -90,6 +102,6 @@ describe("fromDosageToText - rateRatio", () => { }; let result = dosageUtils.fromDosageToText(dosage); - expect(result).toBe("at a rate of 3 pills per hour"); + expect(result).toBe("at a rate of 1 :128"); }); }); diff --git a/src/locales/de/common.json b/src/locales/de/common.json index f6e040c4..ea7c6baa 100644 --- a/src/locales/de/common.json +++ b/src/locales/de/common.json @@ -3,23 +3,39 @@ "and": "und", "then": "dann" }, + "amount": { + "range": { + "withUnit": { + "lowAndHigh": "zwischen {{low}} und {{high}} {{unit}}", + "onlyHigh": "bis {{high}} {{unit}}", + "onlyLow": "mindestens {{low}} {{unit}}" + }, + "withoutUnit": { + "lowAndHigh": "zwischen {{low}} und {{high}}", + "onlyHigh": "bis {{high}}", + "onlyLow": "mindestens {{low}}" + } + }, + "ratio": { + "withUnit": { + "numerator": "{{count}} {{numeratorUnit}}", + "denominator_one": "pro {{denominatorUnit}}", + "denominator_other": "jeder {{count}} {{denominatorUnit}}" + }, + "withoutUnit": { + "numerator": "{{count}}", + "denominator_one": ":{{count}}", + "denominator_other": ":{{count}}" + } + }, + "quantity": "{{quantity}} {{unit}}" + }, "fields": { "doseQuantity": "{{quantity}} {{unit}}", - "doseRange": { - "lowAndHigh": "zwischen {{low}} und {{high}} {{unit}}", - "onlyHigh": "bis {{high}} {{unit}}", - "onlyLow": "{{low}} {{unit}}" - }, + "doseRange": "{{rangeText}}", "rateQuantity": "mit einem Verhältnis von {{quantity}} {{unit}}", - "rateRange": { - "lowAndHigh": "mit einem Verhältnis zwischen {{low}} und {{high}} {{unit}}", - "onlyHigh": "mit einem Verhältnis von {{high}} {{unit}}", - "onlyLow": "mit einem Verhältnis von {{low}} {{unit}}" - }, - "rateRatio": { - "rateRatio_one": "mit einem Verhältnis von {{quantityNumerator}} {{numeratorUnit}} pro {{denominatorUnit}}", - "rateRatio_other": "mit einem Verhältnis von {{quantityNumerator}} {{numeratorUnit}} jeder {{count}} {{denominatorUnit}}" - }, + "rateRange": "mit einem Verhältnis von {{rangeText}}", + "rateRatio": "mit einem Verhältnis von {{ratioText}}", "duration": "für {{durationText}}", "durationMax": "(maximal {{durationMaxText}})", "frequency": { @@ -54,11 +70,7 @@ "onlyStart": "ab {{start}}", "onlyEnd": "bis {{end}}" }, - "boundsRange": { - "lowAndHigh": "für {{low}} bis {{high}} {{unit}}", - "onlyHigh": "bis {{high}} {{unit}}", - "onlyLow": "für mindestens {{low}} {{unit}}" - }, + "boundsRange": "für {{rangeText}}", "count": { "count_one": "{{count}} Mal nehmen", "count_other": "{{count}} Mal nehmen" @@ -73,10 +85,6 @@ }, "maxDosePerLifetime": "bis zu einer maximalen Menge von {{count}} {{unit}} über die Lebenszeit des Patienten", "maxDosePerAdministration": "bis zu einer maximalen Menge von {{count}} {{unit}} pro Dosis", - "maxDosePerPeriod": { - "general": "bis zu einer maximalen Menge von {{maxDosePerPeriodText}}", - "maxDosePerPeriod_one": "{{quantityNumerator}} {{numeratorUnit}} pro {{denominatorUnit}}", - "maxDosePerPeriod_other": "{{quantityNumerator}} {{numeratorUnit}} jeder {{count}} {{denominatorUnit}}" - } + "maxDosePerPeriod": "bis zu einer maximalen Menge von {{maxDosePerPeriodText}}" } } diff --git a/src/locales/en/common.json b/src/locales/en/common.json index 9003a0ed..2c5d3313 100644 --- a/src/locales/en/common.json +++ b/src/locales/en/common.json @@ -3,23 +3,39 @@ "and": "and", "then": "then" }, + "amount": { + "range": { + "withUnit": { + "lowAndHigh": "{{low}} to {{high}} {{unit}}", + "onlyHigh": "up to {{high}} {{unit}}", + "onlyLow": "at least {{low}} {{unit}}" + }, + "withoutUnit": { + "lowAndHigh": "{{low}} to {{high}}", + "onlyHigh": "up to {{high}}", + "onlyLow": "at least {{low}}" + } + }, + "ratio": { + "withUnit": { + "numerator": "{{count}} {{numeratorUnit}}", + "denominator_one": "per {{denominatorUnit}}", + "denominator_other": "every {{count}} {{denominatorUnit}}" + }, + "withoutUnit": { + "numerator": "{{count}}", + "denominator_one": ":{{count}}", + "denominator_other": ":{{count}}" + } + }, + "quantity": "{{quantity}} {{unit}}" + }, "fields": { "doseQuantity": "{{quantity}} {{unit}}", - "doseRange": { - "lowAndHigh": "{{low}} to {{high}} {{unit}}", - "onlyHigh": "up to {{high}} {{unit}}", - "onlyLow": "{{low}} {{unit}}" - }, + "doseRange": "{{rangeText}}", "rateQuantity": "at a rate of {{quantity}} {{unit}}", - "rateRange": { - "lowAndHigh": "at a rate of {{low}} to {{high}} {{unit}}", - "onlyHigh": "at a rate of {{high}} {{unit}}", - "onlyLow": "at a rate of {{low}} {{unit}}" - }, - "rateRatio": { - "rateRatio_one": "at a rate of {{quantityNumerator}} {{numeratorUnit}} per {{denominatorUnit}}", - "rateRatio_other": "at a rate of {{quantityNumerator}} {{numeratorUnit}} every {{count}} {{denominatorUnit}}" - }, + "rateRange": "at a rate of {{rangeText}}", + "rateRatio": "at a rate of {{ratioText}}", "duration": "over {{durationText}}", "durationMax": "(maximum {{durationMaxText}})", "frequency": { @@ -54,11 +70,7 @@ "onlyStart": "from {{start}}", "onlyEnd": "to {{end}}" }, - "boundsRange": { - "lowAndHigh": "for {{low}} to {{high}} {{unit}}", - "onlyHigh": "for up to {{high}} {{unit}}", - "onlyLow": "for at least {{low}} {{unit}}" - }, + "boundsRange": "for {{rangeText}}", "count": { "count_one": "take {{count}} time", "count_other": "take {{count}} times" @@ -73,10 +85,6 @@ }, "maxDosePerLifetime": "up to a maximum of {{count}} {{unit}} for the lifetime of patient", "maxDosePerAdministration": "up to a maximum of {{count}} {{unit}} per dose", - "maxDosePerPeriod": { - "general": "up to a maximum of {{maxDosePerPeriodText}}", - "maxDosePerPeriod_one": "{{quantityNumerator}} {{numeratorUnit}} per {{denominatorUnit}}", - "maxDosePerPeriod_other": "{{quantityNumerator}} {{numeratorUnit}} every {{count}} {{denominatorUnit}}" - } + "maxDosePerPeriod": "up to a maximum of {{maxDosePerPeriodText}}" } } diff --git a/src/locales/fr/common.json b/src/locales/fr/common.json index f5c84831..adba5c7f 100644 --- a/src/locales/fr/common.json +++ b/src/locales/fr/common.json @@ -3,23 +3,39 @@ "and": "et", "then": "puis" }, + "amount": { + "range": { + "withUnit": { + "lowAndHigh": "{{low}} à {{high}} {{unit}}", + "onlyHigh": "jusqu'à {{high}} {{unit}}", + "onlyLow": "au moins {{low}} {{unit}}" + }, + "withoutUnit": { + "lowAndHigh": "{{low}} à {{high}}", + "onlyHigh": "jusqu'à {{high}}", + "onlyLow": "au moins {{low}}" + } + }, + "ratio": { + "withUnit": { + "numerator": "{{count}} {{numeratorUnit}}", + "denominator_one": "par {{denominatorUnit}}", + "denominator_other": "chaque {{count}} {{denominatorUnit}}" + }, + "withoutUnit": { + "numerator": "{{count}}", + "denominator_one": ":{{count}}", + "denominator_other": ":{{count}}" + } + }, + "quantity": "{{quantity}} {{unit}}" + }, "fields": { "doseQuantity": "{{quantity}} {{unit}}", - "doseRange": { - "lowAndHigh": "{{low}} à {{high}} {{unit}}", - "onlyHigh": "jusqu'à {{high}} {{unit}}", - "onlyLow": "{{low}} {{unit}}" - }, + "doseRange": "{{rangeText}}", "rateQuantity": "au taux de {{quantity}} {{unit}}", - "rateRange": { - "lowAndHigh": "au taux de {{low}} à {{high}} {{unit}}", - "onlyHigh": "au taux de {{high}} {{unit}}", - "onlyLow": "au taux de {{low}} {{unit}}" - }, - "rateRatio": { - "rateRatio_one": "au taux de {{quantityNumerator}} {{numeratorUnit}} par {{denominatorUnit}}", - "rateRatio_other": "au taux de {{quantityNumerator}} {{numeratorUnit}} chaque {{count}} {{denominatorUnit}}" - }, + "rateRange": "au taux de {{rangeText}}", + "rateRatio": "au taux de {{ratioText}}", "duration": "durant {{durationText}}", "durationMax": "(maximum {{durationMaxText}})", "frequency": { @@ -54,11 +70,7 @@ "onlyStart": "à partir du {{start}}", "onlyEnd": "jusqu'au {{end}}" }, - "boundsRange": { - "lowAndHigh": "de {{low}} à {{high}} {{unit}}", - "onlyHigh": "jusqu'à {{high}} {{unit}}", - "onlyLow": "pour au moins {{low}} {{unit}}" - }, + "boundsRange": "pour {{rangeText}}", "count": { "count_one": "prendre {{count}} fois", "count_other": "prendre {{count}} fois" @@ -73,10 +85,6 @@ }, "maxDosePerLifetime": "jusqu'à un maximum de {{count}} {{unit}} pour la durée de vie du patient", "maxDosePerAdministration": "jusqu'à un maximum de {{count}} {{unit}} par dose", - "maxDosePerPeriod": { - "general": "jusqu'à un maximum de {{maxDosePerPeriodText}}", - "maxDosePerPeriod_one": "{{quantityNumerator}} {{numeratorUnit}} par {{denominatorUnit}}", - "maxDosePerPeriod_other": "{{quantityNumerator}} {{numeratorUnit}} chaque {{count}} {{denominatorUnit}}" - } + "maxDosePerPeriod": "jusqu'à un maximum de {{maxDosePerPeriodText}}" } } diff --git a/src/locales/nl/common.json b/src/locales/nl/common.json index da42b2d7..04656baf 100644 --- a/src/locales/nl/common.json +++ b/src/locales/nl/common.json @@ -3,23 +3,39 @@ "and": "en", "then": "vervolgens" }, + "amount": { + "range": { + "withUnit": { + "lowAndHigh": "tussen {{low}} en {{high}} {{unit}}", + "onlyHigh": "tot {{high}} {{unit}}", + "onlyLow": "minstens {{low}} {{unit}}" + }, + "withoutUnit": { + "lowAndHigh": "tussen {{low}} en {{high}}", + "onlyHigh": "tot {{high}}", + "onlyLow": "minstens {{low}}" + } + }, + "ratio": { + "withUnit": { + "numerator": "{{count}} {{numeratorUnit}}", + "denominator_one": "per {{denominatorUnit}}", + "denominator_other": "elke {{count}} {{denominatorUnit}}" + }, + "withoutUnit": { + "numerator": "{{count}}", + "denominator_one": ":{{count}}", + "denominator_other": ":{{count}}" + } + }, + "quantity": "{{quantity}} {{unit}}" + }, "fields": { "doseQuantity": "{{quantity}} {{unit}}", - "doseRange": { - "lowAndHigh": "tussen {{low}} en {{high}} {{unit}}", - "onlyHigh": "tot {{high}} {{unit}}", - "onlyLow": "{{low}} {{unit}}" - }, + "doseRange": "{{rangeText}}", "rateQuantity": "met een verhouding van {{quantity}} {{unit}}", - "rateRange": { - "lowAndHigh": "met een verhouding tussen {{low}} en {{high}} {{unit}}", - "onlyHigh": "met een verhouding van {{high}} {{unit}}", - "onlyLow": "met een verhouding van {{low}} {{unit}}" - }, - "rateRatio": { - "rateRatio_one": "met een verhouding van {{quantityNumerator}} {{numeratorUnit}} per {{denominatorUnit}}", - "rateRatio_other": "met een verhouding van {{quantityNumerator}} {{numeratorUnit}} elke {{count}} {{denominatorUnit}}" - }, + "rateRange": "met een verhouding van {{rangeText}}", + "rateRatio": "met een verhouding van {{ratioText}}", "duration": "gedurende {{durationText}}", "durationMax": "(maximaal {{durationMaxText}})", "frequency": { @@ -54,11 +70,7 @@ "onlyStart": "van {{start}}", "onlyEnd": "tot {{end}}" }, - "boundsRange": { - "lowAndHigh": "gedurende {{low}} tot {{high}} {{unit}}", - "onlyHigh": "tot {{high}} {{unit}}", - "onlyLow": "gedurende minstens {{low}} {{unit}}" - }, + "boundsRange": "gedurende {{rangeText}}", "count": { "count_one": "{{count}} keer nemen", "count_other": "{{count}} keer nemen" @@ -73,10 +85,6 @@ }, "maxDosePerLifetime": "tot een maximum van {{count}} {{unit}} gedurende de levensduur van de patiënt", "maxDosePerAdministration": "tot een maximum van {{count}} {{unit}} per dosis", - "maxDosePerPeriod": { - "general": "tot een maximum van {{maxDosePerPeriodText}}", - "maxDosePerPeriod_one": "{{quantityNumerator}} {{numeratorUnit}} per {{denominatorUnit}}", - "maxDosePerPeriod_other": "{{quantityNumerator}} {{numeratorUnit}} elke {{count}} {{denominatorUnit}}" - } + "maxDosePerPeriod": "tot een maximum van {{maxDosePerPeriodText}}" } } diff --git a/src/translators/boundsRange.ts b/src/translators/boundsRange.ts index 08d6163e..934c2497 100644 --- a/src/translators/boundsRange.ts +++ b/src/translators/boundsRange.ts @@ -1,28 +1,8 @@ // types -import type { Config, Quantity, DisplayOrderParams, I18N } from "../types"; +import type { DisplayOrderParams } from "../types"; -// Quantity unit to string -function transformQuantityUnitToString( - i18next: I18N, - quantity: Quantity, - config: Config, -): string { - let quantityValue = quantity.value!; - - // If common units from HL7, do the job - if (quantity.system === "http://hl7.org/fhir/ValueSet/duration-units") { - let code = quantity.code! as "s" | "min" | "h" | "d" | "wk" | "mo" | "a"; - return i18next.t(`unitsOfTime:withoutCount.${code}`, { - count: quantityValue, - }); - } else { - // otherwise, it is UCUM, ... so let the user do the job - return config.fromFHIRQuantityUnitToString({ - language: config.language, - quantity: quantity, - }); - } -} +// Utility function +import { fromRangeToString } from "../utils/fromRangeToString"; export function transformBoundsRangeToText({ dos, @@ -42,35 +22,21 @@ export function transformBoundsRangeToText({ if (boundsRange === undefined) { return undefined; } else { - let low = boundsRange.low; - let high = boundsRange.high; - - // quantity unit - let unit = transformQuantityUnitToString(i18next, high || low!, config); - - // Three cases - - // 1. Both low & high are present - if (high !== undefined && low !== undefined) { - return i18next.t("fields.boundsRange.lowAndHigh", { - low: low.value, - high: high.value, - unit: unit, - }); - } + // Turn range into a text + const rangeText = fromRangeToString({ + range: boundsRange, + config, + i18next, + }); - // 2. Only high is present - if (high !== undefined) { - return i18next.t("fields.boundsRange.onlyHigh", { - high: high.value, - unit: unit, - }); + // Reject if empty + if (rangeText === undefined) { + return undefined; } - // 3. Only low is present - return i18next.t("fields.boundsRange.onlyLow", { - low: low!.value, - unit: unit, + // return the final string + return i18next.t("fields.boundsRange", { + rangeText: rangeText, }); } } diff --git a/src/translators/doseRange.ts b/src/translators/doseRange.ts index fbb55edd..2187da98 100644 --- a/src/translators/doseRange.ts +++ b/src/translators/doseRange.ts @@ -1,3 +1,6 @@ +// Utility function +import { fromRangeToString } from "../utils/fromRangeToString"; + // types import type { DisplayOrderParams } from "../types"; @@ -18,44 +21,19 @@ export function transformDoseRangeToText({ return undefined; } - let low = doseRange.doseRange!.low?.value; - let high = doseRange.doseRange!.high?.value; - - let quantityUnit = - doseRange.doseRange!.high !== undefined - ? doseRange.doseRange!.high - : doseRange.doseRange!.low!; - - // quantity unit - let unit = config.fromFHIRQuantityUnitToString({ - language: config.language, - quantity: quantityUnit, + // Turn range into a text + const text = fromRangeToString({ + range: doseRange.doseRange!, + config, + i18next, }); - // Three cases - - // 1. Both low & high are present - if (high !== undefined && low !== undefined) { - return i18next.t("fields.doseRange.lowAndHigh", { - low: low, - high: high, - unit: unit, - }); - } - - // 2. Only high is present - if (high !== undefined) { - return i18next.t("fields.doseRange.onlyHigh", { - high: high, - unit: unit, - }); + // Reject if empty + if (text === undefined) { + return undefined; } - // 3. Only low is present - // Warning, this case is kind dangerous and clinically unsafe so minimal effort on this ... - - return i18next.t("fields.doseRange.onlyLow", { - low: low, - unit: unit, + return i18next.t("fields.doseRange", { + rangeText: text, }); } diff --git a/src/translators/maxDosePerPeriod.ts b/src/translators/maxDosePerPeriod.ts index f54851da..d9805ef4 100644 --- a/src/translators/maxDosePerPeriod.ts +++ b/src/translators/maxDosePerPeriod.ts @@ -1,4 +1,5 @@ import { fromListToString } from "../utils/fromListToString"; +import { fromRatioToString } from "../utils/fromRatioToString"; // types import type { DisplayOrderParams } from "../types"; @@ -28,42 +29,15 @@ export function transformMaxDosePerPeriodToText({ } // Periods are expressed as ratio (like rateRatio) - const valuesAsString: string[] = values.map((period) => { - // num / dem - let numerator = period.numerator; - let denominator = period.denominator; - - let quantityNum = numerator?.value || 1; - let quantityDenom = denominator?.value || 1; - - // units as text - let numeratorUnit = - numerator !== undefined - ? config.fromFHIRQuantityUnitToString({ - language: config.language, - quantity: numerator, - }) - : ""; - - let denominatorUnit = - denominator !== undefined - ? config.fromFHIRQuantityUnitToString({ - language: config.language, - quantity: denominator, - }) - : ""; - - return i18next.t("fields.maxDosePerPeriod.maxDosePerPeriod", { - count: quantityDenom, - quantityNumerator: quantityNum, - numeratorUnit: numeratorUnit, - denominatorUnit: denominatorUnit, - }); - }); + const valuesAsString = values + .map((period) => { + return fromRatioToString({ config, i18next, ratio: period }); + }) + .filter((s) => s !== undefined) as string[]; const maxDosePerPeriodText = fromListToString(i18next, valuesAsString); - return i18next.t("fields.maxDosePerPeriod.general", { + return i18next.t("fields.maxDosePerPeriod", { count: values.length, maxDosePerPeriodText: maxDosePerPeriodText, }); diff --git a/src/translators/rateRange.ts b/src/translators/rateRange.ts index db81ccce..4a2dfaed 100644 --- a/src/translators/rateRange.ts +++ b/src/translators/rateRange.ts @@ -1,3 +1,6 @@ +// Utility function +import { fromRangeToString } from "../utils/fromRangeToString"; + // types import type { DisplayOrderParams } from "../types"; @@ -11,50 +14,27 @@ export function transformRateRangeToText({ return undefined; } // Find the first entry that match criteria - let rateRange = dos.doseAndRate.find((s) => s.rateRange !== undefined); + let doseAndRate = dos.doseAndRate.find((s) => s.rateRange !== undefined); // If not found, skip - if (rateRange === undefined) { + if (doseAndRate === undefined) { return undefined; } - // low / high - let low = rateRange.rateRange!.low; - let high = rateRange.rateRange!.high; - - let quantityLow = low?.value; - let quantityHigh = high?.value; - - // quantity unit - let unit = config.fromFHIRQuantityUnitToString({ - language: config.language, - quantity: high || low!, + // Turn range into a text + const rangeText = fromRangeToString({ + range: doseAndRate.rateRange!, + config, + i18next, }); - // Three cases - - // 1. Both low & high are present - if (quantityHigh !== undefined && quantityLow !== undefined) { - return i18next.t("fields.rateRange.lowAndHigh", { - low: quantityLow, - high: quantityHigh, - unit: unit, - }); - } - - // 2. Only high is present - if (quantityHigh !== undefined) { - return i18next.t("fields.rateRange.onlyHigh", { - high: quantityHigh, - unit: unit, - }); + // Reject if empty + if (rangeText === undefined) { + return undefined; } - // 3. Only low is present - // Warning, this case is kind dangerous and clinically unsafe so minimal effort on this ... - - return i18next.t("fields.rateRange.onlyLow", { - low: quantityLow, - unit: unit, + // return the final string + return i18next.t("fields.rateRange", { + rangeText: rangeText, }); } diff --git a/src/translators/rateRatio.ts b/src/translators/rateRatio.ts index 462bc891..e23ff812 100644 --- a/src/translators/rateRatio.ts +++ b/src/translators/rateRatio.ts @@ -1,3 +1,5 @@ +import { fromRatioToString } from "../utils/fromRatioToString"; + // types import type { DisplayOrderParams } from "../types"; @@ -11,41 +13,25 @@ export function transformRateRatioToText({ return undefined; } // Find the first entry that match criteria - let rateRatio = dos.doseAndRate.find((s) => s.rateRatio !== undefined); + let doseAndRate = dos.doseAndRate.find((s) => s.rateRatio !== undefined); // If not found, skip - if (rateRatio === undefined) { + if (doseAndRate === undefined) { return undefined; } - // num / dem - let numerator = rateRatio.rateRatio!.numerator; - let denominator = rateRatio.rateRatio!.denominator; - - let quantityNum = numerator?.value || 1; - let quantityDenom = denominator?.value || 1; - - // units as text - let numeratorUnit = - numerator !== undefined - ? config.fromFHIRQuantityUnitToString({ - language: config.language, - quantity: numerator, - }) - : ""; + // Turn ratio to text + const ratioText = fromRatioToString({ + config, + i18next, + ratio: doseAndRate.rateRatio!, + }); - let denominatorUnit = - denominator !== undefined - ? config.fromFHIRQuantityUnitToString({ - language: config.language, - quantity: denominator, - }) - : ""; + if (ratioText === undefined) { + return undefined; + } - return i18next.t("fields.rateRatio.rateRatio", { - count: quantityDenom, - quantityNumerator: quantityNum, - numeratorUnit: numeratorUnit, - denominatorUnit: denominatorUnit, + return i18next.t("fields.rateRatio", { + ratioText: ratioText, }); } diff --git a/src/types.ts b/src/types.ts index 0a3e92d2..dd4b6328 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,6 +11,8 @@ import type { Extension as ExtensionR4, Quantity as QuantityR4, Duration as DurationR4, + Range as RangeR4, + Ratio as RatioR4, } from "fhir/r4"; import type { Dosage as DosageR5, @@ -18,6 +20,8 @@ import type { Extension as ExtensionR5, Quantity as QuantityR5, Duration as DurationR5, + Range as RangeR5, + Ratio as RatioR5, } from "fhir/r5"; // Exported types @@ -27,6 +31,8 @@ export type CodeableConcept = CodeableConceptR4 | CodeableConceptR5; export type Quantity = QuantityR4 | QuantityR5; export type Duration = DurationR4 | DurationR5; export type Extension = ExtensionR4 | ExtensionR5; +export type Range = RangeR4 | RangeR5; +export type Ratio = RatioR4 | RatioR5; export type { FromFHIRQuantityUnitToStringFct, FromCodeableConceptToStringFct }; @@ -241,3 +247,16 @@ export type DisplayOrderParams = { i18next: I18N; }; export type { I18N }; + +// Types for amount functions +export type RangeParams = { + range: Range; + config: Config; + i18next: I18N; +}; + +export type RatioParams = { + ratio: Ratio; + config: Config; + i18next: I18N; +}; diff --git a/src/utils/fromRangeToString.ts b/src/utils/fromRangeToString.ts new file mode 100644 index 00000000..3d5b87d4 --- /dev/null +++ b/src/utils/fromRangeToString.ts @@ -0,0 +1,81 @@ +import type { RangeParams, Config, Quantity, I18N } from "../types"; + +// Quantity unit to string +function transformQuantityUnitToString( + i18next: I18N, + quantity: Quantity, + config: Config, +): string { + let quantityValue = quantity.value!; + + // If common units from HL7, do the job + if (quantity.system === "http://hl7.org/fhir/ValueSet/duration-units") { + let code = quantity.code! as "s" | "min" | "h" | "d" | "wk" | "mo" | "a"; + return i18next.t(`unitsOfTime:withoutCount.${code}`, { + count: quantityValue, + }); + } else { + // otherwise, it is UCUM, ... so let the user do the job + return config.fromFHIRQuantityUnitToString({ + language: config.language, + quantity: quantity, + }); + } +} + +// To cover all nasty cases of Range, only once +// https://build.fhir.org/datatypes.html#Range +export function fromRangeToString({ + range, + config, + i18next, +}: RangeParams): string | undefined { + // Extract params + const { low, high } = range; + const lowValue = low?.value; + const highValue = high?.value; + + // prepare unit display + let quantityUnit = high || low; + let hasUnit = + quantityUnit?.unit !== undefined || quantityUnit?.code !== undefined; + + // Four cases + + // 1. If we have a empty object, return undefined + if (lowValue === undefined && highValue === undefined) { + return undefined; + } + + // quantity unit + let unit = hasUnit + ? transformQuantityUnitToString(i18next, quantityUnit!, config) + : ""; + let technicalKey: "withUnit" | "withoutUnit" = hasUnit + ? "withUnit" + : "withoutUnit"; + + // 2. Both low & high are present + if (lowValue !== undefined && highValue !== undefined) { + return i18next.t(`amount.range.${technicalKey}.lowAndHigh`, { + low: lowValue, + high: highValue, + unit: unit, + }); + } + + // 3. Only high is present + if (highValue !== undefined) { + return i18next.t(`amount.range.${technicalKey}.onlyHigh`, { + high: highValue, + unit: unit, + }); + } + + // 4. Only low is present + // Warning, this case is kind dangerous and clinically unsafe so minimal effort on this ... + return i18next.t(`amount.range.${technicalKey}.onlyLow`, { + low: lowValue, + unit: unit, + }); +} diff --git a/src/utils/fromRatioToString.ts b/src/utils/fromRatioToString.ts new file mode 100644 index 00000000..1e35657d --- /dev/null +++ b/src/utils/fromRatioToString.ts @@ -0,0 +1,74 @@ +import type { RatioParams, Quantity } from "../types"; + +// Quantity has an unit ? +function hasUnit(quantity?: Quantity): boolean { + return (quantity?.unit || quantity?.code) !== undefined; +} + +// To cover all nasty cases of Ratio, only once +// https://build.fhir.org/datatypes.html#Ratio +export function fromRatioToString({ + ratio, + config, + i18next, +}: RatioParams): string | undefined { + // Extract params + const { denominator, numerator } = ratio; + + // units as text + let numeratorUnit = hasUnit(numerator) + ? config.fromFHIRQuantityUnitToString({ + language: config.language, + quantity: numerator!, + }) + : undefined; + + let denominatorUnit = hasUnit(denominator) + ? config.fromFHIRQuantityUnitToString({ + language: config.language, + quantity: denominator!, + }) + : undefined; + + // quantity + let quantityNumerator = numerator?.value; + let quantityDenominator = denominator?.value; + + // Collect + const parts: string[] = []; + + // Deal with numerator first + if (quantityNumerator !== undefined) { + let technicalKey: "withUnit" | "withoutUnit" = + numeratorUnit !== undefined ? "withUnit" : "withoutUnit"; + const numeratorString = i18next.t( + `amount.ratio.${technicalKey}.numerator`, + { + count: quantityNumerator, + numeratorUnit: numeratorUnit, + }, + ); + parts.push(numeratorString); + } + + // Deal with denominator + if (quantityDenominator !== undefined) { + let technicalKey: "withUnit" | "withoutUnit" = + denominatorUnit !== undefined ? "withUnit" : "withoutUnit"; + const denominatorString = i18next.t( + `amount.ratio.${technicalKey}.denominator`, + { + count: quantityDenominator, + denominatorUnit: denominatorUnit, + }, + ); + parts.push(denominatorString); + } + + // Concatenate the result + if (parts.length === 0) { + return undefined; + } else { + return parts.join(" "); + } +}