@cliwant/mcp-sam-gov 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/README.ja.md +20 -12
  2. package/README.ko.md +20 -12
  3. package/README.md +62 -14
  4. package/dist/bea.d.ts +1 -1
  5. package/dist/bea.js +1 -1
  6. package/dist/cbp-border.d.ts +51 -0
  7. package/dist/cbp-border.d.ts.map +1 -0
  8. package/dist/cbp-border.js +123 -0
  9. package/dist/cbp-border.js.map +1 -0
  10. package/dist/census-economic.d.ts +1 -1
  11. package/dist/census-economic.d.ts.map +1 -1
  12. package/dist/census-economic.js +12 -6
  13. package/dist/census-economic.js.map +1 -1
  14. package/dist/cms-facility.d.ts +112 -0
  15. package/dist/cms-facility.d.ts.map +1 -0
  16. package/dist/cms-facility.js +311 -0
  17. package/dist/cms-facility.js.map +1 -0
  18. package/dist/cms-hospital.d.ts +105 -0
  19. package/dist/cms-hospital.d.ts.map +1 -0
  20. package/dist/cms-hospital.js +290 -0
  21. package/dist/cms-hospital.js.map +1 -0
  22. package/dist/cms-supplier.d.ts +133 -0
  23. package/dist/cms-supplier.d.ts.map +1 -0
  24. package/dist/cms-supplier.js +414 -0
  25. package/dist/cms-supplier.js.map +1 -0
  26. package/dist/cms-utilization.d.ts +113 -0
  27. package/dist/cms-utilization.d.ts.map +1 -0
  28. package/dist/cms-utilization.js +328 -0
  29. package/dist/cms-utilization.js.map +1 -0
  30. package/dist/courtlistener.d.ts +115 -0
  31. package/dist/courtlistener.d.ts.map +1 -0
  32. package/dist/courtlistener.js +398 -0
  33. package/dist/courtlistener.js.map +1 -0
  34. package/dist/cpsc.d.ts +81 -0
  35. package/dist/cpsc.d.ts.map +1 -0
  36. package/dist/cpsc.js +283 -0
  37. package/dist/cpsc.js.map +1 -0
  38. package/dist/datagov-catalog.d.ts.map +1 -1
  39. package/dist/datagov-catalog.js +16 -2
  40. package/dist/datagov-catalog.js.map +1 -1
  41. package/dist/dol.d.ts +2 -2
  42. package/dist/dol.js +5 -5
  43. package/dist/dol.js.map +1 -1
  44. package/dist/ecfr.d.ts +2 -2
  45. package/dist/ecfr.d.ts.map +1 -1
  46. package/dist/ecfr.js +24 -10
  47. package/dist/ecfr.js.map +1 -1
  48. package/dist/edgar.d.ts.map +1 -1
  49. package/dist/edgar.js +26 -6
  50. package/dist/edgar.js.map +1 -1
  51. package/dist/epa-envirofacts.d.ts +97 -0
  52. package/dist/epa-envirofacts.d.ts.map +1 -0
  53. package/dist/epa-envirofacts.js +305 -0
  54. package/dist/epa-envirofacts.js.map +1 -0
  55. package/dist/errors.d.ts.map +1 -1
  56. package/dist/errors.js +11 -0
  57. package/dist/errors.js.map +1 -1
  58. package/dist/far.d.ts.map +1 -1
  59. package/dist/far.js +3 -1
  60. package/dist/far.js.map +1 -1
  61. package/dist/federal-register.d.ts +2 -2
  62. package/dist/federal-register.d.ts.map +1 -1
  63. package/dist/federal-register.js +26 -10
  64. package/dist/federal-register.js.map +1 -1
  65. package/dist/fema.d.ts +36 -0
  66. package/dist/fema.d.ts.map +1 -1
  67. package/dist/fema.js +124 -0
  68. package/dist/fema.js.map +1 -1
  69. package/dist/fred.d.ts +1 -1
  70. package/dist/fred.js +1 -1
  71. package/dist/gov-domains.d.ts +66 -0
  72. package/dist/gov-domains.d.ts.map +1 -0
  73. package/dist/gov-domains.js +211 -0
  74. package/dist/gov-domains.js.map +1 -0
  75. package/dist/keys.d.ts +6 -5
  76. package/dist/keys.d.ts.map +1 -1
  77. package/dist/keys.js +25 -6
  78. package/dist/keys.js.map +1 -1
  79. package/dist/nhtsa.d.ts +91 -0
  80. package/dist/nhtsa.d.ts.map +1 -0
  81. package/dist/nhtsa.js +263 -0
  82. package/dist/nhtsa.js.map +1 -0
  83. package/dist/nist-controls.d.ts +48 -0
  84. package/dist/nist-controls.d.ts.map +1 -0
  85. package/dist/nist-controls.js +174 -0
  86. package/dist/nist-controls.js.map +1 -0
  87. package/dist/nonprofit.d.ts +116 -0
  88. package/dist/nonprofit.d.ts.map +1 -0
  89. package/dist/nonprofit.js +342 -0
  90. package/dist/nonprofit.js.map +1 -0
  91. package/dist/nws-weather.d.ts +57 -0
  92. package/dist/nws-weather.d.ts.map +1 -0
  93. package/dist/nws-weather.js +131 -0
  94. package/dist/nws-weather.js.map +1 -0
  95. package/dist/openfda-device.d.ts +85 -0
  96. package/dist/openfda-device.d.ts.map +1 -0
  97. package/dist/openfda-device.js +277 -0
  98. package/dist/openfda-device.js.map +1 -0
  99. package/dist/openfda-drugsfda.d.ts +72 -0
  100. package/dist/openfda-drugsfda.d.ts.map +1 -0
  101. package/dist/openfda-drugsfda.js +230 -0
  102. package/dist/openfda-drugsfda.js.map +1 -0
  103. package/dist/openfda.d.ts +133 -0
  104. package/dist/openfda.d.ts.map +1 -0
  105. package/dist/openfda.js +425 -0
  106. package/dist/openfda.js.map +1 -0
  107. package/dist/server.d.ts.map +1 -1
  108. package/dist/server.js +996 -16
  109. package/dist/server.js.map +1 -1
  110. package/dist/treasury.d.ts +2 -0
  111. package/dist/treasury.d.ts.map +1 -1
  112. package/dist/treasury.js +7 -0
  113. package/dist/treasury.js.map +1 -1
  114. package/dist/usaspending.d.ts +32 -1
  115. package/dist/usaspending.d.ts.map +1 -1
  116. package/dist/usaspending.js +143 -16
  117. package/dist/usaspending.js.map +1 -1
  118. package/package.json +3 -2
  119. package/src/bea.ts +1 -1
  120. package/src/cbp-border.ts +177 -0
  121. package/src/census-economic.ts +12 -6
  122. package/src/cms-facility.ts +379 -0
  123. package/src/cms-hospital.ts +344 -0
  124. package/src/cms-supplier.ts +527 -0
  125. package/src/cms-utilization.ts +389 -0
  126. package/src/courtlistener.ts +465 -0
  127. package/src/cpsc.ts +333 -0
  128. package/src/datagov-catalog.ts +18 -2
  129. package/src/dol.ts +5 -5
  130. package/src/ecfr.ts +27 -10
  131. package/src/edgar.ts +39 -7
  132. package/src/epa-envirofacts.ts +358 -0
  133. package/src/errors.ts +11 -0
  134. package/src/far.ts +3 -1
  135. package/src/federal-register.ts +29 -10
  136. package/src/fema.ts +139 -0
  137. package/src/fred.ts +1 -1
  138. package/src/gov-domains.ts +237 -0
  139. package/src/keys.ts +27 -6
  140. package/src/nhtsa.ts +352 -0
  141. package/src/nist-controls.ts +219 -0
  142. package/src/nonprofit.ts +460 -0
  143. package/src/nws-weather.ts +167 -0
  144. package/src/openfda-device.ts +356 -0
  145. package/src/openfda-drugsfda.ts +313 -0
  146. package/src/openfda.ts +518 -0
  147. package/src/server.ts +1127 -27
  148. package/src/treasury.ts +7 -0
  149. package/src/usaspending.ts +189 -17
