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
283 lines
9.9 KiB
Rust
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('&', "&")
|
|
.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::<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
|
|
}
|