Land — GUF Module¶
The land/ package contains three modules: guf.py (single-parcel physics), collective.py (batch inventory calculator), and calibration.py (rate and weight calibration tools). All follow the layer rule: land/ imports from core/ only.
guf.py — Single-Parcel GUF¶
Module: hours_eoh/land/guf.py
14 functions implementing the Ground Use Fee framework (NLSA TM-0042). All functions follow the layer rules: land/ imports from core/ but is never imported by core/.
For the full mathematical specification, see Ground Use Fee Framework.
Diagrams: Master equation assembly · L(p) weights · Ψ(ε) arc · Trust flow · Write-down pathways
Core GUF Components¶
epsilon_scaling(epsilon) → float¶
The Ψ(ε) bell-shaped function: 4 × ε^0.8 × (1−ε)^1.2 + 0.02
Peaks near ε = 0.40 (Ψ ≈ 1.06), floors at 0.02 at both extremes. Shape parameters a=0.8, b=1.2, floor=0.02 are named constants in data.py.
labor_content_scaling(epsilon) → float¶
The α(ε) function: (1−ε)^0.8 + 0.05, normalized so α(0.40) = 1.0. Scales Use Category Coefficients for the declining labor content of land-use activities.
location_value_index(centrality, transit, services, natural_amenity, weights) → float¶
L(p) — weighted composite of four sub-indices (Eq. 3).
use_category_coefficient(use_category, epsilon) → float¶
U(p,ε) — reference rate for the use category scaled by α(ε) (Eq. 9).
demand_pressure_modifier(demand_supply_ratio, eta) → float¶
D(p) — logarithmic demand modifier, capped at 1.8 (Eq. 11).
ecosystem_service_kappa(kappa_ref, beta, epsilon) → float¶
κ_s(ε) — labor-time replacement cost per unit of ecosystem service at ε (Eq. 15). Cost contracts with automation — the same ecosystem service costs less to engineer as ε rises.
ecosystem_displacement_surcharge(services, epsilon) → dict¶
E(p,ε) — annualized labor-time cost of natural services displaced by development (Eq. 14).
infrastructure_proximity_premium(assets, parcel_location) → dict¶
I(p,ε) — annualized share of collectively funded infrastructure cost attributed to the parcel (Eq. 16).
Full GUF Calculation¶
base_fee(area_slu, location_value, use_category, epsilon, ...) → dict¶
Base fee component: A(p) × L(p) × U(p,ε) × D(p) × Z(p).
ground_use_fee(area_slu, location_value, use_category, epsilon, ...) → dict¶
Master equation (Eq. 1): GUF(p) = Ψ(ε) × [base + E + I] × Ω, applying the GUF floor.
from hours_eoh.land.guf import ground_use_fee
result = ground_use_fee(
area_slu=3.5,
location_value=0.629,
use_category="residential_primary",
epsilon=0.40,
)
print(result["guf_formula"], result["floor_applied"])
Rate Constraints¶
review_cycle_cap(guf_formula, guf_previous, phi) → float¶
Rate-change cap (Eq. 22): min(guf_formula, guf_previous × (1 + φ)). Default φ = 0.10.
income_linked_subsidy(guf_applied, income_teh, median_income_teh) → float¶
Income adjustment factor σ (Eq. 23–24): leaseholders below 40% of median pay 25% of GUF.
soil_health_credit(area_slu, delta_shi, c_soil) → float¶
Soil Health Index credit for agricultural parcels (Eq. 26): −c_soil × A(p) × ΔSHI.
guf_trust_inflow(guf_revenues, subsidies_absorbed) → dict¶
Net Trust revenue from GUF flows after subsidy absorption.
Ecological Write-Down (NLSA §9)¶
rebuilding_surcharge(services_lost, epsilon, amortization_years) → dict¶
R_b(p,ε) — annualized replacement cost of lost ecosystem services (Eq. 28):
R_b = Σ_s [V_s_lost × κ_s(ε)] / Y_r
from hours_eoh.land.guf import rebuilding_surcharge
result = rebuilding_surcharge(
services_lost=[{"label": "biodiversity", "volume_lost": 5.0, "kappa_ref": 0.35, "beta": 0.7}],
epsilon=0.40,
amortization_years=50,
)
ground_use_fee_writedown(area_slu, ..., services_reset, services_lost, ...) → dict¶
Modified GUF during a write-down event (Eq. 29):
GUF_wd = Ψ × [base + E_reset + I + R_b] × Ω
services_reset=None, services_lost=None→ restoration pathway (E uses recovery target baseline)services_lost=[...]→ abandonment pathway (R_b surcharge added)
from hours_eoh.land.guf import ground_use_fee_writedown
result = ground_use_fee_writedown(
area_slu=3.5,
epsilon=0.40,
services_reset=[{"label": "water", "volume": 0.4, "kappa_ref": 1.65, "beta": 0.8, "retained": 0.3}],
services_lost=None, # restoration pathway
)
eoh_accumulation_warning(unfulfilled_eoh, total_eoh, threshold) → dict¶
Preventive signal (§9.8): ratio = unfulfilled / total. Warning triggers when ratio > threshold (default 0.30).
from hours_eoh.land.guf import eoh_accumulation_warning
result = eoh_accumulation_warning(unfulfilled_eoh=450_000, total_eoh=1_200_000)
# Returns: {ratio, threshold, warning, accelerated_rho_review, ecology_fund_priority}
collective.py — Collective Land Inventory¶
Module: hours_eoh/land/collective.py
Batch GUF calculator for a collective's full land inventory. Handles income-linked subsidies, soil-health credits, and review-cycle caps per parcel, then aggregates via guf_trust_inflow().
Standard parcel schema¶
{
# Required
"area_slu": float, # parcel area (1 SLU = 100 m²)
"location_value": float, # L(p) ∈ [0,1]
"use_category": str, # see USE_CATEGORIES in guf.py
# Optional — forwarded to ground_use_fee()
"ecosystem_services": list[dict],
"infrastructure_assets": list[dict],
"demand_supply_ratio": float,
"zone_adj": float,
"occupancy_fraction": float,
"custom_u_ref": float,
"residential": bool,
# Optional — handled by this module
"parcel_id": str, # label for reporting
"occupant_income": float, # enables income-linked subsidy σ-curve
"guf_previous": float, # enables 10% review-cycle cap
"delta_shi": float, # soil-health credit for agricultural parcels
}
The schema maps directly to geo-data pipeline column names; a GeoJSON or CSV loader needs only to rename columns to these keys.
compute_collective_guf(parcels, epsilon, median_income, pop_coverage_frac) → dict¶
Loops all parcels through ground_use_fee(), applies credits and caps, then aggregates.
from hours_eoh.land.collective import compute_collective_guf
parcels = [
{"area_slu": 2.5, "location_value": 0.75, "use_category": "residential_primary"},
{"area_slu": 4.0, "location_value": 0.85, "use_category": "commercial_retail",
"residential": False},
]
result = compute_collective_guf(parcels, epsilon=0.40, median_income=350.0)
# Returns: {epsilon, parcel_count, guf_gross_revenue, subsidies_absorbed,
# guf_net_inflow, guf_by_parcel, psi, pop_coverage_frac}
guf_net_inflow is the value to pass into trust_management() or simulate_period(guf_net_inflow=...).
make_urban_collective(parcel_count) → list[dict]¶
Synthetic dense-urban archetype: 75% residential_primary · 15% commercial_retail · 5% commercial_office · 5% institutional.
make_rural_collective(parcel_count) → list[dict]¶
Synthetic rural archetype: 50% agricultural_active · 20% agricultural_fallow · 20% residential_primary · 10% conservation.
calibration.py — Rate and Weight Calibration¶
Module: hours_eoh/land/calibration.py
Two tools for fitting GUF revenue to a fiscal target or understanding sensitivity to location-value weighting.
guf_rate_calibration(parcel_inventory, target_guf_levy_ratio, population, epsilon, ...) → dict¶
Finds the use-coefficient multiplier k such that aggregate GUF ≈ target × levy_revenue. Uses a closed-form linear solve on the base-fee component (scalable) vs. ecosystem/infrastructure surcharges (fixed).
from hours_eoh.land.calibration import guf_rate_calibration
from hours_eoh.land.collective import make_urban_collective
result = guf_rate_calibration(
parcel_inventory=make_urban_collective(500),
target_guf_levy_ratio=1.0, # GUF co-equal with levy
population=500.0, # match population to inventory scale
epsilon=0.40,
)
# Returns: {calibrated_multiplier, achieved_ratio, levy_revenue,
# guf_at_calibrated_k, target_guf_levy_ratio, converged}
guf_lvi_weight_sensitivity(parcel_inventory, epsilon, weight_variants) → dict¶
Sweeps Location Value Index weight configurations to show how sensitive aggregate GUF is to sub-index weighting (centrality vs. transit vs. services vs. natural amenity). Requires parcels to have centrality, transit, services, natural_amenity sub-index fields; parcels with only a pre-computed location_value produce zero sensitivity (all variants identical).
from hours_eoh.land.calibration import guf_lvi_weight_sensitivity
result = guf_lvi_weight_sensitivity(parcels_with_lvi_fields, epsilon=0.40)
# Returns: {epsilon, parcel_count, variants (list), sensitivity_range, relative_sensitivity}
Ships five canonical weight variants by default; pass weight_variants=[...] to supply your own.