Files
enok/src/kjor.rs
T

251 lines
8.9 KiB
Rust
Raw Normal View History

//! Kjører en fullstendig energiberegning: fra bygningsbeskrivelse til energikarakter.
use crate::data::Datakilde;
use crate::klima::{korreksjon, tmy, Vaerdata, REFERANSEKLIMA};
use crate::merking::{karakter, Energikarakter};
use crate::modell::lost::{Forutsetning, Lost};
use crate::modell::Bygning;
use crate::motor::{iso52016::Systemtilskudd, simuler, Timeserie, Varmetapsbudsjett};
use crate::systemer::{self, Effektbehov, Energibudsjett, Levert, Systemoppsett};
use crate::Result;
use serde::Serialize;
/// Alt som kommer ut av én beregning.
pub struct Resultat {
pub bygning: Bygning,
pub lost: Lost,
pub vaerdata: Vaerdata,
pub klimanavn: String,
pub varmetap: Varmetapsbudsjett,
pub timeserie: Timeserie,
pub systemer: Systemoppsett,
pub budsjett: Energibudsjett,
pub levert: Levert,
pub karakter: Energikarakter,
pub effektbehov: Effektbehov,
pub solceller: Option<crate::systemer::solcelle::Produksjon>,
pub merknader: Vec<String>,
}
/// Kompakt, serialiserbart sammendrag for `resultat.json`.
#[derive(Serialize)]
pub struct Sammendrag<'a> {
pub navn: &'a str,
pub kategori: &'a str,
pub byggeaar: i32,
pub oppvarmet_bra: f64,
pub klimasted: &'a str,
pub referanseklima: &'a str,
pub varmetapsbudsjett: &'a Varmetapsbudsjett,
pub energibudsjett: &'a Energibudsjett,
pub netto_spesifikk: f64,
pub levert: &'a Levert,
pub energikarakter: &'a Energikarakter,
pub effektbehov: &'a Effektbehov,
pub forutsetninger: &'a [Forutsetning],
pub merknader: &'a [String],
pub maanedsverdier: Maanedsverdier,
}
#[derive(Serialize)]
pub struct Maanedsverdier {
pub maaned: [&'static str; 12],
pub romoppvarming: [f64; 12],
pub ventilasjonsvarme: [f64; 12],
pub varmtvann: [f64; 12],
pub vifter_pumper: [f64; 12],
pub belysning: [f64; 12],
pub utstyr: [f64; 12],
pub kjoling: [f64; 12],
pub utetemperatur: [f64; 12],
}
impl Resultat {
pub fn sammendrag(&self) -> Sammendrag<'_> {
let m = |s: &[f64]| Timeserie::maaned_kwh(s);
let mut vifter_pumper = m(&self.timeserie.vifter);
let pumper = m(&self.timeserie.pumper);
let mut kjoling = m(&self.timeserie.romkjoling);
let ventkjol = m(&self.timeserie.ventilasjonskjoling);
for i in 0..12 {
vifter_pumper[i] += pumper[i];
kjoling[i] += ventkjol[i];
}
Sammendrag {
navn: &self.lost.navn,
kategori: self.lost.kategori.nokkel(),
byggeaar: self.lost.byggeaar,
oppvarmet_bra: self.lost.bra,
klimasted: &self.klimanavn,
referanseklima: REFERANSEKLIMA,
varmetapsbudsjett: &self.varmetap,
energibudsjett: &self.budsjett,
netto_spesifikk: self.budsjett.netto_spesifikk(self.lost.bra),
levert: &self.levert,
energikarakter: &self.karakter,
effektbehov: &self.effektbehov,
forutsetninger: &self.lost.forutsetninger,
merknader: &self.merknader,
maanedsverdier: Maanedsverdier {
maaned: crate::MAANEDER,
romoppvarming: m(&self.timeserie.romoppvarming),
ventilasjonsvarme: m(&self.timeserie.ventilasjonsvarme),
varmtvann: m(&self.timeserie.varmtvann),
vifter_pumper,
belysning: m(&self.timeserie.belysning),
utstyr: m(&self.timeserie.utstyr),
kjoling,
utetemperatur: self.vaerdata.maanedsmiddeltemperatur(),
},
}
}
}
/// Henter værdata for bygningen: egen EPW-fil, oppgitt kommune, eller referanseklimaet.
pub fn hent_klima(b: &Bygning) -> Result<(Vaerdata, String, bool)> {
if let Some(sti) = &b.klima.epw {
let v = Vaerdata::fra_fil(sti)?;
// Er kommunen oppgitt, gjelder den for oppslaget av klimakorrigeringsfaktoren.
// Da er navnet uavhengig av hvordan stedsnavnet er skrevet inne i klimafilen.
let navn = b.klima.kommune.clone().unwrap_or_else(|| v.sted.clone());
let er_referanse = crate::data::normaliser_stedsnavn(&navn)
== crate::data::normaliser_stedsnavn(REFERANSEKLIMA);
return Ok((v, navn, er_referanse));
}
// Uten kommune, men med posisjon, velges klimastedet nærmest posisjonen.
let sted = match (&b.klima.kommune, b.klima.posisjon) {
(Some(k), _) => k.clone(),
(None, Some([bredde, lengde])) => {
tmy::naermeste_kommune(&crate::klima::geo::Posisjon { bredde, lengde })?.0
}
(None, None) => REFERANSEKLIMA.to_string(),
};
let er_referanse = crate::data::normaliser_stedsnavn(&sted)
== crate::data::normaliser_stedsnavn(REFERANSEKLIMA);
let v = tmy::for_kommune(&sted)?;
Ok((v, sted, er_referanse))
}
pub fn kjor(b: &Bygning, kilde: &Datakilde) -> Result<Resultat> {
let lost = Lost::fra(b, kilde)?;
let (vaerdata, klimanavn, er_referanse) = hent_klima(b)?;
kjor_med_klima(b, kilde, lost, vaerdata, klimanavn, er_referanse)
}
pub fn kjor_med_klima(
b: &Bygning,
kilde: &Datakilde,
lost: Lost,
vaerdata: Vaerdata,
klimanavn: String,
er_referanse: bool,
) -> Result<Resultat> {
let varmetap = Varmetapsbudsjett::beregn(&lost);
let systemer_oppsett = systemer::sett_opp_systemer(b, &lost, kilde)?;
let mut merknader = systemer_oppsett.merknader.clone();
if b.klima.kommune.is_none() && b.klima.epw.is_none() {
if let Some([bredde, lengde]) = b.klima.posisjon {
let bygg = crate::klima::geo::Posisjon { bredde, lengde };
let stasjon = crate::klima::geo::Posisjon {
bredde: vaerdata.bredde,
lengde: vaerdata.lengde,
};
merknader.push(format!(
"Klimastedet {klimanavn} er valgt ut fra posisjonen {bredde:.4}° nord, {lengde:.4}° øst, \
{:.0} km unna. Sett `kommune` under [klima] for å velge et annet.",
bygg.avstand_km(&stasjon)
));
}
}
2026-09-14 21:20:34 +02:00
merknader.extend(
crate::modell::kontroll::kontroller(b)
.into_iter()
.map(|m| m.melding),
);
// Tap fra varmerør og bereder som står i en uoppvarmet sone varmer den sonen, og
// reduserer dermed varmetapet gjennom etasjeskilleren. Andelen som avgis i den
// oppvarmede delen teller som uregulerbart varmetilskudd der.
let tappevannstap = systemer::tappevannstilskudd_totalt(&lost, &systemer_oppsett);
let sone_tappevann: f64 = lost
.soner
.iter()
.map(|s| s.andel_tappevannstap)
.sum::<f64>()
.min(1.0);
let tilskudd = Systemtilskudd {
tappevann: tappevannstap * systemer_oppsett.tappevann_tap_i_sone * (1.0 - sone_tappevann),
sone_fast: lost
.soner
.iter()
.map(|s| s.fast_effekt + tappevannstap * s.andel_tappevannstap)
.collect(),
sone_andel_romvarme: lost
.soner
.iter()
.map(|s| {
s.andel_distribusjonstap * systemer_oppsett.distribusjon_tap_ut
/ (1.0 - systemer_oppsett.distribusjon_tap_ut).max(1e-6)
})
.collect(),
};
let timeserie = simuler(&lost, &vaerdata, tilskudd)?;
let budsjett = systemer::energibudsjett(&timeserie, &systemer_oppsett);
let solceller = b.solcelle.as_ref().map(|s| {
systemer::solcelle::beregn(s, &vaerdata, &timeserie, &systemer_oppsett, lost.albedo)
});
let (egenbruk, eksportert) = solceller
.as_ref()
.map(|p| (p.egenbruk_kwh, p.eksportert_kwh))
.unwrap_or((0.0, 0.0));
let korrigering = if er_referanse {
None
} else {
let tabell = kilde.klimakorreksjon()?;
let klimaavhengig = systemer::klimaavhengig_spesifikk(&budsjett, lost.bra);
let k = korreksjon::korriger(&tabell, &klimanavn, lost.kategori, klimaavhengig);
if k.is_none() {
merknader.push(format!(
"Fant ingen klimakorrigeringsfaktor for «{klimanavn}». Energiytelsen er oppgitt i lokalt \
klima og er ikke sammenlignbar med energimerkeskalaen."
));
}
k
};
let vekting = kilde.vekting()?;
let levert = systemer::levert(
&budsjett,
&vekting,
lost.bra,
korrigering,
egenbruk,
eksportert,
);
let skala = kilde.skala()?;
let karakter = karakter(
skala.for_kategori(lost.kategori)?,
levert.spesifikk_vektet,
lost.skala_areal,
);
let effektbehov = systemer::effektbehov(&timeserie, &systemer_oppsett);
Ok(Resultat {
bygning: b.clone(),
lost,
vaerdata,
klimanavn,
varmetap,
timeserie,
systemer: systemer_oppsett,
budsjett,
levert,
karakter,
effektbehov,
solceller,
merknader,
})
}