2026-09-14 18:03:00 +02:00
|
|
|
|
//! 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::*;
|
2026-09-14 19:16:18 +02:00
|
|
|
|
use crate::motor::grunn;
|
2026-09-14 18:03:00 +02:00
|
|
|
|
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,
|
2026-09-14 19:16:18 +02:00
|
|
|
|
/// U-verdien som brukes i timesberegningen. Mot grunnen er den korrigert etter
|
|
|
|
|
|
/// NS-EN ISO 13370 og dermed lavere enn konstruksjonens egen.
|
2026-09-14 18:03:00 +02:00
|
|
|
|
pub u_verdi: f64,
|
2026-09-14 19:16:18 +02:00
|
|
|
|
/// Konstruksjonens egen U-verdi, som varmetapsbudsjettet og TEK-kravene gjelder.
|
|
|
|
|
|
pub u_konstruksjon: f64,
|
2026-09-14 18:03:00 +02:00
|
|
|
|
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,
|
2026-09-14 19:16:18 +02:00
|
|
|
|
/// Indeks til den uoppvarmede sonen flaten vender mot, når temperaturen beregnes.
|
|
|
|
|
|
pub sone: Option<usize>,
|
2026-09-14 18:03:00 +02:00
|
|
|
|
/// 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,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-14 19:16:18 +02:00
|
|
|
|
/// En uoppvarmet sone med varmebalanse etter NS-EN ISO 13789.
|
|
|
|
|
|
#[derive(Serialize, Clone, Debug)]
|
|
|
|
|
|
pub struct LostSone {
|
|
|
|
|
|
pub navn: String,
|
|
|
|
|
|
/// Varmetransport fra den oppvarmede delen inn i sonen, W/K.
|
|
|
|
|
|
pub h_mot_oppvarmet: f64,
|
|
|
|
|
|
/// Varmetransport fra sonen til uteluft, inkludert ventilasjon, W/K.
|
|
|
|
|
|
pub h_mot_ute: f64,
|
|
|
|
|
|
/// Varmetransport fra sonen til grunnen, W/K.
|
|
|
|
|
|
pub h_mot_grunn: f64,
|
|
|
|
|
|
/// Andel av varmeanleggets distribusjonstap som avgis i sonen.
|
|
|
|
|
|
pub andel_distribusjonstap: f64,
|
|
|
|
|
|
/// Andel av tappevannsanleggets tap som avgis i sonen.
|
|
|
|
|
|
pub andel_tappevannstap: f64,
|
|
|
|
|
|
/// Fast varmetilskudd, W.
|
|
|
|
|
|
pub fast_effekt: f64,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl LostSone {
|
|
|
|
|
|
/// Sonens temperatur, °C, av den stasjonære varmebalansen.
|
|
|
|
|
|
pub fn temperatur(&self, t_inne: f64, t_ute: f64, t_grunn: f64, tilskudd: f64) -> f64 {
|
|
|
|
|
|
let h = self.h_mot_oppvarmet + self.h_mot_ute + self.h_mot_grunn;
|
|
|
|
|
|
if h <= 1e-9 {
|
|
|
|
|
|
return t_ute;
|
|
|
|
|
|
}
|
|
|
|
|
|
(self.h_mot_oppvarmet * t_inne
|
|
|
|
|
|
+ self.h_mot_ute * t_ute
|
|
|
|
|
|
+ self.h_mot_grunn * t_grunn
|
|
|
|
|
|
+ tilskudd)
|
|
|
|
|
|
/ h
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-14 18:03:00 +02:00
|
|
|
|
#[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>,
|
2026-09-14 19:16:18 +02:00
|
|
|
|
pub soner: Vec<LostSone>,
|
2026-09-14 18:03:00 +02:00
|
|
|
|
/// 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,
|
2026-09-15 20:05:20 +02:00
|
|
|
|
/// Kjølebatteri: tilluften holdes på denne temperaturen i driftstiden.
|
|
|
|
|
|
pub tilluft_kjoling: Option<f64>,
|
2026-09-14 18:03:00 +02:00
|
|
|
|
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],
|
2026-09-15 20:05:20 +02:00
|
|
|
|
/// Driftstider med driftsdager og driftsuker. Profilene over gjelder bare på driftsdager.
|
|
|
|
|
|
pub driftstider: crate::data::Driftstider,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl LostLaster {
|
|
|
|
|
|
/// Om time `t` i året faller på en driftsdag.
|
|
|
|
|
|
pub fn driftsdag(&self, t: usize) -> bool {
|
|
|
|
|
|
self.driftstider.driftsdag(t / 24)
|
|
|
|
|
|
}
|
2026-09-14 18:03:00 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
2026-09-14 19:16:18 +02:00
|
|
|
|
let ledningsevne = b.grunn.ledningsevne.unwrap_or_else(|| {
|
|
|
|
|
|
if b.flate
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.chain(b.sone.iter().flat_map(|s| s.flater.iter()))
|
|
|
|
|
|
.any(|x| x.mot == Mot::Grunn)
|
|
|
|
|
|
{
|
|
|
|
|
|
f.push(Forutsetning {
|
|
|
|
|
|
felt: "grunn.ledningsevne".into(),
|
|
|
|
|
|
verdi: format!("{:.1} W/(m·K)", grunn::LEDNINGSEVNE_JORD),
|
|
|
|
|
|
kilde: "NS-EN ISO 13370 tabell 1, leire eller silt".into(),
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
grunn::LEDNINGSEVNE_JORD
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Sonenavn må være kjent før flatene løses, slik at flatene kan peke på dem.
|
|
|
|
|
|
let sonenavn: Vec<String> = b.sone.iter().map(|s| s.navn.clone()).collect();
|
|
|
|
|
|
for fl in &b.flate {
|
|
|
|
|
|
if let Some(n) = &fl.sone {
|
|
|
|
|
|
if !sonenavn.contains(n) {
|
|
|
|
|
|
bail!("flaten «{}» viser til den ukjente sonen «{}»", fl.navn, n);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-14 18:03:00 +02:00
|
|
|
|
let mut flater = Vec::new();
|
|
|
|
|
|
for fl in &b.flate {
|
2026-09-14 19:16:18 +02:00
|
|
|
|
los_flate_med_grunn(
|
|
|
|
|
|
fl,
|
|
|
|
|
|
&b.flate,
|
|
|
|
|
|
&materialer,
|
|
|
|
|
|
periode,
|
|
|
|
|
|
ledningsevne,
|
|
|
|
|
|
&sonenavn,
|
|
|
|
|
|
&mut flater,
|
|
|
|
|
|
&mut f,
|
|
|
|
|
|
)?;
|
2026-09-14 18:03:00 +02:00
|
|
|
|
}
|
|
|
|
|
|
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]]");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-14 19:16:18 +02:00
|
|
|
|
// Uoppvarmede soner.
|
|
|
|
|
|
let mut soner = Vec::new();
|
|
|
|
|
|
for (i, sone) in b.sone.iter().enumerate() {
|
|
|
|
|
|
let mut egne = Vec::new();
|
|
|
|
|
|
for fl in &sone.flater {
|
|
|
|
|
|
los_flate_med_grunn(
|
|
|
|
|
|
fl,
|
|
|
|
|
|
&sone.flater,
|
|
|
|
|
|
&materialer,
|
|
|
|
|
|
periode,
|
|
|
|
|
|
ledningsevne,
|
|
|
|
|
|
&[],
|
|
|
|
|
|
&mut egne,
|
|
|
|
|
|
&mut f,
|
|
|
|
|
|
)?;
|
|
|
|
|
|
}
|
|
|
|
|
|
let h_mot_oppvarmet: f64 = flater
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.filter(|x| x.sone == Some(i))
|
|
|
|
|
|
.map(|x| x.areal * x.u_verdi)
|
|
|
|
|
|
.sum();
|
|
|
|
|
|
if h_mot_oppvarmet <= 1e-9 {
|
|
|
|
|
|
bail!(
|
|
|
|
|
|
"sonen «{}» har ingen flater mot den oppvarmede delen; sett `sone = \"{}\"` på etasjeskilleren",
|
|
|
|
|
|
sone.navn,
|
|
|
|
|
|
sone.navn
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
let volum = sone.volum.unwrap_or(0.0);
|
|
|
|
|
|
let luftveksling = sone.luftveksling.unwrap_or_else(|| {
|
|
|
|
|
|
f.push(Forutsetning {
|
|
|
|
|
|
felt: format!("sone.{}.luftveksling", sone.navn),
|
|
|
|
|
|
verdi: "0,3 h⁻¹".into(),
|
|
|
|
|
|
kilde: "antatt luftveksling i uoppvarmet sone".into(),
|
|
|
|
|
|
});
|
|
|
|
|
|
0.3
|
|
|
|
|
|
});
|
|
|
|
|
|
let h_mot_ute: f64 = egne
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.filter(|x| x.mot == Mot::Ute)
|
|
|
|
|
|
.map(|x| x.areal * x.u_verdi)
|
|
|
|
|
|
.sum::<f64>()
|
|
|
|
|
|
+ volum * luftveksling * crate::motor::RHO_C_LUFT;
|
|
|
|
|
|
let h_mot_grunn: f64 = egne
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.filter(|x| x.mot == Mot::Grunn)
|
|
|
|
|
|
.map(|x| x.areal * x.u_verdi)
|
|
|
|
|
|
.sum();
|
|
|
|
|
|
soner.push(LostSone {
|
|
|
|
|
|
navn: sone.navn.clone(),
|
|
|
|
|
|
h_mot_oppvarmet,
|
|
|
|
|
|
h_mot_ute,
|
|
|
|
|
|
h_mot_grunn,
|
|
|
|
|
|
andel_distribusjonstap: sone.andel_distribusjonstap.unwrap_or(1.0),
|
|
|
|
|
|
andel_tappevannstap: sone.andel_tappevannstap.unwrap_or(0.0),
|
|
|
|
|
|
fast_effekt: sone.fast_effekt,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-14 18:03:00 +02:00
|
|
|
|
// 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.
|
2026-09-15 20:05:20 +02:00
|
|
|
|
let lufting = if b.lufting.ingen
|
|
|
|
|
|
|| settpunkt_kjoling.is_some()
|
|
|
|
|
|
|| ventilasjon.tilluft_kjoling.is_some()
|
|
|
|
|
|
{
|
2026-09-14 18:03:00 +02:00
|
|
|
|
None
|
|
|
|
|
|
} else {
|
2026-09-14 18:05:16 +02:00
|
|
|
|
// Uten oppgitt grense brukes den normerte settpunkttemperaturen for kjøling.
|
|
|
|
|
|
// NS 3031 setter denne også for bygninger uten kjøleanlegg; overskuddsvarmen
|
|
|
|
|
|
// antas luftet ut, og energien telles ikke.
|
2026-09-14 18:03:00 +02:00
|
|
|
|
let sett = b.lufting.settpunkt.unwrap_or_else(|| {
|
|
|
|
|
|
f.push(Forutsetning {
|
|
|
|
|
|
felt: "lufting.settpunkt".into(),
|
2026-09-14 18:05:16 +02:00
|
|
|
|
verdi: format!("{:.0} °C", normerte.settpunkt.kjoling),
|
|
|
|
|
|
kilde:
|
|
|
|
|
|
"normert settpunkttemperatur for kjøling; bygningen har ikke kjøleanlegg"
|
|
|
|
|
|
.into(),
|
2026-09-14 18:03:00 +02:00
|
|
|
|
});
|
2026-09-14 18:05:16 +02:00
|
|
|
|
normerte.settpunkt.kjoling
|
2026-09-14 18:03:00 +02:00
|
|
|
|
});
|
|
|
|
|
|
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,
|
2026-09-14 19:16:18 +02:00
|
|
|
|
soner,
|
2026-09-14 18:03:00 +02:00
|
|
|
|
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()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-14 19:16:18 +02:00
|
|
|
|
/// Løser en flate, og deler kjellervegger i en del over og en del under terreng.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Mot grunnen brukes NS-EN ISO 13370: jordas varmemotstand gjør at en uisolert kjellervegg
|
|
|
|
|
|
/// eller et uisolert kjellergulv taper langt mindre varme enn U-verdien alene skulle tilsi.
|
|
|
|
|
|
/// Den delen av kjellerveggen som står over terreng er derimot en vanlig yttervegg, og
|
|
|
|
|
|
/// arealet deles mellom de to i forhold til høydene.
|
|
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
|
|
fn los_flate_med_grunn(
|
|
|
|
|
|
fl: &Flate,
|
|
|
|
|
|
naboflater: &[Flate],
|
|
|
|
|
|
materialer: &Materialer,
|
|
|
|
|
|
periode: Option<&crate::data::Periode>,
|
|
|
|
|
|
ledningsevne: f64,
|
|
|
|
|
|
sonenavn: &[String],
|
|
|
|
|
|
ut: &mut Vec<LostFlate>,
|
|
|
|
|
|
logg: &mut Vec<Forutsetning>,
|
|
|
|
|
|
) -> Result<()> {
|
|
|
|
|
|
let mut lost = los_flate(fl, materialer, periode, logg)?;
|
|
|
|
|
|
lost.sone = fl
|
|
|
|
|
|
.sone
|
|
|
|
|
|
.as_ref()
|
|
|
|
|
|
.and_then(|n| sonenavn.iter().position(|x| x == n));
|
|
|
|
|
|
|
|
|
|
|
|
if fl.mot != Mot::Grunn {
|
|
|
|
|
|
ut.push(lost);
|
|
|
|
|
|
return Ok(());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Gulvet mot grunnen gir den ekvivalente tykkelsen som kjellerveggene regnes mot.
|
|
|
|
|
|
let gulv = naboflater
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.filter(|x| x.mot == Mot::Grunn && x.typ == FlateType::Gulv)
|
|
|
|
|
|
.max_by(|a, b| {
|
|
|
|
|
|
a.areal
|
|
|
|
|
|
.partial_cmp(&b.areal)
|
|
|
|
|
|
.unwrap_or(std::cmp::Ordering::Equal)
|
|
|
|
|
|
});
|
|
|
|
|
|
let (u_gulv_k, areal_gulv, omkrets) = match gulv {
|
|
|
|
|
|
Some(g) => (
|
|
|
|
|
|
los_flate(g, materialer, periode, &mut Vec::new())?.u_verdi,
|
|
|
|
|
|
g.areal,
|
|
|
|
|
|
g.omkrets.unwrap_or(0.0),
|
|
|
|
|
|
),
|
|
|
|
|
|
None => (lost.u_verdi, fl.areal, fl.omkrets.unwrap_or(0.0)),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
match fl.typ {
|
|
|
|
|
|
FlateType::Gulv => {
|
|
|
|
|
|
let dybde = fl.dybde.unwrap_or(0.0);
|
|
|
|
|
|
if fl.omkrets.is_none() {
|
|
|
|
|
|
logg.push(Forutsetning {
|
|
|
|
|
|
felt: format!("flate.{}.omkrets", fl.navn),
|
|
|
|
|
|
verdi: format!("{:.1} m", 4.0 * fl.areal.sqrt()),
|
|
|
|
|
|
kilde: "antatt kvadratisk gulv; oppgi omkrets for riktig B′".into(),
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
let u = grunn::u_gulv(
|
|
|
|
|
|
lost.u_verdi,
|
|
|
|
|
|
fl.areal,
|
|
|
|
|
|
fl.omkrets.unwrap_or(0.0),
|
|
|
|
|
|
dybde,
|
|
|
|
|
|
ledningsevne,
|
|
|
|
|
|
);
|
|
|
|
|
|
logg.push(Forutsetning {
|
|
|
|
|
|
felt: format!("flate.{}.u_mot_grunn", fl.navn),
|
|
|
|
|
|
verdi: format!("{u:.3} W/(m²K)"),
|
|
|
|
|
|
kilde: format!(
|
|
|
|
|
|
"NS-EN ISO 13370 av konstruksjonens {:.2} W/(m²K) og {dybde:.1} m dybde",
|
|
|
|
|
|
lost.u_verdi
|
|
|
|
|
|
),
|
|
|
|
|
|
});
|
|
|
|
|
|
lost.u_verdi = u;
|
|
|
|
|
|
ut.push(lost);
|
|
|
|
|
|
}
|
|
|
|
|
|
FlateType::Vegg | FlateType::Tak => {
|
|
|
|
|
|
let dybde = fl.dybde.unwrap_or(0.0);
|
|
|
|
|
|
let over = fl.over_terreng.unwrap_or(0.0);
|
|
|
|
|
|
let hoyde = dybde + over;
|
|
|
|
|
|
let andel_over = if hoyde > 1e-6 { over / hoyde } else { 0.0 };
|
|
|
|
|
|
if fl.dybde.is_none() {
|
|
|
|
|
|
logg.push(Forutsetning {
|
|
|
|
|
|
felt: format!("flate.{}.dybde", fl.navn),
|
|
|
|
|
|
verdi: "0 m".into(),
|
|
|
|
|
|
kilde: "uten oppgitt dybde regnes veggen som å stå i terrengnivå".into(),
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
if andel_over > 1e-6 {
|
|
|
|
|
|
let mut over_flate = lost.clone();
|
|
|
|
|
|
over_flate.navn = format!("{} (over terreng)", lost.navn);
|
|
|
|
|
|
over_flate.areal = fl.areal * andel_over;
|
|
|
|
|
|
over_flate.mot = Mot::Ute;
|
|
|
|
|
|
ut.push(over_flate);
|
|
|
|
|
|
}
|
|
|
|
|
|
if andel_over < 1.0 - 1e-6 {
|
|
|
|
|
|
let u = grunn::u_kjellervegg(
|
|
|
|
|
|
lost.u_verdi,
|
|
|
|
|
|
u_gulv_k,
|
|
|
|
|
|
areal_gulv,
|
|
|
|
|
|
omkrets,
|
|
|
|
|
|
dybde,
|
|
|
|
|
|
ledningsevne,
|
|
|
|
|
|
);
|
|
|
|
|
|
logg.push(Forutsetning {
|
|
|
|
|
|
felt: format!("flate.{}.u_mot_grunn", fl.navn),
|
|
|
|
|
|
verdi: format!("{u:.3} W/(m²K)"),
|
|
|
|
|
|
kilde: format!(
|
|
|
|
|
|
"NS-EN ISO 13370 av konstruksjonens {:.2} W/(m²K) og {dybde:.1} m under terreng",
|
|
|
|
|
|
lost.u_verdi
|
|
|
|
|
|
),
|
|
|
|
|
|
});
|
|
|
|
|
|
let mut under = lost;
|
|
|
|
|
|
if andel_over > 1e-6 {
|
|
|
|
|
|
under.navn = format!("{} (under terreng)", under.navn);
|
|
|
|
|
|
}
|
|
|
|
|
|
under.areal = fl.areal * (1.0 - andel_over);
|
|
|
|
|
|
under.u_verdi = u;
|
|
|
|
|
|
ut.push(under);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
Ok(())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-14 18:03:00 +02:00
|
|
|
|
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,
|
2026-09-14 19:16:18 +02:00
|
|
|
|
u_konstruksjon: u,
|
2026-09-14 18:03:00 +02:00
|
|
|
|
helning,
|
|
|
|
|
|
orientering: fl.orientering,
|
|
|
|
|
|
kappa,
|
|
|
|
|
|
masseplassering: fl.masseplassering,
|
|
|
|
|
|
mot: fl.mot,
|
|
|
|
|
|
b_faktor,
|
|
|
|
|
|
absorptans: fl.absorptans.unwrap_or(0.6),
|
|
|
|
|
|
glass: false,
|
2026-09-14 19:16:18 +02:00
|
|
|
|
sone: None,
|
2026-09-14 18:03:00 +02:00
|
|
|
|
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,
|
2026-09-14 19:16:18 +02:00
|
|
|
|
u_konstruksjon: v.u_verdi,
|
2026-09-14 18:03:00 +02:00
|
|
|
|
helning: v.helning,
|
|
|
|
|
|
orientering: v.orientering,
|
|
|
|
|
|
kappa: 0.0,
|
|
|
|
|
|
masseplassering: Masseplassering::Fordelt,
|
|
|
|
|
|
mot: Mot::Ute,
|
|
|
|
|
|
b_faktor: 1.0,
|
|
|
|
|
|
absorptans: 0.0,
|
|
|
|
|
|
glass: true,
|
2026-09-14 19:16:18 +02:00
|
|
|
|
sone: None,
|
2026-09-14 18:03:00 +02:00
|
|
|
|
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));
|
2026-09-15 20:05:20 +02:00
|
|
|
|
// Kjølebatteriet holder konstant tilluftstemperatur i driftstiden, slik DiBKs modellbygg
|
|
|
|
|
|
// for energirammene er regnet (Multiconsult 2014, avsnitt 7.3).
|
|
|
|
|
|
let tilluft_kjoling = (v.kjolebatteri == Some(true)).then(|| {
|
|
|
|
|
|
v.tilluft_kjoling.unwrap_or_else(|| {
|
|
|
|
|
|
let t = n
|
|
|
|
|
|
.settpunkt
|
|
|
|
|
|
.ventilasjon_tilluft_kjoling
|
|
|
|
|
|
.unwrap_or(n.settpunkt.ventilasjon_tilluft);
|
|
|
|
|
|
logg.push(Forutsetning {
|
|
|
|
|
|
felt: "ventilasjon.tilluft_kjoling".into(),
|
|
|
|
|
|
verdi: format!("{t:.0} °C"),
|
|
|
|
|
|
kilde: format!("normert tilluftstemperatur, {}", n.kilde),
|
|
|
|
|
|
});
|
|
|
|
|
|
t
|
|
|
|
|
|
})
|
|
|
|
|
|
});
|
2026-09-14 18:03:00 +02:00
|
|
|
|
LostVentilasjon {
|
|
|
|
|
|
typ: v.typ,
|
|
|
|
|
|
luftmengde,
|
|
|
|
|
|
luftmengde_natt,
|
|
|
|
|
|
gjenvinning,
|
|
|
|
|
|
sfp,
|
|
|
|
|
|
varmebatteri,
|
|
|
|
|
|
tilluft_settpunkt: n.settpunkt.ventilasjon_tilluft,
|
2026-09-15 20:05:20 +02:00
|
|
|
|
tilluft_kjoling,
|
2026-09-14 18:03:00 +02:00
|
|
|
|
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!(
|
2026-09-15 20:05:20 +02:00
|
|
|
|
"{:.2} W/m² i {:.0} h/døgn, {} dager/uke, {} uker/år",
|
2026-09-14 18:03:00 +02:00
|
|
|
|
n.belysning.effekt,
|
2026-09-15 20:05:20 +02:00
|
|
|
|
n.driftstid.belysning.timer(),
|
|
|
|
|
|
n.driftstid.dager_per_uke,
|
|
|
|
|
|
n.driftstid.uker_per_aar
|
2026-09-14 18:03:00 +02:00
|
|
|
|
),
|
|
|
|
|
|
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);
|
2026-09-15 20:05:20 +02:00
|
|
|
|
let driftsdager = n.driftstid.driftsdager_per_aar() as f64;
|
2026-09-14 18:03:00 +02:00
|
|
|
|
let tappevann = b.tappevann.energibehov.map(|kwh| {
|
2026-09-15 20:05:20 +02:00
|
|
|
|
// kWh/(m²·år) → W/m² i driftstiden på driftsdager.
|
|
|
|
|
|
kwh * 1000.0 / (driftsdager * n.driftstid.varmtvann.timer())
|
2026-09-14 18:03:00 +02:00
|
|
|
|
});
|
|
|
|
|
|
let vv = tappevann.unwrap_or_else(|| {
|
|
|
|
|
|
logg.push(Forutsetning {
|
|
|
|
|
|
felt: "tappevann.energibehov".into(),
|
|
|
|
|
|
verdi: format!(
|
|
|
|
|
|
"{:.1} kWh/(m²·år)",
|
2026-09-15 20:05:20 +02:00
|
|
|
|
n.varmtvann.effekt * n.driftstid.varmtvann.timer() * driftsdager / 1000.0
|
2026-09-14 18:03:00 +02:00
|
|
|
|
),
|
|
|
|
|
|
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),
|
2026-09-15 20:05:20 +02:00
|
|
|
|
driftstider: n.driftstid.clone(),
|
2026-09-14 18:03:00 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|