openfda-mcp-server 1.0.0 → 1.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 +110 -19
- package/dist/defineProperty-BEGP2H1W.js +1 -0
- package/dist/handlers/bulk-data-handlers.d.ts +108 -0
- package/dist/handlers/bulk-data-handlers.js +2 -0
- package/dist/handlers/bulk-data-handlers.js.map +1 -0
- package/dist/handlers/device-handlers.d.ts +1 -1
- package/dist/handlers/device-handlers.js +2 -1
- package/dist/handlers/device-handlers.js.map +1 -0
- package/dist/handlers/drug-handlers.d.ts +16 -1
- package/dist/handlers/drug-handlers.js +2 -1
- package/dist/handlers/drug-handlers.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +4 -2
- package/dist/lib.js +1 -1
- package/dist/server.d.ts +1 -2
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/index.d.ts +215 -360
- package/dist/tools/index.js +2 -1
- package/dist/tools/index.js.map +1 -0
- package/dist/types/fda.d.ts +59 -1
- package/dist/utils/api-client.d.ts +0 -1
- package/dist/utils/api-client.js +2 -1
- package/dist/utils/api-client.js.map +1 -0
- package/dist/utils/bulk-data-client.d.ts +24 -0
- package/dist/utils/bulk-data-client.js +4 -0
- package/dist/utils/bulk-data-client.js.map +1 -0
- package/dist/utils/logger.d.ts +1 -1
- package/dist/utils/logger.js +1 -1
- package/dist/utils/logger.js.map +1 -1
- package/package.json +29 -25
- package/dist/api-client-BGnUXMwX.js +0 -2
- package/dist/api-client-BGnUXMwX.js.map +0 -1
- package/dist/device-handlers-D01LfIJk.js +0 -2
- package/dist/device-handlers-D01LfIJk.js.map +0 -1
- package/dist/drug-handlers-Fz_6NLl5.js +0 -2
- package/dist/drug-handlers-Fz_6NLl5.js.map +0 -1
- package/dist/tools-Bh3wjIRH.js +0 -2
- package/dist/tools-Bh3wjIRH.js.map +0 -1
package/README.md
CHANGED
|
@@ -7,6 +7,8 @@ An MCP (Model Context Protocol) server that provides access to U.S. FDA public d
|
|
|
7
7
|
- **10 FDA Tools**: Query drug and device databases through a standardized MCP interface
|
|
8
8
|
- **Drug Tools**: Adverse events (FAERS), labels, NDC directory, recalls, Drugs@FDA, shortages
|
|
9
9
|
- **Device Tools**: 510(k) clearances, classifications, adverse events (MDR), recalls
|
|
10
|
+
- **Individual Record Lookup**: Retrieve specific reports by ID (safety report ID, set ID, MDR report number)
|
|
11
|
+
- **Advanced Filtering**: Filter drug labels by boxed warnings, request specific label sections
|
|
10
12
|
- **Rate Limit Aware**: Supports authenticated requests for higher rate limits (120k/hour vs 1k/hour)
|
|
11
13
|
- **TypeScript**: Fully typed with Zod schema validation
|
|
12
14
|
|
|
@@ -52,23 +54,69 @@ Get a free API key at: https://open.fda.gov/apis/authentication/
|
|
|
52
54
|
|
|
53
55
|
### Drug Tools
|
|
54
56
|
|
|
55
|
-
| Tool | Description
|
|
56
|
-
| ---------------------------- |
|
|
57
|
-
| `search_drug_adverse_events` | Search FAERS for drug safety reports
|
|
58
|
-
| `search_drug_labels` | Search drug labeling/prescribing
|
|
59
|
-
| `search_drug_ndc` | Search the National Drug Code directory
|
|
60
|
-
| `search_drug_recalls` | Search drug recall enforcement reports
|
|
61
|
-
| `search_drugs_at_fda` | Search approved drug applications
|
|
62
|
-
| `search_drug_shortages` | Search current and resolved drug shortages
|
|
57
|
+
| Tool | Description |
|
|
58
|
+
| ---------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
59
|
+
| `search_drug_adverse_events` | Search FAERS for drug safety reports. Retrieve specific report by `safetyReportId`. |
|
|
60
|
+
| `search_drug_labels` | Search drug labeling/prescribing info. Get by `setId`, filter by `hasBoxedWarning`, select `sections`. |
|
|
61
|
+
| `search_drug_ndc` | Search the National Drug Code directory |
|
|
62
|
+
| `search_drug_recalls` | Search drug recall enforcement reports |
|
|
63
|
+
| `search_drugs_at_fda` | Search approved drug applications |
|
|
64
|
+
| `search_drug_shortages` | Search current and resolved drug shortages |
|
|
63
65
|
|
|
64
66
|
### Device Tools
|
|
65
67
|
|
|
66
|
-
| Tool | Description
|
|
67
|
-
| ------------------------------- |
|
|
68
|
-
| `search_device_510k` | Search 510(k) premarket notifications
|
|
69
|
-
| `search_device_classifications` | Search device classification database
|
|
70
|
-
| `search_device_adverse_events` | Search MDR adverse event reports
|
|
71
|
-
| `search_device_recalls` | Search device recall enforcement reports
|
|
68
|
+
| Tool | Description |
|
|
69
|
+
| ------------------------------- | ----------------------------------------------------------------------------- |
|
|
70
|
+
| `search_device_510k` | Search 510(k) premarket notifications |
|
|
71
|
+
| `search_device_classifications` | Search device classification database |
|
|
72
|
+
| `search_device_adverse_events` | Search MDR adverse event reports. Retrieve specific report by `reportNumber`. |
|
|
73
|
+
| `search_device_recalls` | Search device recall enforcement reports |
|
|
74
|
+
|
|
75
|
+
## Tool Parameters
|
|
76
|
+
|
|
77
|
+
### search_drug_labels
|
|
78
|
+
|
|
79
|
+
| Parameter | Type | Description |
|
|
80
|
+
| ------------------ | -------- | ------------------------------------------------------------ |
|
|
81
|
+
| `setId` | string | Retrieve a specific label by its unique set_id |
|
|
82
|
+
| `drugName` | string | Drug brand or generic name |
|
|
83
|
+
| `indication` | string | Medical indication or use case |
|
|
84
|
+
| `activeIngredient` | string | Active ingredient/substance name |
|
|
85
|
+
| `route` | string | Route of administration (e.g., 'oral', 'intravenous') |
|
|
86
|
+
| `hasBoxedWarning` | boolean | Filter for drugs with boxed warnings (most serious warnings) |
|
|
87
|
+
| `sections` | string[] | Specific label sections to return (see below) |
|
|
88
|
+
| `limit` | number | Maximum results (1-100) |
|
|
89
|
+
| `skip` | number | Number of results to skip for pagination |
|
|
90
|
+
|
|
91
|
+
**Available sections**: `indications_and_usage`, `dosage_and_administration`, `contraindications`, `warnings`, `warnings_and_cautions`, `adverse_reactions`, `drug_interactions`, `clinical_pharmacology`, `mechanism_of_action`, `pharmacokinetics`, `overdosage`, `description`, `how_supplied`, `storage_and_handling`, `boxed_warning`
|
|
92
|
+
|
|
93
|
+
### search_drug_adverse_events
|
|
94
|
+
|
|
95
|
+
| Parameter | Type | Description |
|
|
96
|
+
| ---------------- | ------- | -------------------------------------------------- |
|
|
97
|
+
| `safetyReportId` | string | Retrieve a specific FAERS report by its 8-digit ID |
|
|
98
|
+
| `drugName` | string | Drug or product name to search |
|
|
99
|
+
| `reaction` | string | Adverse reaction (e.g., 'headache', 'nausea') |
|
|
100
|
+
| `manufacturer` | string | Drug manufacturer name |
|
|
101
|
+
| `serious` | boolean | Filter for serious adverse events only |
|
|
102
|
+
| `dateFrom` | string | Start date (YYYY-MM-DD) |
|
|
103
|
+
| `dateTo` | string | End date (YYYY-MM-DD) |
|
|
104
|
+
| `limit` | number | Maximum results (1-100) |
|
|
105
|
+
| `skip` | number | Number of results to skip for pagination |
|
|
106
|
+
|
|
107
|
+
### search_device_adverse_events
|
|
108
|
+
|
|
109
|
+
| Parameter | Type | Description |
|
|
110
|
+
| ------------------ | ------ | ----------------------------------------------- |
|
|
111
|
+
| `reportNumber` | string | Retrieve a specific MDR report by report number |
|
|
112
|
+
| `deviceName` | string | Device generic name |
|
|
113
|
+
| `brandName` | string | Device brand name |
|
|
114
|
+
| `manufacturerName` | string | Device manufacturer name |
|
|
115
|
+
| `eventType` | string | Type: 'Injury', 'Malfunction', 'Death', 'Other' |
|
|
116
|
+
| `dateFrom` | string | Start date (YYYY-MM-DD) |
|
|
117
|
+
| `dateTo` | string | End date (YYYY-MM-DD) |
|
|
118
|
+
| `limit` | number | Maximum results (1-100) |
|
|
119
|
+
| `skip` | number | Number of results to skip for pagination |
|
|
72
120
|
|
|
73
121
|
## Usage Examples
|
|
74
122
|
|
|
@@ -78,6 +126,24 @@ Get a free API key at: https://open.fda.gov/apis/authentication/
|
|
|
78
126
|
Find adverse events for aspirin in the last year that were serious
|
|
79
127
|
```
|
|
80
128
|
|
|
129
|
+
### Get a specific adverse event report
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
Retrieve FAERS report with safety report ID 10003641
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Search for drugs with boxed warnings
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
Find all drugs with boxed warnings related to cardiovascular risks
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Get specific label sections
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
Get the boxed_warning and contraindications sections for warfarin
|
|
145
|
+
```
|
|
146
|
+
|
|
81
147
|
### Search for drug recalls
|
|
82
148
|
|
|
83
149
|
```
|
|
@@ -90,6 +156,12 @@ Search for Class I drug recalls from Pfizer
|
|
|
90
156
|
Find 510(k) clearances for cardiac pacemakers
|
|
91
157
|
```
|
|
92
158
|
|
|
159
|
+
### Get a specific device adverse event report
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
Retrieve MDR report number 2649622-2020-08294
|
|
163
|
+
```
|
|
164
|
+
|
|
93
165
|
### Search for device adverse events
|
|
94
166
|
|
|
95
167
|
```
|
|
@@ -164,7 +236,7 @@ pnpm build
|
|
|
164
236
|
## Programmatic Usage
|
|
165
237
|
|
|
166
238
|
```typescript
|
|
167
|
-
import { createOpenFDAServer, handleSearchDrugAdverseEvents } from "openfda-mcp-server/lib"
|
|
239
|
+
import { createOpenFDAServer, handleSearchDrugAdverseEvents, handleSearchDrugLabels } from "openfda-mcp-server/lib"
|
|
168
240
|
|
|
169
241
|
// Create a custom server
|
|
170
242
|
const server = createOpenFDAServer({
|
|
@@ -172,12 +244,29 @@ const server = createOpenFDAServer({
|
|
|
172
244
|
version: "1.0.0",
|
|
173
245
|
})
|
|
174
246
|
|
|
175
|
-
//
|
|
176
|
-
const
|
|
247
|
+
// Search for adverse events
|
|
248
|
+
const adverseEvents = await handleSearchDrugAdverseEvents({
|
|
177
249
|
drugName: "aspirin",
|
|
178
250
|
serious: true,
|
|
179
251
|
limit: 10,
|
|
180
252
|
})
|
|
253
|
+
|
|
254
|
+
// Get a specific adverse event by ID
|
|
255
|
+
const specificReport = await handleSearchDrugAdverseEvents({
|
|
256
|
+
safetyReportId: "10003641",
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
// Search for drugs with boxed warnings
|
|
260
|
+
const boxedWarningDrugs = await handleSearchDrugLabels({
|
|
261
|
+
hasBoxedWarning: true,
|
|
262
|
+
limit: 10,
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
// Get specific sections of a drug label
|
|
266
|
+
const warfarinLabel = await handleSearchDrugLabels({
|
|
267
|
+
setId: "0cbce382-9c88-4f58-ae0f-532a841e8f95",
|
|
268
|
+
sections: ["boxed_warning", "contraindications", "adverse_reactions"],
|
|
269
|
+
})
|
|
181
270
|
```
|
|
182
271
|
|
|
183
272
|
## API Rate Limits
|
|
@@ -194,5 +283,7 @@ MIT
|
|
|
194
283
|
## Acknowledgments
|
|
195
284
|
|
|
196
285
|
- [OpenFDA](https://open.fda.gov/) - FDA open data API
|
|
197
|
-
|
|
198
|
-
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
**Sponsored by <a href="https://sapientsai.com/"><img src="https://sapientsai.com/images/logo.svg" alt="SapientsAI" width="20" style="vertical-align: middle;"> SapientsAI</a>** — Building agentic AI for businesses
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(t){"@babel/helpers - typeof";return e=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},e(t)}function t(t,n){if(e(t)!=`object`||!t)return t;var r=t[Symbol.toPrimitive];if(r!==void 0){var i=r.call(t,n||`default`);if(e(i)!=`object`)return i;throw TypeError(`@@toPrimitive must return a primitive value.`)}return(n===`string`?String:Number)(t)}function n(n){var r=t(n,`string`);return e(r)==`symbol`?r:r+``}function r(e,t,r){return(t=n(t))in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}export{r as t};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { FDAToolResponse } from "../types/fda.js";
|
|
2
|
+
|
|
3
|
+
//#region src/handlers/bulk-data-handlers.d.ts
|
|
4
|
+
type OrangeBookSearchParams = {
|
|
5
|
+
drugName?: string;
|
|
6
|
+
applicant?: string;
|
|
7
|
+
applNo?: string;
|
|
8
|
+
teCode?: string;
|
|
9
|
+
limit?: number;
|
|
10
|
+
skip?: number;
|
|
11
|
+
};
|
|
12
|
+
type FormattedOrangeBookProduct = {
|
|
13
|
+
tradeName: string;
|
|
14
|
+
ingredient: string;
|
|
15
|
+
applicant: string;
|
|
16
|
+
applicantFullName: string;
|
|
17
|
+
applType: string;
|
|
18
|
+
applNo: string;
|
|
19
|
+
productNo: string;
|
|
20
|
+
dfRoute: string;
|
|
21
|
+
teCode: string;
|
|
22
|
+
approvalDate: string;
|
|
23
|
+
rld: string;
|
|
24
|
+
type: string;
|
|
25
|
+
patentCount: number;
|
|
26
|
+
exclusivityCount: number;
|
|
27
|
+
};
|
|
28
|
+
declare function handleSearchOrangeBook(params: OrangeBookSearchParams): Promise<FDAToolResponse<FormattedOrangeBookProduct[]>>;
|
|
29
|
+
type OrangeBookPatentsParams = {
|
|
30
|
+
drugName?: string;
|
|
31
|
+
applNo?: string;
|
|
32
|
+
patentNo?: string;
|
|
33
|
+
limit?: number;
|
|
34
|
+
skip?: number;
|
|
35
|
+
};
|
|
36
|
+
type FormattedOrangeBookPatent = {
|
|
37
|
+
patentNo: string;
|
|
38
|
+
patentExpireDate: string;
|
|
39
|
+
drugSubstanceFlag: string;
|
|
40
|
+
drugProductFlag: string;
|
|
41
|
+
patentUseCode: string;
|
|
42
|
+
applType: string;
|
|
43
|
+
applNo: string;
|
|
44
|
+
productNo: string;
|
|
45
|
+
tradeName: string;
|
|
46
|
+
ingredient: string;
|
|
47
|
+
submissionDate: string;
|
|
48
|
+
exclusivities: Array<{
|
|
49
|
+
code: string;
|
|
50
|
+
date: string;
|
|
51
|
+
}>;
|
|
52
|
+
};
|
|
53
|
+
declare function handleSearchOrangeBookPatents(params: OrangeBookPatentsParams): Promise<FDAToolResponse<FormattedOrangeBookPatent[]>>;
|
|
54
|
+
type PurpleBookSearchParams = {
|
|
55
|
+
productName?: string;
|
|
56
|
+
applicant?: string;
|
|
57
|
+
blaNumber?: string;
|
|
58
|
+
licenseType?: "351(a)" | "351(k)";
|
|
59
|
+
biosimilar?: boolean;
|
|
60
|
+
interchangeable?: boolean;
|
|
61
|
+
limit?: number;
|
|
62
|
+
skip?: number;
|
|
63
|
+
};
|
|
64
|
+
type FormattedPurpleBookEntry = {
|
|
65
|
+
blaNumber: string;
|
|
66
|
+
proprietaryName: string;
|
|
67
|
+
properName: string;
|
|
68
|
+
applicant: string;
|
|
69
|
+
blaType: string;
|
|
70
|
+
strength: string;
|
|
71
|
+
dosageForm: string;
|
|
72
|
+
route: string;
|
|
73
|
+
status: string;
|
|
74
|
+
licensingStatus: string;
|
|
75
|
+
biosimilar: boolean;
|
|
76
|
+
interchangeable: boolean;
|
|
77
|
+
referenceProductBla: string;
|
|
78
|
+
referenceProductName: string;
|
|
79
|
+
approvalDate: string;
|
|
80
|
+
};
|
|
81
|
+
declare function handleSearchPurpleBook(params: PurpleBookSearchParams): Promise<FDAToolResponse<FormattedPurpleBookEntry[]>>;
|
|
82
|
+
type DrugPatentExpiryParams = {
|
|
83
|
+
drugName?: string;
|
|
84
|
+
applNo?: string;
|
|
85
|
+
includeExclusivity?: boolean;
|
|
86
|
+
includePurpleBook?: boolean;
|
|
87
|
+
limit?: number;
|
|
88
|
+
skip?: number;
|
|
89
|
+
};
|
|
90
|
+
type PatentExpiryEntry = {
|
|
91
|
+
source: string;
|
|
92
|
+
tradeName: string;
|
|
93
|
+
ingredient: string;
|
|
94
|
+
applNo: string;
|
|
95
|
+
patentNo: string;
|
|
96
|
+
patentExpireDate: string;
|
|
97
|
+
drugSubstanceFlag: string;
|
|
98
|
+
drugProductFlag: string;
|
|
99
|
+
patentUseCode: string;
|
|
100
|
+
exclusivities: Array<{
|
|
101
|
+
code: string;
|
|
102
|
+
date: string;
|
|
103
|
+
}>;
|
|
104
|
+
};
|
|
105
|
+
declare function handleSearchDrugPatentExpiry(params: DrugPatentExpiryParams): Promise<FDAToolResponse<PatentExpiryEntry[]>>;
|
|
106
|
+
//#endregion
|
|
107
|
+
export { DrugPatentExpiryParams, OrangeBookPatentsParams, OrangeBookSearchParams, PurpleBookSearchParams, handleSearchDrugPatentExpiry, handleSearchOrangeBook, handleSearchOrangeBookPatents, handleSearchPurpleBook };
|
|
108
|
+
//# sourceMappingURL=bulk-data-handlers.d.ts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{loggers as e}from"../utils/logger.js";import{bulkDataClient as t}from"../utils/bulk-data-client.js";function n(e,t){return t?e.toLowerCase().includes(t.toLowerCase()):!0}function r(e){return`${e.applType}-${e.applNo}-${e.productNo}`}function i(e,t,n){let r=e.length,i=n??0,a=Math.min(t??10,100);return{items:e.slice(i,i+a),total:r}}const a={name:`FDA Orange Book`,lastUpdated:`Monthly`,url:`https://www.fda.gov/drugs/drug-approvals-and-databases/approved-drug-products-therapeutic-equivalence-evaluations-orange-book`},o={name:`FDA Purple Book`,lastUpdated:`Monthly`,url:`https://purplebooksearch.fda.gov/`};async function s(o){e.tools(`searchOrangeBook`,o);try{let{products:e,patents:s,exclusivities:c}=await t.getOrangeBookData(),l=new Map;for(let e of s){let t=r(e);l.set(t,(l.get(t)??0)+1)}let u=new Map;for(let e of c){let t=r(e);u.set(t,(u.get(t)??0)+1)}let{items:d,total:f}=i(e.filter(e=>!(!n(e.tradeName,o.drugName)&&!n(e.ingredient,o.drugName)||!n(e.applicant,o.applicant)&&!n(e.applicantFullName,o.applicant)||o.applNo&&e.applNo!==o.applNo||!n(e.teCode,o.teCode))),o.limit,o.skip);return{success:!0,data:d.map(e=>{let t=r(e);return{tradeName:e.tradeName,ingredient:e.ingredient,applicant:e.applicant,applicantFullName:e.applicantFullName,applType:e.applType,applNo:e.applNo,productNo:e.productNo,dfRoute:e.dfRoute,teCode:e.teCode,approvalDate:e.approvalDate,rld:e.rld,type:e.type,patentCount:l.get(t)??0,exclusivityCount:u.get(t)??0}}),totalResults:f,displayedResults:d.length,dataSource:a}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}async function c(o){e.tools(`searchOrangeBookPatents`,o);try{let{products:e,patents:s,exclusivities:c}=await t.getOrangeBookData(),l=new Map;for(let t of e)l.set(r(t),t);let u=new Map;for(let e of c){let t=r(e),n=u.get(t)??[];n.push(e),u.set(t,n)}let{items:d,total:f}=i(s.filter(e=>{if(o.patentNo&&e.patentNo!==o.patentNo||o.applNo&&e.applNo!==o.applNo)return!1;if(o.drugName){let t=l.get(r(e));if(!t||!n(t.tradeName,o.drugName)&&!n(t.ingredient,o.drugName))return!1}return!0}),o.limit,o.skip);return{success:!0,data:d.map(e=>{let t=r(e),n=l.get(t),i=u.get(t)??[];return{patentNo:e.patentNo,patentExpireDate:e.patentExpireDate,drugSubstanceFlag:e.drugSubstanceFlag,drugProductFlag:e.drugProductFlag,patentUseCode:e.patentUseCode,applType:e.applType,applNo:e.applNo,productNo:e.productNo,tradeName:n?.tradeName??``,ingredient:n?.ingredient??``,submissionDate:e.submissionDate,exclusivities:i.map(e=>({code:e.exclusivityCode,date:e.exclusivityDate}))}}),totalResults:f,displayedResults:d.length,dataSource:a}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}async function l(r){e.tools(`searchPurpleBook`,r);try{let{items:e,total:a}=i((await t.getPurpleBookData()).filter(e=>!(!n(e.proprietaryName,r.productName)&&!n(e.properName,r.productName)||!n(e.applicant,r.applicant)||r.blaNumber&&e.blaNumber!==r.blaNumber||r.licenseType&&e.blaType!==r.licenseType||r.biosimilar!==void 0&&e.biosimilar!==r.biosimilar||r.interchangeable!==void 0&&e.interchangeable!==r.interchangeable)),r.limit,r.skip);return{success:!0,data:e.map(e=>({blaNumber:e.blaNumber,proprietaryName:e.proprietaryName,properName:e.properName,applicant:e.applicant,blaType:e.blaType,strength:e.strength,dosageForm:e.dosageForm,route:e.route,status:e.status,licensingStatus:e.licensingStatus,biosimilar:e.biosimilar,interchangeable:e.interchangeable,referenceProductBla:e.referenceProductBla,referenceProductName:e.referenceProductProprietaryName,approvalDate:e.approvalDate})),totalResults:a,displayedResults:e.length,dataSource:o}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}async function u(o){e.tools(`searchDrugPatentExpiry`,o);try{let s=o.includeExclusivity!==!1,c=o.includePurpleBook===!0,{products:l,patents:u,exclusivities:d}=await t.getOrangeBookData(),f=new Map;for(let e of l)f.set(r(e),e);let p=new Map;if(s)for(let e of d){let t=r(e),n=p.get(t)??[];n.push(e),p.set(t,n)}let m=u.filter(e=>{if(o.applNo&&e.applNo!==o.applNo)return!1;if(o.drugName){let t=f.get(r(e));if(!t||!n(t.tradeName,o.drugName)&&!n(t.ingredient,o.drugName))return!1}return!0}).map(e=>{let t=r(e),n=f.get(t),i=s?p.get(t)??[]:[];return{source:`Orange Book`,tradeName:n?.tradeName??``,ingredient:n?.ingredient??``,applNo:e.applNo,patentNo:e.patentNo,patentExpireDate:e.patentExpireDate,drugSubstanceFlag:e.drugSubstanceFlag,drugProductFlag:e.drugProductFlag,patentUseCode:e.patentUseCode,exclusivities:i.map(e=>({code:e.exclusivityCode,date:e.exclusivityDate}))}});if(c)try{let e=(await t.getPurpleBookData()).filter(e=>!(o.drugName&&!n(e.proprietaryName,o.drugName)&&!n(e.properName,o.drugName)||o.applNo&&e.blaNumber!==o.applNo));for(let t of e)m.push({source:`Purple Book`,tradeName:t.proprietaryName,ingredient:t.properName,applNo:t.blaNumber,patentNo:``,patentExpireDate:``,drugSubstanceFlag:``,drugProductFlag:``,patentUseCode:``,exclusivities:[]})}catch(t){e.bulk(`Purple Book data unavailable: ${t instanceof Error?t.message:String(t)}`)}m.sort((e,t)=>!e.patentExpireDate&&!t.patentExpireDate?0:e.patentExpireDate?t.patentExpireDate?e.patentExpireDate.localeCompare(t.patentExpireDate):-1:1);let{items:h,total:g}=i(m,o.limit,o.skip);return{success:!0,data:h,totalResults:g,displayedResults:h.length,dataSource:a}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}export{u as handleSearchDrugPatentExpiry,s as handleSearchOrangeBook,c as handleSearchOrangeBookPatents,l as handleSearchPurpleBook};
|
|
2
|
+
//# sourceMappingURL=bulk-data-handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bulk-data-handlers.js","names":[],"sources":["../../src/handlers/bulk-data-handlers.ts"],"sourcesContent":["/**\n * Bulk Data Handlers\n * Query handlers for FDA Orange Book and Purple Book data\n */\n\nimport type { FDAToolResponse, OrangeBookExclusivity, OrangeBookPatent, OrangeBookProduct } from \"../types/fda.js\"\nimport { bulkDataClient } from \"../utils/bulk-data-client.js\"\nimport { loggers } from \"../utils/logger.js\"\n\n// Helper for case-insensitive substring matching\nfunction matchesFilter(value: string, filter: string | undefined): boolean {\n if (!filter) return true\n return value.toLowerCase().includes(filter.toLowerCase())\n}\n\n// Join key for Orange Book records\nfunction obJoinKey(record: { applType: string; applNo: string; productNo: string }): string {\n return `${record.applType}-${record.applNo}-${record.productNo}`\n}\n\n// Default pagination values\nconst DEFAULT_LIMIT = 10\nconst MAX_LIMIT = 100\n\nfunction applyPagination<T>(items: T[], limit?: number, skip?: number): { items: T[]; total: number } {\n const total = items.length\n const effectiveSkip = skip ?? 0\n const effectiveLimit = Math.min(limit ?? DEFAULT_LIMIT, MAX_LIMIT)\n return {\n items: items.slice(effectiveSkip, effectiveSkip + effectiveLimit),\n total,\n }\n}\n\nconst ORANGE_BOOK_SOURCE = {\n name: \"FDA Orange Book\",\n lastUpdated: \"Monthly\",\n url: \"https://www.fda.gov/drugs/drug-approvals-and-databases/approved-drug-products-therapeutic-equivalence-evaluations-orange-book\",\n}\n\nconst PURPLE_BOOK_SOURCE = {\n name: \"FDA Purple Book\",\n lastUpdated: \"Monthly\",\n url: \"https://purplebooksearch.fda.gov/\",\n}\n\n// ============================================================\n// search_fda_orange_book\n// ============================================================\n\nexport type OrangeBookSearchParams = {\n drugName?: string\n applicant?: string\n applNo?: string\n teCode?: string\n limit?: number\n skip?: number\n}\n\ntype FormattedOrangeBookProduct = {\n tradeName: string\n ingredient: string\n applicant: string\n applicantFullName: string\n applType: string\n applNo: string\n productNo: string\n dfRoute: string\n teCode: string\n approvalDate: string\n rld: string\n type: string\n patentCount: number\n exclusivityCount: number\n}\n\nexport async function handleSearchOrangeBook(\n params: OrangeBookSearchParams,\n): Promise<FDAToolResponse<FormattedOrangeBookProduct[]>> {\n loggers.tools(\"searchOrangeBook\", params)\n\n try {\n const { products, patents, exclusivities } = await bulkDataClient.getOrangeBookData()\n\n // Build patent/exclusivity count maps\n const patentCounts = new Map<string, number>()\n for (const p of patents) {\n const key = obJoinKey(p)\n patentCounts.set(key, (patentCounts.get(key) ?? 0) + 1)\n }\n\n const exclusivityCounts = new Map<string, number>()\n for (const e of exclusivities) {\n const key = obJoinKey(e)\n exclusivityCounts.set(key, (exclusivityCounts.get(key) ?? 0) + 1)\n }\n\n // Filter products\n const filtered = products.filter((p) => {\n if (!matchesFilter(p.tradeName, params.drugName) && !matchesFilter(p.ingredient, params.drugName)) return false\n if (!matchesFilter(p.applicant, params.applicant) && !matchesFilter(p.applicantFullName, params.applicant))\n return false\n if (params.applNo && p.applNo !== params.applNo) return false\n if (!matchesFilter(p.teCode, params.teCode)) return false\n return true\n })\n\n const { items, total } = applyPagination(filtered, params.limit, params.skip)\n\n const formatted: FormattedOrangeBookProduct[] = items.map((p) => {\n const key = obJoinKey(p)\n return {\n tradeName: p.tradeName,\n ingredient: p.ingredient,\n applicant: p.applicant,\n applicantFullName: p.applicantFullName,\n applType: p.applType,\n applNo: p.applNo,\n productNo: p.productNo,\n dfRoute: p.dfRoute,\n teCode: p.teCode,\n approvalDate: p.approvalDate,\n rld: p.rld,\n type: p.type,\n patentCount: patentCounts.get(key) ?? 0,\n exclusivityCount: exclusivityCounts.get(key) ?? 0,\n }\n })\n\n return {\n success: true,\n data: formatted,\n totalResults: total,\n displayedResults: items.length,\n dataSource: ORANGE_BOOK_SOURCE,\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n// ============================================================\n// search_fda_orange_book_patents\n// ============================================================\n\nexport type OrangeBookPatentsParams = {\n drugName?: string\n applNo?: string\n patentNo?: string\n limit?: number\n skip?: number\n}\n\ntype FormattedOrangeBookPatent = {\n patentNo: string\n patentExpireDate: string\n drugSubstanceFlag: string\n drugProductFlag: string\n patentUseCode: string\n applType: string\n applNo: string\n productNo: string\n tradeName: string\n ingredient: string\n submissionDate: string\n exclusivities: Array<{ code: string; date: string }>\n}\n\nexport async function handleSearchOrangeBookPatents(\n params: OrangeBookPatentsParams,\n): Promise<FDAToolResponse<FormattedOrangeBookPatent[]>> {\n loggers.tools(\"searchOrangeBookPatents\", params)\n\n try {\n const { products, patents, exclusivities } = await bulkDataClient.getOrangeBookData()\n\n // Build product lookup by join key\n const productMap = new Map<string, OrangeBookProduct>()\n for (const p of products) {\n productMap.set(obJoinKey(p), p)\n }\n\n // Build exclusivity lookup by join key\n const exclusivityMap = new Map<string, OrangeBookExclusivity[]>()\n for (const e of exclusivities) {\n const key = obJoinKey(e)\n const existing = exclusivityMap.get(key) ?? []\n existing.push(e)\n exclusivityMap.set(key, existing)\n }\n\n // Filter patents\n const filtered = patents.filter((pat) => {\n if (params.patentNo && pat.patentNo !== params.patentNo) return false\n if (params.applNo && pat.applNo !== params.applNo) return false\n\n if (params.drugName) {\n const product = productMap.get(obJoinKey(pat))\n if (!product) return false\n if (!matchesFilter(product.tradeName, params.drugName) && !matchesFilter(product.ingredient, params.drugName))\n return false\n }\n\n return true\n })\n\n const { items, total } = applyPagination(filtered, params.limit, params.skip)\n\n const formatted: FormattedOrangeBookPatent[] = items.map((pat) => {\n const key = obJoinKey(pat)\n const product = productMap.get(key)\n const excl = exclusivityMap.get(key) ?? []\n\n return {\n patentNo: pat.patentNo,\n patentExpireDate: pat.patentExpireDate,\n drugSubstanceFlag: pat.drugSubstanceFlag,\n drugProductFlag: pat.drugProductFlag,\n patentUseCode: pat.patentUseCode,\n applType: pat.applType,\n applNo: pat.applNo,\n productNo: pat.productNo,\n tradeName: product?.tradeName ?? \"\",\n ingredient: product?.ingredient ?? \"\",\n submissionDate: pat.submissionDate,\n exclusivities: excl.map((e) => ({ code: e.exclusivityCode, date: e.exclusivityDate })),\n }\n })\n\n return {\n success: true,\n data: formatted,\n totalResults: total,\n displayedResults: items.length,\n dataSource: ORANGE_BOOK_SOURCE,\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n// ============================================================\n// search_fda_purple_book\n// ============================================================\n\nexport type PurpleBookSearchParams = {\n productName?: string\n applicant?: string\n blaNumber?: string\n licenseType?: \"351(a)\" | \"351(k)\"\n biosimilar?: boolean\n interchangeable?: boolean\n limit?: number\n skip?: number\n}\n\ntype FormattedPurpleBookEntry = {\n blaNumber: string\n proprietaryName: string\n properName: string\n applicant: string\n blaType: string\n strength: string\n dosageForm: string\n route: string\n status: string\n licensingStatus: string\n biosimilar: boolean\n interchangeable: boolean\n referenceProductBla: string\n referenceProductName: string\n approvalDate: string\n}\n\nexport async function handleSearchPurpleBook(\n params: PurpleBookSearchParams,\n): Promise<FDAToolResponse<FormattedPurpleBookEntry[]>> {\n loggers.tools(\"searchPurpleBook\", params)\n\n try {\n const entries = await bulkDataClient.getPurpleBookData()\n\n const filtered = entries.filter((e) => {\n if (!matchesFilter(e.proprietaryName, params.productName) && !matchesFilter(e.properName, params.productName))\n return false\n if (!matchesFilter(e.applicant, params.applicant)) return false\n if (params.blaNumber && e.blaNumber !== params.blaNumber) return false\n if (params.licenseType && e.blaType !== params.licenseType) return false\n if (params.biosimilar !== undefined && e.biosimilar !== params.biosimilar) return false\n if (params.interchangeable !== undefined && e.interchangeable !== params.interchangeable) return false\n return true\n })\n\n const { items, total } = applyPagination(filtered, params.limit, params.skip)\n\n const formatted: FormattedPurpleBookEntry[] = items.map((e) => ({\n blaNumber: e.blaNumber,\n proprietaryName: e.proprietaryName,\n properName: e.properName,\n applicant: e.applicant,\n blaType: e.blaType,\n strength: e.strength,\n dosageForm: e.dosageForm,\n route: e.route,\n status: e.status,\n licensingStatus: e.licensingStatus,\n biosimilar: e.biosimilar,\n interchangeable: e.interchangeable,\n referenceProductBla: e.referenceProductBla,\n referenceProductName: e.referenceProductProprietaryName,\n approvalDate: e.approvalDate,\n }))\n\n return {\n success: true,\n data: formatted,\n totalResults: total,\n displayedResults: items.length,\n dataSource: PURPLE_BOOK_SOURCE,\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n// ============================================================\n// search_fda_drug_patent_expiry\n// ============================================================\n\nexport type DrugPatentExpiryParams = {\n drugName?: string\n applNo?: string\n includeExclusivity?: boolean\n includePurpleBook?: boolean\n limit?: number\n skip?: number\n}\n\ntype PatentExpiryEntry = {\n source: string\n tradeName: string\n ingredient: string\n applNo: string\n patentNo: string\n patentExpireDate: string\n drugSubstanceFlag: string\n drugProductFlag: string\n patentUseCode: string\n exclusivities: Array<{ code: string; date: string }>\n}\n\nexport async function handleSearchDrugPatentExpiry(\n params: DrugPatentExpiryParams,\n): Promise<FDAToolResponse<PatentExpiryEntry[]>> {\n loggers.tools(\"searchDrugPatentExpiry\", params)\n\n try {\n const includeExclusivity = params.includeExclusivity !== false\n const includePurpleBook = params.includePurpleBook === true\n\n const { products, patents, exclusivities } = await bulkDataClient.getOrangeBookData()\n\n // Build product lookup\n const productMap = new Map<string, OrangeBookProduct>()\n for (const p of products) {\n productMap.set(obJoinKey(p), p)\n }\n\n // Build exclusivity lookup\n const exclusivityMap = new Map<string, OrangeBookExclusivity[]>()\n if (includeExclusivity) {\n for (const e of exclusivities) {\n const key = obJoinKey(e)\n const existing = exclusivityMap.get(key) ?? []\n existing.push(e)\n exclusivityMap.set(key, existing)\n }\n }\n\n // Filter patents by drug name / applNo\n const filteredPatents = patents.filter((pat) => {\n if (params.applNo && pat.applNo !== params.applNo) return false\n if (params.drugName) {\n const product = productMap.get(obJoinKey(pat))\n if (!product) return false\n if (!matchesFilter(product.tradeName, params.drugName) && !matchesFilter(product.ingredient, params.drugName))\n return false\n }\n return true\n })\n\n const entries: PatentExpiryEntry[] = filteredPatents.map((pat) => {\n const key = obJoinKey(pat)\n const product = productMap.get(key)\n const excl = includeExclusivity ? (exclusivityMap.get(key) ?? []) : []\n\n return {\n source: \"Orange Book\",\n tradeName: product?.tradeName ?? \"\",\n ingredient: product?.ingredient ?? \"\",\n applNo: pat.applNo,\n patentNo: pat.patentNo,\n patentExpireDate: pat.patentExpireDate,\n drugSubstanceFlag: pat.drugSubstanceFlag,\n drugProductFlag: pat.drugProductFlag,\n patentUseCode: pat.patentUseCode,\n exclusivities: excl.map((e) => ({ code: e.exclusivityCode, date: e.exclusivityDate })),\n }\n })\n\n // Optionally add Purple Book entries that match\n if (includePurpleBook) {\n try {\n const purpleEntries = await bulkDataClient.getPurpleBookData()\n const filteredPurple = purpleEntries.filter((e) => {\n if (params.drugName) {\n if (!matchesFilter(e.proprietaryName, params.drugName) && !matchesFilter(e.properName, params.drugName))\n return false\n }\n if (params.applNo && e.blaNumber !== params.applNo) return false\n return true\n })\n\n for (const pe of filteredPurple) {\n entries.push({\n source: \"Purple Book\",\n tradeName: pe.proprietaryName,\n ingredient: pe.properName,\n applNo: pe.blaNumber,\n patentNo: \"\",\n patentExpireDate: \"\",\n drugSubstanceFlag: \"\",\n drugProductFlag: \"\",\n patentUseCode: \"\",\n exclusivities: [],\n })\n }\n } catch (error) {\n loggers.bulk(`Purple Book data unavailable: ${error instanceof Error ? error.message : String(error)}`)\n }\n }\n\n // Sort by earliest expiry date (non-empty dates first)\n entries.sort((a, b) => {\n if (!a.patentExpireDate && !b.patentExpireDate) return 0\n if (!a.patentExpireDate) return 1\n if (!b.patentExpireDate) return -1\n return a.patentExpireDate.localeCompare(b.patentExpireDate)\n })\n\n const { items, total } = applyPagination(entries, params.limit, params.skip)\n\n return {\n success: true,\n data: items,\n totalResults: total,\n displayedResults: items.length,\n dataSource: ORANGE_BOOK_SOURCE,\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { FDAToolResponse } from "../types/fda.js";
|
|
2
2
|
|
|
3
3
|
//#region src/handlers/device-handlers.d.ts
|
|
4
|
-
|
|
5
4
|
type Device510KParams = {
|
|
6
5
|
deviceName?: string;
|
|
7
6
|
applicant?: string;
|
|
@@ -50,6 +49,7 @@ type FormattedDeviceClassification = {
|
|
|
50
49
|
};
|
|
51
50
|
declare function handleSearchDeviceClassifications(params: DeviceClassificationParams): Promise<FDAToolResponse<FormattedDeviceClassification[]>>;
|
|
52
51
|
type DeviceAdverseEventsParams = {
|
|
52
|
+
reportNumber?: string;
|
|
53
53
|
deviceName?: string;
|
|
54
54
|
brandName?: string;
|
|
55
55
|
manufacturerName?: string;
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
import"../utils/logger.js";import"../api-client
|
|
1
|
+
import{loggers as e}from"../utils/logger.js";import{fdaAPIClient as t}from"../utils/api-client.js";function n(e,t,n){if(!(!t&&!n))return`${e}:[${t?.replace(/-/g,``)??`*`}+TO+${n?.replace(/-/g,``)??`*`}]`}function r(e){return e.replace(/[+\-&|!(){}[\]^"~*?:\\]/g,`\\$&`).replace(/\s+/g,`+`)}function i(e){let t=e.filter(e=>e.value!==void 0&&e.value.trim()!==``);return t.length===0?``:t.map(e=>`${e.field}:"${r(e.value)}"`).join(`+AND+`)}function a(e,t=200){if(e)return e.length>t?e.substring(0,t)+`...`:e}function o(e){return{kNumber:e.k_number,deviceName:e.device_name??e.openfda?.device_name,applicant:e.applicant,productCode:e.product_code,clearanceType:e.clearance_type,decisionCode:e.decision_code,decisionDescription:e.decision_description,decisionDate:e.decision_date,dateReceived:e.date_received,city:e.city,state:e.state,country:e.country_code}}async function s(r){e.tools(`searchDevice510K`,r);try{let e={search:[i([{field:`device_name`,value:r.deviceName},{field:`applicant`,value:r.applicant},{field:`product_code`,value:r.productCode},{field:`clearance_type`,value:r.clearanceType}]),n(`decision_date`,r.decisionDateFrom,r.decisionDateTo)].filter(Boolean).join(`+AND+`)||void 0,limit:r.limit,skip:r.skip},a=await t.searchDevice510K(e),s=a.results??[];return{success:!0,data:s.map(o),totalResults:a.meta?.results?.total,displayedResults:s.length,searchParams:e,apiUsage:t.getRateLimitInfo()}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}function c(e){return{deviceName:e.device_name,deviceClass:e.device_class,definition:a(e.definition),medicalSpecialty:e.medical_specialty,medicalSpecialtyDescription:e.medical_specialty_description,productCode:e.product_code,regulationNumber:e.regulation_number,gmpExempt:e.gmp_exempt_flag===`Y`,implant:e.implant_flag===`Y`,lifeSustaining:e.life_sustain_support_flag===`Y`}}async function l(n){e.tools(`searchDeviceClassifications`,n);try{let e={search:i([{field:`device_name`,value:n.deviceName},{field:`device_class`,value:n.deviceClass},{field:`medical_specialty`,value:n.medicalSpecialty},{field:`product_code`,value:n.productCode},{field:`regulation_number`,value:n.regulationNumber}])||void 0,limit:n.limit,skip:n.skip},r=await t.searchDeviceClassifications(e),a=r.results??[];return{success:!0,data:a.map(c),totalResults:r.meta?.results?.total,displayedResults:a.length,searchParams:e,apiUsage:t.getRateLimitInfo()}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}function u(e){return{reportNumber:e.report_number,dateOfEvent:e.date_of_event,dateReceived:e.date_received,eventType:e.event_type,adverseEventFlag:e.adverse_event_flag===`Y`,productProblemFlag:e.product_problem_flag===`Y`,devices:e.device?.slice(0,3).map(e=>({brandName:e.brand_name,genericName:e.generic_name,manufacturerName:e.manufacturer_d_name,modelNumber:e.model_number,productCode:e.device_report_product_code,deviceClass:e.openfda?.device_class}))??[],mdrText:e.mdr_text?.slice(0,2).map(e=>({textType:e.text_type_code,text:a(e.text,300)}))??[]}}async function d(a){e.tools(`searchDeviceAdverseEvents`,a);try{if(a.reportNumber){let e={search:`report_number:"${r(a.reportNumber)}"`,limit:1},n=await t.searchDeviceAdverseEvents(e),i=n.results??[];return{success:!0,data:i.map(u),totalResults:n.meta?.results?.total,displayedResults:i.length,searchParams:e,apiUsage:t.getRateLimitInfo()}}let e={search:[i([{field:`device.generic_name`,value:a.deviceName},{field:`device.brand_name`,value:a.brandName},{field:`device.manufacturer_d_name`,value:a.manufacturerName},{field:`event_type`,value:a.eventType}]),n(`date_received`,a.dateFrom,a.dateTo)].filter(Boolean).join(`+AND+`)||void 0,limit:a.limit,skip:a.skip},o=await t.searchDeviceAdverseEvents(e),s=o.results??[];return{success:!0,data:s.map(u),totalResults:o.meta?.results?.total,displayedResults:s.length,searchParams:e,apiUsage:t.getRateLimitInfo()}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}function f(e){return{recallNumber:e.recall_number,recallingFirm:e.recalling_firm,classification:e.classification,status:e.status,productDescription:a(e.product_description),reasonForRecall:a(e.reason_for_recall),recallInitiationDate:e.recall_initiation_date,distributionPattern:a(e.distribution_pattern),city:e.city,state:e.state,deviceName:e.openfda?.device_name,deviceClass:e.openfda?.device_class}}async function p(r){e.tools(`searchDeviceEnforcement`,r);try{let e={search:[i([{field:`recalling_firm`,value:r.recallingFirm},{field:`product_description`,value:r.productDescription},{field:`classification`,value:r.classification},{field:`status`,value:r.status}]),n(`recall_initiation_date`,r.dateFrom,r.dateTo)].filter(Boolean).join(`+AND+`)||void 0,limit:r.limit,skip:r.skip},a=await t.searchDeviceEnforcement(e),o=a.results??[];return{success:!0,data:o.map(f),totalResults:a.meta?.results?.total,displayedResults:o.length,searchParams:e,apiUsage:t.getRateLimitInfo()}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}export{s as handleSearchDevice510K,d as handleSearchDeviceAdverseEvents,l as handleSearchDeviceClassifications,p as handleSearchDeviceEnforcement};
|
|
2
|
+
//# sourceMappingURL=device-handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-handlers.js","names":[],"sources":["../../src/handlers/device-handlers.ts"],"sourcesContent":["/**\n * Device Handlers\n * Query handlers for FDA device-related endpoints\n */\n\nimport type {\n Device510K,\n DeviceAdverseEvent,\n DeviceClassification,\n DeviceEnforcement,\n FDAToolResponse,\n SearchParams,\n} from \"../types/fda.js\"\nimport { fdaAPIClient } from \"../utils/api-client.js\"\nimport { loggers } from \"../utils/logger.js\"\n\n// Helper to build date range query\nfunction buildDateQuery(field: string, dateFrom?: string, dateTo?: string): string | undefined {\n if (!dateFrom && !dateTo) return undefined\n\n // FDA date format is YYYYMMDD\n const from = dateFrom?.replace(/-/g, \"\") ?? \"*\"\n const to = dateTo?.replace(/-/g, \"\") ?? \"*\"\n\n return `${field}:[${from}+TO+${to}]`\n}\n\n// Helper to escape special characters in search terms\nfunction escapeSearchTerm(term: string): string {\n return term.replace(/[+\\-&|!(){}[\\]^\"~*?:\\\\]/g, \"\\\\$&\").replace(/\\s+/g, \"+\")\n}\n\n// Helper to build a search query from multiple terms\nfunction buildSearchQuery(terms: Array<{ field: string; value?: string }>): string {\n const validTerms = terms.filter((t) => t.value !== undefined && t.value.trim() !== \"\")\n if (validTerms.length === 0) return \"\"\n\n return validTerms.map((t) => `${t.field}:\"${escapeSearchTerm(t.value!)}\"`).join(\"+AND+\")\n}\n\n// Format truncated text\nfunction truncateText(text: string | undefined, maxLength = 200): string | undefined {\n if (!text) return undefined\n return text.length > maxLength ? text.substring(0, maxLength) + \"...\" : text\n}\n\n// Device 510(k) Handler\nexport type Device510KParams = {\n deviceName?: string\n applicant?: string\n productCode?: string\n clearanceType?: string\n decisionDateFrom?: string\n decisionDateTo?: string\n limit?: number\n skip?: number\n}\n\ntype FormattedDevice510K = {\n kNumber: string | undefined\n deviceName: string | undefined\n applicant: string | undefined\n productCode: string | undefined\n clearanceType: string | undefined\n decisionCode: string | undefined\n decisionDescription: string | undefined\n decisionDate: string | undefined\n dateReceived: string | undefined\n city: string | undefined\n state: string | undefined\n country: string | undefined\n}\n\nfunction formatDevice510K(device: Device510K): FormattedDevice510K {\n return {\n kNumber: device.k_number,\n deviceName: device.device_name ?? device.openfda?.device_name,\n applicant: device.applicant,\n productCode: device.product_code,\n clearanceType: device.clearance_type,\n decisionCode: device.decision_code,\n decisionDescription: device.decision_description,\n decisionDate: device.decision_date,\n dateReceived: device.date_received,\n city: device.city,\n state: device.state,\n country: device.country_code,\n }\n}\n\nexport async function handleSearchDevice510K(\n params: Device510KParams,\n): Promise<FDAToolResponse<FormattedDevice510K[]>> {\n loggers.tools(\"searchDevice510K\", params)\n\n try {\n const searchTerms: Array<{ field: string; value?: string }> = [\n { field: \"device_name\", value: params.deviceName },\n { field: \"applicant\", value: params.applicant },\n { field: \"product_code\", value: params.productCode },\n { field: \"clearance_type\", value: params.clearanceType },\n ]\n\n const searchQuery = buildSearchQuery(searchTerms)\n const dateQuery = buildDateQuery(\"decision_date\", params.decisionDateFrom, params.decisionDateTo)\n const fullQuery = [searchQuery, dateQuery].filter(Boolean).join(\"+AND+\")\n\n const searchParams: SearchParams = {\n search: fullQuery || undefined,\n limit: params.limit,\n skip: params.skip,\n }\n\n const response = await fdaAPIClient.searchDevice510K(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map(formatDevice510K),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n// Device Classification Handler\nexport type DeviceClassificationParams = {\n deviceName?: string\n deviceClass?: \"1\" | \"2\" | \"3\"\n medicalSpecialty?: string\n productCode?: string\n regulationNumber?: string\n limit?: number\n skip?: number\n}\n\ntype FormattedDeviceClassification = {\n deviceName: string | undefined\n deviceClass: string | undefined\n definition: string | undefined\n medicalSpecialty: string | undefined\n medicalSpecialtyDescription: string | undefined\n productCode: string | undefined\n regulationNumber: string | undefined\n gmpExempt: boolean\n implant: boolean\n lifeSustaining: boolean\n}\n\nfunction formatDeviceClassification(classification: DeviceClassification): FormattedDeviceClassification {\n return {\n deviceName: classification.device_name,\n deviceClass: classification.device_class,\n definition: truncateText(classification.definition),\n medicalSpecialty: classification.medical_specialty,\n medicalSpecialtyDescription: classification.medical_specialty_description,\n productCode: classification.product_code,\n regulationNumber: classification.regulation_number,\n gmpExempt: classification.gmp_exempt_flag === \"Y\",\n implant: classification.implant_flag === \"Y\",\n lifeSustaining: classification.life_sustain_support_flag === \"Y\",\n }\n}\n\nexport async function handleSearchDeviceClassifications(\n params: DeviceClassificationParams,\n): Promise<FDAToolResponse<FormattedDeviceClassification[]>> {\n loggers.tools(\"searchDeviceClassifications\", params)\n\n try {\n const searchTerms: Array<{ field: string; value?: string }> = [\n { field: \"device_name\", value: params.deviceName },\n { field: \"device_class\", value: params.deviceClass },\n { field: \"medical_specialty\", value: params.medicalSpecialty },\n { field: \"product_code\", value: params.productCode },\n { field: \"regulation_number\", value: params.regulationNumber },\n ]\n\n const searchQuery = buildSearchQuery(searchTerms)\n\n const searchParams: SearchParams = {\n search: searchQuery || undefined,\n limit: params.limit,\n skip: params.skip,\n }\n\n const response = await fdaAPIClient.searchDeviceClassifications(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map(formatDeviceClassification),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n// Device Adverse Events (MDR) Handler\nexport type DeviceAdverseEventsParams = {\n reportNumber?: string\n deviceName?: string\n brandName?: string\n manufacturerName?: string\n eventType?: \"Injury\" | \"Malfunction\" | \"Death\" | \"Other\"\n dateFrom?: string\n dateTo?: string\n limit?: number\n skip?: number\n}\n\ntype FormattedDeviceAdverseEvent = {\n reportNumber: string | undefined\n dateOfEvent: string | undefined\n dateReceived: string | undefined\n eventType: string | undefined\n adverseEventFlag: boolean\n productProblemFlag: boolean\n devices: Array<{\n brandName: string | undefined\n genericName: string | undefined\n manufacturerName: string | undefined\n modelNumber: string | undefined\n productCode: string | undefined\n deviceClass: string | undefined\n }>\n mdrText: Array<{\n textType: string | undefined\n text: string | undefined\n }>\n}\n\nfunction formatDeviceAdverseEvent(event: DeviceAdverseEvent): FormattedDeviceAdverseEvent {\n return {\n reportNumber: event.report_number,\n dateOfEvent: event.date_of_event,\n dateReceived: event.date_received,\n eventType: event.event_type,\n adverseEventFlag: event.adverse_event_flag === \"Y\",\n productProblemFlag: event.product_problem_flag === \"Y\",\n devices:\n event.device?.slice(0, 3).map((d) => ({\n brandName: d.brand_name,\n genericName: d.generic_name,\n manufacturerName: d.manufacturer_d_name,\n modelNumber: d.model_number,\n productCode: d.device_report_product_code,\n deviceClass: d.openfda?.device_class,\n })) ?? [],\n mdrText:\n event.mdr_text?.slice(0, 2).map((t) => ({\n textType: t.text_type_code,\n text: truncateText(t.text, 300),\n })) ?? [],\n }\n}\n\nexport async function handleSearchDeviceAdverseEvents(\n params: DeviceAdverseEventsParams,\n): Promise<FDAToolResponse<FormattedDeviceAdverseEvent[]>> {\n loggers.tools(\"searchDeviceAdverseEvents\", params)\n\n try {\n // If reportNumber is provided, do a direct lookup\n if (params.reportNumber) {\n const searchParams: SearchParams = {\n search: `report_number:\"${escapeSearchTerm(params.reportNumber)}\"`,\n limit: 1,\n }\n\n const response = await fdaAPIClient.searchDeviceAdverseEvents(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map(formatDeviceAdverseEvent),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n }\n\n const searchTerms: Array<{ field: string; value?: string }> = [\n { field: \"device.generic_name\", value: params.deviceName },\n { field: \"device.brand_name\", value: params.brandName },\n { field: \"device.manufacturer_d_name\", value: params.manufacturerName },\n { field: \"event_type\", value: params.eventType },\n ]\n\n const searchQuery = buildSearchQuery(searchTerms)\n const dateQuery = buildDateQuery(\"date_received\", params.dateFrom, params.dateTo)\n const fullQuery = [searchQuery, dateQuery].filter(Boolean).join(\"+AND+\")\n\n const searchParams: SearchParams = {\n search: fullQuery || undefined,\n limit: params.limit,\n skip: params.skip,\n }\n\n const response = await fdaAPIClient.searchDeviceAdverseEvents(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map(formatDeviceAdverseEvent),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n// Device Enforcement (Recalls) Handler\nexport type DeviceEnforcementParams = {\n recallingFirm?: string\n productDescription?: string\n classification?: \"Class I\" | \"Class II\" | \"Class III\"\n status?: \"Ongoing\" | \"Completed\" | \"Terminated\" | \"Pending\"\n dateFrom?: string\n dateTo?: string\n limit?: number\n skip?: number\n}\n\ntype FormattedDeviceEnforcement = {\n recallNumber: string | undefined\n recallingFirm: string | undefined\n classification: string | undefined\n status: string | undefined\n productDescription: string | undefined\n reasonForRecall: string | undefined\n recallInitiationDate: string | undefined\n distributionPattern: string | undefined\n city: string | undefined\n state: string | undefined\n deviceName: string | undefined\n deviceClass: string | undefined\n}\n\nfunction formatDeviceEnforcement(recall: DeviceEnforcement): FormattedDeviceEnforcement {\n return {\n recallNumber: recall.recall_number,\n recallingFirm: recall.recalling_firm,\n classification: recall.classification,\n status: recall.status,\n productDescription: truncateText(recall.product_description),\n reasonForRecall: truncateText(recall.reason_for_recall),\n recallInitiationDate: recall.recall_initiation_date,\n distributionPattern: truncateText(recall.distribution_pattern),\n city: recall.city,\n state: recall.state,\n deviceName: recall.openfda?.device_name,\n deviceClass: recall.openfda?.device_class,\n }\n}\n\nexport async function handleSearchDeviceEnforcement(\n params: DeviceEnforcementParams,\n): Promise<FDAToolResponse<FormattedDeviceEnforcement[]>> {\n loggers.tools(\"searchDeviceEnforcement\", params)\n\n try {\n const searchTerms: Array<{ field: string; value?: string }> = [\n { field: \"recalling_firm\", value: params.recallingFirm },\n { field: \"product_description\", value: params.productDescription },\n { field: \"classification\", value: params.classification },\n { field: \"status\", value: params.status },\n ]\n\n const searchQuery = buildSearchQuery(searchTerms)\n const dateQuery = buildDateQuery(\"recall_initiation_date\", params.dateFrom, params.dateTo)\n const fullQuery = [searchQuery, dateQuery].filter(Boolean).join(\"+AND+\")\n\n const searchParams: SearchParams = {\n search: fullQuery || undefined,\n limit: params.limit,\n skip: params.skip,\n }\n\n const response = await fdaAPIClient.searchDeviceEnforcement(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map(formatDeviceEnforcement),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n"],"mappings":"mGAiBA,SAAS,EAAe,EAAe,EAAmB,EAAqC,CACzF,MAAC,GAAY,CAAC,GAMlB,MAAO,GAAG,EAAM,IAHH,GAAU,QAAQ,KAAM,GAAG,EAAI,IAGnB,MAFd,GAAQ,QAAQ,KAAM,GAAG,EAAI,IAEN,GAIpC,SAAS,EAAiB,EAAsB,CAC9C,OAAO,EAAK,QAAQ,2BAA4B,OAAO,CAAC,QAAQ,OAAQ,IAAI,CAI9E,SAAS,EAAiB,EAAyD,CACjF,IAAM,EAAa,EAAM,OAAQ,GAAM,EAAE,QAAU,IAAA,IAAa,EAAE,MAAM,MAAM,GAAK,GAAG,CAGtF,OAFI,EAAW,SAAW,EAAU,GAE7B,EAAW,IAAK,GAAM,GAAG,EAAE,MAAM,IAAI,EAAiB,EAAE,MAAO,CAAC,GAAG,CAAC,KAAK,QAAQ,CAI1F,SAAS,EAAa,EAA0B,EAAY,IAAyB,CAC9E,KACL,OAAO,EAAK,OAAS,EAAY,EAAK,UAAU,EAAG,EAAU,CAAG,MAAQ,EA8B1E,SAAS,EAAiB,EAAyC,CACjE,MAAO,CACL,QAAS,EAAO,SAChB,WAAY,EAAO,aAAe,EAAO,SAAS,YAClD,UAAW,EAAO,UAClB,YAAa,EAAO,aACpB,cAAe,EAAO,eACtB,aAAc,EAAO,cACrB,oBAAqB,EAAO,qBAC5B,aAAc,EAAO,cACrB,aAAc,EAAO,cACrB,KAAM,EAAO,KACb,MAAO,EAAO,MACd,QAAS,EAAO,aACjB,CAGH,eAAsB,EACpB,EACiD,CACjD,EAAQ,MAAM,mBAAoB,EAAO,CAEzC,GAAI,CAYF,IAAM,EAA6B,CACjC,OAHgB,CAFE,EAP0C,CAC5D,CAAE,MAAO,cAAe,MAAO,EAAO,WAAY,CAClD,CAAE,MAAO,YAAa,MAAO,EAAO,UAAW,CAC/C,CAAE,MAAO,eAAgB,MAAO,EAAO,YAAa,CACpD,CAAE,MAAO,iBAAkB,MAAO,EAAO,cAAe,CACzD,CAEgD,CAC/B,EAAe,gBAAiB,EAAO,iBAAkB,EAAO,eAAe,CACvD,CAAC,OAAO,QAAQ,CAAC,KAAK,QAAQ,EAGjD,IAAA,GACrB,MAAO,EAAO,MACd,KAAM,EAAO,KACd,CAEK,EAAW,MAAM,EAAa,iBAAiB,EAAa,CAC5D,EAAU,EAAS,SAAW,EAAE,CAEtC,MAAO,CACL,QAAS,GACT,KAAM,EAAQ,IAAI,EAAiB,CACnC,aAAc,EAAS,MAAM,SAAS,MACtC,iBAAkB,EAAQ,OAC1B,eACA,SAAU,EAAa,kBAAkB,CAC1C,OACM,EAAO,CACd,MAAO,CACL,QAAS,GACT,MAAO,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CAC9D,EA4BL,SAAS,EAA2B,EAAqE,CACvG,MAAO,CACL,WAAY,EAAe,YAC3B,YAAa,EAAe,aAC5B,WAAY,EAAa,EAAe,WAAW,CACnD,iBAAkB,EAAe,kBACjC,4BAA6B,EAAe,8BAC5C,YAAa,EAAe,aAC5B,iBAAkB,EAAe,kBACjC,UAAW,EAAe,kBAAoB,IAC9C,QAAS,EAAe,eAAiB,IACzC,eAAgB,EAAe,4BAA8B,IAC9D,CAGH,eAAsB,EACpB,EAC2D,CAC3D,EAAQ,MAAM,8BAA+B,EAAO,CAEpD,GAAI,CAWF,IAAM,EAA6B,CACjC,OAHkB,EAR0C,CAC5D,CAAE,MAAO,cAAe,MAAO,EAAO,WAAY,CAClD,CAAE,MAAO,eAAgB,MAAO,EAAO,YAAa,CACpD,CAAE,MAAO,oBAAqB,MAAO,EAAO,iBAAkB,CAC9D,CAAE,MAAO,eAAgB,MAAO,EAAO,YAAa,CACpD,CAAE,MAAO,oBAAqB,MAAO,EAAO,iBAAkB,CAC/D,CAEgD,EAGxB,IAAA,GACvB,MAAO,EAAO,MACd,KAAM,EAAO,KACd,CAEK,EAAW,MAAM,EAAa,4BAA4B,EAAa,CACvE,EAAU,EAAS,SAAW,EAAE,CAEtC,MAAO,CACL,QAAS,GACT,KAAM,EAAQ,IAAI,EAA2B,CAC7C,aAAc,EAAS,MAAM,SAAS,MACtC,iBAAkB,EAAQ,OAC1B,eACA,SAAU,EAAa,kBAAkB,CAC1C,OACM,EAAO,CACd,MAAO,CACL,QAAS,GACT,MAAO,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CAC9D,EAsCL,SAAS,EAAyB,EAAwD,CACxF,MAAO,CACL,aAAc,EAAM,cACpB,YAAa,EAAM,cACnB,aAAc,EAAM,cACpB,UAAW,EAAM,WACjB,iBAAkB,EAAM,qBAAuB,IAC/C,mBAAoB,EAAM,uBAAyB,IACnD,QACE,EAAM,QAAQ,MAAM,EAAG,EAAE,CAAC,IAAK,IAAO,CACpC,UAAW,EAAE,WACb,YAAa,EAAE,aACf,iBAAkB,EAAE,oBACpB,YAAa,EAAE,aACf,YAAa,EAAE,2BACf,YAAa,EAAE,SAAS,aACzB,EAAE,EAAI,EAAE,CACX,QACE,EAAM,UAAU,MAAM,EAAG,EAAE,CAAC,IAAK,IAAO,CACtC,SAAU,EAAE,eACZ,KAAM,EAAa,EAAE,KAAM,IAAI,CAChC,EAAE,EAAI,EAAE,CACZ,CAGH,eAAsB,EACpB,EACyD,CACzD,EAAQ,MAAM,4BAA6B,EAAO,CAElD,GAAI,CAEF,GAAI,EAAO,aAAc,CACvB,IAAM,EAA6B,CACjC,OAAQ,kBAAkB,EAAiB,EAAO,aAAa,CAAC,GAChE,MAAO,EACR,CAEK,EAAW,MAAM,EAAa,0BAA0B,EAAa,CACrE,EAAU,EAAS,SAAW,EAAE,CAEtC,MAAO,CACL,QAAS,GACT,KAAM,EAAQ,IAAI,EAAyB,CAC3C,aAAc,EAAS,MAAM,SAAS,MACtC,iBAAkB,EAAQ,OAC1B,eACA,SAAU,EAAa,kBAAkB,CAC1C,CAcH,IAAM,EAA6B,CACjC,OAHgB,CAFE,EAP0C,CAC5D,CAAE,MAAO,sBAAuB,MAAO,EAAO,WAAY,CAC1D,CAAE,MAAO,oBAAqB,MAAO,EAAO,UAAW,CACvD,CAAE,MAAO,6BAA8B,MAAO,EAAO,iBAAkB,CACvE,CAAE,MAAO,aAAc,MAAO,EAAO,UAAW,CACjD,CAEgD,CAC/B,EAAe,gBAAiB,EAAO,SAAU,EAAO,OAAO,CACvC,CAAC,OAAO,QAAQ,CAAC,KAAK,QAAQ,EAGjD,IAAA,GACrB,MAAO,EAAO,MACd,KAAM,EAAO,KACd,CAEK,EAAW,MAAM,EAAa,0BAA0B,EAAa,CACrE,EAAU,EAAS,SAAW,EAAE,CAEtC,MAAO,CACL,QAAS,GACT,KAAM,EAAQ,IAAI,EAAyB,CAC3C,aAAc,EAAS,MAAM,SAAS,MACtC,iBAAkB,EAAQ,OAC1B,eACA,SAAU,EAAa,kBAAkB,CAC1C,OACM,EAAO,CACd,MAAO,CACL,QAAS,GACT,MAAO,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CAC9D,EA+BL,SAAS,EAAwB,EAAuD,CACtF,MAAO,CACL,aAAc,EAAO,cACrB,cAAe,EAAO,eACtB,eAAgB,EAAO,eACvB,OAAQ,EAAO,OACf,mBAAoB,EAAa,EAAO,oBAAoB,CAC5D,gBAAiB,EAAa,EAAO,kBAAkB,CACvD,qBAAsB,EAAO,uBAC7B,oBAAqB,EAAa,EAAO,qBAAqB,CAC9D,KAAM,EAAO,KACb,MAAO,EAAO,MACd,WAAY,EAAO,SAAS,YAC5B,YAAa,EAAO,SAAS,aAC9B,CAGH,eAAsB,EACpB,EACwD,CACxD,EAAQ,MAAM,0BAA2B,EAAO,CAEhD,GAAI,CAYF,IAAM,EAA6B,CACjC,OAHgB,CAFE,EAP0C,CAC5D,CAAE,MAAO,iBAAkB,MAAO,EAAO,cAAe,CACxD,CAAE,MAAO,sBAAuB,MAAO,EAAO,mBAAoB,CAClE,CAAE,MAAO,iBAAkB,MAAO,EAAO,eAAgB,CACzD,CAAE,MAAO,SAAU,MAAO,EAAO,OAAQ,CAC1C,CAEgD,CAC/B,EAAe,yBAA0B,EAAO,SAAU,EAAO,OAAO,CAChD,CAAC,OAAO,QAAQ,CAAC,KAAK,QAAQ,EAGjD,IAAA,GACrB,MAAO,EAAO,MACd,KAAM,EAAO,KACd,CAEK,EAAW,MAAM,EAAa,wBAAwB,EAAa,CACnE,EAAU,EAAS,SAAW,EAAE,CAEtC,MAAO,CACL,QAAS,GACT,KAAM,EAAQ,IAAI,EAAwB,CAC1C,aAAc,EAAS,MAAM,SAAS,MACtC,iBAAkB,EAAQ,OAC1B,eACA,SAAU,EAAa,kBAAkB,CAC1C,OACM,EAAO,CACd,MAAO,CACL,QAAS,GACT,MAAO,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CAC9D"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { FDAToolResponse } from "../types/fda.js";
|
|
2
2
|
|
|
3
3
|
//#region src/handlers/drug-handlers.d.ts
|
|
4
|
-
|
|
5
4
|
type DrugAdverseEventsParams = {
|
|
5
|
+
safetyReportId?: string;
|
|
6
6
|
drugName?: string;
|
|
7
7
|
reaction?: string;
|
|
8
8
|
manufacturer?: string;
|
|
@@ -33,21 +33,36 @@ type FormattedDrugAdverseEvent = {
|
|
|
33
33
|
};
|
|
34
34
|
declare function handleSearchDrugAdverseEvents(params: DrugAdverseEventsParams): Promise<FDAToolResponse<FormattedDrugAdverseEvent[]>>;
|
|
35
35
|
type DrugLabelsParams = {
|
|
36
|
+
setId?: string;
|
|
36
37
|
drugName?: string;
|
|
37
38
|
indication?: string;
|
|
38
39
|
activeIngredient?: string;
|
|
39
40
|
route?: string;
|
|
41
|
+
hasBoxedWarning?: boolean;
|
|
42
|
+
sections?: string[];
|
|
40
43
|
limit?: number;
|
|
41
44
|
skip?: number;
|
|
42
45
|
};
|
|
43
46
|
type FormattedDrugLabel = {
|
|
47
|
+
setId: string | undefined;
|
|
44
48
|
brandName: string | undefined;
|
|
45
49
|
genericName: string | undefined;
|
|
46
50
|
manufacturer: string | undefined;
|
|
47
51
|
activeIngredients: string[];
|
|
52
|
+
boxedWarning: string | undefined;
|
|
48
53
|
indications: string | undefined;
|
|
49
54
|
warnings: string | undefined;
|
|
55
|
+
contraindications: string | undefined;
|
|
56
|
+
adverseReactions: string | undefined;
|
|
57
|
+
drugInteractions: string | undefined;
|
|
50
58
|
dosageAndAdministration: string | undefined;
|
|
59
|
+
clinicalPharmacology: string | undefined;
|
|
60
|
+
mechanismOfAction: string | undefined;
|
|
61
|
+
pharmacokinetics: string | undefined;
|
|
62
|
+
overdosage: string | undefined;
|
|
63
|
+
description: string | undefined;
|
|
64
|
+
howSupplied: string | undefined;
|
|
65
|
+
storageAndHandling: string | undefined;
|
|
51
66
|
route: string[];
|
|
52
67
|
};
|
|
53
68
|
declare function handleSearchDrugLabels(params: DrugLabelsParams): Promise<FDAToolResponse<FormattedDrugLabel[]>>;
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
import"../utils/logger.js";import"../api-client
|
|
1
|
+
import{loggers as e}from"../utils/logger.js";import{fdaAPIClient as t}from"../utils/api-client.js";function n(e,t,n){if(!(!t&&!n))return`${e}:[${t?.replace(/-/g,``)??`*`}+TO+${n?.replace(/-/g,``)??`*`}]`}function r(e){return e.replace(/[+\-&|!(){}[\]^"~*?:\\]/g,`\\$&`).replace(/\s+/g,`+`)}function i(e){let t=e.filter(e=>e.value!==void 0&&e.value.trim()!==``);return t.length===0?``:t.map(e=>`${e.field}:"${r(e.value)}"`).join(`+AND+`)}function a(e,t=200){if(e)return e.length>t?e.substring(0,t)+`...`:e}function o(e){return{reportId:e.safetyreportid,receiveDate:e.receivedate,serious:e.serious===`1`,drugs:e.patient?.drug?.slice(0,3).map(e=>({name:e.medicinalproduct??e.activesubstance?.activesubstancename,indication:e.drugindication,route:e.drugadministrationroute}))??[],reactions:e.patient?.reaction?.slice(0,3).map(e=>({reaction:e.reactionmeddrapt,outcome:e.reactionoutcome}))??[],patient:{age:e.patient?.patientonsetage?`${e.patient.patientonsetage} ${e.patient.patientonsetageunit??``}`:void 0,sex:e.patient?.patientsex===`1`?`Male`:e.patient?.patientsex===`2`?`Female`:void 0,weight:e.patient?.patientweight?`${e.patient.patientweight} kg`:void 0}}}async function s(a){e.tools(`searchDrugAdverseEvents`,a);try{if(a.safetyReportId){let e={search:`safetyreportid:"${r(a.safetyReportId)}"`,limit:1},n=await t.searchDrugAdverseEvents(e),i=n.results??[];return{success:!0,data:i.map(o),totalResults:n.meta?.results?.total,displayedResults:i.length,searchParams:e,apiUsage:t.getRateLimitInfo()}}let e=[{field:`patient.drug.medicinalproduct`,value:a.drugName},{field:`patient.reaction.reactionmeddrapt`,value:a.reaction},{field:`patient.drug.openfda.manufacturer_name`,value:a.manufacturer}];a.serious!==void 0&&e.push({field:`serious`,value:a.serious?`1`:`2`});let s={search:[i(e),n(`receivedate`,a.dateFrom,a.dateTo)].filter(Boolean).join(`+AND+`)||void 0,limit:a.limit,skip:a.skip},c=await t.searchDrugAdverseEvents(s),l=c.results??[];return{success:!0,data:l.map(o),totalResults:c.meta?.results?.total,displayedResults:l.length,searchParams:s,apiUsage:t.getRateLimitInfo()}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}function c(e,t){let n=e=>!t||t.includes(e);return{setId:e.set_id,brandName:e.openfda?.brand_name?.[0],genericName:e.openfda?.generic_name?.[0],manufacturer:e.openfda?.manufacturer_name?.[0],activeIngredients:e.openfda?.substance_name?.slice(0,5)??[],boxedWarning:n(`boxed_warning`)?a(e.boxed_warning?.[0],500):void 0,indications:n(`indications_and_usage`)?a(e.indications_and_usage?.[0]):void 0,warnings:n(`warnings`)?a(e.warnings?.[0]):void 0,contraindications:n(`contraindications`)?a(e.contraindications?.[0]):void 0,adverseReactions:n(`adverse_reactions`)?a(e.adverse_reactions?.[0]):void 0,drugInteractions:n(`drug_interactions`)?a(e.drug_interactions?.[0]):void 0,dosageAndAdministration:n(`dosage_and_administration`)?a(e.dosage_and_administration?.[0]):void 0,clinicalPharmacology:n(`clinical_pharmacology`)?a(e.clinical_pharmacology?.[0]):void 0,mechanismOfAction:n(`mechanism_of_action`)?a(e.mechanism_of_action?.[0]):void 0,pharmacokinetics:n(`pharmacokinetics`)?a(e.pharmacokinetics?.[0]):void 0,overdosage:n(`overdosage`)?a(e.overdosage?.[0]):void 0,description:n(`description`)?a(e.description?.[0]):void 0,howSupplied:n(`how_supplied`)?a(e.how_supplied?.[0]):void 0,storageAndHandling:n(`storage_and_handling`)?a(e.storage_and_handling?.[0]):void 0,route:e.openfda?.route?.slice(0,3)??[]}}async function l(n){e.tools(`searchDrugLabels`,n);try{if(n.setId){let e={search:`set_id:"${r(n.setId)}"`,limit:1},i=await t.searchDrugLabels(e),a=i.results??[];return{success:!0,data:a.map(e=>c(e,n.sections)),totalResults:i.meta?.results?.total,displayedResults:a.length,searchParams:e,apiUsage:t.getRateLimitInfo()}}let e={search:[i([{field:`openfda.brand_name`,value:n.drugName},{field:`indications_and_usage`,value:n.indication},{field:`openfda.substance_name`,value:n.activeIngredient},{field:`openfda.route`,value:n.route}]),n.hasBoxedWarning?`_exists_:boxed_warning`:void 0].filter(Boolean).join(`+AND+`)||void 0,limit:n.limit,skip:n.skip},a=await t.searchDrugLabels(e),o=a.results??[];return{success:!0,data:o.map(e=>c(e,n.sections)),totalResults:a.meta?.results?.total,displayedResults:o.length,searchParams:e,apiUsage:t.getRateLimitInfo()}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}function u(e){return{productNdc:e.product_ndc,brandName:e.brand_name,genericName:e.generic_name,labelerName:e.labeler_name,dosageForm:e.dosage_form,route:e.route??[],activeIngredients:e.active_ingredients?.slice(0,3).map(e=>({name:e.name,strength:e.strength}))??[],marketingStartDate:e.marketing_start_date,productType:e.product_type}}async function d(n){e.tools(`searchDrugNDC`,n);try{let e={search:i([{field:`product_ndc`,value:n.productNdc},{field:`brand_name`,value:n.brandName},{field:`generic_name`,value:n.genericName},{field:`labeler_name`,value:n.labelerName},{field:`dosage_form`,value:n.dosageForm},{field:`route`,value:n.route}])||void 0,limit:n.limit,skip:n.skip},r=await t.searchDrugNDC(e),a=r.results??[];return{success:!0,data:a.map(u),totalResults:r.meta?.results?.total,displayedResults:a.length,searchParams:e,apiUsage:t.getRateLimitInfo()}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}function f(e){return{recallNumber:e.recall_number,recallingFirm:e.recalling_firm,classification:e.classification,status:e.status,productDescription:a(e.product_description),reasonForRecall:a(e.reason_for_recall),recallInitiationDate:e.recall_initiation_date,distributionPattern:a(e.distribution_pattern),city:e.city,state:e.state}}async function p(r){e.tools(`searchDrugEnforcement`,r);try{let e={search:[i([{field:`recalling_firm`,value:r.recallingFirm},{field:`classification`,value:r.classification},{field:`status`,value:r.status},{field:`state`,value:r.state}]),n(`recall_initiation_date`,r.dateFrom,r.dateTo)].filter(Boolean).join(`+AND+`)||void 0,limit:r.limit,skip:r.skip},a=await t.searchDrugEnforcement(e),o=a.results??[];return{success:!0,data:o.map(f),totalResults:a.meta?.results?.total,displayedResults:o.length,searchParams:e,apiUsage:t.getRateLimitInfo()}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}function m(e){let t=e.submissions?.[0];return{applicationNumber:e.application_number,sponsorName:e.sponsor_name,products:e.products?.slice(0,3).map(e=>({brandName:e.brand_name,dosageForm:e.dosage_form,route:e.route,marketingStatus:e.marketing_status,activeIngredients:e.active_ingredients?.slice(0,3).map(e=>({name:e.name,strength:e.strength}))??[]}))??[],latestSubmission:t?{type:t.submission_type,status:t.submission_status,statusDate:t.submission_status_date}:void 0}}async function h(n){e.tools(`searchDrugsFDA`,n);try{let e={search:i([{field:`sponsor_name`,value:n.sponsorName},{field:`application_number`,value:n.applicationNumber},{field:`products.brand_name`,value:n.brandName},{field:`products.marketing_status`,value:n.marketingStatus}])||void 0,limit:n.limit,skip:n.skip},r=await t.searchDrugsFDA(e),a=r.results??[];return{success:!0,data:a.map(m),totalResults:r.meta?.results?.total,displayedResults:a.length,searchParams:e,apiUsage:t.getRateLimitInfo()}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}function g(e){return{genericName:e.generic_name,proprietaryName:e.proprietary_name,status:e.status,description:a(e.description),initialPostingDate:e.initial_posting_date,resolvedShortageDate:e.resolved_shortage_date}}async function _(n){e.tools(`searchDrugShortages`,n);try{let e={search:i([{field:`generic_name`,value:n.genericName},{field:`status`,value:n.status}])||void 0,limit:n.limit,skip:n.skip},r=await t.searchDrugShortages(e),a=r.results??[];return{success:!0,data:a.map(g),totalResults:r.meta?.results?.total,displayedResults:a.length,searchParams:e,apiUsage:t.getRateLimitInfo()}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}export{s as handleSearchDrugAdverseEvents,p as handleSearchDrugEnforcement,l as handleSearchDrugLabels,d as handleSearchDrugNDC,_ as handleSearchDrugShortages,h as handleSearchDrugsFDA};
|
|
2
|
+
//# sourceMappingURL=drug-handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drug-handlers.js","names":[],"sources":["../../src/handlers/drug-handlers.ts"],"sourcesContent":["/**\n * Drug Handlers\n * Query handlers for FDA drug-related endpoints\n */\n\nimport type {\n DrugAdverseEvent,\n DrugEnforcement,\n DrugLabel,\n DrugNDC,\n DrugsFDA,\n DrugShortage,\n FDAToolResponse,\n SearchParams,\n} from \"../types/fda.js\"\nimport { fdaAPIClient } from \"../utils/api-client.js\"\nimport { loggers } from \"../utils/logger.js\"\n\n// Helper to build date range query\nfunction buildDateQuery(field: string, dateFrom?: string, dateTo?: string): string | undefined {\n if (!dateFrom && !dateTo) return undefined\n\n // FDA date format is YYYYMMDD\n const from = dateFrom?.replace(/-/g, \"\") ?? \"*\"\n const to = dateTo?.replace(/-/g, \"\") ?? \"*\"\n\n return `${field}:[${from}+TO+${to}]`\n}\n\n// Helper to escape special characters in search terms\nfunction escapeSearchTerm(term: string): string {\n // Escape special characters that have meaning in FDA's search syntax\n return term.replace(/[+\\-&|!(){}[\\]^\"~*?:\\\\]/g, \"\\\\$&\").replace(/\\s+/g, \"+\")\n}\n\n// Helper to build a search query from multiple terms\nfunction buildSearchQuery(terms: Array<{ field: string; value?: string }>): string {\n const validTerms = terms.filter((t) => t.value !== undefined && t.value.trim() !== \"\")\n if (validTerms.length === 0) return \"\"\n\n return validTerms.map((t) => `${t.field}:\"${escapeSearchTerm(t.value!)}\"`).join(\"+AND+\")\n}\n\n// Format truncated text\nfunction truncateText(text: string | undefined, maxLength = 200): string | undefined {\n if (!text) return undefined\n return text.length > maxLength ? text.substring(0, maxLength) + \"...\" : text\n}\n\n// Drug Adverse Events Handler\nexport type DrugAdverseEventsParams = {\n safetyReportId?: string\n drugName?: string\n reaction?: string\n manufacturer?: string\n serious?: boolean\n dateFrom?: string\n dateTo?: string\n limit?: number\n skip?: number\n}\n\ntype FormattedDrugAdverseEvent = {\n reportId: string | undefined\n receiveDate: string | undefined\n serious: boolean\n drugs: Array<{\n name: string | undefined\n indication: string | undefined\n route: string | undefined\n }>\n reactions: Array<{\n reaction: string | undefined\n outcome: string | undefined\n }>\n patient: {\n age: string | undefined\n sex: string | undefined\n weight: string | undefined\n }\n}\n\nfunction formatDrugAdverseEvent(event: DrugAdverseEvent): FormattedDrugAdverseEvent {\n return {\n reportId: event.safetyreportid,\n receiveDate: event.receivedate,\n serious: event.serious === \"1\",\n drugs:\n event.patient?.drug?.slice(0, 3).map((d) => ({\n name: d.medicinalproduct ?? d.activesubstance?.activesubstancename,\n indication: d.drugindication,\n route: d.drugadministrationroute,\n })) ?? [],\n reactions:\n event.patient?.reaction?.slice(0, 3).map((r) => ({\n reaction: r.reactionmeddrapt,\n outcome: r.reactionoutcome,\n })) ?? [],\n patient: {\n age: event.patient?.patientonsetage\n ? `${event.patient.patientonsetage} ${event.patient.patientonsetageunit ?? \"\"}`\n : undefined,\n sex: event.patient?.patientsex === \"1\" ? \"Male\" : event.patient?.patientsex === \"2\" ? \"Female\" : undefined,\n weight: event.patient?.patientweight ? `${event.patient.patientweight} kg` : undefined,\n },\n }\n}\n\nexport async function handleSearchDrugAdverseEvents(\n params: DrugAdverseEventsParams,\n): Promise<FDAToolResponse<FormattedDrugAdverseEvent[]>> {\n loggers.tools(\"searchDrugAdverseEvents\", params)\n\n try {\n // If safetyReportId is provided, do a direct lookup\n if (params.safetyReportId) {\n const searchParams: SearchParams = {\n search: `safetyreportid:\"${escapeSearchTerm(params.safetyReportId)}\"`,\n limit: 1,\n }\n\n const response = await fdaAPIClient.searchDrugAdverseEvents(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map(formatDrugAdverseEvent),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n }\n\n const searchTerms: Array<{ field: string; value?: string }> = [\n { field: \"patient.drug.medicinalproduct\", value: params.drugName },\n { field: \"patient.reaction.reactionmeddrapt\", value: params.reaction },\n { field: \"patient.drug.openfda.manufacturer_name\", value: params.manufacturer },\n ]\n\n if (params.serious !== undefined) {\n searchTerms.push({ field: \"serious\", value: params.serious ? \"1\" : \"2\" })\n }\n\n const searchQuery = buildSearchQuery(searchTerms)\n const dateQuery = buildDateQuery(\"receivedate\", params.dateFrom, params.dateTo)\n const fullQuery = [searchQuery, dateQuery].filter(Boolean).join(\"+AND+\")\n\n const searchParams: SearchParams = {\n search: fullQuery || undefined,\n limit: params.limit,\n skip: params.skip,\n }\n\n const response = await fdaAPIClient.searchDrugAdverseEvents(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map(formatDrugAdverseEvent),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n// Drug Labels Handler\nexport type DrugLabelsParams = {\n setId?: string\n drugName?: string\n indication?: string\n activeIngredient?: string\n route?: string\n hasBoxedWarning?: boolean\n sections?: string[]\n limit?: number\n skip?: number\n}\n\ntype FormattedDrugLabel = {\n setId: string | undefined\n brandName: string | undefined\n genericName: string | undefined\n manufacturer: string | undefined\n activeIngredients: string[]\n boxedWarning: string | undefined\n indications: string | undefined\n warnings: string | undefined\n contraindications: string | undefined\n adverseReactions: string | undefined\n drugInteractions: string | undefined\n dosageAndAdministration: string | undefined\n clinicalPharmacology: string | undefined\n mechanismOfAction: string | undefined\n pharmacokinetics: string | undefined\n overdosage: string | undefined\n description: string | undefined\n howSupplied: string | undefined\n storageAndHandling: string | undefined\n route: string[]\n}\n\nfunction formatDrugLabel(label: DrugLabel, sections?: string[]): FormattedDrugLabel {\n // Helper to conditionally include section based on sections filter\n const includeSection = (sectionName: string) => !sections || sections.includes(sectionName)\n\n return {\n setId: label.set_id,\n brandName: label.openfda?.brand_name?.[0],\n genericName: label.openfda?.generic_name?.[0],\n manufacturer: label.openfda?.manufacturer_name?.[0],\n activeIngredients: label.openfda?.substance_name?.slice(0, 5) ?? [],\n boxedWarning: includeSection(\"boxed_warning\") ? truncateText(label.boxed_warning?.[0], 500) : undefined,\n indications: includeSection(\"indications_and_usage\") ? truncateText(label.indications_and_usage?.[0]) : undefined,\n warnings: includeSection(\"warnings\") ? truncateText(label.warnings?.[0]) : undefined,\n contraindications: includeSection(\"contraindications\") ? truncateText(label.contraindications?.[0]) : undefined,\n adverseReactions: includeSection(\"adverse_reactions\") ? truncateText(label.adverse_reactions?.[0]) : undefined,\n drugInteractions: includeSection(\"drug_interactions\") ? truncateText(label.drug_interactions?.[0]) : undefined,\n dosageAndAdministration: includeSection(\"dosage_and_administration\")\n ? truncateText(label.dosage_and_administration?.[0])\n : undefined,\n clinicalPharmacology: includeSection(\"clinical_pharmacology\")\n ? truncateText(label.clinical_pharmacology?.[0])\n : undefined,\n mechanismOfAction: includeSection(\"mechanism_of_action\") ? truncateText(label.mechanism_of_action?.[0]) : undefined,\n pharmacokinetics: includeSection(\"pharmacokinetics\") ? truncateText(label.pharmacokinetics?.[0]) : undefined,\n overdosage: includeSection(\"overdosage\") ? truncateText(label.overdosage?.[0]) : undefined,\n description: includeSection(\"description\") ? truncateText(label.description?.[0]) : undefined,\n howSupplied: includeSection(\"how_supplied\") ? truncateText(label.how_supplied?.[0]) : undefined,\n storageAndHandling: includeSection(\"storage_and_handling\")\n ? truncateText(label.storage_and_handling?.[0])\n : undefined,\n route: label.openfda?.route?.slice(0, 3) ?? [],\n }\n}\n\nexport async function handleSearchDrugLabels(params: DrugLabelsParams): Promise<FDAToolResponse<FormattedDrugLabel[]>> {\n loggers.tools(\"searchDrugLabels\", params)\n\n try {\n // If setId is provided, do a direct lookup\n if (params.setId) {\n const searchParams: SearchParams = {\n search: `set_id:\"${escapeSearchTerm(params.setId)}\"`,\n limit: 1,\n }\n\n const response = await fdaAPIClient.searchDrugLabels(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map((label) => formatDrugLabel(label, params.sections)),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n }\n\n const searchTerms: Array<{ field: string; value?: string }> = [\n { field: \"openfda.brand_name\", value: params.drugName },\n { field: \"indications_and_usage\", value: params.indication },\n { field: \"openfda.substance_name\", value: params.activeIngredient },\n { field: \"openfda.route\", value: params.route },\n ]\n\n const searchQuery = buildSearchQuery(searchTerms)\n\n // Build boxed warning existence query\n const boxedWarningQuery = params.hasBoxedWarning ? \"_exists_:boxed_warning\" : undefined\n\n const fullQuery = [searchQuery, boxedWarningQuery].filter(Boolean).join(\"+AND+\")\n\n const searchParams: SearchParams = {\n search: fullQuery || undefined,\n limit: params.limit,\n skip: params.skip,\n }\n\n const response = await fdaAPIClient.searchDrugLabels(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map((label) => formatDrugLabel(label, params.sections)),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n// Drug NDC Handler\nexport type DrugNDCParams = {\n productNdc?: string\n brandName?: string\n genericName?: string\n labelerName?: string\n dosageForm?: string\n route?: string\n limit?: number\n skip?: number\n}\n\ntype FormattedDrugNDC = {\n productNdc: string | undefined\n brandName: string | undefined\n genericName: string | undefined\n labelerName: string | undefined\n dosageForm: string | undefined\n route: string[]\n activeIngredients: Array<{ name: string | undefined; strength: string | undefined }>\n marketingStartDate: string | undefined\n productType: string | undefined\n}\n\nfunction formatDrugNDC(ndc: DrugNDC): FormattedDrugNDC {\n return {\n productNdc: ndc.product_ndc,\n brandName: ndc.brand_name,\n genericName: ndc.generic_name,\n labelerName: ndc.labeler_name,\n dosageForm: ndc.dosage_form,\n route: ndc.route ?? [],\n activeIngredients:\n ndc.active_ingredients?.slice(0, 3).map((ai) => ({\n name: ai.name,\n strength: ai.strength,\n })) ?? [],\n marketingStartDate: ndc.marketing_start_date,\n productType: ndc.product_type,\n }\n}\n\nexport async function handleSearchDrugNDC(params: DrugNDCParams): Promise<FDAToolResponse<FormattedDrugNDC[]>> {\n loggers.tools(\"searchDrugNDC\", params)\n\n try {\n const searchTerms: Array<{ field: string; value?: string }> = [\n { field: \"product_ndc\", value: params.productNdc },\n { field: \"brand_name\", value: params.brandName },\n { field: \"generic_name\", value: params.genericName },\n { field: \"labeler_name\", value: params.labelerName },\n { field: \"dosage_form\", value: params.dosageForm },\n { field: \"route\", value: params.route },\n ]\n\n const searchQuery = buildSearchQuery(searchTerms)\n\n const searchParams: SearchParams = {\n search: searchQuery || undefined,\n limit: params.limit,\n skip: params.skip,\n }\n\n const response = await fdaAPIClient.searchDrugNDC(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map(formatDrugNDC),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n// Drug Enforcement (Recalls) Handler\nexport type DrugEnforcementParams = {\n recallingFirm?: string\n classification?: \"Class I\" | \"Class II\" | \"Class III\"\n status?: \"Ongoing\" | \"Completed\" | \"Terminated\" | \"Pending\"\n state?: string\n dateFrom?: string\n dateTo?: string\n limit?: number\n skip?: number\n}\n\ntype FormattedDrugEnforcement = {\n recallNumber: string | undefined\n recallingFirm: string | undefined\n classification: string | undefined\n status: string | undefined\n productDescription: string | undefined\n reasonForRecall: string | undefined\n recallInitiationDate: string | undefined\n distributionPattern: string | undefined\n city: string | undefined\n state: string | undefined\n}\n\nfunction formatDrugEnforcement(recall: DrugEnforcement): FormattedDrugEnforcement {\n return {\n recallNumber: recall.recall_number,\n recallingFirm: recall.recalling_firm,\n classification: recall.classification,\n status: recall.status,\n productDescription: truncateText(recall.product_description),\n reasonForRecall: truncateText(recall.reason_for_recall),\n recallInitiationDate: recall.recall_initiation_date,\n distributionPattern: truncateText(recall.distribution_pattern),\n city: recall.city,\n state: recall.state,\n }\n}\n\nexport async function handleSearchDrugEnforcement(\n params: DrugEnforcementParams,\n): Promise<FDAToolResponse<FormattedDrugEnforcement[]>> {\n loggers.tools(\"searchDrugEnforcement\", params)\n\n try {\n const searchTerms: Array<{ field: string; value?: string }> = [\n { field: \"recalling_firm\", value: params.recallingFirm },\n { field: \"classification\", value: params.classification },\n { field: \"status\", value: params.status },\n { field: \"state\", value: params.state },\n ]\n\n const searchQuery = buildSearchQuery(searchTerms)\n const dateQuery = buildDateQuery(\"recall_initiation_date\", params.dateFrom, params.dateTo)\n const fullQuery = [searchQuery, dateQuery].filter(Boolean).join(\"+AND+\")\n\n const searchParams: SearchParams = {\n search: fullQuery || undefined,\n limit: params.limit,\n skip: params.skip,\n }\n\n const response = await fdaAPIClient.searchDrugEnforcement(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map(formatDrugEnforcement),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n// Drugs@FDA Handler\nexport type DrugsFDAParams = {\n sponsorName?: string\n applicationNumber?: string\n brandName?: string\n marketingStatus?: string\n limit?: number\n skip?: number\n}\n\ntype FormattedDrugsFDA = {\n applicationNumber: string | undefined\n sponsorName: string | undefined\n products: Array<{\n brandName: string | undefined\n dosageForm: string | undefined\n route: string | undefined\n marketingStatus: string | undefined\n activeIngredients: Array<{ name: string | undefined; strength: string | undefined }>\n }>\n latestSubmission:\n | {\n type: string | undefined\n status: string | undefined\n statusDate: string | undefined\n }\n | undefined\n}\n\nfunction formatDrugsFDA(drug: DrugsFDA): FormattedDrugsFDA {\n const latestSubmission = drug.submissions?.[0]\n\n return {\n applicationNumber: drug.application_number,\n sponsorName: drug.sponsor_name,\n products:\n drug.products?.slice(0, 3).map((p) => ({\n brandName: p.brand_name,\n dosageForm: p.dosage_form,\n route: p.route,\n marketingStatus: p.marketing_status,\n activeIngredients:\n p.active_ingredients?.slice(0, 3).map((ai) => ({\n name: ai.name,\n strength: ai.strength,\n })) ?? [],\n })) ?? [],\n latestSubmission: latestSubmission\n ? {\n type: latestSubmission.submission_type,\n status: latestSubmission.submission_status,\n statusDate: latestSubmission.submission_status_date,\n }\n : undefined,\n }\n}\n\nexport async function handleSearchDrugsFDA(params: DrugsFDAParams): Promise<FDAToolResponse<FormattedDrugsFDA[]>> {\n loggers.tools(\"searchDrugsFDA\", params)\n\n try {\n const searchTerms: Array<{ field: string; value?: string }> = [\n { field: \"sponsor_name\", value: params.sponsorName },\n { field: \"application_number\", value: params.applicationNumber },\n { field: \"products.brand_name\", value: params.brandName },\n { field: \"products.marketing_status\", value: params.marketingStatus },\n ]\n\n const searchQuery = buildSearchQuery(searchTerms)\n\n const searchParams: SearchParams = {\n search: searchQuery || undefined,\n limit: params.limit,\n skip: params.skip,\n }\n\n const response = await fdaAPIClient.searchDrugsFDA(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map(formatDrugsFDA),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n// Drug Shortages Handler\nexport type DrugShortagesParams = {\n genericName?: string\n status?: \"Current\" | \"Resolved\"\n limit?: number\n skip?: number\n}\n\ntype FormattedDrugShortage = {\n genericName: string | undefined\n proprietaryName: string | undefined\n status: string | undefined\n description: string | undefined\n initialPostingDate: string | undefined\n resolvedShortageDate: string | undefined\n}\n\nfunction formatDrugShortage(shortage: DrugShortage): FormattedDrugShortage {\n return {\n genericName: shortage.generic_name,\n proprietaryName: shortage.proprietary_name,\n status: shortage.status,\n description: truncateText(shortage.description),\n initialPostingDate: shortage.initial_posting_date,\n resolvedShortageDate: shortage.resolved_shortage_date,\n }\n}\n\nexport async function handleSearchDrugShortages(\n params: DrugShortagesParams,\n): Promise<FDAToolResponse<FormattedDrugShortage[]>> {\n loggers.tools(\"searchDrugShortages\", params)\n\n try {\n const searchTerms: Array<{ field: string; value?: string }> = [\n { field: \"generic_name\", value: params.genericName },\n { field: \"status\", value: params.status },\n ]\n\n const searchQuery = buildSearchQuery(searchTerms)\n\n const searchParams: SearchParams = {\n search: searchQuery || undefined,\n limit: params.limit,\n skip: params.skip,\n }\n\n const response = await fdaAPIClient.searchDrugShortages(searchParams)\n const results = response.results ?? []\n\n return {\n success: true,\n data: results.map(formatDrugShortage),\n totalResults: response.meta?.results?.total,\n displayedResults: results.length,\n searchParams,\n apiUsage: fdaAPIClient.getRateLimitInfo(),\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{loggers as e}from"./utils/logger.js";import
|
|
2
|
+
import{loggers as e}from"./utils/logger.js";import{createOpenFDAServer as t}from"./server.js";import{Command as n}from"commander";const r=new n;r.name(`openfda-mcp-server`).description(`MCP server for querying U.S. FDA public datasets`).version(`0.1.0`).option(`-t, --transport <type>`,`Transport type (stdio or http)`,`stdio`).option(`-p, --port <number>`,`Port for HTTP transport`,`3000`).option(`--host <host>`,`Host for HTTP transport`,`localhost`).action(async n=>{let{transport:r,port:i,host:a}=n;e.main(`Starting OpenFDA MCP server with transport: ${r}`);let o=t({name:`openfda-mcp-server`,version:`0.1.0`});try{if(r===`http`){let t=parseInt(i,10);e.main(`Starting HTTP server on ${a}:${t}`),await o.start({transportType:`httpStream`,httpStream:{endpoint:`/mcp`,port:t}}),console.error(`OpenFDA MCP server running at http://${a}:${t}/mcp`)}else e.main(`Starting stdio server`),await o.start({transportType:`stdio`})}catch(e){console.error(`Failed to start server:`,e),process.exit(1)}}),r.parse();export{};
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * OpenFDA MCP Server CLI\n * Command-line interface for starting the MCP server\n */\n\nimport { Command } from \"commander\"\n\nimport { createOpenFDAServer } from \"./server.js\"\nimport { loggers } from \"./utils/logger.js\"\n\nconst program = new Command()\n\nprogram\n .name(\"openfda-mcp-server\")\n .description(\"MCP server for querying U.S. FDA public datasets\")\n .version(\"0.1.0\")\n .option(\"-t, --transport <type>\", \"Transport type (stdio or http)\", \"stdio\")\n .option(\"-p, --port <number>\", \"Port for HTTP transport\", \"3000\")\n .option(\"--host <host>\", \"Host for HTTP transport\", \"localhost\")\n .action(async (options) => {\n const { transport, port, host } = options\n\n loggers.main(`Starting OpenFDA MCP server with transport: ${transport}`)\n\n const server = createOpenFDAServer({\n name: \"openfda-mcp-server\",\n version: \"0.1.0\" as `${number}.${number}.${number}`,\n })\n\n try {\n if (transport === \"http\") {\n const portNum = parseInt(port, 10)\n loggers.main(`Starting HTTP server on ${host}:${portNum}`)\n\n await server.start({\n transportType: \"httpStream\",\n httpStream: {\n endpoint: \"/mcp\" as `/${string}`,\n port: portNum,\n },\n })\n\n console.error(`OpenFDA MCP server running at http://${host}:${portNum}/mcp`)\n } else {\n loggers.main(\"Starting stdio server\")\n\n await server.start({\n transportType: \"stdio\",\n })\n }\n } catch (error) {\n console.error(\"Failed to start server:\", error)\n process.exit(1)\n }\n })\n\nprogram.parse()\n"],"mappings":";
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * OpenFDA MCP Server CLI\n * Command-line interface for starting the MCP server\n */\n\nimport { Command } from \"commander\"\n\nimport { createOpenFDAServer } from \"./server.js\"\nimport { loggers } from \"./utils/logger.js\"\n\nconst program = new Command()\n\nprogram\n .name(\"openfda-mcp-server\")\n .description(\"MCP server for querying U.S. FDA public datasets\")\n .version(\"0.1.0\")\n .option(\"-t, --transport <type>\", \"Transport type (stdio or http)\", \"stdio\")\n .option(\"-p, --port <number>\", \"Port for HTTP transport\", \"3000\")\n .option(\"--host <host>\", \"Host for HTTP transport\", \"localhost\")\n .action(async (options) => {\n const { transport, port, host } = options\n\n loggers.main(`Starting OpenFDA MCP server with transport: ${transport}`)\n\n const server = createOpenFDAServer({\n name: \"openfda-mcp-server\",\n version: \"0.1.0\" as `${number}.${number}.${number}`,\n })\n\n try {\n if (transport === \"http\") {\n const portNum = parseInt(port, 10)\n loggers.main(`Starting HTTP server on ${host}:${portNum}`)\n\n await server.start({\n transportType: \"httpStream\",\n httpStream: {\n endpoint: \"/mcp\" as `/${string}`,\n port: portNum,\n },\n })\n\n console.error(`OpenFDA MCP server running at http://${host}:${portNum}/mcp`)\n } else {\n loggers.main(\"Starting stdio server\")\n\n await server.start({\n transportType: \"stdio\",\n })\n }\n } catch (error) {\n console.error(\"Failed to start server:\", error)\n process.exit(1)\n }\n })\n\nprogram.parse()\n"],"mappings":";kIAWA,MAAM,EAAU,IAAI,EAEpB,EACG,KAAK,qBAAqB,CAC1B,YAAY,mDAAmD,CAC/D,QAAQ,QAAQ,CAChB,OAAO,yBAA0B,iCAAkC,QAAQ,CAC3E,OAAO,sBAAuB,0BAA2B,OAAO,CAChE,OAAO,gBAAiB,0BAA2B,YAAY,CAC/D,OAAO,KAAO,IAAY,CACzB,GAAM,CAAE,YAAW,OAAM,QAAS,EAElC,EAAQ,KAAK,+CAA+C,IAAY,CAExE,IAAM,EAAS,EAAoB,CACjC,KAAM,qBACN,QAAS,QACV,CAAC,CAEF,GAAI,CACF,GAAI,IAAc,OAAQ,CACxB,IAAM,EAAU,SAAS,EAAM,GAAG,CAClC,EAAQ,KAAK,2BAA2B,EAAK,GAAG,IAAU,CAE1D,MAAM,EAAO,MAAM,CACjB,cAAe,aACf,WAAY,CACV,SAAU,OACV,KAAM,EACP,CACF,CAAC,CAEF,QAAQ,MAAM,wCAAwC,EAAK,GAAG,EAAQ,MAAM,MAE5E,EAAQ,KAAK,wBAAwB,CAErC,MAAM,EAAO,MAAM,CACjB,cAAe,QAChB,CAAC,OAEG,EAAO,CACd,QAAQ,MAAM,0BAA2B,EAAM,CAC/C,QAAQ,KAAK,EAAE,GAEjB,CAEJ,EAAQ,OAAO"}
|