@pharmatools/drug-data 0.2.0 → 0.2.1

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.
package/dist/index.cjs CHANGED
@@ -168,9 +168,17 @@ async function resolveViaRxNorm(name) {
168
168
  brandNames = single.length ? single : fallback;
169
169
  }
170
170
  let genericName = null;
171
- if (minNames.length) genericName = titleCase(minNames[0]);
172
- else if (inNames.length === 1) genericName = titleCase(inNames[0]);
173
- else if (inNames.length > 1) genericName = titleCase(uniq(inNames).join(" / "));
171
+ if (tty === "IN" || tty === "MIN") {
172
+ genericName = titleCase(canonicalName);
173
+ } else if (minNames.length) {
174
+ genericName = titleCase(minNames[0]);
175
+ } else if (inNames.length === 1) {
176
+ genericName = titleCase(inNames[0]);
177
+ } else if (inNames.length > 1) {
178
+ genericName = titleCase(uniq(inNames).join(" / "));
179
+ } else {
180
+ genericName = titleCase(canonicalName);
181
+ }
174
182
  const classRxcui = !isBrand ? rxcui : (() => {
175
183
  const inGroup = (allRelated?.allRelatedGroup?.conceptGroup ?? []).find((x) => x.tty === "IN");
176
184
  return inGroup?.conceptProperties?.[0]?.rxcui ?? rxcui;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/fetch.ts","../src/names.ts","../src/labels.ts","../src/compare.ts"],"sourcesContent":["// @pharmatools/drug-data — public surface\n\nexport { resolveName } from \"./names.js\";\n// 5-field convenience view (Pharmonym)\nexport { getUspi, getSmpc, getLabels } from \"./labels.js\";\n// General all-section access (PubCrawl)\nexport { getUsLabel, getUkSmpc } from \"./labels.js\";\nexport { compareLabels, SECTION_MAP } from \"./compare.js\";\nexport { cleanSection, formatFdaDate } from \"./fetch.js\";\nexport type {\n Region,\n RegionLabel,\n LabelResult,\n LabelSection,\n UsLabelResult,\n UkSmpcResult,\n ResolveResult,\n LabelComparison,\n CompareResult,\n} from \"./types.js\";\n","/**\n * Shared HTTP + text helpers. Uses the runtime's global `fetch` (Node >=18),\n * so the package has no node-fetch dependency.\n */\n\nconst DEFAULT_TIMEOUT_MS = 12000;\nconst MAX_FIELD_CHARS = 1400;\n\nexport const USER_AGENT = \"drug-data/0.1 (pharmatools.ai; nick@pharmatools.ai)\";\n\nexport async function timedFetch(\n url: string,\n opts: RequestInit = {},\n timeoutMs = DEFAULT_TIMEOUT_MS\n): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n return await fetch(url, {\n ...opts,\n signal: controller.signal,\n headers: { \"User-Agent\": USER_AGENT, ...((opts.headers as Record<string, string>) || {}) },\n });\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function getJson<T = unknown>(\n url: string,\n timeoutMs = DEFAULT_TIMEOUT_MS\n): Promise<T | null> {\n try {\n const res = await timedFetch(url, {}, timeoutMs);\n if (!res.ok) return null;\n return (await res.json()) as T;\n } catch {\n return null;\n }\n}\n\n/**\n * Tidy a raw label section: join arrays, strip a leading numbered ALL-CAPS\n * heading, collapse whitespace, and truncate on a sentence boundary.\n */\nexport function cleanSection(value: unknown, maxChars = MAX_FIELD_CHARS): string {\n if (!value) return \"\";\n let text = Array.isArray(value) ? value.join(\"\\n\\n\") : String(value);\n\n text = text\n .replace(/\\r/g, \"\")\n .replace(/[ \\t]+/g, \" \")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n\n // Drop a leading section header line like \"5 WARNINGS AND PRECAUTIONS\"\n text = text.replace(/^\\s*\\d+(\\.\\d+)*\\s+[A-Z][A-Z \\-/,&]+\\n+/, \"\").trim();\n\n if (text.length > maxChars) {\n const slice = text.slice(0, maxChars);\n const lastStop = Math.max(slice.lastIndexOf(\". \"), slice.lastIndexOf(\"\\n\"));\n text = (lastStop > maxChars * 0.6 ? slice.slice(0, lastStop + 1) : slice).trim() + \" …\";\n }\n return text;\n}\n\nexport function formatFdaDate(yyyymmdd?: string): string {\n if (!yyyymmdd || !/^\\d{8}$/.test(yyyymmdd)) return \"\";\n const d = new Date(`${yyyymmdd.slice(0, 4)}-${yyyymmdd.slice(4, 6)}-${yyyymmdd.slice(6, 8)}`);\n if (isNaN(d.getTime())) return \"\";\n return d.toLocaleDateString(\"en-US\", { year: \"numeric\", month: \"short\", day: \"numeric\" });\n}\n\nexport function titleCase(s: string): string {\n return String(s || \"\").replace(/\\w\\S*/g, (t) => t.charAt(0).toUpperCase() + t.slice(1).toLowerCase());\n}\n\nexport function uniq<T>(arr: T[]): T[] {\n return [...new Set(arr.filter(Boolean))];\n}\n","/**\n * Deterministic brand <-> generic resolution via RxNorm/RxNav, with an openFDA\n * fallback. No AI, no hallucinated names. Brand names are US-registered;\n * international brand names are intentionally not invented.\n */\n\nimport { getJson, titleCase, uniq } from \"./fetch.js\";\nimport type { ResolveResult } from \"./types.js\";\n\nconst RXNAV = \"https://rxnav.nlm.nih.gov/REST\";\nconst OPENFDA = \"https://api.fda.gov/drug/label.json\";\nconst TIMEOUT_MS = 10000;\n\nconst REGION_NOTE = \"Brand names shown are those registered in the United States.\";\n\n/* ----------------------- RxNorm resolution ----------------------- */\n\nasync function findRxcui(name: string): Promise<string | null> {\n const exact = await getJson<any>(`${RXNAV}/rxcui.json?name=${encodeURIComponent(name)}`, TIMEOUT_MS);\n const id = exact?.idGroup?.rxnormId?.[0];\n if (id) return id;\n\n const approx = await getJson<any>(\n `${RXNAV}/approximateTerm.json?term=${encodeURIComponent(name)}&maxEntries=1`,\n TIMEOUT_MS\n );\n const cand = approx?.approximateGroup?.candidate?.[0];\n return cand ? cand.rxcui : null;\n}\n\nfunction collectNames(allRelated: any, tty: string): string[] {\n const groups = allRelated?.allRelatedGroup?.conceptGroup ?? [];\n const g = groups.find((x: any) => x.tty === tty);\n if (!g || !g.conceptProperties) return [];\n return g.conceptProperties.map((c: any) => c.name);\n}\n\n/**\n * Single-ingredient brand names for a generic ingredient, via SBDF concepts\n * whose name is \"<ingredient> <dose form> [Brand]\". Combination products read\n * \"<ing A> / <ing B> ... [Brand]\", so any \"/\" before the bracket is excluded.\n */\nasync function getSingleIngredientBrands(ingredientRxcui: string): Promise<string[]> {\n const data = await getJson<any>(`${RXNAV}/rxcui/${ingredientRxcui}/related.json?tty=SBDF`, TIMEOUT_MS);\n const groups = data?.relatedGroup?.conceptGroup ?? [];\n const g = groups.find((x: any) => x.tty === \"SBDF\");\n if (!g || !g.conceptProperties) return [];\n const brands: string[] = [];\n for (const c of g.conceptProperties) {\n const m = (c.name as string).match(/\\[([^\\]]+)\\]\\s*$/);\n if (!m) continue;\n const prefix = (c.name as string).slice(0, (c.name as string).lastIndexOf(\"[\"));\n if (prefix.includes(\"/\")) continue; // combination product — skip\n brands.push(m[1].trim());\n }\n return uniq(brands).sort();\n}\n\nasync function getDrugClassAndUses(\n rxcui: string\n): Promise<{ drugClass: string | null; uses: string[] }> {\n const data = await getJson<any>(\n `${RXNAV}/rxclass/class/byRxcui.json?rxcui=${rxcui}&relaSource=MEDRT`,\n TIMEOUT_MS\n );\n const items = data?.rxclassDrugInfoList?.rxclassDrugInfo ?? [];\n\n const byType = (t: string): string | null => {\n const hit = items.find((i: any) => i.rxclassMinConceptItem.classType === t);\n return hit ? hit.rxclassMinConceptItem.className : null;\n };\n // Prefer FDA Established Pharmacologic Class, then mechanism, then chemical.\n const drugClass = byType(\"EPC\") || byType(\"MOA\") || byType(\"CHEM\");\n\n const uses = uniq<string>(\n items\n .filter((i: any) => i.rela === \"may_treat\")\n .map((i: any) => i.rxclassMinConceptItem.className as string)\n ).slice(0, 6);\n\n return { drugClass, uses };\n}\n\nasync function resolveViaRxNorm(name: string): Promise<ResolveResult | null> {\n const rxcui = await findRxcui(name);\n if (!rxcui) return null;\n\n const [props, allRelated] = await Promise.all([\n getJson<any>(`${RXNAV}/rxcui/${rxcui}/properties.json`, TIMEOUT_MS),\n getJson<any>(`${RXNAV}/rxcui/${rxcui}/allrelated.json`, TIMEOUT_MS),\n ]);\n\n const tty: string | undefined = props?.properties?.tty;\n const canonicalName: string = props?.properties?.name ?? name;\n if (!tty) return null;\n\n const inNames = collectNames(allRelated, \"IN\");\n const minNames = collectNames(allRelated, \"MIN\");\n\n const isBrand = tty === \"BN\" || tty === \"SBD\" || tty === \"BPCK\";\n const inputType: \"brand\" | \"generic\" = isBrand ? \"brand\" : \"generic\";\n\n // For a generic, list only brands where it is the sole active ingredient.\n let brandNames: string[] | null = null;\n if (!isBrand) {\n const single = await getSingleIngredientBrands(rxcui);\n const fallback = uniq([...collectNames(allRelated, \"BN\"), ...collectNames(allRelated, \"BPCK\")]).sort();\n brandNames = single.length ? single : fallback;\n }\n\n // Generic display name: prefer the canonical combo string, else single, else joined.\n let genericName: string | null = null;\n if (minNames.length) genericName = titleCase(minNames[0]);\n else if (inNames.length === 1) genericName = titleCase(inNames[0]);\n else if (inNames.length > 1) genericName = titleCase(uniq(inNames).join(\" / \"));\n\n // Class + indications from the ingredient rxcui where possible.\n const classRxcui = !isBrand\n ? rxcui\n : (() => {\n const inGroup = (allRelated?.allRelatedGroup?.conceptGroup ?? []).find((x: any) => x.tty === \"IN\");\n return inGroup?.conceptProperties?.[0]?.rxcui ?? rxcui;\n })();\n const { drugClass, uses } = await getDrugClassAndUses(classRxcui);\n\n return {\n inputType,\n inputName: titleCase(canonicalName),\n genericName,\n brandNames,\n drugClass,\n uses,\n rxcui,\n source: \"RxNorm (NLM)\",\n regionNote: REGION_NOTE,\n };\n}\n\n/* ----------------------- openFDA fallback ------------------------ */\n\nasync function resolveViaOpenFda(name: string): Promise<ResolveResult | null> {\n const q = name.replace(/\"/g, \"\");\n const search = `(openfda.brand_name:\"${q}\"+OR+openfda.generic_name:\"${q}\")`;\n const url = `${OPENFDA}?search=${encodeURIComponent(search).replace(/%2B/g, \"+\")}&limit=1`;\n const data = await getJson<any>(url, TIMEOUT_MS);\n const fda = data?.results?.[0]?.openfda;\n if (!fda) return null;\n\n const brand = (fda.brand_name ?? [])[0] ?? \"\";\n const generic = (fda.generic_name ?? [])[0] ?? \"\";\n if (!brand && !generic) return null;\n\n const isBrand = brand.toLowerCase() === name.toLowerCase();\n return {\n inputType: isBrand ? \"brand\" : \"generic\",\n inputName: titleCase(name),\n genericName: generic ? titleCase(generic) : null,\n brandNames: isBrand ? null : uniq<string>(fda.brand_name ?? []).sort(),\n drugClass: (fda.pharm_class_epc ?? [])[0] ?? (fda.pharm_class_moa ?? [])[0] ?? null,\n uses: [],\n rxcui: null,\n source: \"openFDA\",\n regionNote: REGION_NOTE,\n };\n}\n\n/* --------------------------- public ----------------------------- */\n\n/**\n * Resolve a drug name deterministically. Tries RxNorm, then openFDA. Returns\n * null if neither source recognises the name.\n */\nexport async function resolveName(name: string): Promise<ResolveResult | null> {\n if (!name || !name.trim()) return null;\n const clean = name.trim();\n const viaRx = await resolveViaRxNorm(clean).catch(() => null);\n if (viaRx && (viaRx.genericName || (viaRx.brandNames && viaRx.brandNames.length))) {\n return viaRx;\n }\n return await resolveViaOpenFda(clean).catch(() => null);\n}\n","/**\n * Official drug-label lookups.\n * - US: FDA label via the openFDA API -> DailyMed citation\n * - UK/EU: SmPC via the eMC (medicines.org.uk) -> eMC citation\n *\n * Two layers:\n * - getUsLabel / getUkSmpc — general, all-section access (LabelSection[]),\n * used by PubCrawl's get_uspi / get_smpc / compare_labels tools.\n * - getUspi / getSmpc / getLabels — a 5-field convenience view (RegionLabel)\n * used by Pharmonym. Built on the same fetch + parse internals.\n *\n * Every network call is best-effort: failures return null. Pure fetch + parse;\n * caching belongs to the consumer.\n */\n\nimport * as cheerio from \"cheerio\";\nimport { timedFetch, getJson, cleanSection, formatFdaDate } from \"./fetch.js\";\nimport type { RegionLabel, LabelResult, LabelSection, UsLabelResult, UkSmpcResult } from \"./types.js\";\n\nconst OPENFDA_URL = \"https://api.fda.gov/drug/label.json\";\nconst EMC_BASE = \"https://www.medicines.org.uk\";\n\ninterface OpenFdaLabel {\n effective_time?: string;\n boxed_warning?: string[];\n indications_and_usage?: string[];\n dosage_and_administration?: string[];\n dosage_forms_and_strengths?: string[];\n contraindications?: string[];\n warnings_and_cautions?: string[];\n warnings?: string[];\n adverse_reactions?: string[];\n drug_interactions?: string[];\n use_in_specific_populations?: string[];\n overdosage?: string[];\n clinical_pharmacology?: string[];\n description?: string[];\n how_supplied?: string[];\n openfda?: {\n generic_name?: string[];\n brand_name?: string[];\n spl_set_id?: string[];\n };\n}\ninterface OpenFdaResponse {\n results?: OpenFdaLabel[];\n}\n\n// openFDA field -> { LOINC code, display title }. Order = label reading order.\nconst US_SECTION_MAP: Array<{ field: keyof OpenFdaLabel; code: string; title: string }> = [\n { field: \"boxed_warning\", code: \"34066-1\", title: \"Boxed Warning\" },\n { field: \"indications_and_usage\", code: \"34067-9\", title: \"Indications and Usage\" },\n { field: \"dosage_and_administration\", code: \"34068-7\", title: \"Dosage and Administration\" },\n { field: \"dosage_forms_and_strengths\", code: \"43678-2\", title: \"Dosage Forms and Strengths\" },\n { field: \"contraindications\", code: \"34070-3\", title: \"Contraindications\" },\n { field: \"warnings_and_cautions\", code: \"43685-7\", title: \"Warnings and Precautions\" },\n { field: \"warnings\", code: \"34071-1\", title: \"Warnings\" },\n { field: \"adverse_reactions\", code: \"34084-4\", title: \"Adverse Reactions\" },\n { field: \"drug_interactions\", code: \"34073-7\", title: \"Drug Interactions\" },\n { field: \"use_in_specific_populations\", code: \"42228-7\", title: \"Use in Specific Populations\" },\n { field: \"overdosage\", code: \"34088-5\", title: \"Overdosage\" },\n { field: \"clinical_pharmacology\", code: \"34090-1\", title: \"Clinical Pharmacology\" },\n { field: \"description\", code: \"34089-3\", title: \"Description\" },\n { field: \"how_supplied\", code: \"34069-5\", title: \"How Supplied\" },\n];\n\n/* --------------------------- US: openFDA --------------------------- */\n\nfunction score(r: OpenFdaLabel, q: string): number {\n const g = (r.openfda?.generic_name ?? []).join(\", \").toLowerCase();\n let s = 0;\n if (g && g.split(/,| and /).length === 1) s += 2; // single ingredient\n if (g.includes(q.toLowerCase())) s += 1;\n return s;\n}\n\n/** Fetch the most recent FDA label for a drug; prefers single-ingredient products. */\nasync function fetchOpenFdaBest(drug: string): Promise<OpenFdaLabel | null> {\n if (!drug) return null;\n const q = drug.trim().replace(/\"/g, \"\");\n const search = `(openfda.generic_name:\"${q}\"+OR+openfda.brand_name:\"${q}\")`;\n const url = `${OPENFDA_URL}?search=${encodeURIComponent(search).replace(/%2B/g, \"+\")}&sort=effective_time:desc&limit=5`;\n const data = await getJson<OpenFdaResponse>(url);\n const results = data?.results ?? [];\n if (results.length === 0) return null;\n return results.slice().sort((a, b) => score(b, q) - score(a, q))[0];\n}\n\nfunction usMeta(r: OpenFdaLabel, fallbackName: string) {\n const setId = r.openfda?.spl_set_id?.[0] ?? \"\";\n const brand = r.openfda?.brand_name?.[0] ?? \"\";\n const generic = r.openfda?.generic_name?.[0] ?? fallbackName;\n return {\n setId,\n brand,\n generic,\n productName: brand ? `${brand} (${generic})` : generic,\n dailymedUrl: setId\n ? `https://dailymed.nlm.nih.gov/dailymed/drugInfo.cfm?setid=${setId}`\n : \"https://dailymed.nlm.nih.gov/dailymed/\",\n };\n}\n\nfunction matchesUsFilter(entry: { field: string; code: string; title: string }, requested: string[]): boolean {\n return requested.some((req) => {\n const r = req.toLowerCase().trim();\n return (\n entry.code === req ||\n entry.title.toLowerCase().includes(r) ||\n String(entry.field).replace(/_/g, \" \").includes(r) ||\n r.includes(entry.title.toLowerCase())\n );\n });\n}\n\n/**\n * General US label access — returns every available section (or the requested\n * subset) as LabelSection[]. Powers PubCrawl's get_uspi.\n */\nexport async function getUsLabel(drug: string, requested?: string[]): Promise<UsLabelResult | null> {\n const r = await fetchOpenFdaBest(drug);\n if (!r) return null;\n const meta = usMeta(r, drug.trim());\n\n const sections: LabelSection[] = [];\n for (const entry of US_SECTION_MAP) {\n if (requested?.length && !matchesUsFilter(entry, requested)) continue;\n const content = cleanSection(r[entry.field]);\n if (content) sections.push({ code: entry.code, title: entry.title, content });\n }\n if (sections.length === 0) return null;\n\n return {\n drugName: meta.productName,\n setId: meta.setId,\n publishedDate: formatFdaDate(r.effective_time),\n sections,\n dailymedUrl: meta.dailymedUrl,\n };\n}\n\n/** 5-field convenience view of the US label (Pharmonym). */\nexport async function getUspi(drug: string): Promise<RegionLabel | null> {\n const r = await fetchOpenFdaBest(drug);\n if (!r) return null;\n const meta = usMeta(r, drug.trim());\n\n const dosage = cleanSection(r.dosage_and_administration);\n const boxed = cleanSection(r.boxed_warning);\n const warn = cleanSection(r.warnings_and_cautions ?? r.warnings);\n const warnings = [boxed ? `BOXED WARNING — ${boxed}` : \"\", warn].filter(Boolean).join(\"\\n\\n\");\n const adverse = cleanSection(r.adverse_reactions);\n const indications = cleanSection(r.indications_and_usage);\n const contraindications = cleanSection(r.contraindications);\n\n if (!dosage && !warnings && !adverse && !indications && !contraindications) return null;\n\n return {\n region: \"US\",\n authority: \"FDA Prescribing Information\",\n productName: meta.productName,\n indications: indications || null,\n dosage: dosage || null,\n contraindications: contraindications || null,\n warnings: warnings || null,\n adverse: adverse || null,\n updated: formatFdaDate(r.effective_time),\n sourceUrl: meta.dailymedUrl,\n sourceLabel: \"DailyMed (U.S. National Library of Medicine)\",\n };\n}\n\n/* --------------------------- UK/EU: eMC ---------------------------- */\n\ninterface EmcMatch {\n product_id: string;\n name: string;\n}\n\nasync function searchEmcAll(drug: string): Promise<EmcMatch[]> {\n const url = `${EMC_BASE}/emc/search?${new URLSearchParams({ q: drug })}`;\n const res = await timedFetch(url);\n if (!res.ok) return [];\n const html = await res.text();\n const $ = cheerio.load(html);\n const out: EmcMatch[] = [];\n $(\"a[href*='/emc/product/']\").each((_, el) => {\n const href = $(el).attr(\"href\") || \"\";\n const m = href.match(/\\/emc\\/product\\/(\\d+)\\/smpc/);\n if (!m) return;\n const name = $(el).text().trim();\n if (!name || name.toLowerCase().includes(\"health professional\")) return;\n if (out.some((x) => x.product_id === m[1])) return;\n out.push({ product_id: m[1], name });\n });\n return out;\n}\n\nasync function searchEmcBest(drug: string): Promise<EmcMatch | null> {\n const matches = await searchEmcAll(drug);\n if (matches.length === 0) return null;\n const q = drug.toLowerCase();\n matches.sort(\n (a, b) =>\n (b.name.toLowerCase().includes(q) ? 1 : 0) - (a.name.toLowerCase().includes(q) ? 1 : 0)\n );\n return matches[0];\n}\n\nconst SMPC_SECTION_NAMES: Record<string, string> = {\n \"4.1\": \"Therapeutic indications\",\n \"4.2\": \"Posology and method of administration\",\n \"4.3\": \"Contraindications\",\n \"4.4\": \"Special warnings and precautions for use\",\n \"4.5\": \"Interaction with other medicinal products\",\n \"4.6\": \"Fertility, pregnancy and lactation\",\n \"4.7\": \"Effects on ability to drive and use machines\",\n \"4.8\": \"Undesirable effects\",\n \"4.9\": \"Overdose\",\n \"5.1\": \"Pharmacodynamic properties\",\n \"5.2\": \"Pharmacokinetic properties\",\n \"5.3\": \"Preclinical safety data\",\n \"6.1\": \"List of excipients\",\n \"6.2\": \"Incompatibilities\",\n \"6.3\": \"Shelf life\",\n \"6.4\": \"Special precautions for storage\",\n \"6.5\": \"Nature and contents of container\",\n \"6.6\": \"Special precautions for disposal\",\n};\n\nfunction htmlToText(html: string): string {\n const $ = cheerio.load(html);\n $(\"br\").replaceWith(\"\\n\");\n $(\"li\").each((_, el) => {\n $(el).prepend(\"- \");\n $(el).append(\"\\n\");\n });\n $(\"p, div, tr, h1, h2, h3, h4, h5, h6\").each((_, el) => {\n $(el).append(\"\\n\");\n });\n return $.text().replace(/\\n{3,}/g, \"\\n\\n\").trim();\n}\n\nfunction matchesSmpcFilter(code: string, title: string, requested: string[]): boolean {\n return requested.some((req) => {\n const r = req.toLowerCase().trim();\n if (code === req || code === r) return true;\n if (title.toLowerCase().includes(r)) return true;\n if (r.includes(\"indication\")) return code === \"4.1\";\n if (r.includes(\"dosage\") || r.includes(\"dosing\") || r.includes(\"posology\")) return code === \"4.2\";\n if (r.includes(\"contraindication\")) return code === \"4.3\";\n if (r.includes(\"warning\") || r.includes(\"precaution\")) return code === \"4.4\";\n if (r.includes(\"interaction\")) return code === \"4.5\";\n if (r.includes(\"pregnancy\") || r.includes(\"fertility\")) return code === \"4.6\";\n if (r.includes(\"adverse\") || r.includes(\"undesirable\") || r.includes(\"side effect\")) return code === \"4.8\";\n if (r.includes(\"overdose\") || r.includes(\"overdosage\")) return code === \"4.9\";\n if (r.includes(\"pharmacodynamic\")) return code === \"5.1\";\n if (r.includes(\"pharmacokinetic\")) return code === \"5.2\";\n return false;\n });\n}\n\n/**\n * Parse SmPC sections from eMC HTML. Returns every detected numbered section as\n * LabelSection[] (or the requested subset). Detect numbered headings, collect\n * sibling content up to the next heading, with a parent-text fallback.\n */\nfunction parseSmpcSections(html: string, requested?: string[]): LabelSection[] {\n const $ = cheerio.load(html);\n const sectionElements: Array<{ code: string; title: string; element: any }> = [];\n\n $(\"[id*='SECTION'], [id*='section'], .sectionHeading, h2, h3, h4\").each((_, el) => {\n const text = $(el).text().trim();\n const match = text.match(/^(\\d+\\.?\\d*)\\s+(.+)/);\n if (match) sectionElements.push({ code: match[1], title: match[2].trim(), element: $(el) });\n });\n\n if (sectionElements.length === 0) {\n $(\"*\").each((_, el) => {\n const $el = $(el);\n if ($el.children().length > 0 && !$el.is(\"a, span, strong, em, b, i\")) return;\n const text = $el.text().trim();\n const match = text.match(/^(\\d+\\.?\\d*)\\s+(.+)/);\n if (match && SMPC_SECTION_NAMES[match[1]]) {\n sectionElements.push({ code: match[1], title: match[2].trim(), element: $el });\n }\n });\n }\n\n const out: LabelSection[] = [];\n for (let i = 0; i < sectionElements.length; i++) {\n const current = sectionElements[i];\n const next = sectionElements[i + 1];\n\n let content = \"\";\n let node = current.element.next();\n while (node.length > 0) {\n if (next && node.is(next.element)) break;\n const nodeHtml = $.html(node);\n if (nodeHtml) content += htmlToText(nodeHtml) + \"\\n\";\n node = node.next();\n }\n if (!content.trim()) {\n const parentHtml = $.html(current.element.parent());\n if (parentHtml) content = htmlToText(parentHtml).replace(current.element.text().trim(), \"\").trim();\n }\n\n const cleaned = cleanSection(content);\n if (cleaned) out.push({ code: current.code, title: current.title, content: cleaned });\n }\n\n if (requested?.length) {\n return out.filter((s) => matchesSmpcFilter(s.code, s.title, requested));\n }\n return out;\n}\n\n/**\n * General UK/EU SmPC access — returns every available section (or the requested\n * subset) as LabelSection[]. Powers PubCrawl's get_smpc.\n */\nexport async function getUkSmpc(drug: string, requested?: string[]): Promise<UkSmpcResult | null> {\n if (!drug) return null;\n try {\n const best = await searchEmcBest(drug);\n if (!best) return null;\n const res = await timedFetch(`${EMC_BASE}/emc/product/${best.product_id}/smpc`);\n if (!res.ok) return null;\n const sections = parseSmpcSections(await res.text(), requested);\n if (sections.length === 0) return null;\n return {\n drugName: best.name,\n productId: best.product_id,\n sections,\n url: `${EMC_BASE}/emc/product/${best.product_id}/smpc`,\n };\n } catch {\n return null;\n }\n}\n\n/** 5-field convenience view of the UK/EU SmPC (Pharmonym). */\nexport async function getSmpc(drug: string): Promise<RegionLabel | null> {\n if (!drug) return null;\n try {\n const best = await searchEmcBest(drug);\n if (!best) return null;\n const res = await timedFetch(`${EMC_BASE}/emc/product/${best.product_id}/smpc`);\n if (!res.ok) return null;\n const all = parseSmpcSections(await res.text());\n const pick = (code: string) => all.find((s) => s.code === code)?.content ?? null;\n\n const indications = pick(\"4.1\");\n const dosage = pick(\"4.2\");\n const contraindications = pick(\"4.3\");\n const warnings = pick(\"4.4\");\n const adverse = pick(\"4.8\");\n if (!dosage && !warnings && !adverse && !indications && !contraindications) return null;\n\n return {\n region: \"UK/EU\",\n authority: \"Summary of Product Characteristics (SmPC)\",\n productName: best.name,\n indications,\n dosage,\n contraindications,\n warnings,\n adverse,\n updated: \"\",\n sourceUrl: `${EMC_BASE}/emc/product/${best.product_id}/smpc`,\n sourceLabel: \"electronic medicines compendium (emc), UK\",\n };\n } catch {\n return null;\n }\n}\n\n/* --------------------------- public API ---------------------------- */\n\n/** Look up both 5-field labels for a drug. Each side is null if unavailable. */\nexport async function getLabels(drug: string): Promise<LabelResult> {\n const [us, uk] = await Promise.all([\n getUspi(drug).catch(() => null),\n getSmpc(drug).catch(() => null),\n ]);\n return { us, uk };\n}\n","/**\n * US (FDA) vs UK/EU (SmPC) side-by-side label comparison across mapped topics.\n */\n\nimport { getLabels } from \"./labels.js\";\nimport type { CompareResult, LabelComparison } from \"./types.js\";\n\ntype LabelField = \"indications\" | \"dosage\" | \"contraindications\" | \"warnings\" | \"adverse\";\n\n/** Topics compared across US and UK labels, mapped to the fields that hold them. */\nexport const SECTION_MAP: Array<{ topic: string; field: LabelField }> = [\n { topic: \"Indications\", field: \"indications\" },\n { topic: \"Dosing\", field: \"dosage\" },\n { topic: \"Contraindications\", field: \"contraindications\" },\n { topic: \"Warnings\", field: \"warnings\" },\n { topic: \"Adverse Reactions\", field: \"adverse\" },\n];\n\nexport async function compareLabels(drug: string): Promise<CompareResult> {\n const { us, uk } = await getLabels(drug);\n\n const comparisons: LabelComparison[] = SECTION_MAP.map(({ topic, field }) => ({\n topic,\n us: us ? us[field] : null,\n uk: uk ? uk[field] : null,\n })).filter((c) => c.us || c.uk);\n\n return {\n drug,\n comparisons,\n usSource: us?.sourceUrl ?? null,\n ukSource: uk?.sourceUrl ?? null,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACKA,IAAM,qBAAqB;AAC3B,IAAM,kBAAkB;AAEjB,IAAM,aAAa;AAE1B,eAAsB,WACpB,KACA,OAAoB,CAAC,GACrB,YAAY,oBACO;AACnB,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,SAAS;AAC5D,MAAI;AACF,WAAO,MAAM,MAAM,KAAK;AAAA,MACtB,GAAG;AAAA,MACH,QAAQ,WAAW;AAAA,MACnB,SAAS,EAAE,cAAc,YAAY,GAAK,KAAK,WAAsC,CAAC,EAAG;AAAA,IAC3F,CAAC;AAAA,EACH,UAAE;AACA,iBAAa,KAAK;AAAA,EACpB;AACF;AAEA,eAAsB,QACpB,KACA,YAAY,oBACO;AACnB,MAAI;AACF,UAAM,MAAM,MAAM,WAAW,KAAK,CAAC,GAAG,SAAS;AAC/C,QAAI,CAAC,IAAI,GAAI,QAAO;AACpB,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMO,SAAS,aAAa,OAAgB,WAAW,iBAAyB;AAC/E,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,OAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,MAAM,IAAI,OAAO,KAAK;AAEnE,SAAO,KACJ,QAAQ,OAAO,EAAE,EACjB,QAAQ,WAAW,GAAG,EACtB,QAAQ,WAAW,MAAM,EACzB,KAAK;AAGR,SAAO,KAAK,QAAQ,0CAA0C,EAAE,EAAE,KAAK;AAEvE,MAAI,KAAK,SAAS,UAAU;AAC1B,UAAM,QAAQ,KAAK,MAAM,GAAG,QAAQ;AACpC,UAAM,WAAW,KAAK,IAAI,MAAM,YAAY,IAAI,GAAG,MAAM,YAAY,IAAI,CAAC;AAC1E,YAAQ,WAAW,WAAW,MAAM,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,OAAO,KAAK,IAAI;AAAA,EACrF;AACA,SAAO;AACT;AAEO,SAAS,cAAc,UAA2B;AACvD,MAAI,CAAC,YAAY,CAAC,UAAU,KAAK,QAAQ,EAAG,QAAO;AACnD,QAAM,IAAI,oBAAI,KAAK,GAAG,SAAS,MAAM,GAAG,CAAC,CAAC,IAAI,SAAS,MAAM,GAAG,CAAC,CAAC,IAAI,SAAS,MAAM,GAAG,CAAC,CAAC,EAAE;AAC5F,MAAI,MAAM,EAAE,QAAQ,CAAC,EAAG,QAAO;AAC/B,SAAO,EAAE,mBAAmB,SAAS,EAAE,MAAM,WAAW,OAAO,SAAS,KAAK,UAAU,CAAC;AAC1F;AAEO,SAAS,UAAU,GAAmB;AAC3C,SAAO,OAAO,KAAK,EAAE,EAAE,QAAQ,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,IAAI,EAAE,MAAM,CAAC,EAAE,YAAY,CAAC;AACtG;AAEO,SAAS,KAAQ,KAAe;AACrC,SAAO,CAAC,GAAG,IAAI,IAAI,IAAI,OAAO,OAAO,CAAC,CAAC;AACzC;;;ACtEA,IAAM,QAAQ;AACd,IAAM,UAAU;AAChB,IAAM,aAAa;AAEnB,IAAM,cAAc;AAIpB,eAAe,UAAU,MAAsC;AAC7D,QAAM,QAAQ,MAAM,QAAa,GAAG,KAAK,oBAAoB,mBAAmB,IAAI,CAAC,IAAI,UAAU;AACnG,QAAM,KAAK,OAAO,SAAS,WAAW,CAAC;AACvC,MAAI,GAAI,QAAO;AAEf,QAAM,SAAS,MAAM;AAAA,IACnB,GAAG,KAAK,8BAA8B,mBAAmB,IAAI,CAAC;AAAA,IAC9D;AAAA,EACF;AACA,QAAM,OAAO,QAAQ,kBAAkB,YAAY,CAAC;AACpD,SAAO,OAAO,KAAK,QAAQ;AAC7B;AAEA,SAAS,aAAa,YAAiB,KAAuB;AAC5D,QAAM,SAAS,YAAY,iBAAiB,gBAAgB,CAAC;AAC7D,QAAM,IAAI,OAAO,KAAK,CAAC,MAAW,EAAE,QAAQ,GAAG;AAC/C,MAAI,CAAC,KAAK,CAAC,EAAE,kBAAmB,QAAO,CAAC;AACxC,SAAO,EAAE,kBAAkB,IAAI,CAAC,MAAW,EAAE,IAAI;AACnD;AAOA,eAAe,0BAA0B,iBAA4C;AACnF,QAAM,OAAO,MAAM,QAAa,GAAG,KAAK,UAAU,eAAe,0BAA0B,UAAU;AACrG,QAAM,SAAS,MAAM,cAAc,gBAAgB,CAAC;AACpD,QAAM,IAAI,OAAO,KAAK,CAAC,MAAW,EAAE,QAAQ,MAAM;AAClD,MAAI,CAAC,KAAK,CAAC,EAAE,kBAAmB,QAAO,CAAC;AACxC,QAAM,SAAmB,CAAC;AAC1B,aAAW,KAAK,EAAE,mBAAmB;AACnC,UAAM,IAAK,EAAE,KAAgB,MAAM,kBAAkB;AACrD,QAAI,CAAC,EAAG;AACR,UAAM,SAAU,EAAE,KAAgB,MAAM,GAAI,EAAE,KAAgB,YAAY,GAAG,CAAC;AAC9E,QAAI,OAAO,SAAS,GAAG,EAAG;AAC1B,WAAO,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC;AAAA,EACzB;AACA,SAAO,KAAK,MAAM,EAAE,KAAK;AAC3B;AAEA,eAAe,oBACb,OACuD;AACvD,QAAM,OAAO,MAAM;AAAA,IACjB,GAAG,KAAK,qCAAqC,KAAK;AAAA,IAClD;AAAA,EACF;AACA,QAAM,QAAQ,MAAM,qBAAqB,mBAAmB,CAAC;AAE7D,QAAM,SAAS,CAAC,MAA6B;AAC3C,UAAM,MAAM,MAAM,KAAK,CAAC,MAAW,EAAE,sBAAsB,cAAc,CAAC;AAC1E,WAAO,MAAM,IAAI,sBAAsB,YAAY;AAAA,EACrD;AAEA,QAAM,YAAY,OAAO,KAAK,KAAK,OAAO,KAAK,KAAK,OAAO,MAAM;AAEjE,QAAM,OAAO;AAAA,IACX,MACG,OAAO,CAAC,MAAW,EAAE,SAAS,WAAW,EACzC,IAAI,CAAC,MAAW,EAAE,sBAAsB,SAAmB;AAAA,EAChE,EAAE,MAAM,GAAG,CAAC;AAEZ,SAAO,EAAE,WAAW,KAAK;AAC3B;AAEA,eAAe,iBAAiB,MAA6C;AAC3E,QAAM,QAAQ,MAAM,UAAU,IAAI;AAClC,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,CAAC,OAAO,UAAU,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC5C,QAAa,GAAG,KAAK,UAAU,KAAK,oBAAoB,UAAU;AAAA,IAClE,QAAa,GAAG,KAAK,UAAU,KAAK,oBAAoB,UAAU;AAAA,EACpE,CAAC;AAED,QAAM,MAA0B,OAAO,YAAY;AACnD,QAAM,gBAAwB,OAAO,YAAY,QAAQ;AACzD,MAAI,CAAC,IAAK,QAAO;AAEjB,QAAM,UAAU,aAAa,YAAY,IAAI;AAC7C,QAAM,WAAW,aAAa,YAAY,KAAK;AAE/C,QAAM,UAAU,QAAQ,QAAQ,QAAQ,SAAS,QAAQ;AACzD,QAAM,YAAiC,UAAU,UAAU;AAG3D,MAAI,aAA8B;AAClC,MAAI,CAAC,SAAS;AACZ,UAAM,SAAS,MAAM,0BAA0B,KAAK;AACpD,UAAM,WAAW,KAAK,CAAC,GAAG,aAAa,YAAY,IAAI,GAAG,GAAG,aAAa,YAAY,MAAM,CAAC,CAAC,EAAE,KAAK;AACrG,iBAAa,OAAO,SAAS,SAAS;AAAA,EACxC;AAGA,MAAI,cAA6B;AACjC,MAAI,SAAS,OAAQ,eAAc,UAAU,SAAS,CAAC,CAAC;AAAA,WAC/C,QAAQ,WAAW,EAAG,eAAc,UAAU,QAAQ,CAAC,CAAC;AAAA,WACxD,QAAQ,SAAS,EAAG,eAAc,UAAU,KAAK,OAAO,EAAE,KAAK,KAAK,CAAC;AAG9E,QAAM,aAAa,CAAC,UAChB,SACC,MAAM;AACL,UAAM,WAAW,YAAY,iBAAiB,gBAAgB,CAAC,GAAG,KAAK,CAAC,MAAW,EAAE,QAAQ,IAAI;AACjG,WAAO,SAAS,oBAAoB,CAAC,GAAG,SAAS;AAAA,EACnD,GAAG;AACP,QAAM,EAAE,WAAW,KAAK,IAAI,MAAM,oBAAoB,UAAU;AAEhE,SAAO;AAAA,IACL;AAAA,IACA,WAAW,UAAU,aAAa;AAAA,IAClC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR,YAAY;AAAA,EACd;AACF;AAIA,eAAe,kBAAkB,MAA6C;AAC5E,QAAM,IAAI,KAAK,QAAQ,MAAM,EAAE;AAC/B,QAAM,SAAS,wBAAwB,CAAC,8BAA8B,CAAC;AACvE,QAAM,MAAM,GAAG,OAAO,WAAW,mBAAmB,MAAM,EAAE,QAAQ,QAAQ,GAAG,CAAC;AAChF,QAAM,OAAO,MAAM,QAAa,KAAK,UAAU;AAC/C,QAAM,MAAM,MAAM,UAAU,CAAC,GAAG;AAChC,MAAI,CAAC,IAAK,QAAO;AAEjB,QAAM,SAAS,IAAI,cAAc,CAAC,GAAG,CAAC,KAAK;AAC3C,QAAM,WAAW,IAAI,gBAAgB,CAAC,GAAG,CAAC,KAAK;AAC/C,MAAI,CAAC,SAAS,CAAC,QAAS,QAAO;AAE/B,QAAM,UAAU,MAAM,YAAY,MAAM,KAAK,YAAY;AACzD,SAAO;AAAA,IACL,WAAW,UAAU,UAAU;AAAA,IAC/B,WAAW,UAAU,IAAI;AAAA,IACzB,aAAa,UAAU,UAAU,OAAO,IAAI;AAAA,IAC5C,YAAY,UAAU,OAAO,KAAa,IAAI,cAAc,CAAC,CAAC,EAAE,KAAK;AAAA,IACrE,YAAY,IAAI,mBAAmB,CAAC,GAAG,CAAC,MAAM,IAAI,mBAAmB,CAAC,GAAG,CAAC,KAAK;AAAA,IAC/E,MAAM,CAAC;AAAA,IACP,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,YAAY;AAAA,EACd;AACF;AAQA,eAAsB,YAAY,MAA6C;AAC7E,MAAI,CAAC,QAAQ,CAAC,KAAK,KAAK,EAAG,QAAO;AAClC,QAAM,QAAQ,KAAK,KAAK;AACxB,QAAM,QAAQ,MAAM,iBAAiB,KAAK,EAAE,MAAM,MAAM,IAAI;AAC5D,MAAI,UAAU,MAAM,eAAgB,MAAM,cAAc,MAAM,WAAW,SAAU;AACjF,WAAO;AAAA,EACT;AACA,SAAO,MAAM,kBAAkB,KAAK,EAAE,MAAM,MAAM,IAAI;AACxD;;;ACrKA,cAAyB;AAIzB,IAAM,cAAc;AACpB,IAAM,WAAW;AA6BjB,IAAM,iBAAoF;AAAA,EACxF,EAAE,OAAO,iBAAiB,MAAM,WAAW,OAAO,gBAAgB;AAAA,EAClE,EAAE,OAAO,yBAAyB,MAAM,WAAW,OAAO,wBAAwB;AAAA,EAClF,EAAE,OAAO,6BAA6B,MAAM,WAAW,OAAO,4BAA4B;AAAA,EAC1F,EAAE,OAAO,8BAA8B,MAAM,WAAW,OAAO,6BAA6B;AAAA,EAC5F,EAAE,OAAO,qBAAqB,MAAM,WAAW,OAAO,oBAAoB;AAAA,EAC1E,EAAE,OAAO,yBAAyB,MAAM,WAAW,OAAO,2BAA2B;AAAA,EACrF,EAAE,OAAO,YAAY,MAAM,WAAW,OAAO,WAAW;AAAA,EACxD,EAAE,OAAO,qBAAqB,MAAM,WAAW,OAAO,oBAAoB;AAAA,EAC1E,EAAE,OAAO,qBAAqB,MAAM,WAAW,OAAO,oBAAoB;AAAA,EAC1E,EAAE,OAAO,+BAA+B,MAAM,WAAW,OAAO,8BAA8B;AAAA,EAC9F,EAAE,OAAO,cAAc,MAAM,WAAW,OAAO,aAAa;AAAA,EAC5D,EAAE,OAAO,yBAAyB,MAAM,WAAW,OAAO,wBAAwB;AAAA,EAClF,EAAE,OAAO,eAAe,MAAM,WAAW,OAAO,cAAc;AAAA,EAC9D,EAAE,OAAO,gBAAgB,MAAM,WAAW,OAAO,eAAe;AAClE;AAIA,SAAS,MAAM,GAAiB,GAAmB;AACjD,QAAM,KAAK,EAAE,SAAS,gBAAgB,CAAC,GAAG,KAAK,IAAI,EAAE,YAAY;AACjE,MAAI,IAAI;AACR,MAAI,KAAK,EAAE,MAAM,SAAS,EAAE,WAAW,EAAG,MAAK;AAC/C,MAAI,EAAE,SAAS,EAAE,YAAY,CAAC,EAAG,MAAK;AACtC,SAAO;AACT;AAGA,eAAe,iBAAiB,MAA4C;AAC1E,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,IAAI,KAAK,KAAK,EAAE,QAAQ,MAAM,EAAE;AACtC,QAAM,SAAS,0BAA0B,CAAC,4BAA4B,CAAC;AACvE,QAAM,MAAM,GAAG,WAAW,WAAW,mBAAmB,MAAM,EAAE,QAAQ,QAAQ,GAAG,CAAC;AACpF,QAAM,OAAO,MAAM,QAAyB,GAAG;AAC/C,QAAM,UAAU,MAAM,WAAW,CAAC;AAClC,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,SAAO,QAAQ,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,MAAM,GAAG,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC;AACpE;AAEA,SAAS,OAAO,GAAiB,cAAsB;AACrD,QAAM,QAAQ,EAAE,SAAS,aAAa,CAAC,KAAK;AAC5C,QAAM,QAAQ,EAAE,SAAS,aAAa,CAAC,KAAK;AAC5C,QAAM,UAAU,EAAE,SAAS,eAAe,CAAC,KAAK;AAChD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa,QAAQ,GAAG,KAAK,KAAK,OAAO,MAAM;AAAA,IAC/C,aAAa,QACT,4DAA4D,KAAK,KACjE;AAAA,EACN;AACF;AAEA,SAAS,gBAAgB,OAAuD,WAA8B;AAC5G,SAAO,UAAU,KAAK,CAAC,QAAQ;AAC7B,UAAM,IAAI,IAAI,YAAY,EAAE,KAAK;AACjC,WACE,MAAM,SAAS,OACf,MAAM,MAAM,YAAY,EAAE,SAAS,CAAC,KACpC,OAAO,MAAM,KAAK,EAAE,QAAQ,MAAM,GAAG,EAAE,SAAS,CAAC,KACjD,EAAE,SAAS,MAAM,MAAM,YAAY,CAAC;AAAA,EAExC,CAAC;AACH;AAMA,eAAsB,WAAW,MAAc,WAAqD;AAClG,QAAM,IAAI,MAAM,iBAAiB,IAAI;AACrC,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,OAAO,OAAO,GAAG,KAAK,KAAK,CAAC;AAElC,QAAM,WAA2B,CAAC;AAClC,aAAW,SAAS,gBAAgB;AAClC,QAAI,WAAW,UAAU,CAAC,gBAAgB,OAAO,SAAS,EAAG;AAC7D,UAAM,UAAU,aAAa,EAAE,MAAM,KAAK,CAAC;AAC3C,QAAI,QAAS,UAAS,KAAK,EAAE,MAAM,MAAM,MAAM,OAAO,MAAM,OAAO,QAAQ,CAAC;AAAA,EAC9E;AACA,MAAI,SAAS,WAAW,EAAG,QAAO;AAElC,SAAO;AAAA,IACL,UAAU,KAAK;AAAA,IACf,OAAO,KAAK;AAAA,IACZ,eAAe,cAAc,EAAE,cAAc;AAAA,IAC7C;AAAA,IACA,aAAa,KAAK;AAAA,EACpB;AACF;AAGA,eAAsB,QAAQ,MAA2C;AACvE,QAAM,IAAI,MAAM,iBAAiB,IAAI;AACrC,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,OAAO,OAAO,GAAG,KAAK,KAAK,CAAC;AAElC,QAAM,SAAS,aAAa,EAAE,yBAAyB;AACvD,QAAM,QAAQ,aAAa,EAAE,aAAa;AAC1C,QAAM,OAAO,aAAa,EAAE,yBAAyB,EAAE,QAAQ;AAC/D,QAAM,WAAW,CAAC,QAAQ,wBAAmB,KAAK,KAAK,IAAI,IAAI,EAAE,OAAO,OAAO,EAAE,KAAK,MAAM;AAC5F,QAAM,UAAU,aAAa,EAAE,iBAAiB;AAChD,QAAM,cAAc,aAAa,EAAE,qBAAqB;AACxD,QAAM,oBAAoB,aAAa,EAAE,iBAAiB;AAE1D,MAAI,CAAC,UAAU,CAAC,YAAY,CAAC,WAAW,CAAC,eAAe,CAAC,kBAAmB,QAAO;AAEnF,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,aAAa,KAAK;AAAA,IAClB,aAAa,eAAe;AAAA,IAC5B,QAAQ,UAAU;AAAA,IAClB,mBAAmB,qBAAqB;AAAA,IACxC,UAAU,YAAY;AAAA,IACtB,SAAS,WAAW;AAAA,IACpB,SAAS,cAAc,EAAE,cAAc;AAAA,IACvC,WAAW,KAAK;AAAA,IAChB,aAAa;AAAA,EACf;AACF;AASA,eAAe,aAAa,MAAmC;AAC7D,QAAM,MAAM,GAAG,QAAQ,eAAe,IAAI,gBAAgB,EAAE,GAAG,KAAK,CAAC,CAAC;AACtE,QAAM,MAAM,MAAM,WAAW,GAAG;AAChC,MAAI,CAAC,IAAI,GAAI,QAAO,CAAC;AACrB,QAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,QAAM,IAAY,aAAK,IAAI;AAC3B,QAAM,MAAkB,CAAC;AACzB,IAAE,0BAA0B,EAAE,KAAK,CAAC,GAAG,OAAO;AAC5C,UAAM,OAAO,EAAE,EAAE,EAAE,KAAK,MAAM,KAAK;AACnC,UAAM,IAAI,KAAK,MAAM,6BAA6B;AAClD,QAAI,CAAC,EAAG;AACR,UAAM,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK;AAC/B,QAAI,CAAC,QAAQ,KAAK,YAAY,EAAE,SAAS,qBAAqB,EAAG;AACjE,QAAI,IAAI,KAAK,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC,CAAC,EAAG;AAC5C,QAAI,KAAK,EAAE,YAAY,EAAE,CAAC,GAAG,KAAK,CAAC;AAAA,EACrC,CAAC;AACD,SAAO;AACT;AAEA,eAAe,cAAc,MAAwC;AACnE,QAAM,UAAU,MAAM,aAAa,IAAI;AACvC,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,QAAM,IAAI,KAAK,YAAY;AAC3B,UAAQ;AAAA,IACN,CAAC,GAAG,OACD,EAAE,KAAK,YAAY,EAAE,SAAS,CAAC,IAAI,IAAI,MAAM,EAAE,KAAK,YAAY,EAAE,SAAS,CAAC,IAAI,IAAI;AAAA,EACzF;AACA,SAAO,QAAQ,CAAC;AAClB;AAEA,IAAM,qBAA6C;AAAA,EACjD,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACT;AAEA,SAAS,WAAW,MAAsB;AACxC,QAAM,IAAY,aAAK,IAAI;AAC3B,IAAE,IAAI,EAAE,YAAY,IAAI;AACxB,IAAE,IAAI,EAAE,KAAK,CAAC,GAAG,OAAO;AACtB,MAAE,EAAE,EAAE,QAAQ,IAAI;AAClB,MAAE,EAAE,EAAE,OAAO,IAAI;AAAA,EACnB,CAAC;AACD,IAAE,oCAAoC,EAAE,KAAK,CAAC,GAAG,OAAO;AACtD,MAAE,EAAE,EAAE,OAAO,IAAI;AAAA,EACnB,CAAC;AACD,SAAO,EAAE,KAAK,EAAE,QAAQ,WAAW,MAAM,EAAE,KAAK;AAClD;AAEA,SAAS,kBAAkB,MAAc,OAAe,WAA8B;AACpF,SAAO,UAAU,KAAK,CAAC,QAAQ;AAC7B,UAAM,IAAI,IAAI,YAAY,EAAE,KAAK;AACjC,QAAI,SAAS,OAAO,SAAS,EAAG,QAAO;AACvC,QAAI,MAAM,YAAY,EAAE,SAAS,CAAC,EAAG,QAAO;AAC5C,QAAI,EAAE,SAAS,YAAY,EAAG,QAAO,SAAS;AAC9C,QAAI,EAAE,SAAS,QAAQ,KAAK,EAAE,SAAS,QAAQ,KAAK,EAAE,SAAS,UAAU,EAAG,QAAO,SAAS;AAC5F,QAAI,EAAE,SAAS,kBAAkB,EAAG,QAAO,SAAS;AACpD,QAAI,EAAE,SAAS,SAAS,KAAK,EAAE,SAAS,YAAY,EAAG,QAAO,SAAS;AACvE,QAAI,EAAE,SAAS,aAAa,EAAG,QAAO,SAAS;AAC/C,QAAI,EAAE,SAAS,WAAW,KAAK,EAAE,SAAS,WAAW,EAAG,QAAO,SAAS;AACxE,QAAI,EAAE,SAAS,SAAS,KAAK,EAAE,SAAS,aAAa,KAAK,EAAE,SAAS,aAAa,EAAG,QAAO,SAAS;AACrG,QAAI,EAAE,SAAS,UAAU,KAAK,EAAE,SAAS,YAAY,EAAG,QAAO,SAAS;AACxE,QAAI,EAAE,SAAS,iBAAiB,EAAG,QAAO,SAAS;AACnD,QAAI,EAAE,SAAS,iBAAiB,EAAG,QAAO,SAAS;AACnD,WAAO;AAAA,EACT,CAAC;AACH;AAOA,SAAS,kBAAkB,MAAc,WAAsC;AAC7E,QAAM,IAAY,aAAK,IAAI;AAC3B,QAAM,kBAAwE,CAAC;AAE/E,IAAE,+DAA+D,EAAE,KAAK,CAAC,GAAG,OAAO;AACjF,UAAM,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK;AAC/B,UAAM,QAAQ,KAAK,MAAM,qBAAqB;AAC9C,QAAI,MAAO,iBAAgB,KAAK,EAAE,MAAM,MAAM,CAAC,GAAG,OAAO,MAAM,CAAC,EAAE,KAAK,GAAG,SAAS,EAAE,EAAE,EAAE,CAAC;AAAA,EAC5F,CAAC;AAED,MAAI,gBAAgB,WAAW,GAAG;AAChC,MAAE,GAAG,EAAE,KAAK,CAAC,GAAG,OAAO;AACrB,YAAM,MAAM,EAAE,EAAE;AAChB,UAAI,IAAI,SAAS,EAAE,SAAS,KAAK,CAAC,IAAI,GAAG,2BAA2B,EAAG;AACvE,YAAM,OAAO,IAAI,KAAK,EAAE,KAAK;AAC7B,YAAM,QAAQ,KAAK,MAAM,qBAAqB;AAC9C,UAAI,SAAS,mBAAmB,MAAM,CAAC,CAAC,GAAG;AACzC,wBAAgB,KAAK,EAAE,MAAM,MAAM,CAAC,GAAG,OAAO,MAAM,CAAC,EAAE,KAAK,GAAG,SAAS,IAAI,CAAC;AAAA,MAC/E;AAAA,IACF,CAAC;AAAA,EACH;AAEA,QAAM,MAAsB,CAAC;AAC7B,WAAS,IAAI,GAAG,IAAI,gBAAgB,QAAQ,KAAK;AAC/C,UAAM,UAAU,gBAAgB,CAAC;AACjC,UAAM,OAAO,gBAAgB,IAAI,CAAC;AAElC,QAAI,UAAU;AACd,QAAI,OAAO,QAAQ,QAAQ,KAAK;AAChC,WAAO,KAAK,SAAS,GAAG;AACtB,UAAI,QAAQ,KAAK,GAAG,KAAK,OAAO,EAAG;AACnC,YAAM,WAAW,EAAE,KAAK,IAAI;AAC5B,UAAI,SAAU,YAAW,WAAW,QAAQ,IAAI;AAChD,aAAO,KAAK,KAAK;AAAA,IACnB;AACA,QAAI,CAAC,QAAQ,KAAK,GAAG;AACnB,YAAM,aAAa,EAAE,KAAK,QAAQ,QAAQ,OAAO,CAAC;AAClD,UAAI,WAAY,WAAU,WAAW,UAAU,EAAE,QAAQ,QAAQ,QAAQ,KAAK,EAAE,KAAK,GAAG,EAAE,EAAE,KAAK;AAAA,IACnG;AAEA,UAAM,UAAU,aAAa,OAAO;AACpC,QAAI,QAAS,KAAI,KAAK,EAAE,MAAM,QAAQ,MAAM,OAAO,QAAQ,OAAO,SAAS,QAAQ,CAAC;AAAA,EACtF;AAEA,MAAI,WAAW,QAAQ;AACrB,WAAO,IAAI,OAAO,CAAC,MAAM,kBAAkB,EAAE,MAAM,EAAE,OAAO,SAAS,CAAC;AAAA,EACxE;AACA,SAAO;AACT;AAMA,eAAsB,UAAU,MAAc,WAAoD;AAChG,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI;AACF,UAAM,OAAO,MAAM,cAAc,IAAI;AACrC,QAAI,CAAC,KAAM,QAAO;AAClB,UAAM,MAAM,MAAM,WAAW,GAAG,QAAQ,gBAAgB,KAAK,UAAU,OAAO;AAC9E,QAAI,CAAC,IAAI,GAAI,QAAO;AACpB,UAAM,WAAW,kBAAkB,MAAM,IAAI,KAAK,GAAG,SAAS;AAC9D,QAAI,SAAS,WAAW,EAAG,QAAO;AAClC,WAAO;AAAA,MACL,UAAU,KAAK;AAAA,MACf,WAAW,KAAK;AAAA,MAChB;AAAA,MACA,KAAK,GAAG,QAAQ,gBAAgB,KAAK,UAAU;AAAA,IACjD;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,eAAsB,QAAQ,MAA2C;AACvE,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI;AACF,UAAM,OAAO,MAAM,cAAc,IAAI;AACrC,QAAI,CAAC,KAAM,QAAO;AAClB,UAAM,MAAM,MAAM,WAAW,GAAG,QAAQ,gBAAgB,KAAK,UAAU,OAAO;AAC9E,QAAI,CAAC,IAAI,GAAI,QAAO;AACpB,UAAM,MAAM,kBAAkB,MAAM,IAAI,KAAK,CAAC;AAC9C,UAAM,OAAO,CAAC,SAAiB,IAAI,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI,GAAG,WAAW;AAE5E,UAAM,cAAc,KAAK,KAAK;AAC9B,UAAM,SAAS,KAAK,KAAK;AACzB,UAAM,oBAAoB,KAAK,KAAK;AACpC,UAAM,WAAW,KAAK,KAAK;AAC3B,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,UAAU,CAAC,YAAY,CAAC,WAAW,CAAC,eAAe,CAAC,kBAAmB,QAAO;AAEnF,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,aAAa,KAAK;AAAA,MAClB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT,WAAW,GAAG,QAAQ,gBAAgB,KAAK,UAAU;AAAA,MACrD,aAAa;AAAA,IACf;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,eAAsB,UAAU,MAAoC;AAClE,QAAM,CAAC,IAAI,EAAE,IAAI,MAAM,QAAQ,IAAI;AAAA,IACjC,QAAQ,IAAI,EAAE,MAAM,MAAM,IAAI;AAAA,IAC9B,QAAQ,IAAI,EAAE,MAAM,MAAM,IAAI;AAAA,EAChC,CAAC;AACD,SAAO,EAAE,IAAI,GAAG;AAClB;;;ACxXO,IAAM,cAA2D;AAAA,EACtE,EAAE,OAAO,eAAe,OAAO,cAAc;AAAA,EAC7C,EAAE,OAAO,UAAU,OAAO,SAAS;AAAA,EACnC,EAAE,OAAO,qBAAqB,OAAO,oBAAoB;AAAA,EACzD,EAAE,OAAO,YAAY,OAAO,WAAW;AAAA,EACvC,EAAE,OAAO,qBAAqB,OAAO,UAAU;AACjD;AAEA,eAAsB,cAAc,MAAsC;AACxE,QAAM,EAAE,IAAI,GAAG,IAAI,MAAM,UAAU,IAAI;AAEvC,QAAM,cAAiC,YAAY,IAAI,CAAC,EAAE,OAAO,MAAM,OAAO;AAAA,IAC5E;AAAA,IACA,IAAI,KAAK,GAAG,KAAK,IAAI;AAAA,IACrB,IAAI,KAAK,GAAG,KAAK,IAAI;AAAA,EACvB,EAAE,EAAE,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE;AAE9B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,UAAU,IAAI,aAAa;AAAA,IAC3B,UAAU,IAAI,aAAa;AAAA,EAC7B;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/fetch.ts","../src/names.ts","../src/labels.ts","../src/compare.ts"],"sourcesContent":["// @pharmatools/drug-data — public surface\n\nexport { resolveName } from \"./names.js\";\n// 5-field convenience view (Pharmonym)\nexport { getUspi, getSmpc, getLabels } from \"./labels.js\";\n// General all-section access (PubCrawl)\nexport { getUsLabel, getUkSmpc } from \"./labels.js\";\nexport { compareLabels, SECTION_MAP } from \"./compare.js\";\nexport { cleanSection, formatFdaDate } from \"./fetch.js\";\nexport type {\n Region,\n RegionLabel,\n LabelResult,\n LabelSection,\n UsLabelResult,\n UkSmpcResult,\n ResolveResult,\n LabelComparison,\n CompareResult,\n} from \"./types.js\";\n","/**\n * Shared HTTP + text helpers. Uses the runtime's global `fetch` (Node >=18),\n * so the package has no node-fetch dependency.\n */\n\nconst DEFAULT_TIMEOUT_MS = 12000;\nconst MAX_FIELD_CHARS = 1400;\n\nexport const USER_AGENT = \"drug-data/0.1 (pharmatools.ai; nick@pharmatools.ai)\";\n\nexport async function timedFetch(\n url: string,\n opts: RequestInit = {},\n timeoutMs = DEFAULT_TIMEOUT_MS\n): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n return await fetch(url, {\n ...opts,\n signal: controller.signal,\n headers: { \"User-Agent\": USER_AGENT, ...((opts.headers as Record<string, string>) || {}) },\n });\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function getJson<T = unknown>(\n url: string,\n timeoutMs = DEFAULT_TIMEOUT_MS\n): Promise<T | null> {\n try {\n const res = await timedFetch(url, {}, timeoutMs);\n if (!res.ok) return null;\n return (await res.json()) as T;\n } catch {\n return null;\n }\n}\n\n/**\n * Tidy a raw label section: join arrays, strip a leading numbered ALL-CAPS\n * heading, collapse whitespace, and truncate on a sentence boundary.\n */\nexport function cleanSection(value: unknown, maxChars = MAX_FIELD_CHARS): string {\n if (!value) return \"\";\n let text = Array.isArray(value) ? value.join(\"\\n\\n\") : String(value);\n\n text = text\n .replace(/\\r/g, \"\")\n .replace(/[ \\t]+/g, \" \")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n\n // Drop a leading section header line like \"5 WARNINGS AND PRECAUTIONS\"\n text = text.replace(/^\\s*\\d+(\\.\\d+)*\\s+[A-Z][A-Z \\-/,&]+\\n+/, \"\").trim();\n\n if (text.length > maxChars) {\n const slice = text.slice(0, maxChars);\n const lastStop = Math.max(slice.lastIndexOf(\". \"), slice.lastIndexOf(\"\\n\"));\n text = (lastStop > maxChars * 0.6 ? slice.slice(0, lastStop + 1) : slice).trim() + \" …\";\n }\n return text;\n}\n\nexport function formatFdaDate(yyyymmdd?: string): string {\n if (!yyyymmdd || !/^\\d{8}$/.test(yyyymmdd)) return \"\";\n const d = new Date(`${yyyymmdd.slice(0, 4)}-${yyyymmdd.slice(4, 6)}-${yyyymmdd.slice(6, 8)}`);\n if (isNaN(d.getTime())) return \"\";\n return d.toLocaleDateString(\"en-US\", { year: \"numeric\", month: \"short\", day: \"numeric\" });\n}\n\nexport function titleCase(s: string): string {\n return String(s || \"\").replace(/\\w\\S*/g, (t) => t.charAt(0).toUpperCase() + t.slice(1).toLowerCase());\n}\n\nexport function uniq<T>(arr: T[]): T[] {\n return [...new Set(arr.filter(Boolean))];\n}\n","/**\n * Deterministic brand <-> generic resolution via RxNorm/RxNav, with an openFDA\n * fallback. No AI, no hallucinated names. Brand names are US-registered;\n * international brand names are intentionally not invented.\n */\n\nimport { getJson, titleCase, uniq } from \"./fetch.js\";\nimport type { ResolveResult } from \"./types.js\";\n\nconst RXNAV = \"https://rxnav.nlm.nih.gov/REST\";\nconst OPENFDA = \"https://api.fda.gov/drug/label.json\";\nconst TIMEOUT_MS = 10000;\n\nconst REGION_NOTE = \"Brand names shown are those registered in the United States.\";\n\n/* ----------------------- RxNorm resolution ----------------------- */\n\nasync function findRxcui(name: string): Promise<string | null> {\n const exact = await getJson<any>(`${RXNAV}/rxcui.json?name=${encodeURIComponent(name)}`, TIMEOUT_MS);\n const id = exact?.idGroup?.rxnormId?.[0];\n if (id) return id;\n\n const approx = await getJson<any>(\n `${RXNAV}/approximateTerm.json?term=${encodeURIComponent(name)}&maxEntries=1`,\n TIMEOUT_MS\n );\n const cand = approx?.approximateGroup?.candidate?.[0];\n return cand ? cand.rxcui : null;\n}\n\nfunction collectNames(allRelated: any, tty: string): string[] {\n const groups = allRelated?.allRelatedGroup?.conceptGroup ?? [];\n const g = groups.find((x: any) => x.tty === tty);\n if (!g || !g.conceptProperties) return [];\n return g.conceptProperties.map((c: any) => c.name);\n}\n\n/**\n * Single-ingredient brand names for a generic ingredient, via SBDF concepts\n * whose name is \"<ingredient> <dose form> [Brand]\". Combination products read\n * \"<ing A> / <ing B> ... [Brand]\", so any \"/\" before the bracket is excluded.\n */\nasync function getSingleIngredientBrands(ingredientRxcui: string): Promise<string[]> {\n const data = await getJson<any>(`${RXNAV}/rxcui/${ingredientRxcui}/related.json?tty=SBDF`, TIMEOUT_MS);\n const groups = data?.relatedGroup?.conceptGroup ?? [];\n const g = groups.find((x: any) => x.tty === \"SBDF\");\n if (!g || !g.conceptProperties) return [];\n const brands: string[] = [];\n for (const c of g.conceptProperties) {\n const m = (c.name as string).match(/\\[([^\\]]+)\\]\\s*$/);\n if (!m) continue;\n const prefix = (c.name as string).slice(0, (c.name as string).lastIndexOf(\"[\"));\n if (prefix.includes(\"/\")) continue; // combination product — skip\n brands.push(m[1].trim());\n }\n return uniq(brands).sort();\n}\n\nasync function getDrugClassAndUses(\n rxcui: string\n): Promise<{ drugClass: string | null; uses: string[] }> {\n const data = await getJson<any>(\n `${RXNAV}/rxclass/class/byRxcui.json?rxcui=${rxcui}&relaSource=MEDRT`,\n TIMEOUT_MS\n );\n const items = data?.rxclassDrugInfoList?.rxclassDrugInfo ?? [];\n\n const byType = (t: string): string | null => {\n const hit = items.find((i: any) => i.rxclassMinConceptItem.classType === t);\n return hit ? hit.rxclassMinConceptItem.className : null;\n };\n // Prefer FDA Established Pharmacologic Class, then mechanism, then chemical.\n const drugClass = byType(\"EPC\") || byType(\"MOA\") || byType(\"CHEM\");\n\n const uses = uniq<string>(\n items\n .filter((i: any) => i.rela === \"may_treat\")\n .map((i: any) => i.rxclassMinConceptItem.className as string)\n ).slice(0, 6);\n\n return { drugClass, uses };\n}\n\nasync function resolveViaRxNorm(name: string): Promise<ResolveResult | null> {\n const rxcui = await findRxcui(name);\n if (!rxcui) return null;\n\n const [props, allRelated] = await Promise.all([\n getJson<any>(`${RXNAV}/rxcui/${rxcui}/properties.json`, TIMEOUT_MS),\n getJson<any>(`${RXNAV}/rxcui/${rxcui}/allrelated.json`, TIMEOUT_MS),\n ]);\n\n const tty: string | undefined = props?.properties?.tty;\n const canonicalName: string = props?.properties?.name ?? name;\n if (!tty) return null;\n\n const inNames = collectNames(allRelated, \"IN\");\n const minNames = collectNames(allRelated, \"MIN\");\n\n const isBrand = tty === \"BN\" || tty === \"SBD\" || tty === \"BPCK\";\n const inputType: \"brand\" | \"generic\" = isBrand ? \"brand\" : \"generic\";\n\n // For a generic, list only brands where it is the sole active ingredient.\n let brandNames: string[] | null = null;\n if (!isBrand) {\n const single = await getSingleIngredientBrands(rxcui);\n const fallback = uniq([...collectNames(allRelated, \"BN\"), ...collectNames(allRelated, \"BPCK\")]).sort();\n brandNames = single.length ? single : fallback;\n }\n\n // Generic name must reflect the concept's OWN ingredient(s). A single-ingredient\n // lookup (e.g. \"atorvastatin\", tty IN) relates to every combination it appears\n // in (Atorvastatin / Ezetimibe, ...), so the previous MIN-first logic\n // mis-resolved single generics to arbitrary combinations and broke downstream\n // label lookups. When the input concept is itself an ingredient (IN) or a\n // multi-ingredient concept (MIN), its own canonical name is authoritative;\n // only for brands/products do we read the related MIN as the combo generic.\n let genericName: string | null = null;\n if (tty === \"IN\" || tty === \"MIN\") {\n genericName = titleCase(canonicalName);\n } else if (minNames.length) {\n genericName = titleCase(minNames[0]);\n } else if (inNames.length === 1) {\n genericName = titleCase(inNames[0]);\n } else if (inNames.length > 1) {\n genericName = titleCase(uniq(inNames).join(\" / \"));\n } else {\n genericName = titleCase(canonicalName);\n }\n\n // Class + indications from the ingredient rxcui where possible.\n const classRxcui = !isBrand\n ? rxcui\n : (() => {\n const inGroup = (allRelated?.allRelatedGroup?.conceptGroup ?? []).find((x: any) => x.tty === \"IN\");\n return inGroup?.conceptProperties?.[0]?.rxcui ?? rxcui;\n })();\n const { drugClass, uses } = await getDrugClassAndUses(classRxcui);\n\n return {\n inputType,\n inputName: titleCase(canonicalName),\n genericName,\n brandNames,\n drugClass,\n uses,\n rxcui,\n source: \"RxNorm (NLM)\",\n regionNote: REGION_NOTE,\n };\n}\n\n/* ----------------------- openFDA fallback ------------------------ */\n\nasync function resolveViaOpenFda(name: string): Promise<ResolveResult | null> {\n const q = name.replace(/\"/g, \"\");\n const search = `(openfda.brand_name:\"${q}\"+OR+openfda.generic_name:\"${q}\")`;\n const url = `${OPENFDA}?search=${encodeURIComponent(search).replace(/%2B/g, \"+\")}&limit=1`;\n const data = await getJson<any>(url, TIMEOUT_MS);\n const fda = data?.results?.[0]?.openfda;\n if (!fda) return null;\n\n const brand = (fda.brand_name ?? [])[0] ?? \"\";\n const generic = (fda.generic_name ?? [])[0] ?? \"\";\n if (!brand && !generic) return null;\n\n const isBrand = brand.toLowerCase() === name.toLowerCase();\n return {\n inputType: isBrand ? \"brand\" : \"generic\",\n inputName: titleCase(name),\n genericName: generic ? titleCase(generic) : null,\n brandNames: isBrand ? null : uniq<string>(fda.brand_name ?? []).sort(),\n drugClass: (fda.pharm_class_epc ?? [])[0] ?? (fda.pharm_class_moa ?? [])[0] ?? null,\n uses: [],\n rxcui: null,\n source: \"openFDA\",\n regionNote: REGION_NOTE,\n };\n}\n\n/* --------------------------- public ----------------------------- */\n\n/**\n * Resolve a drug name deterministically. Tries RxNorm, then openFDA. Returns\n * null if neither source recognises the name.\n */\nexport async function resolveName(name: string): Promise<ResolveResult | null> {\n if (!name || !name.trim()) return null;\n const clean = name.trim();\n const viaRx = await resolveViaRxNorm(clean).catch(() => null);\n if (viaRx && (viaRx.genericName || (viaRx.brandNames && viaRx.brandNames.length))) {\n return viaRx;\n }\n return await resolveViaOpenFda(clean).catch(() => null);\n}\n","/**\n * Official drug-label lookups.\n * - US: FDA label via the openFDA API -> DailyMed citation\n * - UK/EU: SmPC via the eMC (medicines.org.uk) -> eMC citation\n *\n * Two layers:\n * - getUsLabel / getUkSmpc — general, all-section access (LabelSection[]),\n * used by PubCrawl's get_uspi / get_smpc / compare_labels tools.\n * - getUspi / getSmpc / getLabels — a 5-field convenience view (RegionLabel)\n * used by Pharmonym. Built on the same fetch + parse internals.\n *\n * Every network call is best-effort: failures return null. Pure fetch + parse;\n * caching belongs to the consumer.\n */\n\nimport * as cheerio from \"cheerio\";\nimport { timedFetch, getJson, cleanSection, formatFdaDate } from \"./fetch.js\";\nimport type { RegionLabel, LabelResult, LabelSection, UsLabelResult, UkSmpcResult } from \"./types.js\";\n\nconst OPENFDA_URL = \"https://api.fda.gov/drug/label.json\";\nconst EMC_BASE = \"https://www.medicines.org.uk\";\n\ninterface OpenFdaLabel {\n effective_time?: string;\n boxed_warning?: string[];\n indications_and_usage?: string[];\n dosage_and_administration?: string[];\n dosage_forms_and_strengths?: string[];\n contraindications?: string[];\n warnings_and_cautions?: string[];\n warnings?: string[];\n adverse_reactions?: string[];\n drug_interactions?: string[];\n use_in_specific_populations?: string[];\n overdosage?: string[];\n clinical_pharmacology?: string[];\n description?: string[];\n how_supplied?: string[];\n openfda?: {\n generic_name?: string[];\n brand_name?: string[];\n spl_set_id?: string[];\n };\n}\ninterface OpenFdaResponse {\n results?: OpenFdaLabel[];\n}\n\n// openFDA field -> { LOINC code, display title }. Order = label reading order.\nconst US_SECTION_MAP: Array<{ field: keyof OpenFdaLabel; code: string; title: string }> = [\n { field: \"boxed_warning\", code: \"34066-1\", title: \"Boxed Warning\" },\n { field: \"indications_and_usage\", code: \"34067-9\", title: \"Indications and Usage\" },\n { field: \"dosage_and_administration\", code: \"34068-7\", title: \"Dosage and Administration\" },\n { field: \"dosage_forms_and_strengths\", code: \"43678-2\", title: \"Dosage Forms and Strengths\" },\n { field: \"contraindications\", code: \"34070-3\", title: \"Contraindications\" },\n { field: \"warnings_and_cautions\", code: \"43685-7\", title: \"Warnings and Precautions\" },\n { field: \"warnings\", code: \"34071-1\", title: \"Warnings\" },\n { field: \"adverse_reactions\", code: \"34084-4\", title: \"Adverse Reactions\" },\n { field: \"drug_interactions\", code: \"34073-7\", title: \"Drug Interactions\" },\n { field: \"use_in_specific_populations\", code: \"42228-7\", title: \"Use in Specific Populations\" },\n { field: \"overdosage\", code: \"34088-5\", title: \"Overdosage\" },\n { field: \"clinical_pharmacology\", code: \"34090-1\", title: \"Clinical Pharmacology\" },\n { field: \"description\", code: \"34089-3\", title: \"Description\" },\n { field: \"how_supplied\", code: \"34069-5\", title: \"How Supplied\" },\n];\n\n/* --------------------------- US: openFDA --------------------------- */\n\nfunction score(r: OpenFdaLabel, q: string): number {\n const g = (r.openfda?.generic_name ?? []).join(\", \").toLowerCase();\n let s = 0;\n if (g && g.split(/,| and /).length === 1) s += 2; // single ingredient\n if (g.includes(q.toLowerCase())) s += 1;\n return s;\n}\n\n/** Fetch the most recent FDA label for a drug; prefers single-ingredient products. */\nasync function fetchOpenFdaBest(drug: string): Promise<OpenFdaLabel | null> {\n if (!drug) return null;\n const q = drug.trim().replace(/\"/g, \"\");\n const search = `(openfda.generic_name:\"${q}\"+OR+openfda.brand_name:\"${q}\")`;\n const url = `${OPENFDA_URL}?search=${encodeURIComponent(search).replace(/%2B/g, \"+\")}&sort=effective_time:desc&limit=5`;\n const data = await getJson<OpenFdaResponse>(url);\n const results = data?.results ?? [];\n if (results.length === 0) return null;\n return results.slice().sort((a, b) => score(b, q) - score(a, q))[0];\n}\n\nfunction usMeta(r: OpenFdaLabel, fallbackName: string) {\n const setId = r.openfda?.spl_set_id?.[0] ?? \"\";\n const brand = r.openfda?.brand_name?.[0] ?? \"\";\n const generic = r.openfda?.generic_name?.[0] ?? fallbackName;\n return {\n setId,\n brand,\n generic,\n productName: brand ? `${brand} (${generic})` : generic,\n dailymedUrl: setId\n ? `https://dailymed.nlm.nih.gov/dailymed/drugInfo.cfm?setid=${setId}`\n : \"https://dailymed.nlm.nih.gov/dailymed/\",\n };\n}\n\nfunction matchesUsFilter(entry: { field: string; code: string; title: string }, requested: string[]): boolean {\n return requested.some((req) => {\n const r = req.toLowerCase().trim();\n return (\n entry.code === req ||\n entry.title.toLowerCase().includes(r) ||\n String(entry.field).replace(/_/g, \" \").includes(r) ||\n r.includes(entry.title.toLowerCase())\n );\n });\n}\n\n/**\n * General US label access — returns every available section (or the requested\n * subset) as LabelSection[]. Powers PubCrawl's get_uspi.\n */\nexport async function getUsLabel(drug: string, requested?: string[]): Promise<UsLabelResult | null> {\n const r = await fetchOpenFdaBest(drug);\n if (!r) return null;\n const meta = usMeta(r, drug.trim());\n\n const sections: LabelSection[] = [];\n for (const entry of US_SECTION_MAP) {\n if (requested?.length && !matchesUsFilter(entry, requested)) continue;\n const content = cleanSection(r[entry.field]);\n if (content) sections.push({ code: entry.code, title: entry.title, content });\n }\n if (sections.length === 0) return null;\n\n return {\n drugName: meta.productName,\n setId: meta.setId,\n publishedDate: formatFdaDate(r.effective_time),\n sections,\n dailymedUrl: meta.dailymedUrl,\n };\n}\n\n/** 5-field convenience view of the US label (Pharmonym). */\nexport async function getUspi(drug: string): Promise<RegionLabel | null> {\n const r = await fetchOpenFdaBest(drug);\n if (!r) return null;\n const meta = usMeta(r, drug.trim());\n\n const dosage = cleanSection(r.dosage_and_administration);\n const boxed = cleanSection(r.boxed_warning);\n const warn = cleanSection(r.warnings_and_cautions ?? r.warnings);\n const warnings = [boxed ? `BOXED WARNING — ${boxed}` : \"\", warn].filter(Boolean).join(\"\\n\\n\");\n const adverse = cleanSection(r.adverse_reactions);\n const indications = cleanSection(r.indications_and_usage);\n const contraindications = cleanSection(r.contraindications);\n\n if (!dosage && !warnings && !adverse && !indications && !contraindications) return null;\n\n return {\n region: \"US\",\n authority: \"FDA Prescribing Information\",\n productName: meta.productName,\n indications: indications || null,\n dosage: dosage || null,\n contraindications: contraindications || null,\n warnings: warnings || null,\n adverse: adverse || null,\n updated: formatFdaDate(r.effective_time),\n sourceUrl: meta.dailymedUrl,\n sourceLabel: \"DailyMed (U.S. National Library of Medicine)\",\n };\n}\n\n/* --------------------------- UK/EU: eMC ---------------------------- */\n\ninterface EmcMatch {\n product_id: string;\n name: string;\n}\n\nasync function searchEmcAll(drug: string): Promise<EmcMatch[]> {\n const url = `${EMC_BASE}/emc/search?${new URLSearchParams({ q: drug })}`;\n const res = await timedFetch(url);\n if (!res.ok) return [];\n const html = await res.text();\n const $ = cheerio.load(html);\n const out: EmcMatch[] = [];\n $(\"a[href*='/emc/product/']\").each((_, el) => {\n const href = $(el).attr(\"href\") || \"\";\n const m = href.match(/\\/emc\\/product\\/(\\d+)\\/smpc/);\n if (!m) return;\n const name = $(el).text().trim();\n if (!name || name.toLowerCase().includes(\"health professional\")) return;\n if (out.some((x) => x.product_id === m[1])) return;\n out.push({ product_id: m[1], name });\n });\n return out;\n}\n\nasync function searchEmcBest(drug: string): Promise<EmcMatch | null> {\n const matches = await searchEmcAll(drug);\n if (matches.length === 0) return null;\n const q = drug.toLowerCase();\n matches.sort(\n (a, b) =>\n (b.name.toLowerCase().includes(q) ? 1 : 0) - (a.name.toLowerCase().includes(q) ? 1 : 0)\n );\n return matches[0];\n}\n\nconst SMPC_SECTION_NAMES: Record<string, string> = {\n \"4.1\": \"Therapeutic indications\",\n \"4.2\": \"Posology and method of administration\",\n \"4.3\": \"Contraindications\",\n \"4.4\": \"Special warnings and precautions for use\",\n \"4.5\": \"Interaction with other medicinal products\",\n \"4.6\": \"Fertility, pregnancy and lactation\",\n \"4.7\": \"Effects on ability to drive and use machines\",\n \"4.8\": \"Undesirable effects\",\n \"4.9\": \"Overdose\",\n \"5.1\": \"Pharmacodynamic properties\",\n \"5.2\": \"Pharmacokinetic properties\",\n \"5.3\": \"Preclinical safety data\",\n \"6.1\": \"List of excipients\",\n \"6.2\": \"Incompatibilities\",\n \"6.3\": \"Shelf life\",\n \"6.4\": \"Special precautions for storage\",\n \"6.5\": \"Nature and contents of container\",\n \"6.6\": \"Special precautions for disposal\",\n};\n\nfunction htmlToText(html: string): string {\n const $ = cheerio.load(html);\n $(\"br\").replaceWith(\"\\n\");\n $(\"li\").each((_, el) => {\n $(el).prepend(\"- \");\n $(el).append(\"\\n\");\n });\n $(\"p, div, tr, h1, h2, h3, h4, h5, h6\").each((_, el) => {\n $(el).append(\"\\n\");\n });\n return $.text().replace(/\\n{3,}/g, \"\\n\\n\").trim();\n}\n\nfunction matchesSmpcFilter(code: string, title: string, requested: string[]): boolean {\n return requested.some((req) => {\n const r = req.toLowerCase().trim();\n if (code === req || code === r) return true;\n if (title.toLowerCase().includes(r)) return true;\n if (r.includes(\"indication\")) return code === \"4.1\";\n if (r.includes(\"dosage\") || r.includes(\"dosing\") || r.includes(\"posology\")) return code === \"4.2\";\n if (r.includes(\"contraindication\")) return code === \"4.3\";\n if (r.includes(\"warning\") || r.includes(\"precaution\")) return code === \"4.4\";\n if (r.includes(\"interaction\")) return code === \"4.5\";\n if (r.includes(\"pregnancy\") || r.includes(\"fertility\")) return code === \"4.6\";\n if (r.includes(\"adverse\") || r.includes(\"undesirable\") || r.includes(\"side effect\")) return code === \"4.8\";\n if (r.includes(\"overdose\") || r.includes(\"overdosage\")) return code === \"4.9\";\n if (r.includes(\"pharmacodynamic\")) return code === \"5.1\";\n if (r.includes(\"pharmacokinetic\")) return code === \"5.2\";\n return false;\n });\n}\n\n/**\n * Parse SmPC sections from eMC HTML. Returns every detected numbered section as\n * LabelSection[] (or the requested subset). Detect numbered headings, collect\n * sibling content up to the next heading, with a parent-text fallback.\n */\nfunction parseSmpcSections(html: string, requested?: string[]): LabelSection[] {\n const $ = cheerio.load(html);\n const sectionElements: Array<{ code: string; title: string; element: any }> = [];\n\n $(\"[id*='SECTION'], [id*='section'], .sectionHeading, h2, h3, h4\").each((_, el) => {\n const text = $(el).text().trim();\n const match = text.match(/^(\\d+\\.?\\d*)\\s+(.+)/);\n if (match) sectionElements.push({ code: match[1], title: match[2].trim(), element: $(el) });\n });\n\n if (sectionElements.length === 0) {\n $(\"*\").each((_, el) => {\n const $el = $(el);\n if ($el.children().length > 0 && !$el.is(\"a, span, strong, em, b, i\")) return;\n const text = $el.text().trim();\n const match = text.match(/^(\\d+\\.?\\d*)\\s+(.+)/);\n if (match && SMPC_SECTION_NAMES[match[1]]) {\n sectionElements.push({ code: match[1], title: match[2].trim(), element: $el });\n }\n });\n }\n\n const out: LabelSection[] = [];\n for (let i = 0; i < sectionElements.length; i++) {\n const current = sectionElements[i];\n const next = sectionElements[i + 1];\n\n let content = \"\";\n let node = current.element.next();\n while (node.length > 0) {\n if (next && node.is(next.element)) break;\n const nodeHtml = $.html(node);\n if (nodeHtml) content += htmlToText(nodeHtml) + \"\\n\";\n node = node.next();\n }\n if (!content.trim()) {\n const parentHtml = $.html(current.element.parent());\n if (parentHtml) content = htmlToText(parentHtml).replace(current.element.text().trim(), \"\").trim();\n }\n\n const cleaned = cleanSection(content);\n if (cleaned) out.push({ code: current.code, title: current.title, content: cleaned });\n }\n\n if (requested?.length) {\n return out.filter((s) => matchesSmpcFilter(s.code, s.title, requested));\n }\n return out;\n}\n\n/**\n * General UK/EU SmPC access — returns every available section (or the requested\n * subset) as LabelSection[]. Powers PubCrawl's get_smpc.\n */\nexport async function getUkSmpc(drug: string, requested?: string[]): Promise<UkSmpcResult | null> {\n if (!drug) return null;\n try {\n const best = await searchEmcBest(drug);\n if (!best) return null;\n const res = await timedFetch(`${EMC_BASE}/emc/product/${best.product_id}/smpc`);\n if (!res.ok) return null;\n const sections = parseSmpcSections(await res.text(), requested);\n if (sections.length === 0) return null;\n return {\n drugName: best.name,\n productId: best.product_id,\n sections,\n url: `${EMC_BASE}/emc/product/${best.product_id}/smpc`,\n };\n } catch {\n return null;\n }\n}\n\n/** 5-field convenience view of the UK/EU SmPC (Pharmonym). */\nexport async function getSmpc(drug: string): Promise<RegionLabel | null> {\n if (!drug) return null;\n try {\n const best = await searchEmcBest(drug);\n if (!best) return null;\n const res = await timedFetch(`${EMC_BASE}/emc/product/${best.product_id}/smpc`);\n if (!res.ok) return null;\n const all = parseSmpcSections(await res.text());\n const pick = (code: string) => all.find((s) => s.code === code)?.content ?? null;\n\n const indications = pick(\"4.1\");\n const dosage = pick(\"4.2\");\n const contraindications = pick(\"4.3\");\n const warnings = pick(\"4.4\");\n const adverse = pick(\"4.8\");\n if (!dosage && !warnings && !adverse && !indications && !contraindications) return null;\n\n return {\n region: \"UK/EU\",\n authority: \"Summary of Product Characteristics (SmPC)\",\n productName: best.name,\n indications,\n dosage,\n contraindications,\n warnings,\n adverse,\n updated: \"\",\n sourceUrl: `${EMC_BASE}/emc/product/${best.product_id}/smpc`,\n sourceLabel: \"electronic medicines compendium (emc), UK\",\n };\n } catch {\n return null;\n }\n}\n\n/* --------------------------- public API ---------------------------- */\n\n/** Look up both 5-field labels for a drug. Each side is null if unavailable. */\nexport async function getLabels(drug: string): Promise<LabelResult> {\n const [us, uk] = await Promise.all([\n getUspi(drug).catch(() => null),\n getSmpc(drug).catch(() => null),\n ]);\n return { us, uk };\n}\n","/**\n * US (FDA) vs UK/EU (SmPC) side-by-side label comparison across mapped topics.\n */\n\nimport { getLabels } from \"./labels.js\";\nimport type { CompareResult, LabelComparison } from \"./types.js\";\n\ntype LabelField = \"indications\" | \"dosage\" | \"contraindications\" | \"warnings\" | \"adverse\";\n\n/** Topics compared across US and UK labels, mapped to the fields that hold them. */\nexport const SECTION_MAP: Array<{ topic: string; field: LabelField }> = [\n { topic: \"Indications\", field: \"indications\" },\n { topic: \"Dosing\", field: \"dosage\" },\n { topic: \"Contraindications\", field: \"contraindications\" },\n { topic: \"Warnings\", field: \"warnings\" },\n { topic: \"Adverse Reactions\", field: \"adverse\" },\n];\n\nexport async function compareLabels(drug: string): Promise<CompareResult> {\n const { us, uk } = await getLabels(drug);\n\n const comparisons: LabelComparison[] = SECTION_MAP.map(({ topic, field }) => ({\n topic,\n us: us ? us[field] : null,\n uk: uk ? uk[field] : null,\n })).filter((c) => c.us || c.uk);\n\n return {\n drug,\n comparisons,\n usSource: us?.sourceUrl ?? null,\n ukSource: uk?.sourceUrl ?? null,\n };\n}\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -123,9 +123,17 @@ async function resolveViaRxNorm(name) {
123
123
  brandNames = single.length ? single : fallback;
124
124
  }
125
125
  let genericName = null;
126
- if (minNames.length) genericName = titleCase(minNames[0]);
127
- else if (inNames.length === 1) genericName = titleCase(inNames[0]);
128
- else if (inNames.length > 1) genericName = titleCase(uniq(inNames).join(" / "));
126
+ if (tty === "IN" || tty === "MIN") {
127
+ genericName = titleCase(canonicalName);
128
+ } else if (minNames.length) {
129
+ genericName = titleCase(minNames[0]);
130
+ } else if (inNames.length === 1) {
131
+ genericName = titleCase(inNames[0]);
132
+ } else if (inNames.length > 1) {
133
+ genericName = titleCase(uniq(inNames).join(" / "));
134
+ } else {
135
+ genericName = titleCase(canonicalName);
136
+ }
129
137
  const classRxcui = !isBrand ? rxcui : (() => {
130
138
  const inGroup = (allRelated?.allRelatedGroup?.conceptGroup ?? []).find((x) => x.tty === "IN");
131
139
  return inGroup?.conceptProperties?.[0]?.rxcui ?? rxcui;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/fetch.ts","../src/names.ts","../src/labels.ts","../src/compare.ts"],"sourcesContent":["/**\n * Shared HTTP + text helpers. Uses the runtime's global `fetch` (Node >=18),\n * so the package has no node-fetch dependency.\n */\n\nconst DEFAULT_TIMEOUT_MS = 12000;\nconst MAX_FIELD_CHARS = 1400;\n\nexport const USER_AGENT = \"drug-data/0.1 (pharmatools.ai; nick@pharmatools.ai)\";\n\nexport async function timedFetch(\n url: string,\n opts: RequestInit = {},\n timeoutMs = DEFAULT_TIMEOUT_MS\n): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n return await fetch(url, {\n ...opts,\n signal: controller.signal,\n headers: { \"User-Agent\": USER_AGENT, ...((opts.headers as Record<string, string>) || {}) },\n });\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function getJson<T = unknown>(\n url: string,\n timeoutMs = DEFAULT_TIMEOUT_MS\n): Promise<T | null> {\n try {\n const res = await timedFetch(url, {}, timeoutMs);\n if (!res.ok) return null;\n return (await res.json()) as T;\n } catch {\n return null;\n }\n}\n\n/**\n * Tidy a raw label section: join arrays, strip a leading numbered ALL-CAPS\n * heading, collapse whitespace, and truncate on a sentence boundary.\n */\nexport function cleanSection(value: unknown, maxChars = MAX_FIELD_CHARS): string {\n if (!value) return \"\";\n let text = Array.isArray(value) ? value.join(\"\\n\\n\") : String(value);\n\n text = text\n .replace(/\\r/g, \"\")\n .replace(/[ \\t]+/g, \" \")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n\n // Drop a leading section header line like \"5 WARNINGS AND PRECAUTIONS\"\n text = text.replace(/^\\s*\\d+(\\.\\d+)*\\s+[A-Z][A-Z \\-/,&]+\\n+/, \"\").trim();\n\n if (text.length > maxChars) {\n const slice = text.slice(0, maxChars);\n const lastStop = Math.max(slice.lastIndexOf(\". \"), slice.lastIndexOf(\"\\n\"));\n text = (lastStop > maxChars * 0.6 ? slice.slice(0, lastStop + 1) : slice).trim() + \" …\";\n }\n return text;\n}\n\nexport function formatFdaDate(yyyymmdd?: string): string {\n if (!yyyymmdd || !/^\\d{8}$/.test(yyyymmdd)) return \"\";\n const d = new Date(`${yyyymmdd.slice(0, 4)}-${yyyymmdd.slice(4, 6)}-${yyyymmdd.slice(6, 8)}`);\n if (isNaN(d.getTime())) return \"\";\n return d.toLocaleDateString(\"en-US\", { year: \"numeric\", month: \"short\", day: \"numeric\" });\n}\n\nexport function titleCase(s: string): string {\n return String(s || \"\").replace(/\\w\\S*/g, (t) => t.charAt(0).toUpperCase() + t.slice(1).toLowerCase());\n}\n\nexport function uniq<T>(arr: T[]): T[] {\n return [...new Set(arr.filter(Boolean))];\n}\n","/**\n * Deterministic brand <-> generic resolution via RxNorm/RxNav, with an openFDA\n * fallback. No AI, no hallucinated names. Brand names are US-registered;\n * international brand names are intentionally not invented.\n */\n\nimport { getJson, titleCase, uniq } from \"./fetch.js\";\nimport type { ResolveResult } from \"./types.js\";\n\nconst RXNAV = \"https://rxnav.nlm.nih.gov/REST\";\nconst OPENFDA = \"https://api.fda.gov/drug/label.json\";\nconst TIMEOUT_MS = 10000;\n\nconst REGION_NOTE = \"Brand names shown are those registered in the United States.\";\n\n/* ----------------------- RxNorm resolution ----------------------- */\n\nasync function findRxcui(name: string): Promise<string | null> {\n const exact = await getJson<any>(`${RXNAV}/rxcui.json?name=${encodeURIComponent(name)}`, TIMEOUT_MS);\n const id = exact?.idGroup?.rxnormId?.[0];\n if (id) return id;\n\n const approx = await getJson<any>(\n `${RXNAV}/approximateTerm.json?term=${encodeURIComponent(name)}&maxEntries=1`,\n TIMEOUT_MS\n );\n const cand = approx?.approximateGroup?.candidate?.[0];\n return cand ? cand.rxcui : null;\n}\n\nfunction collectNames(allRelated: any, tty: string): string[] {\n const groups = allRelated?.allRelatedGroup?.conceptGroup ?? [];\n const g = groups.find((x: any) => x.tty === tty);\n if (!g || !g.conceptProperties) return [];\n return g.conceptProperties.map((c: any) => c.name);\n}\n\n/**\n * Single-ingredient brand names for a generic ingredient, via SBDF concepts\n * whose name is \"<ingredient> <dose form> [Brand]\". Combination products read\n * \"<ing A> / <ing B> ... [Brand]\", so any \"/\" before the bracket is excluded.\n */\nasync function getSingleIngredientBrands(ingredientRxcui: string): Promise<string[]> {\n const data = await getJson<any>(`${RXNAV}/rxcui/${ingredientRxcui}/related.json?tty=SBDF`, TIMEOUT_MS);\n const groups = data?.relatedGroup?.conceptGroup ?? [];\n const g = groups.find((x: any) => x.tty === \"SBDF\");\n if (!g || !g.conceptProperties) return [];\n const brands: string[] = [];\n for (const c of g.conceptProperties) {\n const m = (c.name as string).match(/\\[([^\\]]+)\\]\\s*$/);\n if (!m) continue;\n const prefix = (c.name as string).slice(0, (c.name as string).lastIndexOf(\"[\"));\n if (prefix.includes(\"/\")) continue; // combination product — skip\n brands.push(m[1].trim());\n }\n return uniq(brands).sort();\n}\n\nasync function getDrugClassAndUses(\n rxcui: string\n): Promise<{ drugClass: string | null; uses: string[] }> {\n const data = await getJson<any>(\n `${RXNAV}/rxclass/class/byRxcui.json?rxcui=${rxcui}&relaSource=MEDRT`,\n TIMEOUT_MS\n );\n const items = data?.rxclassDrugInfoList?.rxclassDrugInfo ?? [];\n\n const byType = (t: string): string | null => {\n const hit = items.find((i: any) => i.rxclassMinConceptItem.classType === t);\n return hit ? hit.rxclassMinConceptItem.className : null;\n };\n // Prefer FDA Established Pharmacologic Class, then mechanism, then chemical.\n const drugClass = byType(\"EPC\") || byType(\"MOA\") || byType(\"CHEM\");\n\n const uses = uniq<string>(\n items\n .filter((i: any) => i.rela === \"may_treat\")\n .map((i: any) => i.rxclassMinConceptItem.className as string)\n ).slice(0, 6);\n\n return { drugClass, uses };\n}\n\nasync function resolveViaRxNorm(name: string): Promise<ResolveResult | null> {\n const rxcui = await findRxcui(name);\n if (!rxcui) return null;\n\n const [props, allRelated] = await Promise.all([\n getJson<any>(`${RXNAV}/rxcui/${rxcui}/properties.json`, TIMEOUT_MS),\n getJson<any>(`${RXNAV}/rxcui/${rxcui}/allrelated.json`, TIMEOUT_MS),\n ]);\n\n const tty: string | undefined = props?.properties?.tty;\n const canonicalName: string = props?.properties?.name ?? name;\n if (!tty) return null;\n\n const inNames = collectNames(allRelated, \"IN\");\n const minNames = collectNames(allRelated, \"MIN\");\n\n const isBrand = tty === \"BN\" || tty === \"SBD\" || tty === \"BPCK\";\n const inputType: \"brand\" | \"generic\" = isBrand ? \"brand\" : \"generic\";\n\n // For a generic, list only brands where it is the sole active ingredient.\n let brandNames: string[] | null = null;\n if (!isBrand) {\n const single = await getSingleIngredientBrands(rxcui);\n const fallback = uniq([...collectNames(allRelated, \"BN\"), ...collectNames(allRelated, \"BPCK\")]).sort();\n brandNames = single.length ? single : fallback;\n }\n\n // Generic display name: prefer the canonical combo string, else single, else joined.\n let genericName: string | null = null;\n if (minNames.length) genericName = titleCase(minNames[0]);\n else if (inNames.length === 1) genericName = titleCase(inNames[0]);\n else if (inNames.length > 1) genericName = titleCase(uniq(inNames).join(\" / \"));\n\n // Class + indications from the ingredient rxcui where possible.\n const classRxcui = !isBrand\n ? rxcui\n : (() => {\n const inGroup = (allRelated?.allRelatedGroup?.conceptGroup ?? []).find((x: any) => x.tty === \"IN\");\n return inGroup?.conceptProperties?.[0]?.rxcui ?? rxcui;\n })();\n const { drugClass, uses } = await getDrugClassAndUses(classRxcui);\n\n return {\n inputType,\n inputName: titleCase(canonicalName),\n genericName,\n brandNames,\n drugClass,\n uses,\n rxcui,\n source: \"RxNorm (NLM)\",\n regionNote: REGION_NOTE,\n };\n}\n\n/* ----------------------- openFDA fallback ------------------------ */\n\nasync function resolveViaOpenFda(name: string): Promise<ResolveResult | null> {\n const q = name.replace(/\"/g, \"\");\n const search = `(openfda.brand_name:\"${q}\"+OR+openfda.generic_name:\"${q}\")`;\n const url = `${OPENFDA}?search=${encodeURIComponent(search).replace(/%2B/g, \"+\")}&limit=1`;\n const data = await getJson<any>(url, TIMEOUT_MS);\n const fda = data?.results?.[0]?.openfda;\n if (!fda) return null;\n\n const brand = (fda.brand_name ?? [])[0] ?? \"\";\n const generic = (fda.generic_name ?? [])[0] ?? \"\";\n if (!brand && !generic) return null;\n\n const isBrand = brand.toLowerCase() === name.toLowerCase();\n return {\n inputType: isBrand ? \"brand\" : \"generic\",\n inputName: titleCase(name),\n genericName: generic ? titleCase(generic) : null,\n brandNames: isBrand ? null : uniq<string>(fda.brand_name ?? []).sort(),\n drugClass: (fda.pharm_class_epc ?? [])[0] ?? (fda.pharm_class_moa ?? [])[0] ?? null,\n uses: [],\n rxcui: null,\n source: \"openFDA\",\n regionNote: REGION_NOTE,\n };\n}\n\n/* --------------------------- public ----------------------------- */\n\n/**\n * Resolve a drug name deterministically. Tries RxNorm, then openFDA. Returns\n * null if neither source recognises the name.\n */\nexport async function resolveName(name: string): Promise<ResolveResult | null> {\n if (!name || !name.trim()) return null;\n const clean = name.trim();\n const viaRx = await resolveViaRxNorm(clean).catch(() => null);\n if (viaRx && (viaRx.genericName || (viaRx.brandNames && viaRx.brandNames.length))) {\n return viaRx;\n }\n return await resolveViaOpenFda(clean).catch(() => null);\n}\n","/**\n * Official drug-label lookups.\n * - US: FDA label via the openFDA API -> DailyMed citation\n * - UK/EU: SmPC via the eMC (medicines.org.uk) -> eMC citation\n *\n * Two layers:\n * - getUsLabel / getUkSmpc — general, all-section access (LabelSection[]),\n * used by PubCrawl's get_uspi / get_smpc / compare_labels tools.\n * - getUspi / getSmpc / getLabels — a 5-field convenience view (RegionLabel)\n * used by Pharmonym. Built on the same fetch + parse internals.\n *\n * Every network call is best-effort: failures return null. Pure fetch + parse;\n * caching belongs to the consumer.\n */\n\nimport * as cheerio from \"cheerio\";\nimport { timedFetch, getJson, cleanSection, formatFdaDate } from \"./fetch.js\";\nimport type { RegionLabel, LabelResult, LabelSection, UsLabelResult, UkSmpcResult } from \"./types.js\";\n\nconst OPENFDA_URL = \"https://api.fda.gov/drug/label.json\";\nconst EMC_BASE = \"https://www.medicines.org.uk\";\n\ninterface OpenFdaLabel {\n effective_time?: string;\n boxed_warning?: string[];\n indications_and_usage?: string[];\n dosage_and_administration?: string[];\n dosage_forms_and_strengths?: string[];\n contraindications?: string[];\n warnings_and_cautions?: string[];\n warnings?: string[];\n adverse_reactions?: string[];\n drug_interactions?: string[];\n use_in_specific_populations?: string[];\n overdosage?: string[];\n clinical_pharmacology?: string[];\n description?: string[];\n how_supplied?: string[];\n openfda?: {\n generic_name?: string[];\n brand_name?: string[];\n spl_set_id?: string[];\n };\n}\ninterface OpenFdaResponse {\n results?: OpenFdaLabel[];\n}\n\n// openFDA field -> { LOINC code, display title }. Order = label reading order.\nconst US_SECTION_MAP: Array<{ field: keyof OpenFdaLabel; code: string; title: string }> = [\n { field: \"boxed_warning\", code: \"34066-1\", title: \"Boxed Warning\" },\n { field: \"indications_and_usage\", code: \"34067-9\", title: \"Indications and Usage\" },\n { field: \"dosage_and_administration\", code: \"34068-7\", title: \"Dosage and Administration\" },\n { field: \"dosage_forms_and_strengths\", code: \"43678-2\", title: \"Dosage Forms and Strengths\" },\n { field: \"contraindications\", code: \"34070-3\", title: \"Contraindications\" },\n { field: \"warnings_and_cautions\", code: \"43685-7\", title: \"Warnings and Precautions\" },\n { field: \"warnings\", code: \"34071-1\", title: \"Warnings\" },\n { field: \"adverse_reactions\", code: \"34084-4\", title: \"Adverse Reactions\" },\n { field: \"drug_interactions\", code: \"34073-7\", title: \"Drug Interactions\" },\n { field: \"use_in_specific_populations\", code: \"42228-7\", title: \"Use in Specific Populations\" },\n { field: \"overdosage\", code: \"34088-5\", title: \"Overdosage\" },\n { field: \"clinical_pharmacology\", code: \"34090-1\", title: \"Clinical Pharmacology\" },\n { field: \"description\", code: \"34089-3\", title: \"Description\" },\n { field: \"how_supplied\", code: \"34069-5\", title: \"How Supplied\" },\n];\n\n/* --------------------------- US: openFDA --------------------------- */\n\nfunction score(r: OpenFdaLabel, q: string): number {\n const g = (r.openfda?.generic_name ?? []).join(\", \").toLowerCase();\n let s = 0;\n if (g && g.split(/,| and /).length === 1) s += 2; // single ingredient\n if (g.includes(q.toLowerCase())) s += 1;\n return s;\n}\n\n/** Fetch the most recent FDA label for a drug; prefers single-ingredient products. */\nasync function fetchOpenFdaBest(drug: string): Promise<OpenFdaLabel | null> {\n if (!drug) return null;\n const q = drug.trim().replace(/\"/g, \"\");\n const search = `(openfda.generic_name:\"${q}\"+OR+openfda.brand_name:\"${q}\")`;\n const url = `${OPENFDA_URL}?search=${encodeURIComponent(search).replace(/%2B/g, \"+\")}&sort=effective_time:desc&limit=5`;\n const data = await getJson<OpenFdaResponse>(url);\n const results = data?.results ?? [];\n if (results.length === 0) return null;\n return results.slice().sort((a, b) => score(b, q) - score(a, q))[0];\n}\n\nfunction usMeta(r: OpenFdaLabel, fallbackName: string) {\n const setId = r.openfda?.spl_set_id?.[0] ?? \"\";\n const brand = r.openfda?.brand_name?.[0] ?? \"\";\n const generic = r.openfda?.generic_name?.[0] ?? fallbackName;\n return {\n setId,\n brand,\n generic,\n productName: brand ? `${brand} (${generic})` : generic,\n dailymedUrl: setId\n ? `https://dailymed.nlm.nih.gov/dailymed/drugInfo.cfm?setid=${setId}`\n : \"https://dailymed.nlm.nih.gov/dailymed/\",\n };\n}\n\nfunction matchesUsFilter(entry: { field: string; code: string; title: string }, requested: string[]): boolean {\n return requested.some((req) => {\n const r = req.toLowerCase().trim();\n return (\n entry.code === req ||\n entry.title.toLowerCase().includes(r) ||\n String(entry.field).replace(/_/g, \" \").includes(r) ||\n r.includes(entry.title.toLowerCase())\n );\n });\n}\n\n/**\n * General US label access — returns every available section (or the requested\n * subset) as LabelSection[]. Powers PubCrawl's get_uspi.\n */\nexport async function getUsLabel(drug: string, requested?: string[]): Promise<UsLabelResult | null> {\n const r = await fetchOpenFdaBest(drug);\n if (!r) return null;\n const meta = usMeta(r, drug.trim());\n\n const sections: LabelSection[] = [];\n for (const entry of US_SECTION_MAP) {\n if (requested?.length && !matchesUsFilter(entry, requested)) continue;\n const content = cleanSection(r[entry.field]);\n if (content) sections.push({ code: entry.code, title: entry.title, content });\n }\n if (sections.length === 0) return null;\n\n return {\n drugName: meta.productName,\n setId: meta.setId,\n publishedDate: formatFdaDate(r.effective_time),\n sections,\n dailymedUrl: meta.dailymedUrl,\n };\n}\n\n/** 5-field convenience view of the US label (Pharmonym). */\nexport async function getUspi(drug: string): Promise<RegionLabel | null> {\n const r = await fetchOpenFdaBest(drug);\n if (!r) return null;\n const meta = usMeta(r, drug.trim());\n\n const dosage = cleanSection(r.dosage_and_administration);\n const boxed = cleanSection(r.boxed_warning);\n const warn = cleanSection(r.warnings_and_cautions ?? r.warnings);\n const warnings = [boxed ? `BOXED WARNING — ${boxed}` : \"\", warn].filter(Boolean).join(\"\\n\\n\");\n const adverse = cleanSection(r.adverse_reactions);\n const indications = cleanSection(r.indications_and_usage);\n const contraindications = cleanSection(r.contraindications);\n\n if (!dosage && !warnings && !adverse && !indications && !contraindications) return null;\n\n return {\n region: \"US\",\n authority: \"FDA Prescribing Information\",\n productName: meta.productName,\n indications: indications || null,\n dosage: dosage || null,\n contraindications: contraindications || null,\n warnings: warnings || null,\n adverse: adverse || null,\n updated: formatFdaDate(r.effective_time),\n sourceUrl: meta.dailymedUrl,\n sourceLabel: \"DailyMed (U.S. National Library of Medicine)\",\n };\n}\n\n/* --------------------------- UK/EU: eMC ---------------------------- */\n\ninterface EmcMatch {\n product_id: string;\n name: string;\n}\n\nasync function searchEmcAll(drug: string): Promise<EmcMatch[]> {\n const url = `${EMC_BASE}/emc/search?${new URLSearchParams({ q: drug })}`;\n const res = await timedFetch(url);\n if (!res.ok) return [];\n const html = await res.text();\n const $ = cheerio.load(html);\n const out: EmcMatch[] = [];\n $(\"a[href*='/emc/product/']\").each((_, el) => {\n const href = $(el).attr(\"href\") || \"\";\n const m = href.match(/\\/emc\\/product\\/(\\d+)\\/smpc/);\n if (!m) return;\n const name = $(el).text().trim();\n if (!name || name.toLowerCase().includes(\"health professional\")) return;\n if (out.some((x) => x.product_id === m[1])) return;\n out.push({ product_id: m[1], name });\n });\n return out;\n}\n\nasync function searchEmcBest(drug: string): Promise<EmcMatch | null> {\n const matches = await searchEmcAll(drug);\n if (matches.length === 0) return null;\n const q = drug.toLowerCase();\n matches.sort(\n (a, b) =>\n (b.name.toLowerCase().includes(q) ? 1 : 0) - (a.name.toLowerCase().includes(q) ? 1 : 0)\n );\n return matches[0];\n}\n\nconst SMPC_SECTION_NAMES: Record<string, string> = {\n \"4.1\": \"Therapeutic indications\",\n \"4.2\": \"Posology and method of administration\",\n \"4.3\": \"Contraindications\",\n \"4.4\": \"Special warnings and precautions for use\",\n \"4.5\": \"Interaction with other medicinal products\",\n \"4.6\": \"Fertility, pregnancy and lactation\",\n \"4.7\": \"Effects on ability to drive and use machines\",\n \"4.8\": \"Undesirable effects\",\n \"4.9\": \"Overdose\",\n \"5.1\": \"Pharmacodynamic properties\",\n \"5.2\": \"Pharmacokinetic properties\",\n \"5.3\": \"Preclinical safety data\",\n \"6.1\": \"List of excipients\",\n \"6.2\": \"Incompatibilities\",\n \"6.3\": \"Shelf life\",\n \"6.4\": \"Special precautions for storage\",\n \"6.5\": \"Nature and contents of container\",\n \"6.6\": \"Special precautions for disposal\",\n};\n\nfunction htmlToText(html: string): string {\n const $ = cheerio.load(html);\n $(\"br\").replaceWith(\"\\n\");\n $(\"li\").each((_, el) => {\n $(el).prepend(\"- \");\n $(el).append(\"\\n\");\n });\n $(\"p, div, tr, h1, h2, h3, h4, h5, h6\").each((_, el) => {\n $(el).append(\"\\n\");\n });\n return $.text().replace(/\\n{3,}/g, \"\\n\\n\").trim();\n}\n\nfunction matchesSmpcFilter(code: string, title: string, requested: string[]): boolean {\n return requested.some((req) => {\n const r = req.toLowerCase().trim();\n if (code === req || code === r) return true;\n if (title.toLowerCase().includes(r)) return true;\n if (r.includes(\"indication\")) return code === \"4.1\";\n if (r.includes(\"dosage\") || r.includes(\"dosing\") || r.includes(\"posology\")) return code === \"4.2\";\n if (r.includes(\"contraindication\")) return code === \"4.3\";\n if (r.includes(\"warning\") || r.includes(\"precaution\")) return code === \"4.4\";\n if (r.includes(\"interaction\")) return code === \"4.5\";\n if (r.includes(\"pregnancy\") || r.includes(\"fertility\")) return code === \"4.6\";\n if (r.includes(\"adverse\") || r.includes(\"undesirable\") || r.includes(\"side effect\")) return code === \"4.8\";\n if (r.includes(\"overdose\") || r.includes(\"overdosage\")) return code === \"4.9\";\n if (r.includes(\"pharmacodynamic\")) return code === \"5.1\";\n if (r.includes(\"pharmacokinetic\")) return code === \"5.2\";\n return false;\n });\n}\n\n/**\n * Parse SmPC sections from eMC HTML. Returns every detected numbered section as\n * LabelSection[] (or the requested subset). Detect numbered headings, collect\n * sibling content up to the next heading, with a parent-text fallback.\n */\nfunction parseSmpcSections(html: string, requested?: string[]): LabelSection[] {\n const $ = cheerio.load(html);\n const sectionElements: Array<{ code: string; title: string; element: any }> = [];\n\n $(\"[id*='SECTION'], [id*='section'], .sectionHeading, h2, h3, h4\").each((_, el) => {\n const text = $(el).text().trim();\n const match = text.match(/^(\\d+\\.?\\d*)\\s+(.+)/);\n if (match) sectionElements.push({ code: match[1], title: match[2].trim(), element: $(el) });\n });\n\n if (sectionElements.length === 0) {\n $(\"*\").each((_, el) => {\n const $el = $(el);\n if ($el.children().length > 0 && !$el.is(\"a, span, strong, em, b, i\")) return;\n const text = $el.text().trim();\n const match = text.match(/^(\\d+\\.?\\d*)\\s+(.+)/);\n if (match && SMPC_SECTION_NAMES[match[1]]) {\n sectionElements.push({ code: match[1], title: match[2].trim(), element: $el });\n }\n });\n }\n\n const out: LabelSection[] = [];\n for (let i = 0; i < sectionElements.length; i++) {\n const current = sectionElements[i];\n const next = sectionElements[i + 1];\n\n let content = \"\";\n let node = current.element.next();\n while (node.length > 0) {\n if (next && node.is(next.element)) break;\n const nodeHtml = $.html(node);\n if (nodeHtml) content += htmlToText(nodeHtml) + \"\\n\";\n node = node.next();\n }\n if (!content.trim()) {\n const parentHtml = $.html(current.element.parent());\n if (parentHtml) content = htmlToText(parentHtml).replace(current.element.text().trim(), \"\").trim();\n }\n\n const cleaned = cleanSection(content);\n if (cleaned) out.push({ code: current.code, title: current.title, content: cleaned });\n }\n\n if (requested?.length) {\n return out.filter((s) => matchesSmpcFilter(s.code, s.title, requested));\n }\n return out;\n}\n\n/**\n * General UK/EU SmPC access — returns every available section (or the requested\n * subset) as LabelSection[]. Powers PubCrawl's get_smpc.\n */\nexport async function getUkSmpc(drug: string, requested?: string[]): Promise<UkSmpcResult | null> {\n if (!drug) return null;\n try {\n const best = await searchEmcBest(drug);\n if (!best) return null;\n const res = await timedFetch(`${EMC_BASE}/emc/product/${best.product_id}/smpc`);\n if (!res.ok) return null;\n const sections = parseSmpcSections(await res.text(), requested);\n if (sections.length === 0) return null;\n return {\n drugName: best.name,\n productId: best.product_id,\n sections,\n url: `${EMC_BASE}/emc/product/${best.product_id}/smpc`,\n };\n } catch {\n return null;\n }\n}\n\n/** 5-field convenience view of the UK/EU SmPC (Pharmonym). */\nexport async function getSmpc(drug: string): Promise<RegionLabel | null> {\n if (!drug) return null;\n try {\n const best = await searchEmcBest(drug);\n if (!best) return null;\n const res = await timedFetch(`${EMC_BASE}/emc/product/${best.product_id}/smpc`);\n if (!res.ok) return null;\n const all = parseSmpcSections(await res.text());\n const pick = (code: string) => all.find((s) => s.code === code)?.content ?? null;\n\n const indications = pick(\"4.1\");\n const dosage = pick(\"4.2\");\n const contraindications = pick(\"4.3\");\n const warnings = pick(\"4.4\");\n const adverse = pick(\"4.8\");\n if (!dosage && !warnings && !adverse && !indications && !contraindications) return null;\n\n return {\n region: \"UK/EU\",\n authority: \"Summary of Product Characteristics (SmPC)\",\n productName: best.name,\n indications,\n dosage,\n contraindications,\n warnings,\n adverse,\n updated: \"\",\n sourceUrl: `${EMC_BASE}/emc/product/${best.product_id}/smpc`,\n sourceLabel: \"electronic medicines compendium (emc), UK\",\n };\n } catch {\n return null;\n }\n}\n\n/* --------------------------- public API ---------------------------- */\n\n/** Look up both 5-field labels for a drug. Each side is null if unavailable. */\nexport async function getLabels(drug: string): Promise<LabelResult> {\n const [us, uk] = await Promise.all([\n getUspi(drug).catch(() => null),\n getSmpc(drug).catch(() => null),\n ]);\n return { us, uk };\n}\n","/**\n * US (FDA) vs UK/EU (SmPC) side-by-side label comparison across mapped topics.\n */\n\nimport { getLabels } from \"./labels.js\";\nimport type { CompareResult, LabelComparison } from \"./types.js\";\n\ntype LabelField = \"indications\" | \"dosage\" | \"contraindications\" | \"warnings\" | \"adverse\";\n\n/** Topics compared across US and UK labels, mapped to the fields that hold them. */\nexport const SECTION_MAP: Array<{ topic: string; field: LabelField }> = [\n { topic: \"Indications\", field: \"indications\" },\n { topic: \"Dosing\", field: \"dosage\" },\n { topic: \"Contraindications\", field: \"contraindications\" },\n { topic: \"Warnings\", field: \"warnings\" },\n { topic: \"Adverse Reactions\", field: \"adverse\" },\n];\n\nexport async function compareLabels(drug: string): Promise<CompareResult> {\n const { us, uk } = await getLabels(drug);\n\n const comparisons: LabelComparison[] = SECTION_MAP.map(({ topic, field }) => ({\n topic,\n us: us ? us[field] : null,\n uk: uk ? uk[field] : null,\n })).filter((c) => c.us || c.uk);\n\n return {\n drug,\n comparisons,\n usSource: us?.sourceUrl ?? null,\n ukSource: uk?.sourceUrl ?? null,\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/fetch.ts","../src/names.ts","../src/labels.ts","../src/compare.ts"],"sourcesContent":["/**\n * Shared HTTP + text helpers. Uses the runtime's global `fetch` (Node >=18),\n * so the package has no node-fetch dependency.\n */\n\nconst DEFAULT_TIMEOUT_MS = 12000;\nconst MAX_FIELD_CHARS = 1400;\n\nexport const USER_AGENT = \"drug-data/0.1 (pharmatools.ai; nick@pharmatools.ai)\";\n\nexport async function timedFetch(\n url: string,\n opts: RequestInit = {},\n timeoutMs = DEFAULT_TIMEOUT_MS\n): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n return await fetch(url, {\n ...opts,\n signal: controller.signal,\n headers: { \"User-Agent\": USER_AGENT, ...((opts.headers as Record<string, string>) || {}) },\n });\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function getJson<T = unknown>(\n url: string,\n timeoutMs = DEFAULT_TIMEOUT_MS\n): Promise<T | null> {\n try {\n const res = await timedFetch(url, {}, timeoutMs);\n if (!res.ok) return null;\n return (await res.json()) as T;\n } catch {\n return null;\n }\n}\n\n/**\n * Tidy a raw label section: join arrays, strip a leading numbered ALL-CAPS\n * heading, collapse whitespace, and truncate on a sentence boundary.\n */\nexport function cleanSection(value: unknown, maxChars = MAX_FIELD_CHARS): string {\n if (!value) return \"\";\n let text = Array.isArray(value) ? value.join(\"\\n\\n\") : String(value);\n\n text = text\n .replace(/\\r/g, \"\")\n .replace(/[ \\t]+/g, \" \")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n\n // Drop a leading section header line like \"5 WARNINGS AND PRECAUTIONS\"\n text = text.replace(/^\\s*\\d+(\\.\\d+)*\\s+[A-Z][A-Z \\-/,&]+\\n+/, \"\").trim();\n\n if (text.length > maxChars) {\n const slice = text.slice(0, maxChars);\n const lastStop = Math.max(slice.lastIndexOf(\". \"), slice.lastIndexOf(\"\\n\"));\n text = (lastStop > maxChars * 0.6 ? slice.slice(0, lastStop + 1) : slice).trim() + \" …\";\n }\n return text;\n}\n\nexport function formatFdaDate(yyyymmdd?: string): string {\n if (!yyyymmdd || !/^\\d{8}$/.test(yyyymmdd)) return \"\";\n const d = new Date(`${yyyymmdd.slice(0, 4)}-${yyyymmdd.slice(4, 6)}-${yyyymmdd.slice(6, 8)}`);\n if (isNaN(d.getTime())) return \"\";\n return d.toLocaleDateString(\"en-US\", { year: \"numeric\", month: \"short\", day: \"numeric\" });\n}\n\nexport function titleCase(s: string): string {\n return String(s || \"\").replace(/\\w\\S*/g, (t) => t.charAt(0).toUpperCase() + t.slice(1).toLowerCase());\n}\n\nexport function uniq<T>(arr: T[]): T[] {\n return [...new Set(arr.filter(Boolean))];\n}\n","/**\n * Deterministic brand <-> generic resolution via RxNorm/RxNav, with an openFDA\n * fallback. No AI, no hallucinated names. Brand names are US-registered;\n * international brand names are intentionally not invented.\n */\n\nimport { getJson, titleCase, uniq } from \"./fetch.js\";\nimport type { ResolveResult } from \"./types.js\";\n\nconst RXNAV = \"https://rxnav.nlm.nih.gov/REST\";\nconst OPENFDA = \"https://api.fda.gov/drug/label.json\";\nconst TIMEOUT_MS = 10000;\n\nconst REGION_NOTE = \"Brand names shown are those registered in the United States.\";\n\n/* ----------------------- RxNorm resolution ----------------------- */\n\nasync function findRxcui(name: string): Promise<string | null> {\n const exact = await getJson<any>(`${RXNAV}/rxcui.json?name=${encodeURIComponent(name)}`, TIMEOUT_MS);\n const id = exact?.idGroup?.rxnormId?.[0];\n if (id) return id;\n\n const approx = await getJson<any>(\n `${RXNAV}/approximateTerm.json?term=${encodeURIComponent(name)}&maxEntries=1`,\n TIMEOUT_MS\n );\n const cand = approx?.approximateGroup?.candidate?.[0];\n return cand ? cand.rxcui : null;\n}\n\nfunction collectNames(allRelated: any, tty: string): string[] {\n const groups = allRelated?.allRelatedGroup?.conceptGroup ?? [];\n const g = groups.find((x: any) => x.tty === tty);\n if (!g || !g.conceptProperties) return [];\n return g.conceptProperties.map((c: any) => c.name);\n}\n\n/**\n * Single-ingredient brand names for a generic ingredient, via SBDF concepts\n * whose name is \"<ingredient> <dose form> [Brand]\". Combination products read\n * \"<ing A> / <ing B> ... [Brand]\", so any \"/\" before the bracket is excluded.\n */\nasync function getSingleIngredientBrands(ingredientRxcui: string): Promise<string[]> {\n const data = await getJson<any>(`${RXNAV}/rxcui/${ingredientRxcui}/related.json?tty=SBDF`, TIMEOUT_MS);\n const groups = data?.relatedGroup?.conceptGroup ?? [];\n const g = groups.find((x: any) => x.tty === \"SBDF\");\n if (!g || !g.conceptProperties) return [];\n const brands: string[] = [];\n for (const c of g.conceptProperties) {\n const m = (c.name as string).match(/\\[([^\\]]+)\\]\\s*$/);\n if (!m) continue;\n const prefix = (c.name as string).slice(0, (c.name as string).lastIndexOf(\"[\"));\n if (prefix.includes(\"/\")) continue; // combination product — skip\n brands.push(m[1].trim());\n }\n return uniq(brands).sort();\n}\n\nasync function getDrugClassAndUses(\n rxcui: string\n): Promise<{ drugClass: string | null; uses: string[] }> {\n const data = await getJson<any>(\n `${RXNAV}/rxclass/class/byRxcui.json?rxcui=${rxcui}&relaSource=MEDRT`,\n TIMEOUT_MS\n );\n const items = data?.rxclassDrugInfoList?.rxclassDrugInfo ?? [];\n\n const byType = (t: string): string | null => {\n const hit = items.find((i: any) => i.rxclassMinConceptItem.classType === t);\n return hit ? hit.rxclassMinConceptItem.className : null;\n };\n // Prefer FDA Established Pharmacologic Class, then mechanism, then chemical.\n const drugClass = byType(\"EPC\") || byType(\"MOA\") || byType(\"CHEM\");\n\n const uses = uniq<string>(\n items\n .filter((i: any) => i.rela === \"may_treat\")\n .map((i: any) => i.rxclassMinConceptItem.className as string)\n ).slice(0, 6);\n\n return { drugClass, uses };\n}\n\nasync function resolveViaRxNorm(name: string): Promise<ResolveResult | null> {\n const rxcui = await findRxcui(name);\n if (!rxcui) return null;\n\n const [props, allRelated] = await Promise.all([\n getJson<any>(`${RXNAV}/rxcui/${rxcui}/properties.json`, TIMEOUT_MS),\n getJson<any>(`${RXNAV}/rxcui/${rxcui}/allrelated.json`, TIMEOUT_MS),\n ]);\n\n const tty: string | undefined = props?.properties?.tty;\n const canonicalName: string = props?.properties?.name ?? name;\n if (!tty) return null;\n\n const inNames = collectNames(allRelated, \"IN\");\n const minNames = collectNames(allRelated, \"MIN\");\n\n const isBrand = tty === \"BN\" || tty === \"SBD\" || tty === \"BPCK\";\n const inputType: \"brand\" | \"generic\" = isBrand ? \"brand\" : \"generic\";\n\n // For a generic, list only brands where it is the sole active ingredient.\n let brandNames: string[] | null = null;\n if (!isBrand) {\n const single = await getSingleIngredientBrands(rxcui);\n const fallback = uniq([...collectNames(allRelated, \"BN\"), ...collectNames(allRelated, \"BPCK\")]).sort();\n brandNames = single.length ? single : fallback;\n }\n\n // Generic name must reflect the concept's OWN ingredient(s). A single-ingredient\n // lookup (e.g. \"atorvastatin\", tty IN) relates to every combination it appears\n // in (Atorvastatin / Ezetimibe, ...), so the previous MIN-first logic\n // mis-resolved single generics to arbitrary combinations and broke downstream\n // label lookups. When the input concept is itself an ingredient (IN) or a\n // multi-ingredient concept (MIN), its own canonical name is authoritative;\n // only for brands/products do we read the related MIN as the combo generic.\n let genericName: string | null = null;\n if (tty === \"IN\" || tty === \"MIN\") {\n genericName = titleCase(canonicalName);\n } else if (minNames.length) {\n genericName = titleCase(minNames[0]);\n } else if (inNames.length === 1) {\n genericName = titleCase(inNames[0]);\n } else if (inNames.length > 1) {\n genericName = titleCase(uniq(inNames).join(\" / \"));\n } else {\n genericName = titleCase(canonicalName);\n }\n\n // Class + indications from the ingredient rxcui where possible.\n const classRxcui = !isBrand\n ? rxcui\n : (() => {\n const inGroup = (allRelated?.allRelatedGroup?.conceptGroup ?? []).find((x: any) => x.tty === \"IN\");\n return inGroup?.conceptProperties?.[0]?.rxcui ?? rxcui;\n })();\n const { drugClass, uses } = await getDrugClassAndUses(classRxcui);\n\n return {\n inputType,\n inputName: titleCase(canonicalName),\n genericName,\n brandNames,\n drugClass,\n uses,\n rxcui,\n source: \"RxNorm (NLM)\",\n regionNote: REGION_NOTE,\n };\n}\n\n/* ----------------------- openFDA fallback ------------------------ */\n\nasync function resolveViaOpenFda(name: string): Promise<ResolveResult | null> {\n const q = name.replace(/\"/g, \"\");\n const search = `(openfda.brand_name:\"${q}\"+OR+openfda.generic_name:\"${q}\")`;\n const url = `${OPENFDA}?search=${encodeURIComponent(search).replace(/%2B/g, \"+\")}&limit=1`;\n const data = await getJson<any>(url, TIMEOUT_MS);\n const fda = data?.results?.[0]?.openfda;\n if (!fda) return null;\n\n const brand = (fda.brand_name ?? [])[0] ?? \"\";\n const generic = (fda.generic_name ?? [])[0] ?? \"\";\n if (!brand && !generic) return null;\n\n const isBrand = brand.toLowerCase() === name.toLowerCase();\n return {\n inputType: isBrand ? \"brand\" : \"generic\",\n inputName: titleCase(name),\n genericName: generic ? titleCase(generic) : null,\n brandNames: isBrand ? null : uniq<string>(fda.brand_name ?? []).sort(),\n drugClass: (fda.pharm_class_epc ?? [])[0] ?? (fda.pharm_class_moa ?? [])[0] ?? null,\n uses: [],\n rxcui: null,\n source: \"openFDA\",\n regionNote: REGION_NOTE,\n };\n}\n\n/* --------------------------- public ----------------------------- */\n\n/**\n * Resolve a drug name deterministically. Tries RxNorm, then openFDA. Returns\n * null if neither source recognises the name.\n */\nexport async function resolveName(name: string): Promise<ResolveResult | null> {\n if (!name || !name.trim()) return null;\n const clean = name.trim();\n const viaRx = await resolveViaRxNorm(clean).catch(() => null);\n if (viaRx && (viaRx.genericName || (viaRx.brandNames && viaRx.brandNames.length))) {\n return viaRx;\n }\n return await resolveViaOpenFda(clean).catch(() => null);\n}\n","/**\n * Official drug-label lookups.\n * - US: FDA label via the openFDA API -> DailyMed citation\n * - UK/EU: SmPC via the eMC (medicines.org.uk) -> eMC citation\n *\n * Two layers:\n * - getUsLabel / getUkSmpc — general, all-section access (LabelSection[]),\n * used by PubCrawl's get_uspi / get_smpc / compare_labels tools.\n * - getUspi / getSmpc / getLabels — a 5-field convenience view (RegionLabel)\n * used by Pharmonym. Built on the same fetch + parse internals.\n *\n * Every network call is best-effort: failures return null. Pure fetch + parse;\n * caching belongs to the consumer.\n */\n\nimport * as cheerio from \"cheerio\";\nimport { timedFetch, getJson, cleanSection, formatFdaDate } from \"./fetch.js\";\nimport type { RegionLabel, LabelResult, LabelSection, UsLabelResult, UkSmpcResult } from \"./types.js\";\n\nconst OPENFDA_URL = \"https://api.fda.gov/drug/label.json\";\nconst EMC_BASE = \"https://www.medicines.org.uk\";\n\ninterface OpenFdaLabel {\n effective_time?: string;\n boxed_warning?: string[];\n indications_and_usage?: string[];\n dosage_and_administration?: string[];\n dosage_forms_and_strengths?: string[];\n contraindications?: string[];\n warnings_and_cautions?: string[];\n warnings?: string[];\n adverse_reactions?: string[];\n drug_interactions?: string[];\n use_in_specific_populations?: string[];\n overdosage?: string[];\n clinical_pharmacology?: string[];\n description?: string[];\n how_supplied?: string[];\n openfda?: {\n generic_name?: string[];\n brand_name?: string[];\n spl_set_id?: string[];\n };\n}\ninterface OpenFdaResponse {\n results?: OpenFdaLabel[];\n}\n\n// openFDA field -> { LOINC code, display title }. Order = label reading order.\nconst US_SECTION_MAP: Array<{ field: keyof OpenFdaLabel; code: string; title: string }> = [\n { field: \"boxed_warning\", code: \"34066-1\", title: \"Boxed Warning\" },\n { field: \"indications_and_usage\", code: \"34067-9\", title: \"Indications and Usage\" },\n { field: \"dosage_and_administration\", code: \"34068-7\", title: \"Dosage and Administration\" },\n { field: \"dosage_forms_and_strengths\", code: \"43678-2\", title: \"Dosage Forms and Strengths\" },\n { field: \"contraindications\", code: \"34070-3\", title: \"Contraindications\" },\n { field: \"warnings_and_cautions\", code: \"43685-7\", title: \"Warnings and Precautions\" },\n { field: \"warnings\", code: \"34071-1\", title: \"Warnings\" },\n { field: \"adverse_reactions\", code: \"34084-4\", title: \"Adverse Reactions\" },\n { field: \"drug_interactions\", code: \"34073-7\", title: \"Drug Interactions\" },\n { field: \"use_in_specific_populations\", code: \"42228-7\", title: \"Use in Specific Populations\" },\n { field: \"overdosage\", code: \"34088-5\", title: \"Overdosage\" },\n { field: \"clinical_pharmacology\", code: \"34090-1\", title: \"Clinical Pharmacology\" },\n { field: \"description\", code: \"34089-3\", title: \"Description\" },\n { field: \"how_supplied\", code: \"34069-5\", title: \"How Supplied\" },\n];\n\n/* --------------------------- US: openFDA --------------------------- */\n\nfunction score(r: OpenFdaLabel, q: string): number {\n const g = (r.openfda?.generic_name ?? []).join(\", \").toLowerCase();\n let s = 0;\n if (g && g.split(/,| and /).length === 1) s += 2; // single ingredient\n if (g.includes(q.toLowerCase())) s += 1;\n return s;\n}\n\n/** Fetch the most recent FDA label for a drug; prefers single-ingredient products. */\nasync function fetchOpenFdaBest(drug: string): Promise<OpenFdaLabel | null> {\n if (!drug) return null;\n const q = drug.trim().replace(/\"/g, \"\");\n const search = `(openfda.generic_name:\"${q}\"+OR+openfda.brand_name:\"${q}\")`;\n const url = `${OPENFDA_URL}?search=${encodeURIComponent(search).replace(/%2B/g, \"+\")}&sort=effective_time:desc&limit=5`;\n const data = await getJson<OpenFdaResponse>(url);\n const results = data?.results ?? [];\n if (results.length === 0) return null;\n return results.slice().sort((a, b) => score(b, q) - score(a, q))[0];\n}\n\nfunction usMeta(r: OpenFdaLabel, fallbackName: string) {\n const setId = r.openfda?.spl_set_id?.[0] ?? \"\";\n const brand = r.openfda?.brand_name?.[0] ?? \"\";\n const generic = r.openfda?.generic_name?.[0] ?? fallbackName;\n return {\n setId,\n brand,\n generic,\n productName: brand ? `${brand} (${generic})` : generic,\n dailymedUrl: setId\n ? `https://dailymed.nlm.nih.gov/dailymed/drugInfo.cfm?setid=${setId}`\n : \"https://dailymed.nlm.nih.gov/dailymed/\",\n };\n}\n\nfunction matchesUsFilter(entry: { field: string; code: string; title: string }, requested: string[]): boolean {\n return requested.some((req) => {\n const r = req.toLowerCase().trim();\n return (\n entry.code === req ||\n entry.title.toLowerCase().includes(r) ||\n String(entry.field).replace(/_/g, \" \").includes(r) ||\n r.includes(entry.title.toLowerCase())\n );\n });\n}\n\n/**\n * General US label access — returns every available section (or the requested\n * subset) as LabelSection[]. Powers PubCrawl's get_uspi.\n */\nexport async function getUsLabel(drug: string, requested?: string[]): Promise<UsLabelResult | null> {\n const r = await fetchOpenFdaBest(drug);\n if (!r) return null;\n const meta = usMeta(r, drug.trim());\n\n const sections: LabelSection[] = [];\n for (const entry of US_SECTION_MAP) {\n if (requested?.length && !matchesUsFilter(entry, requested)) continue;\n const content = cleanSection(r[entry.field]);\n if (content) sections.push({ code: entry.code, title: entry.title, content });\n }\n if (sections.length === 0) return null;\n\n return {\n drugName: meta.productName,\n setId: meta.setId,\n publishedDate: formatFdaDate(r.effective_time),\n sections,\n dailymedUrl: meta.dailymedUrl,\n };\n}\n\n/** 5-field convenience view of the US label (Pharmonym). */\nexport async function getUspi(drug: string): Promise<RegionLabel | null> {\n const r = await fetchOpenFdaBest(drug);\n if (!r) return null;\n const meta = usMeta(r, drug.trim());\n\n const dosage = cleanSection(r.dosage_and_administration);\n const boxed = cleanSection(r.boxed_warning);\n const warn = cleanSection(r.warnings_and_cautions ?? r.warnings);\n const warnings = [boxed ? `BOXED WARNING — ${boxed}` : \"\", warn].filter(Boolean).join(\"\\n\\n\");\n const adverse = cleanSection(r.adverse_reactions);\n const indications = cleanSection(r.indications_and_usage);\n const contraindications = cleanSection(r.contraindications);\n\n if (!dosage && !warnings && !adverse && !indications && !contraindications) return null;\n\n return {\n region: \"US\",\n authority: \"FDA Prescribing Information\",\n productName: meta.productName,\n indications: indications || null,\n dosage: dosage || null,\n contraindications: contraindications || null,\n warnings: warnings || null,\n adverse: adverse || null,\n updated: formatFdaDate(r.effective_time),\n sourceUrl: meta.dailymedUrl,\n sourceLabel: \"DailyMed (U.S. National Library of Medicine)\",\n };\n}\n\n/* --------------------------- UK/EU: eMC ---------------------------- */\n\ninterface EmcMatch {\n product_id: string;\n name: string;\n}\n\nasync function searchEmcAll(drug: string): Promise<EmcMatch[]> {\n const url = `${EMC_BASE}/emc/search?${new URLSearchParams({ q: drug })}`;\n const res = await timedFetch(url);\n if (!res.ok) return [];\n const html = await res.text();\n const $ = cheerio.load(html);\n const out: EmcMatch[] = [];\n $(\"a[href*='/emc/product/']\").each((_, el) => {\n const href = $(el).attr(\"href\") || \"\";\n const m = href.match(/\\/emc\\/product\\/(\\d+)\\/smpc/);\n if (!m) return;\n const name = $(el).text().trim();\n if (!name || name.toLowerCase().includes(\"health professional\")) return;\n if (out.some((x) => x.product_id === m[1])) return;\n out.push({ product_id: m[1], name });\n });\n return out;\n}\n\nasync function searchEmcBest(drug: string): Promise<EmcMatch | null> {\n const matches = await searchEmcAll(drug);\n if (matches.length === 0) return null;\n const q = drug.toLowerCase();\n matches.sort(\n (a, b) =>\n (b.name.toLowerCase().includes(q) ? 1 : 0) - (a.name.toLowerCase().includes(q) ? 1 : 0)\n );\n return matches[0];\n}\n\nconst SMPC_SECTION_NAMES: Record<string, string> = {\n \"4.1\": \"Therapeutic indications\",\n \"4.2\": \"Posology and method of administration\",\n \"4.3\": \"Contraindications\",\n \"4.4\": \"Special warnings and precautions for use\",\n \"4.5\": \"Interaction with other medicinal products\",\n \"4.6\": \"Fertility, pregnancy and lactation\",\n \"4.7\": \"Effects on ability to drive and use machines\",\n \"4.8\": \"Undesirable effects\",\n \"4.9\": \"Overdose\",\n \"5.1\": \"Pharmacodynamic properties\",\n \"5.2\": \"Pharmacokinetic properties\",\n \"5.3\": \"Preclinical safety data\",\n \"6.1\": \"List of excipients\",\n \"6.2\": \"Incompatibilities\",\n \"6.3\": \"Shelf life\",\n \"6.4\": \"Special precautions for storage\",\n \"6.5\": \"Nature and contents of container\",\n \"6.6\": \"Special precautions for disposal\",\n};\n\nfunction htmlToText(html: string): string {\n const $ = cheerio.load(html);\n $(\"br\").replaceWith(\"\\n\");\n $(\"li\").each((_, el) => {\n $(el).prepend(\"- \");\n $(el).append(\"\\n\");\n });\n $(\"p, div, tr, h1, h2, h3, h4, h5, h6\").each((_, el) => {\n $(el).append(\"\\n\");\n });\n return $.text().replace(/\\n{3,}/g, \"\\n\\n\").trim();\n}\n\nfunction matchesSmpcFilter(code: string, title: string, requested: string[]): boolean {\n return requested.some((req) => {\n const r = req.toLowerCase().trim();\n if (code === req || code === r) return true;\n if (title.toLowerCase().includes(r)) return true;\n if (r.includes(\"indication\")) return code === \"4.1\";\n if (r.includes(\"dosage\") || r.includes(\"dosing\") || r.includes(\"posology\")) return code === \"4.2\";\n if (r.includes(\"contraindication\")) return code === \"4.3\";\n if (r.includes(\"warning\") || r.includes(\"precaution\")) return code === \"4.4\";\n if (r.includes(\"interaction\")) return code === \"4.5\";\n if (r.includes(\"pregnancy\") || r.includes(\"fertility\")) return code === \"4.6\";\n if (r.includes(\"adverse\") || r.includes(\"undesirable\") || r.includes(\"side effect\")) return code === \"4.8\";\n if (r.includes(\"overdose\") || r.includes(\"overdosage\")) return code === \"4.9\";\n if (r.includes(\"pharmacodynamic\")) return code === \"5.1\";\n if (r.includes(\"pharmacokinetic\")) return code === \"5.2\";\n return false;\n });\n}\n\n/**\n * Parse SmPC sections from eMC HTML. Returns every detected numbered section as\n * LabelSection[] (or the requested subset). Detect numbered headings, collect\n * sibling content up to the next heading, with a parent-text fallback.\n */\nfunction parseSmpcSections(html: string, requested?: string[]): LabelSection[] {\n const $ = cheerio.load(html);\n const sectionElements: Array<{ code: string; title: string; element: any }> = [];\n\n $(\"[id*='SECTION'], [id*='section'], .sectionHeading, h2, h3, h4\").each((_, el) => {\n const text = $(el).text().trim();\n const match = text.match(/^(\\d+\\.?\\d*)\\s+(.+)/);\n if (match) sectionElements.push({ code: match[1], title: match[2].trim(), element: $(el) });\n });\n\n if (sectionElements.length === 0) {\n $(\"*\").each((_, el) => {\n const $el = $(el);\n if ($el.children().length > 0 && !$el.is(\"a, span, strong, em, b, i\")) return;\n const text = $el.text().trim();\n const match = text.match(/^(\\d+\\.?\\d*)\\s+(.+)/);\n if (match && SMPC_SECTION_NAMES[match[1]]) {\n sectionElements.push({ code: match[1], title: match[2].trim(), element: $el });\n }\n });\n }\n\n const out: LabelSection[] = [];\n for (let i = 0; i < sectionElements.length; i++) {\n const current = sectionElements[i];\n const next = sectionElements[i + 1];\n\n let content = \"\";\n let node = current.element.next();\n while (node.length > 0) {\n if (next && node.is(next.element)) break;\n const nodeHtml = $.html(node);\n if (nodeHtml) content += htmlToText(nodeHtml) + \"\\n\";\n node = node.next();\n }\n if (!content.trim()) {\n const parentHtml = $.html(current.element.parent());\n if (parentHtml) content = htmlToText(parentHtml).replace(current.element.text().trim(), \"\").trim();\n }\n\n const cleaned = cleanSection(content);\n if (cleaned) out.push({ code: current.code, title: current.title, content: cleaned });\n }\n\n if (requested?.length) {\n return out.filter((s) => matchesSmpcFilter(s.code, s.title, requested));\n }\n return out;\n}\n\n/**\n * General UK/EU SmPC access — returns every available section (or the requested\n * subset) as LabelSection[]. Powers PubCrawl's get_smpc.\n */\nexport async function getUkSmpc(drug: string, requested?: string[]): Promise<UkSmpcResult | null> {\n if (!drug) return null;\n try {\n const best = await searchEmcBest(drug);\n if (!best) return null;\n const res = await timedFetch(`${EMC_BASE}/emc/product/${best.product_id}/smpc`);\n if (!res.ok) return null;\n const sections = parseSmpcSections(await res.text(), requested);\n if (sections.length === 0) return null;\n return {\n drugName: best.name,\n productId: best.product_id,\n sections,\n url: `${EMC_BASE}/emc/product/${best.product_id}/smpc`,\n };\n } catch {\n return null;\n }\n}\n\n/** 5-field convenience view of the UK/EU SmPC (Pharmonym). */\nexport async function getSmpc(drug: string): Promise<RegionLabel | null> {\n if (!drug) return null;\n try {\n const best = await searchEmcBest(drug);\n if (!best) return null;\n const res = await timedFetch(`${EMC_BASE}/emc/product/${best.product_id}/smpc`);\n if (!res.ok) return null;\n const all = parseSmpcSections(await res.text());\n const pick = (code: string) => all.find((s) => s.code === code)?.content ?? null;\n\n const indications = pick(\"4.1\");\n const dosage = pick(\"4.2\");\n const contraindications = pick(\"4.3\");\n const warnings = pick(\"4.4\");\n const adverse = pick(\"4.8\");\n if (!dosage && !warnings && !adverse && !indications && !contraindications) return null;\n\n return {\n region: \"UK/EU\",\n authority: \"Summary of Product Characteristics (SmPC)\",\n productName: best.name,\n indications,\n dosage,\n contraindications,\n warnings,\n adverse,\n updated: \"\",\n sourceUrl: `${EMC_BASE}/emc/product/${best.product_id}/smpc`,\n sourceLabel: \"electronic medicines compendium (emc), UK\",\n };\n } catch {\n return null;\n }\n}\n\n/* --------------------------- public API ---------------------------- */\n\n/** Look up both 5-field labels for a drug. Each side is null if unavailable. */\nexport async function getLabels(drug: string): Promise<LabelResult> {\n const [us, uk] = await Promise.all([\n getUspi(drug).catch(() => null),\n getSmpc(drug).catch(() => null),\n ]);\n return { us, uk };\n}\n","/**\n * US (FDA) vs UK/EU (SmPC) side-by-side label comparison across mapped topics.\n */\n\nimport { getLabels } from \"./labels.js\";\nimport type { CompareResult, LabelComparison } from \"./types.js\";\n\ntype LabelField = \"indications\" | \"dosage\" | \"contraindications\" | \"warnings\" | \"adverse\";\n\n/** Topics compared across US and UK labels, mapped to the fields that hold them. */\nexport const SECTION_MAP: Array<{ topic: string; field: LabelField }> = [\n { topic: \"Indications\", field: \"indications\" },\n { topic: \"Dosing\", field: \"dosage\" },\n { topic: \"Contraindications\", field: \"contraindications\" },\n { topic: \"Warnings\", field: \"warnings\" },\n { topic: \"Adverse Reactions\", field: \"adverse\" },\n];\n\nexport async function compareLabels(drug: string): Promise<CompareResult> {\n const { us, uk } = await getLabels(drug);\n\n const comparisons: LabelComparison[] = SECTION_MAP.map(({ topic, field }) => ({\n topic,\n us: us ? us[field] : null,\n uk: uk ? uk[field] : null,\n })).filter((c) => c.us || c.uk);\n\n return {\n drug,\n comparisons,\n usSource: us?.sourceUrl ?? null,\n ukSource: uk?.sourceUrl ?? null,\n };\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pharmatools/drug-data",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Deterministic drug-name resolution (RxNorm) and official label data (FDA openFDA + UK eMC SmPC), with US/UK comparison. The shared engine behind Pharmonym and PubCrawl.",
5
5
  "type": "module",
6
6
  "exports": {