From bef3bb109024911e00585ace4568823608d84699 Mon Sep 17 00:00:00 2001 From: Bendik Lynghaug Date: Mon, 5 Oct 2026 19:57:21 +0200 Subject: [PATCH] =?UTF-8?q?Vis=20prosjektversjonen=20i=20et=20internkontro?= =?UTF-8?q?llfelt=20p=C3=A5=20forsiden?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rapporten bar enok-versjonen og datoen i bunnteksten på hver side, men ikke hvilken versjon av prosjektet den var laget fra. Det er den opplysningen som knytter rapporten til en bestemt utgivelse av bygningsmodellen, og den hører hjemme ett sted, tydelig plassert. Forsiden har nå et internkontrollfelt med prosjekt, prosjektversjon, oppdragsgiver, energirådgiver, dato og enok-versjon. Bunnteksten gjentar ikke lenger versjon og dato. Versjonen settes med «rapport.versjon» eller «--versjon», slik at byggeserveren kan sende taggen på utgivelsen uten at noen fil endres. Lukker #1 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015GeH3y8fwTaTudaD88P57o --- README.md | 16 ++++++++-- src/main.rs | 10 +++++- src/modell/mod.rs | 4 +++ src/rapport/typst_ut.rs | 31 +++++++++++++++++- tests/rapport.rs | 69 +++++++++++++++++++++++++++++++++++++++++ typst/stil.typ | 26 ++++++++++++++++ 6 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 tests/rapport.rs diff --git a/README.md b/README.md index 59dc207..046a4e2 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ Typst må være installert for PDF-rapporten. Uten Typst kjører du med `--ingen | `enok klima --kommune Oslo` | Viser nøkkeltall for ett klimasted | | `enok skala smaahus --areal 140` | Viser karakterskalaen med arealkorreksjon | -Nyttige valg til `beregn`: `--tillegg FIL`, `--ingen-pdf`, `--ingen-timeserie`, `--enova-xml`, +Nyttige valg til `beregn`: `--tillegg FIL`, `--versjon VERSJON`, `--ingen-pdf`, `--ingen-timeserie`, `--enova-xml`, `--uten-scenarier`, `--ut KATALOG`. ### Utdata @@ -188,6 +188,18 @@ logo = "logo.svg" # vises øverst på forsiden farge = "#880000" # aksentfarge i overskrifter og tabeller avsender = "Firma · Adresse · Org.nr. 000 000 000" skrift_overskrift = "Futura Now Headline" +versjon = "v1.4.0" # prosjektets versjon, se nedenfor +``` + +Forsiden har et internkontrollfelt med hvilket prosjekt rapporten hører til, hvilken versjon +av prosjektet den er laget fra, oppdragsgiver og energirådgiver, datoen og hvilken +enok-versjon som regnet den. Opplysningene står der én gang, ikke i bunnteksten på hver side. + +Prosjektversjonen settes i `rapport.versjon`, eller med `--versjon` på `beregn`. Da kan +byggeserveren sende taggen på utgivelsen uten at noen fil må endres: + +```sh +enok beregn modell.ifc --tillegg prosjekt.toml --versjon "$GITHUB_REF_NAME" ``` Scenarier flettes inn i bygningsbeskrivelsen: tabeller flettes rekursivt, elementer i lister @@ -425,7 +437,7 @@ jobs: - name: Lag rapporten run: | chmod +x enok - ./enok beregn bygning.toml --ut dist + ./enok beregn bygning.toml --ut dist --versjon "$GITHUB_REF_NAME" ``` ## Kontroll av beregningen diff --git a/src/main.rs b/src/main.rs index d31daf3..f38cdcb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -61,6 +61,10 @@ struct Beregn { /// Tilleggsfil som flettes inn: det modellen ikke beskriver, og rettelser. #[arg(short, long, value_name = "FIL")] tillegg: Option, + /// Prosjektets versjon, som vises i internkontrollfeltet på forsiden. Overstyrer + /// `rapport.versjon`. Byggeserveren sender gjerne taggen på utgivelsen. + #[arg(long, value_name = "VERSJON")] + versjon: Option, } #[derive(Args)] @@ -174,7 +178,11 @@ fn les_modell( } fn beregn(a: Beregn, kilde: &Datakilde) -> Result<()> { - let (bygning, merknader, modell) = les_modell(&a.fil, a.tillegg.as_deref())?; + let (mut bygning, merknader, modell) = les_modell(&a.fil, a.tillegg.as_deref())?; + if let Some(v) = &a.versjon { + bygning.rapport.versjon = Some(v.clone()); + } + let bygning = bygning; for m in &merknader { eprintln!(" · {m}"); } diff --git a/src/modell/mod.rs b/src/modell/mod.rs index ee2f027..89c672e 100644 --- a/src/modell/mod.rs +++ b/src/modell/mod.rs @@ -102,6 +102,10 @@ pub struct Rapport { /// Skrift for overskrifter. Utelatt: enoks egen. #[serde(default, skip_serializing_if = "Option::is_none")] pub skrift_overskrift: Option, + /// Prosjektets versjon, typisk taggen på utgivelsen rapporten hører til, for eksempel + /// «v0.3.12». Vises i internkontrollfeltet på forsiden. Kan settes med `--versjon`. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub versjon: Option, } #[derive(Deserialize, Serialize, Clone, Debug)] diff --git a/src/rapport/typst_ut.rs b/src/rapport/typst_ut.rs index cf2d093..4e7461e 100644 --- a/src/rapport/typst_ut.rs +++ b/src/rapport/typst_ut.rs @@ -165,6 +165,35 @@ fn kilde(r: &Resultat, scenarier: &[(String, Resultat)]) -> String { } s.push_str(" ]\n)\n\n#v(0.6cm)\n"); + // Internkontroll: hvilket prosjekt rapporten hører til, hvilken versjon av det, og + // hva den er laget med. Står på forsiden i stedet for i bunnteksten på hver side. + let mut felter: Vec<(String, String)> = Vec::new(); + // Adressen står allerede under tittelen på forsiden. + felter.push(("Prosjekt".into(), l.navn.clone())); + if let Some(v) = &rap.versjon { + felter.push(("Prosjektversjon".into(), v.clone())); + } + if let Some(o) = &b.oppdragsgiver { + felter.push(("Oppdragsgiver".into(), o.clone())); + } + if let Some(ra) = &b.energiraadgiver { + felter.push(("Energirådgiver".into(), ra.clone())); + } + felter.push(("Dato".into(), i_dag())); + felter.push(( + "Beregnet med".into(), + format!("enok {}", env!("CARGO_PKG_VERSION")), + )); + s.push_str("#internkontroll((\n"); + for (navn, verdi) in &felter { + s.push_str(&format!( + " (\"{}\", \"{}\"),\n", + streng(navn), + streng(verdi) + )); + } + s.push_str("))\n\n#v(0.6cm)\n"); + // Nøkkeltall. let nøkkel = vec![ ("Bygningskategori".to_string(), e(&k.kategorinavn)), @@ -828,7 +857,7 @@ fn bunntekst(r: &Resultat) -> String { if let Some(e) = &b.energiraadgiver { deler.push(e.clone()); } - deler.push(format!("enok {} · {}", env!("CARGO_PKG_VERSION"), i_dag())); + // Versjon og dato står i internkontrollfeltet på forsiden, ikke på hver side. deler.join(" · ") } diff --git a/tests/rapport.rs b/tests/rapport.rs new file mode 100644 index 0000000..e66306e --- /dev/null +++ b/tests/rapport.rs @@ -0,0 +1,69 @@ +//! Kontroll av opplysningene rapporten skal bære: hvilket prosjekt, hvilken versjon og når. + +use enok::data::Datakilde; +use enok::{inndata, kjor, rapport}; +use std::path::{Path, PathBuf}; + +fn rapportkilde(versjon: Option<&str>, katalog: &str) -> String { + let mut b = inndata::les_bygning(Path::new("eksempler/referansebygg/smaahus.toml")).unwrap(); + b.rapport.versjon = versjon.map(str::to_string); + let r = kjor::kjor(&b, &Datakilde::default()).unwrap(); + let ut: PathBuf = std::env::temp_dir().join(katalog); + rapport::typst_ut::skriv(&r, &ut, &[]).unwrap(); + std::fs::read_to_string(ut.join("rapport.typ")).unwrap() +} + +fn oppsettlinjen(kilde: &str) -> String { + kilde + .lines() + .find(|l| l.starts_with("#show: oppsett.with(")) + .unwrap_or_default() + .to_string() +} + +/// Prosjektversjonen hører hjemme i internkontrollfeltet på forsiden. +#[test] +fn prosjektversjonen_staar_i_internkontrollfeltet() { + let kilde = rapportkilde(Some("v0.3.12"), "enok-rapport-med-versjon"); + assert!( + kilde.contains("#internkontroll(("), + "mangler internkontrollfeltet" + ); + assert!( + kilde.contains("(\"Prosjektversjon\", \"v0.3.12\")"), + "mangler prosjektversjonen" + ); + assert!(kilde.contains("(\"Prosjekt\","), "mangler prosjektnavnet"); + assert!(kilde.contains("(\"Dato\","), "mangler datoen"); + assert!( + kilde.contains("(\"Beregnet med\", \"enok "), + "mangler enok-versjonen" + ); +} + +/// Uten oppgitt versjon står feltet der, men uten en tom versjonslinje. +#[test] +fn uten_versjon_staar_ingen_versjonslinje() { + let kilde = rapportkilde(None, "enok-rapport-uten-versjon"); + assert!(kilde.contains("#internkontroll((")); + assert!( + !kilde.contains("Prosjektversjon"), + "skal ikke stå tom versjon" + ); +} + +/// Versjon og dato skal stå ett sted, på forsiden, ikke på hver eneste side. +#[test] +fn bunnteksten_gjentar_ikke_versjon_og_dato() { + let kilde = rapportkilde(Some("v0.3.12"), "enok-rapport-bunntekst"); + let linje = oppsettlinjen(&kilde); + assert!(!linje.is_empty(), "fant ikke oppsettlinjen"); + assert!( + !linje.contains("enok "), + "bunnteksten gjentar enok-versjonen: {linje}" + ); + assert!( + !linje.contains("v0.3.12"), + "bunnteksten gjentar prosjektversjonen" + ); +} diff --git a/typst/stil.typ b/typst/stil.typ index 20941e3..e835fe8 100644 --- a/typst/stil.typ +++ b/typst/stil.typ @@ -107,6 +107,32 @@ )[#tekst] } +// Internkontrollfelt på forsiden: hvilket prosjekt, hvilken versjon og når. +#let internkontroll(rader) = context { + block( + width: 100%, + inset: 9pt, + radius: 4pt, + fill: lys, + stroke: 0.4pt + kantlinje, + stack( + spacing: 6pt, + text( + font: overskriftsskrift(), + size: 7.5pt, + fill: aksent-valgt.get(), + tracking: 0.8pt, + )[INTERNKONTROLL], + ..rader.map(rad => grid( + columns: (3.8cm, 1fr), + gutter: 6pt, + text(size: 8.5pt, fill: mid)[#rad.at(0)], + text(size: 8.5pt)[#rad.at(1)], + )), + ), + ) +} + // Stor karakterrute på forsiden. #let karakterrute(bokstav, verdi, farge) = { box(