@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.
- package/README.ja.md +20 -12
- package/README.ko.md +20 -12
- package/README.md +62 -14
- package/dist/bea.d.ts +1 -1
- package/dist/bea.js +1 -1
- package/dist/cbp-border.d.ts +51 -0
- package/dist/cbp-border.d.ts.map +1 -0
- package/dist/cbp-border.js +123 -0
- package/dist/cbp-border.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/datagov-catalog.d.ts.map +1 -1
- package/dist/datagov-catalog.js +16 -2
- package/dist/datagov-catalog.js.map +1 -1
- package/dist/dol.d.ts +2 -2
- package/dist/dol.js +5 -5
- package/dist/dol.js.map +1 -1
- package/dist/ecfr.d.ts +2 -2
- package/dist/ecfr.d.ts.map +1 -1
- package/dist/ecfr.js +24 -10
- package/dist/ecfr.js.map +1 -1
- package/dist/edgar.d.ts.map +1 -1
- package/dist/edgar.js +26 -6
- package/dist/edgar.js.map +1 -1
- package/dist/epa-envirofacts.d.ts +97 -0
- package/dist/epa-envirofacts.d.ts.map +1 -0
- package/dist/epa-envirofacts.js +305 -0
- package/dist/epa-envirofacts.js.map +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +11 -0
- package/dist/errors.js.map +1 -1
- package/dist/far.d.ts.map +1 -1
- package/dist/far.js +3 -1
- package/dist/far.js.map +1 -1
- package/dist/federal-register.d.ts +2 -2
- package/dist/federal-register.d.ts.map +1 -1
- package/dist/federal-register.js +26 -10
- package/dist/federal-register.js.map +1 -1
- package/dist/fema.d.ts +36 -0
- package/dist/fema.d.ts.map +1 -1
- package/dist/fema.js +124 -0
- package/dist/fema.js.map +1 -1
- package/dist/fred.d.ts +1 -1
- package/dist/fred.js +1 -1
- package/dist/gov-domains.d.ts +66 -0
- package/dist/gov-domains.d.ts.map +1 -0
- package/dist/gov-domains.js +211 -0
- package/dist/gov-domains.js.map +1 -0
- package/dist/keys.d.ts +6 -5
- package/dist/keys.d.ts.map +1 -1
- package/dist/keys.js +25 -6
- package/dist/keys.js.map +1 -1
- 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/nist-controls.d.ts +48 -0
- package/dist/nist-controls.d.ts.map +1 -0
- package/dist/nist-controls.js +174 -0
- package/dist/nist-controls.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/nws-weather.d.ts +57 -0
- package/dist/nws-weather.d.ts.map +1 -0
- package/dist/nws-weather.js +131 -0
- package/dist/nws-weather.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-drugsfda.d.ts +72 -0
- package/dist/openfda-drugsfda.d.ts.map +1 -0
- package/dist/openfda-drugsfda.js +230 -0
- package/dist/openfda-drugsfda.js.map +1 -0
- package/dist/openfda.d.ts +133 -0
- package/dist/openfda.d.ts.map +1 -0
- package/dist/openfda.js +425 -0
- package/dist/openfda.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +996 -16
- package/dist/server.js.map +1 -1
- package/dist/treasury.d.ts +2 -0
- package/dist/treasury.d.ts.map +1 -1
- package/dist/treasury.js +7 -0
- package/dist/treasury.js.map +1 -1
- package/dist/usaspending.d.ts +32 -1
- package/dist/usaspending.d.ts.map +1 -1
- package/dist/usaspending.js +143 -16
- package/dist/usaspending.js.map +1 -1
- package/package.json +3 -2
- package/src/bea.ts +1 -1
- package/src/cbp-border.ts +177 -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/datagov-catalog.ts +18 -2
- package/src/dol.ts +5 -5
- package/src/ecfr.ts +27 -10
- package/src/edgar.ts +39 -7
- package/src/epa-envirofacts.ts +358 -0
- package/src/errors.ts +11 -0
- package/src/far.ts +3 -1
- package/src/federal-register.ts +29 -10
- package/src/fema.ts +139 -0
- package/src/fred.ts +1 -1
- package/src/gov-domains.ts +237 -0
- package/src/keys.ts +27 -6
- package/src/nhtsa.ts +352 -0
- package/src/nist-controls.ts +219 -0
- package/src/nonprofit.ts +460 -0
- package/src/nws-weather.ts +167 -0
- package/src/openfda-device.ts +356 -0
- package/src/openfda-drugsfda.ts +313 -0
- package/src/openfda.ts +518 -0
- package/src/server.ts +1127 -27
- package/src/treasury.ts +7 -0
- package/src/usaspending.ts +189 -17
package/dist/census-economic.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* its keyless tier — a request WITHOUT a key is 302-redirected to a "Missing
|
|
9
9
|
* Key" HTML page. So, honestly: with NO `CENSUS_API_KEY` this tool THROWS an
|
|
10
10
|
* `invalid_input` config error BEFORE any fetch (never a fake-empty, never a
|
|
11
|
-
* keyless-pretend). The other
|
|
11
|
+
* keyless-pretend). The other tools stay keyless — this key is scoped to
|
|
12
12
|
* this one source. (Contrast the OPTIONAL keys of datagov/bls/nvd, which lift a
|
|
13
13
|
* tier but are not required.)
|
|
14
14
|
*
|
|
@@ -77,16 +77,22 @@ const YEAR_RE = /^\d{4}$/; // rides in the PATH — strict 4-digit (no path inje
|
|
|
77
77
|
const NAICS_RE = /^\d{2,6}$/; // 2–6 digit NAICS-2017 sector/code
|
|
78
78
|
const STATE_FIPS_RE = /^\d{2}$/; // 2-digit state FIPS
|
|
79
79
|
const GEOGRAPHIES = new Set(["us", "state", "county"]);
|
|
80
|
-
//
|
|
81
|
-
// unavailable cell as a large NEGATIVE value (-999999999 / -888888888 /
|
|
82
|
-
// -666666666 …)
|
|
83
|
-
// value at/below this floor is a sentinel, NOT data.
|
|
80
|
+
// DEFENSIVE large-negative sentinel floor. Some Census products (ACS/SAIPE) encode a
|
|
81
|
+
// withheld/unavailable cell as a large NEGATIVE jam value (-999999999 / -888888888 /
|
|
82
|
+
// -666666666 …); establishment/employment/payroll counts are non-negative, so any
|
|
83
|
+
// value at/below this floor is a sentinel, NOT data → mapped to null.
|
|
84
|
+
// ★HONESTY CAVEAT: CBP itself does NOT primarily use these jam values — modern CBP
|
|
85
|
+
// uses NOISE INFUSION (EMP_N noise-range columns) + suppression FLAGS (EMP_N_F …),
|
|
86
|
+
// which this tool does NOT currently request or interpret (it surfaces values as
|
|
87
|
+
// reported). So this floor is a conservative cross-product guard, not CBP's confirmed
|
|
88
|
+
// mechanism; a keyed live verification of CBP's exact withheld-cell encoding is pending
|
|
89
|
+
// (no CENSUS_API_KEY was available at build time). See SUPPRESSED_NOTE.
|
|
84
90
|
const CENSUS_SENTINEL_FLOOR = -100000000;
|
|
85
91
|
const DEFAULT_YEAR = "2022"; // the latest confirmed CBP vintage (ADR-0047)
|
|
86
92
|
// ─── Honesty notes (ADR-0047 required set) ────────────────────────
|
|
87
93
|
const KEY_REQUIRED_NOTE = "This source REQUIRES a free CENSUS_API_KEY (the Census Data API has no keyless tier). The key is sent ONLY as the &key= query parameter to api.census.gov and is NEVER logged, echoed, or placed in this response.";
|
|
88
94
|
const PAYROLL_UNITS_NOTE = "annualPayrollUsd is ANNUAL payroll in US dollars, converted from the Census PAYANN field's $1,000 units (×1000). establishments and employees are integer counts (as-of the reference year).";
|
|
89
|
-
const SUPPRESSED_NOTE = "
|
|
95
|
+
const SUPPRESSED_NOTE = "Disclosure protection: any large-negative jam sentinel (e.g. -999999999) is mapped to null (withheld) — NEVER a negative number and NEVER 0; a genuine 0 is preserved as 0. NOTE: modern CBP applies NOISE INFUSION (perturbed values) plus suppression flag columns (e.g. EMP_N_F) rather than jam sentinels; this tool surfaces values as reported and does not currently interpret suppression flags, so a flagged/noise-infused cell is returned as its reported number — treat exact small counts as approximate.";
|
|
90
96
|
const NO_PAGINATION_NOTE = "CBP returns the COMPLETE set of geographies matching the filter (no server-side pagination); totalAvailable equals the number of rows returned. Narrow with naics / geography to reduce the row count.";
|
|
91
97
|
// ─── The key seam (REQUIRED; value NEVER leaked past the &key= param) ──
|
|
92
98
|
/** Read CENSUS_API_KEY from env; trim; return the value or undefined (unset/blank). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"census-economic.js","sourceRoot":"","sources":["../src/census-economic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AAEH,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAsC,MAAM,WAAW,CAAC;AAEzE,8EAA8E;AAC9E,iFAAiF;AACjF,WAAW;AACX,OAAO,EAAE,GAAG,EAAE,CAAC;AAEf,qEAAqE;AACrE,MAAM,gBAAgB,GAAG,gBAAgB,CAAC;AAC1C,MAAM,iBAAiB,GAAG,kBAAkB,CAAC,CAAC,gDAAgD;AAE9F,qEAAqE;AACrE,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,yDAAyD;AACpF,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,mCAAmC;AACjE,MAAM,aAAa,GAAG,SAAS,CAAC,CAAC,qBAAqB;AACtD,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;AAEvD,
|
|
1
|
+
{"version":3,"file":"census-economic.js","sourceRoot":"","sources":["../src/census-economic.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cms-facility.ts — CMS "facility directory" across FOUR provider-data datasets
|
|
3
|
+
* (`data.cms.gov`, the provider-data DKAN datastore-query API; ADR-0063). KEYLESS.
|
|
4
|
+
*
|
|
5
|
+
* WHAT IT ADDS: `cms_facility_directory` — a healthcare-facility directory / market
|
|
6
|
+
* lane that generalizes cms_hospital_compare (ADR-0062) BEYOND hospitals: a caller
|
|
7
|
+
* picks a `facilityType` (nursing_home | home_health | hospice | dialysis) and the
|
|
8
|
+
* tool routes to the RIGHT CMS provider-data dataset, returning each facility's
|
|
9
|
+
* name / address / city / state / zip / ownership. The facility-level complement to
|
|
10
|
+
* the utilization and hospital lanes — WHERE these Medicare-certified facilities are.
|
|
11
|
+
*
|
|
12
|
+
* ★THE facilityType → DATASET-ID CONSTANT MAP (the load-bearing SSRF guard): the
|
|
13
|
+
* USER value never enters the URL path. `facilityType` is a Zod ENUM; it indexes a
|
|
14
|
+
* MODULE-CONSTANT map to a VETTED dataset id (e.g. nursing_home → "4pq5-n9py") that
|
|
15
|
+
* is spliced into the path. An unknown facilityType is blocked by the enum
|
|
16
|
+
* (invalid_input) BEFORE any fetch — only one of four compile-time ids can ever
|
|
17
|
+
* reach the path.
|
|
18
|
+
*
|
|
19
|
+
* ★THE ONE-REQUEST COUNT PATTERN (P1 honesty, inherited from cms-hospital): the DKAN
|
|
20
|
+
* datastore-query response is `{ count, results, schema, query }` — `count` is the
|
|
21
|
+
* EXACT per-filter total in the SAME body as the rows. So totalAvailable = the
|
|
22
|
+
* response's top-level `count` (nursing_home ⇒ 14695), NEVER `results.length`.
|
|
23
|
+
*
|
|
24
|
+
* ★FIELD-NAME VARIANCE ACROSS DATASETS (the one genuinely-new complexity vs.
|
|
25
|
+
* cms-hospital) — live-verified 2026-07-15. The facility NAME, ADDRESS, and
|
|
26
|
+
* OWNERSHIP columns are NAMED DIFFERENTLY per dataset, so each is COALESCED over a
|
|
27
|
+
* fixed candidate order (null if none present — NEVER an empty string, NEVER
|
|
28
|
+
* fabricated):
|
|
29
|
+
* name : provider_name → facility_name → legal_business_name
|
|
30
|
+
* address : address → provider_address → address_line_1
|
|
31
|
+
* ownership : ownership_type → type_of_ownership → profit_or_nonprofit
|
|
32
|
+
* (city = citytown, state = state, zip = zip_code are uniform across all four.)
|
|
33
|
+
* Per-dataset verified columns:
|
|
34
|
+
* nursing_home 4pq5-n9py: provider_name / provider_address / ownership_type
|
|
35
|
+
* home_health 6jpm-sxkc: provider_name / address / type_of_ownership
|
|
36
|
+
* hospice yc9t-dgbk: facility_name / address_line_1 / ownership_type
|
|
37
|
+
* dialysis 23ew-n7w9: facility_name / address_line_1 / profit_or_nonprofit
|
|
38
|
+
* ★ADR-0063 said nursing_home's address is `address`; it is actually
|
|
39
|
+
* `provider_address` (probed live) — the coalescing candidate list covers it.
|
|
40
|
+
*
|
|
41
|
+
* ★THE facilityName FILTER COLUMN also varies: the `contains` filter targets the
|
|
42
|
+
* dataset's OWN primary-name column (provider_name for nursing_home/home_health,
|
|
43
|
+
* facility_name for hospice/dialysis) — stored per-type in the constant map.
|
|
44
|
+
*
|
|
45
|
+
* The module writes ZERO fetch/coercion/error/meta code — it REUSES `getJson`
|
|
46
|
+
* (redirect:"error") / `driftError` (datasource.ts), `str` (coerce.ts,
|
|
47
|
+
* null-never-empty-string), and `withMeta`·`buildMeta` (meta.ts, offset pagination
|
|
48
|
+
* + totalAvailable). It MIRRORS cms-hospital.ts's fixed-host SSRF idiom (a single
|
|
49
|
+
* host const + a post-construction hostname/protocol assertion + redirect:"error" +
|
|
50
|
+
* `conditions[i][…]` bracket keys and values carried via URLSearchParams) and its
|
|
51
|
+
* schema_drift catch-ladder (ToolErrorCarrier rethrow FIRST → SyntaxError→driftError
|
|
52
|
+
* → bare rethrow).
|
|
53
|
+
*
|
|
54
|
+
* GET https://data.cms.gov/provider-data/api/1/datastore/query/{datasetId}/0
|
|
55
|
+
* ?limit=&offset=&conditions[0][property]=state&conditions[0][value]=VA&conditions[0][operator]==
|
|
56
|
+
* → { count: 383, results: [ { provider_name, provider_address, … }, … ], schema, query }
|
|
57
|
+
*
|
|
58
|
+
* ★ SSRF: the host is a compile-time literal (`CMS_HOST`); the dataset id is chosen
|
|
59
|
+
* by a Zod-enum key from a MODULE-CONSTANT map (never the user string). Every USER
|
|
60
|
+
* filter VALUE rides as a URLSearchParams VALUE (`conditions[i][value]=…`) — the
|
|
61
|
+
* bracket key AND the value are encoded, so a value can never break out of the
|
|
62
|
+
* path or inject a parameter. state is `^[A-Za-z]{2}$`; facilityName is a bounded
|
|
63
|
+
* free-text charclass; size/offset are coerced to integers. A post-construction
|
|
64
|
+
* hostname/protocol assertion + `redirect:"error"` fail closed on any off-host 3xx.
|
|
65
|
+
*
|
|
66
|
+
* ★ HONESTY (ADR-0063 P1–P5, live-verified 2026-07-15 on data.cms.gov):
|
|
67
|
+
* [P1] totalAvailable = the response's top-level `count` (EXACT per-filter total),
|
|
68
|
+
* NOT the slice length. hasMore = offset+returned < count.
|
|
69
|
+
* [P2] results:[] ⇒ honest empty (returned:0). An invalid facilityType is blocked
|
|
70
|
+
* by the Zod enum (invalid_input). getJson maps a 4xx/5xx via
|
|
71
|
+
* errorFromResponse and THROWS (503 ⇒ upstream_unavailable, 400 ⇒
|
|
72
|
+
* invalid_input, 404 ⇒ not_found); a 200 non-JSON body OR a body missing
|
|
73
|
+
* `count`/`results` ⇒ schema_drift (NEVER a fabricated empty).
|
|
74
|
+
* [P3] name/address/ownership are COALESCED over the candidate order — null if none
|
|
75
|
+
* (NEVER an empty string, NEVER fabricated). String fields via str().
|
|
76
|
+
* [P4] results non-array OR count non-number ⇒ driftError.
|
|
77
|
+
*/
|
|
78
|
+
import { str } from "./coerce.js";
|
|
79
|
+
import { type MetaBundle } from "./meta.js";
|
|
80
|
+
export { str };
|
|
81
|
+
type FacilityType = "nursing_home" | "home_health" | "hospice" | "dialysis";
|
|
82
|
+
export type Facility = {
|
|
83
|
+
name: string | null;
|
|
84
|
+
address: string | null;
|
|
85
|
+
city: string | null;
|
|
86
|
+
state: string | null;
|
|
87
|
+
zip: string | null;
|
|
88
|
+
facilityType: FacilityType;
|
|
89
|
+
ownership: string | null;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Coalesce a row's value over a candidate column order → string | null.
|
|
93
|
+
* Returns the FIRST column whose str() is non-null (str nulls ""/whitespace/"null");
|
|
94
|
+
* null if NONE — NEVER an empty string, NEVER a fabricated value (P3).
|
|
95
|
+
*/
|
|
96
|
+
export declare function coalesceField(row: Record<string, unknown>, fields: string[]): string | null;
|
|
97
|
+
export type CmsFacilityDirectoryArgs = {
|
|
98
|
+
facilityType?: string;
|
|
99
|
+
state?: string;
|
|
100
|
+
facilityName?: string;
|
|
101
|
+
size?: number;
|
|
102
|
+
offset?: number;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Fetch CMS provider-data facility rows for a `facilityType` (+ optional state /
|
|
106
|
+
* facility-name fragment) → normalized facility rows + honest `_meta`. The
|
|
107
|
+
* facilityType (a Zod enum) indexes the FACILITY_DATASETS constant map to a vetted
|
|
108
|
+
* dataset id — the user value never enters the path. A SINGLE request: the response's
|
|
109
|
+
* top-level `count` is the EXACT per-filter total (P1 — never the slice length).
|
|
110
|
+
*/
|
|
111
|
+
export declare function facilityDirectory(args: CmsFacilityDirectoryArgs): Promise<MetaBundle>;
|
|
112
|
+
//# sourceMappingURL=cms-facility.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cms-facility.d.ts","sourceRoot":"","sources":["../src/cms-facility.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4EG;AAIH,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAY,KAAK,UAAU,EAAqB,MAAM,WAAW,CAAC;AAIzE,OAAO,EAAE,GAAG,EAAE,CAAC;AAkBf,KAAK,YAAY,GAAG,cAAc,GAAG,aAAa,GAAG,SAAS,GAAG,UAAU,CAAC;AA8D5E,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,YAAY,EAAE,YAAY,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,MAAM,EAAE,MAAM,EAAE,GACf,MAAM,GAAG,IAAI,CAMf;AA0CD,MAAM,MAAM,wBAAwB,GAAG;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,wBAAwB,GAC7B,OAAO,CAAC,UAAU,CAAC,CAkIrB"}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cms-facility.ts — CMS "facility directory" across FOUR provider-data datasets
|
|
3
|
+
* (`data.cms.gov`, the provider-data DKAN datastore-query API; ADR-0063). KEYLESS.
|
|
4
|
+
*
|
|
5
|
+
* WHAT IT ADDS: `cms_facility_directory` — a healthcare-facility directory / market
|
|
6
|
+
* lane that generalizes cms_hospital_compare (ADR-0062) BEYOND hospitals: a caller
|
|
7
|
+
* picks a `facilityType` (nursing_home | home_health | hospice | dialysis) and the
|
|
8
|
+
* tool routes to the RIGHT CMS provider-data dataset, returning each facility's
|
|
9
|
+
* name / address / city / state / zip / ownership. The facility-level complement to
|
|
10
|
+
* the utilization and hospital lanes — WHERE these Medicare-certified facilities are.
|
|
11
|
+
*
|
|
12
|
+
* ★THE facilityType → DATASET-ID CONSTANT MAP (the load-bearing SSRF guard): the
|
|
13
|
+
* USER value never enters the URL path. `facilityType` is a Zod ENUM; it indexes a
|
|
14
|
+
* MODULE-CONSTANT map to a VETTED dataset id (e.g. nursing_home → "4pq5-n9py") that
|
|
15
|
+
* is spliced into the path. An unknown facilityType is blocked by the enum
|
|
16
|
+
* (invalid_input) BEFORE any fetch — only one of four compile-time ids can ever
|
|
17
|
+
* reach the path.
|
|
18
|
+
*
|
|
19
|
+
* ★THE ONE-REQUEST COUNT PATTERN (P1 honesty, inherited from cms-hospital): the DKAN
|
|
20
|
+
* datastore-query response is `{ count, results, schema, query }` — `count` is the
|
|
21
|
+
* EXACT per-filter total in the SAME body as the rows. So totalAvailable = the
|
|
22
|
+
* response's top-level `count` (nursing_home ⇒ 14695), NEVER `results.length`.
|
|
23
|
+
*
|
|
24
|
+
* ★FIELD-NAME VARIANCE ACROSS DATASETS (the one genuinely-new complexity vs.
|
|
25
|
+
* cms-hospital) — live-verified 2026-07-15. The facility NAME, ADDRESS, and
|
|
26
|
+
* OWNERSHIP columns are NAMED DIFFERENTLY per dataset, so each is COALESCED over a
|
|
27
|
+
* fixed candidate order (null if none present — NEVER an empty string, NEVER
|
|
28
|
+
* fabricated):
|
|
29
|
+
* name : provider_name → facility_name → legal_business_name
|
|
30
|
+
* address : address → provider_address → address_line_1
|
|
31
|
+
* ownership : ownership_type → type_of_ownership → profit_or_nonprofit
|
|
32
|
+
* (city = citytown, state = state, zip = zip_code are uniform across all four.)
|
|
33
|
+
* Per-dataset verified columns:
|
|
34
|
+
* nursing_home 4pq5-n9py: provider_name / provider_address / ownership_type
|
|
35
|
+
* home_health 6jpm-sxkc: provider_name / address / type_of_ownership
|
|
36
|
+
* hospice yc9t-dgbk: facility_name / address_line_1 / ownership_type
|
|
37
|
+
* dialysis 23ew-n7w9: facility_name / address_line_1 / profit_or_nonprofit
|
|
38
|
+
* ★ADR-0063 said nursing_home's address is `address`; it is actually
|
|
39
|
+
* `provider_address` (probed live) — the coalescing candidate list covers it.
|
|
40
|
+
*
|
|
41
|
+
* ★THE facilityName FILTER COLUMN also varies: the `contains` filter targets the
|
|
42
|
+
* dataset's OWN primary-name column (provider_name for nursing_home/home_health,
|
|
43
|
+
* facility_name for hospice/dialysis) — stored per-type in the constant map.
|
|
44
|
+
*
|
|
45
|
+
* The module writes ZERO fetch/coercion/error/meta code — it REUSES `getJson`
|
|
46
|
+
* (redirect:"error") / `driftError` (datasource.ts), `str` (coerce.ts,
|
|
47
|
+
* null-never-empty-string), and `withMeta`·`buildMeta` (meta.ts, offset pagination
|
|
48
|
+
* + totalAvailable). It MIRRORS cms-hospital.ts's fixed-host SSRF idiom (a single
|
|
49
|
+
* host const + a post-construction hostname/protocol assertion + redirect:"error" +
|
|
50
|
+
* `conditions[i][…]` bracket keys and values carried via URLSearchParams) and its
|
|
51
|
+
* schema_drift catch-ladder (ToolErrorCarrier rethrow FIRST → SyntaxError→driftError
|
|
52
|
+
* → bare rethrow).
|
|
53
|
+
*
|
|
54
|
+
* GET https://data.cms.gov/provider-data/api/1/datastore/query/{datasetId}/0
|
|
55
|
+
* ?limit=&offset=&conditions[0][property]=state&conditions[0][value]=VA&conditions[0][operator]==
|
|
56
|
+
* → { count: 383, results: [ { provider_name, provider_address, … }, … ], schema, query }
|
|
57
|
+
*
|
|
58
|
+
* ★ SSRF: the host is a compile-time literal (`CMS_HOST`); the dataset id is chosen
|
|
59
|
+
* by a Zod-enum key from a MODULE-CONSTANT map (never the user string). Every USER
|
|
60
|
+
* filter VALUE rides as a URLSearchParams VALUE (`conditions[i][value]=…`) — the
|
|
61
|
+
* bracket key AND the value are encoded, so a value can never break out of the
|
|
62
|
+
* path or inject a parameter. state is `^[A-Za-z]{2}$`; facilityName is a bounded
|
|
63
|
+
* free-text charclass; size/offset are coerced to integers. A post-construction
|
|
64
|
+
* hostname/protocol assertion + `redirect:"error"` fail closed on any off-host 3xx.
|
|
65
|
+
*
|
|
66
|
+
* ★ HONESTY (ADR-0063 P1–P5, live-verified 2026-07-15 on data.cms.gov):
|
|
67
|
+
* [P1] totalAvailable = the response's top-level `count` (EXACT per-filter total),
|
|
68
|
+
* NOT the slice length. hasMore = offset+returned < count.
|
|
69
|
+
* [P2] results:[] ⇒ honest empty (returned:0). An invalid facilityType is blocked
|
|
70
|
+
* by the Zod enum (invalid_input). getJson maps a 4xx/5xx via
|
|
71
|
+
* errorFromResponse and THROWS (503 ⇒ upstream_unavailable, 400 ⇒
|
|
72
|
+
* invalid_input, 404 ⇒ not_found); a 200 non-JSON body OR a body missing
|
|
73
|
+
* `count`/`results` ⇒ schema_drift (NEVER a fabricated empty).
|
|
74
|
+
* [P3] name/address/ownership are COALESCED over the candidate order — null if none
|
|
75
|
+
* (NEVER an empty string, NEVER fabricated). String fields via str().
|
|
76
|
+
* [P4] results non-array OR count non-number ⇒ driftError.
|
|
77
|
+
*/
|
|
78
|
+
import { ToolErrorCarrier } from "./errors.js";
|
|
79
|
+
import { getJson, driftError } from "./datasource.js";
|
|
80
|
+
import { str } from "./coerce.js";
|
|
81
|
+
import { withMeta } from "./meta.js";
|
|
82
|
+
// Re-export the shared honesty coercion (single audited copy in ./coerce.js) so a
|
|
83
|
+
// regression fails together across sources. NO local str.
|
|
84
|
+
export { str };
|
|
85
|
+
// ─── SSRF core: the single fixed host + module-literal endpoint prefix ──
|
|
86
|
+
const CMS_HOST = "data.cms.gov";
|
|
87
|
+
// HOST-only label — surfaces in ToolError.upstreamEndpoint; keyless, so no token
|
|
88
|
+
// can ever appear here.
|
|
89
|
+
const CMS_LABEL = "cms-facility:data.cms.gov";
|
|
90
|
+
const CMS_PATH_PREFIX = "/provider-data/api/1/datastore/query";
|
|
91
|
+
const CMS_PATH_SUFFIX = "/0";
|
|
92
|
+
const FACILITY_DATASETS = {
|
|
93
|
+
nursing_home: {
|
|
94
|
+
id: "4pq5-n9py",
|
|
95
|
+
nameColumn: "provider_name",
|
|
96
|
+
label: 'CMS "Provider Information" (Nursing Home / Long-Term Care; ~14,695 Medicare/Medicaid-certified nursing homes)',
|
|
97
|
+
},
|
|
98
|
+
home_health: {
|
|
99
|
+
id: "6jpm-sxkc",
|
|
100
|
+
nameColumn: "provider_name",
|
|
101
|
+
label: 'CMS "Home Health Care Agencies" (~12,460 Medicare-certified home health agencies)',
|
|
102
|
+
},
|
|
103
|
+
hospice: {
|
|
104
|
+
id: "yc9t-dgbk",
|
|
105
|
+
nameColumn: "facility_name",
|
|
106
|
+
label: 'CMS "Hospice Provider Information" (~6,852 Medicare-certified hospices)',
|
|
107
|
+
},
|
|
108
|
+
dialysis: {
|
|
109
|
+
id: "23ew-n7w9",
|
|
110
|
+
nameColumn: "facility_name",
|
|
111
|
+
label: 'CMS "Dialysis Facility" (~7,490 Medicare-certified dialysis facilities)',
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
// The valid enum keys — a belt-and-suspenders re-guard behind the server Zod (a
|
|
115
|
+
// DIRECT handler call bypasses Zod). An unknown key ⇒ invalid_input, 0 fetch.
|
|
116
|
+
const FACILITY_TYPES = Object.keys(FACILITY_DATASETS);
|
|
117
|
+
// ─── Coalescing candidate orders (the field-name variance across datasets) ──
|
|
118
|
+
// Each output field probes these source columns IN ORDER; the first non-null (via
|
|
119
|
+
// str(), which nulls ""/whitespace/"null") wins; null if NONE present. NEVER an
|
|
120
|
+
// empty string, NEVER a fabricated value.
|
|
121
|
+
const NAME_FIELDS = ["provider_name", "facility_name", "legal_business_name"];
|
|
122
|
+
const ADDRESS_FIELDS = ["address", "provider_address", "address_line_1"];
|
|
123
|
+
const OWNERSHIP_FIELDS = ["ownership_type", "type_of_ownership", "profit_or_nonprofit"];
|
|
124
|
+
// ─── Validation charclasses (SSRF + "verify the input" honesty) ───
|
|
125
|
+
const STATE_RE = /^[A-Za-z]{2}$/; // 2-letter state/territory abbreviation
|
|
126
|
+
// facilityName rides as a URLSearchParams VALUE (encoded), so this bound is a sanity
|
|
127
|
+
// guard, not an SSRF necessity: letters/digits/space and common punctuation only
|
|
128
|
+
// (facility names carry apostrophes, &, /, -, .).
|
|
129
|
+
const FACILITY_NAME_RE = /^[A-Za-z0-9 &.,()/'-]{1,100}$/;
|
|
130
|
+
const SIZE_MIN = 1;
|
|
131
|
+
const SIZE_MAX = 100;
|
|
132
|
+
const SIZE_DEFAULT = 25;
|
|
133
|
+
// DKAN condition operators (live-verified). state = exact; name = substring.
|
|
134
|
+
const OP_EQUALS = "=";
|
|
135
|
+
const OP_CONTAINS = "contains";
|
|
136
|
+
// ─── Honesty notes (ADR-0063 required set) ────────────────────────
|
|
137
|
+
const COALESCE_NOTE = "Field names vary across the four CMS provider-data datasets — `name`, `address`, and `ownership` are COALESCED over per-dataset candidate columns (name: provider_name/facility_name/legal_business_name; address: address/provider_address/address_line_1; ownership: ownership_type/type_of_ownership/profit_or_nonprofit). A field absent in the chosen dataset is null (unknown), NEVER an empty string and NEVER fabricated.";
|
|
138
|
+
const FILTER_NOTE = "Filters are applied SERVER-SIDE via DKAN conditions (AND-combined): `state` is an EXACT match; `facilityName` is a case-insensitive SUBSTRING (contains) match against the dataset's primary-name column — so a facilityName fragment may match several facilities. totalAvailable is the upstream's EXACT count for this filter set, not the returned-row count.";
|
|
139
|
+
/**
|
|
140
|
+
* Coalesce a row's value over a candidate column order → string | null.
|
|
141
|
+
* Returns the FIRST column whose str() is non-null (str nulls ""/whitespace/"null");
|
|
142
|
+
* null if NONE — NEVER an empty string, NEVER a fabricated value (P3).
|
|
143
|
+
*/
|
|
144
|
+
export function coalesceField(row, fields) {
|
|
145
|
+
for (const f of fields) {
|
|
146
|
+
const v = str(row[f]);
|
|
147
|
+
if (v !== null)
|
|
148
|
+
return v;
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
/** Map ONE datastore-query row → the curated facility shape for `facilityType`. */
|
|
153
|
+
function mapFacility(row, facilityType) {
|
|
154
|
+
const r = (row ?? {});
|
|
155
|
+
return {
|
|
156
|
+
// [P3] name/address/ownership coalesced over candidate columns; null-never-empty.
|
|
157
|
+
name: coalesceField(r, NAME_FIELDS),
|
|
158
|
+
address: coalesceField(r, ADDRESS_FIELDS),
|
|
159
|
+
city: str(r.citytown),
|
|
160
|
+
state: str(r.state),
|
|
161
|
+
zip: str(r.zip_code),
|
|
162
|
+
// Echo the input facilityType (the caller's chosen lane).
|
|
163
|
+
facilityType,
|
|
164
|
+
ownership: coalesceField(r, OWNERSHIP_FIELDS),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
// ─── SSRF-guarded fetch (fixed host + hostname assertion + redirect:"error") ──
|
|
168
|
+
/**
|
|
169
|
+
* GET one data.cms.gov provider-data JSON resource at a MODULE-BUILT URL. The dataset
|
|
170
|
+
* id is a VETTED value from FACILITY_DATASETS (never the user string); the endpoint
|
|
171
|
+
* path is built from module literals; all user filter VALUES are already carried in
|
|
172
|
+
* the URLSearchParams `query`. Asserts the CONSTRUCTED URL's hostname === the fixed
|
|
173
|
+
* host over https, and sets `redirect:"error"` (an off-host 3xx must NOT be
|
|
174
|
+
* followed). Keyless — no headers.
|
|
175
|
+
*/
|
|
176
|
+
async function getCms(datasetId, query) {
|
|
177
|
+
const path = `${CMS_PATH_PREFIX}/${datasetId}${CMS_PATH_SUFFIX}`;
|
|
178
|
+
const url = `https://${CMS_HOST}${path}?${query.toString()}`;
|
|
179
|
+
const built = new URL(url);
|
|
180
|
+
if (built.hostname !== CMS_HOST || built.protocol !== "https:") {
|
|
181
|
+
throw new ToolErrorCarrier({
|
|
182
|
+
kind: "invalid_input",
|
|
183
|
+
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).`,
|
|
184
|
+
retryable: false,
|
|
185
|
+
upstreamEndpoint: CMS_LABEL,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
return getJson(built.toString(), { label: CMS_LABEL, redirect: "error" });
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Fetch CMS provider-data facility rows for a `facilityType` (+ optional state /
|
|
192
|
+
* facility-name fragment) → normalized facility rows + honest `_meta`. The
|
|
193
|
+
* facilityType (a Zod enum) indexes the FACILITY_DATASETS constant map to a vetted
|
|
194
|
+
* dataset id — the user value never enters the path. A SINGLE request: the response's
|
|
195
|
+
* top-level `count` is the EXACT per-filter total (P1 — never the slice length).
|
|
196
|
+
*/
|
|
197
|
+
export async function facilityDirectory(args) {
|
|
198
|
+
// ── [SSRF/input] resolve facilityType → the vetted dataset (belt-and-suspenders
|
|
199
|
+
// behind the server Zod enum; a DIRECT handler call bypasses Zod). An unknown/
|
|
200
|
+
// absent facilityType ⇒ invalid_input, 0 fetch. ──
|
|
201
|
+
const ft = args.facilityType;
|
|
202
|
+
if (ft === undefined || !Object.prototype.hasOwnProperty.call(FACILITY_DATASETS, ft)) {
|
|
203
|
+
throw new ToolErrorCarrier({
|
|
204
|
+
kind: "invalid_input",
|
|
205
|
+
retryable: false,
|
|
206
|
+
message: `Invalid or missing facilityType ${JSON.stringify(ft)} — expected one of: ${FACILITY_TYPES.join(", ")}.`,
|
|
207
|
+
upstreamEndpoint: CMS_LABEL,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
const facilityType = ft;
|
|
211
|
+
const dataset = FACILITY_DATASETS[facilityType];
|
|
212
|
+
// ── Validate + build the DKAN conditions (SSRF: charclass + URLSearchParams
|
|
213
|
+
// value). URLSearchParams encodes both the bracket key and the value. Each
|
|
214
|
+
// condition is an (property, value, operator) triple indexed by position. ──
|
|
215
|
+
const query = new URLSearchParams();
|
|
216
|
+
const filtersApplied = [`facilityType:${facilityType}`];
|
|
217
|
+
let ci = 0;
|
|
218
|
+
const pushCondition = (property, value, operator) => {
|
|
219
|
+
query.set(`conditions[${ci}][property]`, property);
|
|
220
|
+
query.set(`conditions[${ci}][value]`, value);
|
|
221
|
+
query.set(`conditions[${ci}][operator]`, operator);
|
|
222
|
+
ci += 1;
|
|
223
|
+
};
|
|
224
|
+
if (args.state !== undefined && args.state !== "") {
|
|
225
|
+
const state = args.state;
|
|
226
|
+
if (!STATE_RE.test(state)) {
|
|
227
|
+
throw new ToolErrorCarrier({
|
|
228
|
+
kind: "invalid_input",
|
|
229
|
+
retryable: false,
|
|
230
|
+
message: `Invalid state ${JSON.stringify(state)} — expected a 2-letter state/territory code (^[A-Za-z]{2}$), e.g. "VA".`,
|
|
231
|
+
upstreamEndpoint: CMS_LABEL,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
pushCondition("state", state.toUpperCase(), OP_EQUALS);
|
|
235
|
+
filtersApplied.push(`state:${state.toUpperCase()}`);
|
|
236
|
+
}
|
|
237
|
+
if (args.facilityName !== undefined && args.facilityName !== "") {
|
|
238
|
+
const name = args.facilityName;
|
|
239
|
+
if (!FACILITY_NAME_RE.test(name)) {
|
|
240
|
+
throw new ToolErrorCarrier({
|
|
241
|
+
kind: "invalid_input",
|
|
242
|
+
retryable: false,
|
|
243
|
+
message: `Invalid facilityName ${JSON.stringify(name)} — allowed: letters, digits, space, & . , ( ) / ' - (≤100 chars). It is a case-insensitive substring match.`,
|
|
244
|
+
upstreamEndpoint: CMS_LABEL,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
// The name column varies per dataset (provider_name vs facility_name).
|
|
248
|
+
pushCondition(dataset.nameColumn, name, OP_CONTAINS);
|
|
249
|
+
filtersApplied.push(`facilityName~${name}`);
|
|
250
|
+
}
|
|
251
|
+
// ── Coerce size / offset to bounded integers (belt-and-suspenders behind the
|
|
252
|
+
// server Zod; a DIRECT handler call bypasses Zod). ──
|
|
253
|
+
let size = SIZE_DEFAULT;
|
|
254
|
+
if (typeof args.size === "number" && Number.isFinite(args.size)) {
|
|
255
|
+
size = Math.trunc(args.size);
|
|
256
|
+
if (size < SIZE_MIN)
|
|
257
|
+
size = SIZE_MIN;
|
|
258
|
+
if (size > SIZE_MAX)
|
|
259
|
+
size = SIZE_MAX;
|
|
260
|
+
}
|
|
261
|
+
let offset = 0;
|
|
262
|
+
if (typeof args.offset === "number" &&
|
|
263
|
+
Number.isFinite(args.offset) &&
|
|
264
|
+
args.offset > 0) {
|
|
265
|
+
offset = Math.trunc(args.offset);
|
|
266
|
+
}
|
|
267
|
+
query.set("limit", String(size));
|
|
268
|
+
query.set("offset", String(offset));
|
|
269
|
+
// ── The single request. Catch-ladder (cms-hospital shape): preserve the
|
|
270
|
+
// 4xx/5xx/timeout ToolErrorCarrier taxonomy FIRST; reclassify a 200 non-JSON
|
|
271
|
+
// SyntaxError to schema_drift SECOND; bare-rethrow LAST. ──
|
|
272
|
+
let body;
|
|
273
|
+
try {
|
|
274
|
+
body = await getCms(dataset.id, query);
|
|
275
|
+
}
|
|
276
|
+
catch (e) {
|
|
277
|
+
if (e instanceof ToolErrorCarrier)
|
|
278
|
+
throw e;
|
|
279
|
+
if (e instanceof SyntaxError)
|
|
280
|
+
throw driftError(CMS_LABEL, "CMS provider-data API returned a non-JSON body at HTTP 200 — schema drift (never read as an empty result).");
|
|
281
|
+
throw e;
|
|
282
|
+
}
|
|
283
|
+
// [P4] the body MUST be an object with a numeric `count` and an array `results`
|
|
284
|
+
// (a non-array results OR a non-number count is drift, never a fabricated empty).
|
|
285
|
+
const b = (body ?? {});
|
|
286
|
+
if (typeof b.count !== "number" ||
|
|
287
|
+
!Number.isFinite(b.count) ||
|
|
288
|
+
!Array.isArray(b.results)) {
|
|
289
|
+
throw driftError(CMS_LABEL, "CMS provider-data shape drift — the response must carry a numeric `count` and an array `results` of facility rows.");
|
|
290
|
+
}
|
|
291
|
+
const totalAvailable = b.count;
|
|
292
|
+
const facilities = b.results.map((row) => mapFacility(row, facilityType));
|
|
293
|
+
const returned = facilities.length;
|
|
294
|
+
// ── [P1] pagination. hasMore = offset+returned < total (the EXACT count). ──
|
|
295
|
+
const hasMore = offset + returned < totalAvailable;
|
|
296
|
+
const nextOffset = hasMore ? offset + returned : null;
|
|
297
|
+
const DATASET_NOTE = `Source dataset: ${dataset.label} — data.cms.gov provider-data datastore "${dataset.id}". A CMS-published snapshot, not a live/real-time feed. Update the dataset id if CMS re-publishes it.`;
|
|
298
|
+
const meta = {
|
|
299
|
+
source: `${CMS_HOST} CMS provider-data facility directory — ${facilityType} (keyless)`,
|
|
300
|
+
keylessMode: true,
|
|
301
|
+
returned,
|
|
302
|
+
totalAvailable,
|
|
303
|
+
filtersApplied,
|
|
304
|
+
filtersDropped: [],
|
|
305
|
+
fieldsUnavailable: [],
|
|
306
|
+
pagination: { offset, limit: size, hasMore, nextOffset },
|
|
307
|
+
notes: [DATASET_NOTE, COALESCE_NOTE, FILTER_NOTE],
|
|
308
|
+
};
|
|
309
|
+
return withMeta({ facilities }, meta);
|
|
310
|
+
}
|
|
311
|
+
//# sourceMappingURL=cms-facility.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cms-facility.js","sourceRoot":"","sources":["../src/cms-facility.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4EG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAsC,MAAM,WAAW,CAAC;AAEzE,kFAAkF;AAClF,0DAA0D;AAC1D,OAAO,EAAE,GAAG,EAAE,CAAC;AAEf,2EAA2E;AAC3E,MAAM,QAAQ,GAAG,cAAc,CAAC;AAChC,iFAAiF;AACjF,wBAAwB;AACxB,MAAM,SAAS,GAAG,2BAA2B,CAAC;AAC9C,MAAM,eAAe,GAAG,sCAAsC,CAAC;AAC/D,MAAM,eAAe,GAAG,IAAI,CAAC;AAY7B,MAAM,iBAAiB,GAAqC;IAC1D,YAAY,EAAE;QACZ,EAAE,EAAE,WAAW;QACf,UAAU,EAAE,eAAe;QAC3B,KAAK,EACH,+GAA+G;KAClH;IACD,WAAW,EAAE;QACX,EAAE,EAAE,WAAW;QACf,UAAU,EAAE,eAAe;QAC3B,KAAK,EACH,mFAAmF;KACtF;IACD,OAAO,EAAE;QACP,EAAE,EAAE,WAAW;QACf,UAAU,EAAE,eAAe;QAC3B,KAAK,EAAE,yEAAyE;KACjF;IACD,QAAQ,EAAE;QACR,EAAE,EAAE,WAAW;QACf,UAAU,EAAE,eAAe;QAC3B,KAAK,EACH,yEAAyE;KAC5E;CACF,CAAC;AAEF,gFAAgF;AAChF,8EAA8E;AAC9E,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAmB,CAAC;AAExE,+EAA+E;AAC/E,kFAAkF;AAClF,gFAAgF;AAChF,0CAA0C;AAC1C,MAAM,WAAW,GAAG,CAAC,eAAe,EAAE,eAAe,EAAE,qBAAqB,CAAC,CAAC;AAC9E,MAAM,cAAc,GAAG,CAAC,SAAS,EAAE,kBAAkB,EAAE,gBAAgB,CAAC,CAAC;AACzE,MAAM,gBAAgB,GAAG,CAAC,gBAAgB,EAAE,mBAAmB,EAAE,qBAAqB,CAAC,CAAC;AAExF,qEAAqE;AACrE,MAAM,QAAQ,GAAG,eAAe,CAAC,CAAC,wCAAwC;AAC1E,qFAAqF;AACrF,iFAAiF;AACjF,kDAAkD;AAClD,MAAM,gBAAgB,GAAG,+BAA+B,CAAC;AAEzD,MAAM,QAAQ,GAAG,CAAC,CAAC;AACnB,MAAM,QAAQ,GAAG,GAAG,CAAC;AACrB,MAAM,YAAY,GAAG,EAAE,CAAC;AAExB,6EAA6E;AAC7E,MAAM,SAAS,GAAG,GAAG,CAAC;AACtB,MAAM,WAAW,GAAG,UAAU,CAAC;AAE/B,qEAAqE;AACrE,MAAM,aAAa,GACjB,maAAma,CAAC;AACta,MAAM,WAAW,GACf,mWAAmW,CAAC;AAatW;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAC3B,GAA4B,EAC5B,MAAgB;IAEhB,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,MAAM,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,CAAC,KAAK,IAAI;YAAE,OAAO,CAAC,CAAC;IAC3B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,mFAAmF;AACnF,SAAS,WAAW,CAAC,GAAY,EAAE,YAA0B;IAC3D,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,CAA4B,CAAC;IACjD,OAAO;QACL,kFAAkF;QAClF,IAAI,EAAE,aAAa,CAAC,CAAC,EAAE,WAAW,CAAC;QACnC,OAAO,EAAE,aAAa,CAAC,CAAC,EAAE,cAAc,CAAC;QACzC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC;QACrB,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC;QACnB,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC;QACpB,0DAA0D;QAC1D,YAAY;QACZ,SAAS,EAAE,aAAa,CAAC,CAAC,EAAE,gBAAgB,CAAC;KAC9C,CAAC;AACJ,CAAC;AAED,iFAAiF;AACjF;;;;;;;GAOG;AACH,KAAK,UAAU,MAAM,CAAC,SAAiB,EAAE,KAAsB;IAC7D,MAAM,IAAI,GAAG,GAAG,eAAe,IAAI,SAAS,GAAG,eAAe,EAAE,CAAC;IACjE,MAAM,GAAG,GAAG,WAAW,QAAQ,GAAG,IAAI,IAAI,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC;IAC7D,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAC3B,IAAI,KAAK,CAAC,QAAQ,KAAK,QAAQ,IAAI,KAAK,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC/D,MAAM,IAAI,gBAAgB,CAAC;YACzB,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE,0CAA0C,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,KAAK,CAAC,QAAQ,mCAAmC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,gDAAgD;YAC/M,SAAS,EAAE,KAAK;YAChB,gBAAgB,EAAE,SAAS;SAC5B,CAAC,CAAC;IACL,CAAC;IACD,OAAO,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;AAC5E,CAAC;AAUD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAA8B;IAE9B,iFAAiF;IACjF,kFAAkF;IAClF,sDAAsD;IACtD,MAAM,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC;IAC7B,IAAI,EAAE,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,CAAC,EAAE,CAAC;QACrF,MAAM,IAAI,gBAAgB,CAAC;YACzB,IAAI,EAAE,eAAe;YACrB,SAAS,EAAE,KAAK;YAChB,OAAO,EAAE,mCAAmC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,uBAAuB,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;YACjH,gBAAgB,EAAE,SAAS;SAC5B,CAAC,CAAC;IACL,CAAC;IACD,MAAM,YAAY,GAAG,EAAkB,CAAC;IACxC,MAAM,OAAO,GAAG,iBAAiB,CAAC,YAAY,CAAC,CAAC;IAEhD,6EAA6E;IAC7E,8EAA8E;IAC9E,gFAAgF;IAChF,MAAM,KAAK,GAAG,IAAI,eAAe,EAAE,CAAC;IACpC,MAAM,cAAc,GAAa,CAAC,gBAAgB,YAAY,EAAE,CAAC,CAAC;IAClE,IAAI,EAAE,GAAG,CAAC,CAAC;IACX,MAAM,aAAa,GAAG,CAAC,QAAgB,EAAE,KAAa,EAAE,QAAgB,EAAE,EAAE;QAC1E,KAAK,CAAC,GAAG,CAAC,cAAc,EAAE,aAAa,EAAE,QAAQ,CAAC,CAAC;QACnD,KAAK,CAAC,GAAG,CAAC,cAAc,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC;QAC7C,KAAK,CAAC,GAAG,CAAC,cAAc,EAAE,aAAa,EAAE,QAAQ,CAAC,CAAC;QACnD,EAAE,IAAI,CAAC,CAAC;IACV,CAAC,CAAC;IAEF,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,KAAK,EAAE,EAAE,CAAC;QAClD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,gBAAgB,CAAC;gBACzB,IAAI,EAAE,eAAe;gBACrB,SAAS,EAAE,KAAK;gBAChB,OAAO,EAAE,iBAAiB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,yEAAyE;gBACxH,gBAAgB,EAAE,SAAS;aAC5B,CAAC,CAAC;QACL,CAAC;QACD,aAAa,CAAC,OAAO,EAAE,KAAK,CAAC,WAAW,EAAE,EAAE,SAAS,CAAC,CAAC;QACvD,cAAc,CAAC,IAAI,CAAC,SAAS,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IACtD,CAAC;IAED,IAAI,IAAI,CAAC,YAAY,KAAK,SAAS,IAAI,IAAI,CAAC,YAAY,KAAK,EAAE,EAAE,CAAC;QAChE,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC;QAC/B,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,gBAAgB,CAAC;gBACzB,IAAI,EAAE,eAAe;gBACrB,SAAS,EAAE,KAAK;gBAChB,OAAO,EAAE,wBAAwB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,6GAA6G;gBAClK,gBAAgB,EAAE,SAAS;aAC5B,CAAC,CAAC;QACL,CAAC;QACD,uEAAuE;QACvE,aAAa,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;QACrD,cAAc,CAAC,IAAI,CAAC,gBAAgB,IAAI,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,8EAA8E;IAC9E,yDAAyD;IACzD,IAAI,IAAI,GAAG,YAAY,CAAC;IACxB,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAChE,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,IAAI,IAAI,GAAG,QAAQ;YAAE,IAAI,GAAG,QAAQ,CAAC;QACrC,IAAI,IAAI,GAAG,QAAQ;YAAE,IAAI,GAAG,QAAQ,CAAC;IACvC,CAAC;IACD,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IACE,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ;QAC/B,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC;QAC5B,IAAI,CAAC,MAAM,GAAG,CAAC,EACf,CAAC;QACD,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IACjC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAEpC,yEAAyE;IACzE,gFAAgF;IAChF,+DAA+D;IAC/D,IAAI,IAAa,CAAC;IAClB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,YAAY,gBAAgB;YAAE,MAAM,CAAC,CAAC;QAC3C,IAAI,CAAC,YAAY,WAAW;YAC1B,MAAM,UAAU,CACd,SAAS,EACT,4GAA4G,CAC7G,CAAC;QACJ,MAAM,CAAC,CAAC;IACV,CAAC;IAED,gFAAgF;IAChF,kFAAkF;IAClF,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAA2C,CAAC;IACjE,IACE,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ;QAC3B,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC;QACzB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,EACzB,CAAC;QACD,MAAM,UAAU,CACd,SAAS,EACT,oHAAoH,CACrH,CAAC;IACJ,CAAC;IAED,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC;IAC/B,MAAM,UAAU,GAAI,CAAC,CAAC,OAAqB,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC;IACzF,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC;IAEnC,8EAA8E;IAC9E,MAAM,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,cAAc,CAAC;IACnD,MAAM,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;IAEtD,MAAM,YAAY,GAAG,mBAAmB,OAAO,CAAC,KAAK,4CAA4C,OAAO,CAAC,EAAE,uGAAuG,CAAC;IAEnN,MAAM,IAAI,GAA0B;QAClC,MAAM,EAAE,GAAG,QAAQ,2CAA2C,YAAY,YAAY;QACtF,WAAW,EAAE,IAAI;QACjB,QAAQ;QACR,cAAc;QACd,cAAc;QACd,cAAc,EAAE,EAAE;QAClB,iBAAiB,EAAE,EAAE;QACrB,UAAU,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE;QACxD,KAAK,EAAE,CAAC,YAAY,EAAE,aAAa,EAAE,WAAW,CAAC;KAClD,CAAC;IAEF,OAAO,QAAQ,CAAC,EAAE,UAAU,EAAE,EAAE,IAAI,CAAC,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1,105 @@
|
|
|
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 { str, num } from "./coerce.js";
|
|
68
|
+
import { type MetaBundle } from "./meta.js";
|
|
69
|
+
export { num, str };
|
|
70
|
+
export type Hospital = {
|
|
71
|
+
facilityId: string | null;
|
|
72
|
+
facilityName: string | null;
|
|
73
|
+
address: string | null;
|
|
74
|
+
city: string | null;
|
|
75
|
+
state: string | null;
|
|
76
|
+
zip: string | null;
|
|
77
|
+
county: string | null;
|
|
78
|
+
phone: string | null;
|
|
79
|
+
hospitalType: string | null;
|
|
80
|
+
ownership: string | null;
|
|
81
|
+
emergencyServices: boolean | null;
|
|
82
|
+
overallRating: number | null;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Normalize the CMS `emergency_services` "Yes"/"No" flag → boolean | null.
|
|
86
|
+
* "Yes"⇒true, "No"⇒false (case-insensitive); anything else (absent/""/unrecognized)
|
|
87
|
+
* ⇒ null — NEVER a fabricated false (a data-absence-as-false masquerade).
|
|
88
|
+
*/
|
|
89
|
+
export declare function emergencyBool(x: unknown): boolean | null;
|
|
90
|
+
export type CmsHospitalCompareArgs = {
|
|
91
|
+
state?: string;
|
|
92
|
+
facilityName?: string;
|
|
93
|
+
hospitalType?: string;
|
|
94
|
+
size?: number;
|
|
95
|
+
offset?: number;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Fetch CMS Hospital Compare "Hospital General Information" rows for a state and/or
|
|
99
|
+
* facility-name fragment (+ optional hospitalType) → normalized hospital rows +
|
|
100
|
+
* honest `_meta`. REQUIRES state OR facilityName (an all-empty query is refused). A
|
|
101
|
+
* SINGLE request: the response's top-level `count` is the EXACT per-filter total
|
|
102
|
+
* (P1 — never the slice length).
|
|
103
|
+
*/
|
|
104
|
+
export declare function hospitalCompare(args: CmsHospitalCompareArgs): Promise<MetaBundle>;
|
|
105
|
+
//# sourceMappingURL=cms-hospital.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cms-hospital.d.ts","sourceRoot":"","sources":["../src/cms-hospital.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;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;AA0CpB,MAAM,MAAM,QAAQ,GAAG;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,iBAAiB,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,CAOxD;AA8CD,MAAM,MAAM,sBAAsB,GAAG;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,UAAU,CAAC,CAyIrB"}
|