inferers#

Registered curation steps that derive new canonical values from evidence.

Functions#

derive_metrics(→ openplaces.io.curator.CurateState)

Compute footprint area and per-area value ratios.

derive_stories_from_height(...)

Derive a story count from a measured building height.

score_relative_to_group(...)

Score each row's value against the distribution of its group cohort.

classify_parcel_land_use(...)

Classify each parcel's land-use class by weighted indicator voting.

impute_occupancy_type(→ openplaces.io.curator.CurateState)

Impute occupancy_type from ordered evidence, then geometry and dwellings.

refine_occupancy_height(...)

Refine the multi-family class into height bands by story count.

flag_manufactured_home_communities(...)

Flag parcels with more than min_homes manufactured-home footprints.

classify_manufactured_homes(...)

Estimate manufactured vs single-family probability from a 3-tier model.

Module Contents#

openplaces.io.curator.inferers.derive_metrics(state: openplaces.io.curator.CurateState) openplaces.io.curator.CurateState#

Compute footprint area and per-area value ratios.

Adds m2 (footprint area in square metres) and, for the canonical value column plus every improvement_value* / structure_value* evidence column, a matching {column}_per_area ratio (value per square metre).

m2 is left missing on synthetic reference-derived rows (geometry_source like 'parcel.spine', whose geometry is the reference boundary rather than a building outline); their _per_area ratios inherit the missing denominator.

openplaces.io.curator.inferers.derive_stories_from_height(state: openplaces.io.curator.CurateState, column: str = 'n_stories_footprint_fema', height_column: str = 'height_footprint_fema', floor_height_m: float = 3.05) openplaces.io.curator.CurateState#

Derive a story count from a measured building height.

Approximates the story count as height / floor_height_m, rounded and floored at one story. A missing or non-positive height yields a missing story count rather than a fabricated minimum.

Parameters:
  • state (CurateState) – The curation state with the target GeoDataFrame in state.curated.

  • column (str, optional) – Output column name for the derived story count.

  • height_column (str, optional) – Source column holding measured building height (metres). No-op if absent from state.curated.

  • floor_height_m (float, optional) – Assumed height per story, in metres.

openplaces.io.curator.inferers.score_relative_to_group(state: openplaces.io.curator.CurateState, group_column: str, value_column: str, output: str, transform: str | None = 'log1p', statistic: str = 'zscore') openplaces.io.curator.CurateState#

Score each row’s value against the distribution of its group cohort.

For every row, scores value_column (optionally transformed first) against the distribution of that same column within its group_column cohort. With statistic='zscore' (default), the score is (value - cohort_mean) / cohort_std (population std, ddof=0); a cohort with zero variance (or a single member) scores every member missing rather than dividing by zero. With statistic='percentile', the score is the value’s rank within its cohort, in [0, 1]. Rows with a missing value_column score missing, propagating rather than being silently zeroed — a downstream step (e.g. a voting indicator) decides how to treat “no measurement” evidence.

Generic over any pair of columns: holds no reference to specific entities or sources, so it is reusable for any “how anomalous is this value relative to its peers” signal — e.g. comparing a footprint’s size, or a structure’s value per area, against a local cohort.

Parameters:
  • group_column (str) – Column whose value defines each row’s cohort.

  • value_column (str) – Column to score within each cohort.

  • output (str) – Name of the column to write.

  • transform ({'log1p', None}, optional) – Applied to value_column before scoring (default 'log1p', useful for right-skewed measures like area; pass None to score the raw value).

  • statistic ({'zscore', 'percentile'}, optional) – Scoring method (default 'zscore').

openplaces.io.curator.inferers.classify_parcel_land_use(state: openplaces.io.curator.CurateState, rules: list[dict], output: str = 'land_use_class', flag_column: str | None = None, flag_class: str | None = None, score_columns: dict[str, str] | None = None, review_column: str | None = None, review_margin: float = 1.0) openplaces.io.curator.CurateState#

Classify each parcel’s land-use class by weighted indicator voting.

The parcel-curation seam that separates, e.g., a manufactured-home park from an RV park using the same mix of evidence as the NSI group inference: assessor use keywords, the linked-NSI modal occupancy group, and per-parcel footprint morphology counts. Vocabulary-neutral like resolve_by_vote — all class names, keywords, NSI groups, and thresholds live in rules, so this step holds no land-use terminology of its own.

Parameters:
  • rules (list of dict) – Ordered candidate classes, each {class, min_score, indicators, weight}; indicators is a list of specs (see evaluate_indicator()). For each parcel a rule’s score sums its matched indicator weights; among the rules reaching min_score (default 1) the highest score wins, ties broken by order. Parcels matching no rule are left null.

  • output (str, optional) – Output class column (default land_use_class).

  • flag_column (str, optional) – When both are given, write a boolean flag_column set where output equals flag_class (e.g. manufactured_home_park).

  • flag_class (str, optional) – When both are given, write a boolean flag_column set where output equals flag_class (e.g. manufactured_home_park).

  • score_columns (dict of {class: column}, optional) – For named classes, also write that rule’s raw weighted score — not gated by its own min_score — to the given column. This is an ordered/graded signal (e.g. a vacancy likelihood) usable by later steps even for parcels where that class did not win the vote.

  • review_column (str, optional) – Boolean column flagging parcels where the winning class’s score beat the runner-up’s (among rules that individually reached their own min_score) by less than review_margin — the “unresolved” confusion cases worth a second look (e.g. Manufactured Home Park vs. RV Park scoring close or tied). Left unset (None) by default.

  • review_margin (float, optional) – Score margin below which review_column is set (default 1.0).

