reconcilers#
Curation steps that resolve conflicts between competing source columns.
Reconciliation picks (or blends) a single canonical value when two or more columns disagree. It is distinct from imputation (filling missing values) and inference (deriving a brand-new concept).
Functions#
|
Select each canonical value from competing source columns by priority. |
|
Null column wherever condition_column equals condition_value. |
|
Apply parcel-side corrections over the base occupancy and flag conflicts. |
|
Fill missing land-use classes by vote across group-vocabulary evidence. |
|
Override target by tallying weighted votes from independent indicators. |
|
Reconcile street addresses from any number of source inputs. |
Module Contents#
- openplaces.io.curator.reconcilers.reconcile_values(state: openplaces.io.curator.CurateState, priority: dict[str, list[str]]) openplaces.io.curator.CurateState#
Select each canonical value from competing source columns by priority.
For every target feature, the first non-null value across the listed source columns wins (Lochhead et al. 2026, Step C). Source columns are given by their explicit, already-suffixed names so the selection is unambiguous.
- Parameters:
priority (dict of {feature: [source_column, ...]}) –
Each key is the canonical output column; the value is an ordered list of existing source columns to try. Missing columns are skipped.
Example:
priority: n_dwellings: [n_dwellings_overture, n_dwellings_parcel] year_built: [year_built_parcel] improvement_value: [improvement_value_parcel]
- openplaces.io.curator.reconcilers.suppress_where(state: openplaces.io.curator.CurateState, column: str, condition_column: str, condition_value: object = True) openplaces.io.curator.CurateState#
Null column wherever condition_column equals condition_value.
A generic evidence-validity gate: some upstream determination (e.g. a land-use classification) can invalidate an otherwise-present value without itself being a competing source to reconcile against. Distinct from
reconcile_values(picks among several present sources) and imputation (fills a missing value) — this only removes a value that should not have been trusted in the first place.- Parameters:
column (str) – Column to null out.
condition_column (str) – Column whose value triggers the suppression.
condition_value (optional) – Value that triggers suppression (default
True, for a boolean flag column).
- openplaces.io.curator.reconcilers.resolve_occupancy(state: openplaces.io.curator.CurateState, ruleset: str, parcel_column: str = 'use_group_combined_parcel') openplaces.io.curator.CurateState#
Apply parcel-side corrections over the base occupancy and flag conflicts.
The base
occupancy_type(fromimpute_occupancy_type) follows the recipe’s evidence priority. This step applies the high-confidence reviewed keyword override, records the parcel-proposed class, sets a review flag, and summarizes evidence disagreements. All thresholds, columns, and class labels come from the recipeoccupancyblock; the keyword rules come from ruleset.The single class correction here is a
reviewedkeyword rule whose class differs from the base. Value-share and dwelling-count class assignment now happen in the genericresolve_by_votestep, which weighs them against one another rather than letting the last correction win.occupancy_type_parcelis the keyword proposal, else the class coerced from the parcel evidence column (the evidence entry whoselabelisparcel). A review-flag column marks footprints whose improvement value is a small nonzero share of total value (0 < improvement/(improvement+land) < review_max_ratio).occupancy_type_conflictis a categorical summary of every present occupancy evidence (NSI, FEMA, parcel, and any other source inoccupancy.evidence) for rows where two or more disagree (else null), with sources grouped by unique value — e.g."nsi/parcel: Single Family | fema: Manufactured Home"(see_summarize_conflicts()). To keep the column low-cardinality, every non-residential class is collapsed into a single bucket label (occupancy.conflict_other_label, defaultNon-Residential), so only residential — or residential-vs-non-residential — disagreements are surfaced.- Parameters:
ruleset (str) – Filename of the keyword ruleset CSV stored beside the curate recipe.
parcel_column (str, optional) – Parcel land-use column the keyword rules match against.
- openplaces.io.curator.reconcilers.reconcile_land_use(state: openplaces.io.curator.CurateState, columns: list[dict], output: str = 'land_use_class', tiebreaker: str = 'group_parcel', class_map_id: str | None = None, conflict_column: str = 'land_use_class_conflict', report: str | None = None) openplaces.io.curator.CurateState#
Fill missing land-use classes by vote across group-vocabulary evidence.
Each listed column casts one vote per row with its (non-null) value; the value with the most votes wins. On a tie, the tiebreaker column’s value wins when it is among the tied values; a residual tie (tiebreaker absent) falls to the earliest listed column voting for a tied value. The winning group is mapped through the class_map_id crosswalk to the coarse land-use class and fills only rows where output is missing — classes already assigned by the rule-based vote (
classify_parcel_land_use) stay on top. The{output}_sourcesidecar records the winning value’s contributing labels joined with ‘/’ (e.g.nsi/parcel).Also writes conflict_column (see
_summarize_conflicts()), a grouped summary like"nsi/parcel: Single Family | fema: Manufactured Home"for rows where the present values disagree, and saves its most frequent combinations (count-sorted) to the reports directory.- Parameters:
state (CurateState) – The curation state with the target GeoDataFrame in state.curated.
columns (list of dict) – Voting columns in priority order, each
{column, label}. All are expected to share one vocabulary (normalize upstream, e.g. viaremap_column); missing columns are skipped.output (str, optional) – Land-use class column to fill (default
land_use_class).tiebreaker (str, optional) – Column whose value breaks ties when present among the tied values (default
group_parcel).class_map_id (str, optional) – Recipe id of the group -> class crosswalk CSV applied to the winning value. Winning groups missing from the map leave the row unfilled. When omitted, the winning group is written as-is.
conflict_column (str, optional) – Output column for the grouped disagreement summary.
report (str, optional) – Filename for the conflict-combination counts CSV written to the reports directory (skipped when omitted or no conflicts exist).
- openplaces.io.curator.reconcilers.resolve_by_vote(state: openplaces.io.curator.CurateState, target: str, decisions: list[dict]) openplaces.io.curator.CurateState#
Override target by tallying weighted votes from independent indicators.
Each decision proposes one class and lists indicators (predicates over existing columns). For every row, a decision’s score is the sum of the weights of its matched indicators; the decision is eligible where that score reaches its
min_score. Among the eligible decisions the highest score wins (ties broken by recipe order), and the winning class overwrites target. Rows with no eligible decision keep their existing value.This is the generic, vocabulary-neutral reconciliation seam: it holds no class names or thresholds of its own, so the same step resolves any categorical column. New evidence (e.g. a model probability) joins a decision as one more weighted indicator without code changes — a
numeric_at_leastoverp_manufactured_homeis all it takes.- Parameters:
target (str) – Categorical column to override (e.g.
occupancy_type). Created as an all-missing column first when not already present, so this step can also populate a brand-new derived classification, not just correct an existing one.decisions (list of dict) – Ordered candidate classes. Each is
{class, min_score, indicators, require, source}, whereindicatorsis a list of indicator specs (seeevaluate_indicator());min_scoredefaults to 1 and each indicator’sweightdefaults to 1.requireis an optional list of indicator specs (same vocabulary) that must all hold, on top of reachingmin_score— a hard precondition rather than one more weighted vote, for evidence that should veto a decision outright regardless of how strongly the other indicators favor it (e.g. a minimum footprint size). The optionalsourceis the provenance token recorded in{target}_sourcefor rows this decision wins (default'vote'), so the single reason column distinguishes one decision’s outcome from another.
- openplaces.io.curator.reconcilers.reconcile_addresses(state: openplaces.io.curator.CurateState, sources: dict[str, dict[str, str]], output_col: str = 'address', similarity_threshold: float = 80, conflict_column: str = 'address_conflict', complete_from_admin: dict[str, int] | None = None) openplaces.io.curator.CurateState#
Reconcile street addresses from any number of source inputs.
Each key of sources is a provenance token recorded in the output’s source sidecar; its value maps component roles to evidence columns. Roles: address_full (a one-line address string, parsed via openplaces.geo.address.parse_address), address_number, address_street, unit_number, city, state, postal_code.
Declaration order is priority. The base address comes from the highest-priority source with a usable address on each row (non-empty street; sources that declare address_number must also have the number). A lower-priority source agrees with the base when house numbers match and streets match per openplaces.geo.address.match_streets — notation equivalences from address_equivalences.csv are applied, then rapidfuzz similarity against similarity_threshold (0-100); agreeing sources fill the base’s missing components (every component outside MATCH_COMPONENTS) and mark the row ‘reconciled’. Disagreeing sources are excluded from selection but summarized in conflict_column (null when the sources agree or only one is present; see
_summarize_conflicts()). Missing columns are skipped, like in reconcile_values.- Parameters:
sources (dict of {token: {role: column}}) –
Ordered mapping of provenance token to role-column spec.
Example:
sources: parcel: address_full: address_parcel dwelling_overture: address_street: address_street_dwelling_overture address_number: address_number_dwelling_overture
output_col (str) – Canonical output column (default ‘address’).
similarity_threshold (float) – Minimum street similarity (0-100) for two sources to agree.
conflict_column (str) – Output column for the grouped disagreement summary (default ‘address_conflict’).
complete_from_admin (dict of {component: admin_level}, optional) – Fill components that no source provided from the run’s admin id, e.g.
{state: 2}completes a missing state with the admin unit’s level-2 code (validated against ISO 3166-2 for the unit’s country). Only rows that already carry another non-street component are completed, so street-only addresses stay untouched.