Energiberegning etter NS 3031:2025 med timesmetode og energirapport
enok leser en bygningsbeskrivelse i TOML, henter typisk klimaår for kommunen, simulerer 8760 timer etter NS-EN ISO 52016-1 og skriver energirapport, CSV og JSON. Motor - Femnodemodell for opake bygningsdeler, tonodemodell for vinduer, luftnode og strålingsnode per sone, implisitt løsning per time med egen tett LU-løser. - Varme- og kjølebehov ved eksakt interpolasjon mellom to løsninger, siden systemet er lineært. Vinduslufting for bygninger uten kjøleanlegg. - Solberegning etter ISO 52010-1, kontrollert mot pvlib med avvik under 1 prosent. Beregningsgang - Beregningspunktene A til E: netto, brutto, tilført, levert per energibærer og vektet levert energi, med solceller, klimakorrigering og effektbehov. - Energikarakter etter Enovas skala fra 1.1.2026, med arealkorreksjon. Inndata - TOML med scenarier for tiltak, og import fra IFC, gbXML og regneark. - Normerte verdier, karakterskala, vektingsfaktorer og klimadata i data/, som kan overstyres med --data. Kontroll - Stasjonært varmetap mot U·A·ΔT innenfor 2 prosent, energibalansen innenfor 2 prosent, varmetapsbudsjettet for TEK17-referansebygget identisk med publisert tabell. Kjent avvik i romoppvarming mot Simien 8 er dokumentert i README. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015GeH3y8fwTaTudaD88P57o
This commit is contained in:
@@ -0,0 +1,539 @@
|
||||
//! Oppløsning av inndata: fyller manglende felter med normerte verdier eller verdier utledet
|
||||
//! av byggeår, og fører logg over hva som er antatt. Loggen gjengis i rapporten slik at leseren
|
||||
//! ser hvilke tall som er dokumentert og hvilke som er normerte.
|
||||
|
||||
use crate::data::{Datakilde, Materialer, Normerte};
|
||||
use crate::modell::*;
|
||||
use crate::Result;
|
||||
use anyhow::{bail, Context};
|
||||
use serde::Serialize;
|
||||
|
||||
/// Én forutsetning som ikke kom fra inndatafilen.
|
||||
#[derive(Serialize, Clone, Debug)]
|
||||
pub struct Forutsetning {
|
||||
pub felt: String,
|
||||
pub verdi: String,
|
||||
pub kilde: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Regulering {
|
||||
/// Settpunktet gjelder operativ temperatur (middel av luft- og strålingstemperatur).
|
||||
Operativ,
|
||||
/// Settpunktet gjelder lufttemperatur.
|
||||
Luft,
|
||||
}
|
||||
|
||||
/// En ferdig oppløst flate klar for timesberegning.
|
||||
#[derive(Serialize, Clone, Debug)]
|
||||
pub struct LostFlate {
|
||||
pub navn: String,
|
||||
pub typ: FlateType,
|
||||
pub areal: f64,
|
||||
pub u_verdi: f64,
|
||||
pub helning: f64,
|
||||
pub orientering: f64,
|
||||
pub kappa: f64,
|
||||
pub masseplassering: Masseplassering,
|
||||
pub mot: Mot,
|
||||
pub b_faktor: f64,
|
||||
pub absorptans: f64,
|
||||
/// Sant for vinduer og dører (to noder, ingen masse, solinnstråling gjennom glasset).
|
||||
pub glass: bool,
|
||||
/// Effektiv solfaktor inkl. karmandel og skjerming (bare vinduer).
|
||||
pub g_effektiv: f64,
|
||||
/// Skjermingsfaktor når solskjerming er i bruk (1,0 = ingen skjerming).
|
||||
pub skjermingsfaktor: f64,
|
||||
pub automatisk_skjerming: bool,
|
||||
pub horisont: f64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone, Debug)]
|
||||
pub struct Lost {
|
||||
pub navn: String,
|
||||
pub kategori: Kategori,
|
||||
pub byggeaar: i32,
|
||||
pub bra: f64,
|
||||
pub volum: f64,
|
||||
pub antall_boenheter: u32,
|
||||
/// Areal som brukes i arealkorreksjonen på karakterskalaen.
|
||||
pub skala_areal: f64,
|
||||
pub flater: Vec<LostFlate>,
|
||||
/// W/K, summert transmisjonstap uten kuldebroer.
|
||||
pub kuldebro_w_per_k: f64,
|
||||
pub n50: f64,
|
||||
pub skjerming: Skjerming,
|
||||
pub ventilasjon: LostVentilasjon,
|
||||
pub laster: LostLaster,
|
||||
pub settpunkt_oppvarming: f64,
|
||||
pub settpunkt_natt: f64,
|
||||
pub settpunkt_kjoling: Option<f64>,
|
||||
/// Vinduslufting: (settpunkt °C, største luftmengde m³/(h·m²)).
|
||||
pub lufting: Option<(f64, f64)>,
|
||||
pub regulering: Regulering,
|
||||
pub innvendig_varmekapasitet: f64,
|
||||
pub maks_varmeeffekt: Option<f64>,
|
||||
pub maks_kjoleeffekt: Option<f64>,
|
||||
pub albedo: f64,
|
||||
pub forutsetninger: Vec<Forutsetning>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone, Debug)]
|
||||
pub struct LostVentilasjon {
|
||||
pub typ: VentilasjonType,
|
||||
/// m³/(h·m²) i driftstiden.
|
||||
pub luftmengde: f64,
|
||||
pub luftmengde_natt: f64,
|
||||
pub gjenvinning: f64,
|
||||
pub sfp: f64,
|
||||
pub varmebatteri: bool,
|
||||
pub tilluft_settpunkt: f64,
|
||||
pub driftstid: crate::data::Tidsrom,
|
||||
}
|
||||
|
||||
/// Timesprofiler for interne laster, W/m² oppvarmet BRA.
|
||||
#[derive(Serialize, Clone, Debug)]
|
||||
pub struct LostLaster {
|
||||
pub belysning: [f64; 24],
|
||||
pub utstyr_effekt: [f64; 24],
|
||||
pub utstyr_varme: [f64; 24],
|
||||
pub personer: [f64; 24],
|
||||
pub varmtvann: [f64; 24],
|
||||
pub pumper: [f64; 24],
|
||||
pub oppvarming_driftstid: crate::data::Tidsrom,
|
||||
}
|
||||
|
||||
const R_SI: f64 = 0.13;
|
||||
const R_SE: f64 = 0.04;
|
||||
|
||||
impl Lost {
|
||||
pub fn fra(b: &Bygning, kilde: &Datakilde) -> Result<Self> {
|
||||
let mut f = Vec::new();
|
||||
let normerte = kilde.normerte(b.bygning.kategori)?;
|
||||
let materialer = kilde.materialer()?;
|
||||
let info = &b.bygning;
|
||||
|
||||
if info.oppvarmet_bra <= 0.0 {
|
||||
bail!("oppvarmet_bra må være større enn null");
|
||||
}
|
||||
let volum = info.oppvarmet_volum.unwrap_or_else(|| {
|
||||
f.push(Forutsetning {
|
||||
felt: "bygning.oppvarmet_volum".into(),
|
||||
verdi: format!("{:.0} m³", info.oppvarmet_bra * 2.4),
|
||||
kilde: "antatt 2,4 m romhøyde".into(),
|
||||
});
|
||||
info.oppvarmet_bra * 2.4
|
||||
});
|
||||
|
||||
let periode = materialer.periode_for(info.byggeaar);
|
||||
|
||||
let mut flater = Vec::new();
|
||||
for fl in &b.flate {
|
||||
flater.push(los_flate(fl, &materialer, periode, &mut f)?);
|
||||
}
|
||||
for v in &b.vindu {
|
||||
flater.push(los_vindu(v, &mut f));
|
||||
}
|
||||
if flater.is_empty() {
|
||||
bail!("bygningen har ingen flater; legg inn minst én [[flate]]");
|
||||
}
|
||||
|
||||
// Kuldebroer.
|
||||
let kuldebro_w_per_k = if !b.kuldebroer.liste.is_empty() {
|
||||
b.kuldebroer.liste.iter().map(|k| k.psi * k.lengde).sum()
|
||||
} else {
|
||||
let n = b.kuldebroer.normalisert.unwrap_or_else(|| {
|
||||
let v = periode
|
||||
.map(|p| p.kuldebro)
|
||||
.unwrap_or(normerte.termisk.kuldebroer_normalisert);
|
||||
f.push(Forutsetning {
|
||||
felt: "kuldebroer.normalisert".into(),
|
||||
verdi: format!("{v:.2} W/(m²K)"),
|
||||
kilde: periode
|
||||
.map(|p| format!("typisk for {}", p.navn))
|
||||
.unwrap_or_else(|| "normert verdi".into()),
|
||||
});
|
||||
v
|
||||
});
|
||||
n * info.oppvarmet_bra
|
||||
};
|
||||
|
||||
// Lekkasjetall.
|
||||
let n50 = b.infiltrasjon.n50.unwrap_or_else(|| {
|
||||
let v = periode.map(|p| p.n50).unwrap_or(3.0);
|
||||
f.push(Forutsetning {
|
||||
felt: "infiltrasjon.n50".into(),
|
||||
verdi: format!("{v:.1} h⁻¹"),
|
||||
kilde: periode
|
||||
.map(|p| format!("typisk for {}", p.navn))
|
||||
.unwrap_or_else(|| "antatt".into()),
|
||||
});
|
||||
v
|
||||
});
|
||||
|
||||
let ventilasjon = los_ventilasjon(&b.ventilasjon, &normerte, &mut f);
|
||||
let laster = los_laster(b, &normerte, &mut f);
|
||||
|
||||
let settpunkt_oppvarming = b
|
||||
.drift
|
||||
.settpunkt_oppvarming
|
||||
.unwrap_or(normerte.settpunkt.oppvarming);
|
||||
let settpunkt_natt = b
|
||||
.drift
|
||||
.nattsenking
|
||||
.unwrap_or(normerte.settpunkt.oppvarming_natt);
|
||||
let settpunkt_kjoling = if b.kjoling.ingen {
|
||||
None
|
||||
} else {
|
||||
Some(b.kjoling.settpunkt.unwrap_or(normerte.settpunkt.kjoling))
|
||||
};
|
||||
|
||||
let innvendig_varmekapasitet = b.drift.innvendig_varmekapasitet.unwrap_or(10_000.0);
|
||||
|
||||
// Vinduslufting brukes når bygningen ikke har kjøleanlegg.
|
||||
let lufting = if b.lufting.ingen || settpunkt_kjoling.is_some() {
|
||||
None
|
||||
} else {
|
||||
let sett = b.lufting.settpunkt.unwrap_or_else(|| {
|
||||
f.push(Forutsetning {
|
||||
felt: "lufting.settpunkt".into(),
|
||||
verdi: "24 °C".into(),
|
||||
kilde: "antatt grense for vinduslufting i bygning uten kjøling".into(),
|
||||
});
|
||||
24.0
|
||||
});
|
||||
let maks = b.lufting.maks_luftmengde.unwrap_or_else(|| {
|
||||
f.push(Forutsetning {
|
||||
felt: "lufting.maks_luftmengde".into(),
|
||||
verdi: "8 m³/(h·m²)".into(),
|
||||
kilde: "antatt største luftmengde ved vinduslufting".into(),
|
||||
});
|
||||
8.0
|
||||
});
|
||||
Some((sett, maks))
|
||||
};
|
||||
|
||||
let skala_areal = match info.kategori {
|
||||
Kategori::Smaahus => info.bygningens_oppvarmede_bra.unwrap_or(info.oppvarmet_bra),
|
||||
Kategori::Boligblokk => info
|
||||
.bygningens_oppvarmede_bra
|
||||
.map(|a| a / info.antall_boenheter.max(1) as f64)
|
||||
.unwrap_or_else(|| {
|
||||
if info.antall_boenheter > 1 {
|
||||
info.oppvarmet_bra / info.antall_boenheter as f64
|
||||
} else {
|
||||
info.oppvarmet_bra
|
||||
}
|
||||
}),
|
||||
_ => info.oppvarmet_bra,
|
||||
};
|
||||
|
||||
Ok(Lost {
|
||||
navn: info.navn.clone(),
|
||||
kategori: info.kategori,
|
||||
byggeaar: info.byggeaar,
|
||||
bra: info.oppvarmet_bra,
|
||||
volum,
|
||||
antall_boenheter: info.antall_boenheter.max(1),
|
||||
skala_areal,
|
||||
flater,
|
||||
kuldebro_w_per_k,
|
||||
n50,
|
||||
skjerming: b.infiltrasjon.skjerming,
|
||||
ventilasjon,
|
||||
laster,
|
||||
settpunkt_oppvarming,
|
||||
settpunkt_natt,
|
||||
settpunkt_kjoling,
|
||||
lufting,
|
||||
regulering: Regulering::Operativ,
|
||||
innvendig_varmekapasitet,
|
||||
maks_varmeeffekt: b.oppvarming.maks_effekt,
|
||||
maks_kjoleeffekt: b.kjoling.maks_effekt,
|
||||
albedo: b.klima.albedo.unwrap_or(0.2),
|
||||
forutsetninger: f,
|
||||
})
|
||||
}
|
||||
|
||||
/// Samlet areal mot det fri og mot grunn/uoppvarmet (klimaskjermareal), m².
|
||||
pub fn klimaskjermareal(&self) -> f64 {
|
||||
self.flater.iter().map(|f| f.areal).sum()
|
||||
}
|
||||
|
||||
pub fn formfaktor(&self) -> f64 {
|
||||
self.klimaskjermareal() / self.bra
|
||||
}
|
||||
|
||||
pub fn vindusareal(&self) -> f64 {
|
||||
self.flater
|
||||
.iter()
|
||||
.filter(|f| f.glass)
|
||||
.map(|f| f.areal)
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
|
||||
fn los_flate(
|
||||
fl: &Flate,
|
||||
materialer: &Materialer,
|
||||
periode: Option<&crate::data::Periode>,
|
||||
logg: &mut Vec<Forutsetning>,
|
||||
) -> Result<LostFlate> {
|
||||
if fl.areal <= 0.0 {
|
||||
bail!("flaten «{}» har areal {} m²", fl.navn, fl.areal);
|
||||
}
|
||||
// U-verdi: oppgitt, utledet av lag, eller typisk for byggeperioden.
|
||||
let (u, kappa_lag) = if let Some(u) = fl.u_verdi {
|
||||
(u, None)
|
||||
} else if let Some(lag) = &fl.lag {
|
||||
let mut r = R_SI + R_SE;
|
||||
let mut kappa = 0.0;
|
||||
for l in lag {
|
||||
let m = materialer.materiale(&l.materiale).with_context(|| {
|
||||
format!("ukjent materiale «{}» i flaten «{}»", l.materiale, fl.navn)
|
||||
})?;
|
||||
r += l.tykkelse / m.lambda;
|
||||
kappa += l.tykkelse * m.rho * m.c;
|
||||
}
|
||||
(1.0 / r, Some(kappa))
|
||||
} else {
|
||||
let v = periode
|
||||
.map(|p| match fl.typ {
|
||||
FlateType::Vegg => p.vegg,
|
||||
FlateType::Tak => p.tak,
|
||||
FlateType::Gulv => p.gulv,
|
||||
})
|
||||
.with_context(|| format!("flaten «{}» mangler u_verdi og lag", fl.navn))?;
|
||||
logg.push(Forutsetning {
|
||||
felt: format!("flate.{}.u_verdi", fl.navn),
|
||||
verdi: format!("{v:.2} W/(m²K)"),
|
||||
kilde: periode
|
||||
.map(|p| format!("typisk for {}", p.navn))
|
||||
.unwrap_or_default(),
|
||||
});
|
||||
(v, None)
|
||||
};
|
||||
|
||||
let helning = fl.helning.unwrap_or(match fl.typ {
|
||||
FlateType::Vegg => 90.0,
|
||||
FlateType::Tak => 0.0,
|
||||
FlateType::Gulv => 180.0,
|
||||
});
|
||||
|
||||
let kappa = match (kappa_lag, fl.masseklasse) {
|
||||
(_, Some(k)) => k.kappa(),
|
||||
(Some(k), None) => k,
|
||||
(None, None) => {
|
||||
let k = Masseklasse::Middels.kappa();
|
||||
logg.push(Forutsetning {
|
||||
felt: format!("flate.{}.masseklasse", fl.navn),
|
||||
verdi: "middels".into(),
|
||||
kilde: "ISO 52016-1 tabell B.14".into(),
|
||||
});
|
||||
k
|
||||
}
|
||||
};
|
||||
|
||||
let b_faktor = match fl.mot {
|
||||
Mot::Ute => 1.0,
|
||||
Mot::Grunn => 1.0, // håndteres med egen bakketemperatur i timesberegningen
|
||||
Mot::Uoppvarmet => fl.b_faktor.unwrap_or_else(|| {
|
||||
logg.push(Forutsetning {
|
||||
felt: format!("flate.{}.b_faktor", fl.navn),
|
||||
verdi: "0,5".into(),
|
||||
kilde: "NS-EN ISO 13789, uoppvarmet rom".into(),
|
||||
});
|
||||
0.5
|
||||
}),
|
||||
};
|
||||
|
||||
Ok(LostFlate {
|
||||
navn: fl.navn.clone(),
|
||||
typ: fl.typ,
|
||||
areal: fl.areal,
|
||||
u_verdi: u,
|
||||
helning,
|
||||
orientering: fl.orientering,
|
||||
kappa,
|
||||
masseplassering: fl.masseplassering,
|
||||
mot: fl.mot,
|
||||
b_faktor,
|
||||
absorptans: fl.absorptans.unwrap_or(0.6),
|
||||
glass: false,
|
||||
g_effektiv: 0.0,
|
||||
skjermingsfaktor: 1.0,
|
||||
automatisk_skjerming: false,
|
||||
horisont: 0.0,
|
||||
})
|
||||
}
|
||||
|
||||
fn los_vindu(v: &Vindu, logg: &mut Vec<Forutsetning>) -> LostFlate {
|
||||
let areal = v.areal * v.antall;
|
||||
let karm = v.karmandel.unwrap_or_else(|| {
|
||||
logg.push(Forutsetning {
|
||||
felt: format!("vindu.{}.karmandel", v.navn),
|
||||
verdi: "0,20".into(),
|
||||
kilde: "normert karm- og rammeandel".into(),
|
||||
});
|
||||
0.20
|
||||
});
|
||||
let g = if v.dor {
|
||||
0.0
|
||||
} else {
|
||||
v.g_verdi.unwrap_or_else(|| {
|
||||
let antatt = if v.u_verdi <= 1.2 { 0.50 } else { 0.75 };
|
||||
logg.push(Forutsetning {
|
||||
felt: format!("vindu.{}.g_verdi", v.navn),
|
||||
verdi: format!("{antatt:.2}"),
|
||||
kilde: if v.u_verdi <= 1.2 {
|
||||
"antatt 3-lags energiglass"
|
||||
} else {
|
||||
"antatt 2-lags glass"
|
||||
}
|
||||
.into(),
|
||||
});
|
||||
antatt
|
||||
})
|
||||
};
|
||||
// ISO 52016-1: F_w = 0,9 korrigerer for ikke-vinkelrett innfall over året.
|
||||
let g_effektiv = g * (1.0 - karm) * 0.9;
|
||||
let (skjermingsfaktor, automatisk) = match &v.solskjerming {
|
||||
None => (1.0, false),
|
||||
Some(s) => match s.typ {
|
||||
SolskjermingType::Ingen => (1.0, false),
|
||||
SolskjermingType::Manuell => (s.faktor.unwrap_or(0.8), false),
|
||||
SolskjermingType::Automatisk => (s.faktor.unwrap_or(0.4), true),
|
||||
},
|
||||
};
|
||||
LostFlate {
|
||||
navn: v.navn.clone(),
|
||||
typ: FlateType::Vegg,
|
||||
areal,
|
||||
u_verdi: v.u_verdi,
|
||||
helning: v.helning,
|
||||
orientering: v.orientering,
|
||||
kappa: 0.0,
|
||||
masseplassering: Masseplassering::Fordelt,
|
||||
mot: Mot::Ute,
|
||||
b_faktor: 1.0,
|
||||
absorptans: 0.0,
|
||||
glass: true,
|
||||
g_effektiv,
|
||||
skjermingsfaktor,
|
||||
automatisk_skjerming: automatisk,
|
||||
horisont: v.horisont.unwrap_or(0.0),
|
||||
}
|
||||
}
|
||||
|
||||
fn los_ventilasjon(v: &Ventilasjon, n: &Normerte, logg: &mut Vec<Forutsetning>) -> LostVentilasjon {
|
||||
let luftmengde = v.luftmengde.unwrap_or_else(|| {
|
||||
logg.push(Forutsetning {
|
||||
felt: "ventilasjon.luftmengde".into(),
|
||||
verdi: format!("{:.2} m³/(h·m²)", n.ventilasjon.luftmengde_driftstid),
|
||||
kilde: format!("normert verdi, {}", n.kilde),
|
||||
});
|
||||
n.ventilasjon.luftmengde_driftstid
|
||||
});
|
||||
let luftmengde_natt = v
|
||||
.luftmengde_natt
|
||||
.unwrap_or(n.ventilasjon.luftmengde_utenfor_driftstid);
|
||||
let gjenvinning = match v.typ {
|
||||
VentilasjonType::Balansert => v.gjenvinning.unwrap_or_else(|| {
|
||||
logg.push(Forutsetning {
|
||||
felt: "ventilasjon.gjenvinning".into(),
|
||||
verdi: format!("{:.0} %", n.ventilasjon.temperaturvirkningsgrad * 100.0),
|
||||
kilde: "normert temperaturvirkningsgrad".into(),
|
||||
});
|
||||
n.ventilasjon.temperaturvirkningsgrad
|
||||
}),
|
||||
_ => 0.0,
|
||||
};
|
||||
let sfp = v.sfp.unwrap_or(match v.typ {
|
||||
VentilasjonType::Balansert => n.ventilasjon.sfp,
|
||||
VentilasjonType::Avtrekk => 0.6,
|
||||
VentilasjonType::Naturlig => 0.0,
|
||||
});
|
||||
let varmebatteri = v
|
||||
.varmebatteri
|
||||
.unwrap_or(matches!(v.typ, VentilasjonType::Balansert));
|
||||
LostVentilasjon {
|
||||
typ: v.typ,
|
||||
luftmengde,
|
||||
luftmengde_natt,
|
||||
gjenvinning,
|
||||
sfp,
|
||||
varmebatteri,
|
||||
tilluft_settpunkt: n.settpunkt.ventilasjon_tilluft,
|
||||
driftstid: n.driftstid.ventilasjon,
|
||||
}
|
||||
}
|
||||
|
||||
fn profil(effekt: f64, tid: crate::data::Tidsrom, egen: Option<[f64; 24]>) -> [f64; 24] {
|
||||
if let Some(p) = egen {
|
||||
return p;
|
||||
}
|
||||
let mut ut = [0.0; 24];
|
||||
for (h, v) in ut.iter_mut().enumerate() {
|
||||
if tid.aktiv(h) {
|
||||
*v = effekt;
|
||||
}
|
||||
}
|
||||
ut
|
||||
}
|
||||
|
||||
fn los_laster(b: &Bygning, n: &Normerte, logg: &mut Vec<Forutsetning>) -> LostLaster {
|
||||
let bel = b.drift.belysning_effekt.unwrap_or_else(|| {
|
||||
logg.push(Forutsetning {
|
||||
felt: "belysning".into(),
|
||||
verdi: format!(
|
||||
"{:.2} W/m² i {:.0} h/døgn",
|
||||
n.belysning.effekt,
|
||||
n.driftstid.belysning.timer()
|
||||
),
|
||||
kilde: format!("normert, {}", n.kilde),
|
||||
});
|
||||
n.belysning.effekt
|
||||
});
|
||||
let utstyr = b.drift.utstyr_effekt.unwrap_or(n.utstyr.effekt);
|
||||
// Oppgitt utstyrseffekt skalerer varmetilskuddet i samme forhold som den normerte verdien.
|
||||
let utstyr_varme = match b.drift.utstyr_effekt {
|
||||
Some(e) => e * (n.utstyr.varmetilskudd / n.utstyr.effekt.max(1e-9)),
|
||||
None => n.utstyr.varmetilskudd,
|
||||
};
|
||||
let personer = b
|
||||
.drift
|
||||
.personer_varmetilskudd
|
||||
.unwrap_or(n.personer.varmetilskudd);
|
||||
let tappevann = b.tappevann.energibehov.map(|kwh| {
|
||||
// kWh/(m²·år) → W/m² i driftstiden.
|
||||
kwh * 1000.0 / (365.0 * n.driftstid.varmtvann.timer())
|
||||
});
|
||||
let vv = tappevann.unwrap_or_else(|| {
|
||||
logg.push(Forutsetning {
|
||||
felt: "tappevann.energibehov".into(),
|
||||
verdi: format!(
|
||||
"{:.1} kWh/(m²·år)",
|
||||
n.varmtvann.effekt * n.driftstid.varmtvann.timer() * 365.0 / 1000.0
|
||||
),
|
||||
kilde: format!("normert, {}", n.kilde),
|
||||
});
|
||||
n.varmtvann.effekt
|
||||
});
|
||||
let pumpeeffekt = match b.distribusjon.typ {
|
||||
DistribusjonType::Punkt => b.distribusjon.pumpe_effekt.unwrap_or(0.0),
|
||||
_ => b
|
||||
.distribusjon
|
||||
.pumpe_effekt
|
||||
.unwrap_or(n.pumper.effekt_vannbaaren),
|
||||
};
|
||||
LostLaster {
|
||||
belysning: profil(bel, n.driftstid.belysning, n.profil_belysning),
|
||||
utstyr_effekt: profil(utstyr, n.driftstid.utstyr, n.profil_utstyr),
|
||||
utstyr_varme: profil(utstyr_varme, n.driftstid.utstyr, None),
|
||||
personer: profil(personer, n.driftstid.personer, n.profil_personer),
|
||||
varmtvann: profil(vv, n.driftstid.varmtvann, n.profil_varmtvann),
|
||||
pumper: profil(pumpeeffekt, n.driftstid.oppvarming, None),
|
||||
oppvarming_driftstid: n.driftstid.oppvarming,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,598 @@
|
||||
//! 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 (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
|
||||
}
|
||||
|
||||
#[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;
|
||||
Reference in New Issue
Block a user