openplaces.io.curator.inferers.impute_occupancy_type(state: openplaces.io.curator.CurateState) openplaces.io.curator.CurateState#

Impute occupancy_type from ordered evidence, then geometry and dwellings.

Vocabulary, evidence columns, and thresholds all come from the recipe occupancy config block; this step holds no source- or class-specific names.

  1. Base class from occupancy.evidence. Default (evidence_mode: cascade): walk the entries in priority order, coerce each column to a class via the class-map ruleset, and take the first non-null (the recipe ordering sets precedence, e.g. a structure source before an area source). With evidence_mode: vote: a weighted consensus vote across all present evidence, so agreeing lower-priority sources can outvote a lone higher-priority one (see _vote_evidence_class()); per-entry weight (default 1.0) tunes each source’s say.

  2. Footprint-geometry signal (rules.manufactured_home_geometry) where all evidence was null.

  3. n_dwellings single-family gap-fill (rules.single_family_dwellings), residential gaps only. The multi-family-by-dwellings rule is a broad override applied later in resolve_occupancy.

  4. Non-primary residential_classes footprints become secondary_class.

Multi-Family is later refined into height bands by refine_occupancy_height.

openplaces.io.curator.inferers.refine_occupancy_height(state: openplaces.io.curator.CurateState, multi_family_class: str, bands: list[dict], output: str = 'occupancy_type', base_output: str | None = 'occupancy_type_base', n_stories_column: str = 'n_stories') openplaces.io.curator.CurateState#

Refine the multi-family class into height bands by story count.

Writes output: a copy of occupancy_type in which rows equal to multi_family_class are split into the recipe-defined height bands wherever a story count is available. All other classes and the base occupancy_type are carried over unchanged. Rows of multi_family_class with a missing story count keep that class. By default output is occupancy_type itself, so the height-banded class becomes the canonical one; the pre-refinement class is preserved in base_output.

The story count reflects merged evidence, so this step runs after merge_enrichments. All terminology (the multi-family class and the band labels/thresholds) comes from the recipe — this step has no hardcoded class names.

Parameters:
  • multi_family_class (str) – The class to split (e.g. the recipe’s multi-family label).

  • bands (list of dict) – Ordered bands, each {max_stories: <int>, class: <str>}. The first band whose max_stories is not exceeded wins; a final band with no max_stories is the open-ended catch-all (e.g. high-rise).

  • output (str, optional) – Output column name (default occupancy_type).

  • base_output (str, optional) – Column to snapshot the pre-refinement class into (default occupancy_type_base). Pass None to skip. It carries no provenance sidecar of its own — the shared occupancy_type_source records which source picked the class.

  • n_stories_column (str, optional) – Story-count column (default n_stories).

openplaces.io.curator.inferers.flag_manufactured_home_communities(state: openplaces.io.curator.CurateState, min_homes: int = 3, output: str = 'manufactured_home_community', count_column: str = 'n_manufactured_homes_per_parcel') openplaces.io.curator.CurateState#

Flag parcels with more than min_homes manufactured-home footprints.

Recomputed from the FINAL footprint occupancy (after imagery, vote, and height refinement), so it reflects the richest manufactured-home evidence — a correction the one-pass parcel lane cannot see, since it runs before footprint curation. Written as footprint columns: a per-parcel count and a boolean flag. A future second parcel pass can write this correction back to the parcel dataset.

Parameters:
  • min_homes (int, optional) – A parcel is a community when it carries strictly more than this many manufactured-home footprints (default 3, i.e. 4+).

  • output (str, optional) – Boolean community-flag column (default manufactured_home_community).

  • count_column (str, optional) – Per-parcel manufactured-home count column (default n_manufactured_homes_per_parcel).

openplaces.io.curator.inferers.classify_manufactured_homes(state: openplaces.io.curator.CurateState, ruleset: str | None = None, model_type: str = 'calibrated_logistic', min_training_samples: int = 10, plausible_aspect_min: float = 1.8, plausible_area_max_m2: float = 250.0, update_occupancy: bool = False) openplaces.io.curator.CurateState#

Estimate manufactured vs single-family probability from a 3-tier model.

Emits the p_manufactured_home evidence column. Class names and the geometry thresholds come from the recipe occupancy block; assessor labels come from the shared keyword ruleset, so this step stays vocabulary-neutral. By default it does not assign occupancy_type — the generic resolve_by_vote step weighs p_manufactured_home against the other indicators and makes the canonical call.

Parameters:
  • state (CurateState) – The curation state with the target GeoDataFrame in state.curated.

  • ruleset (str, optional) – Filename of the keyword ruleset CSV (beside the curate recipe) used to derive Tier 1 assessor labels from the parcel use column. When omitted, Tier 1 is inactive and the morphology model relies on its fallback.

  • model_type (str, optional) – Type of footprint morphology classifier: ‘calibrated_logistic’ (default), ‘random_forest’, ‘gradient_boosting’, ‘svm’, or ‘xgboost’ (if installed).

  • min_training_samples (int, optional) – Minimum number of assessor-labeled structures for each class to train the local morphology model. If not met, falls back to a rule-based scoring model.

  • plausible_aspect_min (float, optional) – Relaxed geometry envelope (aspect ratio at least, area at most) used to gate imagery/morphology overrides of assessor labels.

  • plausible_area_max_m2 (float, optional) – Relaxed geometry envelope (aspect ratio at least, area at most) used to gate imagery/morphology overrides of assessor labels.

  • update_occupancy (bool, optional) – If True, also writes the manufactured/single-family call straight into occupancy_type. Default False: leave that to resolve_by_vote.