diff --git a/src/components/DrugInfo.tsx b/src/components/DrugInfo.tsx
index 3ab65a0..490d8c4 100644
--- a/src/components/DrugInfo.tsx
+++ b/src/components/DrugInfo.tsx
@@ -12,6 +12,7 @@ import type { ApexAxisChartSeries } from "apexcharts";
import dynamic from "next/dynamic";
import comboDefinitions from "../assets/comboDefinitions.json";
import addDictionaryDefs from "./addDictionaryDefs";
+import { parseDoseRangeMg } from "../utils/parseDoseRangeMg";
// We need to dynamically load this module
const ReactApexChart = dynamic(() => import("react-apexcharts"), {
@@ -454,71 +455,23 @@ const addDosages = (drugData: Drug) => {
[strength]: strengthData,
};
- // This essentially looks for "(any digit) (optional dash) (any digit) (optional unit)"
- const regex = /(\d+(?:\.\d+)?)(?:-(\d+(?:\.\d+)?))?([a-zA-Z]+)?/;
- const match = RegExp(regex).exec(strengthData);
-
- // This is mostly for type-safety
- if (match) {
- // The minimum number is the first number to appear
- const strengthMinNumber = parseFloat(match[1]);
-
- // The maximum number is either the second number, or if there is no second number, the first number * 1.5
- // Later, we have code that says "if there is no maximum, then do a fade out", the "1.5" is purely cosmetic
- // The doseText above displays the "+" so it's clear to the user, they never see the (dosage * 1.5) value
- const strengthMaxNumber = match[2]
- ? parseFloat(match[2])
- : strengthMinNumber * 1.5;
-
- // Get the color associated with the strength
- const strengthColor =
- doseColorData[strength as keyof typeof doseColorData];
- // If it's not already in the doseColors table, add it
- if (!doseColors.includes(strengthColor)) {
- doseColors.push(strengthColor);
- }
-
- // Get the gradient color associated with the strength
- // If it's not already in the doseGradientToColors table, add it
- const strengthGradientColor = match[2]
- ? doseGradientData[strength as keyof typeof doseGradientData]
- : "#ffffff";
- if (!doseGradientToColors.includes(strengthGradientColor)) {
- doseGradientToColors.push(strengthGradientColor);
- }
-
- // Check if the doseSeries already has a series with the same name
- // If it does, push the new data to that series
- // If it doesn't, create a new series
- const existingSeries = doseSeries.find(
- (series) => series.name === strength,
- );
+ const range = parseDoseRangeMg(strengthData);
+ if (!range) return;
- const roaUnit = match[3] ? ` (${match[3]})` : ``;
- if (existingSeries) {
- const existingSeriesData = existingSeries.data as {
- x: string;
- y: number[];
- }[];
-
- existingSeriesData.push({
- x: `${roa}${roaUnit}`,
- y: [strengthMinNumber, strengthMaxNumber],
- });
- return;
- }
-
- const dosePropertySeries = {
- name: strength,
- data: [
- {
- x: `${roa}${roaUnit}`,
- y: [strengthMinNumber, strengthMaxNumber],
- },
- ],
- };
- doseSeries.push(dosePropertySeries);
+ const point = { x: `${roa} (mg)`, y: range };
+ const existingSeries = doseSeries.find(
+ (series) => series.name === strength,
+ );
+ if (existingSeries) {
+ (existingSeries.data as (typeof point)[]).push(point);
+ return;
}
+
+ doseColors.push(doseColorData[strength as keyof typeof doseColorData]);
+ doseGradientToColors.push(
+ doseGradientData[strength as keyof typeof doseGradientData],
+ );
+ doseSeries.push({ name: strength, data: [point] });
});
});
@@ -594,13 +547,21 @@ const addDosages = (drugData: Drug) => {
Dosages
-
+ {doseSeries.length > 0 && (
+ <>
+
+ Chart shows bounded mass ranges in mg. See the table for all
+ doses.
+
+
+ >
+ )}
{/* Dosage Text */}
diff --git a/src/utils/__tests__/parseDoseRangeMg.test.ts b/src/utils/__tests__/parseDoseRangeMg.test.ts
new file mode 100644
index 0000000..7704787
--- /dev/null
+++ b/src/utils/__tests__/parseDoseRangeMg.test.ts
@@ -0,0 +1,24 @@
+import { parseDoseRangeMg } from "../parseDoseRangeMg";
+
+describe("parseDoseRangeMg", () => {
+ it.each([
+ ["800-1500ug", [0.8, 1.5]],
+ ["1.5-3mg", [1.5, 3]],
+ ["325mg-650mg", [325, 650]],
+ ["800 µg – 1.5 mg", [0.8, 1.5]],
+ ])("normalizes %s to milligrams", (source, expected) => {
+ expect(parseDoseRangeMg(source)).toEqual(expected);
+ });
+
+ it.each([
+ "25-50ug/hr",
+ "25ug/hr-50ug/hr",
+ "3mg+",
+ "3mg",
+ "1-3",
+ "3mg-1mg",
+ "1-3mg/kg",
+ ])("leaves %s in the text table only", (source) =>
+ expect(parseDoseRangeMg(source)).toBeUndefined(),
+ );
+});
diff --git a/src/utils/parseDoseRangeMg.ts b/src/utils/parseDoseRangeMg.ts
new file mode 100644
index 0000000..53f0e25
--- /dev/null
+++ b/src/utils/parseDoseRangeMg.ts
@@ -0,0 +1,28 @@
+const massInMg = { ng: 0.000001, ug: 0.001, mg: 1, g: 1000 };
+type MassUnit = keyof typeof massInMg;
+
+export function parseDoseRangeMg(text: string): [number, number] | undefined {
+ // Require two explicit endpoints and at least one mass unit.
+ const endpoints = text
+ .toLowerCase()
+ .replace(/[\u00b5\u03bc]/gu, "u")
+ .split(/[-\u2013\u2014]/u);
+ if (endpoints.length !== 2) return;
+
+ const quantity = /^(\d+(?:\.\d+)?|\.\d+)\s*(ng|ug|mg|g)?$/u;
+ const lower = quantity.exec(endpoints[0].trim());
+ const upper = quantity.exec(endpoints[1].trim());
+ if (!lower || !upper) return;
+
+ const [, low, lowUnit] = lower;
+ const [, high, highUnit] = upper;
+ if (!lowUnit && !highUnit) return;
+
+ const startUnit = (lowUnit || highUnit) as MassUnit;
+ const endUnit = (highUnit || lowUnit) as MassUnit;
+ const start = Number(low) * massInMg[startUnit];
+ const end = Number(high) * massInMg[endUnit];
+ if (Number.isFinite(start) && Number.isFinite(end) && start < end) {
+ return [start, end];
+ }
+}