157 lines
4.8 KiB
Rust
157 lines
4.8 KiB
Rust
//! Leser `bygning.toml` og fletter inn scenarioendringer.
|
|||
|
|
|
||
|
|
use crate::modell::{Bygning, Scenario};
|
||
|
|
use crate::Result;
|
||
|
|
use anyhow::Context;
|
||
|
|
use std::path::Path;
|
||
|
|
use toml::Value;
|
||
|
|
|
||
|
|
pub fn les_bygning(sti: &Path) -> Result<Bygning> {
|
||
|
|
let tekst = std::fs::read_to_string(sti).with_context(|| format!("leser {}", sti.display()))?;
|
||
|
|
toml::from_str(&tekst).with_context(|| format!("tolker {}", sti.display()))
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Lager en ny bygning der scenarioets endringer er flettet inn.
|
||
|
|
///
|
||
|
|
/// Tabeller flettes rekursivt. Tabellarrayer (`flate`, `vindu`, `oppvarming.kilde` …) matches
|
||
|
|
/// på feltet `navn` når det finnes, ellers på `type`; elementer som ikke finnes fra før legges
|
||
|
|
/// til, og `slett = true` fjerner et element. Andre verdier erstattes.
|
||
|
|
pub fn slaa_sammen_scenario(b: &Bygning, s: &Scenario) -> Result<Bygning> {
|
||
|
|
let mut grunn = Value::try_from(b).context("serialiserer bygningen for scenario")?;
|
||
|
|
flett(&mut grunn, &s.endringer);
|
||
|
|
grunn
|
||
|
|
.try_into()
|
||
|
|
.with_context(|| format!("scenarioet «{}» gir en ugyldig bygningsbeskrivelse", s.navn))
|
||
|
|
}
|
||
|
|
|
||
|
|
fn navnenokkel(v: &Value) -> Option<String> {
|
||
|
|
let t = v.as_table()?;
|
||
|
|
t.get("navn")
|
||
|
|
.and_then(|x| x.as_str())
|
||
|
|
.or_else(|| t.get("type").and_then(|x| x.as_str()))
|
||
|
|
.map(|s| s.to_string())
|
||
|
|
}
|
||
|
|
|
||
|
|
fn skal_slettes(v: &Value) -> bool {
|
||
|
|
v.as_table()
|
||
|
|
.and_then(|t| t.get("slett"))
|
||
|
|
.and_then(|x| x.as_bool())
|
||
|
|
.unwrap_or(false)
|
||
|
|
}
|
||
|
|
|
||
|
|
fn flett(grunn: &mut Value, over: &Value) {
|
||
|
|
match (grunn, over) {
|
||
|
|
(Value::Table(g), Value::Table(o)) => {
|
||
|
|
for (k, v) in o {
|
||
|
|
if k == "slett" {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
match g.get_mut(k) {
|
||
|
|
Some(eksisterende) => flett(eksisterende, v),
|
||
|
|
None => {
|
||
|
|
g.insert(k.clone(), v.clone());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
(Value::Array(g), Value::Array(o)) => {
|
||
|
|
for element in o {
|
||
|
|
match navnenokkel(element) {
|
||
|
|
Some(navn) => {
|
||
|
|
let treff = g
|
||
|
|
.iter()
|
||
|
|
.position(|e| navnenokkel(e).as_deref() == Some(&navn));
|
||
|
|
match treff {
|
||
|
|
Some(i) if skal_slettes(element) => {
|
||
|
|
g.remove(i);
|
||
|
|
}
|
||
|
|
Some(i) => flett(&mut g[i], element),
|
||
|
|
None if skal_slettes(element) => {}
|
||
|
|
None => g.push(element.clone()),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
None => g.push(element.clone()),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
(g, o) => *g = o.clone(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[cfg(test)]
|
||
|
|
mod tester {
|
||
|
|
use super::*;
|
||
|
|
|
||
|
|
const GRUNN: &str = r#"
|
||
|
|
[bygning]
|
||
|
|
navn = "Testhus"
|
||
|
|
kategori = "smaahus"
|
||
|
|
byggeaar = 1948
|
||
|
|
oppvarmet_bra = 100.0
|
||
|
|
|
||
|
|
[[flate]]
|
||
|
|
navn = "Yttervegg"
|
||
|
|
type = "vegg"
|
||
|
|
areal = 80.0
|
||
|
|
u_verdi = 1.0
|
||
|
|
|
||
|
|
[[flate]]
|
||
|
|
navn = "Tak"
|
||
|
|
type = "tak"
|
||
|
|
areal = 100.0
|
||
|
|
u_verdi = 0.8
|
||
|
|
"#;
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn scenario_endrer_u_verdi_for_navngitt_flate() {
|
||
|
|
let b: Bygning = toml::from_str(GRUNN).unwrap();
|
||
|
|
let s = Scenario {
|
||
|
|
navn: "Etterisolert".into(),
|
||
|
|
beskrivelse: None,
|
||
|
|
endringer: toml::from_str(r#"flate = [{ navn = "Yttervegg", u_verdi = 0.22 }]"#)
|
||
|
|
.unwrap(),
|
||
|
|
};
|
||
|
|
let ny = slaa_sammen_scenario(&b, &s).unwrap();
|
||
|
|
assert_eq!(ny.flate.len(), 2);
|
||
|
|
assert_eq!(ny.flate[0].u_verdi, Some(0.22));
|
||
|
|
assert_eq!(ny.flate[1].u_verdi, Some(0.8));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn scenario_legger_til_og_sletter() {
|
||
|
|
let b: Bygning = toml::from_str(GRUNN).unwrap();
|
||
|
|
let s = Scenario {
|
||
|
|
navn: "Ombygd".into(),
|
||
|
|
beskrivelse: None,
|
||
|
|
endringer: toml::from_str(
|
||
|
|
r#"flate = [{ navn = "Tak", slett = true }, { navn = "Nytt tak", type = "tak", areal = 100.0, u_verdi = 0.13 }]"#,
|
||
|
|
)
|
||
|
|
.unwrap(),
|
||
|
|
};
|
||
|
|
let ny = slaa_sammen_scenario(&b, &s).unwrap();
|
||
|
|
let navn: Vec<&str> = ny.flate.iter().map(|f| f.navn.as_str()).collect();
|
||
|
|
assert_eq!(navn, vec!["Yttervegg", "Nytt tak"]);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn scenario_endrer_skalarfelt() {
|
||
|
|
let b: Bygning = toml::from_str(GRUNN).unwrap();
|
||
|
|
let s = Scenario {
|
||
|
|
navn: "Varmepumpe".into(),
|
||
|
|
beskrivelse: None,
|
||
|
|
endringer: toml::from_str(
|
||
|
|
r#"
|
||
|
|
[infiltrasjon]
|
||
|
|
n50 = 3.0
|
||
|
|
[oppvarming]
|
||
|
|
kilde = [{ type = "luft_luft_vp", andel_rom = 0.6 }]
|
||
|
|
"#,
|
||
|
|
)
|
||
|
|
.unwrap(),
|
||
|
|
};
|
||
|
|
let ny = slaa_sammen_scenario(&b, &s).unwrap();
|
||
|
|
assert_eq!(ny.infiltrasjon.n50, Some(3.0));
|
||
|
|
assert_eq!(ny.oppvarming.kilde.len(), 1);
|
||
|
|
}
|
||
|
|
}
|