Source code for better_lbnl_os.models.building

"""Building domain model."""

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from better_lbnl_os.models.utility_bills import UtilityBillData
from pydantic import BaseModel, Field, field_validator

from better_lbnl_os.constants import BuildingSpaceType, normalize_space_type


[docs] class BuildingData(BaseModel): """Domain model for building information with business logic methods.""" name: str = Field(..., description="Building name") floor_area: float = Field(..., gt=0, description="Floor area in square feet") space_type: str = Field(..., description="Building space type category (display label)") location: str = Field(..., description="Building location") country_code: str = Field(default="US", description="Country code") climate_zone: str | None = Field(None, description="ASHRAE climate zone")
[docs] @field_validator("space_type") @classmethod def validate_space_type(cls, v: str) -> str: """Normalize and validate the space type against known choices.""" return normalize_space_type(v)
[docs] def validate_bills(self, bills: list[UtilityBillData]) -> list[str]: """Validate utility bills for this building. Returns: list of validation error messages """ errors = [] if not bills: errors.append("No utility bills provided") return errors # Check for gaps in billing periods sorted_bills = sorted(bills, key=lambda b: b.start_date) for i in range(len(sorted_bills) - 1): gap_days = (sorted_bills[i + 1].start_date - sorted_bills[i].end_date).days if gap_days > 1: errors.append( f"Gap of {gap_days} days between bills ending {sorted_bills[i].end_date} " f"and starting {sorted_bills[i + 1].start_date}" ) # Check for reasonable consumption values for bill in bills: daily_avg = bill.calculate_daily_average() if daily_avg <= 0: errors.append(f"Non-positive consumption for bill starting {bill.start_date}") elif daily_avg > 1000 * self.floor_area: # Sanity check errors.append(f"Unusually high consumption for bill starting {bill.start_date}") return errors
[docs] def get_benchmark_category(self) -> str: """Determine benchmark category based on space type.""" from better_lbnl_os.constants import space_type_to_benchmark_category category = space_type_to_benchmark_category(self.space_type) return category.benchmark_id
[docs] def get_space_type_code(self) -> str: """Return the enum code (name) for the current space type (e.g., "Office" -> "OFFICE").""" for st in BuildingSpaceType: if self.space_type == st.value: return st.name return "OTHER"
__all__ = ["BuildingData"]