@cliwant/mcp-sam-gov 0.3.0 → 1.0.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/LICENSE +21 -21
- package/README.ja.md +48 -16
- package/README.ko.md +48 -16
- package/README.md +226 -64
- package/dist/attachments.d.ts +63 -0
- package/dist/attachments.d.ts.map +1 -0
- package/dist/attachments.js +576 -0
- package/dist/attachments.js.map +1 -0
- package/dist/bls.d.ts +267 -0
- package/dist/bls.d.ts.map +1 -0
- package/dist/bls.js +1549 -0
- package/dist/bls.js.map +1 -0
- package/dist/cache.d.ts +2 -0
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +4 -0
- package/dist/cache.js.map +1 -1
- package/dist/census.d.ts +182 -0
- package/dist/census.d.ts.map +1 -0
- package/dist/census.js +576 -0
- package/dist/census.js.map +1 -0
- package/dist/ckan.d.ts +141 -0
- package/dist/ckan.d.ts.map +1 -0
- package/dist/ckan.js +393 -0
- package/dist/ckan.js.map +1 -0
- package/dist/clinicaltrials.d.ts +180 -0
- package/dist/clinicaltrials.d.ts.map +1 -0
- package/dist/clinicaltrials.js +730 -0
- package/dist/clinicaltrials.js.map +1 -0
- package/dist/cms.d.ts +140 -0
- package/dist/cms.d.ts.map +1 -0
- package/dist/cms.js +482 -0
- package/dist/cms.js.map +1 -0
- package/dist/coerce.d.ts +32 -0
- package/dist/coerce.d.ts.map +1 -0
- package/dist/coerce.js +50 -0
- package/dist/coerce.js.map +1 -0
- package/dist/datagov-catalog.d.ts +84 -0
- package/dist/datagov-catalog.d.ts.map +1 -0
- package/dist/datagov-catalog.js +233 -0
- package/dist/datagov-catalog.js.map +1 -0
- package/dist/datagov.d.ts +146 -0
- package/dist/datagov.d.ts.map +1 -0
- package/dist/datagov.js +689 -0
- package/dist/datagov.js.map +1 -0
- package/dist/datagovKey.d.ts +36 -0
- package/dist/datagovKey.d.ts.map +1 -0
- package/dist/datagovKey.js +60 -0
- package/dist/datagovKey.js.map +1 -0
- package/dist/datasource.d.ts +313 -0
- package/dist/datasource.d.ts.map +1 -0
- package/dist/datasource.js +551 -0
- package/dist/datasource.js.map +1 -0
- package/dist/disclosure.d.ts +55 -0
- package/dist/disclosure.d.ts.map +1 -0
- package/dist/disclosure.js +57 -0
- package/dist/disclosure.js.map +1 -0
- package/dist/ecfr.d.ts +4 -2
- package/dist/ecfr.d.ts.map +1 -1
- package/dist/ecfr.js +74 -1
- package/dist/ecfr.js.map +1 -1
- package/dist/echo.d.ts +143 -0
- package/dist/echo.d.ts.map +1 -0
- package/dist/echo.js +424 -0
- package/dist/echo.js.map +1 -0
- package/dist/edgar.d.ts +377 -0
- package/dist/edgar.d.ts.map +1 -0
- package/dist/edgar.js +2418 -0
- package/dist/edgar.js.map +1 -0
- package/dist/errors.d.ts +26 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +69 -1
- package/dist/errors.js.map +1 -1
- package/dist/fac.d.ts +180 -0
- package/dist/fac.d.ts.map +1 -0
- package/dist/fac.js +416 -0
- package/dist/fac.js.map +1 -0
- package/dist/far.d.ts +170 -0
- package/dist/far.d.ts.map +1 -0
- package/dist/far.js +804 -0
- package/dist/far.js.map +1 -0
- package/dist/fdic.d.ts +599 -0
- package/dist/fdic.d.ts.map +1 -0
- package/dist/fdic.js +1624 -0
- package/dist/fdic.js.map +1 -0
- package/dist/federal-register.d.ts +139 -2
- package/dist/federal-register.d.ts.map +1 -1
- package/dist/federal-register.js +419 -3
- package/dist/federal-register.js.map +1 -1
- package/dist/fema.d.ts +181 -0
- package/dist/fema.d.ts.map +1 -0
- package/dist/fema.js +436 -0
- package/dist/fema.js.map +1 -0
- package/dist/fpds.d.ts +108 -0
- package/dist/fpds.d.ts.map +1 -0
- package/dist/fpds.js +519 -0
- package/dist/fpds.js.map +1 -0
- package/dist/gao.d.ts +64 -0
- package/dist/gao.d.ts.map +1 -0
- package/dist/gao.js +640 -0
- package/dist/gao.js.map +1 -0
- package/dist/govinfo.d.ts +111 -0
- package/dist/govinfo.d.ts.map +1 -0
- package/dist/govinfo.js +422 -0
- package/dist/govinfo.js.map +1 -0
- package/dist/grants.d.ts +27 -4
- package/dist/grants.d.ts.map +1 -1
- package/dist/grants.js +111 -6
- package/dist/grants.js.map +1 -1
- package/dist/gsa-csv.d.ts +249 -0
- package/dist/gsa-csv.d.ts.map +1 -0
- package/dist/gsa-csv.js +784 -0
- package/dist/gsa-csv.js.map +1 -0
- package/dist/integrity.d.ts +212 -0
- package/dist/integrity.d.ts.map +1 -0
- package/dist/integrity.js +707 -0
- package/dist/integrity.js.map +1 -0
- package/dist/meta.d.ts +165 -0
- package/dist/meta.d.ts.map +1 -0
- package/dist/meta.js +162 -0
- package/dist/meta.js.map +1 -0
- package/dist/nih.d.ts +117 -0
- package/dist/nih.d.ts.map +1 -0
- package/dist/nih.js +291 -0
- package/dist/nih.js.map +1 -0
- package/dist/nppes.d.ts +157 -0
- package/dist/nppes.d.ts.map +1 -0
- package/dist/nppes.js +648 -0
- package/dist/nppes.js.map +1 -0
- package/dist/nsf.d.ts +176 -0
- package/dist/nsf.d.ts.map +1 -0
- package/dist/nsf.js +554 -0
- package/dist/nsf.js.map +1 -0
- package/dist/nvd.d.ts +176 -0
- package/dist/nvd.d.ts.map +1 -0
- package/dist/nvd.js +912 -0
- package/dist/nvd.js.map +1 -0
- package/dist/ofac.d.ts +205 -0
- package/dist/ofac.d.ts.map +1 -0
- package/dist/ofac.js +919 -0
- package/dist/ofac.js.map +1 -0
- package/dist/pricing.d.ts +110 -0
- package/dist/pricing.d.ts.map +1 -0
- package/dist/pricing.js +843 -0
- package/dist/pricing.js.map +1 -0
- package/dist/sam-gov/client.d.ts +60 -2
- package/dist/sam-gov/client.d.ts.map +1 -1
- package/dist/sam-gov/client.js +320 -54
- package/dist/sam-gov/client.js.map +1 -1
- package/dist/sam-gov/index.d.ts +1 -1
- package/dist/sam-gov/index.d.ts.map +1 -1
- package/dist/sam-gov/index.js +1 -1
- package/dist/sam-gov/index.js.map +1 -1
- package/dist/sam-gov/types.d.ts +24 -0
- package/dist/sam-gov/types.d.ts.map +1 -1
- package/dist/sba.d.ts +72 -0
- package/dist/sba.d.ts.map +1 -0
- package/dist/sba.js +281 -0
- package/dist/sba.js.map +1 -0
- package/dist/server.d.ts +14 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +3889 -296
- package/dist/server.js.map +1 -1
- package/dist/snapshot.d.ts +98 -0
- package/dist/snapshot.d.ts.map +1 -0
- package/dist/snapshot.js +146 -0
- package/dist/snapshot.js.map +1 -0
- package/dist/socrata.d.ts +157 -0
- package/dist/socrata.d.ts.map +1 -0
- package/dist/socrata.js +448 -0
- package/dist/socrata.js.map +1 -0
- package/dist/treasury.d.ts +143 -0
- package/dist/treasury.d.ts.map +1 -0
- package/dist/treasury.js +436 -0
- package/dist/treasury.js.map +1 -0
- package/dist/usaspending.d.ts +260 -65
- package/dist/usaspending.d.ts.map +1 -1
- package/dist/usaspending.js +1629 -201
- package/dist/usaspending.js.map +1 -1
- package/dist/usitc.d.ts +142 -0
- package/dist/usitc.d.ts.map +1 -0
- package/dist/usitc.js +339 -0
- package/dist/usitc.js.map +1 -0
- package/package.json +24 -2
- package/src/attachments.ts +652 -0
- package/src/bls.ts +1943 -0
- package/src/cache.ts +5 -0
- package/src/census.ts +735 -0
- package/src/ckan.ts +495 -0
- package/src/clinicaltrials.ts +923 -0
- package/src/cms.ts +634 -0
- package/src/coerce.ts +47 -0
- package/src/datagov-catalog.ts +296 -0
- package/src/datagov.ts +907 -0
- package/src/datagovKey.ts +68 -0
- package/src/datasource.ts +721 -0
- package/src/disclosure.ts +61 -0
- package/src/ecfr.ts +98 -1
- package/src/echo.ts +496 -0
- package/src/edgar.ts +3014 -0
- package/src/errors.ts +84 -1
- package/src/fac.ts +529 -0
- package/src/far.ts +1007 -0
- package/src/fdic.ts +2052 -0
- package/src/federal-register.ts +512 -3
- package/src/fema.ts +541 -0
- package/src/fpds.ts +620 -0
- package/src/gao.ts +744 -0
- package/src/govinfo.ts +497 -0
- package/src/grants.ts +139 -7
- package/src/gsa-csv.ts +992 -0
- package/src/integrity.ts +928 -0
- package/src/meta.ts +292 -0
- package/src/nih.ts +375 -0
- package/src/nppes.ts +834 -0
- package/src/nsf.ts +706 -0
- package/src/nvd.ts +1124 -0
- package/src/ofac.ts +1166 -0
- package/src/pricing.ts +1075 -0
- package/src/sam-gov/client.ts +345 -63
- package/src/sam-gov/index.ts +5 -1
- package/src/sam-gov/types.ts +22 -0
- package/src/sba.ts +357 -0
- package/src/server.ts +4551 -328
- package/src/snapshot.ts +192 -0
- package/src/socrata.ts +532 -0
- package/src/treasury.ts +575 -0
- package/src/usaspending.ts +1946 -214
- package/src/usitc.ts +420 -0
package/src/sba.ts
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SBA small-business size standards (keyless reference lookup).
|
|
3
|
+
*
|
|
4
|
+
* Why this exists
|
|
5
|
+
* ----------------
|
|
6
|
+
* "Is this firm actually SMALL for this NAICS?" is the gating question for
|
|
7
|
+
* every set-aside eligibility call and for vetting a teaming candidate
|
|
8
|
+
* (`usas_search_teaming_partners` surfaces award-derived candidates — this tool
|
|
9
|
+
* answers whether one of them clears the SBA size standard for the work).
|
|
10
|
+
* The answer is a per-NAICS threshold published by SBA: either an average
|
|
11
|
+
* annual RECEIPTS cap (most services) or an EMPLOYEE-count cap (most
|
|
12
|
+
* manufacturing/mining), with a small set of financial NAICS gated on ASSETS.
|
|
13
|
+
*
|
|
14
|
+
* Source — VERIFIED LIVE 2026-07-03
|
|
15
|
+
* ---------------------------------
|
|
16
|
+
* GET https://www.sba.gov/sites/default/files/data/naics.json
|
|
17
|
+
* → HTTP 200, application/json, a keyless ARRAY of ~997 entries, one per
|
|
18
|
+
* 6-digit NAICS (plus a handful of `<code>_a_Except` exception rows).
|
|
19
|
+
* Each entry:
|
|
20
|
+
* { id, description, sectorId, sectorDescription, subsectorId,
|
|
21
|
+
* subsectorDescription, revenueLimit, assetLimit, employeeCountLimit,
|
|
22
|
+
* parent, footnote }
|
|
23
|
+
* - revenueLimit = receipts-based standard in $ MILLIONS (34 ⇒ $34,000,000)
|
|
24
|
+
* - assetLimit = asset-based standard in $ MILLIONS (850 ⇒ $850,000,000)
|
|
25
|
+
* - employeeCountLimit = employee-based standard as a NUMBER OF EMPLOYEES
|
|
26
|
+
* A NAICS uses exactly ONE of these; the other two are null. (Verified: zero
|
|
27
|
+
* rows carry revenue+assets or revenue+employees together.)
|
|
28
|
+
*
|
|
29
|
+
* IMPORTANT — not a permanent constant (doc-07 caveat)
|
|
30
|
+
* ----------------------------------------------------
|
|
31
|
+
* SBA adjusts size standards periodically (inflation adjustments to the
|
|
32
|
+
* monetary caps, employee-based reviews). This dataset carries NO explicit
|
|
33
|
+
* effective-date field, so we surface the value AS PUBLISHED in the fetched
|
|
34
|
+
* file at retrieval time — with an `asOf` timestamp and an explicit _meta note
|
|
35
|
+
* that high-stakes eligibility must be re-verified at sba.gov. We never present
|
|
36
|
+
* the number as an immutable truth.
|
|
37
|
+
*
|
|
38
|
+
* Keyless. Fetched once and served from the shared 5-minute reference cache
|
|
39
|
+
* (the file is ~200 KB — we cache the parsed lookup, not one fetch per call).
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import { fetchWithRetry, ToolErrorCarrier } from "./errors.js";
|
|
43
|
+
import {
|
|
44
|
+
throughPathChain,
|
|
45
|
+
CircuitBreaker,
|
|
46
|
+
type ResiliencePath,
|
|
47
|
+
type Provenance,
|
|
48
|
+
} from "./datasource.js";
|
|
49
|
+
import { snapshotPath, provenanceMeta } from "./snapshot.js";
|
|
50
|
+
import { memoize } from "./cache.js";
|
|
51
|
+
import { withMeta } from "./meta.js";
|
|
52
|
+
|
|
53
|
+
const SBA_NAICS_URL =
|
|
54
|
+
"https://www.sba.gov/sites/default/files/data/naics.json";
|
|
55
|
+
|
|
56
|
+
// ─── Resilience wiring (ADR-0045 pilot expansion — INERT by default) ───────
|
|
57
|
+
// The whole naics.json file (all ~997 size standards) is a single, queryless,
|
|
58
|
+
// slow-changing PUBLIC reference — the cleanest snapshot candidate: one
|
|
59
|
+
// pre-fetch serves EVERY per-NAICS lookup. The live host + a per-host circuit
|
|
60
|
+
// breaker keyed on the FIXED set {this host} (bounded — m3-regression),
|
|
61
|
+
// CONSULTED only by `throughPathChain` for a ≥2-path chain. When
|
|
62
|
+
// SAMGOV_SNAPSHOT_BASE_URL is unset the chain is single-path (live only), the
|
|
63
|
+
// breaker is a pure no-op, and the tool is BYTE-IDENTICAL to before this ADR.
|
|
64
|
+
const SBA_HOST = "www.sba.gov";
|
|
65
|
+
let sbaBreaker = new CircuitBreaker([SBA_HOST]);
|
|
66
|
+
|
|
67
|
+
/** Test-only: reset the resilience breaker between OFFLINE fixtures (mirrors
|
|
68
|
+
* treasury.ts's `_resetTreasuryBreakerForTests`). */
|
|
69
|
+
export function _resetSbaBreakerForTests(): void {
|
|
70
|
+
sbaBreaker = new CircuitBreaker([SBA_HOST]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** One raw entry in sba.gov/naics.json (only the fields we consume). */
|
|
74
|
+
type SbaNaicsEntry = {
|
|
75
|
+
id?: string;
|
|
76
|
+
description?: string;
|
|
77
|
+
sectorId?: string;
|
|
78
|
+
sectorDescription?: string;
|
|
79
|
+
subsectorId?: string;
|
|
80
|
+
subsectorDescription?: string;
|
|
81
|
+
/** Receipts-based standard, in $ MILLIONS (null when not receipts-based). */
|
|
82
|
+
revenueLimit?: number | null;
|
|
83
|
+
/** Asset-based standard, in $ MILLIONS (null unless a financial NAICS). */
|
|
84
|
+
assetLimit?: number | null;
|
|
85
|
+
/** Employee-based standard, a NUMBER OF EMPLOYEES (null when not emp-based). */
|
|
86
|
+
employeeCountLimit?: number | null;
|
|
87
|
+
parent?: number | string | null;
|
|
88
|
+
footnote?: string[] | null;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const MILLIONS = 1_000_000;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Fetch + parse the SBA naics.json into a Map keyed by 6-digit NAICS id.
|
|
95
|
+
* Cached (5-min TTL) under a single key so the ~200 KB file is pulled at most
|
|
96
|
+
* once per cache window regardless of how many NAICS the agent looks up.
|
|
97
|
+
*
|
|
98
|
+
* On upstream failure this THROWS a ToolErrorCarrier (retry-classified by
|
|
99
|
+
* fetchWithRetry) — it never resolves to an empty map, so a lookup can never be
|
|
100
|
+
* silently reported as "NAICS not found" during an outage.
|
|
101
|
+
*/
|
|
102
|
+
async function loadSizeStandards(): Promise<{
|
|
103
|
+
byId: Map<string, SbaNaicsEntry>;
|
|
104
|
+
count: number;
|
|
105
|
+
provenance: Provenance;
|
|
106
|
+
}> {
|
|
107
|
+
return memoize("sba:size-standards", async () => {
|
|
108
|
+
// ★Route the fetch through the resilience path-chain. The LIVE path is
|
|
109
|
+
// byte-identical to the prior bare fetch — same URL, same init
|
|
110
|
+
// ({headers:{Accept}, signal}), same label — so with no snapshot configured
|
|
111
|
+
// the chain is SINGLE-ENTRY, `throughPathChain` fast-paths (no breaker
|
|
112
|
+
// consult), and behavior is BYTE-IDENTICAL to before this ADR. The snapshot
|
|
113
|
+
// (key `sba_size_standards`, the whole array) is added only when
|
|
114
|
+
// SAMGOV_SNAPSHOT_BASE_URL is configured (else snapshotPath returns null).
|
|
115
|
+
const livePath: ResiliencePath<unknown> = {
|
|
116
|
+
host: SBA_HOST,
|
|
117
|
+
provenance: { dataPath: "live" },
|
|
118
|
+
run: async () => {
|
|
119
|
+
const r = await fetchWithRetry(
|
|
120
|
+
SBA_NAICS_URL,
|
|
121
|
+
{
|
|
122
|
+
headers: { Accept: "application/json" },
|
|
123
|
+
signal: AbortSignal.timeout(15_000),
|
|
124
|
+
},
|
|
125
|
+
"sba:naics.json",
|
|
126
|
+
);
|
|
127
|
+
return (await r.json()) as unknown;
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
const snap = snapshotPath<unknown>("sba_size_standards");
|
|
131
|
+
const paths = snap ? [livePath, snap] : [livePath];
|
|
132
|
+
const { body: raw, provenance } = await throughPathChain<unknown>(
|
|
133
|
+
paths,
|
|
134
|
+
sbaBreaker,
|
|
135
|
+
);
|
|
136
|
+
if (!Array.isArray(raw)) {
|
|
137
|
+
// 200 with an unexpected shape ⇒ schema drift, not "no data".
|
|
138
|
+
throw new ToolErrorCarrier({
|
|
139
|
+
kind: "schema_drift",
|
|
140
|
+
message:
|
|
141
|
+
"sba.gov naics.json did not return a JSON array (schema drift — the size-standards dataset shape changed).",
|
|
142
|
+
retryable: false,
|
|
143
|
+
upstreamEndpoint: "sba:naics.json",
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
const byId = new Map<string, SbaNaicsEntry>();
|
|
147
|
+
for (const entry of raw as SbaNaicsEntry[]) {
|
|
148
|
+
const id = entry?.id;
|
|
149
|
+
// Index only clean 6-digit NAICS ids. The `<code>_a_Except` exception
|
|
150
|
+
// rows share a base code with their parent and would otherwise clobber
|
|
151
|
+
// the canonical entry; a caller looks up a plain 6-digit NAICS.
|
|
152
|
+
if (typeof id === "string" && /^\d{6}$/.test(id) && !byId.has(id)) {
|
|
153
|
+
byId.set(id, entry);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return { byId, count: byId.size, provenance };
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export type SizeStandardResult = {
|
|
161
|
+
naics: string;
|
|
162
|
+
found: boolean;
|
|
163
|
+
description: string | null;
|
|
164
|
+
sector: string | null;
|
|
165
|
+
subsector: string | null;
|
|
166
|
+
standardType: "receipts" | "employees" | "assets" | "receipts+assets" | "unknown";
|
|
167
|
+
/** Receipts/assets → DOLLARS (revenueLimit*1e6); employees → the count. */
|
|
168
|
+
threshold: number | null;
|
|
169
|
+
unit: "USD annual receipts" | "employees" | "USD assets" | null;
|
|
170
|
+
/** Raw normalized values (null where the dataset has none). */
|
|
171
|
+
revenueLimitUSD: number | null;
|
|
172
|
+
employeeCountLimit: number | null;
|
|
173
|
+
assetLimitUSD: number | null;
|
|
174
|
+
footnote: string[] | null;
|
|
175
|
+
/** Retrieval timestamp — the value is "as published as of" this instant. */
|
|
176
|
+
asOf: string;
|
|
177
|
+
sourceUrl: string;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
/** The doc-07 "not a permanent constant" caveat, surfaced in every _meta. */
|
|
181
|
+
const NOT_A_CONSTANT_CAVEAT =
|
|
182
|
+
"SBA size standards are adjusted periodically (e.g. SBA has proposed monetary increases) — " +
|
|
183
|
+
"treat this as the value published in the fetched dataset as of retrieval, not a permanent " +
|
|
184
|
+
"constant; the dataset does not carry an explicit effective-date field, so verify the current " +
|
|
185
|
+
"standard at sba.gov for high-stakes eligibility.";
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Look up the SBA small-business size standard for one 6-digit NAICS.
|
|
189
|
+
*
|
|
190
|
+
* Returns `found:false` (never a fabricated standard) when the NAICS is not in
|
|
191
|
+
* the fetched dataset, with the disclosure carried in `_meta.notes`.
|
|
192
|
+
*/
|
|
193
|
+
export async function sizeStandard(args: { naics: string }) {
|
|
194
|
+
const naics = (args.naics ?? "").trim();
|
|
195
|
+
// Validate shape at the tool boundary — a malformed NAICS is invalid_input,
|
|
196
|
+
// not a silent "not found". (The server also enforces this via Zod.)
|
|
197
|
+
if (!/^\d{6}$/.test(naics)) {
|
|
198
|
+
throw new ToolErrorCarrier({
|
|
199
|
+
kind: "invalid_input",
|
|
200
|
+
message: `naics must be a 6-digit NAICS code (got "${args.naics}").`,
|
|
201
|
+
retryable: false,
|
|
202
|
+
upstreamEndpoint: "sba:naics.json",
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const { byId, count, provenance } = await loadSizeStandards();
|
|
207
|
+
const asOf = new Date().toISOString();
|
|
208
|
+
const entry = byId.get(naics);
|
|
209
|
+
|
|
210
|
+
if (!entry) {
|
|
211
|
+
const data: SizeStandardResult = {
|
|
212
|
+
naics,
|
|
213
|
+
found: false,
|
|
214
|
+
description: null,
|
|
215
|
+
sector: null,
|
|
216
|
+
subsector: null,
|
|
217
|
+
standardType: "unknown",
|
|
218
|
+
threshold: null,
|
|
219
|
+
unit: null,
|
|
220
|
+
revenueLimitUSD: null,
|
|
221
|
+
employeeCountLimit: null,
|
|
222
|
+
assetLimitUSD: null,
|
|
223
|
+
footnote: null,
|
|
224
|
+
asOf,
|
|
225
|
+
sourceUrl: SBA_NAICS_URL,
|
|
226
|
+
};
|
|
227
|
+
return withMeta(data, {
|
|
228
|
+
source: "sba.gov naics.json (keyless)",
|
|
229
|
+
keylessMode: true,
|
|
230
|
+
returned: 0,
|
|
231
|
+
// LEAD-12 fix (C78 adversarial review): a definitive not-found is a COMPLETE,
|
|
232
|
+
// non-truncated answer — the tool scanned the whole cached dataset and confirmed
|
|
233
|
+
// the NAICS is absent. totalAvailable is 0 (zero records match THIS query), NOT
|
|
234
|
+
// the ~978-row dataset size; sourcing `count` here made returned(0) < 978 →
|
|
235
|
+
// truncated:true → complete:false, which falsely told the agent to paginate for
|
|
236
|
+
// the "other 977 results" that do not exist. complete now derives to true. The
|
|
237
|
+
// dataset size stays in the note below (its correct home = prose disclosure).
|
|
238
|
+
totalAvailable: 0,
|
|
239
|
+
fieldsUnavailable: [
|
|
240
|
+
"description",
|
|
241
|
+
"sector",
|
|
242
|
+
"subsector",
|
|
243
|
+
"standardType",
|
|
244
|
+
"threshold",
|
|
245
|
+
"unit",
|
|
246
|
+
"revenueLimitUSD",
|
|
247
|
+
"employeeCountLimit",
|
|
248
|
+
"assetLimitUSD",
|
|
249
|
+
],
|
|
250
|
+
notes: [
|
|
251
|
+
`NAICS ${naics} is not present in the SBA size-standards dataset (${count} 6-digit codes as of retrieval). No size standard was fabricated — confirm the code is a current 6-digit NAICS and check sba.gov.`,
|
|
252
|
+
NOT_A_CONSTANT_CAVEAT,
|
|
253
|
+
],
|
|
254
|
+
// P5 provenance — threaded ONLY when NON-live ⇒ live stays byte-identical.
|
|
255
|
+
...provenanceMeta(provenance),
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Normalize the raw millions figures to dollars. Exactly one of the three
|
|
260
|
+
// limits is set on any real row (verified live), so we classify by presence.
|
|
261
|
+
const revenueLimitUSD =
|
|
262
|
+
typeof entry.revenueLimit === "number"
|
|
263
|
+
? entry.revenueLimit * MILLIONS
|
|
264
|
+
: null;
|
|
265
|
+
const assetLimitUSD =
|
|
266
|
+
typeof entry.assetLimit === "number" ? entry.assetLimit * MILLIONS : null;
|
|
267
|
+
const employeeCountLimit =
|
|
268
|
+
typeof entry.employeeCountLimit === "number"
|
|
269
|
+
? entry.employeeCountLimit
|
|
270
|
+
: null;
|
|
271
|
+
|
|
272
|
+
let standardType: SizeStandardResult["standardType"];
|
|
273
|
+
let threshold: number | null;
|
|
274
|
+
let unit: SizeStandardResult["unit"];
|
|
275
|
+
const fieldsUnavailable: string[] = [];
|
|
276
|
+
|
|
277
|
+
if (revenueLimitUSD !== null) {
|
|
278
|
+
// Receipts-based. (A financial NAICS could in principle also carry an
|
|
279
|
+
// asset figure — none do today — in which case we flag it receipts+assets.)
|
|
280
|
+
standardType = assetLimitUSD !== null ? "receipts+assets" : "receipts";
|
|
281
|
+
threshold = revenueLimitUSD;
|
|
282
|
+
unit = "USD annual receipts";
|
|
283
|
+
} else if (employeeCountLimit !== null) {
|
|
284
|
+
standardType = "employees";
|
|
285
|
+
threshold = employeeCountLimit;
|
|
286
|
+
unit = "employees";
|
|
287
|
+
} else if (assetLimitUSD !== null) {
|
|
288
|
+
// Asset-only standard (financial institutions — e.g. Commercial Banking).
|
|
289
|
+
// Labeled "assets" (NOT "receipts+assets") — these rows carry NO receipts
|
|
290
|
+
// component, so the label must not imply one.
|
|
291
|
+
standardType = "assets";
|
|
292
|
+
threshold = assetLimitUSD;
|
|
293
|
+
unit = "USD assets";
|
|
294
|
+
} else {
|
|
295
|
+
// A row exists but publishes no numeric standard (rare/reserved).
|
|
296
|
+
standardType = "unknown";
|
|
297
|
+
threshold = null;
|
|
298
|
+
unit = null;
|
|
299
|
+
fieldsUnavailable.push("threshold", "unit", "standardType");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const data: SizeStandardResult = {
|
|
303
|
+
naics,
|
|
304
|
+
found: true,
|
|
305
|
+
description: entry.description ?? null,
|
|
306
|
+
sector: entry.sectorDescription ?? null,
|
|
307
|
+
subsector: entry.subsectorDescription ?? null,
|
|
308
|
+
standardType,
|
|
309
|
+
threshold,
|
|
310
|
+
unit,
|
|
311
|
+
revenueLimitUSD,
|
|
312
|
+
employeeCountLimit,
|
|
313
|
+
assetLimitUSD,
|
|
314
|
+
footnote: entry.footnote ?? null,
|
|
315
|
+
asOf,
|
|
316
|
+
sourceUrl: SBA_NAICS_URL,
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
// A human-readable restatement of the threshold, so the AI never mistakes the
|
|
320
|
+
// raw millions figure ("34") for dollars.
|
|
321
|
+
const thresholdNote =
|
|
322
|
+
unit === "USD annual receipts"
|
|
323
|
+
? `Size standard: $${(threshold as number).toLocaleString("en-US")} in average annual receipts (SBA publishes this as $${entry.revenueLimit} million).`
|
|
324
|
+
: unit === "employees"
|
|
325
|
+
? `Size standard: ${(threshold as number).toLocaleString("en-US")} employees.`
|
|
326
|
+
: unit === "USD assets"
|
|
327
|
+
? `Size standard: $${(threshold as number).toLocaleString("en-US")} in assets (SBA publishes this as $${entry.assetLimit} million); this is a financial-institution asset-based standard.`
|
|
328
|
+
: `No numeric size standard is published for NAICS ${naics} in this dataset.`;
|
|
329
|
+
|
|
330
|
+
const notes = [thresholdNote, NOT_A_CONSTANT_CAVEAT];
|
|
331
|
+
if (entry.footnote && entry.footnote.length > 0) {
|
|
332
|
+
notes.push(
|
|
333
|
+
"This NAICS carries an SBA footnote qualifying the standard (see data.footnote).",
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return withMeta(data, {
|
|
338
|
+
source: "sba.gov naics.json (keyless)",
|
|
339
|
+
keylessMode: true,
|
|
340
|
+
returned: 1,
|
|
341
|
+
// LEAD-12 fix (Codex C76 dogfood, C78 live-repro): a single-NAICS lookup has
|
|
342
|
+
// exactly ONE matching size standard — totalAvailable is 1, NOT the whole
|
|
343
|
+
// dataset row count (`count`, ~978). Using `count` made totalAvailable(978) >
|
|
344
|
+
// returned(1), which buildMeta reads as totalProvesTruncation → truncated:true
|
|
345
|
+
// → complete:false, MISLABELING a complete exact answer as a truncated 1-of-978
|
|
346
|
+
// result (the explicit complete:true above was silently overridden).
|
|
347
|
+
totalAvailable: 1,
|
|
348
|
+
complete: true,
|
|
349
|
+
fieldsUnavailable,
|
|
350
|
+
notes,
|
|
351
|
+
// P5 provenance — threaded ONLY when NON-live ⇒ live stays byte-identical.
|
|
352
|
+
// On a snapshot, buildMeta forces complete!==true (M1a) and qualifies the
|
|
353
|
+
// totalAvailable via totalIsEstimated (M1b) — the found standard is disclosed
|
|
354
|
+
// as an as-of figure, never a live-authoritative one.
|
|
355
|
+
...provenanceMeta(provenance),
|
|
356
|
+
});
|
|
357
|
+
}
|