heor-agent-mcp 1.9.2 → 1.10.2
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 +2 -2
- package/dist/analytics.d.ts +18 -4
- package/dist/analytics.d.ts.map +1 -1
- package/dist/analytics.js +22 -6
- package/dist/analytics.js.map +1 -1
- package/dist/providers/regulatory/ageRangeParser.d.ts +13 -0
- package/dist/providers/regulatory/ageRangeParser.d.ts.map +1 -0
- package/dist/providers/regulatory/ageRangeParser.js +78 -0
- package/dist/providers/regulatory/ageRangeParser.js.map +1 -0
- package/dist/providers/regulatory/autoCheck.d.ts +31 -0
- package/dist/providers/regulatory/autoCheck.d.ts.map +1 -0
- package/dist/providers/regulatory/autoCheck.js +93 -0
- package/dist/providers/regulatory/autoCheck.js.map +1 -0
- package/dist/providers/regulatory/cache.d.ts +37 -0
- package/dist/providers/regulatory/cache.d.ts.map +1 -0
- package/dist/providers/regulatory/cache.js +51 -0
- package/dist/providers/regulatory/cache.js.map +1 -0
- package/dist/providers/regulatory/dailymed.d.ts +34 -0
- package/dist/providers/regulatory/dailymed.d.ts.map +1 -0
- package/dist/providers/regulatory/dailymed.js +60 -0
- package/dist/providers/regulatory/dailymed.js.map +1 -0
- package/dist/providers/regulatory/drugNameNormaliser.d.ts +29 -0
- package/dist/providers/regulatory/drugNameNormaliser.d.ts.map +1 -0
- package/dist/providers/regulatory/drugNameNormaliser.js +186 -0
- package/dist/providers/regulatory/drugNameNormaliser.js.map +1 -0
- package/dist/providers/regulatory/emaEpi.d.ts +58 -0
- package/dist/providers/regulatory/emaEpi.d.ts.map +1 -0
- package/dist/providers/regulatory/emaEpi.js +105 -0
- package/dist/providers/regulatory/emaEpi.js.map +1 -0
- package/dist/providers/regulatory/index.d.ts +12 -0
- package/dist/providers/regulatory/index.d.ts.map +1 -0
- package/dist/providers/regulatory/index.js +12 -0
- package/dist/providers/regulatory/index.js.map +1 -0
- package/dist/providers/regulatory/openfda.d.ts +54 -0
- package/dist/providers/regulatory/openfda.d.ts.map +1 -0
- package/dist/providers/regulatory/openfda.js +216 -0
- package/dist/providers/regulatory/openfda.js.map +1 -0
- package/dist/providers/regulatory/types.d.ts +65 -0
- package/dist/providers/regulatory/types.d.ts.map +1 -0
- package/dist/providers/regulatory/types.js +8 -0
- package/dist/providers/regulatory/types.js.map +1 -0
- package/dist/providers/types.d.ts +2 -0
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/server.js +23 -4
- package/dist/server.js.map +1 -1
- package/dist/tools/costEffectivenessModel.d.ts +30 -0
- package/dist/tools/costEffectivenessModel.d.ts.map +1 -1
- package/dist/tools/costEffectivenessModel.js +43 -1
- package/dist/tools/costEffectivenessModel.js.map +1 -1
- package/dist/tools/evidenceUnmetNeed.d.ts +40 -2
- package/dist/tools/evidenceUnmetNeed.d.ts.map +1 -1
- package/dist/tools/evidenceUnmetNeed.js +186 -12
- package/dist/tools/evidenceUnmetNeed.js.map +1 -1
- package/dist/tools/htaDossierPrep.d.ts.map +1 -1
- package/dist/tools/htaDossierPrep.js +97 -0
- package/dist/tools/htaDossierPrep.js.map +1 -1
- package/dist/tools/htaWorkflow.d.ts +44 -0
- package/dist/tools/htaWorkflow.d.ts.map +1 -1
- package/dist/tools/htaWorkflow.js +113 -1
- package/dist/tools/htaWorkflow.js.map +1 -1
- package/dist/tools/regulatoryStatusCheck.d.ts +60 -0
- package/dist/tools/regulatoryStatusCheck.d.ts.map +1 -0
- package/dist/tools/regulatoryStatusCheck.js +418 -0
- package/dist/tools/regulatoryStatusCheck.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ curl -s http://localhost:8080/health
|
|
|
45
45
|
Expected output:
|
|
46
46
|
|
|
47
47
|
```json
|
|
48
|
-
{"status":"ok","server":"heor-agent-mcp","version":"1.
|
|
48
|
+
{"status":"ok","server":"heor-agent-mcp","version":"1.10.2"}
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
✅ If you see the JSON above, the npm package works on your machine. Any further issues are in your MCP client config (Claude Desktop / Cursor / Continue), not the server.
|
|
@@ -126,7 +126,7 @@ The first prompt exercises `literature_search` + `validate_links` (free, no API
|
|
|
126
126
|
|
|
127
127
|
## What's new
|
|
128
128
|
|
|
129
|
-
See [CHANGELOG.md](./CHANGELOG.md) for full version history. Current: **v1.
|
|
129
|
+
See [CHANGELOG.md](./CHANGELOG.md) for full version history. Current: **v1.10.2** (28 tools, 44 data sources).
|
|
130
130
|
|
|
131
131
|
### v1.0.4 highlights (still in v1.6.3)
|
|
132
132
|
|
package/dist/analytics.d.ts
CHANGED
|
@@ -9,17 +9,31 @@ export type ClientSurface = "claude_anthropic_web" | "chatgpt_adapter" | "claude
|
|
|
9
9
|
export declare function inferSurface(clientName: string | undefined): ClientSurface;
|
|
10
10
|
export declare function trackEvent(event: string, properties?: Record<string, unknown>, sessionId?: string): void;
|
|
11
11
|
/**
|
|
12
|
-
* Extracts structured analytics properties from a thrown value.
|
|
13
|
-
* returns the same two property names so PostHog dashboards can query
|
|
14
|
-
* `properties.error_class` and `properties.error_message` reliably.
|
|
12
|
+
* Extracts structured analytics properties from a thrown value.
|
|
15
13
|
*
|
|
16
|
-
*
|
|
14
|
+
* Returns THREE fields so callers can pick the right one for each surface:
|
|
15
|
+
* - `error_class` — constructor/category name, stable for dashboards
|
|
16
|
+
* - `error_message` — FULL message, no truncation. Use this for the
|
|
17
|
+
* client-facing response so callers (including the
|
|
18
|
+
* ChatGPT Custom GPT) see the complete validation
|
|
19
|
+
* feedback and can recover in-conversation.
|
|
20
|
+
* - `telemetry_message` — same message capped at 500 chars. Use this for
|
|
21
|
+
* PostHog `properties.error_message` so we don't
|
|
22
|
+
* blow up event payload size on a 50KB ZodError.
|
|
23
|
+
*
|
|
24
|
+
* Why we split these (added v1.10.2): pre-v1.10.2 the capped field doubled
|
|
25
|
+
* as the client response (server.ts:475), so ChatGPT received a JSON
|
|
26
|
+
* ZodError truncated mid-key and could not recover. The 500-char cap was
|
|
27
|
+
* a telemetry-hygiene decision that quietly broke the response surface.
|
|
28
|
+
*
|
|
29
|
+
* - Error subclasses → constructor name + full message
|
|
17
30
|
* - Strings / numbers / null → class="unknown", message=stringified
|
|
18
31
|
* - Circular objects / weird values → class="unknown", message=safe stringify
|
|
19
32
|
*/
|
|
20
33
|
export declare function classifyToolError(err: unknown): {
|
|
21
34
|
error_class: string;
|
|
22
35
|
error_message: string;
|
|
36
|
+
telemetry_message: string;
|
|
23
37
|
};
|
|
24
38
|
export declare function trackToolCall(toolName: string, durationMs: number, status: "ok" | "error", sessionId?: string, properties?: Record<string, unknown>): void;
|
|
25
39
|
export declare function trackSession(event: "session_start" | "session_end", sessionId: string, properties?: Record<string, unknown>): void;
|
package/dist/analytics.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GACrB,sBAAsB,GACtB,iBAAiB,GACjB,gBAAgB,GAChB,UAAU,GACV,OAAO,GACP,UAAU,GACV,YAAY,CAAC;AAEjB,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,CAe1E;AAgBD,wBAAgB,UAAU,CACxB,KAAK,EAAE,MAAM,EACb,UAAU,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACxC,SAAS,CAAC,EAAE,MAAM,QAanB;AAED
|
|
1
|
+
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GACrB,sBAAsB,GACtB,iBAAiB,GACjB,gBAAgB,GAChB,UAAU,GACV,OAAO,GACP,UAAU,GACV,YAAY,CAAC;AAEjB,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,CAe1E;AAgBD,wBAAgB,UAAU,CACxB,KAAK,EAAE,MAAM,EACb,UAAU,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACxC,SAAS,CAAC,EAAE,MAAM,QAanB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAoBA;AAED,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,IAAI,GAAG,OAAO,EACtB,SAAS,CAAC,EAAE,MAAM,EAClB,UAAU,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,QAYzC;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,eAAe,GAAG,aAAa,EACtC,SAAS,EAAE,MAAM,EACjB,UAAU,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,QAGzC;AAED,wBAAsB,iBAAiB,kBAKtC"}
|
package/dist/analytics.js
CHANGED
|
@@ -47,19 +47,34 @@ export function trackEvent(event, properties = {}, sessionId) {
|
|
|
47
47
|
});
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
|
-
* Extracts structured analytics properties from a thrown value.
|
|
51
|
-
* returns the same two property names so PostHog dashboards can query
|
|
52
|
-
* `properties.error_class` and `properties.error_message` reliably.
|
|
50
|
+
* Extracts structured analytics properties from a thrown value.
|
|
53
51
|
*
|
|
54
|
-
*
|
|
52
|
+
* Returns THREE fields so callers can pick the right one for each surface:
|
|
53
|
+
* - `error_class` — constructor/category name, stable for dashboards
|
|
54
|
+
* - `error_message` — FULL message, no truncation. Use this for the
|
|
55
|
+
* client-facing response so callers (including the
|
|
56
|
+
* ChatGPT Custom GPT) see the complete validation
|
|
57
|
+
* feedback and can recover in-conversation.
|
|
58
|
+
* - `telemetry_message` — same message capped at 500 chars. Use this for
|
|
59
|
+
* PostHog `properties.error_message` so we don't
|
|
60
|
+
* blow up event payload size on a 50KB ZodError.
|
|
61
|
+
*
|
|
62
|
+
* Why we split these (added v1.10.2): pre-v1.10.2 the capped field doubled
|
|
63
|
+
* as the client response (server.ts:475), so ChatGPT received a JSON
|
|
64
|
+
* ZodError truncated mid-key and could not recover. The 500-char cap was
|
|
65
|
+
* a telemetry-hygiene decision that quietly broke the response surface.
|
|
66
|
+
*
|
|
67
|
+
* - Error subclasses → constructor name + full message
|
|
55
68
|
* - Strings / numbers / null → class="unknown", message=stringified
|
|
56
69
|
* - Circular objects / weird values → class="unknown", message=safe stringify
|
|
57
70
|
*/
|
|
58
71
|
export function classifyToolError(err) {
|
|
59
72
|
if (err instanceof Error) {
|
|
73
|
+
const full = err.message ?? "";
|
|
60
74
|
return {
|
|
61
75
|
error_class: err.constructor?.name ?? "Error",
|
|
62
|
-
error_message:
|
|
76
|
+
error_message: full,
|
|
77
|
+
telemetry_message: full.slice(0, 500),
|
|
63
78
|
};
|
|
64
79
|
}
|
|
65
80
|
let message;
|
|
@@ -71,7 +86,8 @@ export function classifyToolError(err) {
|
|
|
71
86
|
}
|
|
72
87
|
return {
|
|
73
88
|
error_class: "unknown",
|
|
74
|
-
error_message: message
|
|
89
|
+
error_message: message,
|
|
90
|
+
telemetry_message: message.slice(0, 500),
|
|
75
91
|
};
|
|
76
92
|
}
|
|
77
93
|
export function trackToolCall(toolName, durationMs, status, sessionId, properties = {}) {
|
package/dist/analytics.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAkBvC,MAAM,UAAU,YAAY,CAAC,UAA8B;IACzD,IAAI,CAAC,UAAU;QAAE,OAAO,YAAY,CAAC;IACrC,MAAM,CAAC,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC;IACnC,IAAI,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC;QAAE,OAAO,sBAAsB,CAAC;IAC/D,IAAI,CAAC,CAAC,UAAU,CAAC,iBAAiB,CAAC;QAAE,OAAO,iBAAiB,CAAC;IAC9D,IACE,CAAC,KAAK,QAAQ;QACd,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC;QACzB,CAAC,CAAC,UAAU,CAAC,gBAAgB,CAAC;QAE9B,OAAO,gBAAgB,CAAC;IAC1B,IAAI,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,UAAU,CAAC;IAChD,IAAI,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAC;IAC1C,IAAI,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,UAAU,CAAC;IAChD,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,IAAI,MAAM,GAAmB,IAAI,CAAC;AAElC,SAAS,SAAS;IAChB,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IACxC,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,MAAM,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE;QACxB,IAAI,EAAE,0BAA0B;QAChC,OAAO,EAAE,CAAC;QACV,aAAa,EAAE,KAAK;KACrB,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,UAAU,CACxB,KAAa,EACb,aAAsC,EAAE,EACxC,SAAkB;IAElB,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;IACvB,IAAI,CAAC,EAAE;QAAE,OAAO;IAEhB,EAAE,CAAC,OAAO,CAAC;QACT,UAAU,EAAE,SAAS,IAAI,WAAW;QACpC,KAAK;QACL,UAAU,EAAE;YACV,cAAc,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,SAAS;YAC5D,GAAG,UAAU;SACd;KACF,CAAC,CAAC;AACL,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort parser for label population text.
|
|
3
|
+
*
|
|
4
|
+
* Extracts { age_min_years, age_max_years, weight_constraint_kg, sex_constraint }
|
|
5
|
+
* from verbatim label text like:
|
|
6
|
+
* "pediatric patients aged 6 to 17 years weighing 45 kg or more"
|
|
7
|
+
*
|
|
8
|
+
* All fields are nullable — unparseable text returns all nulls.
|
|
9
|
+
* The caller always retains the verbatim population text.
|
|
10
|
+
*/
|
|
11
|
+
import type { PopulationParsed } from "./types.js";
|
|
12
|
+
export declare function parseAgeRange(text: string): PopulationParsed;
|
|
13
|
+
//# sourceMappingURL=ageRangeParser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ageRangeParser.d.ts","sourceRoot":"","sources":["../../../src/providers/regulatory/ageRangeParser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAqD5D"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort parser for label population text.
|
|
3
|
+
*
|
|
4
|
+
* Extracts { age_min_years, age_max_years, weight_constraint_kg, sex_constraint }
|
|
5
|
+
* from verbatim label text like:
|
|
6
|
+
* "pediatric patients aged 6 to 17 years weighing 45 kg or more"
|
|
7
|
+
*
|
|
8
|
+
* All fields are nullable — unparseable text returns all nulls.
|
|
9
|
+
* The caller always retains the verbatim population text.
|
|
10
|
+
*/
|
|
11
|
+
export function parseAgeRange(text) {
|
|
12
|
+
const result = {
|
|
13
|
+
age_min_years: null,
|
|
14
|
+
age_max_years: null,
|
|
15
|
+
weight_constraint_kg: null,
|
|
16
|
+
sex_constraint: null,
|
|
17
|
+
};
|
|
18
|
+
if (!text || typeof text !== "string")
|
|
19
|
+
return result;
|
|
20
|
+
const lower = text.toLowerCase();
|
|
21
|
+
// Sex constraint detection
|
|
22
|
+
if (/\b(females?|women|woman|premenopausal|postmenopausal)\b/.test(lower)) {
|
|
23
|
+
result.sex_constraint = "female_only";
|
|
24
|
+
}
|
|
25
|
+
else if (/\b(males?|men|man)\b/.test(lower)) {
|
|
26
|
+
result.sex_constraint = "male_only";
|
|
27
|
+
}
|
|
28
|
+
// Age range: "aged X to Y years" or "X to Y years"
|
|
29
|
+
const rangeMatch = text.match(/(?:aged?\s+)?(\d+)\s+to\s+(\d+)\s+years?/i);
|
|
30
|
+
if (rangeMatch) {
|
|
31
|
+
result.age_min_years = parseInt(rangeMatch[1], 10);
|
|
32
|
+
result.age_max_years = parseInt(rangeMatch[2], 10);
|
|
33
|
+
return _extractWeight(result, text);
|
|
34
|
+
}
|
|
35
|
+
// Minimum age patterns:
|
|
36
|
+
// "18 years of age or older"
|
|
37
|
+
// "6 years of age and older"
|
|
38
|
+
// "≥12 years" or ">= 12 years"
|
|
39
|
+
// "at least 12 years"
|
|
40
|
+
// "12 years and older"
|
|
41
|
+
const minAgePatterns = [
|
|
42
|
+
/(?:aged?\s+)?(\d+)\s+years?\s+(?:of\s+age\s+)?(?:and|or)\s+older/i,
|
|
43
|
+
/(?:aged?\s+)?(\d+)\s+years?\s+(?:of\s+age\s+)?(?:and|or)\s+above/i,
|
|
44
|
+
/(?:at\s+least\s+)?[≥>=]+\s*(\d+)\s+years?/i,
|
|
45
|
+
/patients?\s+(\d+)\s+years?\s+of\s+age\s+or\s+older/i,
|
|
46
|
+
/(?:adults?\s+)?\((\d+)\s+years?\s+and\s+older\)/i,
|
|
47
|
+
/(?:at\s+least\s+)(\d+)\s+years?\s+of\s+age/i,
|
|
48
|
+
/(?:aged?\s+)(\d+)\s+years?\s+(?:and\s+)?(?:and\s+)?older/i,
|
|
49
|
+
/\b(\d+)\s+years?\s+of\s+age\s+(?:and|or)\s+older/i,
|
|
50
|
+
];
|
|
51
|
+
for (const pattern of minAgePatterns) {
|
|
52
|
+
const m = text.match(pattern);
|
|
53
|
+
if (m) {
|
|
54
|
+
result.age_min_years = parseInt(m[1], 10);
|
|
55
|
+
return _extractWeight(result, text);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return _extractWeight(result, text);
|
|
59
|
+
}
|
|
60
|
+
function _extractWeight(result, text) {
|
|
61
|
+
// Weight patterns: "weighing 45 kg or more", "at least 30 kg body weight",
|
|
62
|
+
// "body weight ≥ 45 kg"
|
|
63
|
+
const weightPatterns = [
|
|
64
|
+
/weighing\s+(\d+(?:\.\d+)?)\s*kg/i,
|
|
65
|
+
/at\s+least\s+(\d+(?:\.\d+)?)\s*kg/i,
|
|
66
|
+
/[≥>=]+\s*(\d+(?:\.\d+)?)\s*kg/i,
|
|
67
|
+
/(\d+(?:\.\d+)?)\s*kg\s+or\s+(?:more|greater)/i,
|
|
68
|
+
];
|
|
69
|
+
for (const pattern of weightPatterns) {
|
|
70
|
+
const m = text.match(pattern);
|
|
71
|
+
if (m) {
|
|
72
|
+
result.weight_constraint_kg = parseFloat(m[1]);
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=ageRangeParser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ageRangeParser.js","sourceRoot":"","sources":["../../../src/providers/regulatory/ageRangeParser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,MAAM,GAAqB;QAC/B,aAAa,EAAE,IAAI;QACnB,aAAa,EAAE,IAAI;QACnB,oBAAoB,EAAE,IAAI;QAC1B,cAAc,EAAE,IAAI;KACrB,CAAC;IAEF,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC;IAErD,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAEjC,2BAA2B;IAC3B,IAAI,yDAAyD,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1E,MAAM,CAAC,cAAc,GAAG,aAAa,CAAC;IACxC,CAAC;SAAM,IAAI,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9C,MAAM,CAAC,cAAc,GAAG,WAAW,CAAC;IACtC,CAAC;IAED,mDAAmD;IACnD,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC3E,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,CAAC,aAAa,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACnD,MAAM,CAAC,aAAa,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACnD,OAAO,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACtC,CAAC;IAED,wBAAwB;IACxB,6BAA6B;IAC7B,6BAA6B;IAC7B,+BAA+B;IAC/B,sBAAsB;IACtB,uBAAuB;IACvB,MAAM,cAAc,GAAG;QACrB,mEAAmE;QACnE,mEAAmE;QACnE,4CAA4C;QAC5C,qDAAqD;QACrD,kDAAkD;QAClD,6CAA6C;QAC7C,2DAA2D;QAC3D,mDAAmD;KACpD,CAAC;IAEF,KAAK,MAAM,OAAO,IAAI,cAAc,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9B,IAAI,CAAC,EAAE,CAAC;YACN,MAAM,CAAC,aAAa,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC1C,OAAO,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IAED,OAAO,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,cAAc,CACrB,MAAwB,EACxB,IAAY;IAEZ,2EAA2E;IAC3E,0BAA0B;IAC1B,MAAM,cAAc,GAAG;QACrB,kCAAkC;QAClC,oCAAoC;QACpC,gCAAgC;QAChC,+CAA+C;KAChD,CAAC;IAEF,KAAK,MAAM,OAAO,IAAI,cAAc,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9B,IAAI,CAAC,EAAE,CAAC;YACN,MAAM,CAAC,oBAAoB,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC/C,MAAM;QACR,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fan-out helper for auto-wiring regulatory.status_check into other tools.
|
|
3
|
+
*
|
|
4
|
+
* Design log #26. Used by evidence.unmet_need and hta_workflow Phase 3.6.
|
|
5
|
+
*
|
|
6
|
+
* Concurrency: up to 8 parallel calls (OpenFDA rate-limit guard).
|
|
7
|
+
* Cache: uses the shared 24h module-level cache inside regulatoryStatusCheck.ts.
|
|
8
|
+
* Never throws: each call is wrapped — one failure never aborts the batch.
|
|
9
|
+
*
|
|
10
|
+
* CRITICAL CYCLE-SAFETY: This module MUST NOT import from
|
|
11
|
+
* tools/evidenceUnmetNeed.ts or tools/htaWorkflow.ts (would create a cycle).
|
|
12
|
+
* Only imports from tools/regulatoryStatusCheck.ts (which owns no upstream deps).
|
|
13
|
+
*/
|
|
14
|
+
import type { RegulatoryStatusResult } from "./types.js";
|
|
15
|
+
export interface AutoCheckRequest {
|
|
16
|
+
drug: string;
|
|
17
|
+
region: "us" | "eu" | "uk" | "global";
|
|
18
|
+
indication?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface AutoCheckResult {
|
|
21
|
+
drug: string;
|
|
22
|
+
region: string;
|
|
23
|
+
result: RegulatoryStatusResult;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Fan-out helper for auto-wiring regulatory.status_check into other tools.
|
|
27
|
+
* Concurrency 8 to avoid OpenFDA rate-limit. Cache hits are free.
|
|
28
|
+
* Never throws — wraps each call so one failure doesn't kill the batch.
|
|
29
|
+
*/
|
|
30
|
+
export declare function autoCheckRegulatory(requests: AutoCheckRequest[]): Promise<AutoCheckResult[]>;
|
|
31
|
+
//# sourceMappingURL=autoCheck.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autoCheck.d.ts","sourceRoot":"","sources":["../../../src/providers/regulatory/autoCheck.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAIzD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,QAAQ,CAAC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,sBAAsB,CAAC;CAChC;AAwCD;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,gBAAgB,EAAE,GAC3B,OAAO,CAAC,eAAe,EAAE,CAAC,CA6C5B"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fan-out helper for auto-wiring regulatory.status_check into other tools.
|
|
3
|
+
*
|
|
4
|
+
* Design log #26. Used by evidence.unmet_need and hta_workflow Phase 3.6.
|
|
5
|
+
*
|
|
6
|
+
* Concurrency: up to 8 parallel calls (OpenFDA rate-limit guard).
|
|
7
|
+
* Cache: uses the shared 24h module-level cache inside regulatoryStatusCheck.ts.
|
|
8
|
+
* Never throws: each call is wrapped — one failure never aborts the batch.
|
|
9
|
+
*
|
|
10
|
+
* CRITICAL CYCLE-SAFETY: This module MUST NOT import from
|
|
11
|
+
* tools/evidenceUnmetNeed.ts or tools/htaWorkflow.ts (would create a cycle).
|
|
12
|
+
* Only imports from tools/regulatoryStatusCheck.ts (which owns no upstream deps).
|
|
13
|
+
*/
|
|
14
|
+
import { handleRegulatoryStatusCheck } from "../../tools/regulatoryStatusCheck.js";
|
|
15
|
+
// ── Constants ──────────────────────────────────────────────────────────────
|
|
16
|
+
const MAX_AUTO_REGULATORY_CALLS_PER_REQUEST = 8;
|
|
17
|
+
const CONCURRENCY_LIMIT = 8;
|
|
18
|
+
// ── Helper: build a graceful api_error result on exception ────────────────
|
|
19
|
+
function makeErrorResult(drug, region, message) {
|
|
20
|
+
return {
|
|
21
|
+
schema_version: "1.0",
|
|
22
|
+
drug,
|
|
23
|
+
drug_normalised_inn: drug.toLowerCase(),
|
|
24
|
+
drug_brand_names: [],
|
|
25
|
+
region,
|
|
26
|
+
current_status: "api_error",
|
|
27
|
+
approved_indications: [],
|
|
28
|
+
black_box_warnings: [],
|
|
29
|
+
rems_required: false,
|
|
30
|
+
contraindications: [],
|
|
31
|
+
recent_label_changes: {
|
|
32
|
+
count_12_months: 0,
|
|
33
|
+
last_revision_date: null,
|
|
34
|
+
last_revision_summary: null,
|
|
35
|
+
},
|
|
36
|
+
source_urls: [],
|
|
37
|
+
data_fetched_at: new Date().toISOString(),
|
|
38
|
+
data_age_hours: 0,
|
|
39
|
+
cache_hit: false,
|
|
40
|
+
api_error: {
|
|
41
|
+
source: "autoCheck",
|
|
42
|
+
message,
|
|
43
|
+
retry_after_seconds: 60,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
// ── Fan-out with concurrency limit ─────────────────────────────────────────
|
|
48
|
+
/**
|
|
49
|
+
* Fan-out helper for auto-wiring regulatory.status_check into other tools.
|
|
50
|
+
* Concurrency 8 to avoid OpenFDA rate-limit. Cache hits are free.
|
|
51
|
+
* Never throws — wraps each call so one failure doesn't kill the batch.
|
|
52
|
+
*/
|
|
53
|
+
export async function autoCheckRegulatory(requests) {
|
|
54
|
+
if (requests.length === 0)
|
|
55
|
+
return [];
|
|
56
|
+
// Cap at MAX to prevent fan-out explosion
|
|
57
|
+
const capped = requests.slice(0, MAX_AUTO_REGULATORY_CALLS_PER_REQUEST);
|
|
58
|
+
// Process in batches of CONCURRENCY_LIMIT
|
|
59
|
+
const results = [];
|
|
60
|
+
for (let i = 0; i < capped.length; i += CONCURRENCY_LIMIT) {
|
|
61
|
+
const batch = capped.slice(i, i + CONCURRENCY_LIMIT);
|
|
62
|
+
const batchResults = await Promise.all(batch.map(async (req) => {
|
|
63
|
+
try {
|
|
64
|
+
const toolResult = await handleRegulatoryStatusCheck({
|
|
65
|
+
drug: req.drug,
|
|
66
|
+
region: req.region,
|
|
67
|
+
indication: req.indication,
|
|
68
|
+
});
|
|
69
|
+
// handleRegulatoryStatusCheck returns { content: string, audit }
|
|
70
|
+
const parsed = JSON.parse(typeof toolResult.content === "string"
|
|
71
|
+
? toolResult.content
|
|
72
|
+
: JSON.stringify(toolResult.content));
|
|
73
|
+
return {
|
|
74
|
+
drug: req.drug,
|
|
75
|
+
region: req.region,
|
|
76
|
+
result: parsed,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
// Never throw — return graceful api_error
|
|
81
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
82
|
+
return {
|
|
83
|
+
drug: req.drug,
|
|
84
|
+
region: req.region,
|
|
85
|
+
result: makeErrorResult(req.drug, req.region, message),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}));
|
|
89
|
+
results.push(...batchResults);
|
|
90
|
+
}
|
|
91
|
+
return results;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=autoCheck.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autoCheck.js","sourceRoot":"","sources":["../../../src/providers/regulatory/autoCheck.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 24h in-memory TTL cache for regulatory.status_check results.
|
|
3
|
+
*
|
|
4
|
+
* Design contract:
|
|
5
|
+
* - get() returns null on miss (expired or never set)
|
|
6
|
+
* - get() returns { value, cache_hit: true, data_age_hours } on hit
|
|
7
|
+
* - set() stores value with current timestamp
|
|
8
|
+
* - delete() removes key (used for force_refresh)
|
|
9
|
+
* - TTL configurable; default 24h
|
|
10
|
+
*/
|
|
11
|
+
interface CacheHit<T> {
|
|
12
|
+
value: T;
|
|
13
|
+
cache_hit: true;
|
|
14
|
+
data_age_hours: number;
|
|
15
|
+
}
|
|
16
|
+
export declare class RegulatoryCache<T = unknown> {
|
|
17
|
+
private readonly store;
|
|
18
|
+
private readonly ttlMs;
|
|
19
|
+
constructor(options?: {
|
|
20
|
+
ttlMs?: number;
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* Returns null on cache miss (expired or never set).
|
|
24
|
+
* Returns { value, cache_hit: true, data_age_hours } on hit.
|
|
25
|
+
*/
|
|
26
|
+
get(key: string): CacheHit<T> | null;
|
|
27
|
+
/**
|
|
28
|
+
* Stores value with current timestamp.
|
|
29
|
+
*/
|
|
30
|
+
set(key: string, value: T): void;
|
|
31
|
+
/**
|
|
32
|
+
* Removes key from cache (used by force_refresh logic).
|
|
33
|
+
*/
|
|
34
|
+
delete(key: string): void;
|
|
35
|
+
}
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../src/providers/regulatory/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,UAAU,QAAQ,CAAC,CAAC;IAClB,KAAK,EAAE,CAAC,CAAC;IACT,SAAS,EAAE,IAAI,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,eAAe,CAAC,CAAC,GAAG,OAAO;IACtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoC;IAC1D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAEnB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;IAKxC;;;OAGG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI;IAkBpC;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAIhC;;OAEG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;CAG1B"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 24h in-memory TTL cache for regulatory.status_check results.
|
|
3
|
+
*
|
|
4
|
+
* Design contract:
|
|
5
|
+
* - get() returns null on miss (expired or never set)
|
|
6
|
+
* - get() returns { value, cache_hit: true, data_age_hours } on hit
|
|
7
|
+
* - set() stores value with current timestamp
|
|
8
|
+
* - delete() removes key (used for force_refresh)
|
|
9
|
+
* - TTL configurable; default 24h
|
|
10
|
+
*/
|
|
11
|
+
export class RegulatoryCache {
|
|
12
|
+
store = new Map();
|
|
13
|
+
ttlMs;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
// Default 24h TTL
|
|
16
|
+
this.ttlMs = options?.ttlMs ?? 24 * 60 * 60 * 1000;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Returns null on cache miss (expired or never set).
|
|
20
|
+
* Returns { value, cache_hit: true, data_age_hours } on hit.
|
|
21
|
+
*/
|
|
22
|
+
get(key) {
|
|
23
|
+
const entry = this.store.get(key);
|
|
24
|
+
if (!entry)
|
|
25
|
+
return null;
|
|
26
|
+
const ageMs = Date.now() - entry.storedAt;
|
|
27
|
+
if (ageMs > this.ttlMs) {
|
|
28
|
+
// Expired — remove and return null
|
|
29
|
+
this.store.delete(key);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
value: entry.value,
|
|
34
|
+
cache_hit: true,
|
|
35
|
+
data_age_hours: ageMs / (1000 * 60 * 60),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Stores value with current timestamp.
|
|
40
|
+
*/
|
|
41
|
+
set(key, value) {
|
|
42
|
+
this.store.set(key, { value, storedAt: Date.now() });
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Removes key from cache (used by force_refresh logic).
|
|
46
|
+
*/
|
|
47
|
+
delete(key) {
|
|
48
|
+
this.store.delete(key);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../../src/providers/regulatory/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAaH,MAAM,OAAO,eAAe;IACT,KAAK,GAAG,IAAI,GAAG,EAAyB,CAAC;IACzC,KAAK,CAAS;IAE/B,YAAY,OAA4B;QACtC,kBAAkB;QAClB,IAAI,CAAC,KAAK,GAAG,OAAO,EAAE,KAAK,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IACrD,CAAC;IAED;;;OAGG;IACH,GAAG,CAAC,GAAW;QACb,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QAExB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,QAAQ,CAAC;QAC1C,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;YACvB,mCAAmC;YACnC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACvB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO;YACL,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,SAAS,EAAE,IAAI;YACf,cAAc,EAAE,KAAK,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,CAAC;SACzC,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,GAAG,CAAC,GAAW,EAAE,KAAQ;QACvB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACvD,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,GAAW;QAChB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;CACF"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DailyMed v2 client — CROSS-CHECK / FALLBACK for US regulatory status.
|
|
3
|
+
*
|
|
4
|
+
* Phase 0 verified 2026-05-07:
|
|
5
|
+
* Search: GET https://dailymed.nlm.nih.gov/dailymed/services/v2/spls.json?drug_name={X}
|
|
6
|
+
* Returns: { data: [{spl_version, published_date, title, setid}], metadata }
|
|
7
|
+
*
|
|
8
|
+
* Used in v1 only to cross-check OpenFDA result freshness (compare
|
|
9
|
+
* published_date against OpenFDA's effective_time). Does NOT replace
|
|
10
|
+
* OpenFDA as the primary source.
|
|
11
|
+
*
|
|
12
|
+
* Design log #25.
|
|
13
|
+
*/
|
|
14
|
+
export interface DailyMedEntry {
|
|
15
|
+
setid: string;
|
|
16
|
+
spl_version: number;
|
|
17
|
+
published_date: string;
|
|
18
|
+
title: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Build the DailyMed search URL.
|
|
22
|
+
*/
|
|
23
|
+
export declare function buildDailyMedSearchUrl(drugName: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Parse DailyMed search response into structured entries.
|
|
26
|
+
*/
|
|
27
|
+
export declare function parseDailyMedSearchResponse(response: unknown): DailyMedEntry[];
|
|
28
|
+
/**
|
|
29
|
+
* Fetch DailyMed cross-check for a drug name.
|
|
30
|
+
* Returns null on any error (404, network failure, etc.) — DailyMed is
|
|
31
|
+
* non-critical; OpenFDA is the primary source.
|
|
32
|
+
*/
|
|
33
|
+
export declare function fetchDailyMedCrossCheck(drugName: string): Promise<DailyMedEntry[] | null>;
|
|
34
|
+
//# sourceMappingURL=dailymed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dailymed.d.ts","sourceRoot":"","sources":["../../../src/providers/regulatory/dailymed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;CACf;AAgBD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG/D;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,OAAO,GAChB,aAAa,EAAE,CAYjB;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,aAAa,EAAE,GAAG,IAAI,CAAC,CAcjC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DailyMed v2 client — CROSS-CHECK / FALLBACK for US regulatory status.
|
|
3
|
+
*
|
|
4
|
+
* Phase 0 verified 2026-05-07:
|
|
5
|
+
* Search: GET https://dailymed.nlm.nih.gov/dailymed/services/v2/spls.json?drug_name={X}
|
|
6
|
+
* Returns: { data: [{spl_version, published_date, title, setid}], metadata }
|
|
7
|
+
*
|
|
8
|
+
* Used in v1 only to cross-check OpenFDA result freshness (compare
|
|
9
|
+
* published_date against OpenFDA's effective_time). Does NOT replace
|
|
10
|
+
* OpenFDA as the primary source.
|
|
11
|
+
*
|
|
12
|
+
* Design log #25.
|
|
13
|
+
*/
|
|
14
|
+
const BASE_URL = "https://dailymed.nlm.nih.gov/dailymed/services/v2";
|
|
15
|
+
/**
|
|
16
|
+
* Build the DailyMed search URL.
|
|
17
|
+
*/
|
|
18
|
+
export function buildDailyMedSearchUrl(drugName) {
|
|
19
|
+
const encoded = encodeURIComponent(drugName);
|
|
20
|
+
return `${BASE_URL}/spls.json?drug_name=${encoded}`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Parse DailyMed search response into structured entries.
|
|
24
|
+
*/
|
|
25
|
+
export function parseDailyMedSearchResponse(response) {
|
|
26
|
+
if (!response || typeof response !== "object")
|
|
27
|
+
return [];
|
|
28
|
+
const resp = response;
|
|
29
|
+
const data = resp.data;
|
|
30
|
+
if (!Array.isArray(data))
|
|
31
|
+
return [];
|
|
32
|
+
return data.map((entry) => ({
|
|
33
|
+
setid: entry.setid ?? "",
|
|
34
|
+
spl_version: entry.spl_version ?? 0,
|
|
35
|
+
published_date: entry.published_date ?? "",
|
|
36
|
+
title: entry.title ?? "",
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Fetch DailyMed cross-check for a drug name.
|
|
41
|
+
* Returns null on any error (404, network failure, etc.) — DailyMed is
|
|
42
|
+
* non-critical; OpenFDA is the primary source.
|
|
43
|
+
*/
|
|
44
|
+
export async function fetchDailyMedCrossCheck(drugName) {
|
|
45
|
+
try {
|
|
46
|
+
const url = buildDailyMedSearchUrl(drugName);
|
|
47
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(15_000) });
|
|
48
|
+
if (!res.ok)
|
|
49
|
+
return null;
|
|
50
|
+
const data = (await res.json());
|
|
51
|
+
const entries = parseDailyMedSearchResponse(data);
|
|
52
|
+
if (entries.length === 0)
|
|
53
|
+
return null;
|
|
54
|
+
return entries;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=dailymed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dailymed.js","sourceRoot":"","sources":["../../../src/providers/regulatory/dailymed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,QAAQ,GAAG,mDAAmD,CAAC;AAuBrE;;GAEG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAAgB;IACrD,MAAM,OAAO,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAC7C,OAAO,GAAG,QAAQ,wBAAwB,OAAO,EAAE,CAAC;AACtD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,2BAA2B,CACzC,QAAiB;IAEjB,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IACzD,MAAM,IAAI,GAAG,QAAkC,CAAC;IAChD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACvB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAEpC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC1B,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,EAAE;QACxB,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,CAAC;QACnC,cAAc,EAAE,KAAK,CAAC,cAAc,IAAI,EAAE;QAC1C,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,EAAE;KACzB,CAAC,CAAC,CAAC;AACN,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,QAAgB;IAEhB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,sBAAsB,CAAC,QAAQ,CAAC,CAAC;QAC7C,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACtE,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QAEzB,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAY,CAAC;QAC3C,MAAM,OAAO,GAAG,2BAA2B,CAAC,IAAI,CAAC,CAAC;QAClD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAEtC,OAAO,OAAO,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drug name normaliser — INN / brand / biosimilar-suffix → canonical INN.
|
|
3
|
+
*
|
|
4
|
+
* Design log #25. Covers ~70 most-common HEOR-relevant molecules.
|
|
5
|
+
* Returns did_you_mean[] (Levenshtein-based top-3) when no match.
|
|
6
|
+
*
|
|
7
|
+
* Biosimilar suffix stripping: FDA 4-letter suffixes (e.g., -vfrm, -aooe, -adaz)
|
|
8
|
+
* are stripped to recover the INN stem.
|
|
9
|
+
*/
|
|
10
|
+
interface NormaliseResult {
|
|
11
|
+
inn: string | null;
|
|
12
|
+
did_you_mean?: string[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Normalises a drug name to canonical INN.
|
|
16
|
+
*
|
|
17
|
+
* Lookup priority:
|
|
18
|
+
* 1. INN exact match (case-insensitive)
|
|
19
|
+
* 2. Brand name exact match
|
|
20
|
+
* 3. Strip biosimilar suffix → INN match
|
|
21
|
+
* 4. Not found → null + did_you_mean
|
|
22
|
+
*/
|
|
23
|
+
export declare function normaliseDrugName(drug: string): NormaliseResult;
|
|
24
|
+
/**
|
|
25
|
+
* Returns all known brand names for a given INN.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getBrandNames(inn: string): string[];
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=drugNameNormaliser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drugNameNormaliser.d.ts","sourceRoot":"","sources":["../../../src/providers/regulatory/drugNameNormaliser.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,UAAU,eAAe;IACvB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAuID;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAoC/D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAKnD"}
|