Files
enok/src/modell/mod.rs
T

599 lines
20 KiB
Rust
Raw Normal View History

//! Inndatamodell for en bygning (`bygning.toml`). Alt en energirådgiver kan variere ligger her;
//! det som utelates hentes fra normerte verdier for kategorien og merkes i rapporten.
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Deserialize, Serialize, Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum Kategori {
Smaahus,
Boligblokk,
Barnehage,
Kontorbygg,
Skolebygg,
Universitet,
Sykehus,
Sykehjem,
Hotell,
Idrettsbygg,
Forretningsbygg,
Kulturbygg,
Industribygg,
}
impl Kategori {
pub fn nokkel(&self) -> &'static str {
match self {
Kategori::Smaahus => "smaahus",
Kategori::Boligblokk => "boligblokk",
Kategori::Barnehage => "barnehage",
Kategori::Kontorbygg => "kontorbygg",
Kategori::Skolebygg => "skolebygg",
Kategori::Universitet => "universitet",
Kategori::Sykehus => "sykehus",
Kategori::Sykehjem => "sykehjem",
Kategori::Hotell => "hotell",
Kategori::Idrettsbygg => "idrettsbygg",
Kategori::Forretningsbygg => "forretningsbygg",
Kategori::Kulturbygg => "kulturbygg",
Kategori::Industribygg => "industribygg",
}
}
pub fn er_bolig(&self) -> bool {
matches!(self, Kategori::Smaahus | Kategori::Boligblokk)
}
}
#[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct Bygning {
pub bygning: BygningInfo,
#[serde(default)]
pub klima: Klima,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub flate: Vec<Flate>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub vindu: Vec<Vindu>,
#[serde(default)]
pub kuldebroer: Kuldebroer,
#[serde(default)]
pub infiltrasjon: Infiltrasjon,
#[serde(default)]
pub ventilasjon: Ventilasjon,
#[serde(default)]
pub tappevann: Tappevann,
#[serde(default)]
pub oppvarming: Oppvarming,
#[serde(default)]
pub distribusjon: Distribusjon,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub solcelle: Option<Solcelle>,
#[serde(default)]
pub kjoling: Kjoling,
#[serde(default)]
pub lufting: Lufting,
#[serde(default)]
pub drift: Drift,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub scenario: Vec<Scenario>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct BygningInfo {
pub navn: String,
pub kategori: Kategori,
pub byggeaar: i32,
/// Oppvarmet del av bruksareal, m².
pub oppvarmet_bra: f64,
/// Oppvarmet luftvolum, m³. Utelatt: 2,4 m × BRA.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub oppvarmet_volum: Option<f64>,
#[serde(default = "en")]
pub antall_boenheter: u32,
/// Samlet oppvarmet BRA for hele bygningen når én boenhet i flerbolighus merkes.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bygningens_oppvarmede_bra: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub adresse: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub gnr_bnr: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub beskrivelse: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub energiraadgiver: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub oppdragsgiver: Option<String>,
}
fn en() -> u32 {
1
}
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
pub struct Klima {
/// Kommune for lokalt klima (TMY-fil fra Zenodo 14779899). Utelatt: bare Oslo.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub kommune: Option<String>,
/// Egen EPW-fil for lokalt klima (overstyrer kommune).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub epw: Option<PathBuf>,
/// Referanseklima for energimerket. Standard «Oslo».
#[serde(default, skip_serializing_if = "Option::is_none")]
pub referanse: Option<String>,
/// Bakkerefleksjon (albedo) for solstråling. Standard 0,2.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub albedo: Option<f64>,
}
#[derive(Deserialize, Serialize, Clone, Copy, Debug, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum FlateType {
Vegg,
Tak,
Gulv,
}
#[derive(Deserialize, Serialize, Clone, Copy, Debug, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum Mot {
#[default]
Ute,
Grunn,
Uoppvarmet,
}
#[derive(Deserialize, Serialize, Clone, Copy, Debug, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum Masseklasse {
SvaertLett,
Lett,
Middels,
Tung,
SvaertTung,
}
impl Masseklasse {
/// Arealspesifikk varmekapasitet κ_m, J/(m²K) (ISO 52016-1 Tabell B.14).
pub fn kappa(&self) -> f64 {
match self {
Masseklasse::SvaertLett => 50_000.0,
Masseklasse::Lett => 75_000.0,
Masseklasse::Middels => 110_000.0,
Masseklasse::Tung => 175_000.0,
Masseklasse::SvaertTung => 250_000.0,
}
}
}
/// Hvor massen sitter i konstruksjonen (ISO 52016-1 klasse I/E/IE/D/M).
#[derive(Deserialize, Serialize, Clone, Copy, Debug, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum Masseplassering {
Innvendig,
Utvendig,
Begge,
#[default]
Fordelt,
Midt,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct Lag {
pub materiale: String,
pub tykkelse: f64,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct Flate {
pub navn: String,
#[serde(rename = "type")]
pub typ: FlateType,
pub areal: f64,
/// Himmelretning flatens utside vender mot, grader fra nord med klokka (0 N, 90 Ø, 180 S, 270 V).
#[serde(default)]
pub orientering: f64,
/// Helning fra horisontalplanet: 90 = vertikal vegg, 0 = flatt tak. Utelatt: 90 for vegg, 0 for tak/gulv.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub helning: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub u_verdi: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub lag: Option<Vec<Lag>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub masseklasse: Option<Masseklasse>,
#[serde(default)]
pub masseplassering: Masseplassering,
#[serde(default)]
pub mot: Mot,
/// Temperaturfaktor b for uoppvarmet rom. Utelatt: 0,5 (loft/kjeller), jf. NS-EN ISO 13789.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub b_faktor: Option<f64>,
/// Solabsorptans for utvendig overflate. Utelatt: 0,6.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub absorptans: Option<f64>,
/// For gulv mot grunn / kjellervegg mot grunn: eksponert omkrets (m) og dybde under terreng (m).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub omkrets: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dybde: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub kommentar: Option<String>,
}
#[derive(Deserialize, Serialize, Clone, Copy, Debug, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum SolskjermingType {
#[default]
Ingen,
Manuell,
Automatisk,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct Solskjerming {
#[serde(rename = "type")]
pub typ: SolskjermingType,
/// Gjennomslipp når skjermingen er i bruk (01). Utelatt: 0,8 manuell, 0,4 automatisk.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub faktor: Option<f64>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct Vindu {
pub navn: String,
/// Areal inkl. karm, m².
pub areal: f64,
#[serde(default = "en_f64")]
pub antall: f64,
#[serde(default)]
pub orientering: f64,
#[serde(default = "nitti")]
pub helning: f64,
pub u_verdi: f64,
/// Solfaktor for glasset (g-verdi). Utelatt: 0,75 for 2-lags, 0,5 for 3-lags energiglass.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub g_verdi: Option<f64>,
/// Karm- og rammeandel av arealet. Utelatt: 0,2.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub karmandel: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub solskjerming: Option<Solskjerming>,
/// Horisontavskjerming i grader (terreng, nabobygg). Utelatt: 0.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub horisont: Option<f64>,
/// Er dette en dør (regnes uten solinnstråling)?
#[serde(default)]
pub dor: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub kommentar: Option<String>,
}
fn en_f64() -> f64 {
1.0
}
fn nitti() -> f64 {
90.0
}
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
pub struct Kuldebroer {
/// Normalisert kuldebroverdi, W/(m²K) av oppvarmet BRA. Utelatt: verdi etter byggeår.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub normalisert: Option<f64>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub liste: Vec<Kuldebro>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct Kuldebro {
pub navn: String,
pub psi: f64,
pub lengde: f64,
}
#[derive(Deserialize, Serialize, Clone, Copy, Debug, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum Skjerming {
Skjermet,
#[default]
Moderat,
Utsatt,
}
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
pub struct Infiltrasjon {
/// Lekkasjetall ved 50 Pa, 1/h. Utelatt: verdi etter byggeår.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub n50: Option<f64>,
#[serde(default)]
pub skjerming: Skjerming,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub maalt_dato: Option<String>,
}
#[derive(Deserialize, Serialize, Clone, Copy, Debug, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum VentilasjonType {
#[default]
Naturlig,
Avtrekk,
Balansert,
}
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
pub struct Ventilasjon {
#[serde(rename = "type", default)]
pub typ: VentilasjonType,
/// Gjennomsnittlig luftmengde i driftstiden, m³/(h·m²). Utelatt: normert.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub luftmengde: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub luftmengde_natt: Option<f64>,
/// Temperaturvirkningsgrad varmegjenvinner (01). Bare balansert. Utelatt: normert.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub gjenvinning: Option<f64>,
/// Spesifikk vifteeffekt, kW/(m³/s). Utelatt: normert (balansert) / 0,6 (avtrekk).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sfp: Option<f64>,
/// Har anlegget varmebatteri som ettervarmer tilluften?
#[serde(default, skip_serializing_if = "Option::is_none")]
pub varmebatteri: Option<bool>,
}
#[derive(Deserialize, Serialize, Clone, Copy, Debug, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum TappevannSystem {
#[default]
Aapent,
Sirkulasjon,
}
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
pub struct Tappevann {
#[serde(default)]
pub system: TappevannSystem,
/// Netto energibehov, kWh/(m²·år). Utelatt: normert.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub energibehov: Option<f64>,
/// Tap i bereder og rør som andel av netto behov. Utelatt: 0,25 åpent, 0,45 sirkulasjon.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tap_andel: Option<f64>,
/// Andel av tapet som avgis i oppvarmet sone (uregulerbart varmetilskudd). Utelatt: 0,8 / 0,5.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tap_i_oppvarmet_sone: Option<f64>,
}
#[derive(Deserialize, Serialize, Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum VarmekildeType {
DirekteEl,
ElKjel,
LuftLuftVp,
LuftVannVp,
VaeskeVannVp,
AvtrekkVp,
Vedovn,
Pellets,
Olje,
Gass,
Fjernvarme,
Solfanger,
}
impl VarmekildeType {
pub fn nokkel(&self) -> &'static str {
match self {
VarmekildeType::DirekteEl => "direkte_el",
VarmekildeType::ElKjel => "el_kjel",
VarmekildeType::LuftLuftVp => "luft_luft_vp",
VarmekildeType::LuftVannVp => "luft_vann_vp",
VarmekildeType::VaeskeVannVp => "vaeske_vann_vp",
VarmekildeType::AvtrekkVp => "avtrekk_vp",
VarmekildeType::Vedovn => "vedovn",
VarmekildeType::Pellets => "pellets",
VarmekildeType::Olje => "olje",
VarmekildeType::Gass => "gass",
VarmekildeType::Fjernvarme => "fjernvarme",
VarmekildeType::Solfanger => "solfanger",
}
}
pub fn beskrivelse(&self) -> &'static str {
match self {
VarmekildeType::DirekteEl => "Direkte elektrisk (panelovner, varmekabler)",
VarmekildeType::ElKjel => "Elektrokjel, vannbåren varme",
VarmekildeType::LuftLuftVp => "Varmepumpe luftluft",
VarmekildeType::LuftVannVp => "Varmepumpe luftvann",
VarmekildeType::VaeskeVannVp => "Varmepumpe væskevann (berg/jord/sjø)",
VarmekildeType::AvtrekkVp => "Avtrekksvarmepumpe",
VarmekildeType::Vedovn => "Vedovn",
VarmekildeType::Pellets => "Pelletskamin/-kjel",
VarmekildeType::Olje => "Oljekjel",
VarmekildeType::Gass => "Gasskjel",
VarmekildeType::Fjernvarme => "Fjernvarme",
VarmekildeType::Solfanger => "Solfanger",
}
}
pub fn er_varmepumpe(&self) -> bool {
matches!(
self,
VarmekildeType::LuftLuftVp
| VarmekildeType::LuftVannVp
| VarmekildeType::VaeskeVannVp
| VarmekildeType::AvtrekkVp
)
}
/// Kan kilden levere til vannbåren distribusjon (ventilasjonsvarme, tappevann)?
pub fn vannbaaren(&self) -> bool {
!matches!(
self,
VarmekildeType::DirekteEl | VarmekildeType::LuftLuftVp | VarmekildeType::Vedovn
)
}
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct Varmekilde {
#[serde(rename = "type")]
pub typ: VarmekildeType,
/// Andel av romoppvarmingsbehovet kilden dekker (01).
#[serde(default)]
pub andel_rom: f64,
/// Andel av ventilasjonsvarme. Utelatt: som andel_rom for vannbårne kilder, ellers 0.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub andel_ventilasjon: Option<f64>,
#[serde(default)]
pub andel_tappevann: f64,
/// Årsvarmefaktor (SPF) for varmepumper eller virkningsgrad for kjeler. Utelatt: tabellverdi.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub spf: Option<f64>,
/// Turtemperatur °C for vannbårne varmepumper (35/45/55).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub turtemperatur: Option<f64>,
/// Vedovn: lukket ildsted med lavt partikkelutslipp (gir vektingsfaktor 0,45).
#[serde(default = "sann")]
pub lukket: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub kommentar: Option<String>,
}
fn sann() -> bool {
true
}
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
pub struct Oppvarming {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub kilde: Vec<Varmekilde>,
/// Maks varmeeffekt installert, W (begrenser innetemperatur ved underdekning). Utelatt: ubegrenset.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub maks_effekt: Option<f64>,
}
#[derive(Deserialize, Serialize, Clone, Copy, Debug, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum DistribusjonType {
#[default]
Punkt,
Radiator,
Gulvvarme,
}
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
pub struct Distribusjon {
#[serde(rename = "type", default)]
pub typ: DistribusjonType,
/// Distribusjonstap som andel av avgitt romvarme. Utelatt: 0 punkt, 0,03 radiator, 0,05 gulvvarme.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tap_andel: Option<f64>,
/// Andel av tapet som går til uoppvarmet areal (rør i kjeller/loft). Utelatt: 0,2.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub roer_uoppvarmet: Option<f64>,
/// Pumpeeffekt, W/m² i driftstid. Utelatt: normert for vannbåren, 0 for punkt.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pumpe_effekt: Option<f64>,
/// Turtemperatur °C i vannbårent anlegg (60 radiator, 35 gulvvarme). Brukes for SPF-oppslag.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub turtemperatur: Option<f64>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct Solcelle {
/// Modulareal, m².
pub areal: f64,
#[serde(default = "tjue_prosent")]
pub virkningsgrad: f64,
#[serde(default = "sor")]
pub orientering: f64,
#[serde(default = "tretti")]
pub helning: f64,
/// Ytelsesfaktor (inverter, kabler, snø, smuss). Utelatt: 0,8.
#[serde(default = "aatti_prosent")]
pub ytelsesfaktor: f64,
}
fn tjue_prosent() -> f64 {
0.20
}
fn sor() -> f64 {
180.0
}
fn tretti() -> f64 {
30.0
}
fn aatti_prosent() -> f64 {
0.8
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct Kjoling {
#[serde(default = "sann")]
pub ingen: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub settpunkt: Option<f64>,
/// Årskuldefaktor (SEER). Utelatt: 3,0.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub spf: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub maks_effekt: Option<f64>,
}
impl Default for Kjoling {
fn default() -> Self {
Self {
ingen: true,
settpunkt: None,
spf: None,
maks_effekt: None,
}
}
}
/// Vinduslufting: økt luftmengde når rommene blir for varme.
///
/// Bygninger uten kjøleanlegg luftes gjennom vinduer når det blir for varmt. Uten denne
/// modellen vil en tett bygning med store vindusflater få urealistisk høye innetemperaturer
/// om våren og høsten, og lagret solvarme vil kunstig redusere oppvarmingsbehovet.
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
pub struct Lufting {
/// Slår av vinduslufting. Standard er lufting for bygninger uten kjøleanlegg.
#[serde(default)]
pub ingen: bool,
/// Operativ temperatur der luftingen starter, °C. Utelatt: 24.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub settpunkt: Option<f64>,
/// Største luftmengde ved vinduslufting, m³/(h·m²) oppvarmet BRA. Utelatt: 8.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub maks_luftmengde: Option<f64>,
}
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
pub struct Drift {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub settpunkt_oppvarming: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub nattsenking: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub belysning_effekt: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub utstyr_effekt: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub personer_varmetilskudd: Option<f64>,
/// Effektiv innvendig varmekapasitet for luft og inventar, J/(m²K). Utelatt: 10 000 (ISO 52016-1).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub innvendig_varmekapasitet: Option<f64>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct Scenario {
pub navn: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub beskrivelse: Option<String>,
/// Delvis bygningsbeskrivelse som flettes inn: tabeller flettes rekursivt, tabellarrayer
/// (flate, vindu, oppvarming.kilde …) matches på `navn`/`type`, skalarer erstattes.
/// `slett = true` fjerner et element.
pub endringer: toml::Value,
}
pub mod lost;