/** * @fileoverview Census Bureau Data API service. Handles data queries, response parsing, * and suppression code resolution for api.census.gov/data endpoints. * @module services/census-api/census-api-service */ import type { Context } from '@cyanheads/mcp-ts-core'; import type { CensusDataRow, CensusGeographyLevel, CensusPredicateValue, GeographyCheck } from './types.js'; /** * Zero-pad a fixed-width parent FIPS code to the width the Census API matches on, returning * undefined for a blank value so it reads as omitted. State codes are 2 digits and county codes * 3; the API compares them literally, so `state:5` finds nothing where `state:05` finds Arkansas. * * `*` is a scope, not a code — `in=state:53 county:*` is the only way to reach every block group * in a state — so it passes through untouched rather than becoming `00*`. */ export declare function padFips(value: string | undefined, width: number): string | undefined; /** * The distinct values each record column took across a response, keyed by column code. A column * that took one value did not split anything; one that took several is why a geography came back * on several rows, and its codes are what a caller pins the record with. */ export declare function observeRecordValues(rows: CensusDataRow[]): Record>; export declare class CensusApiService { /** geography.json per dataset+year — immutable upstream, cached for the discovery TTL. */ private readonly geographyLevelsCache; /** Wildcard group-by enumerations per dataset+year+dimension+NAICS scope. */ private readonly predicateValuesCache; /** * Query a Census dataset for variables at a specific geography. * Returns parsed rows with suppression codes resolved. */ queryData(params: { variables: string[]; geographyLevel: string; geographyFips: string; /** State FIPS code — required for sub-state geography levels. */ parentFips?: string; /** County FIPS code — required when querying tracts or block groups within a specific county. */ countyFips?: string; /** * Dataset-specific filter values sent as extra query parameters, keyed by variable code * (e.g. `{ NAICS2017: '5112' }`). Omitting one the dataset requires is not an error * upstream — the API returns the aggregate across that dimension instead. */ predicates?: Record; /** * Attribute column to request per filter dimension the query left unset, keyed by * predicate code (e.g. `{ POPGROUP: 'POPGROUP_LABEL' }`). The attribute carries the * label of the default the API applied; requesting the bare predicate code instead * would flip the API from applying one default to enumerating every category. */ defaultLabelColumns?: Record; /** * Columns that separate several records for one geography, keyed by column code * (e.g. `{ MONTH: 'MONTH_DESC' }`). Both the code and its label column are requested, * so each row carries the value that identifies it and the code to pin it with. Unlike * a required filter dimension, naming one of these in `get=` does not change which rows * come back — the API was already returning them all. */ recordColumns?: Record; dataset: string; year: number; }, ctx: Context): Promise; /** * Check a geography level and its supplied parents against the dataset's own * geography.json before spending a data query on a request the Census API will reject. * * Returns `ok` when the dataset has no metadata for the year — the data call then * reports the real problem rather than this check guessing at one. */ checkGeography(params: { dataset: string; year: number; geographyLevel: string; /** The `for=` value — `*` relaxes the innermost required parent. */ geographyFips: string; /** State FIPS, when supplied by the caller. */ parentFips?: string; /** County FIPS, when supplied by the caller. */ countyFips?: string; }, ctx: Context): Promise; /** * Fetch the list of geography levels supported by a dataset+year from the Census API. * Cached in-memory per dataset+year with the discovery TTL. */ fetchGeographyLevels(dataset: string, year: number, ctx: Context): Promise; /** * Enumerate the codes a filter dimension accepts, by wildcarding it on the data endpoint. * * Setting `=*` turns the predicate into a group-by, so the response carries one row * per code the dimension takes for the scope queried. `variables.json` publishes a * `values.item` map for only two dimensions across the whole catalog, so this is the only * route to the rest. Cached per dataset+year+dimension+scope with the discovery TTL. * * The scope matters: on `ecnbasic` the codes `TAXSTAT` and `TYPOP` take are published per * industry, so an unscoped call returns only the all-establishments row and a `naicsScope` * is what makes the enumeration useful — and complete only for that industry. * * What lands in `get=` decides what the wildcard answers from. A dimension's own label column * can be served from the dataset's published value map, so on `dec/ddhca` a label-only request * hands back all 5,543 declared `POPGROUP` codes; naming a `measure` instead forces the read * against the data file, which answers with the 2,996 the dataset publishes rows for. */ fetchPredicateValues(params: { dataset: string; year: number; /** The filter dimension to enumerate (e.g. "EMPSZES"). */ code: string; /** Attribute column carrying each code's label (e.g. "EMPSZES_LABEL"), when published. */ labelAttribute?: string; /** * Measure variable to request instead of the label column, forcing the enumeration to * read the data file. Codes come back unlabelled, so this is for checking which codes the * dataset publishes rather than for building a list to show. */ measure?: string; /** NAICS dimension code and industry value to scope the enumeration by. */ naicsScope?: { code: string; value: string; }; }, ctx: Context): Promise; private parseResponse; } export declare function initCensusApiService(): void; export declare function getCensusApiService(): CensusApiService; //# sourceMappingURL=census-api-service.d.ts.map