@cliwant/mcp-sam-gov 1.2.0 → 1.4.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 (91) hide show
  1. package/README.ja.md +22 -9
  2. package/README.ko.md +22 -9
  3. package/README.md +70 -12
  4. package/dist/bea.d.ts +105 -0
  5. package/dist/bea.d.ts.map +1 -0
  6. package/dist/bea.js +303 -0
  7. package/dist/bea.js.map +1 -0
  8. package/dist/census-economic.d.ts +1 -1
  9. package/dist/census-economic.d.ts.map +1 -1
  10. package/dist/census-economic.js +12 -6
  11. package/dist/census-economic.js.map +1 -1
  12. package/dist/cms-facility.d.ts +112 -0
  13. package/dist/cms-facility.d.ts.map +1 -0
  14. package/dist/cms-facility.js +311 -0
  15. package/dist/cms-facility.js.map +1 -0
  16. package/dist/cms-hospital.d.ts +105 -0
  17. package/dist/cms-hospital.d.ts.map +1 -0
  18. package/dist/cms-hospital.js +290 -0
  19. package/dist/cms-hospital.js.map +1 -0
  20. package/dist/cms-supplier.d.ts +133 -0
  21. package/dist/cms-supplier.d.ts.map +1 -0
  22. package/dist/cms-supplier.js +414 -0
  23. package/dist/cms-supplier.js.map +1 -0
  24. package/dist/cms-utilization.d.ts +113 -0
  25. package/dist/cms-utilization.d.ts.map +1 -0
  26. package/dist/cms-utilization.js +328 -0
  27. package/dist/cms-utilization.js.map +1 -0
  28. package/dist/courtlistener.d.ts +115 -0
  29. package/dist/courtlistener.d.ts.map +1 -0
  30. package/dist/courtlistener.js +398 -0
  31. package/dist/courtlistener.js.map +1 -0
  32. package/dist/cpsc.d.ts +81 -0
  33. package/dist/cpsc.d.ts.map +1 -0
  34. package/dist/cpsc.js +283 -0
  35. package/dist/cpsc.js.map +1 -0
  36. package/dist/dol.d.ts +118 -0
  37. package/dist/dol.d.ts.map +1 -0
  38. package/dist/dol.js +421 -0
  39. package/dist/dol.js.map +1 -0
  40. package/dist/epa-envirofacts.d.ts +97 -0
  41. package/dist/epa-envirofacts.d.ts.map +1 -0
  42. package/dist/epa-envirofacts.js +292 -0
  43. package/dist/epa-envirofacts.js.map +1 -0
  44. package/dist/fred.d.ts +1 -1
  45. package/dist/fred.js +1 -1
  46. package/dist/keys.d.ts +11 -8
  47. package/dist/keys.d.ts.map +1 -1
  48. package/dist/keys.js +55 -8
  49. package/dist/keys.js.map +1 -1
  50. package/dist/lda.d.ts +105 -0
  51. package/dist/lda.d.ts.map +1 -0
  52. package/dist/lda.js +317 -0
  53. package/dist/lda.js.map +1 -0
  54. package/dist/nhtsa.d.ts +91 -0
  55. package/dist/nhtsa.d.ts.map +1 -0
  56. package/dist/nhtsa.js +263 -0
  57. package/dist/nhtsa.js.map +1 -0
  58. package/dist/nonprofit.d.ts +116 -0
  59. package/dist/nonprofit.d.ts.map +1 -0
  60. package/dist/nonprofit.js +342 -0
  61. package/dist/nonprofit.js.map +1 -0
  62. package/dist/openfda-device.d.ts +85 -0
  63. package/dist/openfda-device.d.ts.map +1 -0
  64. package/dist/openfda-device.js +277 -0
  65. package/dist/openfda-device.js.map +1 -0
  66. package/dist/openfda.d.ts +133 -0
  67. package/dist/openfda.d.ts.map +1 -0
  68. package/dist/openfda.js +402 -0
  69. package/dist/openfda.js.map +1 -0
  70. package/dist/server.d.ts.map +1 -1
  71. package/dist/server.js +872 -6
  72. package/dist/server.js.map +1 -1
  73. package/package.json +2 -1
  74. package/src/bea.ts +372 -0
  75. package/src/census-economic.ts +12 -6
  76. package/src/cms-facility.ts +379 -0
  77. package/src/cms-hospital.ts +344 -0
  78. package/src/cms-supplier.ts +527 -0
  79. package/src/cms-utilization.ts +389 -0
  80. package/src/courtlistener.ts +465 -0
  81. package/src/cpsc.ts +333 -0
  82. package/src/dol.ts +515 -0
  83. package/src/epa-envirofacts.ts +342 -0
  84. package/src/fred.ts +1 -1
  85. package/src/keys.ts +60 -8
  86. package/src/lda.ts +385 -0
  87. package/src/nhtsa.ts +352 -0
  88. package/src/nonprofit.ts +460 -0
  89. package/src/openfda-device.ts +356 -0
  90. package/src/openfda.ts +495 -0
  91. package/src/server.ts +995 -6
