5 Commits
Author SHA1 Message Date
blandClaude Opus 5 8e9544dd24 Kontroller mot de tretten publiserte referansebyggene
test / test (push) Successful in 1m20s
Multiconsults notat for DiBK gir geometri, komponentverdier og beregnet netto energibehov for
bygningskategoriene bak energirammene i TEK17. Sju av dem, de vi har normerte verdier for, er
gjengitt i eksempler/referansebygg/ og regnes om igjen av en ny test.

Resultatet er at enok regner gjennomgående for lavt, fra 15 til 43 prosent, i snitt 28.
Varmetapstallet for småhuset treffer publisert verdi, og de faste postene stemmer nøyaktig,
så avviket ligger i den dynamiske delen. README beskriver hva som er kontrollert, hva avviket
trolig kommer av, og hva tallene kan brukes til inntil videre.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015GeH3y8fwTaTudaD88P57o
2026-09-21 20:55:05 +02:00
blandClaude Opus 5 cbb1cedc5b Regn omkretsen av gulv mot grunnen av geometrien
Release enok / bygg (release) Successful in 1m14s
test / test (push) Successful in 1m17s
B′ etter NS-EN ISO 13370 trenger omkretsen av grunnflaten. Den regnes nå av omrisset til
dekket i modellen, i stedet for å antas av arealet. For Nordhusstykket 5 gir modellen 39,7 m
mot 40 m satt for hånd.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015GeH3y8fwTaTudaD88P57o
2026-09-21 20:42:21 +02:00
blandClaude Opus 5 1e5d4f05bf Kort ned tabellene i rapporten
Release enok / bygg (release) Successful in 1m14s
test / test (push) Successful in 1m17s
Forutsetningene gjentok den samme setningen for hver bygningsdel. Nå står like forutsetninger
på én rad med antallet de gjelder, og kilden er delt i kolonnene «Normert», «Byggeår»,
«Antatt» og «Standard» med avkryssing. Verdier som bare skiller seg litt, som U-verdien mot
grunnen for hver kjellervegg, vises som et intervall.

Bygningskroppen slår sammen like bygningsdeler med et antall, så fjorten like vinduer blir
fire rader i stedet for fjorten. Tall i tabellene bruker komma.

