# generated by datamodel-codegen:
#   filename:  OpenBES.schema.json
#   timestamp: 2026-03-02T10:49:57+00:00

from __future__ import annotations
from enum import StrEnum
from pydantic import BaseModel, ConfigDict, Field, RootModel


class ENERGYSOURCES(StrEnum):
    Electricity = "Electricity"
    Diesel = "Diesel"
    LPG = "LPG"
    Natural_gas = "Natural gas"
    Biomass = "Biomass"
    Pellets = "Pellets"


class COOLINGSYSTEMTYPES(StrEnum):
    Heat_pump = "Heat pump"


class HEATINGSYSTEMTYPES(StrEnum):
    Electric_heating__radiators_ = "Electric heating (radiators)"
    Electric_boiler = "Electric boiler"
    Conventional_boiler = "Conventional boiler"
    Low_temperature_boiler = "Low temperature boiler"
    Condensing_boiler = "Condensing boiler"
    Biomass_boiler = "Biomass boiler"
    Heat_pump = "Heat pump"


class METEOROLOGICALFILE(StrEnum):
    openbes___USA_Denver_725650TYCST_epw = "openbes://USA_Denver_725650TYCST.epw"
    openbes___UK_Oxford_GBR_ENG_RAF_Benson_036580_TMYx_2007_2021_epw = (
        "openbes://UK_Oxford_GBR_ENG_RAF.Benson.036580_TMYx.2007-2021.epw"
    )
    openbes___SPAIN_Sevilla_083910_SWEC_epw = "openbes://SPAIN_Sevilla.083910_SWEC.epw"
    openbes___SPAIN_Madrid_082210_SWEC_epw = "openbes://SPAIN_Madrid.082210_SWEC.epw"


class TERRAINCLASS(StrEnum):
    Open_terrain = "Open terrain"
    Country = "Country"
    Urban = "Urban"


class SUPPORTEDCOUNTRIES(StrEnum):
    Spain = "Spain"
    USA = "USA"
    France = "France"
    UK = "UK"
    Other = "Other"


