/** * @copyright Sister Software * @license AGPL-3.0 * @author Teffen Ellis, et al. * * Fetch the multilingual sub-venue designator vocabulary from Wikidata (#35 wave 1). * * Source : https://query.wikidata.org/sparql (the Wikidata Query Service). * License: CC0. Wikidata's data is public-domain dedicated, so nothing rides on a derived shard. * Tier A. * * ## The pull is CLASS labels, not instance names — and that inversion is the whole design * * The obvious read of "Wikidata for localized designators" is: fetch every airport terminal entity * and read its name in each language. That was tried first and it is the WRONG query. Wikidata * knows 246 items that are `instance of / subclass of*` airport terminal, carrying 775 labels * between them (measured 2026-08-04), and most of those labels are proper names that translate * verbatim — `TWA Flight Center` is spelled `TWA Flight Center` in fifteen languages. The * instance layer is thin and its localization is mostly a no-op. * * The DESIGNATOR is the label of the CLASS. `wd:Q849706` ("airport terminal") is labelled `Terminal` * in German, `terminal aéroportuaire` in French, `ターミナルビル` in Japanese, `航站楼` in Chinese — * and `skos:altLabel` adds the aliases (`Abfertigungsgebäude`, `Flughafenterminal`, `aerostazione`). * Eight concept ids yield 877 label+alias rows across 174 languages, which is the vocabulary the * corpus task asked for and the instance query does not contain. {@link SUBVENUE_CONCEPTS} is that * list of ids; {@link buildDesignatorLabelQuery} is that query. * * Instance labels are fetched too ({@link buildTerminalInstanceQuery}), for a different job: they * are ATTESTED USAGE — evidence of how a designator combines with a modifier or an identifier in * running text. 775 rows is small, and it is a validation set, not a vocabulary. * * ## What a caller must NOT do with the output * * A class label is a CONCEPT NAME, not a designator as written in an address. Q849706's Spanish * label is `terminal aeroportuaria` and its French is `terminal d'aéroport`; nobody writes either on * an envelope, they write `Terminal`. Q247739's Spanish is `puerta de embarque` where the addressed * form is `Puerta`. So this fetch produces CANDIDATE SURFACES that need a head-noun/curation pass * before any of them reaches `neural/venue-structure.ts`'s designator vocabulary — the lexicon * builder marks every one `curated: false` and the burden of promotion is on a human. Wiring the raw * pull straight into the span proposer would admit multi-word phrases that match nothing and, worse, * admit `hall` in a language where it names an ordinary room. * * ## Why `APIClient` here when the OurAirports sibling uses `downloadToFile` * * This is the API-request side of `AGENTS.md`'s split: small JSON bodies, several calls per run, and * a host that publishes a rate policy and enforces it with 429s. Pacing, bounded `Retry-After`-aware * retry, response caching and `ResourceError` mapping all earn their keep, so it extends * {@link APIClient}. `ourairports.ts` is four static file transfers off a CDN and correctly does not. * * WDQS also REQUIRES a descriptive `User-Agent` naming the tool and a contact — an anonymous or * library-default agent is blocked outright by the Wikimedia user-agent policy. See * {@link WIKIDATA_USER_AGENT}. * * Invoke via `mailwoman corpus fetch wikidata-subvenue --out-root `. */ import { APIClient, type ClockLike } from "@mailwoman/core/api"; import type { BaseFetchOptions, FetchSummary } from "./download.ts"; /** * The SPARQL endpoint. Public, no credential. */ export declare const WDQS_ENDPOINT = "https://query.wikidata.org/sparql"; /** * The `User-Agent` every request carries. * * NOT decoration. The Wikimedia user-agent policy blocks requests whose agent is absent, generic, or a library default, * and WDQS enforces it — an unidentified client gets a 403 that no amount of retrying fixes. The policy asks for a tool * name, a URL, and a contact address, all three of which are here. */ export declare const WIKIDATA_USER_AGENT = "mailwoman/1.0 (https://github.com/sister-software/mailwoman; teffen@sister.software) corpus-subvenue-fetch"; /** * One Wikidata concept whose labels are a designator's multilingual surface set. * * `designatorID` is the mailwoman-side vocabulary term, matching `neural/venue-structure.ts`'s * `VENUE_STRUCTURE_DESIGNATORS` wherever the two overlap. `qid` was resolved by `wbsearchentities` and hand-checked * against the entity's English description (recorded below) on 2026-08-04 — a QID picked by search alone is how you end * up pulling the labels of a Bronx neighbourhood called Concourse. * * `wing` is ABSENT and that is a finding, not an oversight: Wikidata has no clean concept for "wing of a building". * `wbsearchentities` for "wing" returns a surname, two English villages, a rugby position and a drone company. Since * `wing` is the single most valuable designator in the arc — `West Wing` is the one modifier case that already parses, * and `East Wing` is the one that does not — its localized surfaces have to come from somewhere else. See the wave-1 * report. */ export interface SubVenueConcept { designatorID: string; qid: string; /** * The entity's English description, recorded so a future reader can tell at a glance whether the QID still names what * we think it names. */ gloss: string; } /** * The concept table. Eight ids, each verified against its English description on 2026-08-04. */ export declare const SUBVENUE_CONCEPTS: readonly SubVenueConcept[]; /** * Build the class-label query: `rdfs:label` and `skos:altLabel` for every concept, in every language, tagged with which * of the two it came from so the lexicon can rank a label above an alias. * * `VALUES` rather than a property path over the whole class tree — the concept list is closed and hand-verified, and a * `wdt:P279*` walk from `building` would drag in every structure type on earth. */ export declare function buildDesignatorLabelQuery(concepts?: readonly SubVenueConcept[]): string; /** * Build the instance-label query — every item that is an `instance of` (through any `subclass of` chain) an airport * terminal, with all of its labels. Measured at 246 items / 775 labels on 2026-08-04, well inside WDQS's 60-second * budget. * * A caveat worth knowing before trusting a row: Wikidata's P31 on these is not clean. `Q1322696` (Kigali International * Airport) is typed as an airport terminal, so the result set mixes AIRPORTS in with terminals. The consumer filters; * this module fetches what the query returns. */ export declare function buildTerminalInstanceQuery(classQID?: string): string; /** * The SPARQL JSON results shape, narrowed to the two column types these queries produce. */ export interface SPARQLResults { results: { bindings: Array>; }; } /** * Whether a decoded body is a SPARQL results envelope. Used as the cache's write validator so an HTML error page served * under a 200 is never persisted for the next run to destructure into `undefined`. */ export declare function isSPARQLResults(value: unknown): value is SPARQLResults; export interface CreateWikidataClientOptions { /** * On-disk response-cache root. Defaults to a `http-cache` directory beside the fetch output. */ cacheDir: string; /** * Time source powering the pacer and the retry backoff. Defaults to the system clock; tests inject a fake so no suite * ever sleeps a real second. */ clock?: ClockLike; /** * Axios overrides, merged over this client's defaults. THE TEST SEAM — pass an `adapter` and no live call is made. * Overriding `headers` wholesale would drop the required `User-Agent`, so don't. */ axios?: ConstructorParameters[0]["axios"]; } /** * A Wikidata Query Service client: paced, retrying, disk-cached, and correctly identified. */ export declare class WikidataClient extends APIClient { /** * Run one SPARQL query and return its results envelope. */ query(sparql: string): Promise; } /** * Construct a {@link WikidataClient} with every default resolved. */ export declare function createWikidataClient(options: CreateWikidataClientOptions): WikidataClient; export type FetchWikidataSubVenueOptions = BaseFetchOptions; /** * Run both queries and write their raw SPARQL JSON into `/wikidata-subvenue/`, with a `MANIFEST.json` carrying * the endpoint, the exact queries, the concept table, row counts and sha256s. * * The RAW envelope is written rather than a reshaped one on purpose: the lexicon build is a separate, pure step * (`sub-venue-lexicon.ts`) and keeping the fetch output byte-faithful to what WDQS served means a lexicon regeneration * never needs the network. */ export declare function fetchWikidataSubVenue(options: FetchWikidataSubVenueOptions, report?: (line: string) => void): Promise; //# sourceMappingURL=wikidata-subvenue.d.ts.map