test / test (push) Failing after 3s
Importen er prøvd mot en virkelig IFC-fil fra Vectorworks Architect 2026 og avdekket tre feil og ett hull. IFC-import - Lengdeenheten hentes nå gjennom prosjektets enhetsoppsett. Filen inneholder to lengdeenheter, og oppslaget traff tilfeldig den ene. - Arealer regnes av geometrien, siden modellen ikke har mengder for vegger og dekker. Trekantene grupperes etter retning, og den største gruppen er delens hovedflate. Metoden gir riktig areal også når vinduer er skåret ut av veggen. - Materialsjikt eksporteres som hver sin geometridel. Deler som ligger i samme plan og overlapper hverandre telles én gang, ellers ble en vegg talt sju ganger. - Himmelretningen følger flatenormalen i stedet for et anslag ut fra tyngdepunktet. - Flater med samme konstruksjon og himmelretning slås sammen, og rommene listes med areal, siden IFC ikke sier hvilke som er oppvarmet. Grunnen og kjelleren - Flater mot grunnen regnes etter NS-EN ISO 13370. En kjellervegg deles i en del over terreng og en nedgravd del, etter høydene `over_terreng` og `dybde`. - Uoppvarmede soner får egen temperatur time for time etter NS-EN ISO 13789, med varmetilskudd fra distribusjonstap og beredere som står der. Uisolerte varmerør i en kjeller teller dermed både som tap og som varme som holder etasjeskilleren varm. - Varmetapsbudsjettet oppgir konstruksjonens egen U-verdi, slik NS 3031 Tabell 3 og energikravene i TEK er formulert, mens timesberegningen bruker grunnens demping. Nordhusstykket 5 er modellert av IFC-geometrien med kjelleren som egen sone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015GeH3y8fwTaTudaD88P57o
653 lines
23 KiB
Rust
653 lines
23 KiB
Rust
//! 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>,
|
||
/// Uoppvarmede soner, typisk kjeller eller loft, med egen temperaturberegning.
|
||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||
pub sone: Vec<Sone>,
|
||
#[serde(default)]
|
||
pub grunn: Grunn,
|
||
#[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>,
|
||
/// Gulv mot grunn: eksponert omkrets, m. Brukes i den karakteristiske dimensjonen B′.
|
||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||
pub omkrets: Option<f64>,
|
||
/// 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<f64>,
|
||
/// 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<f64>,
|
||
/// 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<String>,
|
||
#[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 (0–1). 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
|
||
}
|
||
|
||
/// 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<f64>,
|
||
}
|
||
|
||
/// 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<f64>,
|
||
/// Luftvekslinger per time mot det fri. Utelatt: 0,3 for en kjeller.
|
||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||
pub luftveksling: Option<f64>,
|
||
/// Sonens egne flater mot det fri og mot grunnen.
|
||
#[serde(default, rename = "flate", skip_serializing_if = "Vec::is_empty")]
|
||
pub flater: Vec<Flate>,
|
||
/// 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<f64>,
|
||
/// Andel av tappevannsanleggets tap som avgis her.
|
||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||
pub andel_tappevannstap: Option<f64>,
|
||
/// 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<String>,
|
||
}
|
||
|
||
#[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 (0–1). 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 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<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;
|