@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.
- package/README.ja.md +22 -9
- package/README.ko.md +22 -9
- package/README.md +70 -12
- package/dist/bea.d.ts +105 -0
- package/dist/bea.d.ts.map +1 -0
- package/dist/bea.js +303 -0
- package/dist/bea.js.map +1 -0
- package/dist/census-economic.d.ts +1 -1
- package/dist/census-economic.d.ts.map +1 -1
- package/dist/census-economic.js +12 -6
- package/dist/census-economic.js.map +1 -1
- package/dist/cms-facility.d.ts +112 -0
- package/dist/cms-facility.d.ts.map +1 -0
- package/dist/cms-facility.js +311 -0
- package/dist/cms-facility.js.map +1 -0
- package/dist/cms-hospital.d.ts +105 -0
- package/dist/cms-hospital.d.ts.map +1 -0
- package/dist/cms-hospital.js +290 -0
- package/dist/cms-hospital.js.map +1 -0
- package/dist/cms-supplier.d.ts +133 -0
- package/dist/cms-supplier.d.ts.map +1 -0
- package/dist/cms-supplier.js +414 -0
- package/dist/cms-supplier.js.map +1 -0
- package/dist/cms-utilization.d.ts +113 -0
- package/dist/cms-utilization.d.ts.map +1 -0
- package/dist/cms-utilization.js +328 -0
- package/dist/cms-utilization.js.map +1 -0
- package/dist/courtlistener.d.ts +115 -0
- package/dist/courtlistener.d.ts.map +1 -0
- package/dist/courtlistener.js +398 -0
- package/dist/courtlistener.js.map +1 -0
- package/dist/cpsc.d.ts +81 -0
- package/dist/cpsc.d.ts.map +1 -0
- package/dist/cpsc.js +283 -0
- package/dist/cpsc.js.map +1 -0
- package/dist/dol.d.ts +118 -0
- package/dist/dol.d.ts.map +1 -0
- package/dist/dol.js +421 -0
- package/dist/dol.js.map +1 -0
- package/dist/epa-envirofacts.d.ts +97 -0
- package/dist/epa-envirofacts.d.ts.map +1 -0
- package/dist/epa-envirofacts.js +292 -0
- package/dist/epa-envirofacts.js.map +1 -0
- package/dist/fred.d.ts +1 -1
- package/dist/fred.js +1 -1
- package/dist/keys.d.ts +11 -8
- package/dist/keys.d.ts.map +1 -1
- package/dist/keys.js +55 -8
- package/dist/keys.js.map +1 -1
- package/dist/lda.d.ts +105 -0
- package/dist/lda.d.ts.map +1 -0
- package/dist/lda.js +317 -0
- package/dist/lda.js.map +1 -0
- package/dist/nhtsa.d.ts +91 -0
- package/dist/nhtsa.d.ts.map +1 -0
- package/dist/nhtsa.js +263 -0
- package/dist/nhtsa.js.map +1 -0
- package/dist/nonprofit.d.ts +116 -0
- package/dist/nonprofit.d.ts.map +1 -0
- package/dist/nonprofit.js +342 -0
- package/dist/nonprofit.js.map +1 -0
- package/dist/openfda-device.d.ts +85 -0
- package/dist/openfda-device.d.ts.map +1 -0
- package/dist/openfda-device.js +277 -0
- package/dist/openfda-device.js.map +1 -0
- package/dist/openfda.d.ts +133 -0
- package/dist/openfda.d.ts.map +1 -0
- package/dist/openfda.js +402 -0
- package/dist/openfda.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +872 -6
- package/dist/server.js.map +1 -1
- package/package.json +2 -1
- package/src/bea.ts +372 -0
- package/src/census-economic.ts +12 -6
- package/src/cms-facility.ts +379 -0
- package/src/cms-hospital.ts +344 -0
- package/src/cms-supplier.ts +527 -0
- package/src/cms-utilization.ts +389 -0
- package/src/courtlistener.ts +465 -0
- package/src/cpsc.ts +333 -0
- package/src/dol.ts +515 -0
- package/src/epa-envirofacts.ts +342 -0
- package/src/fred.ts +1 -1
- package/src/keys.ts +60 -8
- package/src/lda.ts +385 -0
- package/src/nhtsa.ts +352 -0
- package/src/nonprofit.ts +460 -0
- package/src/openfda-device.ts +356 -0
- package/src/openfda.ts +495 -0
- package/src/server.ts +995 -6
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cms-hospital.ts — CMS Hospital Compare "Hospital General Information"
|
|
3
|
+
* (`data.cms.gov`, the provider-data DKAN datastore-query API; ADR-0062). KEYLESS.
|
|
4
|
+
*
|
|
5
|
+
* WHAT IT ADDS: `cms_hospital_compare` — a healthcare-facility directory / market
|
|
6
|
+
* lane: for a US state (and/or a facility-name fragment) list Medicare-certified
|
|
7
|
+
* hospitals with their location, type, ownership, emergency-services flag, and CMS
|
|
8
|
+
* star rating. The facility-level complement to the utilization lane
|
|
9
|
+
* (cms_medicare_provider_services, who BILLS) — this is WHERE the hospitals ARE and
|
|
10
|
+
* HOW CMS rates them.
|
|
11
|
+
*
|
|
12
|
+
* ★THE ONE-REQUEST COUNT PATTERN (the load-bearing P1 honesty — SIMPLER than
|
|
13
|
+
* cms-utilization's two-request stats-count): the DKAN datastore-query response
|
|
14
|
+
* is `{ count, results, schema, query }` — `count` is the EXACT per-filter total
|
|
15
|
+
* in the SAME body as the rows. So totalAvailable = the response's top-level
|
|
16
|
+
* `count` (VA ⇒ 96), NEVER `results.length`. A single request; no separate count
|
|
17
|
+
* sub-query is needed (or possible — the endpoint reports the total inline).
|
|
18
|
+
*
|
|
19
|
+
* ★THE FILTER-REQUIRED INPUT GUARD: the dataset is 5432 hospitals. An all-empty
|
|
20
|
+
* query (no state, no facilityName) is REFUSED with invalid_input (0 fetch) —
|
|
21
|
+
* hospitalType alone is NOT enough to scope; a caller MUST pin state OR
|
|
22
|
+
* facilityName.
|
|
23
|
+
*
|
|
24
|
+
* ★THE DKAN CONDITIONS FILTER (live-verified 2026-07-15): filters ride as
|
|
25
|
+
* `conditions[i][property]` / `conditions[i][value]` / `conditions[i][operator]`
|
|
26
|
+
* query triples, AND-combined server-side. `state` uses the exact operator `=`
|
|
27
|
+
* (VA ⇒ 96); `facilityName` + `hospitalType` use the `contains` operator (a
|
|
28
|
+
* case-insensitive substring match, live-verified — "CHILDREN" ⇒ 95). ALL
|
|
29
|
+
* filtering is server-side; NOTHING is silently dropped or client-faked.
|
|
30
|
+
*
|
|
31
|
+
* The module writes ZERO fetch/coercion/error/meta code — it REUSES `getJson`
|
|
32
|
+
* (redirect:"error") / `driftError` (datasource.ts), `str`/`num` (coerce.ts,
|
|
33
|
+
* null-never-empty-string / null-never-0), and `withMeta`·`buildMeta` (meta.ts,
|
|
34
|
+
* offset pagination + totalAvailable). It MIRRORS cms-utilization.ts's fixed-host
|
|
35
|
+
* SSRF idiom (a single host const + a post-construction hostname/protocol assertion
|
|
36
|
+
* + redirect:"error" + `conditions[i][…]` bracket keys and values carried via
|
|
37
|
+
* URLSearchParams) and its schema_drift catch-ladder (ToolErrorCarrier rethrow
|
|
38
|
+
* FIRST so a 5xx keeps its taxonomy → SyntaxError→driftError → bare rethrow).
|
|
39
|
+
*
|
|
40
|
+
* GET https://data.cms.gov/provider-data/api/1/datastore/query/{datasetId}/0
|
|
41
|
+
* ?limit=&offset=&conditions[0][property]=state&conditions[0][value]=VA&conditions[0][operator]==
|
|
42
|
+
* → { count: 96, results: [ { facility_id, facility_name, … }, … ], schema, query }
|
|
43
|
+
*
|
|
44
|
+
* ★ SSRF: the host is a compile-time literal (`CMS_HOST`); the dataset id + the
|
|
45
|
+
* endpoint path are MODULE literals. Every USER filter VALUE rides as a
|
|
46
|
+
* URLSearchParams VALUE (`conditions[i][value]=…`) — URLSearchParams encodes the
|
|
47
|
+
* bracket key AND the value, so a value can never break out into the path or
|
|
48
|
+
* inject a parameter. state is `^[A-Za-z]{2}$`; facilityName/hospitalType are a
|
|
49
|
+
* bounded free-text charclass; size/offset are coerced to integers. A
|
|
50
|
+
* post-construction hostname/protocol assertion + `redirect:"error"` fail closed
|
|
51
|
+
* on any off-host 3xx.
|
|
52
|
+
*
|
|
53
|
+
* ★ HONESTY (ADR-0062 P1–P5, live-verified 2026-07-15 on data.cms.gov):
|
|
54
|
+
* [input] require state OR facilityName — an all-empty query is REFUSED (0 fetch).
|
|
55
|
+
* [P1] totalAvailable = the response's top-level `count` (EXACT — VA = 96), NOT
|
|
56
|
+
* the slice length. hasMore = offset+returned < count.
|
|
57
|
+
* [P2] results:[] ⇒ honest empty (returned:0). getJson maps a 4xx/5xx via
|
|
58
|
+
* errorFromResponse and THROWS (503 ⇒ upstream_unavailable, 400 ⇒
|
|
59
|
+
* invalid_input, 404 ⇒ not_found); a 200 non-JSON/non-array body OR a body
|
|
60
|
+
* missing `count`/`results` ⇒ schema_drift (NEVER a fabricated empty).
|
|
61
|
+
* [P3] hospital_overall_rating "1"–"5" via num(); "Not Available"/""/non-numeric
|
|
62
|
+
* ⇒ null (NEVER 0 — a data-absence-as-zero masquerade is the forbidden
|
|
63
|
+
* class). emergency_services "Yes"⇒true / "No"⇒false / else null. String
|
|
64
|
+
* fields via str() (null-never-empty-string).
|
|
65
|
+
* [P4] results non-array OR count non-number ⇒ driftError.
|
|
66
|
+
*/
|
|
67
|
+
import { ToolErrorCarrier } from "./errors.js";
|
|
68
|
+
import { getJson, driftError } from "./datasource.js";
|
|
69
|
+
import { str, num } from "./coerce.js";
|
|
70
|
+
import { withMeta } from "./meta.js";
|
|
71
|
+
// Re-export the shared honesty coercions (single audited copy in ./coerce.js) so a
|
|
72
|
+
// regression fails together across sources. NO local num/str.
|
|
73
|
+
export { num, str };
|
|
74
|
+
// ─── SSRF core: the single fixed host + module-literal path pieces ──
|
|
75
|
+
const CMS_HOST = "data.cms.gov";
|
|
76
|
+
// HOST-only label — surfaces in ToolError.upstreamEndpoint; keyless, so no token
|
|
77
|
+
// can ever appear here.
|
|
78
|
+
const CMS_LABEL = "cms-hospital:data.cms.gov";
|
|
79
|
+
// ★THE DATASET ID — CMS Hospital Compare "Hospital General Information" (5432
|
|
80
|
+
// Medicare-certified hospitals at build time). ★UPDATE IF CMS RE-IDs IT: the
|
|
81
|
+
// provider-data datastore keys this dataset by a short slug; CMS has historically
|
|
82
|
+
// kept it stable, but a re-publish could change it. The active dataset is surfaced
|
|
83
|
+
// to the caller in a _meta note (DATASET_NOTE) so a consumer never mistakes the
|
|
84
|
+
// vintage.
|
|
85
|
+
const CMS_HOSPITAL_DATASET_ID = "xubh-q36u";
|
|
86
|
+
const CMS_HOSPITAL_PATH = `/provider-data/api/1/datastore/query/${CMS_HOSPITAL_DATASET_ID}/0`;
|
|
87
|
+
// ─── Validation charclasses (SSRF + "verify the input" honesty) ───
|
|
88
|
+
const STATE_RE = /^[A-Za-z]{2}$/; // 2-letter state/territory abbreviation
|
|
89
|
+
// facilityName / hospitalType ride as URLSearchParams VALUES (encoded), so these
|
|
90
|
+
// bounds are a sanity guard, not an SSRF necessity: letters/digits/space and common
|
|
91
|
+
// punctuation only (facility names carry apostrophes, &, /, -, .).
|
|
92
|
+
const FACILITY_NAME_RE = /^[A-Za-z0-9 &.,()/'-]{1,100}$/;
|
|
93
|
+
const HOSPITAL_TYPE_RE = /^[A-Za-z0-9 &.,()/'-]{1,100}$/;
|
|
94
|
+
const SIZE_MIN = 1;
|
|
95
|
+
const SIZE_MAX = 100;
|
|
96
|
+
const SIZE_DEFAULT = 25;
|
|
97
|
+
// DKAN condition operators (live-verified). state = exact; name/type = substring.
|
|
98
|
+
const OP_EQUALS = "=";
|
|
99
|
+
const OP_CONTAINS = "contains";
|
|
100
|
+
// ─── Honesty notes (ADR-0062 required set) ────────────────────────
|
|
101
|
+
const DATASET_NOTE = `Source dataset: CMS Hospital Compare "Hospital General Information" (data.cms.gov provider-data datastore "${CMS_HOSPITAL_DATASET_ID}", ~5,432 Medicare-certified hospitals) — a CMS-published snapshot, not a live/real-time feed. Update the dataset id if CMS re-publishes it.`;
|
|
102
|
+
const FILTER_NOTE = "Filters are applied SERVER-SIDE via DKAN conditions (AND-combined): `state` is an EXACT match; `facilityName` and `hospitalType` are case-insensitive SUBSTRING (contains) matches — so a facilityName fragment may match several hospitals. totalAvailable is the upstream's EXACT count for this filter set, not the returned-row count.";
|
|
103
|
+
const RATING_NOTE = "hospital_overall_rating is CMS's 1–5 star summary rating; \"Not Available\" (and any non-numeric) maps to overallRating:null (unknown), NEVER 0. This is a summary rating, NOT a clinical-quality or fitness determination.";
|
|
104
|
+
/**
|
|
105
|
+
* Normalize the CMS `emergency_services` "Yes"/"No" flag → boolean | null.
|
|
106
|
+
* "Yes"⇒true, "No"⇒false (case-insensitive); anything else (absent/""/unrecognized)
|
|
107
|
+
* ⇒ null — NEVER a fabricated false (a data-absence-as-false masquerade).
|
|
108
|
+
*/
|
|
109
|
+
export function emergencyBool(x) {
|
|
110
|
+
const s = str(x);
|
|
111
|
+
if (s === null)
|
|
112
|
+
return null;
|
|
113
|
+
const low = s.toLowerCase();
|
|
114
|
+
if (low === "yes")
|
|
115
|
+
return true;
|
|
116
|
+
if (low === "no")
|
|
117
|
+
return false;
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
/** Map ONE datastore-query row → the curated hospital shape. */
|
|
121
|
+
function mapHospital(row) {
|
|
122
|
+
const r = (row ?? {});
|
|
123
|
+
return {
|
|
124
|
+
facilityId: str(r.facility_id),
|
|
125
|
+
facilityName: str(r.facility_name),
|
|
126
|
+
address: str(r.address),
|
|
127
|
+
city: str(r.citytown),
|
|
128
|
+
state: str(r.state),
|
|
129
|
+
zip: str(r.zip_code),
|
|
130
|
+
county: str(r.countyparish),
|
|
131
|
+
phone: str(r.telephone_number),
|
|
132
|
+
hospitalType: str(r.hospital_type),
|
|
133
|
+
ownership: str(r.hospital_ownership),
|
|
134
|
+
emergencyServices: emergencyBool(r.emergency_services),
|
|
135
|
+
// [P3] "1"–"5" → number; "Not Available"/""/non-numeric → null (NEVER 0).
|
|
136
|
+
// num() returns null for a non-numeric string (Number("Not Available")=NaN) and
|
|
137
|
+
// for "" — exactly the P3 contract.
|
|
138
|
+
overallRating: num(r.hospital_overall_rating),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// ─── SSRF-guarded fetch (fixed host + hostname assertion + redirect:"error") ──
|
|
142
|
+
/**
|
|
143
|
+
* GET one data.cms.gov provider-data JSON resource at a MODULE-BUILT URL (the
|
|
144
|
+
* dataset id + the endpoint path are literals; all user filter VALUES are already
|
|
145
|
+
* carried in the URLSearchParams `query`). Asserts the CONSTRUCTED URL's hostname
|
|
146
|
+
* === the fixed host over https, and sets `redirect:"error"` (an off-host 3xx must
|
|
147
|
+
* NOT be followed). Keyless — no headers.
|
|
148
|
+
*/
|
|
149
|
+
async function getCms(query) {
|
|
150
|
+
const url = `https://${CMS_HOST}${CMS_HOSPITAL_PATH}?${query.toString()}`;
|
|
151
|
+
const built = new URL(url);
|
|
152
|
+
if (built.hostname !== CMS_HOST || built.protocol !== "https:") {
|
|
153
|
+
throw new ToolErrorCarrier({
|
|
154
|
+
kind: "invalid_input",
|
|
155
|
+
message: `Constructed CMS provider-data 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).`,
|
|
156
|
+
retryable: false,
|
|
157
|
+
upstreamEndpoint: CMS_LABEL,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return getJson(built.toString(), { label: CMS_LABEL, redirect: "error" });
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Fetch CMS Hospital Compare "Hospital General Information" rows for a state and/or
|
|
164
|
+
* facility-name fragment (+ optional hospitalType) → normalized hospital rows +
|
|
165
|
+
* honest `_meta`. REQUIRES state OR facilityName (an all-empty query is refused). A
|
|
166
|
+
* SINGLE request: the response's top-level `count` is the EXACT per-filter total
|
|
167
|
+
* (P1 — never the slice length).
|
|
168
|
+
*/
|
|
169
|
+
export async function hospitalCompare(args) {
|
|
170
|
+
// ── [input guard] require state OR facilityName (never scan the whole dataset).
|
|
171
|
+
// hospitalType alone is NOT sufficient to scope. ──
|
|
172
|
+
const hasState = args.state !== undefined && args.state !== "";
|
|
173
|
+
const hasFacilityName = args.facilityName !== undefined && args.facilityName !== "";
|
|
174
|
+
if (!hasState && !hasFacilityName) {
|
|
175
|
+
throw new ToolErrorCarrier({
|
|
176
|
+
kind: "invalid_input",
|
|
177
|
+
retryable: false,
|
|
178
|
+
message: "cms_hospital_compare requires at least `state` (2-letter) OR `facilityName` — an all-empty query would scan the entire ~5,432-hospital table and is refused. hospitalType alone is not enough; add state or facilityName and retry.",
|
|
179
|
+
upstreamEndpoint: CMS_LABEL,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
// ── Validate + build the DKAN conditions (SSRF: charclass + URLSearchParams
|
|
183
|
+
// value). URLSearchParams encodes both the bracket key and the value. Each
|
|
184
|
+
// condition is an (property, value, operator) triple indexed by position. ──
|
|
185
|
+
const query = new URLSearchParams();
|
|
186
|
+
const filtersApplied = [];
|
|
187
|
+
let ci = 0;
|
|
188
|
+
const pushCondition = (property, value, operator) => {
|
|
189
|
+
query.set(`conditions[${ci}][property]`, property);
|
|
190
|
+
query.set(`conditions[${ci}][value]`, value);
|
|
191
|
+
query.set(`conditions[${ci}][operator]`, operator);
|
|
192
|
+
ci += 1;
|
|
193
|
+
};
|
|
194
|
+
if (hasState) {
|
|
195
|
+
const state = args.state;
|
|
196
|
+
if (!STATE_RE.test(state)) {
|
|
197
|
+
throw new ToolErrorCarrier({
|
|
198
|
+
kind: "invalid_input",
|
|
199
|
+
retryable: false,
|
|
200
|
+
message: `Invalid state ${JSON.stringify(state)} — expected a 2-letter state/territory code (^[A-Za-z]{2}$), e.g. "VA".`,
|
|
201
|
+
upstreamEndpoint: CMS_LABEL,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
pushCondition("state", state.toUpperCase(), OP_EQUALS);
|
|
205
|
+
filtersApplied.push(`state:${state.toUpperCase()}`);
|
|
206
|
+
}
|
|
207
|
+
if (hasFacilityName) {
|
|
208
|
+
const name = args.facilityName;
|
|
209
|
+
if (!FACILITY_NAME_RE.test(name)) {
|
|
210
|
+
throw new ToolErrorCarrier({
|
|
211
|
+
kind: "invalid_input",
|
|
212
|
+
retryable: false,
|
|
213
|
+
message: `Invalid facilityName ${JSON.stringify(name)} — allowed: letters, digits, space, & . , ( ) / ' - (≤100 chars). It is a case-insensitive substring match.`,
|
|
214
|
+
upstreamEndpoint: CMS_LABEL,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
pushCondition("facility_name", name, OP_CONTAINS);
|
|
218
|
+
filtersApplied.push(`facilityName~${name}`);
|
|
219
|
+
}
|
|
220
|
+
if (args.hospitalType !== undefined && args.hospitalType !== "") {
|
|
221
|
+
const ht = args.hospitalType;
|
|
222
|
+
if (!HOSPITAL_TYPE_RE.test(ht)) {
|
|
223
|
+
throw new ToolErrorCarrier({
|
|
224
|
+
kind: "invalid_input",
|
|
225
|
+
retryable: false,
|
|
226
|
+
message: `Invalid hospitalType ${JSON.stringify(ht)} — allowed: letters, digits, space, & . , ( ) / ' - (≤100 chars). It is a case-insensitive substring match, e.g. "Acute".`,
|
|
227
|
+
upstreamEndpoint: CMS_LABEL,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
pushCondition("hospital_type", ht, OP_CONTAINS);
|
|
231
|
+
filtersApplied.push(`hospitalType~${ht}`);
|
|
232
|
+
}
|
|
233
|
+
// ── Coerce size / offset to bounded integers (belt-and-suspenders behind the
|
|
234
|
+
// server Zod; a DIRECT handler call bypasses Zod). ──
|
|
235
|
+
let size = SIZE_DEFAULT;
|
|
236
|
+
if (typeof args.size === "number" && Number.isFinite(args.size)) {
|
|
237
|
+
size = Math.trunc(args.size);
|
|
238
|
+
if (size < SIZE_MIN)
|
|
239
|
+
size = SIZE_MIN;
|
|
240
|
+
if (size > SIZE_MAX)
|
|
241
|
+
size = SIZE_MAX;
|
|
242
|
+
}
|
|
243
|
+
let offset = 0;
|
|
244
|
+
if (typeof args.offset === "number" &&
|
|
245
|
+
Number.isFinite(args.offset) &&
|
|
246
|
+
args.offset > 0) {
|
|
247
|
+
offset = Math.trunc(args.offset);
|
|
248
|
+
}
|
|
249
|
+
query.set("limit", String(size));
|
|
250
|
+
query.set("offset", String(offset));
|
|
251
|
+
// ── The single request. Catch-ladder (cms-utilization shape): preserve the
|
|
252
|
+
// 4xx/5xx/timeout ToolErrorCarrier taxonomy FIRST; reclassify a 200 non-JSON
|
|
253
|
+
// SyntaxError to schema_drift SECOND; bare-rethrow LAST. ──
|
|
254
|
+
let body;
|
|
255
|
+
try {
|
|
256
|
+
body = await getCms(query);
|
|
257
|
+
}
|
|
258
|
+
catch (e) {
|
|
259
|
+
if (e instanceof ToolErrorCarrier)
|
|
260
|
+
throw e;
|
|
261
|
+
if (e instanceof SyntaxError)
|
|
262
|
+
throw driftError(CMS_LABEL, "CMS provider-data API returned a non-JSON body at HTTP 200 — schema drift (never read as an empty result).");
|
|
263
|
+
throw e;
|
|
264
|
+
}
|
|
265
|
+
// [P4] the body MUST be an object with a numeric `count` and an array `results`
|
|
266
|
+
// (a non-array results OR a non-number count is drift, never a fabricated empty).
|
|
267
|
+
const b = (body ?? {});
|
|
268
|
+
if (typeof b.count !== "number" || !Number.isFinite(b.count) || !Array.isArray(b.results)) {
|
|
269
|
+
throw driftError(CMS_LABEL, "CMS provider-data shape drift — the response must carry a numeric `count` and an array `results` of hospital rows.");
|
|
270
|
+
}
|
|
271
|
+
const totalAvailable = b.count;
|
|
272
|
+
const hospitals = b.results.map(mapHospital);
|
|
273
|
+
const returned = hospitals.length;
|
|
274
|
+
// ── [P1] pagination. hasMore = offset+returned < total (the EXACT count). ──
|
|
275
|
+
const hasMore = offset + returned < totalAvailable;
|
|
276
|
+
const nextOffset = hasMore ? offset + returned : null;
|
|
277
|
+
const meta = {
|
|
278
|
+
source: `${CMS_HOST} CMS Hospital Compare — Hospital General Information (keyless)`,
|
|
279
|
+
keylessMode: true,
|
|
280
|
+
returned,
|
|
281
|
+
totalAvailable,
|
|
282
|
+
filtersApplied,
|
|
283
|
+
filtersDropped: [],
|
|
284
|
+
fieldsUnavailable: [],
|
|
285
|
+
pagination: { offset, limit: size, hasMore, nextOffset },
|
|
286
|
+
notes: [DATASET_NOTE, FILTER_NOTE, RATING_NOTE],
|
|
287
|
+
};
|
|
288
|
+
return withMeta({ hospitals }, meta);
|
|
289
|
+
}
|
|
290
|
+
//# sourceMappingURL=cms-hospital.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cms-hospital.js","sourceRoot":"","sources":["../src/cms-hospital.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cms-supplier.ts — two CMS supplier/vetting lanes on `data.cms.gov` (the SAME
|
|
3
|
+
* data-API v1 dataset endpoint + the SAME two-request stats-count pattern as
|
|
4
|
+
* cms-utilization.ts / ADR-0061; ADR-0064). KEYLESS.
|
|
5
|
+
*
|
|
6
|
+
* WHAT IT ADDS
|
|
7
|
+
* 1. `cms_dmepos_suppliers` — CMS "Medicare Durable Medical Equipment,
|
|
8
|
+
* Devices & Supplies by Supplier": for a given supplier (NPI) or state, the
|
|
9
|
+
* DMEPOS supplier's identity + aggregate Medicare figures (HCPCS codes,
|
|
10
|
+
* beneficiaries, claims, services, submitted / Medicare-allowed / -paid
|
|
11
|
+
* amounts). A supplier-market / competitor-utilization lane on the SUPPLY
|
|
12
|
+
* side (who bills Medicare for equipment).
|
|
13
|
+
* 2. `cms_revoked_providers` — CMS "Revoked Medicare Providers & Suppliers":
|
|
14
|
+
* the legally-published debarment / revocation list (7,059 rows), with the
|
|
15
|
+
* revoked provider's identity, provider type, revocation reason, effective
|
|
16
|
+
* date, and re-enrollment-bar expiration. A vetting / due-diligence lane in
|
|
17
|
+
* the SAME class as the OFAC / SAM-exclusions lists already shipped —
|
|
18
|
+
* surfacing the names IS the point (this is a public exclusion list).
|
|
19
|
+
*
|
|
20
|
+
* ★THE TWO-REQUEST PATTERN (the load-bearing P1 honesty — MIRRORS cms-utilization):
|
|
21
|
+
* the data-API's `/data` slice is a bare JSON array that reports NO total. So the
|
|
22
|
+
* EXACT total for a filter comes from a SEPARATE count sub-query — the identical
|
|
23
|
+
* `filter[...]` on the `/data-viewer/stats` endpoint returns
|
|
24
|
+
* `{ "data": { "found_rows": N, "total_rows": M } }`. Each tool runs the stats
|
|
25
|
+
* count FIRST (best-effort) then the data slice: totalAvailable = found_rows (P1,
|
|
26
|
+
* the per-filter EXACT total), NEVER the returned rows' length. If the stats
|
|
27
|
+
* sub-query fails or is absent, totalAvailable falls to null + a disclosing note
|
|
28
|
+
* (never a length-faked total) and the data slice still returns.
|
|
29
|
+
*
|
|
30
|
+
* ★THE FILTER-REQUIRED INPUT GUARD (dmepos only): the supplier table is large; an
|
|
31
|
+
* all-empty query (no npi, no state) is REFUSED with invalid_input (0 fetch) — a
|
|
32
|
+
* caller MUST pin npi OR state. The revocation list is only ~7K rows, so it is
|
|
33
|
+
* safe to page unfiltered (all its filters are optional).
|
|
34
|
+
*
|
|
35
|
+
* The module writes ZERO fetch/coercion/error/meta code — it REUSES `getJson`
|
|
36
|
+
* (redirect:"error") / `driftError` (datasource.ts), `str`/`num` (coerce.ts,
|
|
37
|
+
* null-never-empty-string / null-never-0), and `withMeta`·`buildMeta` (meta.ts).
|
|
38
|
+
* It MIRRORS cms-utilization.ts's fixed-host SSRF idiom (a single host const + a
|
|
39
|
+
* post-construction hostname/protocol assertion + redirect:"error") and its
|
|
40
|
+
* count-first two-request pattern + schema_drift catch-ladder.
|
|
41
|
+
*
|
|
42
|
+
* ★ SSRF: the host is a compile-time literal (`CMS_HOST`); the dataset UUIDs + 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}$`; lastName is a bounded
|
|
47
|
+
* free-text charclass; size/offset are coerced to integers. A post-construction
|
|
48
|
+
* hostname/protocol assertion + `redirect:"error"` fail closed on any off-host 3xx.
|
|
49
|
+
*
|
|
50
|
+
* ★ HONESTY (ADR-0064 P1–P5, live-verified 2026-07-15 on data.cms.gov):
|
|
51
|
+
* [P1] totalAvailable = the stats sub-query's found_rows (EXACT), NOT the slice
|
|
52
|
+
* length. hasMore = offset+returned < total. Stats fails/absent ⇒
|
|
53
|
+
* totalAvailable:null + a disclosing note (never length-faked).
|
|
54
|
+
* [P2] empty array ⇒ honest empty (returned:0). dmepos all-empty input ⇒
|
|
55
|
+
* invalid_input (0 fetch). getJson maps a 4xx (⇒ invalid_input / not_found)
|
|
56
|
+
* / 5xx (⇒ upstream_unavailable) and THROWS; a 200 non-array/non-JSON body ⇒
|
|
57
|
+
* schema_drift (NEVER a fabricated empty).
|
|
58
|
+
* [P3] aggregates via num() (numeric strings → numbers; a real 0 stays 0; absent
|
|
59
|
+
* ⇒ null, never 0-faked); NPI / codes / reasons / dates as strings
|
|
60
|
+
* (null-never-empty-string); a coalesced name ⇒ null if none.
|
|
61
|
+
* [P4] a data body that is not an array ⇒ driftError; a stats body missing
|
|
62
|
+
* found_rows ⇒ totalAvailable:null (handled, not a crash).
|
|
63
|
+
*/
|
|
64
|
+
import { str, num } from "./coerce.js";
|
|
65
|
+
import { type MetaBundle } from "./meta.js";
|
|
66
|
+
export { num, str };
|
|
67
|
+
/**
|
|
68
|
+
* Join the CMS Last_Name_Org + First_Name into one display name. An ORGANIZATION
|
|
69
|
+
* supplier (entity code "O") carries the org name in Last_Name_Org with an empty
|
|
70
|
+
* First_Name ⇒ just the org name. An INDIVIDUAL carries both ⇒ "Last, First".
|
|
71
|
+
* Either absent ⇒ the present one; both absent ⇒ null (never a fabricated "").
|
|
72
|
+
*/
|
|
73
|
+
export declare function joinSupplierName(lastOrg: unknown, first: unknown): string | null;
|
|
74
|
+
export type DmeposSupplier = {
|
|
75
|
+
npi: string | null;
|
|
76
|
+
supplierName: string | null;
|
|
77
|
+
credentials: string | null;
|
|
78
|
+
entityType: string | null;
|
|
79
|
+
city: string | null;
|
|
80
|
+
state: string | null;
|
|
81
|
+
zip: string | null;
|
|
82
|
+
totalHcpcsCodes: number | null;
|
|
83
|
+
totalBeneficiaries: number | null;
|
|
84
|
+
totalClaims: number | null;
|
|
85
|
+
totalServices: number | null;
|
|
86
|
+
submittedCharges: number | null;
|
|
87
|
+
medicareAllowed: number | null;
|
|
88
|
+
medicarePayment: number | null;
|
|
89
|
+
};
|
|
90
|
+
export type CmsDmeposSuppliersArgs = {
|
|
91
|
+
npi?: string;
|
|
92
|
+
state?: string;
|
|
93
|
+
size?: number;
|
|
94
|
+
offset?: number;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Fetch DMEPOS supplier rows for an NPI / state → normalized supplier rows +
|
|
98
|
+
* honest `_meta`. REQUIRES npi OR state (an all-empty query is refused). Runs a
|
|
99
|
+
* stats count sub-query FIRST for the EXACT total (P1), then the data slice; a count
|
|
100
|
+
* failure degrades to totalAvailable:null + a note (never a length-faked total).
|
|
101
|
+
*/
|
|
102
|
+
export declare function dmeposSuppliers(args: CmsDmeposSuppliersArgs): Promise<MetaBundle>;
|
|
103
|
+
/**
|
|
104
|
+
* Coalesce the revoked-provider display name: an ORGANIZATION carries ORG_NAME ⇒
|
|
105
|
+
* use it. An INDIVIDUAL carries FIRST_NAME + LAST_NAME ⇒ "First Last" (either
|
|
106
|
+
* present alone ⇒ that one). None present ⇒ null (never a fabricated "").
|
|
107
|
+
*/
|
|
108
|
+
export declare function coalesceRevokedName(org: unknown, first: unknown, last: unknown): string | null;
|
|
109
|
+
export type RevokedProvider = {
|
|
110
|
+
enrollmentId: string | null;
|
|
111
|
+
npi: string | null;
|
|
112
|
+
name: string | null;
|
|
113
|
+
state: string | null;
|
|
114
|
+
providerType: string | null;
|
|
115
|
+
revocationReason: string | null;
|
|
116
|
+
revocationEffectiveDate: string | null;
|
|
117
|
+
reenrollmentBarExpiration: string | null;
|
|
118
|
+
};
|
|
119
|
+
export type CmsRevokedProvidersArgs = {
|
|
120
|
+
npi?: string;
|
|
121
|
+
state?: string;
|
|
122
|
+
lastName?: string;
|
|
123
|
+
size?: number;
|
|
124
|
+
offset?: number;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Fetch CMS revocation-list rows (all filters optional — the ~7K-row list is safe
|
|
128
|
+
* to page unfiltered) → normalized revocation rows + honest `_meta`. Runs a stats
|
|
129
|
+
* count sub-query FIRST for the EXACT total (P1), then the data slice; a count
|
|
130
|
+
* failure degrades to totalAvailable:null + a note (never a length-faked total).
|
|
131
|
+
*/
|
|
132
|
+
export declare function revokedProviders(args: CmsRevokedProvidersArgs): Promise<MetaBundle>;
|
|
133
|
+
//# sourceMappingURL=cms-supplier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cms-supplier.d.ts","sourceRoot":"","sources":["../src/cms-supplier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AAIH,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAY,KAAK,UAAU,EAAqB,MAAM,WAAW,CAAC;AAIzE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AA8JpB;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAKhF;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC,CAAC;AA0BF,MAAM,MAAM,sBAAsB,GAAG;IACnC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,UAAU,CAAC,CAiFrB;AAMD;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,OAAO,GACZ,MAAM,GAAG,IAAI,CAOf;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,yBAAyB,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1C,CAAC;AAiBF,MAAM,MAAM,uBAAuB,GAAG;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,uBAAuB,GAC5B,OAAO,CAAC,UAAU,CAAC,CAiFrB"}
|