feat: write template

This commit is contained in:
u80864958 committed 2026-09-18 16:36:52 +02:00
commit a0a6d2cf84
98 files changed
+6609

No files matched your search

+45
View File
@@ -0,0 +1,45 @@
#let _acronymDict = state("b57641b0-ef4f-4914-9114-20ce81a4a2ab", (:))
#let setDefaults(defaults) = {
_acronymDict.update(dict => {
for (acronym, explanation) in defaults.pairs() {
dict.insert(acronym, explanation)
}
return dict
})
}
#let acronym(acronym, explanation) = {
_acronymDict.update(dict => {
dict.insert(acronym, explanation)
return dict
})
acronym
}
#let acronymList() = {
context {
[ = Abkürzungsverzeichnis]
terms(tight: true)
figure(
table(
columns: (1fr, 3fr),
table.header([Abkürzung], [Bedeutung]),
.._acronymDict
.get()
.keys()
.sorted(key: lower)
.map(key => {
return (
key,
_acronymDict.get().at(key),
)
})
.flatten(),
),
caption: [Abkürzungsverzeichnis],
)
}
}
+111
View File
@@ -0,0 +1,111 @@
#let timetable = state("6467cc7b-cfda-4f4d-a2fe-2e5e696dff91", (:))
#let format_int(x) = (
if x == 0 [0] else if x > 0 [$+$#str(x)] else [#str(x)]
)
#let addTimetable(times) = {
timetable.update(tt => {
for (date, tasks) in times.pairs() {
tt.insert(date, tasks.filter(t => t.name != "Total"))
}
return tt
})
}
#let dayNr = state("345de571-1c11-4fba-9ebf-61c2088e286a", 0)
#let entry(
day: datetime,
defaultPerson: str,
supplementaryPersons: (:),
tagesablauf,
hilfestellungen,
gut,
schlecht,
erkenntnisse,
) = {
dayNr.update(i => i + 1)
context {
if dayNr.get() > 1 { pagebreak(weak: true) }
heading(level: 2, [Tag #dayNr.get() | #day.display("[day].[month].[year]")])
show heading.where(level: 3): set heading(numbering: none, outlined: false)
show heading.where(level: 3): set heading(numbering: none, outlined: false)
let taetigkeiten = timetable.get().at(day.display("[day padding:none]-[month padding:none]-[year]"))
let total_soll = (
taetigkeiten.fold(
0,
(acc, t) => if t.keys().contains("should") { acc + t.should } else { acc },
)
/ 60
) // total should hours
let total_ist = (
taetigkeiten.fold(
0,
(acc, t) => if t.keys().contains("is") { acc + t.is } else { acc },
)
/ 60
) // total should hours
figure(
table(
columns: (3fr, 1fr, 1fr, 1fr, 1fr),
align: (left, center, center, center, center),
table.header([Tätigkeiten], [Beteiligte Personen], [Aufwand SOLL [h]], [Aufwand IST [h]], [Differenz [h]]),
..taetigkeiten
.map(t => (
t.at("name"),
if supplementaryPersons.keys().contains(t.at("name")) {
(defaultPerson, ..supplementaryPersons.at(t.at("name"))).join(", ")
} else {
defaultPerson
},
str(t.at("should", default: "") / 60),
str(t.at("is", default: "") / 60),
if t.keys().contains("is") and t.keys().contains("should") {
format_int((t.is / 60) - (t.should / 60))
} else { "" },
))
.flatten(),
[*Totaler Zeitaufwand*],
[],
str(total_soll),
str(total_ist),
format_int(total_ist - total_soll),
),
caption: [Tätigkeiten Tag #dayNr.get()],
)
[
=== Tagesablauf
#tagesablauf
=== Hilfestellungen
#hilfestellungen
=== Reflexion
_Was lief gut?_
#gut
_Was lief weniger gut?_
#schlecht
_Erkenntnisse_
#erkenntnisse
]
}
}
+134
View File
@@ -0,0 +1,134 @@
// Execute: `git ls-files > ../../07_Anhang/source-paths.txt` while in the code directory
#import "../pallete.typ": palette
#import "@preview/shadowed:0.3.0": shadow
#let excluded = (
"package-lock.json",
"go.sum",
".vscode",
".husky",
".npmrc",
)
#let generated = (
"package.json",
"go.mod",
"tsconfig.",
"jestGlobalMocks.ts",
"setupJest.ts",
"jest.config.ts",
"jest.config.js",
"styles.scss",
"index.html",
".browserslistrc",
".editorconfig",
".prettierrc",
".npmrc",
".husky",
"code-project",
"eslint.config.mjs",
"angular.json",
"api.gen.go",
".bru",
)
#let generated-changed = (
"angular.json",
)
#let refs = (
"schema.cql": "schema-cql",
"kafka-startup.sh": "kafka-startup",
"service_test.go": "journal-integrationtest",
"auto-backup": "auto-backup",
)
#let code(files: (), generated: generated, excluded: excluded) = {
show figure.where(kind: raw): set block(breakable: true)
// Sort files by path
let sorted-files = files.sorted(key: f => f.path)
for file in sorted-files {
let path = file.path
if excluded.any(ex => path.contains(ex)) {
continue
}
let content = file.content
let refkey = refs.keys().find(r => path.contains(r))
let ref = if refkey != none { refs.at(refkey) } else { none }
let filetype = path.split(".").last()
let map = (
kt: "kotlin",
sh: "bash",
ts: "typescript",
scss: "css",
html: "html",
go: "go",
mod: "gomod",
gitignore: "gitignore",
json: "json",
"Dockerfile": "Dockerfile",
cql: "sql",
yaml: "yml",
yml: "yml",
example: "env", // .env.exampln
md: "markdown",
js: "js",
mjs: "js",
Makefile: "make",
makefile: "make",
)
if map.keys().contains(filetype) {
filetype = map.at(filetype)
} else {
filetype = "text"
}
[
// #show heading: it => {
// let h1 = query(metadata.where(value: path).before(here()))
// if h1.len() != 0 and h1.first().location().position().y > page.height * .8 {
// pagebreak()
// }
// it
// }
// #show figure: set block(breakable: true)
//
#show heading.where(level: 3): it => {
let threshold = 20%
block(breakable: false, height: threshold, spacing: 0pt)
v(-threshold)
it
}
#metadata(path)
#heading(outlined: false, level: 3)[
#path #if generated.any(gen => path.contains(gen)) { [ | Generiert ] }
]
#figure(
block(
stroke: 0.5pt + palette.ui.border,
width: 100%,
inset: 5pt,
fill: white,
)[
#set align(left)
#raw(content, block: true, lang: filetype, align: start)
],
placement: none,
caption: [Datei #path],
)
]
}
}
+32
View File
@@ -0,0 +1,32 @@
#import "@preview/shadowed:0.3.0": shadow
#import "../pallete.typ": palette
#let cover(img: bytes, part: content, description: content) = {
page(
background: image(
img,
height: 100%,
),
margin: 0pt,
)[
#align(
bottom,
shadow(blur: 140pt, fill: rgb(0, 0, 0, 50%), radius: -50pt, rect(
width: 100%,
fill: palette.secondary.at("900"),
inset: 2.25cm,
{
show heading: set text(26pt, font: "Arial", fill: white)
heading(bookmarked: true, numbering: none, strong(part))
par(text(size: 26pt, font: "Arial", fill: palette.gray.at("50"), weight: "bold", description))
par(
text(
size: 12pt,
fill: palette.gray.at("300"),
)[Individuelle praktische Arbeit],
)
},
)),
)
]
}
+51
View File
@@ -0,0 +1,51 @@
#let _glossaryDict = state("e8759020-8d68-4920-9ce6-246b09c3094b", none)
#let setDefaults(defaults) = {
_glossaryDict.update(dict => {
for (term, explanation) in defaults.pairs() {
dict.insert(term, explanation)
}
return dict
})
}
#let term(term, explanation) = {
_glossaryDict.update(dict => {
if dict == none {
return (
"" + term: explanation,
)
}
dict.insert(term, explanation)
return dict
})
term
}
#let glossary() = {
context {
if _glossaryDict.get() == none {
return
}
[ = Glossar]
terms(tight: true)
figure(
table(
columns: (1fr, 3fr),
table.header([Begriff], [Bedeutung]),
.._glossaryDict
.get()
.keys()
.sorted(key: lower)
.map(key => (
key,
_glossaryDict.get().at(key),
))
.flatten(),
),
caption: [Glossar],
)
}
}
+39
View File
@@ -0,0 +1,39 @@
#let _persons = state("598041ec-f6ce-4a04-872a-032a8325b760", ())
#let byFunction(
function,
) = {
let ps = _persons.get().filter(person => person.at("function", default: "") == function)
if ps.len() > 0 {
return ps.at(0)
} else {
return (
name: "Axel Schweiss",
email: "axel.schweiss@bit.admin.ch",
shorthand: "AS",
function: function,
where: "In der Genehmigung",
git: "xxxxxxxxx",
)
}
}
#let overview() = {
return (
[Name],
[Kontakt],
[Kürzel],
.._persons
.get()
.map(p => (
[#p.at("function-name", default: p.function): \ #p.name],
[
#if p.at("phone", default: "") != "" { [Telefon: #link("tel:" + p.phone, p.phone) \ ] }
#if p.at("email", default: "") != "" { [Email: #link("mailto:" + p.email, p.email) \ ] }
],
p.shorthand,
))
.flatten(),
)
}
+15
View File
@@ -0,0 +1,15 @@
#let requirementIDCounter = state("8067fd32-20a6-4b95-b50d-c5e2f431b7b0", 0)
#let id(uid: "") = {
requirementIDCounter.update(s => s + 1)
context {
let val = requirementIDCounter.get()
let myLabel = label("req-" + if uid == "" { "a" + str(val) } else { uid })
[#figure([A#val], kind: "req", supplement: "Anforderung ", numbering: num => (
"A" + str(num)
)) #myLabel]
}
}
+22
View File
@@ -0,0 +1,22 @@
#import "../pallete.typ": palette
#let sideways(..args) = {
// This rule is scoped only to this specific table
show table.cell.where(x: 0): strong
show table.cell.where(y: 0): it => strong(delta: -300, it)
table(
// Your existing stroke/fill logic
fill: (_, y) => if calc.odd(y) { rgb(palette.ui.bg-dark) },
stroke: (x, y) => (
top: none,
bottom: none,
left: if x == 0 { 0pt } else if x == 1 { palette.ui.border } else { 1pt + palette.ui.border },
right: if x == 0 { 2pt },
),
..args
)
}
+1
View File
@@ -0,0 +1 @@
#import "./testcase.typ": case, cases, defectClassToString, protocoll, results
+166
View File
@@ -0,0 +1,166 @@
#import "../../pallete.typ": palette
#import "../tabler.typ"
#let _testCases = state("9ca8f801-3bfd-4a72-a716-966aec564315", ())
#let _testResults = state("7372165e-6204-43a2-8780-46a41d7e2841", ())
#let defectClassToString(defectClass) = {
if (defectClass == 4) {
return "kritischer Mangel"
}
if (defectClass == 3) {
return "schwerer Mangel"
}
if (defectClass == 2) {
return "leichter Mangel"
}
if (defectClass == 1) {
return "belangloser Mangel"
}
return "mängelfrei"
}
#let case(
// Welcher Usercase oder requirement der testfall testet.
requirement: [],
description: [],
precondition: [],
steps: [],
result: [],
) = {
// style
// show table.cell.where(x: 0): strong
// show table.cell.where(y: 0): it => strong(delta: -300, it)
_testCases.update(cases => {
cases.push((
requirement: requirement,
description: description,
precondition: precondition,
steps: steps,
result: result,
))
return cases
})
context {
let id = "TC-" + str(_testCases.get().len())
[
#figure(
tabler.sideways(
columns: (auto, 1fr, 10em),
[ID / Bezeichnung],
[#id],
[Testet: #requirement],
[Beschreibung],
table.cell(colspan: 2, description),
[Testvoraussetzung],
table.cell(colspan: 2, precondition),
[Testschritte],
table.cell(colspan: 2, steps),
[Erwartetes Ergebnis],
table.cell(colspan: 2, result),
),
caption: [Testfall #id],
)#label(id)
]
}
}
#let cases() = {
return _testCases
.final()
.enumerate(start: 1)
.map(c => (
id: "TC-" + str(c.at(0)),
requirement: c.at(1).requirement,
description: c.at(1).description,
precondition: c.at(1).precondition,
steps: c.at(1).steps,
result: c.at(1).result,
))
}
#let protocoll(
case: "",
date: datetime.today(),
tester: "",
defectClass: none,
defect: "",
result,
) = {
// style
show table.cell.where(x: 0): strong
show table.cell.where(y: 0): it => strong(delta: -300, it)
if (defectClass == none) {
defectClass = 0
}
let dcColor() = {
if (defectClass >= 4) {
return palette.red.at("300")
}
if (defectClass >= 2) {
return palette.orange.at("300")
}
if (defectClass > 0) {
return palette.yellow.at("300")
}
return palette.green.at("300")
}
_testResults.update(cases => {
cases.push((
case: case,
date: date,
defectClass: defectClass,
defect: defect,
))
return cases
})
context {
figure(
tabler.sideways(
columns: (auto, 1fr),
[Testfall:],
link(label(case), case),
[Tester:],
[#tester],
[Datum:],
date.display("[day].[month].[year]"),
[Mängelklasse:],
table.cell(fill: dcColor())[#defectClass],
..if (defectClass > 0) {
([Mängel:], defect)
},
[Ergebnis:],
result,
),
caption: [Testergebnis für #link(label(case), case) vom #date.display("[day].[month].[year]")],
)
}
}
// table.header([Testfall], [Durchführungsdatum], [Mängelklasse], [Mängel]),
#let results() = {
return _testResults
.final()
.map(r => (
r.case,
r.date.display("[day].[month].[year]"),
table.cell(fill: if (r.defectClass >= 4) {
palette.red.at("300")
} else if (r.defectClass >= 2) {
palette.orange.at("300")
} else if (r.defectClass > 0) {
palette.yellow.at("300")
} else { palette.green.at("300") })[#r.defectClass],
if r.defect == "" { "-" } else { r.defect },
))
}
+124
View File
@@ -0,0 +1,124 @@
#import "./persons.typ"
#import "../pallete.typ": palette
// helpers for title
#let getWhere(person) = {
let function = person.function
if function == "Validierungsexperte" {
return "In der Genehmigung"
}
if function == "Hauptexperte" or function == "Nebenexperte" or function == "Verantwortliche Fachkraft" {
return "In der Durchführung"
}
if function == "Berufsbildner" {
return "Zur Information, Kenntnis"
}
return ""
}
#let beteiligtePersonen() = {
return (
persons.byFunction("Validierungsexperte"),
persons.byFunction("Hauptexperte"),
persons.byFunction("Nebenexperte"),
persons.byFunction("Verantwortliche Fachkraft"),
persons.byFunction("Berufsbildner"),
)
}
#let title(
title: content,
author: content,
company: content,
departement: content,
tradeschool: "Gewerblich-industrielle Berufsschule Bern GIBB",
metadata: (
specialisation: "Applikationsentwicklung",
managementMethod: "HERMES",
// year as in "Jahrgang"
year: "IPA 2025",
place: "Kanton Bern",
dateOfSubmission: datetime,
currentversion: version,
),
) = {
// set text(font: "Helvetica Neue")
place(
top + left,
image("../assets/Logo_RGB_farbig_positiv.svg", height: 2cm),
)
place(
top + right,
[
#set text(size: 8pt, font: "Helvetica Neue")
#v(4pt)
Eidgenössisches Finanzdepartement EFD
*#company* \
#departement \
Individuelle praktische Arbeit
],
)
v(4cm)
text(
size: 21pt,
font: "Helvetica Neue",
fill: palette.secondary.at("500"),
title,
)
align(
right,
text(
size: 17pt,
font: "Helvetica Neue",
fill: gray,
[
Individuelle praktische Arbeit \
#author
],
),
)
set table(stroke: 0.1pt + palette.ui.border)
v(.5cm)
[
#set text(font: "Helvetica Neue")
#table(
columns: (.5fr, 1fr),
inset: 8pt,
align: horizon,
[*Autor*], author,
[*Firma*], company,
[*Abteilung*], departement,
[*Berufsschule*], [gibb Berufsfachschule Bern],
)
#heading(level: 3, [Beteiligte Personen], outlined: false)
#context table(
columns: (.5fr, .5fr, .5fr),
inset: 8pt,
..beteiligtePersonen()
.map(p => (
par([*#p.at("function-name", default: p.function)*], justify: false),
link("mailto:" + p.email)[#p.name],
context getWhere(p),
))
.flatten()
)
#table(
columns: (.5fr, 1fr),
inset: 8pt,
align: horizon,
[*Fachrichtung*], [#metadata.specialisation],
[*Projektvorgehensmodell*], [#metadata.managementMethod],
[*Jahrgang, Kanton*], [#metadata.dateOfSubmission.year(), #metadata.place],
[*Abgabedatum*], metadata.dateOfSubmission.display("[day].[month].[year]"),
[*Version*], [Version v#metadata.currentversion],
)
]
}
File diff suppressed because it is too large. Load diff
+81
View File
@@ -0,0 +1,81 @@
#import "tabler.typ"
// Colors used across the template.
#let stroke-color = luma(200)
#let fill-color = luma(240)
#let idState = state("04cb31e7-92f1-4e72-82d8-e5ef190d0745", 0)
#let usecase(
name: "",
diagramm: none,
actors: ([],),
precondition: [],
process: [],
acceptanceCriteria: none,
offset: 2,
) = {
idState.update(s => s + 1)
set heading(offset: offset, numbering: none)
context [
#figure(
none,
kind: "usecase",
supplement: _ => [UC-#h(-0.25em)],
)
#label("usecase-" + str(idState.get()))
#figure(
tabler.sideways(
columns: (auto, 1fr),
[Bezeichnung ],
[UC-#idState.get(): #name],
..(
if diagramm != none {
(
[Diagramm],
block(
inset: (top: 0.5em, left: 0.5em, right: 0.5em),
width: 100%,
figure(
diagramm,
caption: [Diagramm für #{ "UC-" + str(idState.get()) }],
),
),
)
} else { () }
),
[Beteiligte Akteure],
actors.join(", "),
[Vorbedingungen],
precondition,
[Ablauf],
process,
..(
if acceptanceCriteria != none {
(
[Akzeptanzkriterien],
[
#for criteria in acceptanceCriteria {
[+ #criteria]
}
],
)
}
),
),
caption: [Anwendungsfall: #{ "UC-" + str(idState.get()) }],
)
]
}
+81
View File
@@ -0,0 +1,81 @@
#let toVersions(commits: (("2025-11-19", "u80864958", "msg"),), persons: dictionary, final: false) = {
let versions = ()
for commit in commits.rev() {
let v = version(0, 0, 0)
let message = str(commit.at(2))
if versions.len() > 0 {
// versions.at(-1).v
v = versions.at(-1).v
if message.starts-with("fix") or message.starts-with("chore") {
v = version(v.at(0), v.at(1), v.at(2) + 1)
} else if message.starts-with("feat") {
v = version(v.at(0), v.at(1) + 1, 0)
}
}
if message.contains(":") {
message = commit.at(2).split(":").slice(1).flatten().at(0).trim()
}
versions.push(
(
date: toml(bytes("date = " + commit.at(0))).date,
v: v,
author: persons.at(default: persons.at(persons.keys().at(0)), commit.at(1)),
topic: message,
),
)
}
if final {
versions.push(
(
date: datetime.today(),
v: version(1, 0, 0),
author: persons.at(persons.keys().at(0)),
topic: "Finalisierung",
),
)
}
return versions
}
#let parseCurrentVersion(
commits: (("2025-11-19", "u80864958", "init"),),
persons: (
"u80864958": "Niklas Breitenstein",
"u80864987": "Johar Naamneh",
),
final: false,
) = {
return toVersions(commits: commits, persons: persons, final: final).at(-1).v
}
#let versionhistoryTable(
commits: (("2025-11-19", "u80864958", "init"),),
persons: (
"u80864958": "Niklas Breitenstein",
"u80864987": "Johar Naamneh",
),
final: false,
) = {
let versions = toVersions(commits: commits, persons: persons, final: final)
set heading(numbering: none)
[= Änderungsverzeichnis]
set par(justify: false)
figure(
table(
columns: (6em, 6em, 2fr, 2.7fr),
table.header([Version], [Datum], [Name], [Themen]),
..versions
.map(v => (
[v#v.v],
v.date.display("[day].[month].[year]"),
v.author,
v.topic,
))
.flatten(),
),
caption: [Änderungsverzeichnis],
)
}