@pharmatools/drug-data 0.1.0 → 0.2.0

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/README.md CHANGED
@@ -37,6 +37,22 @@ await compareLabels("atorvastatin");
37
37
 
38
38
  CommonJS consumers use `require("@pharmatools/drug-data")` — same API.
39
39
 
40
+ ### General (all-section) access
41
+
42
+ `getLabels` returns a compact 5-field view (uses, dosing, contraindications,
43
+ warnings, adverse). For every available label section, use the general API:
44
+
45
+ ```ts
46
+ import { getUsLabel, getUkSmpc } from "@pharmatools/drug-data";
47
+
48
+ await getUsLabel("atorvastatin"); // { drugName, setId, publishedDate, sections[], dailymedUrl }
49
+ await getUsLabel("atorvastatin", ["drug interactions"]); // filter to specific sections
50
+ await getUkSmpc("atorvastatin"); // { drugName, productId, sections[], url } — all SmPC sections
51
+ await getUkSmpc("atorvastatin", ["4.5"]); // filter by section number or name
52
+ ```
53
+
54
+ Each `section` is `{ code, title, content }`.
55
+
40
56
  ## Develop
41
57
 
42
58
  ```bash
package/dist/index.cjs CHANGED
@@ -36,6 +36,8 @@ __export(index_exports, {
36
36
  formatFdaDate: () => formatFdaDate,
37
37
  getLabels: () => getLabels,
38
38
  getSmpc: () => getSmpc,
39
+ getUkSmpc: () => getUkSmpc,
40
+ getUsLabel: () => getUsLabel,
39
41
  getUspi: () => getUspi,
40
42
  resolveName: () => resolveName
41
43
  });
@@ -223,7 +225,30 @@ async function resolveName(name) {
223
225
  var cheerio = __toESM(require("cheerio"), 1);
224
226
  var OPENFDA_URL = "https://api.fda.gov/drug/label.json";
225
227
  var EMC_BASE = "https://www.medicines.org.uk";
226
- async function getUspi(drug) {
228
+ var US_SECTION_MAP = [
229
+ { field: "boxed_warning", code: "34066-1", title: "Boxed Warning" },
230
+ { field: "indications_and_usage", code: "34067-9", title: "Indications and Usage" },
231
+ { field: "dosage_and_administration", code: "34068-7", title: "Dosage and Administration" },
232
+ { field: "dosage_forms_and_strengths", code: "43678-2", title: "Dosage Forms and Strengths" },
233
+ { field: "contraindications", code: "34070-3", title: "Contraindications" },
234
+ { field: "warnings_and_cautions", code: "43685-7", title: "Warnings and Precautions" },
235
+ { field: "warnings", code: "34071-1", title: "Warnings" },
236
+ { field: "adverse_reactions", code: "34084-4", title: "Adverse Reactions" },
237
+ { field: "drug_interactions", code: "34073-7", title: "Drug Interactions" },
238
+ { field: "use_in_specific_populations", code: "42228-7", title: "Use in Specific Populations" },
239
+ { field: "overdosage", code: "34088-5", title: "Overdosage" },
240
+ { field: "clinical_pharmacology", code: "34090-1", title: "Clinical Pharmacology" },
241
+ { field: "description", code: "34089-3", title: "Description" },
242
+ { field: "how_supplied", code: "34069-5", title: "How Supplied" }
243
+ ];
244
+ function score(r, q) {
245
+ const g = (r.openfda?.generic_name ?? []).join(", ").toLowerCase();
246
+ let s = 0;
247
+ if (g && g.split(/,| and /).length === 1) s += 2;
248
+ if (g.includes(q.toLowerCase())) s += 1;
249
+ return s;
250
+ }
251
+ async function fetchOpenFdaBest(drug) {
227
252
  if (!drug) return null;
228
253
  const q = drug.trim().replace(/"/g, "");
229
254
  const search = `(openfda.generic_name:"${q}"+OR+openfda.brand_name:"${q}")`;
@@ -231,8 +256,49 @@ async function getUspi(drug) {
231
256
  const data = await getJson(url);
232
257
  const results = data?.results ?? [];
233
258
  if (results.length === 0) return null;
234
- const ranked = results.slice().sort((a, b) => score(b, q) - score(a, q));
235
- const r = ranked[0];
259
+ return results.slice().sort((a, b) => score(b, q) - score(a, q))[0];
260
+ }
261
+ function usMeta(r, fallbackName) {
262
+ const setId = r.openfda?.spl_set_id?.[0] ?? "";
263
+ const brand = r.openfda?.brand_name?.[0] ?? "";
264
+ const generic = r.openfda?.generic_name?.[0] ?? fallbackName;
265
+ return {
266
+ setId,
267
+ brand,
268
+ generic,
269
+ productName: brand ? `${brand} (${generic})` : generic,
270
+ dailymedUrl: setId ? `https://dailymed.nlm.nih.gov/dailymed/drugInfo.cfm?setid=${setId}` : "https://dailymed.nlm.nih.gov/dailymed/"
271
+ };
272
+ }
273
+ function matchesUsFilter(entry, requested) {
274
+ return requested.some((req) => {
275
+ const r = req.toLowerCase().trim();
276
+ return entry.code === req || entry.title.toLowerCase().includes(r) || String(entry.field).replace(/_/g, " ").includes(r) || r.includes(entry.title.toLowerCase());
277
+ });
278
+ }
279
+ async function getUsLabel(drug, requested) {
280
+ const r = await fetchOpenFdaBest(drug);
281
+ if (!r) return null;
282
+ const meta = usMeta(r, drug.trim());
283
+ const sections = [];
284
+ for (const entry of US_SECTION_MAP) {
285
+ if (requested?.length && !matchesUsFilter(entry, requested)) continue;
286
+ const content = cleanSection(r[entry.field]);
287
+ if (content) sections.push({ code: entry.code, title: entry.title, content });
288
+ }
289
+ if (sections.length === 0) return null;
290
+ return {
291
+ drugName: meta.productName,
292
+ setId: meta.setId,
293
+ publishedDate: formatFdaDate(r.effective_time),
294
+ sections,
295
+ dailymedUrl: meta.dailymedUrl
296
+ };
297
+ }
298
+ async function getUspi(drug) {
299
+ const r = await fetchOpenFdaBest(drug);
300
+ if (!r) return null;
301
+ const meta = usMeta(r, drug.trim());
236
302
  const dosage = cleanSection(r.dosage_and_administration);
237
303
  const boxed = cleanSection(r.boxed_warning);
238
304
  const warn = cleanSection(r.warnings_and_cautions ?? r.warnings);
@@ -241,31 +307,21 @@ async function getUspi(drug) {
241
307
  const indications = cleanSection(r.indications_and_usage);
242
308
  const contraindications = cleanSection(r.contraindications);
243
309
  if (!dosage && !warnings && !adverse && !indications && !contraindications) return null;
244
- const setId = r.openfda?.spl_set_id?.[0] ?? "";
245
- const brand = r.openfda?.brand_name?.[0] ?? "";
246
- const generic = r.openfda?.generic_name?.[0] ?? q;
247
310
  return {
248
311
  region: "US",
249
312
  authority: "FDA Prescribing Information",
250
- productName: brand ? `${brand} (${generic})` : generic,
313
+ productName: meta.productName,
251
314
  indications: indications || null,
252
315
  dosage: dosage || null,
253
316
  contraindications: contraindications || null,
254
317
  warnings: warnings || null,
255
318
  adverse: adverse || null,
256
319
  updated: formatFdaDate(r.effective_time),
257
- sourceUrl: setId ? `https://dailymed.nlm.nih.gov/dailymed/drugInfo.cfm?setid=${setId}` : "https://dailymed.nlm.nih.gov/dailymed/",
320
+ sourceUrl: meta.dailymedUrl,
258
321
  sourceLabel: "DailyMed (U.S. National Library of Medicine)"
259
322
  };
260
323
  }
261
- function score(r, q) {
262
- const g = (r.openfda?.generic_name ?? []).join(", ").toLowerCase();
263
- let s = 0;
264
- if (g && g.split(/,| and /).length === 1) s += 2;
265
- if (g.includes(q.toLowerCase())) s += 1;
266
- return s;
267
- }
268
- async function searchEmc(drug) {
324
+ async function searchEmcAll(drug) {
269
325
  const url = `${EMC_BASE}/emc/search?${new URLSearchParams({ q: drug })}`;
270
326
  const res = await timedFetch(url);
271
327
  if (!res.ok) return [];
@@ -283,13 +339,15 @@ async function searchEmc(drug) {
283
339
  });
284
340
  return out;
285
341
  }
286
- var SMPC_WANT = {
287
- "4.1": "indications",
288
- "4.2": "dosage",
289
- "4.3": "contraindications",
290
- "4.4": "warnings",
291
- "4.8": "adverse"
292
- };
342
+ async function searchEmcBest(drug) {
343
+ const matches = await searchEmcAll(drug);
344
+ if (matches.length === 0) return null;
345
+ const q = drug.toLowerCase();
346
+ matches.sort(
347
+ (a, b) => (b.name.toLowerCase().includes(q) ? 1 : 0) - (a.name.toLowerCase().includes(q) ? 1 : 0)
348
+ );
349
+ return matches[0];
350
+ }
293
351
  var SMPC_SECTION_NAMES = {
294
352
  "4.1": "Therapeutic indications",
295
353
  "4.2": "Posology and method of administration",
@@ -302,7 +360,13 @@ var SMPC_SECTION_NAMES = {
302
360
  "4.9": "Overdose",
303
361
  "5.1": "Pharmacodynamic properties",
304
362
  "5.2": "Pharmacokinetic properties",
305
- "5.3": "Preclinical safety data"
363
+ "5.3": "Preclinical safety data",
364
+ "6.1": "List of excipients",
365
+ "6.2": "Incompatibilities",
366
+ "6.3": "Shelf life",
367
+ "6.4": "Special precautions for storage",
368
+ "6.5": "Nature and contents of container",
369
+ "6.6": "Special precautions for disposal"
306
370
  };
307
371
  function htmlToText(html) {
308
372
  const $ = cheerio.load(html);
@@ -316,7 +380,25 @@ function htmlToText(html) {
316
380
  });
317
381
  return $.text().replace(/\n{3,}/g, "\n\n").trim();
318
382
  }
319
- function parseSmpcSections(html) {
383
+ function matchesSmpcFilter(code, title, requested) {
384
+ return requested.some((req) => {
385
+ const r = req.toLowerCase().trim();
386
+ if (code === req || code === r) return true;
387
+ if (title.toLowerCase().includes(r)) return true;
388
+ if (r.includes("indication")) return code === "4.1";
389
+ if (r.includes("dosage") || r.includes("dosing") || r.includes("posology")) return code === "4.2";
390
+ if (r.includes("contraindication")) return code === "4.3";
391
+ if (r.includes("warning") || r.includes("precaution")) return code === "4.4";
392
+ if (r.includes("interaction")) return code === "4.5";
393
+ if (r.includes("pregnancy") || r.includes("fertility")) return code === "4.6";
394
+ if (r.includes("adverse") || r.includes("undesirable") || r.includes("side effect")) return code === "4.8";
395
+ if (r.includes("overdose") || r.includes("overdosage")) return code === "4.9";
396
+ if (r.includes("pharmacodynamic")) return code === "5.1";
397
+ if (r.includes("pharmacokinetic")) return code === "5.2";
398
+ return false;
399
+ });
400
+ }
401
+ function parseSmpcSections(html, requested) {
320
402
  const $ = cheerio.load(html);
321
403
  const sectionElements = [];
322
404
  $("[id*='SECTION'], [id*='section'], .sectionHeading, h2, h3, h4").each((_, el) => {
@@ -335,11 +417,9 @@ function parseSmpcSections(html) {
335
417
  }
336
418
  });
337
419
  }
338
- const fields = {};
420
+ const out = [];
339
421
  for (let i = 0; i < sectionElements.length; i++) {
340
422
  const current = sectionElements[i];
341
- const field = SMPC_WANT[current.code];
342
- if (!field) continue;
343
423
  const next = sectionElements[i + 1];
344
424
  let content = "";
345
425
  let node = current.element.next();
@@ -351,39 +431,59 @@ function parseSmpcSections(html) {
351
431
  }
352
432
  if (!content.trim()) {
353
433
  const parentHtml = $.html(current.element.parent());
354
- if (parentHtml) {
355
- content = htmlToText(parentHtml).replace(current.element.text().trim(), "").trim();
356
- }
434
+ if (parentHtml) content = htmlToText(parentHtml).replace(current.element.text().trim(), "").trim();
357
435
  }
358
- fields[field] = cleanSection(content);
436
+ const cleaned = cleanSection(content);
437
+ if (cleaned) out.push({ code: current.code, title: current.title, content: cleaned });
438
+ }
439
+ if (requested?.length) {
440
+ return out.filter((s) => matchesSmpcFilter(s.code, s.title, requested));
441
+ }
442
+ return out;
443
+ }
444
+ async function getUkSmpc(drug, requested) {
445
+ if (!drug) return null;
446
+ try {
447
+ const best = await searchEmcBest(drug);
448
+ if (!best) return null;
449
+ const res = await timedFetch(`${EMC_BASE}/emc/product/${best.product_id}/smpc`);
450
+ if (!res.ok) return null;
451
+ const sections = parseSmpcSections(await res.text(), requested);
452
+ if (sections.length === 0) return null;
453
+ return {
454
+ drugName: best.name,
455
+ productId: best.product_id,
456
+ sections,
457
+ url: `${EMC_BASE}/emc/product/${best.product_id}/smpc`
458
+ };
459
+ } catch {
460
+ return null;
359
461
  }
360
- return fields;
361
462
  }
362
463
  async function getSmpc(drug) {
363
464
  if (!drug) return null;
364
465
  try {
365
- const matches = await searchEmc(drug);
366
- if (matches.length === 0) return null;
367
- const q = drug.toLowerCase();
368
- matches.sort(
369
- (a, b) => (b.name.toLowerCase().includes(q) ? 1 : 0) - (a.name.toLowerCase().includes(q) ? 1 : 0)
370
- );
371
- const best = matches[0];
466
+ const best = await searchEmcBest(drug);
467
+ if (!best) return null;
372
468
  const res = await timedFetch(`${EMC_BASE}/emc/product/${best.product_id}/smpc`);
373
469
  if (!res.ok) return null;
374
- const html = await res.text();
375
- const sec = parseSmpcSections(html);
376
- if (!sec.dosage && !sec.warnings && !sec.adverse && !sec.indications && !sec.contraindications)
377
- return null;
470
+ const all = parseSmpcSections(await res.text());
471
+ const pick = (code) => all.find((s) => s.code === code)?.content ?? null;
472
+ const indications = pick("4.1");
473
+ const dosage = pick("4.2");
474
+ const contraindications = pick("4.3");
475
+ const warnings = pick("4.4");
476
+ const adverse = pick("4.8");
477
+ if (!dosage && !warnings && !adverse && !indications && !contraindications) return null;
378
478
  return {
379
479
  region: "UK/EU",
380
480
  authority: "Summary of Product Characteristics (SmPC)",
381
481
  productName: best.name,
382
- indications: sec.indications || null,
383
- dosage: sec.dosage || null,
384
- contraindications: sec.contraindications || null,
385
- warnings: sec.warnings || null,
386
- adverse: sec.adverse || null,
482
+ indications,
483
+ dosage,
484
+ contraindications,
485
+ warnings,
486
+ adverse,
387
487
  updated: "",
388
488
  sourceUrl: `${EMC_BASE}/emc/product/${best.product_id}/smpc`,
389
489
  sourceLabel: "electronic medicines compendium (emc), UK"
@@ -430,6 +530,8 @@ async function compareLabels(drug) {
430
530
  formatFdaDate,
431
531
  getLabels,
432
532
  getSmpc,
533
+ getUkSmpc,
534
+ getUsLabel,
433
535
  getUspi,
434
536
  resolveName
435
537
  });
@@ -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\";\nexport { getUspi, getSmpc, getLabels } 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 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 * Every network call is best-effort: any failure returns null so callers never\n * break. Pure fetch + parse — caching belongs to the consumer.\n */\n\nimport * as cheerio from \"cheerio\";\nimport { timedFetch, getJson, cleanSection, formatFdaDate } from \"./fetch.js\";\nimport type { RegionLabel, LabelResult } 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 dosage_and_administration?: string[];\n boxed_warning?: string[];\n warnings_and_cautions?: string[];\n warnings?: string[];\n adverse_reactions?: string[];\n indications_and_usage?: string[];\n contraindications?: 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/* --------------------------- US: openFDA --------------------------- */\n\nexport async function getUspi(drug: string): Promise<RegionLabel | 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\n const data = await getJson<OpenFdaResponse>(url);\n const results = data?.results ?? [];\n if (results.length === 0) return null;\n\n // Prefer a single-ingredient product over combination labels.\n const ranked = results.slice().sort((a, b) => score(b, q) - score(a, q));\n const r = ranked[0];\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 const setId = r.openfda?.spl_set_id?.[0] ?? \"\";\n const brand = r.openfda?.brand_name?.[0] ?? \"\";\n const generic = r.openfda?.generic_name?.[0] ?? q;\n\n return {\n region: \"US\",\n authority: \"FDA Prescribing Information\",\n productName: brand ? `${brand} (${generic})` : generic,\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: setId\n ? `https://dailymed.nlm.nih.gov/dailymed/drugInfo.cfm?setid=${setId}`\n : \"https://dailymed.nlm.nih.gov/dailymed/\",\n sourceLabel: \"DailyMed (U.S. National Library of Medicine)\",\n };\n}\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/* --------------------------- UK/EU: eMC ---------------------------- */\n\ninterface EmcMatch {\n product_id: string;\n name: string;\n}\n\nasync function searchEmc(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\n// SmPC section number -> the field we store it under\nconst SMPC_WANT: Record<string, keyof Pick<RegionLabel, \"indications\" | \"dosage\" | \"contraindications\" | \"warnings\" | \"adverse\">> = {\n \"4.1\": \"indications\",\n \"4.2\": \"dosage\",\n \"4.3\": \"contraindications\",\n \"4.4\": \"warnings\",\n \"4.8\": \"adverse\",\n};\n\n// Canonical SmPC section titles — used as a fallback heading detector\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};\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\n/**\n * Parse the wanted SmPC sections from eMC HTML. Detect numbered headings, then\n * collect sibling content up to the next heading, with a parent-text fallback.\n */\nfunction parseSmpcSections(html: string): Record<string, string> {\n const $ = cheerio.load(html);\n const sectionElements: Array<{ code: string; title: string; element: any }> = [];\n\n // Strategy 1: headings whose text starts with a section number\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 // Strategy 2: fall back to scanning leaf nodes for known section headers\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 fields: Record<string, string> = {};\n for (let i = 0; i < sectionElements.length; i++) {\n const current = sectionElements[i];\n const field = SMPC_WANT[current.code];\n if (!field) continue;\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\n if (!content.trim()) {\n const parentHtml = $.html(current.element.parent());\n if (parentHtml) {\n content = htmlToText(parentHtml).replace(current.element.text().trim(), \"\").trim();\n }\n }\n\n fields[field] = cleanSection(content);\n }\n return fields;\n}\n\nexport async function getSmpc(drug: string): Promise<RegionLabel | null> {\n if (!drug) return null;\n try {\n const matches = await searchEmc(drug);\n if (matches.length === 0) return null;\n\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 const best = matches[0];\n\n const res = await timedFetch(`${EMC_BASE}/emc/product/${best.product_id}/smpc`);\n if (!res.ok) return null;\n const html = await res.text();\n const sec = parseSmpcSections(html);\n\n if (!sec.dosage && !sec.warnings && !sec.adverse && !sec.indications && !sec.contraindications)\n return null;\n\n return {\n region: \"UK/EU\",\n authority: \"Summary of Product Characteristics (SmPC)\",\n productName: best.name,\n indications: sec.indications || null,\n dosage: sec.dosage || null,\n contraindications: sec.contraindications || null,\n warnings: sec.warnings || null,\n adverse: sec.adverse || null,\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 labels for a drug. Each side is null if unavailable. Never throws. */\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/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":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -18,6 +18,27 @@ interface LabelResult {
18
18
  us: RegionLabel | null;
19
19
  uk: RegionLabel | null;
20
20
  }
21
+ /** One label section in general (all-section) access. */
22
+ interface LabelSection {
23
+ code: string;
24
+ title: string;
25
+ content: string;
26
+ }
27
+ /** General US label result (openFDA), all available sections. */
28
+ interface UsLabelResult {
29
+ drugName: string;
30
+ setId: string;
31
+ publishedDate: string;
32
+ sections: LabelSection[];
33
+ dailymedUrl: string;
34
+ }
35
+ /** General UK/EU SmPC result (eMC), all available sections. */
36
+ interface UkSmpcResult {
37
+ drugName: string;
38
+ productId: string;
39
+ sections: LabelSection[];
40
+ url: string;
41
+ }
21
42
  /** Deterministic brand <-> generic resolution result. */
22
43
  interface ResolveResult {
23
44
  inputType: "brand" | "generic";
@@ -61,13 +82,31 @@ declare function resolveName(name: string): Promise<ResolveResult | null>;
61
82
  * - US: FDA label via the openFDA API -> DailyMed citation
62
83
  * - UK/EU: SmPC via the eMC (medicines.org.uk) -> eMC citation
63
84
  *
64
- * Every network call is best-effort: any failure returns null so callers never
65
- * break. Pure fetch + parse caching belongs to the consumer.
85
+ * Two layers:
86
+ * - getUsLabel / getUkSmpc general, all-section access (LabelSection[]),
87
+ * used by PubCrawl's get_uspi / get_smpc / compare_labels tools.
88
+ * - getUspi / getSmpc / getLabels — a 5-field convenience view (RegionLabel)
89
+ * used by Pharmonym. Built on the same fetch + parse internals.
90
+ *
91
+ * Every network call is best-effort: failures return null. Pure fetch + parse;
92
+ * caching belongs to the consumer.
66
93
  */
67
94
 
95
+ /**
96
+ * General US label access — returns every available section (or the requested
97
+ * subset) as LabelSection[]. Powers PubCrawl's get_uspi.
98
+ */
99
+ declare function getUsLabel(drug: string, requested?: string[]): Promise<UsLabelResult | null>;
100
+ /** 5-field convenience view of the US label (Pharmonym). */
68
101
  declare function getUspi(drug: string): Promise<RegionLabel | null>;
102
+ /**
103
+ * General UK/EU SmPC access — returns every available section (or the requested
104
+ * subset) as LabelSection[]. Powers PubCrawl's get_smpc.
105
+ */
106
+ declare function getUkSmpc(drug: string, requested?: string[]): Promise<UkSmpcResult | null>;
107
+ /** 5-field convenience view of the UK/EU SmPC (Pharmonym). */
69
108
  declare function getSmpc(drug: string): Promise<RegionLabel | null>;
70
- /** Look up both labels for a drug. Each side is null if unavailable. Never throws. */
109
+ /** Look up both 5-field labels for a drug. Each side is null if unavailable. */
71
110
  declare function getLabels(drug: string): Promise<LabelResult>;
72
111
 
73
112
  /**
@@ -89,4 +128,4 @@ declare function compareLabels(drug: string): Promise<CompareResult>;
89
128
  declare function cleanSection(value: unknown, maxChars?: number): string;
90
129
  declare function formatFdaDate(yyyymmdd?: string): string;
91
130
 
92
- export { type CompareResult, type LabelComparison, type LabelResult, type Region, type RegionLabel, type ResolveResult, SECTION_MAP, cleanSection, compareLabels, formatFdaDate, getLabels, getSmpc, getUspi, resolveName };
131
+ export { type CompareResult, type LabelComparison, type LabelResult, type LabelSection, type Region, type RegionLabel, type ResolveResult, SECTION_MAP, type UkSmpcResult, type UsLabelResult, cleanSection, compareLabels, formatFdaDate, getLabels, getSmpc, getUkSmpc, getUsLabel, getUspi, resolveName };
package/dist/index.d.ts CHANGED
@@ -18,6 +18,27 @@ interface LabelResult {
18
18
  us: RegionLabel | null;
19
19
  uk: RegionLabel | null;
20
20
  }
21
+ /** One label section in general (all-section) access. */
22
+ interface LabelSection {
23
+ code: string;
24
+ title: string;
25
+ content: string;
26
+ }
27
+ /** General US label result (openFDA), all available sections. */
28
+ interface UsLabelResult {
29
+ drugName: string;
30
+ setId: string;
31
+ publishedDate: string;
32
+ sections: LabelSection[];
33
+ dailymedUrl: string;
34
+ }
35
+ /** General UK/EU SmPC result (eMC), all available sections. */
36
+ interface UkSmpcResult {
37
+ drugName: string;
38
+ productId: string;
39
+ sections: LabelSection[];
40
+ url: string;
41
+ }
21
42
  /** Deterministic brand <-> generic resolution result. */
22
43
  interface ResolveResult {
23
44
  inputType: "brand" | "generic";
@@ -61,13 +82,31 @@ declare function resolveName(name: string): Promise<ResolveResult | null>;
61
82
  * - US: FDA label via the openFDA API -> DailyMed citation
62
83
  * - UK/EU: SmPC via the eMC (medicines.org.uk) -> eMC citation
63
84
  *
64
- * Every network call is best-effort: any failure returns null so callers never
65
- * break. Pure fetch + parse caching belongs to the consumer.
85
+ * Two layers:
86
+ * - getUsLabel / getUkSmpc general, all-section access (LabelSection[]),
87
+ * used by PubCrawl's get_uspi / get_smpc / compare_labels tools.
88
+ * - getUspi / getSmpc / getLabels — a 5-field convenience view (RegionLabel)
89
+ * used by Pharmonym. Built on the same fetch + parse internals.
90
+ *
91
+ * Every network call is best-effort: failures return null. Pure fetch + parse;
92
+ * caching belongs to the consumer.
66
93
  */
67
94
 
95
+ /**
96
+ * General US label access — returns every available section (or the requested
97
+ * subset) as LabelSection[]. Powers PubCrawl's get_uspi.
98
+ */
99
+ declare function getUsLabel(drug: string, requested?: string[]): Promise<UsLabelResult | null>;
100
+ /** 5-field convenience view of the US label (Pharmonym). */
68
101
  declare function getUspi(drug: string): Promise<RegionLabel | null>;
102
+ /**
103
+ * General UK/EU SmPC access — returns every available section (or the requested
104
+ * subset) as LabelSection[]. Powers PubCrawl's get_smpc.
105
+ */
106
+ declare function getUkSmpc(drug: string, requested?: string[]): Promise<UkSmpcResult | null>;
107
+ /** 5-field convenience view of the UK/EU SmPC (Pharmonym). */
69
108
  declare function getSmpc(drug: string): Promise<RegionLabel | null>;
70
- /** Look up both labels for a drug. Each side is null if unavailable. Never throws. */
109
+ /** Look up both 5-field labels for a drug. Each side is null if unavailable. */
71
110
  declare function getLabels(drug: string): Promise<LabelResult>;
72
111
 
73
112
  /**
@@ -89,4 +128,4 @@ declare function compareLabels(drug: string): Promise<CompareResult>;
89
128
  declare function cleanSection(value: unknown, maxChars?: number): string;
90
129
  declare function formatFdaDate(yyyymmdd?: string): string;
91
130
 
92
- export { type CompareResult, type LabelComparison, type LabelResult, type Region, type RegionLabel, type ResolveResult, SECTION_MAP, cleanSection, compareLabels, formatFdaDate, getLabels, getSmpc, getUspi, resolveName };
131
+ export { type CompareResult, type LabelComparison, type LabelResult, type LabelSection, type Region, type RegionLabel, type ResolveResult, SECTION_MAP, type UkSmpcResult, type UsLabelResult, cleanSection, compareLabels, formatFdaDate, getLabels, getSmpc, getUkSmpc, getUsLabel, getUspi, resolveName };
package/dist/index.js CHANGED
@@ -180,7 +180,30 @@ async function resolveName(name) {
180
180
  import * as cheerio from "cheerio";
181
181
  var OPENFDA_URL = "https://api.fda.gov/drug/label.json";
182
182
  var EMC_BASE = "https://www.medicines.org.uk";
183
- async function getUspi(drug) {
183
+ var US_SECTION_MAP = [
184
+ { field: "boxed_warning", code: "34066-1", title: "Boxed Warning" },
185
+ { field: "indications_and_usage", code: "34067-9", title: "Indications and Usage" },
186
+ { field: "dosage_and_administration", code: "34068-7", title: "Dosage and Administration" },
187
+ { field: "dosage_forms_and_strengths", code: "43678-2", title: "Dosage Forms and Strengths" },
188
+ { field: "contraindications", code: "34070-3", title: "Contraindications" },
189
+ { field: "warnings_and_cautions", code: "43685-7", title: "Warnings and Precautions" },
190
+ { field: "warnings", code: "34071-1", title: "Warnings" },
191
+ { field: "adverse_reactions", code: "34084-4", title: "Adverse Reactions" },
192
+ { field: "drug_interactions", code: "34073-7", title: "Drug Interactions" },
193
+ { field: "use_in_specific_populations", code: "42228-7", title: "Use in Specific Populations" },
194
+ { field: "overdosage", code: "34088-5", title: "Overdosage" },
195
+ { field: "clinical_pharmacology", code: "34090-1", title: "Clinical Pharmacology" },
196
+ { field: "description", code: "34089-3", title: "Description" },
197
+ { field: "how_supplied", code: "34069-5", title: "How Supplied" }
198
+ ];
199
+ function score(r, q) {
200
+ const g = (r.openfda?.generic_name ?? []).join(", ").toLowerCase();
201
+ let s = 0;
202
+ if (g && g.split(/,| and /).length === 1) s += 2;
203
+ if (g.includes(q.toLowerCase())) s += 1;
204
+ return s;
205
+ }
206
+ async function fetchOpenFdaBest(drug) {
184
207
  if (!drug) return null;
185
208
  const q = drug.trim().replace(/"/g, "");
186
209
  const search = `(openfda.generic_name:"${q}"+OR+openfda.brand_name:"${q}")`;
@@ -188,8 +211,49 @@ async function getUspi(drug) {
188
211
  const data = await getJson(url);
189
212
  const results = data?.results ?? [];
190
213
  if (results.length === 0) return null;
191
- const ranked = results.slice().sort((a, b) => score(b, q) - score(a, q));
192
- const r = ranked[0];
214
+ return results.slice().sort((a, b) => score(b, q) - score(a, q))[0];
215
+ }
216
+ function usMeta(r, fallbackName) {
217
+ const setId = r.openfda?.spl_set_id?.[0] ?? "";
218
+ const brand = r.openfda?.brand_name?.[0] ?? "";
219
+ const generic = r.openfda?.generic_name?.[0] ?? fallbackName;
220
+ return {
221
+ setId,
222
+ brand,
223
+ generic,
224
+ productName: brand ? `${brand} (${generic})` : generic,
225
+ dailymedUrl: setId ? `https://dailymed.nlm.nih.gov/dailymed/drugInfo.cfm?setid=${setId}` : "https://dailymed.nlm.nih.gov/dailymed/"
226
+ };
227
+ }
228
+ function matchesUsFilter(entry, requested) {
229
+ return requested.some((req) => {
230
+ const r = req.toLowerCase().trim();
231
+ return entry.code === req || entry.title.toLowerCase().includes(r) || String(entry.field).replace(/_/g, " ").includes(r) || r.includes(entry.title.toLowerCase());
232
+ });
233
+ }
234
+ async function getUsLabel(drug, requested) {
235
+ const r = await fetchOpenFdaBest(drug);
236
+ if (!r) return null;
237
+ const meta = usMeta(r, drug.trim());
238
+ const sections = [];
239
+ for (const entry of US_SECTION_MAP) {
240
+ if (requested?.length && !matchesUsFilter(entry, requested)) continue;
241
+ const content = cleanSection(r[entry.field]);
242
+ if (content) sections.push({ code: entry.code, title: entry.title, content });
243
+ }
244
+ if (sections.length === 0) return null;
245
+ return {
246
+ drugName: meta.productName,
247
+ setId: meta.setId,
248
+ publishedDate: formatFdaDate(r.effective_time),
249
+ sections,
250
+ dailymedUrl: meta.dailymedUrl
251
+ };
252
+ }
253
+ async function getUspi(drug) {
254
+ const r = await fetchOpenFdaBest(drug);
255
+ if (!r) return null;
256
+ const meta = usMeta(r, drug.trim());
193
257
  const dosage = cleanSection(r.dosage_and_administration);
194
258
  const boxed = cleanSection(r.boxed_warning);
195
259
  const warn = cleanSection(r.warnings_and_cautions ?? r.warnings);
@@ -198,31 +262,21 @@ async function getUspi(drug) {
198
262
  const indications = cleanSection(r.indications_and_usage);
199
263
  const contraindications = cleanSection(r.contraindications);
200
264
  if (!dosage && !warnings && !adverse && !indications && !contraindications) return null;
201
- const setId = r.openfda?.spl_set_id?.[0] ?? "";
202
- const brand = r.openfda?.brand_name?.[0] ?? "";
203
- const generic = r.openfda?.generic_name?.[0] ?? q;
204
265
  return {
205
266
  region: "US",
206
267
  authority: "FDA Prescribing Information",
207
- productName: brand ? `${brand} (${generic})` : generic,
268
+ productName: meta.productName,
208
269
  indications: indications || null,
209
270
  dosage: dosage || null,
210
271
  contraindications: contraindications || null,
211
272
  warnings: warnings || null,
212
273
  adverse: adverse || null,
213
274
  updated: formatFdaDate(r.effective_time),
214
- sourceUrl: setId ? `https://dailymed.nlm.nih.gov/dailymed/drugInfo.cfm?setid=${setId}` : "https://dailymed.nlm.nih.gov/dailymed/",
275
+ sourceUrl: meta.dailymedUrl,
215
276
  sourceLabel: "DailyMed (U.S. National Library of Medicine)"
216
277
  };
217
278
  }
218
- function score(r, q) {
219
- const g = (r.openfda?.generic_name ?? []).join(", ").toLowerCase();
220
- let s = 0;
221
- if (g && g.split(/,| and /).length === 1) s += 2;
222
- if (g.includes(q.toLowerCase())) s += 1;
223
- return s;
224
- }
225
- async function searchEmc(drug) {
279
+ async function searchEmcAll(drug) {
226
280
  const url = `${EMC_BASE}/emc/search?${new URLSearchParams({ q: drug })}`;
227
281
  const res = await timedFetch(url);
228
282
  if (!res.ok) return [];
@@ -240,13 +294,15 @@ async function searchEmc(drug) {
240
294
  });
241
295
  return out;
242
296
  }
243
- var SMPC_WANT = {
244
- "4.1": "indications",
245
- "4.2": "dosage",
246
- "4.3": "contraindications",
247
- "4.4": "warnings",
248
- "4.8": "adverse"
249
- };
297
+ async function searchEmcBest(drug) {
298
+ const matches = await searchEmcAll(drug);
299
+ if (matches.length === 0) return null;
300
+ const q = drug.toLowerCase();
301
+ matches.sort(
302
+ (a, b) => (b.name.toLowerCase().includes(q) ? 1 : 0) - (a.name.toLowerCase().includes(q) ? 1 : 0)
303
+ );
304
+ return matches[0];
305
+ }
250
306
  var SMPC_SECTION_NAMES = {
251
307
  "4.1": "Therapeutic indications",
252
308
  "4.2": "Posology and method of administration",
@@ -259,7 +315,13 @@ var SMPC_SECTION_NAMES = {
259
315
  "4.9": "Overdose",
260
316
  "5.1": "Pharmacodynamic properties",
261
317
  "5.2": "Pharmacokinetic properties",
262
- "5.3": "Preclinical safety data"
318
+ "5.3": "Preclinical safety data",
319
+ "6.1": "List of excipients",
320
+ "6.2": "Incompatibilities",
321
+ "6.3": "Shelf life",
322
+ "6.4": "Special precautions for storage",
323
+ "6.5": "Nature and contents of container",
324
+ "6.6": "Special precautions for disposal"
263
325
  };
264
326
  function htmlToText(html) {
265
327
  const $ = cheerio.load(html);
@@ -273,7 +335,25 @@ function htmlToText(html) {
273
335
  });
274
336
  return $.text().replace(/\n{3,}/g, "\n\n").trim();
275
337
  }
276
- function parseSmpcSections(html) {
338
+ function matchesSmpcFilter(code, title, requested) {
339
+ return requested.some((req) => {
340
+ const r = req.toLowerCase().trim();
341
+ if (code === req || code === r) return true;
342
+ if (title.toLowerCase().includes(r)) return true;
343
+ if (r.includes("indication")) return code === "4.1";
344
+ if (r.includes("dosage") || r.includes("dosing") || r.includes("posology")) return code === "4.2";
345
+ if (r.includes("contraindication")) return code === "4.3";
346
+ if (r.includes("warning") || r.includes("precaution")) return code === "4.4";
347
+ if (r.includes("interaction")) return code === "4.5";
348
+ if (r.includes("pregnancy") || r.includes("fertility")) return code === "4.6";
349
+ if (r.includes("adverse") || r.includes("undesirable") || r.includes("side effect")) return code === "4.8";
350
+ if (r.includes("overdose") || r.includes("overdosage")) return code === "4.9";
351
+ if (r.includes("pharmacodynamic")) return code === "5.1";
352
+ if (r.includes("pharmacokinetic")) return code === "5.2";
353
+ return false;
354
+ });
355
+ }
356
+ function parseSmpcSections(html, requested) {
277
357
  const $ = cheerio.load(html);
278
358
  const sectionElements = [];
279
359
  $("[id*='SECTION'], [id*='section'], .sectionHeading, h2, h3, h4").each((_, el) => {
@@ -292,11 +372,9 @@ function parseSmpcSections(html) {
292
372
  }
293
373
  });
294
374
  }
295
- const fields = {};
375
+ const out = [];
296
376
  for (let i = 0; i < sectionElements.length; i++) {
297
377
  const current = sectionElements[i];
298
- const field = SMPC_WANT[current.code];
299
- if (!field) continue;
300
378
  const next = sectionElements[i + 1];
301
379
  let content = "";
302
380
  let node = current.element.next();
@@ -308,39 +386,59 @@ function parseSmpcSections(html) {
308
386
  }
309
387
  if (!content.trim()) {
310
388
  const parentHtml = $.html(current.element.parent());
311
- if (parentHtml) {
312
- content = htmlToText(parentHtml).replace(current.element.text().trim(), "").trim();
313
- }
389
+ if (parentHtml) content = htmlToText(parentHtml).replace(current.element.text().trim(), "").trim();
314
390
  }
315
- fields[field] = cleanSection(content);
391
+ const cleaned = cleanSection(content);
392
+ if (cleaned) out.push({ code: current.code, title: current.title, content: cleaned });
393
+ }
394
+ if (requested?.length) {
395
+ return out.filter((s) => matchesSmpcFilter(s.code, s.title, requested));
396
+ }
397
+ return out;
398
+ }
399
+ async function getUkSmpc(drug, requested) {
400
+ if (!drug) return null;
401
+ try {
402
+ const best = await searchEmcBest(drug);
403
+ if (!best) return null;
404
+ const res = await timedFetch(`${EMC_BASE}/emc/product/${best.product_id}/smpc`);
405
+ if (!res.ok) return null;
406
+ const sections = parseSmpcSections(await res.text(), requested);
407
+ if (sections.length === 0) return null;
408
+ return {
409
+ drugName: best.name,
410
+ productId: best.product_id,
411
+ sections,
412
+ url: `${EMC_BASE}/emc/product/${best.product_id}/smpc`
413
+ };
414
+ } catch {
415
+ return null;
316
416
  }
317
- return fields;
318
417
  }
319
418
  async function getSmpc(drug) {
320
419
  if (!drug) return null;
321
420
  try {
322
- const matches = await searchEmc(drug);
323
- if (matches.length === 0) return null;
324
- const q = drug.toLowerCase();
325
- matches.sort(
326
- (a, b) => (b.name.toLowerCase().includes(q) ? 1 : 0) - (a.name.toLowerCase().includes(q) ? 1 : 0)
327
- );
328
- const best = matches[0];
421
+ const best = await searchEmcBest(drug);
422
+ if (!best) return null;
329
423
  const res = await timedFetch(`${EMC_BASE}/emc/product/${best.product_id}/smpc`);
330
424
  if (!res.ok) return null;
331
- const html = await res.text();
332
- const sec = parseSmpcSections(html);
333
- if (!sec.dosage && !sec.warnings && !sec.adverse && !sec.indications && !sec.contraindications)
334
- return null;
425
+ const all = parseSmpcSections(await res.text());
426
+ const pick = (code) => all.find((s) => s.code === code)?.content ?? null;
427
+ const indications = pick("4.1");
428
+ const dosage = pick("4.2");
429
+ const contraindications = pick("4.3");
430
+ const warnings = pick("4.4");
431
+ const adverse = pick("4.8");
432
+ if (!dosage && !warnings && !adverse && !indications && !contraindications) return null;
335
433
  return {
336
434
  region: "UK/EU",
337
435
  authority: "Summary of Product Characteristics (SmPC)",
338
436
  productName: best.name,
339
- indications: sec.indications || null,
340
- dosage: sec.dosage || null,
341
- contraindications: sec.contraindications || null,
342
- warnings: sec.warnings || null,
343
- adverse: sec.adverse || null,
437
+ indications,
438
+ dosage,
439
+ contraindications,
440
+ warnings,
441
+ adverse,
344
442
  updated: "",
345
443
  sourceUrl: `${EMC_BASE}/emc/product/${best.product_id}/smpc`,
346
444
  sourceLabel: "electronic medicines compendium (emc), UK"
@@ -386,6 +484,8 @@ export {
386
484
  formatFdaDate,
387
485
  getLabels,
388
486
  getSmpc,
487
+ getUkSmpc,
488
+ getUsLabel,
389
489
  getUspi,
390
490
  resolveName
391
491
  };
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 * Every network call is best-effort: any failure returns null so callers never\n * break. Pure fetch + parse — caching belongs to the consumer.\n */\n\nimport * as cheerio from \"cheerio\";\nimport { timedFetch, getJson, cleanSection, formatFdaDate } from \"./fetch.js\";\nimport type { RegionLabel, LabelResult } 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 dosage_and_administration?: string[];\n boxed_warning?: string[];\n warnings_and_cautions?: string[];\n warnings?: string[];\n adverse_reactions?: string[];\n indications_and_usage?: string[];\n contraindications?: 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/* --------------------------- US: openFDA --------------------------- */\n\nexport async function getUspi(drug: string): Promise<RegionLabel | 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\n const data = await getJson<OpenFdaResponse>(url);\n const results = data?.results ?? [];\n if (results.length === 0) return null;\n\n // Prefer a single-ingredient product over combination labels.\n const ranked = results.slice().sort((a, b) => score(b, q) - score(a, q));\n const r = ranked[0];\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 const setId = r.openfda?.spl_set_id?.[0] ?? \"\";\n const brand = r.openfda?.brand_name?.[0] ?? \"\";\n const generic = r.openfda?.generic_name?.[0] ?? q;\n\n return {\n region: \"US\",\n authority: \"FDA Prescribing Information\",\n productName: brand ? `${brand} (${generic})` : generic,\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: setId\n ? `https://dailymed.nlm.nih.gov/dailymed/drugInfo.cfm?setid=${setId}`\n : \"https://dailymed.nlm.nih.gov/dailymed/\",\n sourceLabel: \"DailyMed (U.S. National Library of Medicine)\",\n };\n}\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/* --------------------------- UK/EU: eMC ---------------------------- */\n\ninterface EmcMatch {\n product_id: string;\n name: string;\n}\n\nasync function searchEmc(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\n// SmPC section number -> the field we store it under\nconst SMPC_WANT: Record<string, keyof Pick<RegionLabel, \"indications\" | \"dosage\" | \"contraindications\" | \"warnings\" | \"adverse\">> = {\n \"4.1\": \"indications\",\n \"4.2\": \"dosage\",\n \"4.3\": \"contraindications\",\n \"4.4\": \"warnings\",\n \"4.8\": \"adverse\",\n};\n\n// Canonical SmPC section titles — used as a fallback heading detector\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};\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\n/**\n * Parse the wanted SmPC sections from eMC HTML. Detect numbered headings, then\n * collect sibling content up to the next heading, with a parent-text fallback.\n */\nfunction parseSmpcSections(html: string): Record<string, string> {\n const $ = cheerio.load(html);\n const sectionElements: Array<{ code: string; title: string; element: any }> = [];\n\n // Strategy 1: headings whose text starts with a section number\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 // Strategy 2: fall back to scanning leaf nodes for known section headers\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 fields: Record<string, string> = {};\n for (let i = 0; i < sectionElements.length; i++) {\n const current = sectionElements[i];\n const field = SMPC_WANT[current.code];\n if (!field) continue;\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\n if (!content.trim()) {\n const parentHtml = $.html(current.element.parent());\n if (parentHtml) {\n content = htmlToText(parentHtml).replace(current.element.text().trim(), \"\").trim();\n }\n }\n\n fields[field] = cleanSection(content);\n }\n return fields;\n}\n\nexport async function getSmpc(drug: string): Promise<RegionLabel | null> {\n if (!drug) return null;\n try {\n const matches = await searchEmc(drug);\n if (matches.length === 0) return null;\n\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 const best = matches[0];\n\n const res = await timedFetch(`${EMC_BASE}/emc/product/${best.product_id}/smpc`);\n if (!res.ok) return null;\n const html = await res.text();\n const sec = parseSmpcSections(html);\n\n if (!sec.dosage && !sec.warnings && !sec.adverse && !sec.indications && !sec.contraindications)\n return null;\n\n return {\n region: \"UK/EU\",\n authority: \"Summary of Product Characteristics (SmPC)\",\n productName: best.name,\n indications: sec.indications || null,\n dosage: sec.dosage || null,\n contraindications: sec.contraindications || null,\n warnings: sec.warnings || null,\n adverse: sec.adverse || null,\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 labels for a drug. Each side is null if unavailable. Never throws. */\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 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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pharmatools/drug-data",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
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": {