Files
enok/src/rapport/enova_xml.rs
T
blandClaude Opus 5 d02a3d9666 Legg til barnehage, kontor-, skole-, idretts- og kulturbygg
Driftstidene har nå dager per uke og uker per år, og lastene gjelder bare på driftsdager.
Normerte verdier for de nye kategoriene er hentet fra publiserte Simien-utskrifter og
DiBK-utredninger og kontrollert mot energibudsjettene i de samme kildene. Boligblokk har
fått egne verdier i stedet for en kopi av småhus.

Balanserte anlegg kan ha kjølebatteri, og varmegjenvinneren reguleres ned når den ellers
ville varmet tilluften over settpunktet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015GeH3y8fwTaTudaD88P57o
2026-09-15 20:05:20 +02:00

283 lines
9.9 KiB
Rust

//! 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('&', "&amp;")
.replace('<', "&lt;")
.replace('>', "&gt;")
.replace('"', "&quot;")
}
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::<f64>()
/ 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#"<?xml version="1.0" encoding="UTF-8"?>"#);
let _ = writeln!(
s,
r#"<Enheter xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">"#
);
let _ = writeln!(s, " <Enhet>");
// ── adresse ────────────────────────────────────────────────────
let _ = writeln!(
s,
" <Adresser>\n <BygningsAdresse>\n <AdresseInfo>"
);
if let Some(a) = &b.adresse {
let _ = writeln!(s, " <Gateadresse>{}</Gateadresse>", esc(a));
}
let _ = writeln!(
s,
" <BygningsbeskrivelseFritekst>{}</BygningsbeskrivelseFritekst>",
esc(&b.navn)
);
let _ = writeln!(
s,
" </AdresseInfo>\n </BygningsAdresse>\n </Adresser>"
);
// ── inngangsverdier ────────────────────────────────────────────
let _ = writeln!(s, " <Inngangsverdi>");
let _ = writeln!(
s,
" <Bygningskategori>{}</Bygningskategori>",
esc(&r.karakter.kategorinavn.to_uppercase())
);
let _ = writeln!(s, " <Byggeaar>{}</Byggeaar>", 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, " <M2Yttervegger>{vegger:.0}</M2Yttervegger>");
let _ = writeln!(s, " <M2Tak>{tak:.0}</M2Tak>");
let _ = writeln!(s, " <M2Gulv>{gulv:.0}</M2Gulv>");
let _ = writeln!(s, " <M2VinduDorGlass>{glass:.0}</M2VinduDorGlass>");
let _ = writeln!(s, " <BRAOppvarmet>{:.0}</BRAOppvarmet>", l.bra);
let _ = writeln!(s, " <BRA>{:.0}</BRA>", l.bra);
let _ = writeln!(s, " <M3OppvarmetLuft>{:.0}</M3OppvarmetLuft>", l.volum);
let _ = writeln!(
s,
" <UYttervegger>{:.2}</UYttervegger>",
u_snitt(r, |f| !f.glass && f.typ == FlateType::Vegg)
);
let _ = writeln!(
s,
" <UTak>{:.2}</UTak>",
u_snitt(r, |f| !f.glass && f.typ == FlateType::Tak)
);
let _ = writeln!(
s,
" <UGulv>{:.2}</UGulv>",
u_snitt(r, |f| !f.glass && f.typ == FlateType::Gulv)
);
let _ = writeln!(
s,
" <UVinduer>{:.2}</UVinduer>",
u_snitt(r, |f| f.glass)
);
let _ = writeln!(
s,
" <ArealAndelVinduDorGlass>{:.1}</ArealAndelVinduDorGlass>",
glass / l.bra * 100.0
);
let _ = writeln!(
s,
" <NormalisertKuldebro>{:.2}</NormalisertKuldebro>",
l.kuldebro_w_per_k / l.bra
);
let _ = writeln!(s, " <Lekkasjetall>{:.2}</Lekkasjetall>", l.n50);
let _ = writeln!(
s,
" <TempVirkningsgradVarmegjenvinner>{:.2}</TempVirkningsgradVarmegjenvinner>",
l.ventilasjon.gjenvinning * 100.0
);
let _ = writeln!(
s,
" <VifteeffektLuftmengderIDriftstiden>{:.2}</VifteeffektLuftmengderIDriftstiden>",
l.ventilasjon.sfp
);
let _ = writeln!(
s,
" <VentilasjonsluftmengdeIDriftstidenGjSn>{:.2}</VentilasjonsluftmengdeIDriftstidenGjSn>",
l.ventilasjon.luftmengde
);
let _ = writeln!(
s,
" <SettpunkttemperaturOppvarming>{:.1}</SettpunkttemperaturOppvarming>",
l.settpunkt_oppvarming
);
let _ = writeln!(
s,
" <SettpunkttemperaturKjoling>{:.1}</SettpunkttemperaturKjoling>",
l.settpunkt_kjoling.unwrap_or(0.0)
);
let _ = writeln!(s, " <Driftstider>");
let d = &l.laster.driftstider.oppvarming;
let _ = writeln!(
s,
" <Ventilasjon>{:.0}</Ventilasjon>",
l.ventilasjon.driftstid.timer()
);
let _ = writeln!(s, " <Oppvarming>{:.0}</Oppvarming>", d.timer());
let _ = writeln!(s, " </Driftstider>");
let _ = writeln!(
s,
" <Ventilasjonstype>{}</Ventilasjonstype>",
match l.ventilasjon.typ {
VentilasjonType::Balansert => "BALANSERT",
VentilasjonType::Avtrekk => "AVTREKK",
VentilasjonType::Naturlig => "NATURLIG",
}
);
let kilder: Vec<String> = r
.systemer
.kilder
.iter()
.filter(|k| k.andel_rom > 1e-6)
.map(|k| k.beskrivelse.clone())
.collect();
let _ = writeln!(
s,
" <Oppvarmingssystem>{}</Oppvarmingssystem>",
esc(&kilder.join("; "))
);
let _ = writeln!(
s,
" <KlimastasjonKilde>{} (TMY CERRA 1991-2020)</KlimastasjonKilde>",
esc(&r.klimanavn)
);
let _ = writeln!(s, " <Programvare>");
let _ = writeln!(s, " <Applikasjon>enok</Applikasjon>");
let _ = writeln!(
s,
" <Versjon>{}</Versjon>",
env!("CARGO_PKG_VERSION")
);
let _ = writeln!(
s,
" <FriBeskrivelse>Dynamisk timesberegning etter NS 3031:2025 / NS-EN ISO 52016-1</FriBeskrivelse>"
);
let _ = writeln!(s, " </Programvare>");
if let Some(e) = &b.energiraadgiver {
let _ = writeln!(
s,
" <Energiradgiver>\n <Navn>{}</Navn>\n </Energiradgiver>",
esc(e)
);
}
let _ = writeln!(s, " </Inngangsverdi>");
// ── beregningsresultat ─────────────────────────────────────────
let _ = writeln!(s, " <Beregningsresultat>");
let _ = writeln!(s, " <NettoEnergibudsjettPrKvm>");
let p = |navn: &str, v: f64| format!(" <{navn}>{:.1}</{navn}>", 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,
" <TotaltNettoEnergibehov>{:.1}</TotaltNettoEnergibehov>",
r.budsjett.netto_spesifikk(l.bra)
);
let _ = writeln!(s, " </NettoEnergibudsjettPrKvm>");
let _ = writeln!(
s,
" <LevertEnergiSpesifiktNormalisertKlima>{:.2}</LevertEnergiSpesifiktNormalisertKlima>",
r.levert.spesifikk_vektet
);
let _ = writeln!(
s,
" <LevertEnergiNormalisertKlima>{:.0}</LevertEnergiNormalisertKlima>",
r.levert.vektet_totalt
);
let _ = writeln!(s, " <ForventetLevertEnergi>\n <LevertEnergi>");
let hent = |b: Energibaerer| -> f64 {
r.levert
.per_baerer
.iter()
.find(|(x, _)| *x == b)
.map(|(_, v)| *v)
.unwrap_or(0.0)
};
let _ = writeln!(
s,
" <Elektrisitet>{:.0}</Elektrisitet>",
hent(Energibaerer::Elektrisitet)
);
let _ = writeln!(s, " <Olje>{:.0}</Olje>", hent(Energibaerer::Olje));
let _ = writeln!(s, " <Gass>{:.0}</Gass>", hent(Energibaerer::Gass));
let _ = writeln!(
s,
" <Fjernvarme>{:.0}</Fjernvarme>",
hent(Energibaerer::Fjernvarme)
);
let bio = hent(Energibaerer::BiobrenselLukket) + hent(Energibaerer::BiobrenselAapent);
let _ = writeln!(s, " <Biobrensel>{bio:.0}</Biobrensel>");
let _ = writeln!(
s,
" <AnnenEnergivare>{:.0}</AnnenEnergivare>",
hent(Energibaerer::Annet)
);
let _ = writeln!(
s,
" <Totalt>{:.0}</Totalt>",
r.levert.levert_totalt
);
let _ = writeln!(s, " </LevertEnergi>\n </ForventetLevertEnergi>");
let _ = writeln!(
s,
" <Energikarakter>{}</Energikarakter>",
r.karakter.bokstav
);
let _ = writeln!(s, " </Beregningsresultat>");
let _ = writeln!(s, " </Enhet>\n</Enheter>");
s
}