class WindowCounts(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    front: list[int] | None = Field(
        default=None,
        description="Array of window counts per floor on the front side of the building.",
    )
    back: list[int] | None = Field(
        default=None,
        description="Array of window counts per floor on the back side of the building.",
    )
    left: list[int] | None = Field(
        default=None,
        description="Array of window counts per floor on the left side of the building.",
    )
    right: list[int] | None = Field(
        default=None,
        description="Array of window counts per floor on the right side of the building.",
    )


class Building(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    area: str | None = Field(
        default=None, description="Street/region the building is in."
    )
    height: float | None = Field(
        default=None, description="Height of the building in m."
    )
    length: float = Field(..., description="Length of the building in m.")
    width: float = Field(..., description="Width of the building in m.")
    name: str | None = Field(default=None, description="Name of the building")
    type: str | None = Field(
        default=None, description="Type of building (e.g. Office, School)."
    )
    floor_count: int | None = Field(
        default=1, description="Number of floors in the building."
    )
    floor_to_ceiling_height: float | None = Field(
        default=2.7, description="Floor to ceiling height in m."
    )
    slab_thickness: float | None = Field(
        default=0.2, description="Slab thickness in m."
    )
    orientation_angle: float | None = Field(
        default=0.0,
        description="Orientation angle of the building in degrees clockwise from North=0.",
    )
    roof_angle: float | None = Field(
        default=0.0, description="Roof angle in degrees from horizontal."
    )
    solar_external_shading_summer: float | None = Field(
        default=0.0,
        description="External solar shading factor for summer (Summer = air temp > 22C).",
    )
    solar_external_shading_winter: float | None = Field(
        default=0.0,
        description="External solar shading factor for winter (Winter = air temp <= 22C).",
    )
    terrain_class: TERRAINCLASS | None = Field(
        default=None, description="Terrain class around the building."
    )
    thermal_bridge_facade_ground: bool | None = Field(
        default=None,
        description="Whether to model the thermal bridge factor for facade to ground junctions.",
    )
    thermal_bridge_facade_intermediate: bool | None = Field(
        default=None,
        description="Whether to model the thermal bridge factor for facade to intermediate floor junctions.",
    )
    thermal_bridge_facade_roof: bool | None = Field(
        default=None,
        description="Whether to model the thermal bridge factor for facade to roof junctions.",
    )
    thermal_bridge_shading: bool | None = Field(
        default=None,
        description="Whether to model the thermal bridge factor for shading devices.",
    )
    thermal_bridge_window: bool | None = Field(
        default=None,
        description="Whether to model the thermal bridge factor for windows.",
    )
    uvalue_facade: float | None = Field(
        default=0.0, description="U-value of the facade in W/m2K."
    )
    uvalue_roof: float | None = Field(
        default=0.0, description="U-value of the roof in W/m2K."
    )
    uvalue_window: float | None = Field(
        default=0.0, description="U-value of the windows in W/m2K."
    )
    uvalue_floor: float | None = Field(
        default=0.0, description="U-value of the floor in W/m2K."
    )
    window_frame_factor: float | None = Field(
        default=0.0, description="Window frame factor (fraction)."
    )
    window_gvalue: float | None = Field(
        default=0.0, description="Window g-value (fraction)."
    )
    window_height: float | None = Field(default=0.0, description="Window height in m.")
    window_length: float | None = Field(default=0.0, description="Window length in m.")
    window_counts: WindowCounts | None = Field(
        default=None,
        description="A map of building side to array of window counts per floor.",
    )


class Duration(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    start: int = Field(..., description="Start hour (1-24).")
    end: int = Field(..., description="End hour (1-24).")


class Zone(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    name: str = Field(..., description="Zone name")
    areas: list[float] = Field(..., description="Zone area for each floor in m2")
    conditioned: bool | None = Field(
        default=True, description="Whether this zone uses HVAC."
    )
    active_hours: Duration | None = Field(
        default=None, description="Times the zone is occupied each day."
    )


class ZoneSimultaneity(RootModel[dict[str, float]]):
    root: dict[str, float]


class OccupationSchedule(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    January: bool | None = False
    February: bool | None = False
    March: bool | None = False
    April: bool | None = False
    May: bool | None = False
    June: bool | None = False
    July: bool | None = False
    August: bool | None = False
    September: bool | None = False
    October: bool | None = False
    November: bool | None = False
    December: bool | None = False
    Monday: bool | None = False
    Tuesday: bool | None = False
    Wednesday: bool | None = False
    Thursday: bool | None = False
    Friday: bool | None = False
    Saturday: bool | None = False
    Sunday: bool | None = False


class Range(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    min: float = Field(..., description="Minimum value of the range.")
    max: float = Field(..., description="Maximum value of the range.")


class Consumption(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    January: float | None = 0.0
    February: float | None = 0.0
    March: float | None = 0.0
    April: float | None = 0.0
    May: float | None = 0.0
    June: float | None = 0.0
    July: float | None = 0.0
    August: float | None = 0.0
    September: float | None = 0.0
    October: float | None = 0.0
    November: float | None = 0.0
    December: float | None = 0.0


class HeatingSystem(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    type: HEATINGSYSTEMTYPES | None = None
    energy_source: ENERGYSOURCES = Field(
        ..., description="Energy source for this heating system."
    )
    efficiency_cop: float = Field(..., description="COP or efficiency (if applicable).")
    min_demand: float | None = Field(
        default=0.0, description="Minimum demand threshold."
    )
    nominal_capacity: float = Field(..., description="Nominal capacity.")
    count: int | None = Field(default=1, description="Number of identical units.")
    active_hours: Duration = Field(..., description="Times the system is on each day.")
    simultaneity: ZoneSimultaneity | None = None


class CoolingSystem(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    type: COOLINGSYSTEMTYPES | None = None
    energy_source: ENERGYSOURCES
    efficiency_ratio: float = Field(
        ..., description="EER/COP or energy efficiency ratio."
    )
    min_demand: float | None = Field(default=None, description="Minimum demand.")
    nominal_capacity: float = Field(..., description="Nominal capacity.")
    sensible_nominal_capacity: float = Field(..., description="Sensible capacity.")
    count: int | None = Field(default=1, description="Number of identical units.")
    active_hours: Duration = Field(..., description="Times the system is on each day.")
    simultaneity: ZoneSimultaneity | None = None


class VentilationSystem(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    type: float | None = Field(default=None, description="Optional numeric type code.")
    energy_source: ENERGYSOURCES
    airflow: float = Field(..., description="Airflow (m3/s).")
    heat_recovery_efficiency: float | None = Field(
        default=None, description="Heat recovery efficiency."
    )
    rated_input_power: float | None = Field(
        default=None, description="Rated input power."
    )
    ventilated_area: float | None = Field(
        default=1.0, description="Proportion of area ventilated."
    )
    active_hours: Duration | None = Field(
        default_factory=lambda: Duration.model_validate({"start": 1, "end": 24}),
        description="Times the system is on each day.",
    )


class Ballast(StrEnum):
    Electronic_ballast = "Electronic ballast"
    Ferromagnetic_ballast = "Ferromagnetic ballast"


class LightingSystemTech(StrEnum):
    Tubular_fluorescent_T8 = "Tubular fluorescent T8"
    Tubular_fluorescent_T5 = "Tubular fluorescent T5"
    Compact_fluorescent = "Compact fluorescent"
    Incandescent = "Incandescent"
    Halogen = "Halogen"
    Mercury_vapor = "Mercury vapor"
    Sodium_vapour = "Sodium vapour"
    Metal_halide = "Metal halide"
    Induction = "Induction"
    LED = "LED"


class HotWaterSystem(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    demand: Consumption = Field(..., description="Hot water demand (litres).")
    reference_temperature: float = Field(..., description="Reference temperature (°C).")
    supply_temperature: float = Field(..., description="Supply temperature (°C).")
    energy_source: ENERGYSOURCES = Field(
        ..., description="Energy source for this hot water system."
    )
    efficiency_cop: float = Field(
        ..., description="Efficiency of the hot water system."
    )
    nominal_capacity: float = Field(
        ..., description="Nominal capacity of the hot water system."
    )
    type: float | None = Field(default=None, description="[UNUSED]")


class Courtyard(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    length: float = Field(..., description="Courtyard length (m).")
    width: float = Field(..., description="Courtyard width (m).")
    count: int | None = Field(
        default=1, description="How many courtyards of this geometry exist."
    )


class OpenCourtyard(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    depth: float = Field(..., description="Courtyard depth (m).")
    count: int | None = Field(
        default=1, description="How many courtyards of this geometry exist."
    )


class OpenCourtyardMap(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    front: OpenCourtyard | None = None
    back: OpenCourtyard | None = None
    left: OpenCourtyard | None = None
    right: OpenCourtyard | None = None


class HeatCapacity(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    Am: float = Field(..., description="Mass factor??????")
    Cm: float = Field(..., description="Capacitance??????")


class OpenBESParameters(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    altitude: float | None = Field(
        default=None,
        description="Altitude (m). [UNUSED: the EPW file's 'altitude' is used instead]",
    )
    include_appliances: bool | None = Field(
        default=True, description="Model appliance consumption."
    )
    cooling_load_factor: float | None = Field(
        default=1.0, description="Multiplier applied to cooling loads."
    )
    density_of_air: float | None = Field(
        default=1.211, description="Air density (kg/m3)."
    )
    facade_absorption_coefficient: float | None = Field(
        default=0.6, description="Facade solar absorption coefficient."
    )
    facade_correction_factor: float | None = Field(
        default=1.0, description="Facade correction factor."
    )
    facade_emissivity: float | None = Field(
        default=0.9, description="Facade emissivity value."
    )
    floor_correction_factor: float | None = Field(
        default=1.0, description="Floor correction factor."
    )
    heat_capacity_correction_factor: float | None = Field(
        default=1.0, description="Heat capacity correction factor."
    )
    heat_capacity_joule: float | None = Field(
        default=None, description="Heat capacity of air in J/kgK."
    )
    heating_load_factor: float | None = Field(
        default=1.0, description="Multiplier applied to heating loads."
    )
    infiltration_correction_factor: float | None = Field(
        default=1.0, description="Correction factor for infiltration."
    )
    leakage_air_flow_dependent: float | None = Field(
        default=0.0001,
        description="Leakage air flow rate dependent on other factors (m3/s).",
    )
    include_lighting: bool | None = Field(
        default=True, description="Whether lighting is modelled."
    )
    nia_gba_ratio: float | None = Field(
        default=0.95, description="Net internal area / gross building area fraction."
    )
    include_occupancy: bool | None = Field(
        default=True, description="Whether occupancy schedules are modelled."
    )
    output_csv_precision: int | None = Field(
        default=2, description="Number of decimal places in CSV output."
    )
    pressure_of_air: float | None = Field(
        default=None, description="Air pressure. [UNUSED]"
    )
    roof_absorption_coefficient: float | None = Field(
        default=0.8, description="Roof solar absorption coefficient."
    )
    roof_correction_factor: float | None = Field(
        default=1.0, description="Roof correction factor."
    )
    roof_emissivity: float | None = Field(default=0.9, description="Roof emissivity.")
    shading_correction_factor: float | None = Field(
        default=1.0, description="Shading correction factor."
    )
    specific_heat_of_air: float | None = Field(
        default=1.015, description="Specific heat of air."
    )
    view_factor_to_sky_facade: float | None = Field(
        default=0.5, description="View factor to sky from facade."
    )
    view_factor_to_sky_roof: float | None = Field(
        default=1.0, description="View factor to sky from roof."
    )
    window_correction_factor: float | None = Field(
        default=1.0, description="Window correction factor."
    )
    window_angular_correction_factors: list[float] | None = Field(
        default=[1.0, -0.189, 0.644, -0.596, 0.0],
        description="Angular correction factors for windows.",
        max_length=5,
        min_length=5,
    )


class LightingControl(StrEnum):
    Manual = "Manual"
    Automatic = "Automatic"


class SpaceThermalDemandResult(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    demand_total: float | None = Field(
        default=None, description="Annual energy required for heating/cooling in kWh."
    )
    demand_scaled: float | None = Field(
        default=None,
        description="Annual energy required for heating/cooling in kWh/m2.",
    )
    demand_on_all_year: float | None = Field(
        default=None,
        description="Annual energy demanded in kWh/m2 if the system is on all year round.",
    )
    load_csv: str | None = Field(
        default=None, description="Load in kWh/m2 by calendar month."
    )
    load_duraction_csv: str | None = Field(
        default=None, description="Load in kW by annual percentile."
    )


class VentilationSystemResult(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    energy_demand: float | None = Field(
        default=None, description="Energy demanded in kWh/m2"
    )
    peak_load: float | None = Field(
        default=None, description="Maximum hourly energy required in kW."
    )
    sfp: float | None = Field(
        default=None, description="Specific fan power in kW/m3/s."
    )
    mechanical_ventilation_rate: float | None = Field(
        default=None,
        description="Mechanical ventilation rate in m3/h per m2. Recommended: 3 m3/h m2.",
    )
    ventilation_rate: float | None = Field(
        default=None,
        description="Ventilation rate in l/s m2. Reference: 0.83l/s m2 or 12.5l/s per person.",
    )
    ach: float | None = Field(default=None, description="Air changes per hour.")


class ModelValidation(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    energy_use_csv: str | None = Field(
        default=None,
        description="Energy used for each calendar month in kWh. Compare to user-supplied values in `OpenBESSpecification.*_consumption`.",
    )
    nmbe: float | None = Field(default=None, description="Normalised mean bias error.")
    cv_rmse: float | None = Field(
        default=None,
        description="Coefficient of variation of the root mean square deviation.",
    )
    r2: float | None = Field(default=None, description="R-squared model fit.")


class Month(StrEnum):
    January = "January"
    February = "February"
    March = "March"
    April = "April"
    May = "May"
    June = "June"
    July = "July"
    August = "August"
    September = "September"
    October = "October"
    November = "November"
    December = "December"


class HourPeak(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    value: float | None = Field(default=None, description="The statistic in question.")
    month: Month | None = None
    day: float | None = Field(default=None, description="Day of the month.")
    hour: float | None = Field(default=None, description="Hour of the day.")


class LocationSimulationOutput(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    altitude: float | None = Field(
        default=None, description="Altitude as read from the weather file."
    )
    solstice_ghr_csv: str | None = Field(
        default=None,
        description="Global Horizontal Radiation (Wh/m2) for the longest and shortest days of the year (21 Dec, 21 Jun).",
    )
    max_outdoor_temperature: HourPeak | None = None
    min_outdoor_temperature: HourPeak | None = None
    climate_quantiles_csv: str | None = Field(
        default=None,
        description="Outdoor air temperature percentiles from climate data.",
    )
    degree_days_csv: str | None = Field(
        default=None, description="Heating/cooling degree days by month."
    )
    annual_incident_solar_radiation_csv: str | None = Field(
        default=None,
        description="Annual incident solar radiation in kWh/m2 for each 1/8th compass point.",
    )


class GeometrySimulationOutput(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    gross_building_area: float | None = Field(
        default=None, description="Total building area in m2."
    )
    conditioned_floor_area: float | None = Field(
        default=None,
        description="Floor area subject to HVAC systems. Used as the divisor in most /m2 statistics.",
    )
    indoor_air_volume: float | None = Field(
        default=None,
        description="Volume of air in the building's conditioned areas in m3.",
    )
    indoor_air_heat_capacity: float | None = Field(
        default=None, description="Indoor air heat capacity in kJ/K"
    )
    building_geometry_csv: str | None = Field(
        default=None,
        description="Building area by floor and category in m2, and window-to-wall ratio (unitless).",
    )
    building_geometry_orientation_csv: str | None = Field(
        default=None,
        description="Building facade, window, and roof area in m2 by category and orientation.",
    )


class ClimateSimulationOutput(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    external_internal_temperature_csv: str | None = Field(
        default=None,
        description="Internal (HVAC) and external temperatures for each hour of the year.",
    )
    max_indoor_temperature: HourPeak | None = None
    min_indoor_temperature: HourPeak | None = None
    mean_indoor_temperature: float | None = Field(
        default=None, description="Mean indoor temperature over the year."
    )
    discomfort_hours_percent: float | None = Field(
        default=None,
        description="The number of hours when the building is occupied and it would be outside 18-26C if HVAC were not operating.",
    )
    discomfort_hours_percent_summer: float | None = Field(
        default=None,
        description="The number of hours when the building is occupied and it would be outside 18-26C if HVAC were not operating during June, July, and August.",
    )
    heat_exchange_breakdown_csv: str | None = Field(
        default=None,
        description="The heat loss/gain in kWh/m2 for each month, broken down by transmission category.",
    )
    space_thermal_demand_csv: str | None = Field(
        default=None,
        description="The heating and cooling energy requirement in kWh/m2 for each month of the year based on heat, mass balance, and occupancy.",
    )
    infiltration_ach: float | None = Field(
        default=None, description="Air changes/hour from air infiltration."
    )
    natural_ach: float | None = Field(
        default=None, description="Air changes/hour from natural ventilation."
    )
    heating_demand: SpaceThermalDemandResult | None = None
    cooling_demand: SpaceThermalDemandResult | None = None
    running_average_outside_temp_csv: str | None = Field(
        default=None,
        description="The 7-day weighted running mean of the outside temperature for each hour of the year. The first 6 days copy the value from day 7.",
    )
    overheating_limits_csv: str | None = Field(
        default=None,
        description="Category I, II, and III overheating limits in °C at different outdoor temperatures.",
    )
    solar_heat_gains_csv: str | None = Field(
        default=None,
        description="Solar heat gains in kWh by orientation and category. For kWh/m2 divide by relevant `building_facade_csv` area.",
    )
    window_transmissivity_coefficient_csv: str | None = Field(
        default=None,
        description="Annual transmissivity coefficient of windows by orientation.",
    )


class VentilationSimulationOutput(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    ventilation_systems: list[VentilationSystemResult] | None = None


class LightingSimulationOutput(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    lighting_demand: float | None = Field(
        default=None, description="Energy demand for lighting in kWh/m2."
    )
    lighting_peak_load: float | None = Field(
        default=None, description="Peak lighting load in kW."
    )
    lighting_load_ratio: float | None = Field(
        default=None, description="Lighting energy required in W/m2."
    )


class HotWaterSimulationOutput(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    hot_water_demand: float | None = Field(
        default=None, description="Energy demand for water heating in kWh/m2."
    )


class BuildingEnergySimulationOutput(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    other_energy_use_electricity: float | None = Field(
        default=None, description="Miscellaneous electricity use in kWh/m2."
    )
    other_energy_use_gas: float | None = Field(
        default=None, description="Miscellaneous natural gas usage in kWh/m2."
    )
    on_site_electricity_generated: float | None = Field(
        default=None, description="Renewable electricity generated on site in kWh/m2."
    )
    on_site_electricity_used: float | None = Field(
        default=None,
        description="Renewable electricity generated and consumed on site in kWh/m2.",
    )
    on_site_electricity_fraction: float | None = Field(
        default=None,
        description="Renewable energy consumption / Final energy consumption",
    )
    all_renewable_fraction: float | None = Field(
        default=None,
        description="Fraction of final energy consumption drawn from renewable energy sources including both on- and off-site renewable energy sources.",
    )
    final_energy_consumption: float | None = Field(
        default=None, description="Final energy consumption in kWh/m2."
    )
    primary_energy_consumption: float | None = Field(
        default=None, description="Primary energy consumption in kWh/m2."
    )
    non_renewable_primary_energy_consumption: float | None = Field(
        default=None, description="Non-renewable primary energy consumption in kWh/m2."
    )
    co2_equivalent_emissions: float | None = Field(
        default=None, description="CO2 equivalent emissions in kg CO2 eq/m2."
    )
    final_energy_consumption_csv: str | None = Field(
        default=None,
        description="Final energy consumption in kWh for each energy use category. Divide by `conditioned_floor_area` for kWh/m2.",
    )
    electricity_validation: ModelValidation | None = None
    gas_validation: ModelValidation | None = None


class OpenBESMetaData(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    version: str = Field(..., description="Version of Open BES used.")
    timestamp: str | None = Field(
        default=None, description="Timestamp of when the simulation was run."
    )
    EPW_file_checksum: str | None = Field(
        default=None, description="MD5 Checksum of the EPW file used in the simulation."
    )


class LightingSystem(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    tech: LightingSystemTech
    ballast: Ballast | None = Field(default=None, description="Ballast type.")
    lamp_number: int = Field(..., description="Number of lamps.")
    lamp_power: float = Field(..., description="Lamp power (W).")
    luminary_number: int = Field(..., description="Number of luminaires.")
    name: str | None = Field(
        default=None, description="Optional human label for the system."
    )
    active_hours: Duration = Field(
        ..., description="Times the lighting is on each day."
    )
    count: int | None = Field(
        default=1, description="Count of similar lighting systems aggregated together."
    )
    simultaneity_factor: float = Field(
        ..., description="Simultaneity factor (fraction)."
    )


class OpenBESSpecification(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    parameters: OpenBESParameters | None = Field(
        default=None,
        description="Collapsed parameters object. Use this to hold heating/cooling/ventilation/lighting/courtyards arrays.",
    )
    building: Building | None = Field(
        default=None, description="Building general information."
    )
    zones: list[Zone] | None = Field(default=None, description="Zones in the building.")
    occupation_schedule: OccupationSchedule | None = Field(
        default=None, description="Occupation schedule for the building."
    )
    heating_systems: list[HeatingSystem] | None = Field(
        default=None, description="Heating systems in the building."
    )
    cooling_systems: list[CoolingSystem] | None = Field(
        default=None, description="Cooling systems in the building."
    )
    ventilation_systems: list[VentilationSystem] | None = Field(
        default=None, description="Ventilation systems in the building."
    )
    lighting_systems: list[LightingSystem] | None = Field(
        default=None, description="Lighting systems in the building."
    )
    hot_water_systems: list[HotWaterSystem] | None = Field(
        default=None, description="Hot water systems in the building."
    )
    courtyards: list[Courtyard] | None = Field(
        default=None, description="Courtyards in the building."
    )
    open_courtyards: OpenCourtyardMap | None = Field(
        default=None, description="Open courtyards on each side of the building."
    )
    appliances_load: float | None = Field(
        default=0.0, description="Load from appliances (Wh/m2)."
    )
    biomass_consumption: Consumption | None = Field(
        default=None, description="Amount of biomass consumed by the building. [UNUSED]"
    )
    biomass_pellets_consumption: Consumption | None = Field(
        default=None,
        description="Amount of biomass pellets consumed by the building. [UNUSED]",
    )
    building_standby_electricity_consumption: Consumption | None = Field(
        default=None,
        description="Electrical standby load for the building.",
    )
    diesel_consumption: Consumption | None = Field(
        default=None, description="Amount of diesel consumed by the building. [UNUSED]"
    )
    electricity_consumption: Consumption | None = Field(
        default=None,
        description="Amount of electricity consumed by the building. [UNUSED]",
    )
    other_electricity_consumption: Consumption | None = Field(
        default=None,
        description="Amount of other electricity consumed by the building. [UNUSED]",
    )
    energy_generated: Consumption | None = Field(
        default=None, description="Amount of energy generated by the building. [UNUSED]"
    )
    energy_used: Consumption | None = Field(
        default=None, description="Amount of energy used by the building. [UNUSED]"
    )
    other_gas_consumption: Consumption | None = Field(
        default=None,
        description="Amount of other gas consumed by the building. [UNUSED]",
    )
    LPG_consumption: Consumption | None = Field(
        default=None, description="Amount of LPG consumed by the building. [UNUSED]"
    )
    natural_gas_consumption: Consumption | None = Field(
        default=None,
        description="Amount of natural gas consumed by the building. [UNUSED]",
    )
    country: SUPPORTEDCOUNTRIES | None = None
    heat_capacity: HeatCapacity | None = Field(
        default=None, description="Heat capacity properties of the building."
    )
    holiday: bool | None = Field(
        default=None, description="Whether the building is open on public holidays."
    )
    leakage_air_flow: float | None = Field(
        default=None, description="Leakage air flow rate (m3/s)."
    )
    leakage_air_flow_independent: float | None = Field(
        default=0.0,
        description="Leakage air flow rate independent of other factors (m3/s).",
    )
    lighting_control: LightingControl | None = Field(
        default="Automatic", description="Type of lighting control system."
    )
    lighting_active_hours: Duration | None = Field(
        default=None, description="Daily active hours for lighting."
    )
    lighting_simultaneity_factor: float | None = Field(
        default=0.2, description="Simultaneity factor for lighting."
    )
    location: str | None = Field(
        default=None,
        description="Location identifier or name (used to map to meteorological file).",
    )
    max_building_occupation: int | None = Field(
        default=None,
        description="Maximum occupancy of the building in number of people.",
    )
    meteorological_file_path: METEOROLOGICALFILE | None = Field(
        default=None, description="Meteorological filename or identifier."
    )
    natural_ventilation_night: float | None = Field(
        default=None, description="Night-time natural ventilation rate."
    )
    setpoint_temperature_day: Range | None = Field(
        default_factory=lambda: Range.model_validate({"min": 20.0, "max": 20.0}),
        description="Daytime temperature setpoint range.",
    )
    setpoint_temperature_night: Range | None = Field(
        default_factory=lambda: Range.model_validate({"min": 20.0, "max": 20.0}),
        description="Night-time temperature setpoint range.",
    )
    typical_occupation: int | None = Field(
        default=None,
        description="Typical occupancy of the building in number of people.",
    )


class ThermalSystemResult(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    conditioned_area: float | None = Field(
        default=None,
        description="Conditioned equivalent area in m2 (conditioned area adjusted for simultaneity).",
    )
    energy_demand: float | None = Field(
        default=None,
        description="Energy demanded in kWh/m2 when specific zonal occupancy is simulated.",
    )
    system_usage: float | None = Field(
        default=None,
        description="Energy demand divided by conditioned area (no simultaneity adjustment) in kWh/m2",
    )
    peak_load: HourPeak | None = None
    peak_capacity: float | None = Field(
        default=None, description="Maximum hourly energy consumed in kW."
    )
    peak_ratio: float | None = Field(
        default=None,
        description="Sensible peak capacity for cooling systems. For heating systems, peak capacity/load at 99.6th percentile.",
    )


class HeatingSimulationOutput(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    heating_systems: list[ThermalSystemResult] | None = None


class CoolingSimulationOutput(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    cooling_systems: list[ThermalSystemResult] | None = None


class OpenBESOutput(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    location_simulation_output: LocationSimulationOutput | None = None
    geometry_simulation_output: GeometrySimulationOutput | None = None
    climate_simulation_output: ClimateSimulationOutput | None = None
    ventilation_simulation_output: VentilationSimulationOutput | None = None
    heating_simulation_output: HeatingSimulationOutput | None = None
    cooling_simulation_output: CoolingSimulationOutput | None = None
    lighting_simulation_output: LightingSimulationOutput | None = None
    hot_water_simulation_output: HotWaterSimulationOutput | None = None
    building_energy_simulation_output: BuildingEnergySimulationOutput | None = None


class OpenBESCase(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    inputs: OpenBESSpecification
    outputs: OpenBESOutput | None = None
    log: list[str] | None = Field(
        default=None, description="Log output from the simulation run."
    )
    meta: OpenBESMetaData | None = None