@@ -0,0 +1,328 @@
1
+ /**
2
+ * cms-utilization.ts — CMS Medicare Physician & Other Practitioners "by Provider
3
+ * and Service" utilization (`data.cms.gov`, the data-API v1 dataset endpoint;
4
+ * ADR-0061). KEYLESS.
5
+ *
6
+ * WHAT IT ADDS: `cms_medicare_provider_services` — a healthcare-market /
7
+ * competitor-utilization lane: for a given provider (NPI) or state, what Medicare
8
+ * Part-B services (HCPCS) did providers render, to how many beneficiaries, at what
9
+ * submitted / Medicare-allowed / Medicare-paid amounts. The demand-side complement
10
+ * to NPPES (who the providers ARE) — this is what they actually BILL.
11
+ *
12
+ * ★THE TWO-REQUEST PATTERN (the load-bearing P1 honesty — MIRRORS epa-envirofacts):
13
+ * the data-API's `/data` slice is a bare JSON array that reports NO total. So the
14
+ * EXACT total for a filter comes from a SEPARATE count sub-query — the identical
15
+ * `filter[...]` on the `/data-viewer/stats` endpoint returns
16
+ * `{ "data": { "found_rows": N, "total_rows": M } }`. This tool runs the stats
17
+ * count FIRST (best-effort) then the data slice: totalAvailable = found_rows (P1,
18
+ * the per-filter EXACT total), NEVER the returned rows' length. If the stats
19
+ * sub-query fails or is absent, totalAvailable falls to null + a disclosing note
20
+ * (never a length-faked total) and the data slice still returns.
21
+ *
22
+ * ★THE FILTER-REQUIRED INPUT GUARD: the table is 9.78M rows. An all-empty query
23
+ * (no npi, no state) is REFUSED with invalid_input (0 fetch) — providerType /
24
+ * hcpcsCode alone are NOT enough to scope; a caller MUST pin npi OR state.
25
+ *
26
+ * The module writes ZERO fetch/coercion/error/meta code — it REUSES `getJson`
27
+ * (redirect:"error") / `driftError` (datasource.ts), `str`/`num` (coerce.ts,
28
+ * null-never-empty-string / null-never-0), and `withMeta`·`buildMeta` (meta.ts,
29
+ * offset pagination + totalAvailable). It MIRRORS census-economic.ts's fixed-host
30
+ * SSRF idiom (a single host const + a post-construction hostname/protocol assertion
31
+ * + redirect:"error") and epa-envirofacts.ts's count-first two-request pattern +
32
+ * schema_drift catch-ladder (ToolErrorCarrier rethrow FIRST so a 5xx keeps its
33
+ * taxonomy → SyntaxError→driftError → bare rethrow).
34
+ *
35
+ * GET https://data.cms.gov/data-api/v1/dataset/{uuid}/data-viewer/stats
36
+ * ?filter[Rndrng_Prvdr_State_Abrvtn]=VA (the COUNT sub-query)
37
+ * → { "data": { "found_rows": 278254, "total_rows": 9781673 } }
38
+ * GET https://data.cms.gov/data-api/v1/dataset/{uuid}/data
39
+ * ?size=&offset=&filter[Rndrng_Prvdr_State_Abrvtn]=VA (the DATA slice)
40
+ * → [ { Rndrng_NPI, Rndrng_Prvdr_Last_Org_Name, …, Avg_Mdcr_Pymt_Amt }, … ]
41
+ *
42
+ * ★ SSRF: the host is a compile-time literal (`CMS_HOST`); the dataset UUID + the
43
+ * endpoint paths are MODULE literals. Every USER filter value rides as a
44
+ * URLSearchParams VALUE (`filter[Col]=Val`) — URLSearchParams encodes the bracket
45
+ * key AND the value, so a value can never break out into the path or inject a
46
+ * parameter. npi is `^\d{10}$`; state `^[A-Za-z]{2}$`; hcpcsCode `^[A-Za-z0-9]{1,10}$`;
47
+ * providerType is a bounded free-text charclass; size/offset are coerced to
48
+ * integers. A post-construction hostname/protocol assertion + `redirect:"error"`
49
+ * fail closed on any off-host 3xx.
50
+ *
51
+ * ★ PII NOTE: this is public PROVIDER-level AGGREGATE data — no patient identifiers.
52
+ * Provider name / practice address / NPI is public professional information (the
53
+ * same public surface as NPPES), so it is fine to surface.
54
+ *
55
+ * ★ HONESTY (ADR-0061 P1–P5, live-verified 2026-07-15 on data.cms.gov):
56
+ * [input] require npi OR state — an all-empty query is REFUSED (0 fetch) so the
57
+ * whole 9.78M-row table is never scanned.
58
+ * [P1] totalAvailable = the stats sub-query's found_rows (EXACT — e.g. VA =
59
+ * 278254), NOT the slice length. hasMore = offset+returned < total. Stats
60
+ * fails/absent ⇒ totalAvailable:null + a disclosing note.
61
+ * [P2] an empty array ⇒ honest empty (returned:0). getJson maps a 4xx/5xx via
62
+ * errorFromResponse and THROWS (503 ⇒ upstream_unavailable, 400 ⇒
63
+ * invalid_input, 404 ⇒ not_found); a 200 non-array/non-JSON body ⇒
64
+ * schema_drift (NEVER a fabricated empty).
65
+ * [P3] Tot_ / Avg_ fields via num() (numeric strings → numbers; a real 0 stays 0;
66
+ * absent ⇒ null, never 0-faked); NPI/codes/HCPCS/names as strings
67
+ * (null-never-empty-string).
68
+ * [P4] a data body that is not an array ⇒ driftError; a stats body missing
69
+ * found_rows ⇒ totalAvailable:null (handled, not a crash).
70
+ */
71
+ import { ToolErrorCarrier } from "./errors.js";
72
+ import { getJson, driftError } from "./datasource.js";
73
+ import { str, num } from "./coerce.js";
74
+ import { withMeta } from "./meta.js";
75
+ // Re-export the shared honesty coercions (single audited copy in ./coerce.js) so a
76
+ // regression fails together across sources. NO local num/str.
77
+ export { num, str };
78
+ // ─── SSRF core: the single fixed host + module-literal path pieces ──
79
+ const CMS_HOST = "data.cms.gov";
80
+ // HOST-only label — surfaces in ToolError.upstreamEndpoint; keyless, so no token
81
+ // can ever appear here.
82
+ const CMS_LABEL = "cms-utilization:data.cms.gov";
83
+ // ★THE DATASET UUID — a SPECIFIC ANNUAL VINTAGE of "Medicare Physician & Other
84
+ // Practitioners — by Provider and Service" (9,781,673 rows at build time, the most
85
+ // recent published year). ★UPDATE YEARLY: CMS publishes a NEW uuid for each new
86
+ // calendar year of utilization; this constant pins ONE vintage. The active vintage
87
+ // is surfaced to the caller in a _meta note (VINTAGE_NOTE) so a consumer never
88
+ // mistakes it for "current" or an unspecified year.
89
+ const CMS_DATASET_UUID = "92396110-2aed-4d63-a6a2-5d6207d46a29";
90
+ // ─── Validation charclasses (SSRF + "verify the input" honesty) ───
91
+ const NPI_RE = /^\d{10}$/; // a 10-digit National Provider Identifier
92
+ const STATE_RE = /^[A-Za-z]{2}$/; // 2-letter state/territory abbreviation
93
+ const HCPCS_RE = /^[A-Za-z0-9]{1,10}$/; // HCPCS/CPT code, e.g. "97110", "G0463"
94
+ // providerType is a specialty label, e.g. "Physical Therapist in Private Practice".
95
+ // It rides as a URLSearchParams VALUE (encoded), so this bound is a sanity guard,
96
+ // not an SSRF necessity: letters/digits/space and common punctuation only.
97
+ const PROVIDER_TYPE_RE = /^[A-Za-z0-9 &.,()/'-]{1,100}$/;
98
+ const SIZE_MIN = 1;
99
+ const SIZE_MAX = 100;
100
+ const SIZE_DEFAULT = 25;
101
+ // ─── Honesty notes (ADR-0061 required set) ────────────────────────
102
+ const VINTAGE_NOTE = `Source dataset: CMS "Medicare Physician & Other Practitioners — by Provider and Service" (data.cms.gov dataset ${CMS_DATASET_UUID}) — a SPECIFIC ANNUAL VINTAGE (the most recent published year at build time), NOT a live/current or a multi-year figure. Amounts and service counts are as-of that reference year. CMS publishes a new dataset id each year.`;
103
+ const AGGREGATE_NOTE = "These are public PROVIDER-level AGGREGATE utilization figures (no patient identifiers). Averages (avgSubmittedCharge / avgMedicareAllowed / avgMedicarePayment) are per-service means for the provider+HCPCS row; totalBeneficiaries is CMS-rounded and suppressed below 11 in the source. This is a utilization snapshot, NOT a fraud, quality, or fitness determination.";
104
+ const COUNT_FALLBACK_NOTE = "The count sub-query (…/data-viewer/stats) failed or did not report found_rows, so totalAvailable is null (unknown) — it was NOT faked from the returned row count. hasMore is a heuristic (a full page ⇒ likely more); re-page with offset to confirm.";
105
+ /**
106
+ * Join the CMS Last_Org_Name + First_Name into one display name. An ORGANIZATION
107
+ * row (entity code "O") carries the org name in Last_Org_Name with an empty
108
+ * First_Name ⇒ just the org name. An INDIVIDUAL carries both ⇒ "Last, First".
109
+ * Either absent ⇒ the present one; both absent ⇒ null (never a fabricated "").
110
+ */
111
+ export function joinProviderName(lastOrg, first) {
112
+ const last = str(lastOrg);
113
+ const firstName = str(first);
114
+ if (last !== null && firstName !== null)
115
+ return `${last}, ${firstName}`;
116
+ return last ?? firstName ?? null;
117
+ }
118
+ /** Map ONE data-API row → the curated provider-service shape. */
119
+ function mapService(row) {
120
+ const r = (row ?? {});
121
+ return {
122
+ npi: str(r.Rndrng_NPI),
123
+ providerName: joinProviderName(r.Rndrng_Prvdr_Last_Org_Name, r.Rndrng_Prvdr_First_Name),
124
+ credentials: str(r.Rndrng_Prvdr_Crdntls),
125
+ providerType: str(r.Rndrng_Prvdr_Type),
126
+ city: str(r.Rndrng_Prvdr_City),
127
+ state: str(r.Rndrng_Prvdr_State_Abrvtn),
128
+ zip: str(r.Rndrng_Prvdr_Zip5),
129
+ hcpcsCode: str(r.HCPCS_Cd),
130
+ hcpcsDescription: str(r.HCPCS_Desc),
131
+ totalBeneficiaries: num(r.Tot_Benes),
132
+ totalServices: num(r.Tot_Srvcs),
133
+ avgSubmittedCharge: num(r.Avg_Sbmtd_Chrg),
134
+ avgMedicareAllowed: num(r.Avg_Mdcr_Alowd_Amt),
135
+ avgMedicarePayment: num(r.Avg_Mdcr_Pymt_Amt),
136
+ };
137
+ }
138
+ // ─── SSRF-guarded fetch (fixed host + hostname assertion + redirect:"error") ──
139
+ /**
140
+ * GET one data.cms.gov JSON resource at a MODULE-BUILT URL (the dataset UUID + the
141
+ * endpoint path are literals; all user filter VALUES are already carried in the
142
+ * URLSearchParams `query`). Asserts the CONSTRUCTED URL's hostname === the fixed
143
+ * host over https, and sets `redirect:"error"` (an off-host 3xx must NOT be
144
+ * followed). Keyless — no headers.
145
+ */
146
+ async function getCms(path, query) {
147
+ const url = `https://${CMS_HOST}${path}?${query.toString()}`;
148
+ const built = new URL(url);
149
+ if (built.hostname !== CMS_HOST || built.protocol !== "https:") {
150
+ throw new ToolErrorCarrier({
151
+ kind: "invalid_input",
152
+ message: `Constructed CMS data-API URL host ${JSON.stringify(built.hostname)} (${built.protocol}) does not match the fixed host ${JSON.stringify(CMS_HOST)} over https — refusing to fetch (SSRF safety).`,
153
+ retryable: false,
154
+ upstreamEndpoint: CMS_LABEL,
155
+ });
156
+ }
157
+ return getJson(built.toString(), { label: CMS_LABEL, redirect: "error" });
158
+ }
159
+ /**
160
+ * Fetch Medicare Part-B provider-service utilization rows for an NPI / state (+
161
+ * optional providerType / hcpcsCode) → normalized service rows + honest `_meta`.
162
+ * REQUIRES npi OR state (an all-empty query is refused). Runs a stats count
163
+ * sub-query FIRST for the EXACT total (P1), then the data slice; a count failure
164
+ * degrades to totalAvailable:null + a note (never a length-faked total).
165
+ */
166
+ export async function providerServices(args) {
167
+ // ── [input guard] require npi OR state (never scan the whole 9.78M-row table).
168
+ // providerType / hcpcsCode alone are NOT sufficient to scope. ──
169
+ const hasNpi = args.npi !== undefined && args.npi !== "";
170
+ const hasState = args.state !== undefined && args.state !== "";
171
+ if (!hasNpi && !hasState) {
172
+ throw new ToolErrorCarrier({
173
+ kind: "invalid_input",
174
+ retryable: false,
175
+ message: "cms_medicare_provider_services requires at least `npi` (10-digit) OR `state` (2-letter) — an all-empty query would scan the entire 9.78M-row Medicare utilization table and is refused. providerType/hcpcsCode alone are not enough; add npi or state and retry.",
176
+ upstreamEndpoint: CMS_LABEL,
177
+ });
178
+ }
179
+ // ── Validate + build the filter params (SSRF: charclass + URLSearchParams value).
180
+ // URLSearchParams encodes both the bracket key and the value. ──
181
+ const filters = new URLSearchParams();
182
+ const filtersApplied = [];
183
+ if (hasNpi) {
184
+ const npi = args.npi;
185
+ if (!NPI_RE.test(npi)) {
186
+ throw new ToolErrorCarrier({
187
+ kind: "invalid_input",
188
+ retryable: false,
189
+ message: `Invalid npi ${JSON.stringify(npi)} — expected a 10-digit National Provider Identifier (^\\d{10}$).`,
190
+ upstreamEndpoint: CMS_LABEL,
191
+ });
192
+ }
193
+ filters.set("filter[Rndrng_NPI]", npi);
194
+ filtersApplied.push(`npi:${npi}`);
195
+ }
196
+ if (hasState) {
197
+ const state = args.state;
198
+ if (!STATE_RE.test(state)) {
199
+ throw new ToolErrorCarrier({
200
+ kind: "invalid_input",
201
+ retryable: false,
202
+ message: `Invalid state ${JSON.stringify(state)} — expected a 2-letter state/territory code (^[A-Za-z]{2}$), e.g. "VA".`,
203
+ upstreamEndpoint: CMS_LABEL,
204
+ });
205
+ }
206
+ filters.set("filter[Rndrng_Prvdr_State_Abrvtn]", state.toUpperCase());
207
+ filtersApplied.push(`state:${state.toUpperCase()}`);
208
+ }
209
+ if (args.providerType !== undefined && args.providerType !== "") {
210
+ const pt = args.providerType;
211
+ if (!PROVIDER_TYPE_RE.test(pt)) {
212
+ throw new ToolErrorCarrier({
213
+ kind: "invalid_input",
214
+ retryable: false,
215
+ message: `Invalid providerType ${JSON.stringify(pt)} — allowed: letters, digits, space, & . , ( ) / ' - (≤100 chars).`,
216
+ upstreamEndpoint: CMS_LABEL,
217
+ });
218
+ }
219
+ filters.set("filter[Rndrng_Prvdr_Type]", pt);
220
+ filtersApplied.push(`providerType:${pt}`);
221
+ }
222
+ if (args.hcpcsCode !== undefined && args.hcpcsCode !== "") {
223
+ const hcpcs = args.hcpcsCode;
224
+ if (!HCPCS_RE.test(hcpcs)) {
225
+ throw new ToolErrorCarrier({
226
+ kind: "invalid_input",
227
+ retryable: false,
228
+ message: `Invalid hcpcsCode ${JSON.stringify(hcpcs)} — expected an alphanumeric HCPCS/CPT code (^[A-Za-z0-9]{1,10}$), e.g. "97110" or "G0463".`,
229
+ upstreamEndpoint: CMS_LABEL,
230
+ });
231
+ }
232
+ filters.set("filter[HCPCS_Cd]", hcpcs.toUpperCase());
233
+ filtersApplied.push(`hcpcsCode:${hcpcs.toUpperCase()}`);
234
+ }
235
+ // ── Coerce size / offset to bounded integers (belt-and-suspenders behind the
236
+ // server Zod; a DIRECT handler call bypasses Zod). ──
237
+ let size = SIZE_DEFAULT;
238
+ if (typeof args.size === "number" && Number.isFinite(args.size)) {
239
+ size = Math.trunc(args.size);
240
+ if (size < SIZE_MIN)
241
+ size = SIZE_MIN;
242
+ if (size > SIZE_MAX)
243
+ size = SIZE_MAX;
244
+ }
245
+ let offset = 0;
246
+ if (typeof args.offset === "number" && Number.isFinite(args.offset) && args.offset > 0) {
247
+ offset = Math.trunc(args.offset);
248
+ }
249
+ const dataBase = `/data-api/v1/dataset/${CMS_DATASET_UUID}/data`;
250
+ const statsPath = `/data-api/v1/dataset/${CMS_DATASET_UUID}/data-viewer/stats`;
251
+ // ── (1) The COUNT sub-query FIRST (best-effort — the EXACT total, P1). The stats
252
+ // body is `{ data: { found_rows, total_rows } }`. Any failure (network/5xx/
253
+ // drift/missing field) degrades to totalAvailable:null + a disclosing note; it
254
+ // NEVER throws and NEVER fakes the total from the slice length. ──
255
+ let totalAvailable = null;
256
+ let countFailed = false;
257
+ try {
258
+ const statsBody = await getCms(statsPath, filters);
259
+ const dataObj = statsBody !== null &&
260
+ typeof statsBody === "object" &&
261
+ typeof statsBody.data === "object" &&
262
+ statsBody.data !== null
263
+ ? statsBody.data
264
+ : undefined;
265
+ if (dataObj !== undefined) {
266
+ const t = num(dataObj.found_rows);
267
+ if (t !== null && t >= 0) {
268
+ totalAvailable = t;
269
+ }
270
+ else {
271
+ countFailed = true; // present body but no usable found_rows (P4)
272
+ }
273
+ }
274
+ else {
275
+ countFailed = true; // stats body not the expected { data: {…} } shape
276
+ }
277
+ }
278
+ catch {
279
+ countFailed = true; // any count error ⇒ degrade, never propagate (P1)
280
+ }
281
+ // ── (2) The DATA slice — the authoritative request (a bare JSON array). ──
282
+ const dataQuery = new URLSearchParams(filters);
283
+ dataQuery.set("size", String(size));
284
+ dataQuery.set("offset", String(offset));
285
+ // Catch-ladder (epa-envirofacts / datagov-catalog shape): preserve the
286
+ // 4xx/5xx/timeout ToolErrorCarrier taxonomy FIRST; reclassify a 200 non-JSON
287
+ // SyntaxError to schema_drift SECOND; bare-rethrow LAST.
288
+ let body;
289
+ try {
290
+ body = await getCms(dataBase, dataQuery);
291
+ }
292
+ catch (e) {
293
+ if (e instanceof ToolErrorCarrier)
294
+ throw e;
295
+ if (e instanceof SyntaxError)
296
+ throw driftError(CMS_LABEL, "CMS data-API returned a non-JSON body at HTTP 200 — schema drift (never read as an empty result).");
297
+ throw e;
298
+ }
299
+ // [P4] the data body MUST be an array (a non-array 200 is drift, never a
300
+ // fabricated empty).
301
+ if (!Array.isArray(body)) {
302
+ throw driftError(CMS_LABEL, "CMS data-API shape drift — the /data response must be a JSON array of provider-service rows.");
303
+ }
304
+ const services = body.map(mapService);
305
+ const returned = services.length;
306
+ // ── [P1] pagination. total known ⇒ hasMore = offset+returned < total; total
307
+ // unknown (count degraded) ⇒ a full page is the honest heuristic for "more". ──
308
+ const hasMore = totalAvailable !== null
309
+ ? offset + returned < totalAvailable
310
+ : returned === size;
311
+ const nextOffset = hasMore ? offset + returned : null;
312
+ const notes = [VINTAGE_NOTE, AGGREGATE_NOTE];
313
+ if (countFailed)
314
+ notes.push(COUNT_FALLBACK_NOTE);
315
+ const meta = {
316
+ source: `${CMS_HOST} CMS Medicare Physician & Other Practitioners — by Provider and Service (keyless)`,
317
+ keylessMode: true,
318
+ returned,
319
+ totalAvailable,
320
+ filtersApplied,
321
+ filtersDropped: [],
322
+ fieldsUnavailable: [],
323
+ pagination: { offset, limit: size, hasMore, nextOffset },
324
+ notes,
325
+ };
326
+ return withMeta({ services }, meta);
327
+ }
328
+ //# sourceMappingURL=cms-utilization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cms-utilization.js","sourceRoot":"","sources":["../src/cms-utilization.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * courtlistener.ts — US FEDERAL COURT OPINIONS via CourtListener (ADR-0055) — the
3
+ * LITIGATION / case-law lane. Federal court decisions (opinions/clusters) — who
4
+ * sued whom in which court, the nature of suit, the disposition — the judicial
5
+ * signal no contract/spending/lobbying source carries (e.g. bid-protest and
6
+ * contract-claim opinions from the US Court of Federal Claims `uscfc` and the
7
+ * Federal Circuit `cafc`).
8
+ *
9
+ * ★ PROVENANCE — THIS IS NOT A .gov API (must be disclosed). The DATA is US federal
10
+ * court PUBLIC RECORDS, but the API is **CourtListener**, operated by the **Free
11
+ * Law Project** (a non-profit) which republishes those records KEYLESS. The .gov
12
+ * primary source (PACER) is PAYWALLED. So every response's `_meta.source` AND a
13
+ * note name CourtListener/Free Law Project and disclose the PACER-paywall — the
14
+ * tool never presents itself as a government API.
15
+ *
16
+ * ★ THIS IS A KEYLESS TOOL WITH AN *OPTIONAL* RATE-LIMIT TOKEN (the lda.ts /
17
+ * socrata app-token lineage, NOT the census/fred/bea key-REQUIRED lineage).
18
+ * Anonymous GETs return HTTP 200 — it works with NO token. A free
19
+ * COURTLISTENER_API_TOKEN only RAISES the shared rate limit; when set it rides
20
+ * ONLY as the `Authorization: Token <value>` request header (never the
21
+ * URL/label/_meta/notes/log — the K-test). When unset, NO auth header is sent
22
+ * (genuine keyless). This mirrors lda.ts's optional-Authorization discipline.
23
+ *
24
+ * GET https://www.courtlistener.com/api/rest/v4/search/
25
+ * ?q=&court=&filed_after=&filed_before=&type=o&order_by=<order>[&cursor=]
26
+ * → { count, next, previous, results:[{ caseName, court, court_id, dateFiled,
27
+ * docketNumber, suitNature, status, judge, citation, absolute_url, … }] }
28
+ *
29
+ * ★ HONESTY (ADR-0055 P1–P5):
30
+ * [P1] totalAvailable = `count` (the API's REAL total for the filter, e.g. the
31
+ * uscfc opinion corpus ~10595) — NEVER results.length. ★CURSOR pagination:
32
+ * `next` is a FULL URL carrying an opaque `cursor=` param (NOT page/offset).
33
+ * We EXTRACT the `cursor` value out of `next` and return it as `nextCursor`
34
+ * (offset/nextOffset null — a numeric offset is meaningless); hasMore = next
35
+ * is a non-null string. CourtListener v4 stops counting on deep cursor pages
36
+ * (`count:null`) — that is DISCLOSED (totalAvailable:null + a note), never
37
+ * fabricated as results.length.
38
+ * [P2] a genuine no-match (results:[]) ⇒ honest empty (returned:0). A 400 (bad
39
+ * param) ⇒ invalid_input surfacing the API's message. A 429 (unauth
40
+ * throttle) ⇒ rate_limited THROW (Retry-After honored, never routed around).
41
+ * A 5xx/timeout ⇒ upstream_unavailable THROW. A 200 non-JSON ⇒ schema_drift.
42
+ * [P3] dates are strings; `citation` may be an array/object ⇒ flattened to a safe
43
+ * string / string[] (never fabricated); judge / natureOfSuit / docketNumber
44
+ * are null when absent (never empty-string); every scalar null-never-empty.
45
+ * [P4] `results` non-array, or `count` neither a number NOR null ⇒ driftError
46
+ * (never a fabricated empty/total).
47
+ * [K-test] OPTIONAL token: when COURTLISTENER_API_TOKEN is set it rides ONLY the
48
+ * `Authorization: Token …` header — NEVER the URL/label/_meta/notes/log.
49
+ * Unset ⇒ anonymous (no auth header at all).
50
+ * [SSRF] fixed host `www.courtlistener.com`; a post-construction hostname/protocol
51
+ * assert + `redirect:"error"`; `court` charclass `^[a-z0-9]+$`; the dates
52
+ * charclass `^\d{4}-\d{2}-\d{2}$`; every VALUE rides URLSearchParams. The
53
+ * `cursor` is opaque but charclass-guarded; and when we EXTRACT the cursor
54
+ * from a `next` URL we RE-ASSERT that URL's host is courtlistener.com — an
55
+ * off-host `next` is REFUSED (schema_drift), never followed.
56
+ */
57
+ import { type MetaBundle } from "./meta.js";
58
+ export declare const COURTLISTENER_HOST = "www.courtlistener.com";
59
+ export declare const COURTLISTENER_CURSOR_RE: RegExp;
60
+ /**
61
+ * The optional Authorization header (keyless-first, lda.ts/socrata app-token
62
+ * lineage). Present ONLY when COURTLISTENER_API_TOKEN is set (non-blank); the value
63
+ * is NEVER logged / never placed in the URL, label, `_meta`, or a note. When unset,
64
+ * `{}` (no header ⇒ genuine anonymous).
65
+ */
66
+ export declare function courtlistenerAuthHeader(): Record<string, string>;
67
+ /** true iff a CourtListener token is configured (for the `_meta` note — never the value). */
68
+ export declare function courtlistenerTokenPresent(): boolean;
69
+ export type CourtlistenerOpinion = {
70
+ caseName: string | null;
71
+ court: string | null;
72
+ courtId: string | null;
73
+ dateFiled: string | null;
74
+ docketNumber: string | null;
75
+ natureOfSuit: string | null;
76
+ status: string | null;
77
+ judge: string | null;
78
+ citation: string | string[] | null;
79
+ absoluteUrl: string | null;
80
+ };
81
+ /**
82
+ * Flatten CourtListener's `citation` (which may be a string, an array of strings,
83
+ * or an object) to a safe `string | string[] | null` — NEVER fabricated. An array
84
+ * ⇒ the non-null strings (or null when none); a string ⇒ that string; an object ⇒
85
+ * its non-null string VALUES (or null when none); absent ⇒ null. `[object Object]`
86
+ * can never leak (we never `String()` an object).
87
+ */
88
+ export declare function flattenCitation(x: unknown): string | string[] | null;
89
+ /**
90
+ * Extract the opaque `cursor` value from a CourtListener `next` URL. RE-ASSERTS the
91
+ * `next` URL's host is courtlistener.com — an OFF-HOST `next` is REFUSED (drift),
92
+ * never parsed/followed (SSRF: a poisoned `next` could otherwise steer the next
93
+ * page off-host). Returns the decoded cursor string, or null when `next` is null /
94
+ * carries no cursor. A non-string / unparseable `next` ⇒ null (treated as "no more").
95
+ */
96
+ export declare function extractNextCursor(next: unknown, label: string): string | null;
97
+ export type CourtlistenerSearchOpinionsArgs = {
98
+ query?: string;
99
+ court?: string;
100
+ dateFiledAfter?: string;
101
+ dateFiledBefore?: string;
102
+ natureOfSuit?: string;
103
+ cursor?: string;
104
+ order?: string;
105
+ };
106
+ /**
107
+ * Search US federal court opinions via CourtListener (`/api/rest/v4/search/`,
108
+ * type=o) → curated opinion rows + honest `_meta`. KEYLESS (an optional
109
+ * COURTLISTENER_API_TOKEN only raises the rate limit, sent as the Authorization
110
+ * header only). ★PROVENANCE: this is CourtListener/Free Law Project (a non-profit),
111
+ * NOT a .gov API — PACER (the .gov source) is paywalled. totalAvailable is the
112
+ * API's REAL `count`; CURSOR pagination (nextCursor extracted from `next`).
113
+ */
114
+ export declare function searchOpinions(args: CourtlistenerSearchOpinionsArgs): Promise<MetaBundle>;
115
+ //# sourceMappingURL=courtlistener.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"courtlistener.d.ts","sourceRoot":"","sources":["../src/courtlistener.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAKH,OAAO,EAAY,KAAK,UAAU,EAAqB,MAAM,WAAW,CAAC;AAGzE,eAAO,MAAM,kBAAkB,0BAA0B,CAAC;AAgB1D,eAAO,MAAM,uBAAuB,QAAmC,CAAC;AAiBxE;;;;;GAKG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAIhE;AAED,6FAA6F;AAC7F,wBAAgB,yBAAyB,IAAI,OAAO,CAGnD;AAGD,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC;IACnC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAcpE;AA4BD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAqB7E;AAGD,MAAM,MAAM,+BAA+B,GAAG;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,+BAA+B,GACpC,OAAO,CAAC,UAAU,CAAC,CAiMrB"}