package/src/treasury.ts CHANGED
@@ -83,6 +83,13 @@ export const TREASURY_DATASETS = {
83
83
  mts_table_1: "/v1/accounting/mts/mts_table_1",
84
84
  rates_of_exchange: "/v1/accounting/od/rates_of_exchange",
85
85
  debt_outstanding: "/v2/accounting/od/debt_outstanding",
86
+ // interest_expense = ACTUAL interest PAID on the debt (debt-service cost by
87
+ // security type), distinct from avg_interest_rates (rates only). LIVE-VERIFIED
88
+ // 2026-07-16 (total-count 7245, truthful pagination). tror = the Treasury Report
89
+ // on Receivables — federal receivables + delinquent-debt collections BY AGENCY
90
+ // (debt-collection contracting / agency financial-management signal; total 3953).
91
+ interest_expense: "/v2/accounting/od/interest_expense",
92
+ tror: "/v2/debt/tror",
86
93
  } as const;
87
94
 
88
95
  export type TreasuryDatasetKey = keyof typeof TREASURY_DATASETS;
@@ -39,6 +39,31 @@ const USAS = "https://api.usaspending.gov/api/v2";
39
39
 
40
40
  export type UsasFilters = Record<string, unknown>;
41
41
 
42
+ /**
43
+ * Build the awarding-agency filter for spending_by_category / spending_by_award —
44
+ * ALWAYS a canonical NAME match. A purely-numeric value is a toptier CODE (e.g.
45
+ * "036"), which the sibling code-based tools (usas_get_agency_profile /
46
+ * usas_get_agency_budget_function) take but which silently matches NOTHING here —
47
+ * returning a confidently-wrong empty (e.g. "VA had $0 subagency spending" when VA
48
+ * actually has $66.87B). Fail loud instead of faking a zero (dogfooding 2026-07-16;
49
+ * same confidently-wrong-empty class as the subaward drift). No real agency NAME is
50
+ * purely numeric, so the /^\d+$/ test never rejects a legitimate name.
51
+ */
52
+ function awardingAgencyFilter(
53
+ agency: string,
54
+ ): { type: string; tier: string; name: string }[] {
55
+ if (/^\d+$/.test(agency.trim())) {
56
+ throw new ToolErrorCarrier({
57
+ kind: "invalid_input",
58
+ retryable: false,
59
+ message: `agency ${JSON.stringify(
60
+ agency,
61
+ )} looks like a toptier CODE, but this filter matches the canonical agency NAME (e.g. "Department of Veterans Affairs") — a code silently matches nothing here and would return a false empty. Resolve the name via usas_lookup_agency or usas_list_toptier_agencies, or use a code-based tool (usas_get_agency_profile / usas_get_agency_budget_function) that takes a toptierCode.`,
62
+ });
63
+ }
64
+ return [{ type: "awarding", tier: "toptier", name: agency }];
65
+ }
66
+
42
67
  function buildFilters(args: {
43
68
  agency?: string;
44
69
  naics?: string;
@@ -48,9 +73,7 @@ function buildFilters(args: {
48
73
  }): UsasFilters {
49
74
  const filters: UsasFilters = { award_type_codes: ["A", "B", "C", "D"] };
50
75
  if (args.agency) {
51
- filters.agencies = [
52
- { type: "awarding", tier: "toptier", name: args.agency },
53
- ];
76
+ filters.agencies = awardingAgencyFilter(args.agency);
54
77
  }
55
78
  if (args.naics) filters.naics_codes = [args.naics];
56
79
  if (args.fiscalYear) {
@@ -180,6 +203,20 @@ function categoryAggregateMeta(opts: {
180
203
  `Capped at the top ${opts.limit} categories by amount; more categories may exist. This endpoint reports no grand total, so the true number of categories is unknown (totalAvailable is null, NOT the returned count). These extra categories are NOT page-reachable — all six callers post page:1 with NO offset/page input (nextOffset is null). Raise limit (up to 50) or narrow filters to see the rest.`,
181
204
  );
182
205
  }
206
+ // Confidently-wrong-empty guard (dogfooding 2026-07-16): a SET agency filter that
207
+ // yields ZERO rows is ambiguous — a genuine zero OR a NAME that didn't match the
208
+ // exact canonical toptier name (an abbreviation / mis-case / partial / typo
209
+ // silently matches nothing on this NAME filter, so "$0" reads as authoritative
210
+ // when the name simply missed). Disclose the ambiguity. (A numeric CODE is already
211
+ // loud-failed by awardingAgencyFilter; this catches the wrong-NAME residual.)
212
+ const agencyFilterSet =
213
+ Array.isArray((opts.filters as { agencies?: unknown[] } | undefined)?.agencies) &&
214
+ (((opts.filters as { agencies?: unknown[] }).agencies?.length ?? 0) > 0);
215
+ if (agencyFilterSet && opts.returned === 0) {
216
+ notes.push(
217
+ `The agency filter matched NO records. This filter requires the EXACT canonical toptier agency NAME (e.g. "Department of Veterans Affairs"): an abbreviation, mis-cased, partial, or mistyped name silently matches nothing here. Verify the exact name via usas_list_toptier_agencies or usas_lookup_agency. If the name is correct, this is a genuine zero for this slice.`,
218
+ );
219
+ }
183
220
  if (opts.extraNotes) notes.push(...opts.extraNotes);
184
221
  return {
185
222
  source: opts.source,
@@ -672,20 +709,24 @@ export async function searchAwardsByRecipient(args: {
672
709
  // ─── Subaward enumeration ─────────────────────────────────────────
673
710
 
674
711
  export async function searchSubawards(args: {
675
- primeRecipientName?: string;
712
+ subRecipientName?: string;
676
713
  agency?: string;
677
714
  naics?: string;
678
715
  fiscalYear?: number;
679
716
  limit?: number;
680
717
  }) {
681
718
  const filters = buildFilters(args);
682
- if (args.primeRecipientName) {
683
- filters.recipient_search_text = [args.primeRecipientName];
719
+ // recipient_search_text on a subawards spending_by_award search matches the
720
+ // SUBAWARDEE name (live-verified 2026-07-16), so this param is subRecipientName —
721
+ // NOT a prime filter (the old `primeRecipientName` name was inverted).
722
+ if (args.subRecipientName) {
723
+ filters.recipient_search_text = [args.subRecipientName];
684
724
  }
685
725
  type Resp = {
686
726
  results?: {
687
727
  "Sub-Award ID"?: string;
688
728
  "Sub-Award Recipient"?: string;
729
+ "Sub-Awardee Name"?: string;
689
730
  "Sub-Award Amount"?: number;
690
731
  "Sub-Award Date"?: string;
691
732
  NAICS?: { code?: string; description?: string };
@@ -705,6 +746,13 @@ export async function searchSubawards(args: {
705
746
  filters,
706
747
  fields: [
707
748
  "Sub-Award ID",
749
+ // DRIFT FIX (dogfooding 2026-07-15): USAspending's spending_by_award now
750
+ // returns the subawardee under "Sub-Awardee Name"; the legacy
751
+ // "Sub-Award Recipient" field is still echoed but is ALWAYS null. Request
752
+ // BOTH and prefer the live one (the endpoint echoes an unknown field as
753
+ // null, so requesting both is safe). Without this, every subRecipient was
754
+ // silently null — the supply-chain/teaming lane could not name any sub.
755
+ "Sub-Awardee Name",
708
756
  "Sub-Award Recipient",
709
757
  "Sub-Award Amount",
710
758
  "Sub-Award Date",
@@ -720,13 +768,13 @@ export async function searchSubawards(args: {
720
768
  const data = {
721
769
  subawards: results.map((r) => ({
722
770
  subAwardId: r["Sub-Award ID"] ?? "",
723
- // minor m2 (W3-1 honesty): an ABSENT Sub-Award Recipient → null, NOT a
724
- // fabricated "(name redacted)". The old sentinel asserted a specific PRIVACY
725
- // reason on ANY nullish value (schema gap / null echo / genuine redaction
726
- // alike) — internally inconsistent with the null-never-fabricate discipline
727
- // this same function applies to `amount` below. Honest null; the caller
728
- // reads absence, not an invented redaction cause.
729
- subRecipient: r["Sub-Award Recipient"] ?? null,
771
+ // The subawardee name read from the CURRENT field "Sub-Awardee Name",
772
+ // falling back to the legacy "Sub-Award Recipient" (now always null upstream).
773
+ // A genuinely absent name in BOTH null (honest absence, never a fabricated
774
+ // "(name redacted)"consistent with the null-never-fabricate discipline
775
+ // `amount` uses below).
776
+ subRecipient:
777
+ r["Sub-Awardee Name"] ?? r["Sub-Award Recipient"] ?? null,
730
778
  // F2 (P3 null-never-0): an ABSENT Sub-Award Amount → null, NEVER a
731
779
  // fabricated $0. A genuine 0 still survives (`??` fires only on null).
732
780
  amount: r["Sub-Award Amount"] ?? null,
@@ -1364,7 +1412,7 @@ export async function searchRecompetes(args: {
1364
1412
  const filtersApplied: string[] = ["awardType(contracts A/B/C/D)"];
1365
1413
  const filtersDropped: string[] = [];
1366
1414
  if (args.agency) {
1367
- filters.agencies = [{ type: "awarding", tier: "toptier", name: args.agency }];
1415
+ filters.agencies = awardingAgencyFilter(args.agency);
1368
1416
  filtersApplied.push("agency");
1369
1417
  }
1370
1418
  if (args.naics) {
@@ -1730,6 +1778,20 @@ export async function spendingOverTime(args: {
1730
1778
  `Completeness for group='${group}': this endpoint returns no pagination envelope, and no-truncation is verified only for fiscal_year granularity — a very long ${group} series could in principle be capped server-side without a signal. Confirm the span (${spanStart ?? "?"} … ${spanEnd ?? "?"}) covers your expected range.`,
1731
1779
  );
1732
1780
  }
1781
+ // Confidently-wrong-empty guard (dogfooding 2026-07-16): a SET agency filter that
1782
+ // yields an ALL-ZERO timeline is ambiguous — a genuine zero OR a NAME that missed
1783
+ // the exact canonical toptier name (an abbreviation / mis-case / typo silently
1784
+ // matches nothing, producing a false "$0 every period" that reads as authoritative
1785
+ // "this agency never spent on X"). Disclose it. (A numeric CODE is already
1786
+ // loud-failed by awardingAgencyFilter; this catches the wrong-NAME residual.)
1787
+ if (
1788
+ args.agency &&
1789
+ (timeline.length === 0 || timeline.every((t) => t.total === 0))
1790
+ ) {
1791
+ notes.push(
1792
+ `The agency filter produced an ALL-ZERO timeline. This filter requires the EXACT canonical toptier agency NAME (e.g. "Department of Veterans Affairs"): an abbreviation, mis-cased, partial, or mistyped name silently matches nothing, yielding a false "$0 in every period". Verify the exact name via usas_list_toptier_agencies or usas_lookup_agency. If the name is correct, the agency genuinely had no CONTRACT obligations in this span.`,
1793
+ );
1794
+ }
1733
1795
 
1734
1796
  return withMeta(
1735
1797
  { group: json.group ?? group, timeline },
@@ -1842,9 +1904,7 @@ export async function searchCfdaSpending(args: {
1842
1904
  award_type_codes: ["02", "03", "04", "05"], // grants
1843
1905
  };
1844
1906
  if (args.agency) {
1845
- filters.agencies = [
1846
- { type: "awarding", tier: "toptier", name: args.agency },
1847
- ];
1907
+ filters.agencies = awardingAgencyFilter(args.agency);
1848
1908
  }
1849
1909
  if (args.fiscalYear) {
1850
1910
  filters.time_period = [
@@ -2625,6 +2685,118 @@ export async function glossary(args: { limit?: number; search?: string }) {
2625
2685
  });
2626
2686
  }
2627
2687
 
2688
+ // ─── Disaster / emergency-fund spending (DEFC axis) ───────────────
2689
+ /**
2690
+ * List the Disaster Emergency Fund Codes (DEFC) — the supplemental-appropriation
2691
+ * tags (COVID-19 relief, IIJA/infrastructure, and other emergency laws) that
2692
+ * usas_disaster_spending filters on. GET references/def_codes/ (keyless). Returns the
2693
+ * COMPLETE code set (no pagination), each with `group` ('covid_19' | 'infrastructure'
2694
+ * | null), title, and public law. Discovery front-door for usas_disaster_spending.
2695
+ */
2696
+ export async function listDisasterCodes() {
2697
+ return memoize("usas:def_codes", async () => {
2698
+ type Resp = {
2699
+ codes?: {
2700
+ code?: string;
2701
+ disaster?: string | null;
2702
+ title?: string;
2703
+ public_law?: string;
2704
+ }[];
2705
+ };
2706
+ const json = await getUsas<Resp>("references/def_codes/");
2707
+ const results = json.codes ?? [];
2708
+ const codes = results.map((c) => ({
2709
+ code: c.code ?? "",
2710
+ // The supplemental-law GROUP: 'covid_19' (COVID relief), 'infrastructure'
2711
+ // (IIJA), or null (other emergency appropriations). NOT fabricated when absent.
2712
+ group: c.disaster ?? null,
2713
+ title: c.title ?? "",
2714
+ publicLaw: typeof c.public_law === "string" ? c.public_law : null,
2715
+ }));
2716
+ return withMeta(
2717
+ { codes },
2718
+ referenceMeta({
2719
+ source: "usaspending.gov/api/v2 references/def_codes",
2720
+ returned: codes.length,
2721
+ limit: codes.length,
2722
+ totalAvailable: codes.length,
2723
+ // The endpoint takes no `limit` and returns the COMPLETE DEFC set → complete.
2724
+ limitHonored: false,
2725
+ extraNotes: [
2726
+ "Complete list of Disaster Emergency Fund Codes (DEFC). group 'covid_19' = COVID-19 relief appropriations; 'infrastructure' = IIJA (Infrastructure Investment and Jobs Act); null = other supplemental/emergency appropriations. Pass one or more `code` values to usas_disaster_spending's `defCodes` to see spending tagged to those funds.",
2727
+ ],
2728
+ }),
2729
+ );
2730
+ });
2731
+ }
2732
+
2733
+ /**
2734
+ * Disaster / emergency-fund spending BY GEOGRAPHY — obligations or outlays tagged to
2735
+ * one or more Disaster Emergency Fund Codes (DEFC: COVID-19, IIJA, etc.), broken out
2736
+ * per state / county / congressional district. POST disaster/spending_by_geography/
2737
+ * (keyless). Answers "which geographies captured COVID/IIJA relief money" — a
2738
+ * distinct axis the standard award search does not expose. `defCodes` REQUIRED
2739
+ * (discover them via usas_list_disaster_codes). The geography endpoint returns the
2740
+ * COMPLETE set of geo units (no pagination) ⇒ totalAvailable = returned, complete.
2741
+ * amount/perCapita are number|null (a real 0 stays 0 — some DEFCs report $0
2742
+ * obligations with a nonzero awardCount; absent → null, never a fabricated 0).
2743
+ */
2744
+ export async function disasterSpending(args: {
2745
+ defCodes: string[];
2746
+ spendingType?: "obligation" | "outlay";
2747
+ geoLayer?: "state" | "county" | "district";
2748
+ }): Promise<MetaBundle> {
2749
+ const spendingType = args.spendingType ?? "obligation";
2750
+ const geoLayer = args.geoLayer ?? "state";
2751
+ type Resp = {
2752
+ geo_layer?: string;
2753
+ results?: {
2754
+ amount?: number;
2755
+ display_name?: string;
2756
+ shape_code?: string;
2757
+ population?: number | null;
2758
+ per_capita?: number;
2759
+ award_count?: number;
2760
+ }[];
2761
+ };
2762
+ const json = await postUsas<Resp>("disaster/spending_by_geography/", {
2763
+ filter: { def_codes: args.defCodes },
2764
+ spending_type: spendingType,
2765
+ geo_layer: geoLayer,
2766
+ });
2767
+ const results = json.results ?? [];
2768
+ // null-never-0: a real 0 (a DEFC that obligated nothing in that geo) stays 0; an
2769
+ // absent/non-number amount is null (never a fabricated 0).
2770
+ const nn = (v: unknown): number | null => (typeof v === "number" ? v : null);
2771
+ const geographies = results.map((r) => ({
2772
+ name: r.display_name ?? "",
2773
+ code: r.shape_code ?? null,
2774
+ amount: nn(r.amount),
2775
+ awardCount: nn(r.award_count),
2776
+ population: nn(r.population),
2777
+ perCapita: nn(r.per_capita),
2778
+ }));
2779
+ return withMeta(
2780
+ { spendingType, geoLayer, defCodes: args.defCodes, geographies },
2781
+ {
2782
+ source: "usaspending.gov/api/v2 disaster/spending_by_geography",
2783
+ keylessMode: true,
2784
+ returned: geographies.length,
2785
+ // The endpoint returns EVERY geo unit for the layer (no cursor/paging) → the
2786
+ // returned set IS complete; totalAvailable = returned, never a fabricated cap.
2787
+ totalAvailable: geographies.length,
2788
+ truncated: false,
2789
+ filtersApplied: ["defCodes", "spendingType", "geoLayer"],
2790
+ filtersDropped: [],
2791
+ notes: [
2792
+ "Spending tagged to the given Disaster Emergency Fund Codes (DEFC), per geography. amount/perCapita are number|null (a real 0 stays 0; absent → null).",
2793
+ "obligation vs outlay differ: some DEFCs (e.g. IIJA/infrastructure) report $0 OBLIGATIONS by geography while awardCount is nonzero — a $0 with awards is NOT 'no activity'; try spendingType:'outlay' and read awardCount alongside the amount.",
2794
+ "This returns the COMPLETE set of geo units for the layer (no pagination) — totalAvailable is the returned count, not a fabricated total.",
2795
+ ],
2796
+ } satisfies Partial<ResponseMeta>,
2797
+ );
2798
+ }
2799
+
2628
2800
  export async function listToptierAgencies(args: { limit?: number }) {
2629
2801
  const limit = args.limit ?? 50;
2630
2802
  return memoize(`usas:toptier:${limit}`, async () => {