//! Eksport til XML-formatet energimerkesystemet (EMS) tar imot for NS 3031:2014-beregninger. //! //! Feltnavnene følger Enovas eksempelfil i veiledningen for opplasting av flere filer. //! Formatet er knyttet til NS 3031:2014; Enova tar i tillegg imot JSON etter NS 3031:2025, //! men den spesifikasjonen er bare tilgjengelig bak ekspertinnlogging. Bruk derfor denne //! eksporten bevisst: tallene er beregnet etter NS 3031:2025 og skrives inn i 2014-strukturen. use crate::kjor::Resultat; use crate::modell::{FlateType, VentilasjonType}; use crate::systemer::Energibaerer; use crate::Result; use std::fmt::Write; use std::path::Path; fn esc(s: &str) -> String { s.replace('&', "&") .replace('<', "<") .replace('>', ">") .replace('"', """) } fn areal(r: &Resultat, f: impl Fn(&crate::modell::lost::LostFlate) -> bool) -> f64 { r.lost.flater.iter().filter(|x| f(x)).map(|x| x.areal).sum() } fn u_snitt(r: &Resultat, f: impl Fn(&crate::modell::lost::LostFlate) -> bool + Copy) -> f64 { let a = areal(r, f); if a <= 0.0 { return 0.0; } r.lost .flater .iter() .filter(|x| f(x)) .map(|x| x.areal * x.u_verdi) .sum::() / a } pub fn skriv(r: &Resultat, sti: &Path) -> Result<()> { std::fs::write(sti, kilde(r))?; Ok(()) } pub fn kilde(r: &Resultat) -> String { let l = &r.lost; let b = &r.bygning.bygning; let ts = &r.timeserie; let kwh = crate::motor::Timeserie::sum_kwh; let mut s = String::new(); let _ = writeln!(s, r#""#); let _ = writeln!( s, r#""# ); let _ = writeln!(s, " "); // ── adresse ──────────────────────────────────────────────────── let _ = writeln!( s, " \n \n " ); if let Some(a) = &b.adresse { let _ = writeln!(s, " {}", esc(a)); } let _ = writeln!( s, " {}", esc(&b.navn) ); let _ = writeln!( s, " \n \n " ); // ── inngangsverdier ──────────────────────────────────────────── let _ = writeln!(s, " "); let _ = writeln!( s, " {}", esc(&r.karakter.kategorinavn.to_uppercase()) ); let _ = writeln!(s, " {}", b.byggeaar); let vegger = areal(r, |f| !f.glass && f.typ == FlateType::Vegg); let tak = areal(r, |f| !f.glass && f.typ == FlateType::Tak); let gulv = areal(r, |f| !f.glass && f.typ == FlateType::Gulv); let glass = areal(r, |f| f.glass); let _ = writeln!(s, " {vegger:.0}"); let _ = writeln!(s, " {tak:.0}"); let _ = writeln!(s, " {gulv:.0}"); let _ = writeln!(s, " {glass:.0}"); let _ = writeln!(s, " {:.0}", l.bra); let _ = writeln!(s, " {:.0}", l.bra); let _ = writeln!(s, " {:.0}", l.volum); let _ = writeln!( s, " {:.2}", u_snitt(r, |f| !f.glass && f.typ == FlateType::Vegg) ); let _ = writeln!( s, " {:.2}", u_snitt(r, |f| !f.glass && f.typ == FlateType::Tak) ); let _ = writeln!( s, " {:.2}", u_snitt(r, |f| !f.glass && f.typ == FlateType::Gulv) ); let _ = writeln!( s, " {:.2}", u_snitt(r, |f| f.glass) ); let _ = writeln!( s, " {:.1}", glass / l.bra * 100.0 ); let _ = writeln!( s, " {:.2}", l.kuldebro_w_per_k / l.bra ); let _ = writeln!(s, " {:.2}", l.n50); let _ = writeln!( s, " {:.2}", l.ventilasjon.gjenvinning * 100.0 ); let _ = writeln!( s, " {:.2}", l.ventilasjon.sfp ); let _ = writeln!( s, " {:.2}", l.ventilasjon.luftmengde ); let _ = writeln!( s, " {:.1}", l.settpunkt_oppvarming ); let _ = writeln!( s, " {:.1}", l.settpunkt_kjoling.unwrap_or(0.0) ); let _ = writeln!(s, " "); let d = &l.laster.driftstider.oppvarming; let _ = writeln!( s, " {:.0}", l.ventilasjon.driftstid.timer() ); let _ = writeln!(s, " {:.0}", d.timer()); let _ = writeln!(s, " "); let _ = writeln!( s, " {}", match l.ventilasjon.typ { VentilasjonType::Balansert => "BALANSERT", VentilasjonType::Avtrekk => "AVTREKK", VentilasjonType::Naturlig => "NATURLIG", } ); let kilder: Vec = r .systemer .kilder .iter() .filter(|k| k.andel_rom > 1e-6) .map(|k| k.beskrivelse.clone()) .collect(); let _ = writeln!( s, " {}", esc(&kilder.join("; ")) ); let _ = writeln!( s, " {} (TMY CERRA 1991-2020)", esc(&r.klimanavn) ); let _ = writeln!(s, " "); let _ = writeln!(s, " enok"); let _ = writeln!( s, " {}", env!("CARGO_PKG_VERSION") ); let _ = writeln!( s, " Dynamisk timesberegning etter NS 3031:2025 / NS-EN ISO 52016-1" ); let _ = writeln!(s, " "); if let Some(e) = &b.energiraadgiver { let _ = writeln!( s, " \n {}\n ", esc(e) ); } let _ = writeln!(s, " "); // ── beregningsresultat ───────────────────────────────────────── let _ = writeln!(s, " "); let _ = writeln!(s, " "); let p = |navn: &str, v: f64| format!(" <{navn}>{:.1}", v / l.bra); let _ = writeln!(s, "{}", p("Romoppvarming", kwh(&ts.romoppvarming))); let _ = writeln!(s, "{}", p("Ventilasjonsvarme", kwh(&ts.ventilasjonsvarme))); let _ = writeln!(s, "{}", p("Varmtvann", kwh(&ts.varmtvann))); let _ = writeln!(s, "{}", p("Vifter", kwh(&ts.vifter))); let _ = writeln!(s, "{}", p("Pumper", kwh(&ts.pumper))); let _ = writeln!(s, "{}", p("Belysning", kwh(&ts.belysning))); let _ = writeln!(s, "{}", p("TekniskUtstyr", kwh(&ts.utstyr))); let _ = writeln!(s, "{}", p("Romkjoling", kwh(&ts.romkjoling))); let _ = writeln!( s, "{}", p("Ventilasjonskjoling", kwh(&ts.ventilasjonskjoling)) ); let _ = writeln!( s, " {:.1}", r.budsjett.netto_spesifikk(l.bra) ); let _ = writeln!(s, " "); let _ = writeln!( s, " {:.2}", r.levert.spesifikk_vektet ); let _ = writeln!( s, " {:.0}", r.levert.vektet_totalt ); let _ = writeln!(s, " \n "); let hent = |b: Energibaerer| -> f64 { r.levert .per_baerer .iter() .find(|(x, _)| *x == b) .map(|(_, v)| *v) .unwrap_or(0.0) }; let _ = writeln!( s, " {:.0}", hent(Energibaerer::Elektrisitet) ); let _ = writeln!(s, " {:.0}", hent(Energibaerer::Olje)); let _ = writeln!(s, " {:.0}", hent(Energibaerer::Gass)); let _ = writeln!( s, " {:.0}", hent(Energibaerer::Fjernvarme) ); let bio = hent(Energibaerer::BiobrenselLukket) + hent(Energibaerer::BiobrenselAapent); let _ = writeln!(s, " {bio:.0}"); let _ = writeln!( s, " {:.0}", hent(Energibaerer::Annet) ); let _ = writeln!( s, " {:.0}", r.levert.levert_totalt ); let _ = writeln!(s, " \n "); let _ = writeln!( s, " {}", r.karakter.bokstav ); let _ = writeln!(s, " "); let _ = writeln!(s, " \n"); s }