For Nordhusstykket 5 går rapporten fra fjorten til sju sider.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015GeH3y8fwTaTudaD88P57o
2026-09-21 15:55:19 +02:00
blandClaude Opus 5 133152742f Mål grunnmuren over bakken fra overkant mur
Release enok / bygg (release) Successful in 1m13s
test / test (push) Successful in 1m20s
Høyden ble målt fra gulvet i etasjen over, så dekkets egen tykkelse ble regnet som mur over
bakken. Nå måles den fra overkant av selve muren, som geometrien gir den. For Nordhusstykket
5 går høyden fra 1,11–1,50 m til 0,76–1,24 m, i snitt 0,98 m.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015GeH3y8fwTaTudaD88P57o
2026-09-21 15:48:52 +02:00
blandClaude Opus 5 959db648a9 Skill dekket over kjelleren fra dekket på bakken
test / test (push) Successful in 1m17s
Release enok / bygg (release) Successful in 1m13s
Et dekke like under gulvet i første etasje er himlingen over kjelleren, også når det heter
noe med «kjellergulv»; det er et vanlig navn på dekket over kjelleren. Bare navn som sier
«på grunn» eller «on grade» gir dekke mot grunnen, ellers avgjør høyden: nederste dekke
ligger mot grunnen, resten er etasjeskillere inne i bygningen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015GeH3y8fwTaTudaD88P57o
2026-09-21 15:43:03 +02:00
14 changed files with 1136 additions and 41 deletions
Generated
+1 -1
View File
@@ -222,7 +222,7 @@ dependencies = [
[[package]]
name = "enok"
version = "0.4.4"
version = "0.4.8"
dependencies = [
"anyhow",
"approx",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "enok"
version = "0.4.4"
version = "0.4.8"
edition = "2021"
description = "Årlig energiberegning og energirapport for boliger etter NS 3031 (timesmetode, ISO 52016-1)"
license = "MIT"
+45 -13
View File
@@ -444,22 +444,54 @@ Motoren er kontrollert mot uavhengige referanser:
| Normerte energiposter for småhus | varmtvann 25,1, belysning 11,4, teknisk utstyr 17,5 kWh/(m²·år) |
| Faste energiposter for yrkesbyggene | belysning, utstyr og varmtvann treffer energibudsjettene i kildene |
### Kjent avvik
### Kontroll mot de publiserte referansebyggene
Standard Norges lanseringseksempel for NS 3031:2025 oppgir 46,2 kWh/(m²·år) romoppvarming og
106,4 kWh/(m²·år) netto energibehov for TEK17-referansebygget beregnet med Simien 8. enok
beregner 27,1 og 86,6 for den samme bygningen.
Multiconsult for DiBK, «Grunnlag og forutsetninger for beregnede energirammer» (13.11.2015),
gir geometri, komponentverdier og beregnet netto energibehov for bygningskategoriene som
ligger til grunn for energirammene i TEK17, beregnet med Simien etter NS 3031:2014. Modellene
er gjengitt i `eksempler/referansebygg/`, og `tests/referansebygg_2015.rs` regner dem om igjen:
Varmetapsbudsjettet, de normerte energipostene, solinnstrålingen og energibalansen stemmer
hver for seg, så avviket ligger i hvor stor del av solvarmen som nyttiggjøres. NS 3031:2025
Tillegg A er ikke offentlig tilgjengelig, så de normerte solfaktorene, skjermings­forutsetningene
og timesprofilene Simien bruker, lar seg ikke etterprøve her. `tests/referansebygg.rs` låser
dagens verdier slik at endringer i motoren blir synlige, og oppdateres når standardens
forutsetninger er tilgjengelige.
| Bygg | enok | Publisert | Avvik |
| --- | ---: | ---: | ---: |
| Småhus | 85,3 | 107,5 | 20,6 % |
| Boligblokk | 77,6 | 91,8 | 15,5 % |
| Barnehage | 80,1 | 132,0 | 39,3 % |
| Kontorbygning | 93,5 | 115,3 | 18,9 % |
| Skolebygning | 72,8 | 106,8 | 31,8 % |
| Idrettsbygning | 106,7 | 144,3 | 26,0 % |
| Kulturbygning | 73,2 | 128,0 | 42,8 % |
| **Snitt** | | | **27,9 %** |
Bruk derfor tallene til å sammenligne tiltak mot hverandre og til å se hvor varmen forsvinner.
Til en energiattest som skal leveres til Enova må resultatet kontrolleres mot et validert
beregningsprogram.
Netto energibehov, kWh/(m²·år), Oslo-klima. Kjør `cargo test --release --test
referansebygg_2015 -- --nocapture` for tabellen.
**enok regner gjennomgående for lavt.** Avviket er systematisk og går én vei, så det kommer
neppe av inndataene for den enkelte kategorien. Dette er kontrollert:
- Varmetapstallet for småhuset blir 0,70 W/(m²K) mot publisert 0,69, så geometri og
U-verdier i modellene er riktig gjengitt.
- De faste postene, varmtvann, belysning og teknisk utstyr, treffer de normerte verdiene
nøyaktig.
- Solinnstrålingen avviker under 1 % fra pvlib, og energibalansen går opp.
Avviket ligger dermed i den dynamiske delen: romoppvarming og ventilasjonsvarme. De to mest
sannsynlige årsakene er hvor stor del av sol- og internvarmen som regnes nyttiggjort, der
NS 3031:2025 Tillegg A gir timesprofiler og solfaktorer som ikke er offentlig tilgjengelige,
og luftmengdene i NS 3031 tabell B.1, som er anslått for flere yrkesbyggkategorier.
Standard Norges lanseringseksempel for NS 3031:2025 peker samme vei: det oppgir 46,2
kWh/(m²·år) romoppvarming og 106,4 kWh/(m²·år) netto energibehov for et TEK17-småhus, mot
enoks 27,1 og 86,6.
### Hva tallene kan brukes til
Sammenligning av tiltak mot hverandre, og å se hvor varmen forsvinner. Et systematisk avvik
som dette slår omtrent likt ut på alle alternativene, så rekkefølgen mellom tiltak står seg.
Den absolutte energikarakteren gjør den ikke. En energiattest skal lages av en godkjent
ekspert i Enovas system, med et beregningsprogram som er validert for formålet, og enok er
ikke validert av noen. Bruk derfor karakteren her som en indikasjon, og kontroller den mot et
validert program før den brukes til noe.
## Datagrunnlag
+99
View File
@@ -0,0 +1,99 @@
# Barnehage — referansebygg for energirammene i TEK17.
#
# Geometri og komponentverdier fra Multiconsult for DiBK, «Grunnlag og forutsetninger for
# beregnede energirammer» (128298-RIEn-NOT-001, 13. november 2015), tabell 1 og 2. Notatet
# oppgir beregnet netto energibehov 132.0 kWh/(m²·år) for denne modellen, beregnet
# med Simien etter NS 3031:2014. Filen er enoks gjengivelse av den samme modellen.
#
# Forenklinger: taket er regnet flatt, og veggarealet er omkrets × etasjehøyde × antall
# etasjer minus vindusarealet.
[bygning]
navn = "Barnehage (referansebygg TEK17)"
kategori = "barnehage"
byggeaar = 2017
oppvarmet_bra = 300.0
oppvarmet_volum = 960.0
[klima]
kommune = "Oslo"
[[flate]]
navn = "Yttervegger"
type = "vegg"
areal = 205.0
u_verdi = 0.18
masseklasse = "middels"
[[flate]]
navn = "Tak"
type = "tak"
areal = 300.0
u_verdi = 0.13
masseklasse = "middels"
[[flate]]
navn = "Gulv på grunn"
type = "gulv"
areal = 300.0
u_verdi = 0.10
mot = "grunn"
omkrets = 80.0
masseklasse = "tung"
kommentar = "U-verdien i notatet er oppgitt inkludert varmemotstanden i grunnen"
[[vindu]]
navn = "Vinduer nord"
areal = 22.5
orientering = 0
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
[[vindu]]
navn = "Vinduer øst"
areal = 15.0
orientering = 90
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
[[vindu]]
navn = "Vinduer sør"
areal = 22.5
orientering = 180
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
[[vindu]]
navn = "Vinduer vest"
areal = 15.0
orientering = 270
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
[kuldebroer]
normalisert = 0.07
[infiltrasjon]
n50 = 0.6
skjerming = "moderat"
[ventilasjon]
type = "balansert"
luftmengde = 8.0
gjenvinning = 0.85
sfp = 1.5
[drift]
belysning_effekt = 6.4
[distribusjon]
type = "punkt"
[[oppvarming.kilde]]
type = "direkte_el"
andel_rom = 1.0
andel_tappevann = 1.0
+83
View File
@@ -0,0 +1,83 @@
# Boligblokk — referansebygg for energirammene i TEK17.
#
# Geometri og komponentverdier fra Multiconsult for DiBK, «Grunnlag og forutsetninger for
# beregnede energirammer» (128298-RIEn-NOT-001, 13. november 2015), tabell 1 og 2. Notatet
# oppgir beregnet netto energibehov 91.8 kWh/(m²·år) for denne modellen, beregnet
# med Simien etter NS 3031:2014. Filen er enoks gjengivelse av den samme modellen.
#
# Forenklinger: taket er regnet flatt, og veggarealet er omkrets × etasjehøyde × antall
# etasjer minus vindusarealet.
[bygning]
navn = "Boligblokk (referansebygg TEK17)"
kategori = "boligblokk"
byggeaar = 2017
oppvarmet_bra = 900.0
oppvarmet_volum = 2430.0
[klima]
kommune = "Oslo"
[[flate]]
navn = "Yttervegger"
type = "vegg"
areal = 495.0
u_verdi = 0.18
masseklasse = "middels"
[[flate]]
navn = "Tak"
type = "tak"
areal = 300.0
u_verdi = 0.13
masseklasse = "middels"
[[flate]]
navn = "Gulv på grunn"
type = "gulv"
areal = 300.0
u_verdi = 0.10
mot = "grunn"
omkrets = 80.0
masseklasse = "tung"
kommentar = "U-verdien i notatet er oppgitt inkludert varmemotstanden i grunnen"
[[vindu]]
navn = "Vinduer nord"
areal = 112.5
orientering = 0
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
[[vindu]]
navn = "Vinduer sør"
areal = 112.5
orientering = 180
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
[kuldebroer]
normalisert = 0.07
[infiltrasjon]
n50 = 0.6
skjerming = "moderat"
[ventilasjon]
type = "balansert"
luftmengde = 1.5
gjenvinning = 0.8
sfp = 1.5
[drift]
belysning_effekt = 1.95
[distribusjon]
type = "punkt"
[[oppvarming.kilde]]
type = "direkte_el"
andel_rom = 1.0
andel_tappevann = 1.0
+104
View File
@@ -0,0 +1,104 @@
# Idrettsbygning — referansebygg for energirammene i TEK17.
#
# Geometri og komponentverdier fra Multiconsult for DiBK, «Grunnlag og forutsetninger for
# beregnede energirammer» (128298-RIEn-NOT-001, 13. november 2015), tabell 1 og 2. Notatet
# oppgir beregnet netto energibehov 144.3 kWh/(m²·år) for denne modellen, beregnet
# med Simien etter NS 3031:2014. Filen er enoks gjengivelse av den samme modellen.
#
# Forenklinger: taket er regnet flatt, og veggarealet er omkrets × etasjehøyde × antall
# etasjer minus vindusarealet.
[bygning]
navn = "Idrettsbygning (referansebygg TEK17)"
kategori = "idrettsbygg"
byggeaar = 2017
oppvarmet_bra = 3200.0
oppvarmet_volum = 25600.0
[klima]
kommune = "Oslo"
[[flate]]
navn = "Yttervegger"
type = "vegg"
areal = 1192.0
u_verdi = 0.18
masseklasse = "middels"
[[flate]]
navn = "Tak"
type = "tak"
areal = 3200.0
u_verdi = 0.13
masseklasse = "middels"
[[flate]]
navn = "Gulv på grunn"
type = "gulv"
areal = 3200.0
u_verdi = 0.10
mot = "grunn"
omkrets = 240.0
masseklasse = "tung"
kommentar = "U-verdien i notatet er oppgitt inkludert varmemotstanden i grunnen"
[[vindu]]
navn = "Vinduer nord"
areal = 240.0
orientering = 0
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
solskjerming = { type = "automatisk", faktor = 0.125 }
[[vindu]]
navn = "Vinduer øst"
areal = 160.0
orientering = 90
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
solskjerming = { type = "automatisk", faktor = 0.125 }
[[vindu]]
navn = "Vinduer sør"
areal = 240.0
orientering = 180
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
solskjerming = { type = "automatisk", faktor = 0.125 }
[[vindu]]
navn = "Vinduer vest"
areal = 160.0
orientering = 270
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
solskjerming = { type = "automatisk", faktor = 0.125 }
[kuldebroer]
normalisert = 0.07
[infiltrasjon]
n50 = 0.6
skjerming = "moderat"
[ventilasjon]
type = "balansert"
luftmengde = 6.4
gjenvinning = 0.85
sfp = 1.5
kjolebatteri = true
[drift]
belysning_effekt = 6.4
[distribusjon]
type = "radiator"
[[oppvarming.kilde]]
type = "direkte_el"
andel_rom = 1.0
andel_tappevann = 1.0
+104
View File
@@ -0,0 +1,104 @@
# Kontorbygning — referansebygg for energirammene i TEK17.
#
# Geometri og komponentverdier fra Multiconsult for DiBK, «Grunnlag og forutsetninger for
# beregnede energirammer» (128298-RIEn-NOT-001, 13. november 2015), tabell 1 og 2. Notatet
# oppgir beregnet netto energibehov 115.3 kWh/(m²·år) for denne modellen, beregnet
# med Simien etter NS 3031:2014. Filen er enoks gjengivelse av den samme modellen.
#
# Forenklinger: taket er regnet flatt, og veggarealet er omkrets × etasjehøyde × antall
# etasjer minus vindusarealet.
[bygning]
navn = "Kontorbygning (referansebygg TEK17)"
kategori = "kontorbygg"
byggeaar = 2017
oppvarmet_bra = 3600.0
oppvarmet_volum = 11520.0
[klima]
kommune = "Oslo"
[[flate]]
navn = "Yttervegger"
type = "vegg"
areal = 780.0
u_verdi = 0.18
masseklasse = "middels"
[[flate]]
navn = "Tak"
type = "tak"
areal = 1200.0
u_verdi = 0.13
masseklasse = "middels"
[[flate]]
navn = "Gulv på grunn"
type = "gulv"
areal = 1200.0
u_verdi = 0.10
mot = "grunn"
omkrets = 160.0
masseklasse = "tung"
kommentar = "U-verdien i notatet er oppgitt inkludert varmemotstanden i grunnen"
[[vindu]]
navn = "Vinduer nord"
areal = 270.0
orientering = 0
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
solskjerming = { type = "automatisk", faktor = 0.125 }
[[vindu]]
navn = "Vinduer øst"
areal = 180.0
orientering = 90
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
solskjerming = { type = "automatisk", faktor = 0.125 }
[[vindu]]
navn = "Vinduer sør"
areal = 270.0
orientering = 180
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
solskjerming = { type = "automatisk", faktor = 0.125 }
[[vindu]]
navn = "Vinduer vest"
areal = 180.0
orientering = 270
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
solskjerming = { type = "automatisk", faktor = 0.125 }
[kuldebroer]
normalisert = 0.07
[infiltrasjon]
n50 = 0.6
skjerming = "moderat"
[ventilasjon]
type = "balansert"
luftmengde = 8.0
gjenvinning = 0.85
sfp = 1.5
kjolebatteri = true
[drift]
belysning_effekt = 6.4
[distribusjon]
type = "radiator"
[[oppvarming.kilde]]
type = "direkte_el"
andel_rom = 1.0
andel_tappevann = 1.0
+104
View File
@@ -0,0 +1,104 @@
# Kulturbygning — referansebygg for energirammene i TEK17.
#
# Geometri og komponentverdier fra Multiconsult for DiBK, «Grunnlag og forutsetninger for
# beregnede energirammer» (128298-RIEn-NOT-001, 13. november 2015), tabell 1 og 2. Notatet
# oppgir beregnet netto energibehov 128.0 kWh/(m²·år) for denne modellen, beregnet
# med Simien etter NS 3031:2014. Filen er enoks gjengivelse av den samme modellen.
#
# Forenklinger: taket er regnet flatt, og veggarealet er omkrets × etasjehøyde × antall
# etasjer minus vindusarealet.
[bygning]
navn = "Kulturbygning (referansebygg TEK17)"
kategori = "kulturbygg"
byggeaar = 2017
oppvarmet_bra = 2400.0
oppvarmet_volum = 7680.0
[klima]
kommune = "Oslo"
[[flate]]
navn = "Yttervegger"
type = "vegg"
areal = 520.0
u_verdi = 0.18
masseklasse = "middels"
[[flate]]
navn = "Tak"
type = "tak"
areal = 1200.0
u_verdi = 0.13
masseklasse = "middels"
[[flate]]
navn = "Gulv på grunn"
type = "gulv"
areal = 1200.0
u_verdi = 0.10
mot = "grunn"
omkrets = 160.0
masseklasse = "tung"
kommentar = "U-verdien i notatet er oppgitt inkludert varmemotstanden i grunnen"
[[vindu]]
navn = "Vinduer nord"
areal = 180.0
orientering = 0
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
solskjerming = { type = "automatisk", faktor = 0.125 }
[[vindu]]
navn = "Vinduer øst"
areal = 120.0
orientering = 90
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
solskjerming = { type = "automatisk", faktor = 0.125 }
[[vindu]]
navn = "Vinduer sør"
areal = 180.0
orientering = 180
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
solskjerming = { type = "automatisk", faktor = 0.125 }
[[vindu]]
navn = "Vinduer vest"
areal = 120.0
orientering = 270
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
solskjerming = { type = "automatisk", faktor = 0.125 }
[kuldebroer]
normalisert = 0.07
[infiltrasjon]
n50 = 0.6
skjerming = "moderat"
[ventilasjon]
type = "balansert"
luftmengde = 8.0
gjenvinning = 0.85
sfp = 1.5
kjolebatteri = true
[drift]
belysning_effekt = 6.4
[distribusjon]
type = "radiator"
[[oppvarming.kilde]]
type = "direkte_el"
andel_rom = 1.0
andel_tappevann = 1.0
+104
View File
@@ -0,0 +1,104 @@
# Skolebygning — referansebygg for energirammene i TEK17.
#
# Geometri og komponentverdier fra Multiconsult for DiBK, «Grunnlag og forutsetninger for
# beregnede energirammer» (128298-RIEn-NOT-001, 13. november 2015), tabell 1 og 2. Notatet
# oppgir beregnet netto energibehov 106.8 kWh/(m²·år) for denne modellen, beregnet
# med Simien etter NS 3031:2014. Filen er enoks gjengivelse av den samme modellen.
#
# Forenklinger: taket er regnet flatt, og veggarealet er omkrets × etasjehøyde × antall
# etasjer minus vindusarealet.
[bygning]
navn = "Skolebygning (referansebygg TEK17)"
kategori = "skolebygg"
byggeaar = 2017
oppvarmet_bra = 2400.0
oppvarmet_volum = 7680.0
[klima]
kommune = "Oslo"
[[flate]]
navn = "Yttervegger"
type = "vegg"
areal = 520.0
u_verdi = 0.18
masseklasse = "middels"
[[flate]]
navn = "Tak"
type = "tak"
areal = 1200.0
u_verdi = 0.13
masseklasse = "middels"
[[flate]]
navn = "Gulv på grunn"
type = "gulv"
areal = 1200.0
u_verdi = 0.10
mot = "grunn"
omkrets = 160.0
masseklasse = "tung"
kommentar = "U-verdien i notatet er oppgitt inkludert varmemotstanden i grunnen"
[[vindu]]
navn = "Vinduer nord"
areal = 180.0
orientering = 0
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
solskjerming = { type = "automatisk", faktor = 0.125 }
[[vindu]]
navn = "Vinduer øst"
areal = 120.0
orientering = 90
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
solskjerming = { type = "automatisk", faktor = 0.125 }
[[vindu]]
navn = "Vinduer sør"
areal = 180.0
orientering = 180
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
solskjerming = { type = "automatisk", faktor = 0.125 }
[[vindu]]
navn = "Vinduer vest"
areal = 120.0
orientering = 270
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
solskjerming = { type = "automatisk", faktor = 0.125 }
[kuldebroer]
normalisert = 0.07
[infiltrasjon]
n50 = 0.6
skjerming = "moderat"
[ventilasjon]
type = "balansert"
luftmengde = 8.0
gjenvinning = 0.85
sfp = 1.5
kjolebatteri = true
[drift]
belysning_effekt = 8.0
[distribusjon]
type = "radiator"
[[oppvarming.kilde]]
type = "direkte_el"
andel_rom = 1.0
andel_tappevann = 1.0
+99
View File
@@ -0,0 +1,99 @@
# Småhus — referansebygg for energirammene i TEK17.
#
# Geometri og komponentverdier fra Multiconsult for DiBK, «Grunnlag og forutsetninger for
# beregnede energirammer» (128298-RIEn-NOT-001, 13. november 2015), tabell 1 og 2. Notatet
# oppgir beregnet netto energibehov 107.5 kWh/(m²·år) for denne modellen, beregnet
# med Simien etter NS 3031:2014. Filen er enoks gjengivelse av den samme modellen.
#
# Forenklinger: taket er regnet flatt, og veggarealet er omkrets × etasjehøyde × antall
# etasjer minus vindusarealet.
[bygning]
navn = "Småhus (referansebygg TEK17)"
kategori = "smaahus"
byggeaar = 2017
oppvarmet_bra = 160.0
oppvarmet_volum = 459.2
[klima]
kommune = "Oslo"
[[flate]]
navn = "Yttervegger"
type = "vegg"
areal = 188.2
u_verdi = 0.18
masseklasse = "middels"
[[flate]]
navn = "Tak"
type = "tak"
areal = 80.0
u_verdi = 0.13
masseklasse = "middels"
[[flate]]
navn = "Gulv på grunn"
type = "gulv"
areal = 80.0
u_verdi = 0.10
mot = "grunn"
omkrets = 36.0
masseklasse = "tung"
kommentar = "U-verdien i notatet er oppgitt inkludert varmemotstanden i grunnen"
[[vindu]]
navn = "Vinduer nord"
areal = 12.0
orientering = 0
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
[[vindu]]
navn = "Vinduer øst"
areal = 8.0
orientering = 90
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
[[vindu]]
navn = "Vinduer sør"
areal = 12.0
orientering = 180
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
[[vindu]]
navn = "Vinduer vest"
areal = 8.0
orientering = 270
u_verdi = 0.80
g_verdi = 0.40
karmandel = 0.20
[kuldebroer]
normalisert = 0.05
[infiltrasjon]
n50 = 0.6
skjerming = "moderat"
[ventilasjon]
type = "balansert"
luftmengde = 1.2
gjenvinning = 0.8
sfp = 1.5
[drift]
belysning_effekt = 1.95
[distribusjon]
type = "punkt"
[[oppvarming.kilde]]
type = "direkte_el"
andel_rom = 1.0
andel_tappevann = 1.0
+21 -15
View File
@@ -186,6 +186,8 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
areal: flate.map(|f| f.areal).unwrap_or(0.0),
normal: flate.map(|f| f.normal),
z: plass.map(|p| p.2).unwrap_or(0.0) + flate.map(|f| f.hoyde).unwrap_or(0.0),
topp: plass.map(|p| p.2).unwrap_or(0.0) + flate.map(|f| f.topp).unwrap_or(0.0),
omkrets: flate.map(|f| f.omkrets()).unwrap_or(0.0),
});
}
@@ -363,12 +365,13 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
}
// Terrengmodellen sier hvor høyt bakken ligger akkurat der denne veggen står, så en
// skrånende tomt gir forskjellig høyde over bakken på hver side av huset.
// Høyden måles fra overkant mur, ikke fra gulvet over: dekket selv står ikke i bakken.
let over_bakken = d
.plass
.and_then(|(x, y, _, _, _)| terreng_ved(&terrengmodell, x, y))
.map(|z| (etasjekontekst.forste_over_hoyde.unwrap_or(0.0) - z).clamp(0.0, kjellerdybde))
.map(|z| (d.topp - z).clamp(0.0, kjellerdybde))
.unwrap_or(kjeller_over_terreng);
if i_kjeller && mot == Mot::Grunn {
if i_kjeller && mot == Mot::Grunn && typ == FlateType::Vegg {
grunnmurshoyder.push(over_bakken);
}
let mal = if i_kjeller {
@@ -389,7 +392,8 @@ pub fn fra_tekst_med(tekst: &str, standardnavn: &str, valg: &Importvalg) -> Resu
mot,
b_faktor: None,
absorptans: None,
omkrets: None,
omkrets: (typ == FlateType::Gulv && mot == Mot::Grunn && d.omkrets > 0.0)
.then_some(d.omkrets),
dybde: (i_kjeller && mot == Mot::Grunn).then_some(kjellerdybde - over_bakken),
over_terreng: (i_kjeller && mot == Mot::Grunn).then_some(over_bakken),
sone,
@@ -1316,6 +1320,10 @@ struct Del {
normal: Option<[f64; 3]>,
/// Delens høyde over byggets nullpunkt, av geometrien, m.
z: f64,
/// Overkanten av delen over byggets nullpunkt, m. For en kjellervegg er det overkant mur.
topp: f64,
/// Omkretsen av delens omriss, m. Et gulv mot grunnen trenger den for B.
omkrets: f64,
}
/// Oversetter en fritekstverdi fra modellen til en bygningskategori.
@@ -1504,20 +1512,15 @@ fn klassifiser(
if d.ytre == Some(true) {
return Some((FlateType::Gulv, Mot::Ute));
}
// Mot grunnen i det nederste planet, eller når navnet sier det. Høyden av geometrien
// avgjør, siden etasjetilknytningen i en eksport kan være feil.
let paa_grunnen = [
"kjellergulv",
"on grade",
"på grunn",
"paa grunn",
"såle",
"sale",
];
if (d.z - laveste).abs() < 0.5 || paa_grunnen.iter().any(|o| navn.contains(o)) {
// Høyden av geometrien avgjør, siden etasjetilknytningen i en eksport kan være feil.
// Et dekke som ligger på bakken sier det i navnet. Ellers er et dekke like under
// gulvet i første etasje himlingen over kjelleren, og det nederste dekket ligger mot
// grunnen. «Kjellergulv» er ikke nok til å si at dekket ligger på bakken: dekket over
// kjelleren bærer ofte det samme navnet.
let paa_grunnen = ["on grade", "på grunn", "paa grunn", "såle", "sale"];
if paa_grunnen.iter().any(|o| navn.contains(o)) {
return Some((FlateType::Gulv, Mot::Grunn));
}
// Dekket i laveste etasje over en kjeller er grensen mot den uoppvarmede sonen.
if e.kjeller.is_some() {
let ved_forste_over = e
.forste_over_hoyde
@@ -1527,6 +1530,9 @@ fn klassifiser(
return Some((FlateType::Gulv, Mot::Uoppvarmet));
}
}
if (d.z - laveste).abs() < 0.5 {
return Some((FlateType::Gulv, Mot::Grunn));
}
// Dekker lenger opp er etasjeskillere inne i bygningen.
return None;
}
+33
View File
@@ -30,6 +30,8 @@ pub struct Hovedflate {
/// Midlere høyde over delens eget nullpunkt, m. Sier hvilken etasje delen ligger i når
/// plasseringen i filen ikke gjør det.
pub hoyde: f64,
/// Øverste punkt over delens eget nullpunkt, m. For en kjellervegg er det overkant mur.
pub topp: f64,
}
impl Nett {
@@ -103,11 +105,18 @@ impl Nett {
} else {
self.punkter.iter().map(|p| p[2]).sum::<f64>() / self.punkter.len() as f64 * skala
};
let topp = self
.punkter
.iter()
.map(|p| p[2])
.fold(f64::NEG_INFINITY, f64::max)
* skala;
Some(Hovedflate {
areal: sum / 2.0,
normal,
plan: self.utstrekning(normal, skala),
hoyde,
topp: if topp.is_finite() { topp } else { 0.0 },
})
}
@@ -189,9 +198,14 @@ pub fn samlet_hovedflate(deler: &[Nett], skala: f64) -> Option<Hovedflate> {
} else {
grupper[0].hoyde
};
let topp = grupper
.iter()
.map(|g| g.topp)
.fold(f64::NEG_INFINITY, f64::max);
Some(Hovedflate {
areal,
hoyde,
topp,
..grupper[0]
})
}
@@ -210,6 +224,22 @@ fn overlapper(a: &[f64; 4], b: &[f64; 4]) -> bool {
dekning(a[0], a[1], b[0], b[1]) && dekning(a[2], a[3], b[2], b[3])
}
impl Hovedflate {
/// Omkretsen av flatens omriss, m, regnet av utstrekningen i flatens eget plan.
///
/// Et gulv mot grunnen trenger omkretsen for B etter NS-EN ISO 13370. Omrisset regnes
/// som et rektangel, som stemmer godt for de fleste grunnflater.
pub fn omkrets(&self) -> f64 {
let bredde = self.plan[1] - self.plan[0];
let lengde = self.plan[3] - self.plan[2];
if bredde > 0.0 && lengde > 0.0 {
2.0 * (bredde + lengde)
} else {
0.0
}
}
}
/// Henter geometrien til et produkt via `Representation`, med én oppføring per geometridel.
pub fn netter_for_produkt(fil: &Fil, representasjon: Option<u64>) -> Vec<Nett> {
let mut deler = Vec::new();
@@ -562,6 +592,9 @@ mod tester {
let h = n.hovedflate(1.0).unwrap();
assert!((h.areal - 12.0).abs() < 1e-9, "areal ble {}", h.areal);
assert!(h.normal[2].abs() > 0.99, "normal ble {:?}", h.normal);
// Midlere høyde er halve tykkelsen, og overkanten er tykkelsen.
assert!((h.hoyde - z / 2.0).abs() < 1e-9, "høyde ble {}", h.hoyde);
assert!((h.topp - z).abs() < 1e-9, "topp ble {}", h.topp);
}
/// Et hull i flaten skal trekkes fra, siden hullet mangler trekanter.
+246 -11
View File
@@ -2,6 +2,7 @@
use crate::kjor::Resultat;
use crate::merking::skala::BOKSTAVER;
use crate::modell::lost::Forutsetning;
use crate::modell::{FlateType, Mot};
use crate::rapport::svg;
use crate::Result;
@@ -437,7 +438,10 @@ fn kilde(r: &Resultat, scenarier: &[(String, Resultat)]) -> String {
// ── inndata ─────────────────────────────────────────────────────
s.push_str("#pagebreak()\n\n= Inndata\n\n== Bygningskropp\n\n");
s.push_str("#tabell(\n kolonner: (1fr, auto, auto, auto, auto, auto),\n justering: (left, left, right, right, right, left),\n overskrift: (\"Bygningsdel\", \"Type\", \"Areal, m²\", \"U-verdi\", \"Orientering\", \"Mot\"),\n rader: (\n");
s.push_str("#tabell(\n kolonner: (1fr, auto, auto, auto, auto, auto, auto),\n justering: (left, left, right, right, right, right, left),\n overskrift: (\"Bygningsdel\", \"Type\", \"Antall\", \"Areal, m²\", \"U-verdi\", \"Orientering\", \"Mot\"),\n rader: (\n");
// Like bygningsdeler står på én linje: modellen deler gjerne den samme konstruksjonen i
// mange like vinduer eller veggfelt, og da sier antallet mer enn én rad per stykk.
let mut samlet: Vec<(String, String, String, String, usize, f64)> = Vec::new();
for f in &l.flater {
let typ = if f.glass {
"Vindu/dør"
@@ -453,15 +457,62 @@ fn kilde(r: &Resultat, scenarier: &[(String, Resultat)]) -> String {
Mot::Grunn => "grunnen",
Mot::Uoppvarmet => "uoppvarmet",
};
let navn = f
.navn
.split(" #")
.next()
.unwrap_or(&f.navn)
.trim()
.to_string();
let nokkel = (
navn,
typ.to_string(),
tall(f.u_verdi, 2),
himmelretning(f.orientering, f.helning),
mot.to_string(),
);
match samlet.iter_mut().find(|(n, t, u, o, _, _)| {
(n.as_str(), t.as_str(), u.as_str(), o.as_str())
== (
nokkel.0.as_str(),
nokkel.1.as_str(),
nokkel.2.as_str(),
nokkel.3.as_str(),
)
}) {
Some((_, _, _, _, antall, areal)) => {
*antall += 1;
*areal += f.areal;
}
None => samlet.push((nokkel.0, nokkel.1, nokkel.2, nokkel.3, 1, f.areal)),
}
let _ = nokkel.4;
}
for (navn, typ, u, orientering, antall, areal) in &samlet {
let mot = l
.flater
.iter()
.find(|f| f.navn.split(" #").next().unwrap_or(&f.navn).trim() == navn)
.map(|f| match f.mot {
Mot::Ute => "det fri",
Mot::Grunn => "grunnen",
Mot::Uoppvarmet => "uoppvarmet",
})
.unwrap_or("det fri");
s.push_str(&format!(
" {}\n",
rad(&[
e(&f.navn),
typ.into(),
tall(f.areal, 1),
tall(f.u_verdi, 2),
himmelretning(f.orientering, f.helning),
mot.into()
e(navn),
typ.clone(),
if *antall > 1 {
antall.to_string()
} else {
String::new()
},
tall(*areal, 1),
u.clone(),
orientering.clone(),
mot.into(),
])
));
}
@@ -496,15 +547,28 @@ fn kilde(r: &Resultat, scenarier: &[(String, Resultat)]) -> String {
if l.forutsetninger.is_empty() {
s.push_str("Alle inndata er oppgitt i bygningsbeskrivelsen.\n\n");
} else {
// Faste bredder, ellers spiser de to første kolonnene plassen kilden trenger.
s.push_str("#tabell(\n kolonner: (1.15fr, 0.85fr, 1.6fr),\n justering: (left, left, left),\n overskrift: (\"Felt\", \"Verdi\", \"Kilde\"),\n rader: (\n");
for f in &l.forutsetninger {
s.push_str("#tabell(\n kolonner: (1.25fr, 1fr, 1.15fr, auto, auto, auto, auto),\n justering: (left, left, left, center, center, center, left),\n overskrift: (\"Felt\", \"Verdi\", \"Gjelder\", \"Normert\", \"Byggeår\", \"Antatt\", \"Standard\"),\n rader: (\n");
for f in grupper_forutsetninger(&l.forutsetninger) {
s.push_str(&format!(
" {}\n",
rad(&[e(&f.felt), e(&f.verdi), e(&f.kilde)])
rad(&[
e(&f.felt),
e(&f.verdi),
e(&f.gjelder),
kryss(f.normert),
kryss(f.byggeaar),
kryss(f.antatt),
e(&f.standard),
])
));
}
s.push_str(" ),\n)\n\n");
s.push_str(
"#merknad[*Normert* er standardverdier for bygningskategorien, som skal brukes i \
energimerket. *Byggeår* er typiske verdier for byggeperioden, brukt der modellen ikke \
oppgir noe. *Antatt* er satt av beregningen og bør kontrolleres. *Standard* viser \
hvilken standard verdien er regnet etter.]\n\n",
);
}
for m in &r.merknader {
s.push_str(&format!("#merknad[{}]\n\n", e(m)));
@@ -563,6 +627,177 @@ fn kilde(r: &Resultat, scenarier: &[(String, Resultat)]) -> String {
s
}
/// Kilden uten tall, slik at «… av konstruksjonens 3,39 W/(m²K) og 1,29 m dybde» og den
/// samme setningen for en annen vegg regnes som samme slags kilde.
fn kildenokkel(kilde: &str) -> String {
let mut ut = String::with_capacity(kilde.len());
let mut i_tall = false;
for c in kilde.chars() {
if c.is_ascii_digit() {
if !i_tall {
ut.push('#');
}
i_tall = true;
} else {
i_tall = false;
ut.push(c);
}
}
ut
}
/// Tallet fremst i en verdi som «1.353 W/(m²K)», når verdien begynner med et tall.
fn tallverdi(verdi: &str) -> Option<f64> {
let tall: String = verdi
.chars()
.take_while(|c| c.is_ascii_digit() || *c == '.' || *c == ',' || *c == '-')
.collect();
tall.replace(',', ".").parse().ok()
}
/// Slår to verdier med samme enhet sammen til et intervall, «1,211,39 W/(m²K)».
fn slaa_sammen_intervall(a: &str, b: &str) -> String {
let enhet = a.trim_start_matches(|c: char| {
c.is_ascii_digit() || c == '.' || c == ',' || c == '-' || c == ''
});
let mut tall: Vec<f64> = Vec::new();
for del in a.split('').chain(b.split('')) {
if let Some(t) = tallverdi(del.trim()) {
tall.push(t);
}
}
let minste = tall.iter().copied().fold(f64::INFINITY, f64::min);
let storste = tall.iter().copied().fold(f64::NEG_INFINITY, f64::max);
if (storste - minste).abs() < 5e-3 {
format!("{minste:.2}{enhet}")
} else {
format!("{minste:.2}{storste:.2}{enhet}")
}
}
/// Norsk desimaltegn i tall, uten å røre tekst som «ISO 52016-1».
fn komma(verdi: &str) -> String {
let mut ut = String::with_capacity(verdi.len());
let tegn: Vec<char> = verdi.chars().collect();
for (i, c) in tegn.iter().enumerate() {
let mellom_tall = i > 0
&& i + 1 < tegn.len()
&& tegn[i - 1].is_ascii_digit()
&& tegn[i + 1].is_ascii_digit();
ut.push(if *c == '.' && mellom_tall { ',' } else { *c });
}
ut
}
fn kryss(: bool) -> String {
if {
"".into()
} else {
String::new()
}
}
/// En rad i forutsetningstabellen: like forutsetninger for mange bygningsdeler slås sammen.
struct Forutsetningsrad {
felt: String,
verdi: String,
gjelder: String,
normert: bool,
byggeaar: bool,
antatt: bool,
standard: String,
}
/// Slår sammen forutsetninger som gjelder flere bygningsdeler, og deler kilden i kolonner.
///
/// «flate.Yttervegg nord.u_verdi» og «flate.Yttervegg sør.u_verdi» med samme verdi og kilde
/// blir til én rad «flate.u_verdi» som gjelder 2 flater.
fn grupper_forutsetninger(forutsetninger: &[Forutsetning]) -> Vec<Forutsetningsrad> {
let flertall = |prefiks: &str, antall: usize| match prefiks {
"flate" => format!("{antall} flater"),
"vindu" => format!("{antall} vinduer"),
"sone" => format!("{antall} soner"),
annet => format!("{antall} × {annet}"),
};
let mut rader: Vec<(String, String, String, Vec<String>, String)> = Vec::new();
for f in forutsetninger {
// «prefiks.navn.felt» deles i mønsteret «prefiks.felt» og navnet det gjelder.
let (prefiks, resten) = f.felt.split_once('.').unwrap_or(("", f.felt.as_str()));
let (navn, felt) = match resten.rsplit_once('.') {
Some((navn, felt)) => (navn.to_string(), felt.to_string()),
None => (String::new(), resten.to_string()),
};
let monster = if prefiks.is_empty() {
felt.clone()
} else {
format!("{prefiks}.{felt}")
};
match rader
.iter_mut()
.find(|(m, v, k, _, _)| *m == monster && *v == f.verdi && *k == f.kilde)
{
Some((_, _, _, navn_liste, _)) => navn_liste.push(navn),
None => rader.push((
monster,
f.verdi.clone(),
f.kilde.clone(),
vec![navn],
prefiks.to_string(),
)),
}
}
// Verdier som bare skiller seg litt, som en U-verdi mot grunnen regnet for hver vegg,
// samles til ett intervall i stedet for én rad per vegg.
let mut samlet: Vec<(String, String, String, Vec<String>, String)> = Vec::new();
for (felt, verdi, kilde, navn, prefiks) in rader {
// Kilden bærer ofte tallene for den enkelte veggen; de maskeres, slik at samme
// slags kilde kjennes igjen.
let like = samlet
.iter_mut()
.find(|(f, _, k, _, _)| *f == felt && kildenokkel(k) == kildenokkel(&kilde));
match like {
Some((_, v, _, n, _)) if tallverdi(v).is_some() && tallverdi(&verdi).is_some() => {
*v = slaa_sammen_intervall(v, &verdi);
n.extend(navn);
}
_ => samlet.push((felt, verdi, kilde, navn, prefiks)),
}
}
samlet
.into_iter()
.map(|(felt, verdi, kilde, navn, prefiks)| {
let gjelder = match navn.len() {
0 => String::new(),
1 if navn[0].is_empty() => String::new(),
1 => navn[0].clone(),
n => flertall(&prefiks, n),
};
let lav = kilde.to_lowercase();
let standard = [
"NS-EN ISO 13370",
"NS-EN ISO 13789",
"ISO 52016-1",
"NS 3031",
]
.into_iter()
.find(|s| kilde.contains(s))
.unwrap_or("")
.to_string();
let normert = lav.contains("normert");
let byggeaar = lav.contains("typisk for");
Forutsetningsrad {
felt,
verdi: komma(&verdi),
gjelder,
normert,
byggeaar,
antatt: !normert && !byggeaar && standard.is_empty() || lav.contains("antatt"),
standard,
}
})
.collect()
}
/// Tekst i en Typst-strengliteral: bare omvendt skråstrek og anførselstegn skal ut.
fn streng(s: &str) -> String {
s.replace('\\', "\\\\").replace('"', "\\\"")
+92
View File
@@ -0,0 +1,92 @@
//! Kontroll mot de publiserte referansebyggene for energirammene i TEK17.
//!
//! Multiconsult for DiBK, «Grunnlag og forutsetninger for beregnede energirammer»
//! (128298-RIEn-NOT-001, 13. november 2015), gir geometri (tabell 1), komponentverdier
//! (tabell 2) og beregnet netto energibehov (tabell 3) for tretten bygningskategorier,
//! beregnet med Simien etter NS 3031:2014. Modellene er gjengitt i `eksempler/referansebygg/`.
//!
//! Dette er den eneste kontrollen der hele beregningen kan sammenlignes med et publisert
//! resultat for flere bygningskategorier. Testen låser dagens avvik, slik at det blir synlig
//! både når motoren nærmer seg de publiserte tallene og når noe gjør avviket større.
//!
//! Kjør `cargo test --release referansebygg_2015 -- --nocapture` for hele tabellen.
use enok::data::Datakilde;
use enok::{inndata, kjor};
use std::path::Path;
/// Fil, publisert netto energibehov, og enoks verdi slik den er i dag, kWh/(m²·år).
const BYGG: [(&str, f64, f64); 7] = [
("smaahus", 107.5, 85.3),
("boligblokk", 91.8, 77.6),
("barnehage", 132.0, 80.1),
("kontorbygg", 115.3, 93.5),
("skolebygg", 106.8, 72.8),
("idrettsbygg", 144.3, 106.7),
("kulturbygg", 128.0, 73.2),
];
fn netto(navn: &str) -> f64 {
let sti = format!("eksempler/referansebygg/{navn}.toml");
let b = inndata::les_bygning(Path::new(&sti)).unwrap();
let r = kjor::kjor(&b, &Datakilde::default()).unwrap();
r.budsjett.netto_spesifikk(r.lost.bra)
}
/// Verdiene skal ikke endre seg uten at noen har ment det.
#[test]
fn referansebyggene_gir_de_samme_tallene() {
println!(
"\n {:14}{:>10}{:>12}{:>9}",
"bygg", "enok", "publisert", "avvik"
);
let mut avvik = Vec::new();
for (navn, publisert, laast) in BYGG {
let n = netto(navn);
println!(
" {navn:14}{n:10.1}{publisert:12.1}{:8.1} %",
100.0 * (n - publisert) / publisert
);
avvik.push(100.0 * (n - publisert) / publisert);
assert!(
(n - laast).abs() < 1.0,
"{navn}: {n:.1} kWh/(m²·år), låst verdi {laast:.1}"
);
}
let snitt = avvik.iter().sum::<f64>() / avvik.len() as f64;
println!(" {:14}{:>10}{:>12}{snitt:8.1} %\n", "snitt", "", "");
}
/// Varmetapstallet for småhuset skal treffe den publiserte verdien for TEK17-nivå.
///
/// Geometri og U-verdier er dermed riktig gjengitt, og avviket i netto energibehov ligger i
/// den dynamiske beregningen, ikke i inndataene.
#[test]
fn varmetapstallet_for_smaahuset_stemmer() {
let b = inndata::les_bygning(Path::new("eksempler/referansebygg/smaahus.toml")).unwrap();
let r = kjor::kjor(&b, &Datakilde::default()).unwrap();
let publisert = 0.69;
assert!(
(r.varmetap.totalt - publisert).abs() < 0.03,
"varmetapstall {:.2}, publisert {publisert:.2}",
r.varmetap.totalt
);
}
/// De faste postene følger av normerte verdier og skal stemme uavhengig av motoren.
#[test]
fn faste_poster_i_referansebyggene() {
let post = |r: &kjor::Resultat, nr: &str| {
r.budsjett
.poster
.iter()
.find(|p| p.nr == nr)
.map(|p| p.netto / r.lost.bra)
.unwrap_or(0.0)
};
let b = inndata::les_bygning(Path::new("eksempler/referansebygg/smaahus.toml")).unwrap();
let r = kjor::kjor(&b, &Datakilde::default()).unwrap();
assert!((post(&r, "2") - 25.1).abs() < 0.2, "varmtvann");
assert!((post(&r, "5") - 11.4).abs() < 0.2, "belysning");
assert!((post(&r, "6") - 17.5).abs() < 0.2, "teknisk utstyr");
}