En ny eksport av Nordhusstykket 5 viste hva mer modellen kan bære, og avdekket feil. Posisjon og klimasted - IfcMapConversion med IfcProjectedCRS gjøres om til breddegrad og lengdegrad. EUREF89 og WGS 84 UTM, NN2000-varianter og Web Mercator forstås. Omregningen er kontrollert mot PROJ. - Vectorworks' standardplassering i Washington, som står når ingenting er satt, kjennes igjen og brukes ikke. - Ny `posisjon` under [klima]. Uten kommune velger beregningen nærmeste av de 358 klimastedene i TMY-arkivet, og sier i rapporten hvor langt unna det ligger. Opplysninger fra modellen - Adressen hentes fra IfcBuilding eller IfcSite. Før ble den første postadressen i filen brukt, og det var firmaets adresse i forfatteropplysningene. - Energirådgiver fra person og organisasjon i forfatteropplysningene. - Byggets navn fra prosjektet når bygningen heter «Default Building». - Referansehøyde og terrenghøyde på bygningen gir hvor mye av grunnmuren som står over bakken. - IsLandmarked gir en merknad om at vernede bygninger er unntatt energiattestplikten. En eksport uten bygningsdeler stopper nå med en feilmelding som peker på lagtilknytningen i eksportdialogen, i stedet for å skrive en tom bygning. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015GeH3y8fwTaTudaD88P57o
251 lines
8.9 KiB
Rust
251 lines
8.9 KiB
Rust
//! 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<crate::systemer::solcelle::Produksjon>,
|
|
pub merknader: Vec<String>,
|
|
}
|
|
|
|
/// 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<Resultat> {
|
|
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<Resultat> {
|
|
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::<f64>()
|
|
.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,
|
|
})
|
|
}
|