inferers#
Registered curation steps that derive new canonical values from evidence.
Functions#
|
Compute footprint area and per-area value ratios. |
Derive a story count from a measured building height. |
|
Score each row's value against the distribution of its group cohort. |
|
Classify each parcel's land-use class by weighted indicator voting. |
|
|
Impute |
Refine the multi-family class into height bands by story count. |
|
Flag parcels with more than min_homes manufactured-home footprints. |
|
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 canonicalvaluecolumn plus everyimprovement_value*/structure_value*evidence column, a matching{column}_per_arearatio (value per square metre).m2is left missing on synthetic reference-derived rows (geometry_sourcelike'parcel.spine', whose geometry is the reference boundary rather than a building outline); their_per_arearatios 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. Withstatistic='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; passNoneto 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};indicatorsis a list of specs (seeevaluate_indicator()). For each parcel a rule’s score sums its matched indicator weights; among the rules reachingmin_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_columnset whereoutputequalsflag_class(e.g.manufactured_home_park).flag_class (str, optional) – When both are given, write a boolean
flag_columnset whereoutputequalsflag_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_columnis set (default 1.0).
- openplaces.io.curator.inferers.impute_occupancy_type(state: openplaces.io.curator.CurateState) openplaces.io.curator.CurateState#
Impute
occupancy_typefrom ordered evidence, then geometry and dwellings.Vocabulary, evidence columns, and thresholds all come from the recipe
occupancyconfig block; this step holds no source- or class-specific names.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). Withevidence_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-entryweight(default 1.0) tunes each source’s say.Footprint-geometry signal (
rules.manufactured_home_geometry) where all evidence was null.n_dwellingssingle-family gap-fill (rules.single_family_dwellings), residential gaps only. The multi-family-by-dwellings rule is a broad override applied later inresolve_occupancy.Non-primary
residential_classesfootprints becomesecondary_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_typein 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 baseoccupancy_typeare carried over unchanged. Rows of multi_family_class with a missing story count keep that class. By default output isoccupancy_typeitself, 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 whosemax_storiesis not exceeded wins; a final band with nomax_storiesis 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 sharedoccupancy_type_sourcerecords 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_homeevidence column. Class names and the geometry thresholds come from the recipeoccupancyblock; assessor labels come from the shared keyword ruleset, so this step stays vocabulary-neutral. By default it does not assignoccupancy_type— the genericresolve_by_votestep weighsp_manufactured_homeagainst 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 toresolve_by_vote.