//! 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, /// Uoppvarmede soner, typisk kjeller eller loft, med egen temperaturberegning. #[serde(default, skip_serializing_if = "Vec::is_empty")] pub sone: Vec, #[serde(default)] pub grunn: Grunn, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub vindu: Vec, #[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, #[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, } #[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, #[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, #[serde(default, skip_serializing_if = "Option::is_none")] pub adresse: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub gnr_bnr: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub beskrivelse: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub energiraadgiver: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub oppdragsgiver: Option, } 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, /// Egen EPW-fil for lokalt klima (overstyrer kommune). #[serde(default, skip_serializing_if = "Option::is_none")] pub epw: Option, /// Referanseklima for energimerket. Standard «Oslo». #[serde(default, skip_serializing_if = "Option::is_none")] pub referanse: Option, /// Bakkerefleksjon (albedo) for solstråling. Standard 0,2. #[serde(default, skip_serializing_if = "Option::is_none")] pub albedo: Option, } #[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, #[serde(default, skip_serializing_if = "Option::is_none")] pub u_verdi: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub lag: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub masseklasse: Option, #[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, /// Solabsorptans for utvendig overflate. Utelatt: 0,6. #[serde(default, skip_serializing_if = "Option::is_none")] pub absorptans: Option, /// Gulv mot grunn: eksponert omkrets, m. Brukes i den karakteristiske dimensjonen B′. #[serde(default, skip_serializing_if = "Option::is_none")] pub omkrets: Option, /// Dybde under terreng, m. For gulv: gulvets dybde. For kjellervegg: veggens nedgravde høyde. #[serde(default, skip_serializing_if = "Option::is_none")] pub dybde: Option, /// Kjellervegg: høyden som står over terreng, m. Den delen regnes som vanlig yttervegg, /// og arealet deles mellom over og under terreng i forhold til høydene. #[serde(default, skip_serializing_if = "Option::is_none")] pub over_terreng: Option, /// Navnet på den uoppvarmede sonen flaten vender mot, når `mot = "uoppvarmet"`. /// Uten dette brukes temperaturfaktoren `b_faktor` i stedet. #[serde(default, skip_serializing_if = "Option::is_none")] pub sone: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub kommentar: Option, } #[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 (0–1). Utelatt: 0,8 manuell, 0,4 automatisk. #[serde(default, skip_serializing_if = "Option::is_none")] pub faktor: Option, } #[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, /// Karm- og rammeandel av arealet. Utelatt: 0,2. #[serde(default, skip_serializing_if = "Option::is_none")] pub karmandel: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub solskjerming: Option, /// Horisontavskjerming i grader (terreng, nabobygg). Utelatt: 0. #[serde(default, skip_serializing_if = "Option::is_none")] pub horisont: Option, /// 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, } fn en_f64() -> f64 { 1.0 } fn nitti() -> f64 { 90.0 } /// Grunnforhold for beregning etter NS-EN ISO 13370. #[derive(Deserialize, Serialize, Clone, Debug, Default)] pub struct Grunn { /// Varmeledningsevne for jorda, W/(m·K). Utelatt: 2,0 (leire eller silt). #[serde(default, skip_serializing_if = "Option::is_none")] pub ledningsevne: Option, } /// En uoppvarmet sone, typisk kjeller, loft eller garasje. /// /// Sonen har ingen egen oppvarming, men får varme fra den oppvarmede delen gjennom /// etasjeskilleren og fra varmerør og bereder som står der. Temperaturen regnes ut time for /// time av varmebalansen etter NS-EN ISO 13789, slik at et varmt rørnett i kjelleren både /// teller som tap fra varmeanlegget og som varme som holder etasjeskilleren varm. #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Sone { pub navn: String, /// Luftvolum, m³. Brukes til ventilasjonstapet fra sonen. #[serde(default, skip_serializing_if = "Option::is_none")] pub volum: Option, /// Luftvekslinger per time mot det fri. Utelatt: 0,3 for en kjeller. #[serde(default, skip_serializing_if = "Option::is_none")] pub luftveksling: Option, /// Sonens egne flater mot det fri og mot grunnen. #[serde(default, rename = "flate", skip_serializing_if = "Vec::is_empty")] pub flater: Vec, /// Andel av varmeanleggets distribusjonstap som avgis i denne sonen. Utelatt: 1,0 når /// bygningen har vannbåren varme og sonen er den eneste uoppvarmede. #[serde(default, skip_serializing_if = "Option::is_none")] pub andel_distribusjonstap: Option, /// Andel av tappevannsanleggets tap som avgis her. #[serde(default, skip_serializing_if = "Option::is_none")] pub andel_tappevannstap: Option, /// Fast varmetilskudd i sonen, W. For eksempel kappetap fra en kjel. #[serde(default)] pub fast_effekt: f64, #[serde(default, skip_serializing_if = "Option::is_none")] pub kommentar: Option, } #[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, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub liste: Vec, } #[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, #[serde(default)] pub skjerming: Skjerming, #[serde(default, skip_serializing_if = "Option::is_none")] pub maalt_dato: Option, } #[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, #[serde(default, skip_serializing_if = "Option::is_none")] pub luftmengde_natt: Option, /// Temperaturvirkningsgrad varmegjenvinner (0–1). Bare balansert. Utelatt: normert. #[serde(default, skip_serializing_if = "Option::is_none")] pub gjenvinning: Option, /// Spesifikk vifteeffekt, kW/(m³/s). Utelatt: normert (balansert) / 0,6 (avtrekk). #[serde(default, skip_serializing_if = "Option::is_none")] pub sfp: Option, /// Har anlegget varmebatteri som ettervarmer tilluften? #[serde(default, skip_serializing_if = "Option::is_none")] pub varmebatteri: Option, } #[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, /// 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, /// 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, } #[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 luft–luft", VarmekildeType::LuftVannVp => "Varmepumpe luft–vann", VarmekildeType::VaeskeVannVp => "Varmepumpe væske–vann (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 (0–1). #[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, #[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, /// Turtemperatur °C for vannbårne varmepumper (35/45/55). #[serde(default, skip_serializing_if = "Option::is_none")] pub turtemperatur: Option, /// 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, } fn sann() -> bool { true } #[derive(Deserialize, Serialize, Clone, Debug, Default)] pub struct Oppvarming { #[serde(default, skip_serializing_if = "Vec::is_empty")] pub kilde: Vec, /// Maks varmeeffekt installert, W (begrenser innetemperatur ved underdekning). Utelatt: ubegrenset. #[serde(default, skip_serializing_if = "Option::is_none")] pub maks_effekt: Option, } #[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, /// 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, /// 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, /// 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, } #[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, /// Årskuldefaktor (SEER). Utelatt: 3,0. #[serde(default, skip_serializing_if = "Option::is_none")] pub spf: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub maks_effekt: Option, } 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, /// Største luftmengde ved vinduslufting, m³/(h·m²) oppvarmet BRA. Utelatt: 8. #[serde(default, skip_serializing_if = "Option::is_none")] pub maks_luftmengde: Option, } #[derive(Deserialize, Serialize, Clone, Debug, Default)] pub struct Drift { #[serde(default, skip_serializing_if = "Option::is_none")] pub settpunkt_oppvarming: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub nattsenking: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub belysning_effekt: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub utstyr_effekt: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub personer_varmetilskudd: Option, /// 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, } #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Scenario { pub navn: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub beskrivelse: Option, /// 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;