//! 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, pub merknader: Vec, } /// 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 { 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 { 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) )); } } 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::() .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(×erie, &systemer_oppsett); let solceller = b.solcelle.as_ref().map(|s| { systemer::solcelle::beregn(s, &vaerdata, ×erie, &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(×erie, &systemer_oppsett); Ok(Resultat { bygning: b.clone(), lost, vaerdata, klimanavn, varmetap, timeserie, systemer: systemer_oppsett, budsjett, levert, karakter, effektbehov, solceller, merknader, }) }