Energiberegning etter NS 3031:2025 med timesmetode og energirapport
enok leser en bygningsbeskrivelse i TOML, henter typisk klimaår for kommunen, simulerer 8760 timer etter NS-EN ISO 52016-1 og skriver energirapport, CSV og JSON. Motor - Femnodemodell for opake bygningsdeler, tonodemodell for vinduer, luftnode og strålingsnode per sone, implisitt løsning per time med egen tett LU-løser. - Varme- og kjølebehov ved eksakt interpolasjon mellom to løsninger, siden systemet er lineært. Vinduslufting for bygninger uten kjøleanlegg. - Solberegning etter ISO 52010-1, kontrollert mot pvlib med avvik under 1 prosent. Beregningsgang - Beregningspunktene A til E: netto, brutto, tilført, levert per energibærer og vektet levert energi, med solceller, klimakorrigering og effektbehov. - Energikarakter etter Enovas skala fra 1.1.2026, med arealkorreksjon. Inndata - TOML med scenarier for tiltak, og import fra IFC, gbXML og regneark. - Normerte verdier, karakterskala, vektingsfaktorer og klimadata i data/, som kan overstyres med --data. Kontroll - Stasjonært varmetap mot U·A·ΔT innenfor 2 prosent, energibalansen innenfor 2 prosent, varmetapsbudsjettet for TEK17-referansebygget identisk med publisert tabell. Kjent avvik i romoppvarming mot Simien 8 er dokumentert i README. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015GeH3y8fwTaTudaD88P57o
This commit is contained in:
@@ -0,0 +1,282 @@
|
||||
//! 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.oppvarming_driftstid;
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user