/** * @copyright Sister Software * @license AGPL-3.0 * @author Teffen Ellis, et al. * * `state-ia-contractors`: Iowa Active Construction Contractor Registrations CSV consumer. * * Iowa Workforce Development publishes a public registry of active construction contractors. Each * row includes a business name, street address, city/state/zip and contact info. * * The adapter consumes the CSV the operator pre-downloads via `fetch-state-sources.ts`. * * Output: one row per contractor with `venue` (business name) and address quad `(house_number, * street, locality, region, postcode)`. * * The id spells the publisher's own word for the trade. Iowa titles the dataset `IA Active * Construction Contractor Registrations`, and the root instructions preserve an external name. The * id is also stored as data: it is the `source` string on every row of a built corpus and the key a * training config's `source_weights` map addresses, so renaming it strands every corpus built * before the rename. * * License: stamped `"Public Domain"` per Iowa state government open-data terms. */ import { formatAddressRow } from "@mailwoman/codex/address/format" import { isPresent } from "@mailwoman/core/objects" import { CSVSpliterator } from "spliterator" import { stableSourceID } from "#adapters/source-id" import { splitStreetLine } from "#adapters/street-line" import { SourceRegister } from "#registers" import { AddressRole, type AdapterOptions, type CanonicalRow, type CorpusAdapter, SurfaceOrigin } from "#types" import { lookupStateAbbreviation } from "#us/fips-state" /** * Registry id for this adapter. * * Stamped into every row it emits, so a corpus record can be traced back to the dataset it came from. */ export const STATE_IA_CONTRACTORS_ADAPTER_ID = "state-ia-contractors" /** * License assigned by this source (Public Domain), attached to each row so downstream * consumers inherit the terms rather than having to look them up. */ export const STATE_IA_CONTRACTORS_DEFAULT_LICENSE = "Public Domain" interface IaContractorRow { "Registration #": string "Business Name": string "Address 1": string "Address 2": string City: string State: string "Zip Code": string "First Name": string "Last Name": string } export function createStateIaContractorsAdapter(): CorpusAdapter { return { id: STATE_IA_CONTRACTORS_ADAPTER_ID, defaultLicense: STATE_IA_CONTRACTORS_DEFAULT_LICENSE, addressRole: AddressRole.RegisteredOffice, register: SourceRegister.IowaContractors, surface: SurfaceOrigin.Rendered, description: "Iowa Active Construction Contractor Registrations — business name + full street address (public-domain).", async *rows(opts: AdapterOptions): AsyncIterable { if (opts.country && opts.country !== "US") { throw new Error(`state-ia-contractors adapter: only US supported, got country=${opts.country}`) } const rows = CSVSpliterator.fromAsync(opts.inputPath, { normalizeKeys: false, }) let emitted = 0 for await (const record of rows as AsyncIterable) { if (opts.signal?.aborted) break if (opts.limit !== undefined && emitted >= opts.limit) break const businessName = record["Business Name"] ?? "" const address1 = record["Address 1"] ?? "" const address2 = record["Address 2"] ?? "" const city = record.City ?? "" const stateAbbr = record.State ?? "" const zip = record["Zip Code"] ?? "" if (!city || !zip) continue const state = lookupStateAbbreviation(stateAbbr) if (!state) continue const fullAddress = [address1, address2].filter(isPresent).join(" ") const split = splitStreetLine(fullAddress) if (!split) continue const venue = businessName || undefined const components: CanonicalRow["components"] = { ...(venue ? { venue } : {}), ...(split.house_number ? { house_number: split.house_number } : {}), street: split.street, locality: city, region: state.abbreviation, postcode: zip, } const rendered = formatAddressRow(components, "US", { singleLine: true }) if (!rendered) continue const { raw, components: aligned } = rendered if (Object.keys(aligned).length <= 2) continue const regNum = record["Registration #"] ?? "" const sourceID = regNum ? `${STATE_IA_CONTRACTORS_ADAPTER_ID}-${regNum}` : stableSourceID(STATE_IA_CONTRACTORS_ADAPTER_ID, aligned) yield { raw, components: aligned, country: "US", locale: "en-US", source: STATE_IA_CONTRACTORS_ADAPTER_ID, source_id: sourceID, corpus_version: "", license: STATE_IA_CONTRACTORS_DEFAULT_LICENSE, } emitted++ } }, } } /** * The configured adapter instance registered with the corpus builder. */ export const stateIaContractorsAdapter = createStateIaContractorsAdapter()