@cliwant/mcp-sam-gov 1.3.0 → 1.5.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.
Files changed (149) hide show
  1. package/README.ja.md +20 -12
  2. package/README.ko.md +20 -12
  3. package/README.md +62 -14
  4. package/dist/bea.d.ts +1 -1
  5. package/dist/bea.js +1 -1
  6. package/dist/cbp-border.d.ts +51 -0
  7. package/dist/cbp-border.d.ts.map +1 -0
  8. package/dist/cbp-border.js +123 -0
  9. package/dist/cbp-border.js.map +1 -0
  10. package/dist/census-economic.d.ts +1 -1
  11. package/dist/census-economic.d.ts.map +1 -1
  12. package/dist/census-economic.js +12 -6
  13. package/dist/census-economic.js.map +1 -1
  14. package/dist/cms-facility.d.ts +112 -0
  15. package/dist/cms-facility.d.ts.map +1 -0
  16. package/dist/cms-facility.js +311 -0
  17. package/dist/cms-facility.js.map +1 -0
  18. package/dist/cms-hospital.d.ts +105 -0
  19. package/dist/cms-hospital.d.ts.map +1 -0
  20. package/dist/cms-hospital.js +290 -0
  21. package/dist/cms-hospital.js.map +1 -0
  22. package/dist/cms-supplier.d.ts +133 -0
  23. package/dist/cms-supplier.d.ts.map +1 -0
  24. package/dist/cms-supplier.js +414 -0
  25. package/dist/cms-supplier.js.map +1 -0
  26. package/dist/cms-utilization.d.ts +113 -0
  27. package/dist/cms-utilization.d.ts.map +1 -0
  28. package/dist/cms-utilization.js +328 -0
  29. package/dist/cms-utilization.js.map +1 -0
  30. package/dist/courtlistener.d.ts +115 -0
  31. package/dist/courtlistener.d.ts.map +1 -0
  32. package/dist/courtlistener.js +398 -0
  33. package/dist/courtlistener.js.map +1 -0
  34. package/dist/cpsc.d.ts +81 -0
  35. package/dist/cpsc.d.ts.map +1 -0
  36. package/dist/cpsc.js +283 -0
  37. package/dist/cpsc.js.map +1 -0
  38. package/dist/datagov-catalog.d.ts.map +1 -1
  39. package/dist/datagov-catalog.js +16 -2
  40. package/dist/datagov-catalog.js.map +1 -1
  41. package/dist/dol.d.ts +2 -2
  42. package/dist/dol.js +5 -5
  43. package/dist/dol.js.map +1 -1
  44. package/dist/ecfr.d.ts +2 -2
  45. package/dist/ecfr.d.ts.map +1 -1
  46. package/dist/ecfr.js +24 -10
  47. package/dist/ecfr.js.map +1 -1
  48. package/dist/edgar.d.ts.map +1 -1
  49. package/dist/edgar.js +26 -6
  50. package/dist/edgar.js.map +1 -1
  51. package/dist/epa-envirofacts.d.ts +97 -0
  52. package/dist/epa-envirofacts.d.ts.map +1 -0
  53. package/dist/epa-envirofacts.js +305 -0
  54. package/dist/epa-envirofacts.js.map +1 -0
  55. package/dist/errors.d.ts.map +1 -1
  56. package/dist/errors.js +11 -0
  57. package/dist/errors.js.map +1 -1
  58. package/dist/far.d.ts.map +1 -1
  59. package/dist/far.js +3 -1
  60. package/dist/far.js.map +1 -1
  61. package/dist/federal-register.d.ts +2 -2
  62. package/dist/federal-register.d.ts.map +1 -1
  63. package/dist/federal-register.js +26 -10
  64. package/dist/federal-register.js.map +1 -1
  65. package/dist/fema.d.ts +36 -0
  66. package/dist/fema.d.ts.map +1 -1
  67. package/dist/fema.js +124 -0
  68. package/dist/fema.js.map +1 -1
  69. package/dist/fred.d.ts +1 -1
  70. package/dist/fred.js +1 -1
  71. package/dist/gov-domains.d.ts +66 -0
  72. package/dist/gov-domains.d.ts.map +1 -0
  73. package/dist/gov-domains.js +211 -0
  74. package/dist/gov-domains.js.map +1 -0
  75. package/dist/keys.d.ts +6 -5
  76. package/dist/keys.d.ts.map +1 -1
  77. package/dist/keys.js +25 -6
  78. package/dist/keys.js.map +1 -1
  79. package/dist/nhtsa.d.ts +91 -0
  80. package/dist/nhtsa.d.ts.map +1 -0
  81. package/dist/nhtsa.js +263 -0
  82. package/dist/nhtsa.js.map +1 -0
  83. package/dist/nist-controls.d.ts +48 -0
  84. package/dist/nist-controls.d.ts.map +1 -0
  85. package/dist/nist-controls.js +174 -0
  86. package/dist/nist-controls.js.map +1 -0
  87. package/dist/nonprofit.d.ts +116 -0
  88. package/dist/nonprofit.d.ts.map +1 -0
  89. package/dist/nonprofit.js +342 -0
  90. package/dist/nonprofit.js.map +1 -0
  91. package/dist/nws-weather.d.ts +57 -0
  92. package/dist/nws-weather.d.ts.map +1 -0
  93. package/dist/nws-weather.js +131 -0
  94. package/dist/nws-weather.js.map +1 -0
  95. package/dist/openfda-device.d.ts +85 -0
  96. package/dist/openfda-device.d.ts.map +1 -0
  97. package/dist/openfda-device.js +277 -0
  98. package/dist/openfda-device.js.map +1 -0
  99. package/dist/openfda-drugsfda.d.ts +72 -0
  100. package/dist/openfda-drugsfda.d.ts.map +1 -0
  101. package/dist/openfda-drugsfda.js +230 -0
  102. package/dist/openfda-drugsfda.js.map +1 -0
  103. package/dist/openfda.d.ts +133 -0
  104. package/dist/openfda.d.ts.map +1 -0
  105. package/dist/openfda.js +425 -0
  106. package/dist/openfda.js.map +1 -0
  107. package/dist/server.d.ts.map +1 -1
  108. package/dist/server.js +996 -16
  109. package/dist/server.js.map +1 -1
  110. package/dist/treasury.d.ts +2 -0
  111. package/dist/treasury.d.ts.map +1 -1
  112. package/dist/treasury.js +7 -0
  113. package/dist/treasury.js.map +1 -1
  114. package/dist/usaspending.d.ts +32 -1
  115. package/dist/usaspending.d.ts.map +1 -1
  116. package/dist/usaspending.js +143 -16
  117. package/dist/usaspending.js.map +1 -1
  118. package/package.json +3 -2
  119. package/src/bea.ts +1 -1
  120. package/src/cbp-border.ts +177 -0
  121. package/src/census-economic.ts +12 -6
  122. package/src/cms-facility.ts +379 -0
  123. package/src/cms-hospital.ts +344 -0
  124. package/src/cms-supplier.ts +527 -0
  125. package/src/cms-utilization.ts +389 -0
  126. package/src/courtlistener.ts +465 -0
  127. package/src/cpsc.ts +333 -0
  128. package/src/datagov-catalog.ts +18 -2
  129. package/src/dol.ts +5 -5
  130. package/src/ecfr.ts +27 -10
  131. package/src/edgar.ts +39 -7
  132. package/src/epa-envirofacts.ts +358 -0
  133. package/src/errors.ts +11 -0
  134. package/src/far.ts +3 -1
  135. package/src/federal-register.ts +29 -10
  136. package/src/fema.ts +139 -0
  137. package/src/fred.ts +1 -1
  138. package/src/gov-domains.ts +237 -0
  139. package/src/keys.ts +27 -6
  140. package/src/nhtsa.ts +352 -0
  141. package/src/nist-controls.ts +219 -0
  142. package/src/nonprofit.ts +460 -0
  143. package/src/nws-weather.ts +167 -0
  144. package/src/openfda-device.ts +356 -0
  145. package/src/openfda-drugsfda.ts +313 -0
  146. package/src/openfda.ts +518 -0
  147. package/src/server.ts +1127 -27
  148. package/src/treasury.ts +7 -0
  149. package/src/usaspending.ts +189 -17
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openfda-device.d.ts","sourceRoot":"","sources":["../src/openfda-device.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAKH,OAAO,EAAY,KAAK,UAAU,EAAqB,MAAM,WAAW,CAAC;AA6BzE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AAmBF,iFAAiF;AACjF,MAAM,MAAM,oBAAoB,GAAG;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,oBAAoB,GAAG,MAAM,CAejE;AAGD,MAAM,MAAM,2BAA2B,GAAG,oBAAoB,GAAG;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,2BAA2B,GAChC,OAAO,CAAC,UAAU,CAAC,CAyJrB"}
@@ -0,0 +1,277 @@
1
+ /**
2
+ * openfda-device.ts — openFDA 510(k) DEVICE CLEARANCES (api.fda.gov) — the
3
+ * MEDICAL-DEVICE REGULATORY lane (ADR-0056). The FDA's premarket-notification
4
+ * (510(k)) clearances: the applicant, the device, the clearance number (K-number),
5
+ * the decision (date + description), the clearance type, the product code, the
6
+ * advisory committee, and geography.
7
+ *
8
+ * ★ THIS IS THE SAME SOURCE + ENVELOPE + CRUX as openfda.ts (ADR-0054). Same host
9
+ * (api.fda.gov), same response envelope `{ meta:{ results:{ skip, limit, total }},
10
+ * results:[…] }`, same ★no-match→HTTP-404-NOT_FOUND-as-honest-empty crux, same
11
+ * optional query-key K-test, same fixed-host SSRF idiom, same structured-only
12
+ * (no raw Lucene passthrough) search assembly. It REUSES openfda.ts's `fetchOpenfda`
13
+ * (the SSRF-guarded classified fetch) and `readOpenfdaError` (the error-body reader)
14
+ * verbatim — the 404-reclassification logic is NOT reinvented — plus `luceneQuote`
15
+ * (the phrase-escape), `openfdaApiKey` (the optional-key env seam), and `OPENFDA_HOST`.
16
+ *
17
+ * GET https://api.fda.gov/device/510k.json
18
+ * ?search=<lucene>&limit=<1..100>&skip=<offset>[&api_key=<KEY>]
19
+ * → { meta:{ disclaimer, results:{ skip, limit, total }}, results:[ {…} ] }
20
+ * (live total ~175507).
21
+ *
22
+ * ★ HONESTY (mirrors openfda.ts exactly):
23
+ * [P1] totalAvailable = `meta.results.total` EXACT (e.g. 175507), NEVER
24
+ * results.length. skip/limit offset pagination:
25
+ * hasMore = skip + returned < total; nextOffset = hasMore ? skip+returned : null.
26
+ * [★P2] a 404 whose body is `{error:{code:"NOT_FOUND"}}` (a no-match query OR an
27
+ * unknown field) ⇒ HONEST EMPTY (returned:0, totalAvailable:0) — NOT thrown,
28
+ * NOT not_found. Any OTHER 4xx (e.g. a 400 syntax error) ⇒ invalid_input
29
+ * surfacing openFDA's message. 5xx/timeout ⇒ upstream_unavailable THROW. A
30
+ * 200 non-JSON body ⇒ schema_drift. (Reuses openfda.ts's exact fetch path.)
31
+ * [P3] dates (`decision_date`, YYYY-MM-DD) and every scalar surfaced as a STRING
32
+ * via `str` (null-never-empty-string) — no numeric coercion; never fabricated.
33
+ * [P4] `meta.results` or `results` absent / non-array ⇒ driftError (never a
34
+ * fabricated empty).
35
+ * [K-test] OPTIONAL OPENFDA_API_KEY: when set it rides `&api_key=` ONLY (openFDA has
36
+ * no header option — the query-key is inherent). Mitigation: the `label` is
37
+ * host+path ONLY (`openfda:/device/510k`, NO query), so no token reaches
38
+ * ToolError.upstreamEndpoint; `_meta.source` names the MODE only; the key is
39
+ * ABSENT from the serialized {data,_meta}, notes, and any log. Unset ⇒ keyless.
40
+ * [SSRF] fixed host `api.fda.gov` (fetchOpenfda asserts hostname/protocol +
41
+ * redirect:"error"); all filter VALUES are Lucene-escaped + phrase-quoted and
42
+ * ride URLSearchParams `search=`; limit/skip are integers; state is charclass
43
+ * `^[A-Za-z]{2}$`. No raw Lucene passthrough — structured only, injection-safe.
44
+ */
45
+ import { ToolErrorCarrier, errorFromResponse } from "./errors.js";
46
+ import { driftError } from "./datasource.js";
47
+ import { str } from "./coerce.js";
48
+ import { withMeta } from "./meta.js";
49
+ import { OPENFDA_HOST, fetchOpenfda, readOpenfdaError, luceneQuote, openfdaApiKey, } from "./openfda.js";
50
+ // state filter charclass (a 2-letter US state/territory postal code).
51
+ const STATE_RE = /^[A-Za-z]{2}$/;
52
+ const DEFAULT_LIMIT = 25;
53
+ const MAX_LIMIT = 100;
54
+ /** host+path-only label (→ ToolError.upstreamEndpoint); NEVER carries the key. */
55
+ const DEVICE_LABEL = "openfda:/device/510k";
56
+ // ─── Honesty notes (ADR-0056 required set) ────────────────────────
57
+ const NOT_DETERMINATION_NOTE = "openFDA 510(k) device clearances are FDA-published premarket-notification records; treat the decision/date/clearance-type as of the source's last publication. This is reference data, not a live regulatory determination.";
58
+ const KEYLESS_NOTE = "Keyless: no OPENFDA_API_KEY is set. openFDA allows ~1000 requests/day without a key; a free key raises the rate limit (get one at https://open.fda.gov/apis/authentication/).";
59
+ const KEYED_NOTE = "OPENFDA_API_KEY is set — it rides ONLY the &api_key= query parameter to api.fda.gov (openFDA has no header option), raising the rate limit. Its value is NEVER logged, echoed, or placed in this response.";
60
+ const NO_FILTER_NOTE = "No structured filters were applied — this is an unscoped scan of the WHOLE 510(k) device-clearance collection. Add applicant / deviceName / productCode / clearanceType / kNumber / state to scope the result set.";
61
+ /** Map ONE openFDA 510(k) result row → the curated shape. Every scalar via `str`. */
62
+ function mapClearance(row) {
63
+ const r = (row ?? {});
64
+ return {
65
+ applicant: str(r.applicant),
66
+ deviceName: str(r.device_name),
67
+ kNumber: str(r.k_number),
68
+ decisionDate: str(r.decision_date),
69
+ decisionDescription: str(r.decision_description),
70
+ clearanceType: str(r.clearance_type),
71
+ productCode: str(r.product_code),
72
+ advisoryCommittee: str(r.advisory_committee),
73
+ state: str(r.state),
74
+ };
75
+ }
76
+ /**
77
+ * Assemble the openFDA `search=` Lucene string from structured 510(k) filters — each
78
+ * value Lucene-escaped + phrase-quoted (reusing openfda.ts's `luceneQuote`), joined by
79
+ * ` AND `. Returns "" when no filter is present (openFDA then returns the whole
80
+ * collection). The clause → field mapping is FIXED here; a caller can never inject a
81
+ * raw field:value (no raw Lucene passthrough).
82
+ */
83
+ export function buildDeviceSearch(f) {
84
+ const clauses = [];
85
+ if (f.applicant !== undefined)
86
+ clauses.push(`applicant:${luceneQuote(f.applicant)}`);
87
+ if (f.deviceName !== undefined)
88
+ clauses.push(`device_name:${luceneQuote(f.deviceName)}`);
89
+ if (f.productCode !== undefined)
90
+ clauses.push(`product_code:${luceneQuote(f.productCode)}`);
91
+ if (f.clearanceType !== undefined)
92
+ clauses.push(`clearance_type:${luceneQuote(f.clearanceType)}`);
93
+ if (f.kNumber !== undefined)
94
+ clauses.push(`k_number:${luceneQuote(f.kNumber)}`);
95
+ if (f.state !== undefined)
96
+ clauses.push(`state:${luceneQuote(f.state.toUpperCase())}`);
97
+ return clauses.join(" AND ");
98
+ }
99
+ /**
100
+ * Search openFDA 510(k) DEVICE CLEARANCES with structured filters → curated clearance
101
+ * rows + honest `_meta`. KEYLESS (an OPTIONAL OPENFDA_API_KEY only raises the rate
102
+ * limit). totalAvailable = meta.results.total (EXACT); skip/limit offset pagination.
103
+ * ★A no-match query (openFDA HTTP 404 NOT_FOUND) ⇒ an honest empty, never a throw.
104
+ */
105
+ export async function deviceClearances(args) {
106
+ // ── Validate + default the inputs (belt-and-suspenders behind the server Zod; a
107
+ // DIRECT handler call bypasses Zod). ──
108
+ if (args.state !== undefined && !STATE_RE.test(args.state)) {
109
+ throw new ToolErrorCarrier({
110
+ kind: "invalid_input",
111
+ retryable: false,
112
+ message: `Invalid state ${JSON.stringify(args.state)} — expected a 2-letter US state/territory postal code (^[A-Za-z]{2}$), e.g. "CA".`,
113
+ upstreamEndpoint: DEVICE_LABEL,
114
+ });
115
+ }
116
+ const limit = clampLimit(args.limit);
117
+ const skip = clampSkip(args.skip);
118
+ // ── Assemble the query (structured-only; all VALUES via URLSearchParams — no
119
+ // host/path steer). The OPTIONAL key rides ONLY here in &api_key=. ──
120
+ const filters = {
121
+ applicant: args.applicant,
122
+ deviceName: args.deviceName,
123
+ productCode: args.productCode,
124
+ clearanceType: args.clearanceType,
125
+ kNumber: args.kNumber,
126
+ state: args.state,
127
+ };
128
+ const search = buildDeviceSearch(filters);
129
+ const filtersApplied = [];
130
+ if (args.applicant !== undefined)
131
+ filtersApplied.push("applicant");
132
+ if (args.deviceName !== undefined)
133
+ filtersApplied.push("deviceName");
134
+ if (args.productCode !== undefined)
135
+ filtersApplied.push("productCode");
136
+ if (args.clearanceType !== undefined)
137
+ filtersApplied.push("clearanceType");
138
+ if (args.kNumber !== undefined)
139
+ filtersApplied.push("kNumber");
140
+ if (args.state !== undefined)
141
+ filtersApplied.push("state");
142
+ const params = new URLSearchParams();
143
+ if (search !== "")
144
+ params.set("search", search);
145
+ params.set("limit", String(limit));
146
+ params.set("skip", String(skip));
147
+ const key = openfdaApiKey();
148
+ if (key !== undefined)
149
+ params.set("api_key", key); // OPTIONAL — &api_key= ONLY
150
+ const url = `https://${OPENFDA_HOST}/device/510k.json?${params.toString()}`;
151
+ // ── Fetch + classify. ★P2 CRUX: a 404 whose body is {error:{code:"NOT_FOUND"}} is
152
+ // a genuine no-match (or an unknown field) ⇒ an HONEST EMPTY, never a thrown
153
+ // not_found. Any OTHER 4xx (e.g. 400 syntax) ⇒ invalid_input surfacing openFDA's
154
+ // message; a 5xx/429 ⇒ the shared taxonomy THROWS. (fetchOpenfda/readOpenfdaError
155
+ // are the SAME helpers as openfda.ts — the 404-reclassify logic is not reinvented.) ──
156
+ const res = await fetchOpenfda(url, DEVICE_LABEL);
157
+ if (res.status === 404) {
158
+ const { code, message } = await readOpenfdaError(res);
159
+ if (code === "NOT_FOUND") {
160
+ // Honest empty — NOT thrown, NOT not_found (the openFDA no-match idiom).
161
+ return emptyResult(limit, skip, filtersApplied, key !== undefined);
162
+ }
163
+ // A non-NOT_FOUND 404 ⇒ the shared not_found taxonomy (never a fake-empty).
164
+ throw new ToolErrorCarrier({
165
+ ...errorFromResponse(res, DEVICE_LABEL),
166
+ message: message
167
+ ? `openFDA returned HTTP 404 at ${DEVICE_LABEL}: ${message}`
168
+ : `Resource not found at ${DEVICE_LABEL} (HTTP 404).`,
169
+ });
170
+ }
171
+ if (res.status >= 400 && res.status < 500 && res.status !== 429) {
172
+ // A 4xx OTHER than 404/429 (e.g. 400 syntax) ⇒ invalid_input surfacing the
173
+ // openFDA error message (a caller-fixable request, never a fake-empty).
174
+ const { message } = await readOpenfdaError(res);
175
+ throw new ToolErrorCarrier({
176
+ kind: "invalid_input",
177
+ retryable: false,
178
+ message: message
179
+ ? `openFDA rejected the request (HTTP ${res.status}) at ${DEVICE_LABEL}: ${message}`
180
+ : `Bad request (HTTP ${res.status}) at ${DEVICE_LABEL}.`,
181
+ upstreamStatus: res.status,
182
+ upstreamEndpoint: DEVICE_LABEL,
183
+ });
184
+ }
185
+ if (!res.ok) {
186
+ // 429 → rate_limited; 5xx → upstream_unavailable (a DOWN service is NEVER an empty
187
+ // result). Delegated to the shared errors.ts taxonomy.
188
+ throw new ToolErrorCarrier(errorFromResponse(res, DEVICE_LABEL));
189
+ }
190
+ // ── 200 ⇒ parse JSON; a non-JSON body ⇒ r.json() SyntaxError ⇒ schema_drift (never
191
+ // read as an empty result). ──
192
+ let body;
193
+ try {
194
+ body = await res.json();
195
+ }
196
+ catch (e) {
197
+ if (e instanceof SyntaxError) {
198
+ throw driftError(DEVICE_LABEL, `openFDA ${DEVICE_LABEL} returned a non-JSON body at HTTP 200 — schema drift (never read as an empty result).`);
199
+ }
200
+ throw e;
201
+ }
202
+ // ── [P4] meta.results (the total carrier) and results (the rows) MUST be present +
203
+ // well-shaped; anything else is drift, never a fabricated empty. ──
204
+ const b = (body ?? {});
205
+ const meta = (b.meta ?? {});
206
+ const metaResults = meta.results;
207
+ if (metaResults === undefined ||
208
+ metaResults === null ||
209
+ typeof metaResults !== "object") {
210
+ throw driftError(DEVICE_LABEL, `openFDA ${DEVICE_LABEL} shape drift — meta.results (the skip/limit/total carrier) is missing.`);
211
+ }
212
+ if (!Array.isArray(b.results)) {
213
+ throw driftError(DEVICE_LABEL, `openFDA ${DEVICE_LABEL} shape drift — results must be an array.`);
214
+ }
215
+ const clearances = b.results.map(mapClearance);
216
+ const returned = clearances.length;
217
+ // ── [P1] totalAvailable = meta.results.total EXACT (the REAL total), NEVER
218
+ // results.length. skip/limit offset pagination. ──
219
+ const rawTotal = metaResults.total;
220
+ const totalAvailable = typeof rawTotal === "number" && Number.isFinite(rawTotal) ? rawTotal : null;
221
+ const hasMore = totalAvailable !== null && skip + returned < totalAvailable;
222
+ const nextOffset = hasMore ? skip + returned : null;
223
+ const notes = [NOT_DETERMINATION_NOTE, keyNote(key !== undefined)];
224
+ if (filtersApplied.length === 0)
225
+ notes.push(NO_FILTER_NOTE);
226
+ return withMeta({ clearances }, {
227
+ // MODE only — never the key value (K-test).
228
+ source: `${OPENFDA_HOST} /device/510k (openFDA 510(k) device clearances; ${key !== undefined ? "OPENFDA_API_KEY rate-limit key applied" : "keyless"})`,
229
+ keylessMode: true, // a keyless tool; the optional key only raises the rate limit
230
+ returned,
231
+ totalAvailable,
232
+ filtersApplied,
233
+ filtersDropped: [],
234
+ fieldsUnavailable: [],
235
+ pagination: { offset: skip, limit, hasMore, nextOffset },
236
+ notes,
237
+ });
238
+ }
239
+ // ─── Small helpers ────────────────────────────────────────────────
240
+ function keyNote(hasKey) {
241
+ return hasKey ? KEYED_NOTE : KEYLESS_NOTE;
242
+ }
243
+ /** An honest empty result (★P2: a 404 NOT_FOUND no-match) — returned:0, total:0. */
244
+ function emptyResult(limit, skip, filtersApplied, hasKey) {
245
+ return withMeta({ clearances: [] }, {
246
+ source: `${OPENFDA_HOST} /device/510k (openFDA 510(k) device clearances; ${hasKey ? "OPENFDA_API_KEY rate-limit key applied" : "keyless"})`,
247
+ keylessMode: true,
248
+ returned: 0,
249
+ totalAvailable: 0,
250
+ filtersApplied,
251
+ filtersDropped: [],
252
+ fieldsUnavailable: [],
253
+ pagination: { offset: skip, limit, hasMore: false, nextOffset: null },
254
+ notes: [
255
+ "No 510(k) device clearances matched this query (openFDA returned HTTP 404 NOT_FOUND — the source's honest no-match). This is an exact empty, not an error.",
256
+ NOT_DETERMINATION_NOTE,
257
+ keyNote(hasKey),
258
+ ],
259
+ });
260
+ }
261
+ function clampLimit(v) {
262
+ if (typeof v !== "number" || !Number.isFinite(v))
263
+ return DEFAULT_LIMIT;
264
+ const n = Math.floor(v);
265
+ if (n < 1)
266
+ return 1;
267
+ if (n > MAX_LIMIT)
268
+ return MAX_LIMIT;
269
+ return n;
270
+ }
271
+ function clampSkip(v) {
272
+ if (typeof v !== "number" || !Number.isFinite(v))
273
+ return 0;
274
+ const n = Math.floor(v);
275
+ return n < 0 ? 0 : n;
276
+ }
277
+ //# sourceMappingURL=openfda-device.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openfda-device.js","sourceRoot":"","sources":["../src/openfda-device.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * openfda-drugsfda.ts — openFDA DRUG APPROVALS (Drugs@FDA, api.fda.gov) — the
3
+ * PHARMA REGULATORY-APPROVAL lane. FDA-approved drug applications (NDA/ANDA/BLA):
4
+ * the sponsor, the application number, each approved product (brand/generic name,
5
+ * dosage form, route, marketing status), and the submission/approval history.
6
+ * Answers "what drugs did sponsor X get approved, and which are still marketed" —
7
+ * pharma vendor product/approval intelligence.
8
+ *
9
+ * ★ SAME SOURCE + ENVELOPE + CRUX as openfda.ts / openfda-device.ts (ADR-0054/0056).
10
+ * Same host (api.fda.gov), same envelope `{ meta:{ results:{ skip,limit,total }},
11
+ * results:[…] }`, same ★no-match→HTTP-404-NOT_FOUND-as-honest-empty crux, same
12
+ * optional query-key K-test, same fixed-host SSRF idiom, same structured-only (no
13
+ * raw Lucene passthrough) search. REUSES openfda.ts's `fetchOpenfda`,
14
+ * `readOpenfdaError`, `luceneQuote`, `openfdaApiKey`, `OPENFDA_HOST` verbatim.
15
+ *
16
+ * GET https://api.fda.gov/drug/drugsfda.json?search=<lucene>&limit=&skip=[&api_key=]
17
+ * → { meta:{ results:{ skip,limit,total }}, results:[ { application_number,
18
+ * sponsor_name, products:[…], submissions:[…] } ] }.
19
+ *
20
+ * ★ HONESTY (mirrors the siblings exactly): [P1] totalAvailable = meta.results.total
21
+ * EXACT (never results.length); skip/limit offset pagination. [★P2] 404 NOT_FOUND
22
+ * ⇒ honest empty (never thrown); other 4xx ⇒ invalid_input surfacing openFDA's
23
+ * message; 5xx/429 ⇒ THROW. [P3] every scalar via `str` (null-never-""); nested
24
+ * products/submissions arrays default to [] and each field is str (never fabricated).
25
+ * [P4] meta.results / results absent-or-mis-shaped ⇒ driftError. [K-test] the
26
+ * OPTIONAL OPENFDA_API_KEY rides ONLY &api_key=; label/source name mode only.
27
+ * [SSRF] fixed host; all VALUES Lucene-escaped + phrase-quoted via URLSearchParams.
28
+ */
29
+ import { type MetaBundle } from "./meta.js";
30
+ export type DrugsfdaProduct = {
31
+ brandName: string | null;
32
+ genericIngredients: {
33
+ name: string | null;
34
+ strength: string | null;
35
+ }[];
36
+ dosageForm: string | null;
37
+ route: string | null;
38
+ marketingStatus: string | null;
39
+ };
40
+ export type DrugsfdaSubmission = {
41
+ submissionType: string | null;
42
+ submissionNumber: string | null;
43
+ submissionStatus: string | null;
44
+ submissionStatusDate: string | null;
45
+ submissionClass: string | null;
46
+ };
47
+ export type DrugsfdaApplication = {
48
+ applicationNumber: string | null;
49
+ sponsorName: string | null;
50
+ products: DrugsfdaProduct[];
51
+ submissions: DrugsfdaSubmission[];
52
+ };
53
+ export type DrugsfdaFilters = {
54
+ sponsorName?: string;
55
+ brandName?: string;
56
+ activeIngredient?: string;
57
+ applicationNumber?: string;
58
+ };
59
+ export declare function buildDrugsfdaSearch(f: DrugsfdaFilters): string;
60
+ export type DrugApprovalsArgs = DrugsfdaFilters & {
61
+ limit?: number;
62
+ skip?: number;
63
+ };
64
+ /**
65
+ * Search openFDA Drugs@FDA drug-approval applications with structured filters →
66
+ * curated application rows (sponsor, application number, approved products, submission
67
+ * history) + honest `_meta`. KEYLESS (OPTIONAL OPENFDA_API_KEY only raises the rate
68
+ * limit). totalAvailable = meta.results.total (EXACT); skip/limit pagination. A
69
+ * no-match query (openFDA HTTP 404 NOT_FOUND) ⇒ an honest empty, never a throw.
70
+ */
71
+ export declare function drugApprovals(args: DrugApprovalsArgs): Promise<MetaBundle>;
72
+ //# sourceMappingURL=openfda-drugsfda.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openfda-drugsfda.d.ts","sourceRoot":"","sources":["../src/openfda-drugsfda.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAKH,OAAO,EAAY,KAAK,UAAU,EAAqB,MAAM,WAAW,CAAC;AA4BzE,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,kBAAkB,EAAE;QAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,EAAE,CAAC;IACvE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC,CAAC;AACF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC,CAAC;AACF,MAAM,MAAM,mBAAmB,GAAG;IAChC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,WAAW,EAAE,kBAAkB,EAAE,CAAC;CACnC,CAAC;AAsCF,MAAM,MAAM,eAAe,GAAG;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,eAAe,GAAG,MAAM,CAW9D;AAGD,MAAM,MAAM,iBAAiB,GAAG,eAAe,GAAG;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,CAmHhF"}
@@ -0,0 +1,230 @@
1
+ /**
2
+ * openfda-drugsfda.ts — openFDA DRUG APPROVALS (Drugs@FDA, api.fda.gov) — the
3
+ * PHARMA REGULATORY-APPROVAL lane. FDA-approved drug applications (NDA/ANDA/BLA):
4
+ * the sponsor, the application number, each approved product (brand/generic name,
5
+ * dosage form, route, marketing status), and the submission/approval history.
6
+ * Answers "what drugs did sponsor X get approved, and which are still marketed" —
7
+ * pharma vendor product/approval intelligence.
8
+ *
9
+ * ★ SAME SOURCE + ENVELOPE + CRUX as openfda.ts / openfda-device.ts (ADR-0054/0056).
10
+ * Same host (api.fda.gov), same envelope `{ meta:{ results:{ skip,limit,total }},
11
+ * results:[…] }`, same ★no-match→HTTP-404-NOT_FOUND-as-honest-empty crux, same
12
+ * optional query-key K-test, same fixed-host SSRF idiom, same structured-only (no
13
+ * raw Lucene passthrough) search. REUSES openfda.ts's `fetchOpenfda`,
14
+ * `readOpenfdaError`, `luceneQuote`, `openfdaApiKey`, `OPENFDA_HOST` verbatim.
15
+ *
16
+ * GET https://api.fda.gov/drug/drugsfda.json?search=<lucene>&limit=&skip=[&api_key=]
17
+ * → { meta:{ results:{ skip,limit,total }}, results:[ { application_number,
18
+ * sponsor_name, products:[…], submissions:[…] } ] }.
19
+ *
20
+ * ★ HONESTY (mirrors the siblings exactly): [P1] totalAvailable = meta.results.total
21
+ * EXACT (never results.length); skip/limit offset pagination. [★P2] 404 NOT_FOUND
22
+ * ⇒ honest empty (never thrown); other 4xx ⇒ invalid_input surfacing openFDA's
23
+ * message; 5xx/429 ⇒ THROW. [P3] every scalar via `str` (null-never-""); nested
24
+ * products/submissions arrays default to [] and each field is str (never fabricated).
25
+ * [P4] meta.results / results absent-or-mis-shaped ⇒ driftError. [K-test] the
26
+ * OPTIONAL OPENFDA_API_KEY rides ONLY &api_key=; label/source name mode only.
27
+ * [SSRF] fixed host; all VALUES Lucene-escaped + phrase-quoted via URLSearchParams.
28
+ */
29
+ import { ToolErrorCarrier, errorFromResponse } from "./errors.js";
30
+ import { driftError } from "./datasource.js";
31
+ import { str } from "./coerce.js";
32
+ import { withMeta } from "./meta.js";
33
+ import { OPENFDA_HOST, fetchOpenfda, readOpenfdaError, luceneQuote, openfdaApiKey, } from "./openfda.js";
34
+ const DEFAULT_LIMIT = 25;
35
+ const MAX_LIMIT = 100;
36
+ /** host+path-only label (→ ToolError.upstreamEndpoint); NEVER carries the key. */
37
+ const DRUGSFDA_LABEL = "openfda:/drug/drugsfda";
38
+ // ─── Honesty notes ────────────────────────────────────────────────
39
+ const NOT_DETERMINATION_NOTE = "openFDA Drugs@FDA records are FDA-published drug-application data; treat approval/marketing status as of the source's last publication. This is reference data, not a live regulatory determination.";
40
+ const KEYLESS_NOTE = "Keyless: no OPENFDA_API_KEY is set. openFDA allows ~1000 requests/day without a key; a free key raises the rate limit (get one at https://open.fda.gov/apis/authentication/).";
41
+ const KEYED_NOTE = "OPENFDA_API_KEY is set — it rides ONLY the &api_key= query parameter to api.fda.gov (openFDA has no header option), raising the rate limit. Its value is NEVER logged, echoed, or placed in this response.";
42
+ const NO_FILTER_NOTE = "No structured filters were applied — this is an unscoped scan of the WHOLE Drugs@FDA collection. Add sponsorName / brandName / activeIngredient / applicationNumber to scope the result set.";
43
+ const MARKETING_NOTE = "products[].marketingStatus is the FDA marketing category (e.g. 'Prescription', 'Over-the-counter', 'Discontinued', 'None (Tentative Approval)') — a 'Discontinued' product is NOT an approval revocation. submissions[] is the application's action history (ORIG approval + subsequent supplements).";
44
+ /** Map ONE Drugs@FDA result row → the curated shape. Every scalar via `str`. */
45
+ function mapApplication(row) {
46
+ const r = (row ?? {});
47
+ const products = Array.isArray(r.products) ? r.products : [];
48
+ const submissions = Array.isArray(r.submissions) ? r.submissions : [];
49
+ return {
50
+ applicationNumber: str(r.application_number),
51
+ sponsorName: str(r.sponsor_name),
52
+ products: products.map((p) => {
53
+ const pr = (p ?? {});
54
+ const ings = Array.isArray(pr.active_ingredients) ? pr.active_ingredients : [];
55
+ return {
56
+ brandName: str(pr.brand_name),
57
+ genericIngredients: ings.map((a) => {
58
+ const ai = (a ?? {});
59
+ return { name: str(ai.name), strength: str(ai.strength) };
60
+ }),
61
+ dosageForm: str(pr.dosage_form),
62
+ route: str(pr.route),
63
+ marketingStatus: str(pr.marketing_status),
64
+ };
65
+ }),
66
+ submissions: submissions.map((s) => {
67
+ const su = (s ?? {});
68
+ return {
69
+ submissionType: str(su.submission_type),
70
+ submissionNumber: str(su.submission_number),
71
+ submissionStatus: str(su.submission_status),
72
+ submissionStatusDate: str(su.submission_status_date),
73
+ submissionClass: str(su.submission_class_code_description),
74
+ };
75
+ }),
76
+ };
77
+ }
78
+ export function buildDrugsfdaSearch(f) {
79
+ const clauses = [];
80
+ if (f.sponsorName !== undefined)
81
+ clauses.push(`sponsor_name:${luceneQuote(f.sponsorName)}`);
82
+ if (f.brandName !== undefined)
83
+ clauses.push(`products.brand_name:${luceneQuote(f.brandName)}`);
84
+ if (f.activeIngredient !== undefined)
85
+ clauses.push(`products.active_ingredients.name:${luceneQuote(f.activeIngredient)}`);
86
+ if (f.applicationNumber !== undefined)
87
+ clauses.push(`application_number:${luceneQuote(f.applicationNumber)}`);
88
+ return clauses.join(" AND ");
89
+ }
90
+ /**
91
+ * Search openFDA Drugs@FDA drug-approval applications with structured filters →
92
+ * curated application rows (sponsor, application number, approved products, submission
93
+ * history) + honest `_meta`. KEYLESS (OPTIONAL OPENFDA_API_KEY only raises the rate
94
+ * limit). totalAvailable = meta.results.total (EXACT); skip/limit pagination. A
95
+ * no-match query (openFDA HTTP 404 NOT_FOUND) ⇒ an honest empty, never a throw.
96
+ */
97
+ export async function drugApprovals(args) {
98
+ const limit = clampLimit(args.limit);
99
+ const skip = clampSkip(args.skip);
100
+ const filters = {
101
+ sponsorName: args.sponsorName,
102
+ brandName: args.brandName,
103
+ activeIngredient: args.activeIngredient,
104
+ applicationNumber: args.applicationNumber,
105
+ };
106
+ const search = buildDrugsfdaSearch(filters);
107
+ const filtersApplied = [];
108
+ if (args.sponsorName !== undefined)
109
+ filtersApplied.push("sponsorName");
110
+ if (args.brandName !== undefined)
111
+ filtersApplied.push("brandName");
112
+ if (args.activeIngredient !== undefined)
113
+ filtersApplied.push("activeIngredient");
114
+ if (args.applicationNumber !== undefined)
115
+ filtersApplied.push("applicationNumber");
116
+ const params = new URLSearchParams();
117
+ if (search !== "")
118
+ params.set("search", search);
119
+ params.set("limit", String(limit));
120
+ params.set("skip", String(skip));
121
+ const key = openfdaApiKey();
122
+ if (key !== undefined)
123
+ params.set("api_key", key); // OPTIONAL — &api_key= ONLY
124
+ const url = `https://${OPENFDA_HOST}/drug/drugsfda.json?${params.toString()}`;
125
+ const res = await fetchOpenfda(url, DRUGSFDA_LABEL);
126
+ if (res.status === 404) {
127
+ const { code, message } = await readOpenfdaError(res);
128
+ if (code === "NOT_FOUND") {
129
+ return emptyResult(limit, skip, filtersApplied, key !== undefined);
130
+ }
131
+ throw new ToolErrorCarrier({
132
+ ...errorFromResponse(res, DRUGSFDA_LABEL),
133
+ message: message
134
+ ? `openFDA returned HTTP 404 at ${DRUGSFDA_LABEL}: ${message}`
135
+ : `Resource not found at ${DRUGSFDA_LABEL} (HTTP 404).`,
136
+ });
137
+ }
138
+ if (res.status >= 400 && res.status < 500 && res.status !== 429) {
139
+ const { message } = await readOpenfdaError(res);
140
+ throw new ToolErrorCarrier({
141
+ kind: "invalid_input",
142
+ retryable: false,
143
+ message: message
144
+ ? `openFDA rejected the request (HTTP ${res.status}) at ${DRUGSFDA_LABEL}: ${message}`
145
+ : `Bad request (HTTP ${res.status}) at ${DRUGSFDA_LABEL}.`,
146
+ upstreamStatus: res.status,
147
+ upstreamEndpoint: DRUGSFDA_LABEL,
148
+ });
149
+ }
150
+ if (!res.ok) {
151
+ throw new ToolErrorCarrier(errorFromResponse(res, DRUGSFDA_LABEL));
152
+ }
153
+ let body;
154
+ try {
155
+ body = await res.json();
156
+ }
157
+ catch (e) {
158
+ if (e instanceof SyntaxError) {
159
+ throw driftError(DRUGSFDA_LABEL, `openFDA ${DRUGSFDA_LABEL} returned a non-JSON body at HTTP 200 — schema drift (never read as an empty result).`);
160
+ }
161
+ throw e;
162
+ }
163
+ const b = (body ?? {});
164
+ const meta = (b.meta ?? {});
165
+ const metaResults = meta.results;
166
+ if (metaResults === undefined || metaResults === null || typeof metaResults !== "object") {
167
+ throw driftError(DRUGSFDA_LABEL, `openFDA ${DRUGSFDA_LABEL} shape drift — meta.results (the skip/limit/total carrier) is missing.`);
168
+ }
169
+ if (!Array.isArray(b.results)) {
170
+ throw driftError(DRUGSFDA_LABEL, `openFDA ${DRUGSFDA_LABEL} shape drift — results must be an array.`);
171
+ }
172
+ const applications = b.results.map(mapApplication);
173
+ const returned = applications.length;
174
+ const rawTotal = metaResults.total;
175
+ const totalAvailable = typeof rawTotal === "number" && Number.isFinite(rawTotal) ? rawTotal : null;
176
+ const hasMore = totalAvailable !== null && skip + returned < totalAvailable;
177
+ const nextOffset = hasMore ? skip + returned : null;
178
+ const notes = [NOT_DETERMINATION_NOTE, MARKETING_NOTE, keyNote(key !== undefined)];
179
+ if (filtersApplied.length === 0)
180
+ notes.push(NO_FILTER_NOTE);
181
+ return withMeta({ applications }, {
182
+ source: `${OPENFDA_HOST} /drug/drugsfda (openFDA Drugs@FDA approvals; ${key !== undefined ? "OPENFDA_API_KEY rate-limit key applied" : "keyless"})`,
183
+ keylessMode: true,
184
+ returned,
185
+ totalAvailable,
186
+ filtersApplied,
187
+ filtersDropped: [],
188
+ fieldsUnavailable: [],
189
+ pagination: { offset: skip, limit, hasMore, nextOffset },
190
+ notes,
191
+ });
192
+ }
193
+ // ─── Small helpers ────────────────────────────────────────────────
194
+ function keyNote(hasKey) {
195
+ return hasKey ? KEYED_NOTE : KEYLESS_NOTE;
196
+ }
197
+ function emptyResult(limit, skip, filtersApplied, hasKey) {
198
+ return withMeta({ applications: [] }, {
199
+ source: `${OPENFDA_HOST} /drug/drugsfda (openFDA Drugs@FDA approvals; ${hasKey ? "OPENFDA_API_KEY rate-limit key applied" : "keyless"})`,
200
+ keylessMode: true,
201
+ returned: 0,
202
+ totalAvailable: 0,
203
+ filtersApplied,
204
+ filtersDropped: [],
205
+ fieldsUnavailable: [],
206
+ pagination: { offset: skip, limit, hasMore: false, nextOffset: null },
207
+ notes: [
208
+ "No Drugs@FDA applications matched this query (openFDA returned HTTP 404 NOT_FOUND — the source's honest no-match). This is an exact empty, not an error.",
209
+ NOT_DETERMINATION_NOTE,
210
+ keyNote(hasKey),
211
+ ],
212
+ });
213
+ }
214
+ function clampLimit(v) {
215
+ if (typeof v !== "number" || !Number.isFinite(v))
216
+ return DEFAULT_LIMIT;
217
+ const n = Math.floor(v);
218
+ if (n < 1)
219
+ return 1;
220
+ if (n > MAX_LIMIT)
221
+ return MAX_LIMIT;
222
+ return n;
223
+ }
224
+ function clampSkip(v) {
225
+ if (typeof v !== "number" || !Number.isFinite(v))
226
+ return 0;
227
+ const n = Math.floor(v);
228
+ return n < 0 ? 0 : n;
229
+ }
230
+ //# sourceMappingURL=openfda-drugsfda.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openfda-drugsfda.js","sourceRoot":"","sources":["../src/openfda-drugsfda.ts"],"names":[],"mappings":"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"}