diff --git a/infrastructure/postgres/property_baseline_performance_table.py b/infrastructure/postgres/property_baseline_performance_table.py index 03906c0c..89019478 100644 --- a/infrastructure/postgres/property_baseline_performance_table.py +++ b/infrastructure/postgres/property_baseline_performance_table.py @@ -72,26 +72,31 @@ class PropertyBaselinePerformanceModel(SQLModel, table=True): space_heating_kwh: float water_heating_kwh: float + # The Fuel Rates snapshot period the bill was priced against (FE-owned column, + # nullable). Not yet threaded through Bill Derivation, so left None for now. + fuel_rates_period: Optional[str] = Field(default=None) + # Bill Derivation block (ADR-0014 §6). Nullable: all None when no calculator - # ran (stub path). The ``bill_`` prefix avoids clashing with the + # ran (stub path). Column names are unprefixed to mirror the FE-owned table — + # the per-section ``heating_kwh`` / ``hot_water_kwh`` do not clash with the # recorded-demand ``space_heating_kwh`` / ``water_heating_kwh`` above. - bill_heating_kwh: Optional[float] = Field(default=None) - bill_heating_cost_gbp: Optional[float] = Field(default=None) - bill_hot_water_kwh: Optional[float] = Field(default=None) - bill_hot_water_cost_gbp: Optional[float] = Field(default=None) - bill_lighting_kwh: Optional[float] = Field(default=None) - bill_lighting_cost_gbp: Optional[float] = Field(default=None) - bill_appliances_kwh: Optional[float] = Field(default=None) - bill_appliances_cost_gbp: Optional[float] = Field(default=None) - bill_cooking_kwh: Optional[float] = Field(default=None) - bill_cooking_cost_gbp: Optional[float] = Field(default=None) - bill_pumps_fans_kwh: Optional[float] = Field(default=None) - bill_pumps_fans_cost_gbp: Optional[float] = Field(default=None) - bill_cooling_kwh: Optional[float] = Field(default=None) - bill_cooling_cost_gbp: Optional[float] = Field(default=None) - bill_standing_charges_gbp: Optional[float] = Field(default=None) - bill_seg_credit_gbp: Optional[float] = Field(default=None) - bill_total_annual_bill_gbp: Optional[float] = Field(default=None) + heating_kwh: Optional[float] = Field(default=None) + heating_cost_gbp: Optional[float] = Field(default=None) + hot_water_kwh: Optional[float] = Field(default=None) + hot_water_cost_gbp: Optional[float] = Field(default=None) + lighting_kwh: Optional[float] = Field(default=None) + lighting_cost_gbp: Optional[float] = Field(default=None) + appliances_kwh: Optional[float] = Field(default=None) + appliances_cost_gbp: Optional[float] = Field(default=None) + cooking_kwh: Optional[float] = Field(default=None) + cooking_cost_gbp: Optional[float] = Field(default=None) + pumps_fans_kwh: Optional[float] = Field(default=None) + pumps_fans_cost_gbp: Optional[float] = Field(default=None) + cooling_kwh: Optional[float] = Field(default=None) + cooling_cost_gbp: Optional[float] = Field(default=None) + standing_charges_gbp: Optional[float] = Field(default=None) + seg_credit_gbp: Optional[float] = Field(default=None) + total_annual_bill_gbp: Optional[float] = Field(default=None) @classmethod def from_domain( @@ -122,15 +127,15 @@ class PropertyBaselinePerformanceModel(SQLModel, table=True): return for section, stem in _SECTION_COLUMN_STEM.items(): cost = bill.sections.get(section) - setattr(self, f"bill_{stem}_kwh", cost.kwh if cost is not None else None) + setattr(self, f"{stem}_kwh", cost.kwh if cost is not None else None) setattr( self, - f"bill_{stem}_cost_gbp", + f"{stem}_cost_gbp", cost.cost_gbp if cost is not None else None, ) - self.bill_standing_charges_gbp = bill.standing_charges_gbp - self.bill_seg_credit_gbp = bill.seg_credit_gbp - self.bill_total_annual_bill_gbp = bill.total_gbp + self.standing_charges_gbp = bill.standing_charges_gbp + self.seg_credit_gbp = bill.seg_credit_gbp + self.total_annual_bill_gbp = bill.total_gbp def to_domain(self) -> PropertyBaselinePerformance: return PropertyBaselinePerformance( @@ -157,18 +162,18 @@ class PropertyBaselinePerformanceModel(SQLModel, table=True): not-None discriminator: a persisted bill always sets it, so its absence means no calculator ran and the bill was None. A section is rebuilt only when its kWh column is not None (paired with its cost).""" - if self.bill_total_annual_bill_gbp is None: + if self.total_annual_bill_gbp is None: return None sections: dict[BillSection, BillSectionCost] = {} for section, stem in _SECTION_COLUMN_STEM.items(): - kwh = cast(Optional[float], getattr(self, f"bill_{stem}_kwh")) + kwh = cast(Optional[float], getattr(self, f"{stem}_kwh")) if kwh is None: continue - cost_gbp = cast(float, getattr(self, f"bill_{stem}_cost_gbp")) + cost_gbp = cast(float, getattr(self, f"{stem}_cost_gbp")) sections[section] = BillSectionCost(kwh=kwh, cost_gbp=cost_gbp) return Bill( sections=sections, - standing_charges_gbp=cast(float, self.bill_standing_charges_gbp), - seg_credit_gbp=cast(float, self.bill_seg_credit_gbp), - total_gbp=self.bill_total_annual_bill_gbp, + standing_charges_gbp=cast(float, self.standing_charges_gbp), + seg_credit_gbp=cast(float, self.seg_credit_gbp), + total_gbp=self.total_annual_bill_gbp, )