/** * @copyright Sister Software * @license AGPL-3.0 * @author Teffen Ellis, et al. * * PCN1 placetype census (hierarchy-evidence campaign, R4c). Per gazetteer PARENT surface, the * distribution of its children's PROJECTED `ComponentTag`s — "this parent has 33 boroughs and 642 * neighbourhoods, i.e. 675 dependent-locality-class children". The general form of the shipped * PIX1 pair index: where PIX1 answers "is THIS child known under THIS parent?", PCN1 answers "does * this parent have children of this KIND at all?" — the conditional prior that turns a globally * rare tag into a conditionally common one (see plan/reference/placetype-evidence.mdx). * * Why both artifacts exist, rather than folding the census's links into the pair index: a pair * entry ASSERTS a surface is a dependent locality, so every batch of them needs a venue-confound * board before it ships (the law-1 directional class — "East Acton" opening a venue name). A * census node asserts nothing about any surface; it can only tilt a reading the model already * entertains under a parent it already identified. That makes the census the safe way to cover * the long tail the pair batches will never individually clear. * * This file owns BOTH ends of the format — `serializePlacetypeCensus` (Node, build tooling) and * `PlacetypeCensusResolver` (browser and server alike) — the same single-file discipline as * `pair-index-resolver.ts` and `postcode-binary-resolver.ts`, so the layout can never drift * between writer and reader. * * Binary layout (little-endian): magic "PCN1" (4 bytes), u32 headerLen, headerLen bytes of * UTF-8-encoded JSON (`PlacetypeCensusHeader`), u32 nodeCount, then nodeCount records of: * * ``` * u16 parentLen, parent utf8[parentLen], u8 entryCount, entryCount × (u8 tagIdx, u32 count) * ``` * * sorted by `parent` in UTF-16 code-unit order. `tagIdx` indexes `COMPONENT_TAGS` (u8, asserted at * serialize time). Per-node entries are sorted by descending count, so a reader that wants only * the dominant class can stop at the first entry. * * `parent` is expected to be already folded by the caller (`normalizeFSTToken`, the same fold PIX1 * uses — `foldVersion` in the header records which), so one query-time fold serves both artifacts. */ import { type ComponentTag } from "@mailwoman/core/types"; /** * One parent node's children-tag distribution. */ export interface PlacetypeCensusNode { /** * Folded parent place name (e.g. `"london"`). */ parent: string; /** * Child counts by PROJECTED tag — the placetype→`ComponentTag` projection is the BUILDER's job (see * `gazetteer-pipeline/placetype-census.ts`), so this artifact never carries a placetype vocabulary of its own. */ counts: Partial>; /** * Sum of `counts` — the node's denominator, precomputed so a consumer never has to re-sum to get a share. */ total: number; } export interface PlacetypeCensusHeader { /** * ISO country code this census was built for. */ country: string; schemaVersion: 1; /** * Which fold the parent surfaces were built against — matches `PairIndexHeader.foldVersion`, so a consumer folds once * and probes both artifacts. */ foldVersion: 1; /** * MD5s of the source artifact(s) this census was built from, for provenance. */ sourceMD5s: string[]; /** * ISO date the census was built. */ buildDate: string; /** * GLOBAL share of each projected tag across every counted child in the country — the denominator a consumer needs to * turn a node's share into a LIFT (`nodeShare / baseRate`). Shipped in the header rather than recomputed by the * consumer because the base rate is a property of the BUILD (which placetypes were counted, over which source), and a * consumer re-deriving it from the node table would silently get a different number: the node table only carries * parents that cleared the build's inclusion rule, so its totals are not the country's totals. */ baseRates: Partial>; /** * OPTIONAL soft-prior bias magnitude a census hit contributes at decode time. ABSENT until a calibration task * measures one — the census ships as a probeable artifact first (R4c is data + loader + offline probe, NO decode * wiring), and a defaulted number here would let an uncalibrated bias reach the decoder unnoticed. */ delta?: number; } /** * Serialize a placetype census to PCN1 bytes. * * Asserts its input is deduplicated by `parent` and throws otherwise — collapsing duplicate parents here would hide a * build bug (two extractions merged without summing their counts) behind a silently plausible artifact. */ export declare function serializePlacetypeCensus(header: PlacetypeCensusHeader, nodes: readonly PlacetypeCensusNode[]): Buffer; /** * Minimal subset of {@link PlacetypeCensusResolver} a consumer module reads — structural typing so callers depend on * the shape, not the class (the same `…Like` convention as `PairIndexLike` / `QueryShapeLike`). The observability rung * (`placetype-pair-prior.ts`'s census probe) needs exactly these two: presence (`probe`) and magnitude (`lift`). * * `share` is deliberately NOT on this interface. Within-parent share was measured at ~100% for the dominant class * everywhere, so a share-proportional consumer reads a constant — `lift` (share ÷ the country base rate) is the only * one of the two that varies with the parent, and naming just it keeps a future consumer from reaching for the flat * one. */ export interface PlacetypeCensusLike { probe(parent: string): PlacetypeCensusNode | null; lift(parent: string, tag: ComponentTag): number; /** * The census header's ISO country code, when the implementation carries a header. Optional for the same reason * `PairIndexLike.country` is: a hand-built test double may omit it. */ readonly country?: string; } /** * Map-backed reader over PCN1 bytes. Pure JS, no Node imports — the browser runtime loads the same artifact. */ export declare class PlacetypeCensusResolver implements PlacetypeCensusLike { #private; readonly header: PlacetypeCensusHeader; constructor(bytes: Uint8Array); get size(): number; /** * Exposes the header's ISO country code so the resolver conforms to {@link PlacetypeCensusLike} — the country gate at * the load site reads it to refuse a census built for a different country than the locale being parsed. */ get country(): string; /** * Look up one folded parent surface. Returns `null` when the parent has no census node — ABSENCE IS NOT EVIDENCE (the * meaning-of-zero rule): a missing node means the gazetteer has no counted children there, which is usually coverage, * so a consumer must treat `null` as neutral and never as a prohibition. */ probe(parent: string): PlacetypeCensusNode | null; /** * The share of `parent`'s counted children projecting onto `tag` — `0` when the parent is unknown or the tag is * unseen there. Positive evidence only: read a `0` as "no support from this artifact", never as "this tag is wrong". */ share(parent: string, tag: ComponentTag): number; /** * `share(parent, tag)` divided by the country's global base rate for `tag` — how much MORE likely this tag is under * this parent than under a parent drawn at random. `1` means "no different from the country at large", `0` means no * support. Returns `0` (not `Infinity`) when the base rate is absent, so a missing denominator can never manufacture * unbounded evidence. */ lift(parent: string, tag: ComponentTag): number; } //# sourceMappingURL=placetype-census.d.ts.map