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

+3
View File
@@ -0,0 +1,3 @@
.DS_Store
template/IPA_Dokumentation_*.pdf
+63
View File
@@ -0,0 +1,63 @@
# CHFOIT-IPA
CHFOIT-IPA is a specialized [Typst](https://typst.app/) library and template tailored for the "Individuelle Produktivarbeit" (IPA) within
the Federal Office of Information Technology (BIT) of Switzerland.
It automates the administrative overhead of documentation-versioning, schedule syncing,
and code embedding. It allows you to focus on the technical content of your project.
## Features
- **Swiss Government Styling**: Implements the official color palette and typography of the [Swiss Design System](https://github.com/swiss/designsystem).
- **Automated Document Assembly**: A modular structure that pulls content from structured subdirectories.
- **Open API Spec renderer**: You can use `"@local/chfoit-ipa:0.0.0": oasviz` to render your swagger.json
- **Dynamic Asset Pipeline**:
- **Draw.io Integration**: Automatic export of `.drawio` diagrams to PDF.
- **Excel to Document**: Syncs Excel-based schedules into PDF attachments and provides the timetable for the "Arbeitsjournal"
- **Git-Powered Versioning**: Automatically populates the "Version History" table using `git log` metadata and person mapping.
- **Smart Appendix**: Automatically tracks and renders source code from the `04_Code/` directory with syntax highlighting.
- **Language Support**: Automatic "ß" to "ss" conversion.
## Usage
### Setup Configuration
1. Clone this Repository
2. Make a symlink to your local Typst packages:
`ln -s "$PWD" "${data-dir}/typst/packages/local/chfoit-ipa/0.0.0"`
The variable data-dir is:
- `$XDG_DATA_HOME` or `~/.local/share` on Linux
- `~/Library/Application Support` on macOS
- `%APPDATA%` on Windows
3. Initialize your IPA with this command:
```
typst init @local/chfoit-ipa:0.0.0 test
```
4. Change the placeholder Variables
5. Have a successful IPA
### Build Process
The template relies on a `Makefile` to handle external dependencies like Draw.io and Excel.
Here are the most important targets:
- **Build everything**: `make `
- **Live Preview**: `make watch` (Watches only Typst files).
- **Build Diagrams**: `make drawio`.
## Requirements
To use the full automation suite, ensure the following are installed:
- **Typst CLI**: The core compiler.
- **Draw.io Desktop**: For automated diagram exports.
If you are on macOS you may have to specify `DRAWIO_CMD="/Applications/draw.io.app/Contents/MacOS/draw.io"`
when running your makefiles.
- **LibreOffice (Headless)**: To convert `.xlsx` project plans to PDF.
- **Python 3**: For converting Excel schedules to YAML for dynamic Typst.
Make sure you have the dependencies from the `02_Zeitplan/requirements.txt` file installed.
+426
View File
@@ -0,0 +1,426 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Ebene_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 168.21 47.49" style="enable-background:new 0 0 168.21 47.49;" xml:space="preserve">
<style type="text/css">
.st0{fill:#010000;}
.st1{fill:#FF0000;}
.st2{fill:#FFFFFF;}
</style>
<g>
<g>
<path class="st0" d="M34.19,9.3c0.34,0,0.62-0.09,0.84-0.26c0.22-0.17,0.33-0.44,0.34-0.81c0-0.26-0.13-0.47-0.38-0.65
c-0.11-0.09-0.24-0.17-0.38-0.25s-0.29-0.16-0.44-0.24C34.02,7,33.87,6.91,33.73,6.82c-0.15-0.09-0.28-0.19-0.39-0.3
C33.1,6.3,32.97,6.03,32.97,5.7c0-0.43,0.17-0.76,0.49-1.01c0.31-0.24,0.7-0.36,1.17-0.36c0.13,0,0.29,0.01,0.48,0.03
c0.19,0.03,0.37,0.08,0.55,0.14l-0.08,0.47c-0.26-0.13-0.57-0.2-0.95-0.2c-0.13,0-0.27,0.01-0.4,0.04
c-0.13,0.03-0.25,0.08-0.37,0.16c-0.1,0.06-0.19,0.15-0.26,0.27c-0.07,0.12-0.1,0.27-0.1,0.44c0,0.12,0.03,0.24,0.1,0.35
c0.07,0.1,0.16,0.2,0.27,0.28c0.12,0.08,0.25,0.17,0.39,0.24c0.14,0.08,0.29,0.16,0.44,0.25c0.31,0.15,0.59,0.34,0.83,0.57
c0.12,0.11,0.21,0.24,0.28,0.39c0.06,0.15,0.1,0.32,0.1,0.52c-0.01,0.52-0.19,0.9-0.55,1.13c-0.35,0.23-0.74,0.35-1.17,0.35
c-0.51-0.01-0.91-0.08-1.19-0.23l0.05-0.53C33.33,9.2,33.71,9.3,34.19,9.3z"/>
<path class="st0" d="M39.95,6.34c-0.27-0.12-0.54-0.19-0.82-0.19c-0.46,0-0.81,0.15-1.06,0.44c-0.25,0.29-0.38,0.68-0.38,1.16
c0,0.45,0.12,0.82,0.38,1.12c0.25,0.31,0.58,0.48,1.01,0.48c0.3,0,0.58-0.06,0.86-0.17l0.04,0.44c-0.31,0.09-0.64,0.14-0.98,0.13
c-0.62-0.01-1.07-0.21-1.37-0.61c-0.31-0.39-0.46-0.85-0.46-1.4c0-0.6,0.18-1.09,0.53-1.45c0.34-0.36,0.8-0.55,1.37-0.56
c0.23,0,0.43,0.02,0.58,0.05c0.08,0.01,0.15,0.03,0.21,0.04c0.05,0.02,0.1,0.04,0.13,0.05L39.95,6.34z"/>
<path class="st0" d="M41.38,4.03h0.47v2.45h0.02c0.1-0.21,0.26-0.39,0.47-0.53c0.21-0.13,0.45-0.2,0.73-0.2
c0.51,0,0.86,0.16,1.04,0.45c0.18,0.3,0.27,0.69,0.26,1.18v2.29h-0.47V7.38c0-0.17-0.01-0.33-0.03-0.48
c-0.02-0.15-0.06-0.27-0.13-0.38c-0.12-0.24-0.37-0.36-0.75-0.37c-0.41,0.01-0.7,0.17-0.88,0.47c-0.18,0.31-0.26,0.64-0.26,0.99
v2.05h-0.47V4.03z"/>
<path class="st0" d="M50.07,9.66H49.5l-1.04-3.31h-0.02L47.4,9.66h-0.57l-1.23-3.83h0.52l1.01,3.31h0.02l1.07-3.31h0.57l1.01,3.31
h0.01l1.06-3.31h0.47L50.07,9.66z"/>
<path class="st0" d="M55.12,9.54c-0.36,0.14-0.73,0.21-1.11,0.21c-0.62-0.01-1.07-0.2-1.34-0.58c-0.27-0.37-0.4-0.84-0.4-1.43
c0-0.6,0.16-1.08,0.45-1.45c0.29-0.36,0.67-0.55,1.16-0.56c0.54,0.01,0.93,0.2,1.17,0.56c0.24,0.36,0.35,0.81,0.35,1.34v0.24
h-2.62c0,0.42,0.11,0.76,0.32,1.03c0.21,0.29,0.52,0.43,0.94,0.44c0.19,0,0.38-0.03,0.59-0.1c0.21-0.06,0.37-0.12,0.49-0.19V9.54z
M54.89,7.48c0-0.35-0.07-0.66-0.22-0.92c-0.16-0.27-0.41-0.4-0.76-0.41C53.57,6.16,53.3,6.3,53.1,6.57
c-0.21,0.27-0.31,0.57-0.31,0.91H54.89z"/>
<path class="st0" d="M57.37,4.81h-0.47V4.18h0.47V4.81z M56.89,5.83h0.47v3.83h-0.47V5.83z"/>
<path class="st0" d="M58.79,9.26l2.3-3.02h-2.21V5.83h2.76v0.41l-2.3,3.02h2.3v0.41h-2.85V9.26z"/>
<path class="st0" d="M65.63,9.54c-0.36,0.14-0.73,0.21-1.11,0.21c-0.63-0.01-1.07-0.2-1.34-0.58c-0.27-0.37-0.4-0.84-0.4-1.43
c0-0.6,0.16-1.08,0.45-1.45c0.29-0.36,0.67-0.55,1.16-0.56c0.54,0.01,0.93,0.2,1.17,0.56c0.24,0.36,0.35,0.81,0.35,1.34v0.24H63.3
c0,0.42,0.11,0.76,0.32,1.03c0.21,0.29,0.52,0.43,0.94,0.44c0.19,0,0.38-0.03,0.59-0.1c0.21-0.06,0.37-0.12,0.49-0.19V9.54z
M65.4,7.48c0-0.35-0.07-0.66-0.22-0.92c-0.16-0.27-0.41-0.4-0.76-0.41C64.08,6.16,63.8,6.3,63.6,6.57
C63.4,6.84,63.3,7.14,63.3,7.48H65.4z"/>
<path class="st0" d="M67.4,6.69c0-0.35-0.01-0.64-0.03-0.86h0.47v0.74h0.01c0.09-0.23,0.22-0.42,0.38-0.58
c0.16-0.16,0.36-0.24,0.6-0.25c0.12,0,0.23,0.02,0.33,0.04v0.5c-0.1-0.03-0.22-0.05-0.35-0.05c-0.19,0.01-0.34,0.05-0.46,0.14
c-0.12,0.09-0.22,0.21-0.29,0.37c-0.07,0.15-0.12,0.31-0.15,0.49c-0.04,0.19-0.05,0.37-0.05,0.54v1.89H67.4V6.69z"/>
<path class="st0" d="M70.96,4.81h-0.47V4.18h0.47V4.81z M70.48,5.83h0.47v3.83h-0.47V5.83z"/>
<path class="st0" d="M73.44,9.35c0.23,0,0.44-0.06,0.6-0.19c0.16-0.11,0.24-0.28,0.25-0.5c0-0.23-0.1-0.4-0.28-0.53
c-0.18-0.12-0.39-0.24-0.64-0.34c-0.11-0.05-0.23-0.11-0.33-0.17s-0.21-0.13-0.3-0.2c-0.08-0.08-0.15-0.17-0.21-0.28
c-0.06-0.1-0.08-0.22-0.08-0.35c0-0.38,0.14-0.65,0.39-0.81c0.25-0.15,0.53-0.23,0.86-0.23c0.11,0,0.25,0.01,0.43,0.03
c0.18,0.03,0.35,0.08,0.52,0.14L74.6,6.33c-0.27-0.12-0.56-0.18-0.85-0.18c-0.25,0-0.44,0.04-0.59,0.14
c-0.08,0.04-0.13,0.11-0.17,0.19c-0.05,0.08-0.07,0.19-0.07,0.31c0,0.08,0.03,0.16,0.08,0.23c0.06,0.06,0.13,0.12,0.22,0.18
c0.09,0.05,0.19,0.1,0.31,0.16c0.12,0.05,0.23,0.1,0.34,0.17c0.25,0.1,0.47,0.25,0.65,0.42c0.18,0.18,0.28,0.42,0.29,0.73
c0,0.2-0.04,0.37-0.12,0.5c-0.09,0.14-0.19,0.25-0.32,0.34c-0.28,0.17-0.58,0.25-0.89,0.25c-0.37,0-0.73-0.06-1.07-0.18l0.04-0.47
C72.73,9.26,73.06,9.34,73.44,9.35z"/>
<path class="st0" d="M78.73,6.34c-0.27-0.12-0.54-0.19-0.82-0.19c-0.46,0-0.81,0.15-1.06,0.44c-0.25,0.29-0.38,0.68-0.38,1.16
c0,0.45,0.12,0.82,0.38,1.12c0.25,0.31,0.58,0.48,1.01,0.48c0.3,0,0.58-0.06,0.86-0.17l0.04,0.44c-0.31,0.09-0.64,0.14-0.98,0.13
c-0.62-0.01-1.07-0.21-1.37-0.61c-0.31-0.39-0.46-0.85-0.46-1.4c0-0.6,0.18-1.09,0.53-1.45c0.34-0.36,0.8-0.55,1.37-0.56
c0.23,0,0.43,0.02,0.58,0.05c0.08,0.01,0.15,0.03,0.21,0.04c0.05,0.02,0.1,0.04,0.13,0.05L78.73,6.34z"/>
<path class="st0" d="M80.16,4.03h0.47v2.45h0.02c0.1-0.21,0.26-0.39,0.47-0.53c0.21-0.13,0.45-0.2,0.73-0.2
c0.51,0,0.86,0.16,1.04,0.45c0.18,0.3,0.27,0.69,0.26,1.18v2.29h-0.47V7.38c0-0.17-0.01-0.33-0.03-0.48
c-0.02-0.15-0.06-0.27-0.13-0.38c-0.12-0.24-0.37-0.36-0.75-0.37c-0.41,0.01-0.7,0.17-0.88,0.47c-0.18,0.31-0.26,0.64-0.26,0.99
v2.05h-0.47V4.03z"/>
<path class="st0" d="M87.48,9.54c-0.36,0.14-0.73,0.21-1.11,0.21c-0.62-0.01-1.07-0.2-1.34-0.58c-0.27-0.37-0.4-0.84-0.4-1.43
c0-0.6,0.16-1.08,0.45-1.45c0.29-0.36,0.67-0.55,1.16-0.56c0.54,0.01,0.93,0.2,1.17,0.56c0.24,0.36,0.35,0.81,0.35,1.34v0.24
h-2.62c0,0.42,0.11,0.76,0.32,1.03c0.21,0.29,0.52,0.43,0.94,0.44c0.19,0,0.38-0.03,0.59-0.1c0.21-0.06,0.37-0.12,0.49-0.19V9.54z
M87.25,7.48c0-0.35-0.07-0.66-0.22-0.92c-0.16-0.27-0.41-0.4-0.76-0.41c-0.35,0.01-0.62,0.15-0.82,0.42
c-0.21,0.27-0.31,0.57-0.31,0.91H87.25z"/>
<path class="st0" d="M91.75,4.42h2.57v0.45h-2.05v1.87h1.94v0.45h-1.94v2.02h2.14v0.45h-2.66V4.42z"/>
<path class="st0" d="M96.51,4.81h-0.47V4.18h0.47V4.81z M96.03,5.83h0.47v3.83h-0.47V5.83z"/>
<path class="st0" d="M101.32,9.66h-0.47V9.05h-0.01c-0.25,0.47-0.65,0.7-1.19,0.7c-0.53-0.01-0.92-0.2-1.18-0.58
c-0.27-0.38-0.4-0.85-0.4-1.43c0-0.6,0.12-1.08,0.37-1.44c0.25-0.37,0.65-0.56,1.21-0.56c0.19,0,0.36,0.03,0.5,0.08
c0.15,0.06,0.27,0.12,0.36,0.21c0.2,0.18,0.31,0.32,0.34,0.44h0.01V4.03h0.47V9.66z M98.63,8.3c0.04,0.19,0.1,0.37,0.18,0.53
c0.09,0.15,0.21,0.27,0.35,0.37c0.14,0.1,0.32,0.16,0.55,0.16c0.21,0,0.39-0.05,0.54-0.16c0.14-0.1,0.26-0.22,0.35-0.38
c0.17-0.34,0.25-0.69,0.25-1.06c0-0.18-0.02-0.36-0.05-0.56c-0.04-0.18-0.1-0.35-0.2-0.51c-0.09-0.16-0.21-0.29-0.35-0.39
c-0.15-0.1-0.33-0.14-0.54-0.14c-0.23,0-0.41,0.05-0.55,0.14c-0.15,0.09-0.26,0.22-0.35,0.38c-0.08,0.15-0.14,0.32-0.18,0.51
c-0.04,0.19-0.05,0.38-0.05,0.57C98.58,7.93,98.59,8.11,98.63,8.3z"/>
<path class="st0" d="M106.07,9.35c0,0.54-0.13,1-0.4,1.37c-0.27,0.37-0.74,0.56-1.4,0.57c-0.25,0-0.48-0.03-0.69-0.08
c-0.21-0.05-0.36-0.09-0.47-0.12l0.04-0.47c0.31,0.17,0.69,0.26,1.13,0.27c0.31-0.01,0.55-0.06,0.72-0.17
c0.18-0.1,0.31-0.24,0.4-0.42c0.09-0.18,0.15-0.39,0.17-0.62c0.02-0.23,0.03-0.49,0.03-0.76h-0.02c-0.27,0.51-0.66,0.76-1.16,0.75
c-0.6-0.02-1.02-0.23-1.26-0.65c-0.12-0.19-0.21-0.4-0.26-0.62c-0.06-0.22-0.09-0.44-0.09-0.65c0-0.6,0.12-1.08,0.37-1.44
c0.25-0.37,0.65-0.56,1.21-0.56c0.25,0,0.47,0.03,0.66,0.08c0.18,0.07,0.36,0.22,0.53,0.44h0.01V5.83h0.47V9.35z M103.59,8.78
c0.17,0.31,0.45,0.47,0.85,0.48c0.43-0.01,0.73-0.17,0.91-0.5c0.17-0.31,0.25-0.65,0.25-1.01c0-0.48-0.08-0.86-0.24-1.15
c-0.17-0.29-0.46-0.45-0.89-0.45c-0.23,0-0.41,0.05-0.55,0.14c-0.15,0.09-0.26,0.22-0.35,0.38c-0.08,0.15-0.14,0.32-0.18,0.51
c-0.04,0.19-0.05,0.38-0.05,0.57C103.33,8.14,103.41,8.48,103.59,8.78z"/>
<path class="st0" d="M110.37,9.54c-0.36,0.14-0.73,0.21-1.11,0.21c-0.62-0.01-1.07-0.2-1.34-0.58c-0.27-0.37-0.4-0.84-0.4-1.43
c0-0.6,0.16-1.08,0.45-1.45c0.29-0.36,0.67-0.55,1.16-0.56c0.54,0.01,0.93,0.2,1.17,0.56c0.24,0.36,0.35,0.81,0.35,1.34v0.24
h-2.62c0,0.42,0.11,0.76,0.32,1.03c0.21,0.29,0.52,0.43,0.94,0.44c0.19,0,0.38-0.03,0.59-0.1c0.21-0.06,0.37-0.12,0.49-0.19V9.54z
M110.13,7.48c0-0.35-0.07-0.66-0.22-0.92c-0.16-0.27-0.41-0.4-0.76-0.41c-0.35,0.01-0.62,0.15-0.82,0.42
c-0.21,0.27-0.31,0.57-0.31,0.91H110.13z"/>
<path class="st0" d="M112.14,6.74c0-0.3-0.01-0.6-0.03-0.9h0.46v0.68h0.02c0.04-0.09,0.1-0.18,0.16-0.27
c0.06-0.09,0.14-0.17,0.23-0.25c0.1-0.08,0.21-0.14,0.35-0.19c0.14-0.04,0.31-0.07,0.5-0.07c0.47,0,0.81,0.15,1.01,0.42
c0.2,0.27,0.29,0.62,0.29,1.05v2.45h-0.47V7.34c0-0.37-0.07-0.66-0.2-0.87c-0.15-0.21-0.38-0.31-0.71-0.31
c-0.42,0.01-0.72,0.16-0.89,0.45c-0.09,0.14-0.15,0.28-0.19,0.44c-0.04,0.17-0.06,0.33-0.06,0.5v2.12h-0.47V6.74z"/>
<path class="st0" d="M117.04,6.35c0.29-0.4,0.74-0.6,1.35-0.61c0.62,0.01,1.08,0.21,1.37,0.61c0.28,0.39,0.42,0.85,0.42,1.4
c0,0.54-0.14,1-0.42,1.39c-0.29,0.4-0.75,0.61-1.37,0.62c-0.61-0.01-1.06-0.21-1.35-0.62c-0.29-0.39-0.44-0.85-0.44-1.39
C116.6,7.2,116.75,6.74,117.04,6.35z M117.45,8.87c0.21,0.31,0.52,0.47,0.94,0.47c0.42-0.01,0.73-0.16,0.95-0.47
c0.21-0.29,0.31-0.67,0.31-1.13c0-0.46-0.1-0.84-0.31-1.14c-0.22-0.3-0.54-0.45-0.95-0.46c-0.42,0-0.73,0.16-0.94,0.46
c-0.22,0.31-0.33,0.69-0.33,1.14C117.12,8.2,117.23,8.58,117.45,8.87z"/>
<path class="st0" d="M122.36,9.35c0.23,0,0.44-0.06,0.6-0.19c0.16-0.11,0.24-0.28,0.25-0.5c0-0.23-0.1-0.4-0.28-0.53
c-0.18-0.12-0.39-0.24-0.64-0.34c-0.11-0.05-0.23-0.11-0.33-0.17s-0.21-0.13-0.3-0.2c-0.08-0.08-0.15-0.17-0.21-0.28
c-0.06-0.1-0.08-0.22-0.08-0.35c0-0.38,0.14-0.65,0.39-0.81c0.25-0.15,0.53-0.23,0.86-0.23c0.11,0,0.25,0.01,0.43,0.03
c0.18,0.03,0.35,0.08,0.52,0.14l-0.04,0.41c-0.27-0.12-0.56-0.18-0.85-0.18c-0.25,0-0.44,0.04-0.59,0.14
c-0.08,0.04-0.13,0.11-0.17,0.19c-0.05,0.08-0.07,0.19-0.07,0.31c0,0.08,0.03,0.16,0.08,0.23c0.06,0.06,0.13,0.12,0.22,0.18
c0.09,0.05,0.19,0.1,0.31,0.16c0.12,0.05,0.23,0.1,0.34,0.17c0.25,0.1,0.47,0.25,0.65,0.42c0.18,0.18,0.28,0.42,0.29,0.73
c0,0.2-0.04,0.37-0.12,0.5c-0.09,0.14-0.19,0.25-0.32,0.34c-0.28,0.17-0.58,0.25-0.89,0.25c-0.37,0-0.73-0.06-1.07-0.18l0.04-0.47
C121.65,9.26,121.98,9.34,122.36,9.35z"/>
<path class="st0" d="M125.86,9.35c0.23,0,0.44-0.06,0.6-0.19c0.16-0.11,0.24-0.28,0.25-0.5c0-0.23-0.1-0.4-0.28-0.53
c-0.18-0.12-0.39-0.24-0.64-0.34c-0.11-0.05-0.23-0.11-0.33-0.17s-0.21-0.13-0.3-0.2c-0.08-0.08-0.15-0.17-0.21-0.28
c-0.06-0.1-0.08-0.22-0.08-0.35c0-0.38,0.14-0.65,0.39-0.81c0.25-0.15,0.53-0.23,0.86-0.23c0.11,0,0.25,0.01,0.43,0.03
c0.18,0.03,0.35,0.08,0.52,0.14l-0.04,0.41c-0.27-0.12-0.56-0.18-0.85-0.18c-0.25,0-0.44,0.04-0.59,0.14
c-0.08,0.04-0.13,0.11-0.17,0.19c-0.05,0.08-0.07,0.19-0.07,0.31c0,0.08,0.03,0.16,0.08,0.23c0.06,0.06,0.13,0.12,0.22,0.18
c0.09,0.05,0.19,0.1,0.31,0.16c0.12,0.05,0.23,0.1,0.34,0.17c0.25,0.1,0.47,0.25,0.65,0.42c0.18,0.18,0.28,0.42,0.29,0.73
c0,0.2-0.04,0.37-0.12,0.5c-0.09,0.14-0.19,0.25-0.32,0.34c-0.28,0.17-0.58,0.25-0.89,0.25c-0.37,0-0.73-0.06-1.07-0.18l0.04-0.47
C125.15,9.26,125.48,9.34,125.86,9.35z"/>
<path class="st0" d="M131.22,9.54c-0.36,0.14-0.73,0.21-1.11,0.21c-0.62-0.01-1.07-0.2-1.34-0.58c-0.27-0.37-0.4-0.84-0.4-1.43
c0-0.6,0.16-1.08,0.45-1.45c0.29-0.36,0.67-0.55,1.16-0.56c0.54,0.01,0.93,0.2,1.17,0.56c0.24,0.36,0.35,0.81,0.35,1.34v0.24
h-2.62c0,0.42,0.11,0.76,0.32,1.03c0.21,0.29,0.52,0.43,0.94,0.44c0.19,0,0.38-0.03,0.59-0.1c0.21-0.06,0.37-0.12,0.49-0.19V9.54z
M130.99,7.48c0-0.35-0.07-0.66-0.22-0.92c-0.16-0.27-0.41-0.4-0.76-0.41c-0.35,0.01-0.62,0.15-0.82,0.42
c-0.21,0.27-0.31,0.57-0.31,0.91H130.99z"/>
<path class="st0" d="M133,6.74c0-0.3-0.01-0.6-0.03-0.9h0.46v0.68h0.02c0.04-0.09,0.1-0.18,0.16-0.27
c0.06-0.09,0.14-0.17,0.23-0.25c0.1-0.08,0.21-0.14,0.35-0.19c0.14-0.04,0.31-0.07,0.5-0.07c0.47,0,0.81,0.15,1.01,0.42
c0.2,0.27,0.29,0.62,0.29,1.05v2.45h-0.47V7.34c0-0.37-0.07-0.66-0.2-0.87c-0.15-0.21-0.38-0.31-0.71-0.31
c-0.42,0.01-0.72,0.16-0.89,0.45c-0.09,0.14-0.15,0.28-0.19,0.44c-0.04,0.17-0.06,0.33-0.06,0.5v2.12H133V6.74z"/>
<path class="st0" d="M138.46,9.35c0.23,0,0.44-0.06,0.6-0.19c0.16-0.11,0.24-0.28,0.25-0.5c0-0.23-0.1-0.4-0.28-0.53
c-0.18-0.12-0.39-0.24-0.64-0.34c-0.11-0.05-0.23-0.11-0.33-0.17s-0.21-0.13-0.3-0.2c-0.08-0.08-0.15-0.17-0.21-0.28
c-0.06-0.1-0.08-0.22-0.08-0.35c0-0.38,0.14-0.65,0.39-0.81c0.25-0.15,0.53-0.23,0.86-0.23c0.11,0,0.25,0.01,0.43,0.03
c0.18,0.03,0.35,0.08,0.52,0.14l-0.04,0.41c-0.27-0.12-0.56-0.18-0.85-0.18c-0.25,0-0.44,0.04-0.59,0.14
c-0.08,0.04-0.13,0.11-0.17,0.19c-0.05,0.08-0.07,0.19-0.07,0.31c0,0.08,0.03,0.16,0.08,0.23c0.06,0.06,0.13,0.12,0.22,0.18
c0.09,0.05,0.19,0.1,0.31,0.16c0.12,0.05,0.23,0.1,0.34,0.17c0.25,0.1,0.47,0.25,0.65,0.42c0.18,0.18,0.28,0.42,0.29,0.73
c0,0.2-0.04,0.37-0.12,0.5c-0.09,0.14-0.19,0.25-0.32,0.34c-0.28,0.17-0.58,0.25-0.89,0.25c-0.37,0-0.73-0.06-1.07-0.18l0.04-0.47
C137.75,9.26,138.08,9.34,138.46,9.35z"/>
<path class="st0" d="M143.75,6.34c-0.27-0.12-0.54-0.19-0.82-0.19c-0.46,0-0.81,0.15-1.06,0.44c-0.25,0.29-0.38,0.68-0.38,1.16
c0,0.45,0.12,0.82,0.38,1.12c0.25,0.31,0.58,0.48,1.01,0.48c0.3,0,0.58-0.06,0.86-0.17l0.04,0.44c-0.31,0.09-0.64,0.14-0.98,0.13
c-0.62-0.01-1.07-0.21-1.37-0.61c-0.31-0.39-0.46-0.85-0.46-1.4c0-0.6,0.18-1.09,0.53-1.45c0.34-0.36,0.8-0.55,1.37-0.56
c0.23,0,0.43,0.02,0.58,0.05c0.08,0.01,0.15,0.03,0.21,0.04c0.05,0.02,0.1,0.04,0.13,0.05L143.75,6.34z"/>
<path class="st0" d="M145.17,4.03h0.47v2.45h0.02c0.1-0.21,0.26-0.39,0.47-0.53c0.21-0.13,0.45-0.2,0.73-0.2
c0.51,0,0.86,0.16,1.04,0.45c0.18,0.3,0.27,0.69,0.26,1.18v2.29h-0.47V7.38c0-0.17-0.01-0.33-0.03-0.48
c-0.02-0.15-0.06-0.27-0.13-0.38c-0.12-0.24-0.37-0.36-0.75-0.37c-0.41,0.01-0.7,0.17-0.88,0.47c-0.18,0.31-0.26,0.64-0.26,0.99
v2.05h-0.47V4.03z"/>
<path class="st0" d="M152.13,9.05h-0.01c-0.11,0.23-0.28,0.4-0.5,0.52c-0.22,0.12-0.46,0.18-0.71,0.19
c-0.26,0-0.48-0.05-0.65-0.13c-0.17-0.08-0.3-0.17-0.39-0.29c-0.16-0.25-0.24-0.47-0.23-0.67c0-0.32,0.08-0.57,0.23-0.76
c0.15-0.19,0.34-0.32,0.58-0.4c0.24-0.08,0.49-0.14,0.77-0.17c0.27-0.02,0.53-0.02,0.78-0.02h0.09v-0.2
c0.02-0.66-0.27-0.98-0.87-0.98c-0.4,0-0.77,0.1-1.13,0.31V6.01c0.36-0.17,0.73-0.25,1.13-0.26c0.91-0.01,1.35,0.47,1.34,1.43
v1.69c0,0.29,0.01,0.56,0.04,0.8h-0.47V9.05z M152.08,7.73h-0.13c-0.21,0-0.41,0-0.62,0.01c-0.21,0.02-0.41,0.06-0.58,0.13
c-0.18,0.06-0.32,0.15-0.42,0.28c-0.12,0.12-0.17,0.29-0.17,0.5c0,0.25,0.08,0.42,0.24,0.52c0.15,0.11,0.34,0.17,0.56,0.17
c0.28,0,0.49-0.06,0.65-0.17c0.15-0.1,0.27-0.23,0.34-0.38c0.08-0.15,0.12-0.31,0.13-0.46c0.01-0.16,0.01-0.28,0.01-0.38V7.73z"/>
<path class="st0" d="M154.59,6.24h-0.77V5.83h0.77V5.56c0-0.21,0.01-0.41,0.03-0.6c0.01-0.19,0.05-0.36,0.11-0.51
c0.07-0.15,0.17-0.28,0.31-0.37c0.14-0.09,0.32-0.13,0.56-0.13c0.08,0,0.16,0,0.25,0.01c0.08,0.02,0.15,0.04,0.2,0.07l-0.03,0.4
c-0.1-0.05-0.23-0.07-0.38-0.07c-0.16,0-0.27,0.03-0.35,0.1c-0.08,0.08-0.14,0.18-0.17,0.29c-0.03,0.12-0.04,0.24-0.04,0.37
c0,0.14-0.01,0.27-0.01,0.39v0.33h0.86v0.41h-0.86v3.42h-0.47V6.24z"/>
<path class="st0" d="M159,6.24h-0.87v2.48c0,0.17,0.04,0.32,0.12,0.44c0.08,0.13,0.21,0.19,0.39,0.19c0.17,0,0.31-0.03,0.43-0.09
l0.03,0.39c-0.05,0.02-0.13,0.04-0.23,0.07c-0.11,0.02-0.22,0.04-0.35,0.04c-0.21,0-0.37-0.03-0.49-0.1
c-0.12-0.07-0.2-0.16-0.26-0.28c-0.05-0.12-0.08-0.26-0.1-0.41c-0.01-0.15-0.02-0.31-0.02-0.49V6.24h-0.75V5.83h0.75V4.91
l0.47-0.17v1.09H159V6.24z"/>
<path class="st0" d="M36.21,15.17c-0.21-0.02-0.42-0.04-0.61-0.04c-0.68,0.01-1.2,0.22-1.57,0.64c-0.37,0.42-0.56,0.96-0.56,1.62
c0,0.67,0.18,1.21,0.55,1.62c0.36,0.42,0.89,0.63,1.58,0.64c0.18,0,0.38-0.02,0.61-0.05c0.22-0.03,0.41-0.09,0.56-0.17l0.03,0.45
c-0.15,0.08-0.35,0.14-0.58,0.17c-0.24,0.03-0.44,0.05-0.62,0.05c-0.83-0.01-1.48-0.26-1.94-0.75c-0.47-0.49-0.71-1.14-0.71-1.96
c0-0.81,0.24-1.46,0.71-1.96c0.47-0.49,1.11-0.74,1.93-0.75c0.17,0,0.38,0.01,0.62,0.04c0.24,0.03,0.43,0.08,0.58,0.16l-0.03,0.48
C36.6,15.27,36.41,15.21,36.21,15.17z"/>
<path class="st0" d="M38.67,16.7c0.29-0.4,0.74-0.6,1.35-0.61c0.62,0.01,1.08,0.21,1.37,0.61c0.28,0.39,0.42,0.85,0.42,1.4
c0,0.54-0.14,1-0.42,1.39c-0.29,0.4-0.75,0.61-1.37,0.62c-0.61-0.01-1.06-0.21-1.35-0.62c-0.29-0.39-0.44-0.85-0.44-1.39
C38.23,17.55,38.38,17.09,38.67,16.7z M39.08,19.22c0.21,0.31,0.52,0.47,0.94,0.47c0.42-0.01,0.73-0.16,0.95-0.47
c0.21-0.29,0.31-0.67,0.31-1.13c0-0.46-0.1-0.84-0.31-1.14c-0.22-0.3-0.54-0.45-0.95-0.46c-0.42,0-0.73,0.16-0.94,0.46
c-0.22,0.31-0.33,0.69-0.33,1.14C38.75,18.55,38.86,18.93,39.08,19.22z"/>
<path class="st0" d="M43.28,17.09c0-0.3-0.01-0.6-0.03-0.9h0.46v0.68h0.02c0.04-0.09,0.1-0.18,0.16-0.27
c0.06-0.09,0.14-0.17,0.23-0.25c0.1-0.08,0.21-0.14,0.35-0.19c0.14-0.04,0.31-0.07,0.5-0.07c0.47,0,0.81,0.15,1.01,0.42
c0.2,0.27,0.29,0.62,0.29,1.05v2.45H45.8v-2.33c0-0.37-0.07-0.66-0.2-0.87c-0.15-0.21-0.38-0.31-0.71-0.31
c-0.42,0.01-0.72,0.16-0.89,0.45c-0.09,0.14-0.15,0.28-0.19,0.44c-0.04,0.17-0.06,0.33-0.06,0.5v2.12h-0.47V17.09z"/>
<path class="st0" d="M48.36,16.59h-0.77v-0.41h0.77v-0.28c0-0.21,0.01-0.41,0.03-0.6c0.01-0.19,0.05-0.36,0.11-0.51
c0.06-0.15,0.17-0.28,0.31-0.37c0.14-0.09,0.32-0.13,0.56-0.13c0.08,0,0.16,0,0.25,0.01c0.08,0.02,0.15,0.04,0.19,0.07l-0.03,0.4
c-0.1-0.05-0.23-0.07-0.38-0.07c-0.15,0-0.27,0.03-0.35,0.1c-0.08,0.08-0.14,0.18-0.17,0.29c-0.03,0.12-0.05,0.24-0.05,0.37
c0,0.14-0.01,0.27-0.01,0.39v0.33h0.86v0.41h-0.86v3.42h-0.47V16.59z"/>
<path class="st0" d="M53.69,19.89c-0.36,0.14-0.73,0.21-1.11,0.21c-0.62-0.01-1.07-0.2-1.34-0.58c-0.27-0.37-0.4-0.84-0.4-1.43
c0-0.6,0.16-1.08,0.45-1.45c0.29-0.36,0.67-0.55,1.16-0.56c0.54,0.01,0.93,0.2,1.17,0.56c0.24,0.36,0.35,0.81,0.35,1.34v0.24
h-2.62c0,0.42,0.11,0.76,0.32,1.03c0.21,0.29,0.52,0.43,0.94,0.44c0.19,0,0.38-0.03,0.59-0.1c0.21-0.06,0.37-0.12,0.49-0.19V19.89
z M53.46,17.83c0-0.35-0.07-0.66-0.22-0.92c-0.16-0.27-0.41-0.4-0.76-0.41c-0.35,0.01-0.62,0.15-0.82,0.42
c-0.21,0.27-0.31,0.57-0.31,0.91H53.46z M52.69,14.68h0.59l-0.95,1.08H52L52.69,14.68z"/>
<path class="st0" d="M58.49,20.01h-0.47V19.4h-0.01c-0.25,0.47-0.65,0.7-1.19,0.7c-0.53-0.01-0.92-0.2-1.18-0.58
c-0.27-0.38-0.4-0.85-0.4-1.43c0-0.6,0.12-1.08,0.37-1.44c0.25-0.37,0.65-0.56,1.21-0.56c0.19,0,0.36,0.03,0.5,0.08
c0.15,0.06,0.27,0.12,0.36,0.21c0.2,0.18,0.31,0.32,0.34,0.44h0.01v-2.45h0.47V20.01z M55.81,18.65c0.04,0.19,0.1,0.37,0.18,0.53
c0.09,0.15,0.21,0.27,0.35,0.37c0.14,0.1,0.32,0.16,0.55,0.16c0.21,0,0.39-0.05,0.54-0.16c0.14-0.1,0.26-0.22,0.35-0.38
c0.17-0.34,0.25-0.69,0.25-1.06c0-0.18-0.02-0.36-0.05-0.56c-0.04-0.18-0.1-0.35-0.2-0.51c-0.09-0.16-0.21-0.29-0.35-0.39
c-0.15-0.1-0.33-0.14-0.54-0.14c-0.23,0-0.41,0.05-0.55,0.14c-0.15,0.09-0.26,0.22-0.35,0.38c-0.08,0.15-0.14,0.32-0.18,0.51
c-0.04,0.19-0.05,0.38-0.05,0.57C55.75,18.28,55.77,18.46,55.81,18.65z"/>
<path class="st0" d="M62.79,19.89c-0.36,0.14-0.73,0.21-1.11,0.21c-0.62-0.01-1.07-0.2-1.34-0.58c-0.27-0.37-0.4-0.84-0.4-1.43
c0-0.6,0.16-1.08,0.45-1.45c0.29-0.36,0.67-0.55,1.16-0.56c0.54,0.01,0.93,0.2,1.17,0.56c0.24,0.36,0.35,0.81,0.35,1.34v0.24
h-2.62c0,0.42,0.11,0.76,0.32,1.03c0.21,0.29,0.52,0.43,0.94,0.44c0.19,0,0.38-0.03,0.59-0.1c0.21-0.06,0.37-0.12,0.49-0.19V19.89
z M62.55,17.83c0-0.35-0.07-0.66-0.22-0.92c-0.16-0.27-0.41-0.4-0.76-0.41c-0.35,0.01-0.62,0.15-0.82,0.42
c-0.21,0.27-0.31,0.57-0.31,0.91H62.55z M61.79,14.68h0.59l-0.95,1.08h-0.35L61.79,14.68z"/>
<path class="st0" d="M64.56,17.04c0-0.35-0.01-0.64-0.03-0.86H65v0.74h0.01c0.09-0.23,0.22-0.42,0.38-0.58
c0.16-0.16,0.36-0.24,0.6-0.25c0.12,0,0.23,0.02,0.33,0.04v0.5c-0.1-0.03-0.22-0.05-0.35-0.05c-0.19,0.01-0.34,0.05-0.46,0.14
c-0.12,0.09-0.22,0.21-0.29,0.37c-0.07,0.15-0.12,0.31-0.15,0.49c-0.04,0.19-0.05,0.37-0.05,0.54v1.89h-0.47V17.04z"/>
<path class="st0" d="M69.84,19.4h-0.02c-0.11,0.23-0.28,0.4-0.5,0.52s-0.46,0.18-0.71,0.19c-0.27,0-0.48-0.05-0.65-0.13
c-0.17-0.08-0.3-0.17-0.39-0.29c-0.16-0.25-0.24-0.47-0.23-0.67c0-0.32,0.08-0.57,0.23-0.76c0.15-0.19,0.34-0.32,0.58-0.4
c0.24-0.08,0.49-0.14,0.77-0.17c0.27-0.02,0.53-0.02,0.78-0.02h0.09v-0.2c0.01-0.66-0.28-0.98-0.87-0.98
c-0.4,0-0.77,0.1-1.13,0.31v-0.46c0.35-0.17,0.73-0.25,1.13-0.26c0.91-0.01,1.35,0.47,1.34,1.43v1.69
c-0.01,0.29,0.01,0.56,0.04,0.8h-0.47V19.4z M69.79,18.08h-0.14c-0.21,0-0.41,0-0.62,0.01c-0.22,0.02-0.41,0.06-0.59,0.13
c-0.18,0.06-0.31,0.15-0.42,0.28c-0.12,0.12-0.17,0.29-0.17,0.5c0,0.25,0.08,0.42,0.24,0.52c0.15,0.11,0.33,0.17,0.56,0.17
c0.27,0,0.49-0.06,0.65-0.17c0.15-0.1,0.27-0.23,0.34-0.38c0.07-0.15,0.12-0.31,0.13-0.46c0.01-0.16,0.02-0.28,0.02-0.38V18.08z"
/>
<path class="st0" d="M73.63,16.59h-0.87v2.48c0,0.17,0.04,0.32,0.12,0.44c0.08,0.13,0.21,0.19,0.39,0.19
c0.17,0,0.32-0.03,0.44-0.09L73.73,20c-0.06,0.02-0.13,0.04-0.23,0.07c-0.1,0.02-0.22,0.04-0.35,0.04c-0.21,0-0.37-0.03-0.49-0.1
c-0.12-0.07-0.2-0.16-0.26-0.28c-0.05-0.12-0.08-0.26-0.1-0.41c-0.02-0.15-0.02-0.31-0.02-0.49v-2.24h-0.75v-0.41h0.75v-0.92
l0.47-0.17v1.09h0.87V16.59z"/>
<path class="st0" d="M75.53,15.16h-0.47v-0.63h0.47V15.16z M75.06,16.18h0.47v3.83h-0.47V16.18z"/>
<path class="st0" d="M77.45,16.7c0.29-0.4,0.74-0.6,1.35-0.61c0.62,0.01,1.08,0.21,1.37,0.61c0.28,0.39,0.42,0.85,0.42,1.4
c0,0.54-0.14,1-0.42,1.39c-0.29,0.4-0.75,0.61-1.37,0.62c-0.61-0.01-1.06-0.21-1.35-0.62c-0.29-0.39-0.44-0.85-0.44-1.39
C77.02,17.55,77.16,17.09,77.45,16.7z M77.87,19.22c0.21,0.31,0.52,0.47,0.94,0.47c0.42-0.01,0.73-0.16,0.95-0.47
c0.21-0.29,0.31-0.67,0.31-1.13c0-0.46-0.1-0.84-0.31-1.14c-0.22-0.3-0.54-0.45-0.95-0.46c-0.42,0-0.73,0.16-0.94,0.46
c-0.22,0.31-0.33,0.69-0.33,1.14C77.54,18.55,77.65,18.93,77.87,19.22z"/>
<path class="st0" d="M82.07,17.09c0-0.3-0.01-0.6-0.03-0.9h0.46v0.68h0.02c0.04-0.09,0.1-0.18,0.16-0.27
c0.06-0.09,0.14-0.17,0.23-0.25c0.1-0.08,0.21-0.14,0.35-0.19c0.14-0.04,0.31-0.07,0.5-0.07c0.47,0,0.81,0.15,1.01,0.42
c0.2,0.27,0.29,0.62,0.29,1.05v2.45h-0.47v-2.33c0-0.37-0.07-0.66-0.2-0.87c-0.15-0.21-0.38-0.31-0.71-0.31
c-0.42,0.01-0.72,0.16-0.89,0.45c-0.09,0.14-0.15,0.28-0.19,0.44c-0.04,0.17-0.06,0.33-0.06,0.5v2.12h-0.47V17.09z"/>
<path class="st0" d="M90.13,19.7c0.23,0,0.44-0.06,0.6-0.19c0.16-0.11,0.24-0.28,0.25-0.5c0-0.23-0.1-0.4-0.28-0.53
c-0.18-0.12-0.39-0.24-0.64-0.34c-0.11-0.05-0.23-0.11-0.33-0.17s-0.21-0.13-0.3-0.2c-0.08-0.08-0.15-0.17-0.21-0.28
c-0.06-0.1-0.08-0.22-0.08-0.35c0-0.38,0.14-0.65,0.39-0.81c0.25-0.15,0.53-0.23,0.86-0.23c0.11,0,0.25,0.01,0.43,0.03
c0.18,0.03,0.35,0.08,0.52,0.14l-0.04,0.41c-0.27-0.12-0.56-0.18-0.85-0.18c-0.25,0-0.44,0.04-0.59,0.14
c-0.08,0.04-0.13,0.11-0.17,0.19c-0.05,0.08-0.07,0.19-0.07,0.31c0,0.08,0.03,0.16,0.08,0.23c0.06,0.06,0.13,0.12,0.22,0.18
c0.09,0.05,0.19,0.1,0.31,0.16c0.12,0.05,0.23,0.1,0.34,0.17c0.25,0.1,0.47,0.25,0.65,0.42c0.18,0.18,0.28,0.42,0.29,0.73
c0,0.2-0.04,0.37-0.12,0.5c-0.09,0.14-0.19,0.25-0.32,0.34c-0.28,0.17-0.58,0.25-0.89,0.25c-0.37,0-0.73-0.06-1.07-0.18l0.04-0.47
C89.42,19.61,89.75,19.69,90.13,19.7z"/>
<path class="st0" d="M95.91,19.11c0,0.29,0,0.59,0.03,0.9h-0.46v-0.68h-0.01c-0.04,0.08-0.08,0.17-0.14,0.25
c-0.06,0.09-0.14,0.18-0.23,0.26c-0.09,0.07-0.21,0.13-0.35,0.18c-0.14,0.06-0.31,0.08-0.51,0.08c-0.47,0-0.8-0.15-1.01-0.42
c-0.21-0.27-0.31-0.62-0.3-1.05v-2.45h0.47v2.33c0,0.37,0.07,0.65,0.22,0.86c0.14,0.22,0.37,0.33,0.69,0.33
c0.21,0,0.4-0.05,0.55-0.14c0.15-0.08,0.26-0.2,0.34-0.33c0.09-0.14,0.15-0.29,0.19-0.45c0.03-0.16,0.05-0.32,0.05-0.48v-2.12
h0.47V19.11z"/>
<path class="st0" d="M98.16,15.16h-0.47v-0.63h0.47V15.16z M97.68,16.18h0.47v3.83h-0.47V16.18z"/>
<path class="st0" d="M100.64,19.7c0.23,0,0.44-0.06,0.6-0.19c0.16-0.11,0.24-0.28,0.25-0.5c0-0.23-0.1-0.4-0.28-0.53
c-0.18-0.12-0.39-0.24-0.64-0.34c-0.11-0.05-0.23-0.11-0.33-0.17s-0.21-0.13-0.3-0.2c-0.08-0.08-0.15-0.17-0.21-0.28
c-0.06-0.1-0.08-0.22-0.08-0.35c0-0.38,0.14-0.65,0.39-0.81c0.25-0.15,0.53-0.23,0.86-0.23c0.11,0,0.25,0.01,0.43,0.03
c0.18,0.03,0.35,0.08,0.52,0.14l-0.04,0.41c-0.27-0.12-0.56-0.18-0.85-0.18c-0.25,0-0.44,0.04-0.59,0.14
c-0.08,0.04-0.13,0.11-0.17,0.19c-0.05,0.08-0.07,0.19-0.07,0.31c0,0.08,0.03,0.16,0.08,0.23c0.06,0.06,0.13,0.12,0.22,0.18
c0.09,0.05,0.19,0.1,0.31,0.16c0.12,0.05,0.23,0.1,0.34,0.17c0.25,0.1,0.47,0.25,0.65,0.42c0.18,0.18,0.28,0.42,0.29,0.73
c0,0.2-0.04,0.37-0.12,0.5c-0.09,0.14-0.19,0.25-0.32,0.34c-0.28,0.17-0.58,0.25-0.89,0.25c-0.37,0-0.73-0.06-1.07-0.18l0.04-0.47
C99.93,19.61,100.26,19.69,100.64,19.7z"/>
<path class="st0" d="M104.14,19.7c0.23,0,0.44-0.06,0.6-0.19c0.16-0.11,0.24-0.28,0.25-0.5c0-0.23-0.1-0.4-0.28-0.53
c-0.18-0.12-0.39-0.24-0.64-0.34c-0.11-0.05-0.23-0.11-0.33-0.17s-0.21-0.13-0.3-0.2c-0.08-0.08-0.15-0.17-0.21-0.28
c-0.06-0.1-0.08-0.22-0.08-0.35c0-0.38,0.14-0.65,0.39-0.81c0.25-0.15,0.53-0.23,0.86-0.23c0.11,0,0.25,0.01,0.43,0.03
c0.18,0.03,0.35,0.08,0.52,0.14l-0.04,0.41c-0.27-0.12-0.56-0.18-0.85-0.18c-0.25,0-0.44,0.04-0.59,0.14
c-0.08,0.04-0.13,0.11-0.17,0.19c-0.05,0.08-0.07,0.19-0.07,0.31c0,0.08,0.03,0.16,0.08,0.23c0.06,0.06,0.13,0.12,0.22,0.18
c0.09,0.05,0.19,0.1,0.31,0.16c0.12,0.05,0.23,0.1,0.34,0.17c0.25,0.1,0.47,0.25,0.65,0.42c0.18,0.18,0.28,0.42,0.29,0.73
c0,0.2-0.04,0.37-0.12,0.5c-0.09,0.14-0.19,0.25-0.32,0.34c-0.28,0.17-0.58,0.25-0.89,0.25c-0.37,0-0.73-0.06-1.07-0.18l0.04-0.47
C103.44,19.61,103.77,19.69,104.14,19.7z"/>
<path class="st0" d="M109.51,19.89c-0.36,0.14-0.73,0.21-1.11,0.21c-0.62-0.01-1.07-0.2-1.34-0.58c-0.27-0.37-0.4-0.84-0.4-1.43
c0-0.6,0.16-1.08,0.45-1.45c0.29-0.36,0.67-0.55,1.16-0.56c0.54,0.01,0.93,0.2,1.17,0.56c0.24,0.36,0.35,0.81,0.35,1.34v0.24
h-2.62c0,0.42,0.11,0.76,0.32,1.03c0.21,0.29,0.52,0.43,0.94,0.44c0.19,0,0.38-0.03,0.59-0.1c0.21-0.06,0.37-0.12,0.49-0.19V19.89
z M109.28,17.83c0-0.35-0.07-0.66-0.22-0.92c-0.16-0.27-0.41-0.4-0.76-0.41c-0.35,0.01-0.62,0.15-0.82,0.42
c-0.21,0.27-0.31,0.57-0.31,0.91H109.28z"/>
<path class="st0" d="M36.21,25.52c-0.21-0.02-0.42-0.04-0.61-0.04c-0.68,0.01-1.2,0.22-1.57,0.64c-0.37,0.42-0.56,0.96-0.56,1.62
c0,0.67,0.18,1.21,0.55,1.62C34.38,29.78,34.91,30,35.6,30c0.18,0,0.38-0.02,0.61-0.05c0.22-0.03,0.41-0.09,0.56-0.17l0.03,0.45
c-0.15,0.08-0.35,0.14-0.58,0.17c-0.24,0.03-0.44,0.05-0.62,0.05c-0.83-0.01-1.48-0.26-1.94-0.75c-0.47-0.49-0.71-1.14-0.71-1.96
c0-0.81,0.24-1.46,0.71-1.96c0.47-0.49,1.11-0.74,1.93-0.75c0.17,0,0.38,0.01,0.62,0.04c0.24,0.03,0.43,0.08,0.58,0.16l-0.03,0.48
C36.6,25.62,36.41,25.56,36.21,25.52z"/>
<path class="st0" d="M38.67,27.05c0.29-0.4,0.74-0.6,1.35-0.61c0.62,0.01,1.08,0.21,1.37,0.61c0.28,0.39,0.42,0.85,0.42,1.4
c0,0.54-0.14,1-0.42,1.39c-0.29,0.4-0.75,0.61-1.37,0.62c-0.61-0.01-1.06-0.21-1.35-0.62c-0.29-0.39-0.44-0.85-0.44-1.39
C38.23,27.9,38.38,27.44,38.67,27.05z M39.08,29.57c0.21,0.31,0.52,0.47,0.94,0.47c0.42-0.01,0.73-0.16,0.95-0.47
c0.21-0.29,0.31-0.67,0.31-1.13c0-0.46-0.1-0.84-0.31-1.14c-0.22-0.3-0.54-0.45-0.95-0.46c-0.42,0-0.73,0.16-0.94,0.46
c-0.22,0.31-0.33,0.69-0.33,1.14C38.75,28.9,38.86,29.28,39.08,29.57z"/>
<path class="st0" d="M43.28,27.44c0-0.3-0.01-0.6-0.03-0.9h0.46v0.68h0.02c0.04-0.09,0.1-0.18,0.16-0.27
c0.06-0.09,0.14-0.17,0.23-0.25c0.1-0.08,0.21-0.14,0.35-0.19c0.14-0.04,0.31-0.07,0.5-0.07c0.47,0,0.81,0.15,1.01,0.42
c0.2,0.27,0.29,0.62,0.29,1.05v2.45H45.8v-2.33c0-0.37-0.07-0.66-0.2-0.87c-0.15-0.21-0.38-0.31-0.71-0.31
c-0.42,0.01-0.72,0.16-0.89,0.45c-0.09,0.14-0.15,0.28-0.19,0.44c-0.04,0.17-0.06,0.33-0.06,0.5v2.12h-0.47V27.44z"/>
<path class="st0" d="M48.36,26.94h-0.77v-0.41h0.77v-0.28c0-0.21,0.01-0.41,0.03-0.6c0.01-0.19,0.05-0.36,0.11-0.51
c0.06-0.15,0.17-0.28,0.31-0.37c0.14-0.09,0.32-0.13,0.56-0.13c0.08,0,0.16,0,0.25,0.01c0.08,0.02,0.15,0.04,0.19,0.07l-0.03,0.4
c-0.1-0.05-0.23-0.07-0.38-0.07c-0.15,0-0.27,0.03-0.35,0.1c-0.08,0.08-0.14,0.18-0.17,0.29c-0.03,0.12-0.05,0.24-0.05,0.37
c0,0.14-0.01,0.27-0.01,0.39v0.33h0.86v0.41h-0.86v3.42h-0.47V26.94z"/>
<path class="st0" d="M53.69,30.24c-0.36,0.14-0.73,0.21-1.11,0.21c-0.62-0.01-1.07-0.2-1.34-0.58c-0.27-0.37-0.4-0.84-0.4-1.43
c0-0.6,0.16-1.08,0.45-1.45c0.29-0.36,0.67-0.55,1.16-0.56c0.54,0.01,0.93,0.2,1.17,0.56c0.24,0.36,0.35,0.81,0.35,1.34v0.24
h-2.62c0,0.42,0.11,0.76,0.32,1.03c0.21,0.29,0.52,0.43,0.94,0.44c0.19,0,0.38-0.03,0.59-0.1c0.21-0.06,0.37-0.12,0.49-0.19V30.24
z M53.46,28.18c0-0.35-0.07-0.66-0.22-0.92c-0.16-0.27-0.41-0.4-0.76-0.41c-0.35,0.01-0.62,0.15-0.82,0.42
c-0.21,0.27-0.31,0.57-0.31,0.91H53.46z"/>
<path class="st0" d="M58.49,30.36h-0.47v-0.61h-0.01c-0.25,0.47-0.65,0.7-1.19,0.7c-0.53-0.01-0.92-0.2-1.18-0.58
c-0.27-0.38-0.4-0.85-0.4-1.43c0-0.6,0.12-1.08,0.37-1.44c0.25-0.37,0.65-0.56,1.21-0.56c0.19,0,0.36,0.03,0.5,0.08
c0.15,0.06,0.27,0.12,0.36,0.21c0.2,0.18,0.31,0.32,0.34,0.44h0.01v-2.45h0.47V30.36z M55.81,29c0.04,0.19,0.1,0.37,0.18,0.53
c0.09,0.15,0.21,0.27,0.35,0.37c0.14,0.1,0.32,0.16,0.55,0.16c0.21,0,0.39-0.05,0.54-0.16c0.14-0.1,0.26-0.22,0.35-0.38
c0.17-0.34,0.25-0.69,0.25-1.06c0-0.18-0.02-0.36-0.05-0.56c-0.04-0.18-0.1-0.35-0.2-0.51c-0.09-0.16-0.21-0.29-0.35-0.39
c-0.15-0.1-0.33-0.14-0.54-0.14c-0.23,0-0.41,0.05-0.55,0.14c-0.15,0.09-0.26,0.22-0.35,0.38c-0.08,0.15-0.14,0.32-0.18,0.51
c-0.04,0.19-0.05,0.38-0.05,0.57C55.75,28.63,55.77,28.81,55.81,29z"/>
<path class="st0" d="M62.79,30.24c-0.36,0.14-0.73,0.21-1.11,0.21c-0.62-0.01-1.07-0.2-1.34-0.58c-0.27-0.37-0.4-0.84-0.4-1.43
c0-0.6,0.16-1.08,0.45-1.45c0.29-0.36,0.67-0.55,1.16-0.56c0.54,0.01,0.93,0.2,1.17,0.56c0.24,0.36,0.35,0.81,0.35,1.34v0.24
h-2.62c0,0.42,0.11,0.76,0.32,1.03c0.21,0.29,0.52,0.43,0.94,0.44c0.19,0,0.38-0.03,0.59-0.1c0.21-0.06,0.37-0.12,0.49-0.19V30.24
z M62.55,28.18c0-0.35-0.07-0.66-0.22-0.92c-0.16-0.27-0.41-0.4-0.76-0.41c-0.35,0.01-0.62,0.15-0.82,0.42
c-0.21,0.27-0.31,0.57-0.31,0.91H62.55z"/>
<path class="st0" d="M64.56,27.39c0-0.35-0.01-0.64-0.03-0.86H65v0.74h0.01c0.09-0.23,0.22-0.42,0.38-0.58
c0.16-0.16,0.36-0.24,0.6-0.25c0.12,0,0.23,0.02,0.33,0.04v0.5c-0.1-0.03-0.22-0.05-0.35-0.05c-0.19,0.01-0.34,0.05-0.46,0.14
c-0.12,0.09-0.22,0.21-0.29,0.37c-0.07,0.15-0.12,0.31-0.15,0.49c-0.04,0.19-0.05,0.37-0.05,0.54v1.89h-0.47V27.39z"/>
<path class="st0" d="M69.84,29.75h-0.02c-0.11,0.23-0.28,0.4-0.5,0.52s-0.46,0.18-0.71,0.19c-0.27,0-0.48-0.05-0.65-0.13
c-0.17-0.08-0.3-0.17-0.39-0.29c-0.16-0.25-0.24-0.47-0.23-0.67c0-0.32,0.08-0.57,0.23-0.76c0.15-0.19,0.34-0.32,0.58-0.4
c0.24-0.08,0.49-0.14,0.77-0.17c0.27-0.02,0.53-0.02,0.78-0.02h0.09v-0.2c0.01-0.66-0.28-0.98-0.87-0.98
c-0.4,0-0.77,0.1-1.13,0.31v-0.46c0.35-0.17,0.73-0.25,1.13-0.26c0.91-0.01,1.35,0.47,1.34,1.43v1.69
c-0.01,0.29,0.01,0.56,0.04,0.8h-0.47V29.75z M69.79,28.43h-0.14c-0.21,0-0.41,0-0.62,0.01c-0.22,0.02-0.41,0.06-0.59,0.13
c-0.18,0.06-0.31,0.15-0.42,0.28c-0.12,0.12-0.17,0.29-0.17,0.5c0,0.25,0.08,0.42,0.24,0.52c0.15,0.11,0.33,0.17,0.56,0.17
c0.27,0,0.49-0.06,0.65-0.17c0.15-0.1,0.27-0.23,0.34-0.38c0.07-0.15,0.12-0.31,0.13-0.46c0.01-0.16,0.02-0.28,0.02-0.38V28.43z"
/>
<path class="st0" d="M71.62,29.96l2.3-3.02h-2.21v-0.41h2.76v0.41l-2.3,3.02h2.3v0.41h-2.85V29.96z"/>
<path class="st0" d="M76.37,25.51h-0.47v-0.63h0.47V25.51z M75.89,26.53h0.47v3.83h-0.47V26.53z"/>
<path class="st0" d="M78.29,27.05c0.29-0.4,0.74-0.6,1.35-0.61c0.62,0.01,1.08,0.21,1.37,0.61c0.28,0.39,0.42,0.85,0.42,1.4
c0,0.54-0.14,1-0.42,1.39c-0.29,0.4-0.75,0.61-1.37,0.62c-0.61-0.01-1.06-0.21-1.35-0.62c-0.29-0.39-0.44-0.85-0.44-1.39
C77.85,27.9,78,27.44,78.29,27.05z M78.7,29.57c0.21,0.31,0.52,0.47,0.94,0.47c0.42-0.01,0.73-0.16,0.95-0.47
c0.21-0.29,0.31-0.67,0.31-1.13c0-0.46-0.1-0.84-0.31-1.14c-0.22-0.3-0.54-0.45-0.95-0.46c-0.42,0-0.73,0.16-0.94,0.46
c-0.22,0.31-0.33,0.69-0.33,1.14C78.37,28.9,78.48,29.28,78.7,29.57z"/>
<path class="st0" d="M82.9,27.44c0-0.3-0.01-0.6-0.03-0.9h0.46v0.68h0.02c0.04-0.09,0.1-0.18,0.16-0.27
c0.06-0.09,0.14-0.17,0.23-0.25c0.1-0.08,0.21-0.14,0.35-0.19c0.14-0.04,0.31-0.07,0.5-0.07c0.47,0,0.81,0.15,1.01,0.42
c0.2,0.27,0.29,0.62,0.29,1.05v2.45h-0.47v-2.33c0-0.37-0.07-0.66-0.2-0.87c-0.15-0.21-0.38-0.31-0.71-0.31
c-0.42,0.01-0.72,0.16-0.89,0.45c-0.09,0.14-0.15,0.28-0.19,0.44c-0.04,0.17-0.06,0.33-0.06,0.5v2.12H82.9V27.44z"/>
<path class="st0" d="M90.23,30.24c-0.36,0.14-0.73,0.21-1.11,0.21c-0.62-0.01-1.07-0.2-1.34-0.58c-0.27-0.37-0.4-0.84-0.4-1.43
c0-0.6,0.16-1.08,0.45-1.45c0.29-0.36,0.67-0.55,1.16-0.56c0.54,0.01,0.93,0.2,1.17,0.56c0.24,0.36,0.35,0.81,0.35,1.34v0.24
h-2.62c0,0.42,0.11,0.76,0.32,1.03c0.21,0.29,0.52,0.43,0.94,0.44c0.19,0,0.38-0.03,0.59-0.1c0.21-0.06,0.37-0.12,0.49-0.19V30.24
z M89.99,28.18c0-0.35-0.07-0.66-0.22-0.92c-0.16-0.27-0.41-0.4-0.76-0.41c-0.35,0.01-0.62,0.15-0.82,0.42
c-0.21,0.27-0.31,0.57-0.31,0.91H89.99z"/>
<path class="st0" d="M95.66,30c0.34,0,0.62-0.09,0.84-0.26c0.22-0.17,0.33-0.44,0.34-0.81c0-0.26-0.13-0.47-0.38-0.65
c-0.11-0.09-0.24-0.17-0.38-0.25s-0.29-0.16-0.44-0.24c-0.15-0.08-0.29-0.18-0.44-0.27c-0.15-0.09-0.28-0.19-0.39-0.3
c-0.25-0.21-0.37-0.49-0.38-0.82c0-0.43,0.17-0.76,0.49-1.01c0.31-0.24,0.7-0.36,1.17-0.36c0.13,0,0.29,0.01,0.48,0.03
c0.19,0.03,0.37,0.08,0.55,0.14l-0.08,0.47c-0.26-0.13-0.57-0.2-0.95-0.2c-0.13,0-0.27,0.01-0.4,0.04
c-0.13,0.03-0.25,0.08-0.37,0.16c-0.1,0.06-0.19,0.15-0.26,0.27c-0.07,0.12-0.1,0.27-0.1,0.44c0,0.12,0.03,0.24,0.1,0.35
c0.07,0.1,0.16,0.2,0.27,0.28c0.12,0.08,0.25,0.17,0.39,0.24c0.14,0.08,0.29,0.16,0.44,0.25c0.31,0.15,0.59,0.34,0.83,0.57
c0.12,0.11,0.21,0.24,0.28,0.39c0.06,0.15,0.1,0.32,0.1,0.52c-0.01,0.52-0.19,0.9-0.55,1.13c-0.35,0.23-0.74,0.35-1.17,0.35
c-0.51-0.01-0.91-0.08-1.19-0.23l0.05-0.53C94.8,29.9,95.18,30,95.66,30z"/>
<path class="st0" d="M100.26,30.36H99.7l-1.3-3.83h0.52l1.06,3.31H100l1.11-3.31h0.5L100.26,30.36z"/>
<path class="st0" d="M103.32,25.51h-0.47v-0.63h0.47V25.51z M102.85,26.53h0.47v3.83h-0.47V26.53z"/>
<path class="st0" d="M104.74,29.96l2.3-3.02h-2.21v-0.41h2.76v0.41l-2.3,3.02h2.3v0.41h-2.85V29.96z"/>
<path class="st0" d="M108.66,29.96l2.3-3.02h-2.21v-0.41h2.76v0.41l-2.3,3.02h2.3v0.41h-2.85V29.96z"/>
<path class="st0" d="M115.5,30.24c-0.36,0.14-0.73,0.21-1.11,0.21c-0.62-0.01-1.07-0.2-1.34-0.58c-0.27-0.37-0.4-0.84-0.4-1.43
c0-0.6,0.16-1.08,0.45-1.45c0.29-0.36,0.67-0.55,1.16-0.56c0.54,0.01,0.93,0.2,1.17,0.56c0.24,0.36,0.35,0.81,0.35,1.34v0.24
h-2.62c0,0.42,0.11,0.76,0.32,1.03c0.21,0.29,0.52,0.43,0.94,0.44c0.19,0,0.38-0.03,0.59-0.1c0.21-0.06,0.37-0.12,0.49-0.19V30.24
z M115.26,28.18c0-0.35-0.07-0.66-0.22-0.92c-0.16-0.27-0.41-0.4-0.76-0.41c-0.35,0.01-0.62,0.15-0.82,0.42
c-0.21,0.27-0.31,0.57-0.31,0.91H115.26z"/>
<path class="st0" d="M117.27,27.39c0-0.35-0.01-0.64-0.03-0.86h0.47v0.74h0.01c0.09-0.23,0.22-0.42,0.38-0.58
c0.16-0.16,0.36-0.24,0.6-0.25c0.12,0,0.23,0.02,0.33,0.04v0.5c-0.1-0.03-0.22-0.05-0.35-0.05c-0.19,0.01-0.34,0.05-0.46,0.14
c-0.12,0.09-0.22,0.21-0.29,0.37c-0.07,0.15-0.12,0.31-0.15,0.49c-0.04,0.19-0.05,0.37-0.05,0.54v1.89h-0.47V27.39z"/>
<path class="st0" d="M122.55,29.75h-0.02c-0.11,0.23-0.28,0.4-0.5,0.52s-0.46,0.18-0.71,0.19c-0.27,0-0.48-0.05-0.65-0.13
c-0.17-0.08-0.3-0.17-0.39-0.29c-0.16-0.25-0.24-0.47-0.23-0.67c0-0.32,0.08-0.57,0.23-0.76c0.15-0.19,0.34-0.32,0.58-0.4
c0.24-0.08,0.49-0.14,0.77-0.17c0.27-0.02,0.53-0.02,0.78-0.02h0.09v-0.2c0.01-0.66-0.28-0.98-0.87-0.98
c-0.4,0-0.77,0.1-1.13,0.31v-0.46c0.35-0.17,0.73-0.25,1.13-0.26c0.91-0.01,1.35,0.47,1.34,1.43v1.69
c-0.01,0.29,0.01,0.56,0.04,0.8h-0.47V29.75z M122.51,28.43h-0.14c-0.21,0-0.41,0-0.62,0.01c-0.22,0.02-0.41,0.06-0.59,0.13
c-0.18,0.06-0.31,0.15-0.42,0.28c-0.12,0.12-0.17,0.29-0.17,0.5c0,0.25,0.08,0.42,0.24,0.52c0.15,0.11,0.33,0.17,0.56,0.17
c0.27,0,0.49-0.06,0.65-0.17c0.15-0.1,0.27-0.23,0.34-0.38c0.07-0.15,0.12-0.31,0.13-0.46c0.01-0.16,0.02-0.28,0.02-0.38V28.43z"
/>
<path class="st0" d="M36.21,35.87c-0.21-0.02-0.42-0.04-0.61-0.04c-0.68,0.01-1.2,0.22-1.57,0.64c-0.37,0.42-0.56,0.96-0.56,1.62
c0,0.67,0.18,1.21,0.55,1.62c0.36,0.42,0.89,0.63,1.58,0.64c0.18,0,0.38-0.02,0.61-0.05c0.22-0.03,0.41-0.09,0.56-0.17l0.03,0.45
c-0.15,0.08-0.35,0.14-0.58,0.17c-0.24,0.03-0.44,0.05-0.62,0.05c-0.83-0.01-1.48-0.26-1.94-0.75c-0.47-0.49-0.71-1.14-0.71-1.96
c0-0.81,0.24-1.46,0.71-1.96c0.47-0.49,1.11-0.74,1.93-0.75c0.17,0,0.38,0.01,0.62,0.04c0.24,0.03,0.43,0.08,0.58,0.16l-0.03,0.48
C36.6,35.97,36.41,35.91,36.21,35.87z"/>
<path class="st0" d="M38.67,37.4c0.29-0.4,0.74-0.6,1.35-0.61c0.62,0.01,1.08,0.21,1.37,0.61c0.28,0.39,0.42,0.85,0.42,1.4
c0,0.54-0.14,1-0.42,1.39c-0.29,0.4-0.75,0.61-1.37,0.62c-0.61-0.01-1.06-0.21-1.35-0.62c-0.29-0.39-0.44-0.85-0.44-1.39
C38.23,38.25,38.38,37.79,38.67,37.4z M39.08,39.92c0.21,0.31,0.52,0.47,0.94,0.47c0.42-0.01,0.73-0.16,0.95-0.47
c0.21-0.29,0.31-0.67,0.31-1.13c0-0.46-0.1-0.84-0.31-1.14c-0.22-0.3-0.54-0.45-0.95-0.46c-0.42,0-0.73,0.16-0.94,0.46
c-0.22,0.31-0.33,0.69-0.33,1.14C38.75,39.25,38.86,39.63,39.08,39.92z"/>
<path class="st0" d="M43.28,37.78c0-0.3-0.01-0.6-0.03-0.9h0.46v0.68h0.02c0.04-0.09,0.1-0.18,0.16-0.27
c0.06-0.09,0.14-0.17,0.23-0.25c0.1-0.08,0.21-0.14,0.35-0.19c0.14-0.04,0.31-0.07,0.5-0.07c0.47,0,0.81,0.15,1.01,0.42
c0.2,0.27,0.29,0.62,0.29,1.05v2.45H45.8v-2.33c0-0.37-0.07-0.66-0.2-0.87c-0.15-0.21-0.38-0.31-0.71-0.31
c-0.42,0.01-0.72,0.16-0.89,0.45c-0.09,0.14-0.15,0.28-0.19,0.44c-0.04,0.17-0.06,0.33-0.06,0.5v2.12h-0.47V37.78z"/>
<path class="st0" d="M48.36,37.29h-0.77v-0.41h0.77v-0.28c0-0.21,0.01-0.41,0.03-0.6c0.01-0.19,0.05-0.36,0.11-0.51
c0.06-0.15,0.17-0.28,0.31-0.37c0.14-0.09,0.32-0.13,0.56-0.13c0.08,0,0.16,0,0.25,0.01c0.08,0.02,0.15,0.04,0.19,0.07l-0.03,0.4
c-0.1-0.05-0.23-0.07-0.38-0.07c-0.15,0-0.27,0.03-0.35,0.1c-0.08,0.08-0.14,0.18-0.17,0.29c-0.03,0.12-0.05,0.24-0.05,0.37
c0,0.14-0.01,0.27-0.01,0.39v0.33h0.86v0.41h-0.86v3.42h-0.47V37.29z"/>
<path class="st0" d="M53.69,40.59c-0.36,0.14-0.73,0.21-1.11,0.21c-0.62-0.01-1.07-0.2-1.34-0.58c-0.27-0.37-0.4-0.84-0.4-1.43
c0-0.6,0.16-1.08,0.45-1.45c0.29-0.36,0.67-0.55,1.16-0.56c0.54,0.01,0.93,0.2,1.17,0.56c0.24,0.36,0.35,0.81,0.35,1.34v0.24
h-2.62c0,0.42,0.11,0.76,0.32,1.03c0.21,0.29,0.52,0.43,0.94,0.44c0.19,0,0.38-0.03,0.59-0.1c0.21-0.06,0.37-0.12,0.49-0.19V40.59
z M53.46,38.53c0-0.35-0.07-0.66-0.22-0.92c-0.16-0.27-0.41-0.4-0.76-0.41c-0.35,0.01-0.62,0.15-0.82,0.42
c-0.21,0.27-0.31,0.57-0.31,0.91H53.46z"/>
<path class="st0" d="M58.49,40.71h-0.47V40.1h-0.01c-0.25,0.47-0.65,0.7-1.19,0.7c-0.53-0.01-0.92-0.2-1.18-0.58
c-0.27-0.38-0.4-0.85-0.4-1.43c0-0.6,0.12-1.08,0.37-1.44c0.25-0.37,0.65-0.56,1.21-0.56c0.19,0,0.36,0.03,0.5,0.08
c0.15,0.06,0.27,0.12,0.36,0.21c0.2,0.18,0.31,0.32,0.34,0.44h0.01v-2.45h0.47V40.71z M55.81,39.35c0.04,0.19,0.1,0.37,0.18,0.53
c0.09,0.15,0.21,0.27,0.35,0.37c0.14,0.1,0.32,0.16,0.55,0.16c0.21,0,0.39-0.05,0.54-0.16c0.14-0.1,0.26-0.22,0.35-0.38
c0.17-0.34,0.25-0.69,0.25-1.06c0-0.18-0.02-0.36-0.05-0.56c-0.04-0.18-0.1-0.35-0.2-0.51c-0.09-0.16-0.21-0.29-0.35-0.39
c-0.15-0.1-0.33-0.14-0.54-0.14c-0.23,0-0.41,0.05-0.55,0.14c-0.15,0.09-0.26,0.22-0.35,0.38c-0.08,0.15-0.14,0.32-0.18,0.51
c-0.04,0.19-0.05,0.38-0.05,0.57C55.75,38.98,55.77,39.16,55.81,39.35z"/>
<path class="st0" d="M62.79,40.59c-0.36,0.14-0.73,0.21-1.11,0.21c-0.62-0.01-1.07-0.2-1.34-0.58c-0.27-0.37-0.4-0.84-0.4-1.43
c0-0.6,0.16-1.08,0.45-1.45c0.29-0.36,0.67-0.55,1.16-0.56c0.54,0.01,0.93,0.2,1.17,0.56c0.24,0.36,0.35,0.81,0.35,1.34v0.24
h-2.62c0,0.42,0.11,0.76,0.32,1.03c0.21,0.29,0.52,0.43,0.94,0.44c0.19,0,0.38-0.03,0.59-0.1c0.21-0.06,0.37-0.12,0.49-0.19V40.59
z M62.55,38.53c0-0.35-0.07-0.66-0.22-0.92c-0.16-0.27-0.41-0.4-0.76-0.41c-0.35,0.01-0.62,0.15-0.82,0.42
c-0.21,0.27-0.31,0.57-0.31,0.91H62.55z"/>
<path class="st0" d="M64.56,37.74c0-0.35-0.01-0.64-0.03-0.86H65v0.74h0.01c0.09-0.23,0.22-0.42,0.38-0.58
c0.16-0.16,0.36-0.24,0.6-0.25c0.12,0,0.23,0.02,0.33,0.04v0.5c-0.1-0.03-0.22-0.05-0.35-0.05c-0.19,0.01-0.34,0.05-0.46,0.14
c-0.12,0.09-0.22,0.21-0.29,0.37c-0.07,0.15-0.12,0.31-0.15,0.49c-0.04,0.19-0.05,0.37-0.05,0.54v1.89h-0.47V37.74z"/>
<path class="st0" d="M69.84,40.1h-0.02c-0.11,0.23-0.28,0.4-0.5,0.52s-0.46,0.18-0.71,0.19c-0.27,0-0.48-0.05-0.65-0.13
c-0.17-0.08-0.3-0.17-0.39-0.29c-0.16-0.25-0.24-0.47-0.23-0.67c0-0.32,0.08-0.57,0.23-0.76c0.15-0.19,0.34-0.32,0.58-0.4
c0.24-0.08,0.49-0.14,0.77-0.17c0.27-0.02,0.53-0.02,0.78-0.02h0.09v-0.2c0.01-0.66-0.28-0.98-0.87-0.98
c-0.4,0-0.77,0.1-1.13,0.31v-0.46c0.35-0.17,0.73-0.25,1.13-0.26c0.91-0.01,1.35,0.47,1.34,1.43v1.69
c-0.01,0.29,0.01,0.56,0.04,0.8h-0.47V40.1z M69.79,38.78h-0.14c-0.21,0-0.41,0-0.62,0.01c-0.22,0.02-0.41,0.06-0.59,0.13
c-0.18,0.06-0.31,0.15-0.42,0.28c-0.12,0.12-0.17,0.29-0.17,0.5c0,0.25,0.08,0.42,0.24,0.52c0.15,0.11,0.33,0.17,0.56,0.17
c0.27,0,0.49-0.06,0.65-0.17c0.15-0.1,0.27-0.23,0.34-0.38c0.07-0.15,0.12-0.31,0.13-0.46c0.01-0.16,0.02-0.28,0.02-0.38V38.78z"
/>
<path class="st0" d="M71.62,40.31l2.3-3.02h-2.21v-0.41h2.76v0.41l-2.3,3.02h2.3v0.41h-2.85V40.31z"/>
<path class="st0" d="M76.37,35.86h-0.47v-0.63h0.47V35.86z M75.89,36.88h0.47v3.83h-0.47V36.88z"/>
<path class="st0" d="M81.13,39.81c0,0.29,0,0.59,0.03,0.9H80.7v-0.68h-0.01c-0.04,0.08-0.08,0.17-0.14,0.25
c-0.06,0.09-0.14,0.18-0.23,0.26c-0.09,0.07-0.21,0.13-0.35,0.18c-0.14,0.06-0.31,0.08-0.51,0.08c-0.47,0-0.8-0.15-1.01-0.42
c-0.21-0.27-0.31-0.62-0.3-1.05v-2.45h0.47v2.33c0,0.37,0.07,0.65,0.22,0.86c0.14,0.22,0.37,0.33,0.69,0.33
c0.21,0,0.4-0.05,0.55-0.14c0.15-0.08,0.26-0.2,0.34-0.33c0.09-0.14,0.15-0.29,0.19-0.45c0.03-0.16,0.05-0.32,0.05-0.48v-2.12
h0.47V39.81z"/>
<path class="st0" d="M82.9,37.78c0-0.3-0.01-0.6-0.03-0.9h0.46v0.68h0.02c0.04-0.09,0.1-0.18,0.16-0.27
c0.06-0.09,0.14-0.17,0.23-0.25c0.1-0.08,0.21-0.14,0.35-0.19c0.14-0.04,0.31-0.07,0.5-0.07c0.47,0,0.81,0.15,1.01,0.42
c0.2,0.27,0.29,0.62,0.29,1.05v2.45h-0.47v-2.33c0-0.37-0.07-0.66-0.2-0.87c-0.15-0.21-0.38-0.31-0.71-0.31
c-0.42,0.01-0.72,0.16-0.89,0.45c-0.09,0.14-0.15,0.28-0.19,0.44c-0.04,0.17-0.06,0.33-0.06,0.5v2.12H82.9V37.78z"/>
<path class="st0" d="M90.96,40.4c0.23,0,0.44-0.06,0.6-0.19c0.16-0.11,0.24-0.28,0.25-0.5c0-0.23-0.1-0.4-0.28-0.53
c-0.18-0.12-0.39-0.24-0.64-0.34c-0.11-0.05-0.23-0.11-0.33-0.17s-0.21-0.13-0.3-0.2c-0.08-0.08-0.15-0.17-0.21-0.28
c-0.06-0.1-0.08-0.22-0.08-0.35c0-0.38,0.14-0.65,0.39-0.81c0.25-0.15,0.53-0.23,0.86-0.23c0.11,0,0.25,0.01,0.43,0.03
c0.18,0.03,0.35,0.08,0.52,0.14l-0.04,0.41c-0.27-0.12-0.56-0.18-0.85-0.18c-0.25,0-0.44,0.04-0.59,0.14
c-0.08,0.04-0.13,0.11-0.17,0.19c-0.05,0.08-0.07,0.19-0.07,0.31c0,0.08,0.03,0.16,0.08,0.23c0.06,0.06,0.13,0.12,0.22,0.18
c0.09,0.05,0.19,0.1,0.31,0.16c0.12,0.05,0.23,0.1,0.34,0.17c0.25,0.1,0.47,0.25,0.65,0.42c0.18,0.18,0.28,0.42,0.29,0.73
c0,0.2-0.04,0.37-0.12,0.5c-0.09,0.14-0.19,0.25-0.32,0.34c-0.28,0.17-0.58,0.25-0.89,0.25c-0.37,0-0.73-0.06-1.07-0.18l0.04-0.47
C90.25,40.31,90.59,40.39,90.96,40.4z"/>
<path class="st0" d="M95.09,40.71h-0.56l-1.3-3.83h0.52l1.06,3.31h0.01l1.11-3.31h0.5L95.09,40.71z"/>
<path class="st0" d="M98.15,35.86h-0.47v-0.63h0.47V35.86z M97.68,36.88h0.47v3.83h-0.47V36.88z"/>
<path class="st0" d="M99.57,40.31l2.3-3.02h-2.21v-0.41h2.76v0.41l-2.3,3.02h2.3v0.41h-2.85V40.31z"/>
<path class="st0" d="M103.84,37.74c0-0.35-0.01-0.64-0.03-0.86h0.47v0.74h0.01c0.09-0.23,0.22-0.42,0.38-0.58
c0.16-0.16,0.36-0.24,0.6-0.25c0.12,0,0.23,0.02,0.33,0.04v0.5c-0.1-0.03-0.22-0.05-0.35-0.05c-0.19,0.01-0.34,0.05-0.46,0.14
c-0.12,0.09-0.22,0.21-0.29,0.37c-0.07,0.15-0.12,0.31-0.15,0.49c-0.04,0.19-0.05,0.37-0.05,0.54v1.89h-0.47V37.74z"/>
<path class="st0" d="M109.13,40.1h-0.02c-0.11,0.23-0.28,0.4-0.5,0.52s-0.46,0.18-0.71,0.19c-0.27,0-0.48-0.05-0.65-0.13
c-0.17-0.08-0.3-0.17-0.39-0.29c-0.16-0.25-0.24-0.47-0.23-0.67c0-0.32,0.08-0.57,0.23-0.76c0.15-0.19,0.34-0.32,0.58-0.4
c0.24-0.08,0.49-0.14,0.77-0.17c0.27-0.02,0.53-0.02,0.78-0.02h0.09v-0.2c0.01-0.66-0.28-0.98-0.87-0.98
c-0.4,0-0.77,0.1-1.13,0.31v-0.46c0.35-0.17,0.73-0.25,1.13-0.26c0.91-0.01,1.35,0.47,1.34,1.43v1.69
c-0.01,0.29,0.01,0.56,0.04,0.8h-0.47V40.1z M109.08,38.78h-0.14c-0.21,0-0.41,0-0.62,0.01c-0.22,0.02-0.41,0.06-0.59,0.13
c-0.18,0.06-0.31,0.15-0.42,0.28c-0.12,0.12-0.17,0.29-0.17,0.5c0,0.25,0.08,0.42,0.24,0.52c0.15,0.11,0.33,0.17,0.56,0.17
c0.27,0,0.49-0.06,0.65-0.17c0.15-0.1,0.27-0.23,0.34-0.38c0.07-0.15,0.12-0.31,0.13-0.46c0.01-0.16,0.02-0.28,0.02-0.38V38.78z"
/>
</g>
<g>
<path class="st1" d="M24.45,4.19c0,0-3.97-1.74-10.61-1.74h0c-6.64,0-10.61,1.74-10.61,1.74s-0.36,7.7,1.15,12.05
c2.66,7.65,9.46,9.8,9.46,9.8h0h0c0,0,6.8-2.15,9.46-9.8C24.8,11.89,24.45,4.19,24.45,4.19z"/>
</g>
<polygon class="st2" points="20.88,10.83 20.88,15.07 15.94,15.07 15.94,20.02 11.7,20.02 11.7,15.07 6.75,15.07 6.75,10.83
11.7,10.83 11.7,5.88 15.94,5.88 15.94,10.83 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 44 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 716 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1013 KiB

+311
View File
@@ -0,0 +1,311 @@
#import "./util/title.typ": title as titlepage
#import "./util/versionhistory.typ": parseCurrentVersion, versionhistoryTable
#import "./util/cover.typ": cover
#import "./util/code.typ": code
#import "./util/glossary.typ"
#import "./util/acronyms.typ"
#import "./pallete.typ": palette
#let std-bibliography = bibliography
#import "./util/persons.typ"
#let ipa(
// The title for your work.
title: [Your Title],
peoples: (
(
name: "Axel Schweiss",
email: "axel.schweiss@bit.admin.ch",
shorthand: "AS",
role: "Kandidat",
git: "xxxxxxxxx",
),
),
company: [Bundesamt für Informatik und Telekommunikation BIT],
departement: [Entwicklung 5 - BS-BSC-EN5],
metadata: (
specialisation: "Applikationsentwicklung",
managementMethod: "HERMES",
// year as in "Jahrgang"
year: "IPA 2025",
place: "Kanton Bern",
dateOfSubmission: datetime(day: 1, month: 12, year: 2024),
commits: (),
final-version: false,
),
// The result of a call to the `bibliography` function or `none`.
// Example: bibliography("refs.bib")
// More info: https://typst.app/docs/reference/model/bibliography/
bibliography: none,
abbreviations: none,
part1: (
cover: read("./assets/cover1.jpg", encoding: none),
content: content,
),
part2: (
cover: read("./assets/cover2.jpg", encoding: none),
content: content,
),
// Display an appendix after the body but before the bibliography.
appendix: (
files: (),
// default acronyms fir the "Abkürzungs Verzeichniss"
acronyms: (:),
// default terns fir the "Glossar"
terms: (:),
body: none,
),
// The content of your work.
body,
) = {
context {
persons._persons.update(_ => return peoples)
let author = persons.byFunction("Kandidat")
let persons-dict = (:)
for person in peoples {
if person.keys().contains("git") {
persons-dict.insert(person.git, person.name)
}
}
let currentversion = parseCurrentVersion(
commits: metadata.commits,
persons: persons-dict,
final: datetime.today() == metadata.dateOfSubmission,
)
// Set the document's metadata.
set document(title: title, author: author.name)
set text(
lang: "de",
region: "ch",
font: (
"Helvetica",
),
size: 11pt,
fill: palette.ui.text,
)
// Set raw text font.
// Default is Fira Mono at 8.8pt
show raw: set text(font: ("Fira Mono", "PT Mono"), size: 9pt, fill: palette.ui.text)
// Configure page size and margins.
set page(
paper: "a4",
number-align: bottom + right,
margin: 2.5cm,
footer: [
#set text(size: 8pt)
#title | #author.name | #datetime.today().display("[day].[month].[year]")
#h(1fr)
Version #currentversion | #context counter(page).display("1 / 1", both: true)
],
)
// Cover page.
titlepage(
title: title,
author: author.name,
company: company,
departement: departement,
metadata: (..metadata, currentversion: currentversion),
)
// Configure paragraph properties.
// Default leading is 0.65em.
// Default spacing is 1.2em.
set par(leading: 0.7em, spacing: 1.35em, justify: true, linebreaks: "optimized")
// Add vertical space after headings.
show heading: it => {
it
v(2%, weak: true)
}
// Do not hyphenate headings.
show heading: set text(hyphenate: false)
// Display inline code in a small box that retains the correct baseline.
show raw.where(block: false): box.with(
fill: palette.gray.at("200"),
inset: (x: 3pt, y: 0pt),
outset: (y: 3pt),
radius: 2pt,
)
// Display block code with padding.
show raw.where(block: true): block.with(inset: 5pt)
// figure settings
show figure.where(kind: raw): set figure(supplement: [Codeblock])
show figure.where(kind: code): set block(breakable: true)
show figure.where(kind: table): set block(breakable: true)
set table(
// Increase the table cell's padding
inset: 7pt, // default is 5pt
stroke: (0.5pt + palette.ui.border),
)
let frame(stroke) = (
(
x,
y,
) => (
left: none,
right: none,
top: if y == 0 { 0pt } else { stroke },
bottom: if y == 0 { 2pt } else { stroke },
)
)
set table(
fill: (_, y) => if calc.odd(y) {
rgb(palette.ui.bg-dark)
},
stroke: frame(rgb(palette.ui.border)),
)
set table.cell(align: left)
show table.cell.where(y: 0): strong
show figure.caption: emph
// Show all headings by default in outline
show outline: set heading(outlined: true)
// Allow figures to break across multiple pages, except code blocks
// Add some padding at the bottom to every figure
show figure: it => pad(bottom: 10pt, it)
// Declare versionhistoryTable after table settings
pagebreak(weak: true)
versionhistoryTable(
commits: metadata.commits,
persons: persons-dict,
final: datetime.today() == metadata.dateOfSubmission,
)
// Wrap `body` in curly braces so that it has its own context. This way show/set rules
// will only apply to body.
{
show "ß": "ss"
// Configure heading numbering.
set heading(numbering: (..n) => {
if n.pos().len() < 4 {
numbering("1.1", ..n)
} else {
numbering(() => h(-0.3em))
}
})
// pagebreak before lvl 1 headings
show heading.where(level: 1): it => {
if it.numbering != none and it.body != [Zeitplan] {
pagebreak(weak: true)
}
it
}
// pagebreak if heading would start at last 20% of page for lvl 2 headings, but this fucks page margins so it is commented out
// show heading.where(level: 2): it => {
// let threshold = 20%
// block(breakable: false, height: threshold, spacing: 0pt)
// v(-threshold)
// // set block(spacing: 1em)
// it
// }
//
show ref: it => if it.element == none or it.element.func() != heading {
it
} else {
let h = it.element // heading
let c = context {
let n = numbering("1.1", ..counter(heading).at(h.location())) // numbering
return h.supplement + " " + n + " (" + h.body + ")"
}
link(it.target, c)
}
cover(
part: [TEIL 1],
img: if part1.keys().contains("cover") { part1.cover } else { read("./assets/cover1.jpg", encoding: none) },
description: [Ablauf - Organisation - Umfeld],
)
outline(
depth: 3,
indent: auto,
)
pagebreak(weak: true)
part1.content
cover(
part: [TEIL 2],
img: if part2.keys().contains("cover") { part2.cover } else { read("./assets/cover2.jpg", encoding: none) },
description: [Individueller, praktischer Teil],
)
part2.content
show outline: set heading(numbering: "1.1")
outline(
title: [Abbildungsverzeichnis],
target: figure.where(kind: image),
)
outline(
title: [Tabellenverzeichnis],
target: figure.where(kind: table),
)
if bibliography != none {
show std-bibliography: set heading(numbering: "1.1")
set par(justify: false)
// pagebreak()
{
set std-bibliography(
title: "Quellenverzeichnis",
style: "ieee",
)
bibliography
}
}
acronyms.setDefaults(appendix.at("acronyms", default: (:)))
acronyms.acronymList()
glossary.setDefaults(appendix.at("terms", default: (:)))
glossary.glossary()
[
= Anhang
#if appendix.body != none {
appendix.body
}
== Quellcode
#{
outline(
title: none,
target: figure.where(kind: raw),
)
code(files: appendix.files)
}
]
}
}
}
+17
View File
@@ -0,0 +1,17 @@
// main function
#import "./ipa.typ": ipa
// other top level elements
#import "./util/acronyms.typ": acronym
#import "./util/glossary.typ": term
#import "./pallete.typ": palette
// export of other modules
#import "./util/arbeits-journale.typ" as arbeitsjournal
#import "./oasviz/lib.typ" as oasviz
#import "./util/test/lib.typ" as test
#import "./util/persons.typ" as persons
#import "./util/usecase.typ": usecase
#import "./util/requirements.typ"
+49
View File
@@ -0,0 +1,49 @@
# oasviz - OpenAPI Specification Visualizer
`oasviz` is a Typst library designed for visualizing OpenAPI Specification (OAS) documents. It provides a suite of components to render API endpoints, data schemas, and server configurations in a structured and aesthetically pleasing format.
This library was significantly refactored and optimized with the assistance of the **Gemini CLI Development Agent**.
## Design Philosophy
- **Style Inheritance**: The library is built to be "style-neutral." It does not impose its own font sizes, internal padding, or block margins. Instead, it is designed to inherit these settings from the global `set` rules of the host document (e.g., `lib.typ`).
- **Swiss Minimalism**: Follows a Swiss-inspired minimalist aesthetic, utilizing square shapes (`radius: 0pt`), clean borders, and a high-contrast layout.
- **Project Integration**: Deeply integrated with the project's core utilities:
- `../pallete.typ`: Uses the centralized palette for UI elements and semantic HTTP method colors.
- `../util/tabler.typ`: Utilizes specialized table layouts for operation documentation.
## Core Features
### 1. Localization (i18n)
The library uses a dedicated `translate` function supporting English and German. It employs a flat dot-notation key structure for clarity and maintainability. Recent updates have expanded the key set to ensure full localization of all UI elements, including captions and schema details.
**Key Improvements:**
- **Expanded Abbreviations**: "Ex" is now "Example" (EN) / "Beispiel" (DE), and "Ref" is "Uses schema" (EN) / "Verwendet Schema" (DE).
- **Full Schema Localization**: Terms like "Type", "Array of", and "any" are now fully translated.
- **Consistent Naming**: All keys follow a strict dot-notation pattern (e.g., `endpoints.uses-schema`, `common.example`).
**Example Keys:**
- `translate("endpoints.title")`
- `translate("servers.table.addr")`
- `translate("common.example")`
### 2. Semantic Method Badges
HTTP methods are visualized using flat, color-coded badges that prioritize readability while maintaining the minimalist design language.
### 3. Schema Rendering
Automatically renders complex JSON Schema definitions into clean, readable tables, complete with property types, descriptions, and examples.
## Usage
The primary entry point is the `render` function, which accepts a Typst dictionary representing an OpenAPI document:
```typst
#import "oasviz/oasviz.typ": render
#let data = yaml("openapi.yaml")
#render(data)
```
+70
View File
@@ -0,0 +1,70 @@
// Localization Helper
#let translate(key) = {
context {
let lang = text.lang
let en = (
"servers.title": "Servers",
"servers.desc": "This section lists the available servers used to access the API.",
"servers.caption": "Server List",
"servers.table.addr": "Address",
"servers.table.desc": "Description",
"schemas.title": "Schemas",
"schemas.desc": "This section defines the data structures sent and received by the API.",
"schemas.caption": "Schema",
"schemas.property": "Property",
"schemas.type": "Type",
"schemas.desc-ex": "Description/Example",
"endpoints.title": "Endpoints",
"endpoints.desc": "This section describes the available commands (endpoints) that can be sent to the API.",
"endpoints.endpoint": "Endpoint",
"endpoints.method": "HTTP Method",
"endpoints.summary": "Summary",
"endpoints.description": "Description",
"endpoints.parameters": "Parameters:",
"endpoints.request-body": "Request Body:",
"endpoints.responses": "Responses:",
"endpoints.uses-schema": "Uses schema ",
"endpoints.array-of": "Array of ",
"endpoints.type-prefix": "Type",
"endpoints.caption": "Endpoint",
"common.example": "Example",
"common.any": "any",
)
let de = (
"servers.title": "Server",
"servers.desc": "In diesem Abschnitt werden die verfügbaren Server aufgelistet, über die die API erreicht werden kann.",
"servers.caption": "Serverliste",
"servers.table.addr": "Adresse",
"servers.table.desc": "Beschreibung",
"schemas.title": "Datenschemata",
"schemas.desc": "Hier sind die Datenstrukturen definiert, die von der API gesendet und empfangen werden.",
"schemas.caption": "Schema",
"schemas.property": "Eigenschaft",
"schemas.type": "Typ",
"schemas.desc-ex": "Beschreibung/Beispiel",
"endpoints.title": "Endpunkte",
"endpoints.desc": "In diesem Abschnitt werden die verfügbaren Befehle (Endpunkte) beschrieben, die an die API gesendet werden können.",
"endpoints.endpoint": "Endpunkt",
"endpoints.method": "HTTP-Methode",
"endpoints.summary": "Zusammenfassung",
"endpoints.description": "Beschreibung",
"endpoints.parameters": [Parameter: \ (Name, Ort, Typ, \ Beschreibung)],
"endpoints.request-body": "Anfrage-Daten:",
"endpoints.responses": [Antworten: \ (Statuscode, Daten) ],
"endpoints.uses-schema": "Verwendet Schema ",
"endpoints.array-of": "Liste von ",
"endpoints.type-prefix": "Typ",
"endpoints.caption": "Endpunkt",
"common.example": "Beispiel",
"common.any": "beliebig",
)
let dict = if lang == "de" { de } else { en }
let val = dict.at(key, default: key)
if val != none {
val
}
}
}
+8
View File
@@ -0,0 +1,8 @@
// oasviz - OpenAPI Specification Visualizer for Typst
//
// This package provides tools to render OpenAPI Specification (OAS) documents
// directly within Typst.
//
#import "./oasviz.typ": render
+279
View File
@@ -0,0 +1,279 @@
// oasviz.typ
//
// oasviz is a Typst library designed for visualizing OpenAPI Specification (OAS) documents.
// It provides a suite of functions to render API endpoints, schemas, servers, and other
// components in a structured and aesthetically pleasing format, ideal for technical
// documentation and API reports.
//
#import "i18n.typ": translate
#import "../util/tabler.typ"
#import "../pallete.typ": palette
// Helper to render a badge for HTTP methods
#let method-badge(method) = {
let lightness = "600"
let color = if method == "get" { palette.blue.at(lightness) } else if method == "post" {
palette.green.at(lightness)
} else if method == "put" {
palette.orange.at(lightness)
} else if method == "delete" { palette.red.at(lightness) } else if method == "patch" {
palette.purple.at(lightness)
} else { palette.gray.at(lightness) }
box(
fill: color,
stroke: none,
radius: 2pt,
inset: (x: 0.4em, y: 0.4em),
text(weight: "bold", fill: white, size: 0.9em, upper(method)),
)
}
// Render Servers
#let render-servers(servers) = {
if servers != none and servers.len() > 0 [
= #translate("servers.title")
#translate("servers.desc")
#figure(
table(
columns: (auto, 1fr),
stroke: none,
align: left,
translate("servers.table.addr"),
translate("servers.table.desc"),
table.hline(stroke: 1pt + palette.ui.border),
..(
for server in servers {
(
raw(server.url),
if "description" in server { server.description } else { "-" },
)
}
),
),
caption: translate("servers.caption"),
)
]
}
#let _get-type-display(val) = {
if val == none { return translate("common.any") }
if "$ref" in val {
return raw(val.at("$ref").split("/").last())
}
if "type" in val {
if val.type == "array" and "items" in val {
return [#translate("endpoints.array-of") #_get-type-display(val.items)]
}
return val.type
}
return translate("common.any")
}
#let _resolve-all-properties(schema, all-schemas, seen: ()) = {
let props = (:)
if "properties" in schema {
props = props + schema.properties
}
if "allOf" in schema {
for element in schema.allOf {
if "$ref" in element {
let ref-name = element.at("$ref").split("/").last()
if ref-name not in seen {
let ref-schema = all-schemas.at(ref-name, default: (:))
props = props + _resolve-all-properties(ref-schema, all-schemas, seen: seen + (ref-name,))
}
} else if "properties" in element {
props = props + element.properties
} else if "allOf" in element {
props = props + _resolve-all-properties(element, all-schemas, seen: seen)
}
}
}
return props
}
// Render a single Schema/Type
#let render-schema(name, schema, all-schemas: (:)) = [
=== #name #if "type" in schema {
[ (#schema.type)]
}
#let props = _resolve-all-properties(schema, all-schemas)
#if props.len() > 0 {
figure(
table(
columns: (auto, auto, 1fr),
align: (x, y) => left,
table.header(
[*#translate("schemas.property")*], [*#translate("schemas.type")*], [*#translate("schemas.desc-ex")*]
),
..props
.pairs()
.map(((key, val)) => {
(
raw(key),
_get-type-display(val),
{
if "description" in val { val.description + " " }
if "example" in val {
text(fill: palette.ui.text-light, [#translate("common.example"): #val.example])
}
},
)
})
.flatten(),
),
caption: [#translate("schemas.caption") #name],
)
} else {
sym.dash
}
]
// Render Components/Schemas
#let render-components(components) = {
if components != none and "schemas" in components {
[
= #translate("schemas.title")
#context if translate("schemas.desc") != none {
par(translate("schemas.desc"))
}
]
let schemas = components.schemas
for (name, schema) in schemas.pairs() {
render-schema(name, schema, all-schemas: schemas)
}
}
}
#let _resolve-ref(val, pool) = {
if type(val) == dictionary and "$ref" in val {
let ref-name = val.at("$ref").split("/").last()
return pool.at(ref-name, default: val)
}
return val
}
// Render a single Operation (Endpoint)
#let render-operation(method, path, op, components: (:)) = [
#figure(
tabler.sideways(
columns: (auto, auto, auto, auto, 1fr),
translate("endpoints.endpoint"),
table.cell(colspan: 4, strong(path)),
translate("endpoints.method"),
table.cell(colspan: 4, method-badge(method)),
..if "summary" in op {
(translate("endpoints.summary"), table.cell(colspan: 4, op.summary))
},
..if "description" in op {
(translate("endpoints.description"), table.cell(colspan: 4, op.description))
},
..if "parameters" in op and op.parameters.len() > 0 {
(
table.cell(rowspan: op.parameters.len(), translate("endpoints.parameters")),
..for p-raw in op.parameters {
let p = _resolve-ref(p-raw, components.at("parameters", default: (:)))
(
p.name,
p.at("in", default: "query"),
if "schema" in p { _get-type-display(p.schema) } else { "-" },
p.at("description", default: ""),
)
},
)
},
..if "requestBody" in op {
let rb = _resolve-ref(op.requestBody, components.at("requestBodies", default: (:)))
(
translate("endpoints.request-body"),
..for (media-type, details) in rb.at("content", default: (:)).pairs() {
(
table.cell(
colspan: 2,
raw(media-type),
),
table.cell(colspan: 2, if "schema" in details {
[#translate("endpoints.type-prefix"): #_get-type-display(details.schema)]
} else {
"-"
}),
)
},
)
},
..if "responses" in op {
(
table.cell(rowspan: op.responses.len(), translate("endpoints.responses")),
..op
.responses
.pairs()
.map(((code, resp-raw)) => {
let resp = _resolve-ref(resp-raw, components.at("responses", default: (:)))
let schema-display = []
if "content" in resp {
let content-map = resp.content
let media-type = if "application/json" in content-map { "application/json" } else {
content-map.keys().at(0, default: none)
}
if media-type != none {
let media-obj = content-map.at(media-type)
if "schema" in media-obj {
schema-display = [#translate("endpoints.type-prefix"): #_get-type-display(media-obj.schema)]
}
}
}
(
strong(code),
table.cell(colspan: 3, [
#resp.at("description", default: "")
#if schema-display != [] [ \ #text(fill: palette.ui.text-light, schema-display) ]
]),
)
})
.flatten(),
)
},
),
caption: [#translate("endpoints.caption") #upper(method) #path],
)
]
// Main Render Function
#let render(data) = {
show figure.where(kind: table): set block(breakable: false)
let components = data.at("components", default: (:))
// Servers
if "servers" in data {
render-servers(data.servers)
}
// Paths
if "paths" in data {
[
= #translate("endpoints.title")
#context if translate("endpoints.desc") != none {
par(translate("endpoints.desc"))
}
]
for (path, item) in data.paths.pairs() {
let methods = ("get", "post", "put", "delete", "patch", "head", "options", "trace")
for method in methods {
if method in item {
render-operation(method, path, item.at(method), components: components)
}
}
}
}
// Components
if "components" in data {
render-components(data.components)
}
}
+149
View File
@@ -0,0 +1,149 @@
#let base = (
petrol-anthracite: (
"50": rgb("#f0f4f7"),
"100": rgb("#dfe4e9"),
"200": rgb("#acb4bd"),
"300": rgb("#828e9a"),
"400": rgb("#596978"),
"500": rgb("#46596b"),
"600": rgb("#2f4356"),
"700": rgb("#263645"),
"800": rgb("#1c2834"),
"900": rgb("#131b22"),
),
blue: (
"50": rgb("#eff6ff"),
"100": rgb("#dbeafe"),
"200": rgb("#bfdbfe"),
"300": rgb("#93c5fd"),
"400": rgb("#60a5fa"),
"500": rgb("#3b82f6"),
"600": rgb("#2563eb"),
"700": rgb("#1d4ed8"),
"800": rgb("#1e40af"),
"900": rgb("#1e3a8a"),
),
green: (
"50": rgb("#ecfdf5"),
"100": rgb("#d1fae5"),
"200": rgb("#a7f3d0"),
"300": rgb("#6ee7b7"),
"400": rgb("#34d399"),
"500": rgb("#10b981"),
"600": rgb("#059669"),
"700": rgb("#047857"),
"800": rgb("#065f46"),
"900": rgb("#064e3b"),
),
indigo: (
"50": rgb("#eef2ff"),
"100": rgb("#e0e7ff"),
"200": rgb("#c7d2fe"),
"300": rgb("#a5b4fc"),
"400": rgb("#818cf8"),
"500": rgb("#6366f1"),
"600": rgb("#4f46e5"),
"700": rgb("#4338ca"),
"800": rgb("#3730a3"),
"900": rgb("#312e81"),
),
orange: (
"50": rgb("#fff7ed"),
"100": rgb("#ffedd5"),
"200": rgb("#fed7aa"),
"300": rgb("#fdba74"),
"400": rgb("#fb923c"),
"500": rgb("#f97316"),
"600": rgb("#ea580c"),
"700": rgb("#c2410c"),
"800": rgb("#9a3412"),
"900": rgb("#7c2d12"),
),
pink: (
"50": rgb("#fdf2f8"),
"100": rgb("#fce7f3"),
"200": rgb("#fbcfe8"),
"300": rgb("#f9a8d4"),
"400": rgb("#f472b6"),
"500": rgb("#ec4899"),
"600": rgb("#db2777"),
"700": rgb("#be185d"),
"800": rgb("#9d174d"),
"900": rgb("#831843"),
),
purple: (
"50": rgb("#f5f3ff"),
"100": rgb("#ede9fe"),
"200": rgb("#ddd6fe"),
"300": rgb("#c4b5fd"),
"400": rgb("#a78bfa"),
"500": rgb("#8655F6"),
"600": rgb("#7c3aed"),
"700": rgb("#6d28d9"),
"800": rgb("#5b21b6"),
"900": rgb("#4c1d95"),
),
red: (
"50": rgb("#ffedee"),
"100": rgb("#fae1e2"),
"200": rgb("#ffccce"),
"300": rgb("#fa9da1"),
"400": rgb("#fc656b"),
"500": rgb("#e53940"),
"600": rgb("#d8232a"),
"700": rgb("#bf1f25"),
"800": rgb("#99191e"),
"900": rgb("#801519"),
),
teal: (
"50": rgb("#f2fdfc"),
"100": rgb("#cbfbf8"),
"200": rgb("#98f6f3"),
"300": rgb("#5de8ea"),
"400": rgb("#2bced4"),
"500": rgb("#14afb8"),
"600": rgb("#0d8b96"),
"700": rgb("#0f6b75"),
"800": rgb("#11575f"),
"900": rgb("#13474e"),
),
yellow: (
"50": rgb("#fffbeb"),
"100": rgb("#fef3c7"),
"200": rgb("#fde68a"),
"300": rgb("#fcd34d"),
"400": rgb("#fbbf24"),
"500": rgb("#f59e0b"),
"600": rgb("#d97706"),
"700": rgb("#b45309"),
"800": rgb("#92400e"),
"900": rgb("#78350f"),
),
gray: (
"50": rgb("#f9fafb"),
"100": rgb("#f3f4f6"),
"200": rgb("#e5e7eb"),
"300": rgb("#d1d5db"),
"400": rgb("#9ca3af"),
"500": rgb("#6b7280"),
"600": rgb("#4b5563"),
"700": rgb("#374151"),
"800": rgb("#1f2937"),
"900": rgb("#111827"),
),
)
#let palette = (
..base,
// UI Functional Colors
primary: base.red,
secondary: base.petrol-anthracite,
text: base.gray,
ui: (
text: base.gray.at("800"), // Default text color
text-light: base.gray.at("500"), // Default light text
link: base.red.at("600"), // color-primary-600
bg-dark: base.petrol-anthracite.at("50"), // background-color
border: rgb(31, 41, 55), // border-bottom-color
),
)
+12
View File
@@ -0,0 +1,12 @@
.DS_Store
.DS_Store
**/.DS_Store
03_Zeitplan/Zeitplan.pdf
03_Zeitplan/.~lock.Zeitplan.xlsx#
02_Grafiken/.$*.drawio.bkp
sourcepaths.txt
versionhistory.csv
timetable.yaml
@@ -0,0 +1,16 @@
// IPA-Dokumentenvorgabe BiVo2021-2:
//
// Die Aufgaben (Detailbeschreibung) muss 1:1 aus PkOrg übernommen
// werden. Inkl. allen vorgegebenen Titel.
//
// Unterkapitel aus der PkOrg-Aufgabenstellung: Titel der Arbeit,
// Ausgangslage, Detaillierte Aufgabenstellung, Mittel und Methoden,
// Vorkenntnisse, Vorarbeiten, Neue Lerninhalte, Arbeiten in den letzten
// 6 Monaten.
//
// Der Inhalt darf an die Zielformatierung (IPA-Dokument) angepasst
// werden, inklusive eventueller Korrektur der Rechtschreibfehler. Die
// Veränderung der Satzstellung oder der Inhalte ist nicht erlaubt.
= Aufgabenstellung <chap-aufgabenstellung>
@@ -0,0 +1,30 @@
// IPA-Dokumentenvorgabe BiVo2021-2:
//
// Gibt es in Ihrer Unternehmung Standards oder sonstige Konventionen,
// so sind diese bei der Einreichung der detaillierten Aufgabenstellung als
// Beilage mit einzureichen, danach werden diese validiert. Bitte
// informieren Sie hierfür ihre verantwortliche Fachkraft.
//
// Gibt es keine Konventionen in der Unternehmung, so gelten die mit den
// Experten vereinbarten Best Practices, welche eingehalten werden
// müssen. Für Applikationsentwickler gelten in dem Fall im Minimum die
// IPA-Coding-Conventions, welche im PkOrg heruntergeladen werden
// können.
= Standards
#lorem(200)
// // Hinweis:
// // Gibt es in der Unternehmung Konventionen oder Standards, sind diese hier
// // aufzuführen und zu begründen. Für Applikationsentwickler gelten im Minimum
// // die IPA-Coding-Conventions.
// //
// // == Versionsverwaltung
// //
// // Als Versionsverwaltungswerkzeug wird GIT verwendet. Alle Commits werden
// // nach Conventional-Commits geschrieben.
// //
// // #figure(
// // image("../../02_Grafiken/Conventional-Commits.pdf", width: 80%),
// // caption: [Visualisierung Conventional-Commits],
// // ) <fig-conventional-commits>
@@ -0,0 +1,42 @@
// IPA-Dokumentenvorgabe BiVo2021-2:
//
// Gibt es in der IPA relevante, schützenswerte Daten? Welches sind die
// notwendigen Informationen/Daten? Sind alle nötigen Informationen und
// Daten vorhanden? Dürfen Daten in der Dokumentation nicht angegeben
// werden? (Bspw. Sicherheitshandbuch der Firma).
//
// Für IT-Systeme gibt es eventuell auch ein ISDS-Konzept oder etwas
// Vergleichbares, welches für die IPA beachtet werden muss? Ist dies der
// Fall, bitte die relevanten Dokumente auf PkOrg hochladen und im IPA-
// Bericht darauf verweisen.
= IPA-Schutzbedarfsanalyse <schuban>
#lorem(200)
// In der Schutzbedarfsanalyse wird geprüft, ob in der Dokumentation der IPA oder
// in der Applikation schützenswerte Daten verarbeitet werden. Zudem werden die
// erforderlichen Massnahmen festgelegt, um den Schutz dieser Daten sicherzustellen.
//
// == Dokumentation
//
// In der Dokumentation enthaltene Informationen dürfen keine Rückschlüsse auf die
// folgenden Punkte preisgeben:
//
// - Konfiguration der Unternehmensinfrastruktur
// - IP-Adressen
// - Gerätenamen
// - Domainnamen
// - Benutzernamen
// - Passwörter
// - Interne Entwicklungsvorgaben
// - Vertrauliche Daten aus der Applikation
//
// Falls die Dokumentation Informationen zu den genannten Punkten beinhaltet,
// muss diese für eine öffentliche Version geschwärzt werden. Die interne Version,
// welche dem Experten zur Verfügung gestellt wird, darf diese Informationen beinhalten.
//
// Da diese IPA kein Deployment bzw. keine Inbetriebnahme der Applikation beinhaltet,
// sollten voraussichtlich keine Gerätenamen oder Ähnliches verwendet werden müssen.
//
// Der IKT-Grundschutz @grundschutz muss eingehalten werden.
@@ -0,0 +1,80 @@
#import "@local/chfoit-ipa:0.0.0": acronym, term
// IPA-Dokumentenvorgabe BiVo2021-2:
//
// Wie werden die Ergebnisse der IPA abgelegt und was wird unternommen,
// um die Ergebnisse der IPA vor Verlust zu schützen.
// Beispiel:
// • Backup-Konzept: was wird wie und wann gesichert
// • Einsatz von Sourcecode-Verwaltungssystemen: wie wird damit
// gearbeitet
// ➔ Die Nachweise (Backup Log, Commit History, …) müssen innerhalb
// des Anhangs der Dokumentation abgegeben werden.
= Organisation der IPA-Ergebnisse
#lorem(200)
// == Arbeitsplatz
//
// #figure(
// image("../../02_Grafiken/arbeitsplatz.png", width: 80%),
// caption: [Arbeitsplatz der IPA],
// ) <fig-arbeitsplatz>
//
// Beschreibung des Arbeitsplatzes, der verwendeten Arbeitsgeräte und
// Hilfsmittel (z. B. Editor, Satzsystem, Diagrammwerkzeug).
//
// == Verwaltung der Ergebnisse
//
// Die Ergebnisse werden jeweils in einem Git #term(
// "Repository",
// "Ein zentraler, digitaler Speicherort für Daten, Dateien und Projektunterlagen,
// der eine strukturierte Verwaltung, Versionierung und gemeinsame Nutzung (Kollaboration) ermöglicht.",
// ) verwaltet. Eines für die Dokumentation und eines für den Code.
//
// Die Dokumentation ist in folgender Verzeichnisstruktur abgelegt:
//
// #figure(
// image("../../02_Grafiken/dirtree.png", width: 80%),
// caption: [Verzeichnisbaum der IPA],
// ) <fig-dirtree>
//
// #pagebreak(weak: true)
// #show table.cell.where(x: 0): set text(font: "Fira Mono")
// #figure(
// table(
// columns: (auto, auto),
// table.header([Verzeichnis], [Beschreibung]),
// [01_Dokumentation], [Alle Informationen, die in der Dokumentation verwendet werden.],
// [01_Dokumentation/01_Teil1], [Kapitel des Teil 1 als einzelne Typst-Dateien.],
// [01_Dokumentation/02_Teil2], [Kapitel des Teil 2 als einzelne Typst-Dateien.],
// [01_Dokumentation/03_Anhang], [Kapitel des Anhangs als einzelne Typst-Dateien.],
// [02_Grafiken], [Jegliche Grafiken, die für die Dokumentation verwendet werden.],
// [03_Zeitplan], [Hier wird der Zeitplan abgelegt],
// [04_Code], [Ablage für den Code. Wird in Git als Submodul gespeichert.],
// ),
// caption: [Erklärung der Ordnerstruktur der IPA],
// ) <ordnerstruktur-erklärung>
//
// == Datensicherung IPA
//
// === Code
//
// Der Code wird mit dem Versionsverwaltungstool Git versionisiert.
// Damit können einfach Teil-Features gepusht werden und es bleiben jederzeit
// alle Änderungen nachvollziehbar. Ebenfalls kann jederzeit auf einen alten
// Stand zurückgespielt werden.
//
// ==== Branch-Strategie
//
// #figure(
// image("../../02_Grafiken/branch-strategy.png", width: 80%),
// caption: [Git Branch-Strategie],
// ) <fig-branch-strategy>
//
// === Dokumentation
//
// Der gesamte IPA-Ordner ist mit Git versionisiert. Nach jeder signifikanten
// Änderung wird ein Commit erstellt und gepusht. Durch die lokale Versionierung
// sowie die regelmässigen Commits ist die Dokumentation immer gesichert.
@@ -0,0 +1,61 @@
// IPA-Dokumentenvorgabe BiVo2021-2:
//
//Projektvorgehensmodell beschreiben und begründen, warum das
// entsprechende Modell gewählt wurde.
// Wichtig:
// • Änderungen zu einem Standardmodell müssen immer beschrieben
// sein. Sind diese nicht beschrieben, werden vom Standard
// abweichende Elemente als Verletzung der Methode gewertet.
// • Dass ein Modell im jeweiligen Betrieb verwendet wird, reicht
// nicht als Begründung für die Verwendung des Modells!
// • IPERKA wird im Kanton Bern als Projektmethode nicht
// akzeptiert und darf nicht verwendet werden.
= Projektmethode <chap-projektmethode>
#lorem(200)
//
// Die IPA wird nach HERMES 2022 mit dem Szenario IT-Entwicklung (klassisch) wie in @fig-hermes-uebersicht abgebildet durchgeführt.
// Die folgenden Unterkapitel erläutern dies Weiter.
//
// #figure(
// image("../../02_Grafiken/hermes-uebersicht.png", width: 100%),
// caption: [Übersicht der Projektplanung],
// ) <fig-hermes-uebersicht>
//
// == Szenario
//
// Die IPA wird mit dem Szenario "IT-Entwicklung" durchgeführt, da nach @chap-aufgabenstellung eine Eigenständige IT-Lösung entwickelt wird.
//
// == Phasen
// Im rahmen der IPA wird die Einführungsphase ausgelassen, da das Produkt
// wie in @chap-aufgabenstellung beschrieben nicht eingeführt werden muss.
//
// Wie der @fig-hermes-uebersicht zu entnehmen ist, werden folgende Phasen von HERMES durchgeführt:
//
// - Initialisierungsphase: Hier wird das Fundament des Projektes gelegt. Varianten Angeschaut und Ausgewählt.
// - Konzeptphase: Wie es der Name sagt, werden da die Konzepte zur Realisierung erarbeitet, wie z.B. das Testkonzept.
// - Realisierungsphase: Hier wird die Applikation implementiert.
// - Abschlussphase: Hier wird das Projekt abgeschlossen und abgegeben.
//
// == Module
//
// In der @fig-hermes-modules werden die HERMES-module die in der IPA verwendet werden Beschrieben.
//
// #figure(
// table(
// columns: (auto, 1fr),
// [Modul], [Beschreibung],
// [Projektsteuerung],
// ["Das Modul Projektsteuerung sorgt für die gesamthafte und organisationsübergreifende Steuerung des Projekts und stellt die Erreichung der gesetzten Ziele sicher." @hermes-online],
//
// [Projektführung],
// ["Im Rahmen des Moduls Projektführung erfolgt die Planung, Führung und Koordination des Projekts zur Erreichung der Projektergebnisse und der Vorgehensziele sowie die Durchführung aller notwendigen flankierenden Massnahmen." @hermes-online],
//
// [IT-System], ["Das Modul IT-System dient der Entwicklung eines Systems." @hermes-online],
// [Tests],
// ["Das Modul Tests dient einer systematischen und effizienten Organisation und Durchführung des Testens von Lösungen." @hermes-online],
// ),
//
// caption: [Übersicht HERMES module],
// )<fig-hermes-modules>
@@ -0,0 +1,69 @@
#import "@local/chfoit-ipa:0.0.0": persons
#import "@preview/pintorita:0.1.4"
// IPA-Dokumentenvorgabe BiVo2021-2:
//
// Projektorganisation (Personen & Kontaktangaben, Projektrollen) Siehe
// Details im Beurteilungskriterium A11 – «Abbildung der
// Projektaufbauorganisation»
//
//
// A11: Welche Informationen zur Projektaufbauorganisation sind verlangt?
//
// 1. Die Projektaufbauorganisation ist Bestandteil von Teil 1 des IPA-Berichts.
// 2. Die zur Projektmethode passenden Rollen wurden identifiziert.
// 3. Die Rollen wurden prägnant und verständlich beschrieben.
// 4. Die Abhängigkeit der Rollen zueinander wurde korrekt dargestellt oder beschrieben.
= Projektaufbauorganisation
#lorem(200)
// TODO: Beschreibung zu den Rollen
//
// #figure(
// image("../../02_Grafiken/projektorganisation.pdf", width: 80%),
// caption: [Projektorganisation],
// )
//
// == Projektrollen
// #figure(
// table(
// columns: (auto, 1fr),
// [Auftraggeber],
// [Der Auftraggeber ist hierarchisch gesehen die höchste rolle. Er hat das Projekt beauftragt und stellt die Anforderungen sowie Rahmenbedingungen.],
//
// [Projektausschuss],
// [Der Projektausschuss besteht aus der Veranwortlichen Fachkraft (VF). Er überwacht und hilft. Er bewertet die IPA zusammen mit den Experten.],
//
// [Qualität & Risikomanager],
// ["Der Qualitäts- und Risikomanager unterstützt den Auftraggeber mit einer unabhängigen Beurteilung des Projekts." @hermes-online Er bewertet ausserdem,
// die IPA zusammen mit der verantwortlichen Fachkraft.],
//
// [Projektleiter],
// [Der Projektleiter leitet das Projekt im Auftrag des Auftraggebers. Er koordiniert und verwaltet die Resoucen.],
//
// [Entwickler ],
// [
// "Die Rolle Entwickler ist umfassend und bezeichnet den Produktentwickler und den IT-Entwickler in einem. Der Entwickler realisiert das Produkt bzw. das System gemäss den Lösungsanforderungen und den vorangehenden Konzepten. [...]" @hermes-online
// ],
// ),
// caption: [Projektrollen],
// )
//
// == Personen
//
// Aus Platzgründen werden die Personennamen teilweise mit folgenden Kürzeln abgekürzt. Falls
// Kontakt zu einem IPA-Teilnehmenden aufgenommen werden möchte, können die Daten aus
// @fig-kontakt entnommen werden.
//
// #context [
// #figure(
// table(
// columns: (auto, auto, 1fr),
// ..persons.overview(),
// ),
// caption: [Namenskürzel & Kontaktangabe],
// )<fig-kontakt> ]
@@ -0,0 +1,30 @@
// IPA-Dokumentenvorgabe BiVo2021-2:
//
// Der zeitliche Ablauf der IPA. Siehe Details im Beurteilungskriterium A4-
// «Zeitplan»
//
// A4: Was sind die Anforderungen an den Zeitplan?
// - 1 Der Zeitplan ist Bestandteil von Teil 1 des IPA-Berichts.
// - 2 Der Zeitplan ist übersichtlich gestaltet.
// - 3 Struktur und Elemente des Zeitplans orientieren sich nach der gewählten
// Projektmethode.
// - 4 Es wurde eine Zeitachse definiert (Datum), die Zeitachse weist eine vernünftige
// Granularität auf (bspw. Stundenblöcke).
// - 5 Die identifizierten Aktivitäten sind zweckmässig und folgen einer sinnvollen Logik.
// - 6 Die IPA-Zeitvorgabe ist im Zeitplan korrekt berücksichtigt.
#page(
paper: "a3",
margin: 2cm,
flipped: true,
{
[
= Zeitplan <chap-zeitplan>
]
set block(below: 0em)
figure(
image("../../03_Zeitplan/Zeitplan.pdf", height: 93%, width: 100%, fit: "contain"),
caption: [Zeitplan],
)
},
)
@@ -0,0 +1,73 @@
#import "@local/chfoit-ipa:0.0.0": arbeitsjournal
// IPA-Dokumentenvorgabe BiVo2021-2:
//
// Referenz zum Zeitplan und Ablauf. Beinhaltet Hinweise zu eventuellen
// Herausforderungen innerhalb des Ablaufs.
// Auflistung oder Beschreibung (ca. ½ Seite) aller Aktivitäten gemäss
// Zeitplan. Reflexion, Erkenntnisse und Massnahmen Best Practices ca. ½
// Seite.
// Doc7: Was ist beim Führen des Arbeitsjournals zu beachten?
// 1. Das Arbeitsjournal ist Bestandteil von Teil 1 des IPA-Berichts.
// 2. Die Darstellung ist übersichtlich, klar und verständlich.
// 3. Das Arbeitsjournal besteht aus individuellen Tagesberichten.
// 4. Alle Aktivitäten gemäss Zeitplan sowie Überzeiten und ungeplante Arbeiten sind
// erwähnt.
// 5. Erfolge und Misserfolge sind erwähnt. Misserfolge werden kritisch hinterfragt.
// 6. Sämtliche in Anspruch genommenen Unterstützungen, einschliesslich externer Hilfe
// und KI-Nutzung, sind umfassend aufgeführt und entsprechend begründet.
//
= Arbeitsjournale
// entry wrapper for default values
#let entry(
day: datetime,
supplementaryPersons: (:),
tagesablauf,
hilfestellungen,
gut,
schlecht,
erkenntnisse,
) = {
// if you have any persons who are always there at one specific task
// you can add them here.
let sp = ("Täglicher Austausch": ("VF",))
for (item, persons) in supplementaryPersons.pairs() {
sp.insert(item, persons)
}
arbeitsjournal.entry(
day: day,
defaultPerson: "MM", // that's you
supplementaryPersons: sp,
tagesablauf,
hilfestellungen,
gut,
schlecht,
erkenntnisse,
)
}
#entry(
day: datetime(year: 2026, month: 3, day: 1),
[
#lorem(100)
],
[
#lorem(50)
],
[
#lorem(50)
],
[
#lorem(50)
],
[
#lorem(50)
],
),
@@ -0,0 +1,11 @@
// IPA-Dokumentenvorgabe BiVo2021-2:
//
// Persönliches Fazit über die gesamte Arbeit. Siehe Details im
// Beurteilungskriterium Doc8 – «Persönliches Fazit»
= Persönliches Fazit
#lorem(500)
@@ -0,0 +1,11 @@
#include "01_Aufgabenstellung.typ"
#include "02_Standarts.typ"
#include "03_Schutzbedarfsanalyse.typ"
#include "04_OrgansiationErgebnisse.typ"
#include "05_ProjektVorgehen.typ"
#include "06_Projektorganisation.typ"
#include "07_Zeitplan.typ"
#include "08_Arbeitsjournale.typ"
#include "09_Fazit.typ"
@@ -0,0 +1,20 @@
// Doc6: Was sind die Anforderungen an eine Kurzfassung?
//
// 1. Die Kurzfassung ist Bestandteil von Teil 2 des IPA-Berichts.
// 2. Es werden die Kerninformationen wiedergegeben, weder mehr noch weniger.
// 3. Die Kurzfassung beschränkt sich auf eine A4-Seite und enthält keine Grafik.
// 4. Die Kurzfassung weist eine klare Struktur auf und beinhaltet 3 bis 4 Kapitel.
// 5. Die Ausrichtung auf die Zielgruppe ist gewährleistet.
// 6. Die Kurzfassung endet sinnvoll, bspw. mit einer Schlussfolgerung oder einer
// Empfehlung.
// 7. Die Kurzfassung ist objektiv und verzichtet auf die Schilderung persönlicher
// Erfahrungen.
= Kurzfassung
== Ausgangssituation
== Umsetzung
== Ergebnis
@@ -0,0 +1,37 @@
#import "@local/chfoit-ipa:0.0.0": acronym
#import "@preview/wrap-it:0.1.1": wrap-content
= Einführung
#lorem(200)
// Der folgende Teil klärt die Rahmenbedingungen der IPA wie die Einführung in die Firma, das Projekt und die Aufgabenstellung.
//
// == Die Firma
//
// Der Webauftritt des Bundesamtes für Informatik und Telekommunikation (#acronym("BIT", "Bundesamt für Informatik und Telekommunikation")) beschreibt das BIT Folgendermassen:
//
// #wrap-content(
// box(inset: (left: 1em, bottom: 1em), width: 19em, figure(
// image("../../02_Grafiken/placeholder.png"),
// caption: [Campus Eichenweg 1 & 3 in Zollikofen],
// )),
// align: top + right,
// )[
//
// #quote()[
// Das BIT ist der grösste IT-Leistungserbringer der Bundesverwaltung.
// Es stellt rund 50 000 Arbeitsplatzsysteme zur Verfügung, entwickelt zusammen mit den Verwaltungseinheiten massgeschneiderte,
// sichere sowie benutzerfreundliche IT-Lösungen und betreibt über 1000 Fachanwendungen mehrheitlich in eigenen, modernen Rechenzentren.
// ]@bit
// ]
//
// == Arbeitsbereich
//
// Beschreibung des eigenen Arbeitsbereichs, des Teams und der bisherigen
// Tätigkeiten im Betrieb.
//
// == Projekt
//
// Kurzbeschreibung des Projekts und Verweis auf die Aufgabenstellung.
// Weitere Informationen können der Aufgabenstellung entnommen werden.
@@ -0,0 +1,4 @@
== Studie IST- und SOLL-Zustand
=== IST-Zustand
=== SOLL-Zustand
@@ -0,0 +1,62 @@
== Ziele <chap-ziele>
#lorem(200)
// Dieses Kapitel beschreibt die Projektvorgehensziele sowie die Produktziele die Erfüllt werden müssen, damit das Projekt erfolgreich ist.
//
// === Produktziele
// // Beispiele von Kategorien:
// // • Finanzziele, Kosten/Nutzen/Wirtschaftlichkeit
// // • Leistung, Effizienz, Durchlaufzeit
// // • Qualität, Fehlerreduktion
// // • Kunden-/ Anwenderzufriedenheit, Service Public
// // • Sicherheit, Zuverlässigkeit
// // • Rechtliche Konformität
// // • Flexibilität
// // • Ausbaubarkeit
// // • Wartbarkeit
// // • Nachhaltigkeit
//
// #figure(
// table(
// columns: (auto, 1fr, 1fr, auto),
// [Nr.], [Ziel ], [Messgrösse], [Gewicht \ (M/S/K) #footnote[M = Muss, S = Soll, K = Kann]],
//
// [1], [Bsp.], [ Bsp.], [M],
// ),
// caption: [Projektvorgehensziele],
// )
//
// === Projektvorgehensziele
// // Beispiele von Kategorien:
// // • Projektkosten (Budget)
// // • Termine
// // • Meilensteine
// // • Methodik
// // • Kommunikation
// #figure(
// table(
// columns: (auto, 1fr, 1fr, auto),
// [Nr.], [Projektziel], [Messgrösse], [Gewicht \ (M/S/K)],
//
// //--- Projektkosten ---
// [1],
// [Das Projekt bleibt innerhalb des vorgegebenen Zeitbudgets der Probe-IPA.],
// [Es werden maximal 90h für das Projekt aufgewendet.],
// [M],
//
// // --- Meilensteine ---
// [2],
// [Alle definierten Meilensteine werden Eingehalten.],
// [Die Meilensteine (Beschrieben in @chap-zeitplan) werden erreicht.],
// [M],
//
// // --- Methodik ---
// [3],
// [Das Projekt wird nach der Projektdurführungsmethode in @chap-projektmethode durchgeführt.],
// [Das Projekt wird nach der Projektdurführungsmethode in @chap-projektmethode durchgeführt.],
// [S],
// ),
// caption: [Projektvorgehensziele],
// )
@@ -0,0 +1,21 @@
#import "@local/chfoit-ipa:0.0.0": requirements
== Anforderungen
#lorem(200)
// #figure(
// table(
// columns: (2.5em, 1fr, 2.5em, 1fr, auto, auto),
// [ID],
// [Anforderung],
// [Art #footnote[Art = Anforderungsart: F = Funktional, Q = Qualität]],
// [Abnahmekriterium],
// [W#footnote[ Wichtigkeit: 5 = muss zwingend umgesetzt werden; 4 = sehr wichtig, 3 = wichtig, 2 = normal, 1 = nicht wichtig]],
// [D#footnote[ Dringlichkeit: 5 = muss sofort umgesetzt werden, 4 = sehr dringend, 3 = dringend, 2 = normal, 1 = nicht dringend]],
//
// requirements.id(), [Titel], [F], [Beschreibung], [5], [5],
// ),
//
// caption: [Anforderungen],
// )
@@ -0,0 +1,57 @@
== Lösungsvarianten
#lorem(200)
// In diesem Kapitel werden zwei Varianten zur Umsetzung der Applikation
// analysiert und bewertet. Anschliessend wird mit Hilfe dieser Informationen
// die beste Variante für die Umsetzung gewählt.
//
// === Variante 1: <variante-1>
//
// Beschreibung der ersten Lösungsvariante.
//
// === Variante 2: <variante-2>
//
// Beschreibung der zweiten Lösungsvariante.
//
// === Bewertung der Varianten
//
// Beim Bewerten der Varianten werden folgende Kriterien beachtet:
//
// #figure(
// table(
// columns: (auto, 1fr, auto),
// [Name], [Begründung], [Gewichtung],
// [Kriterium 1], [Begründung für das Kriterium.], [1],
// [Kriterium 2], [Begründung für das Kriterium.], [1],
// [Kriterium 3], [Begründung für das Kriterium.], [1],
// ),
// caption: [Übersicht der Bewertungskriterien für den Variantenentscheid],
// )
//
// ==== Variante 1
// #figure(
// table(
// columns: (auto, auto, auto, 1fr),
// [Kriterium], [Bewertung \ x/10], [Gewich-\ tung], [Begründung],
// [Kriterium 1], [8], [1], [Begründung der Bewertung.],
// table.footer([Summe:], [8]),
// ),
// caption: [Auswertung der Variante 1],
// )
//
// ==== Variante 2
// #figure(
// table(
// columns: (auto, auto, auto, 1fr),
// [Kriterium], [Bewertung \ x/10], [Gewich-\ tung], [Begründung],
// [Kriterium 1], [7], [1], [Begründung der Bewertung.],
// table.footer([Summe:], [7]),
// ),
// caption: [Auswertung der Variante 2],
// )
//
// === Variantenentscheid <variantenentscheid>
//
// Die Applikation wird mit #link(<variante-1>, [Variante 1]) umgesetzt,
// da die Bewertung im Vergleich zu #link(<variante-2>, [Variante 2]) höher ausfällt.
@@ -0,0 +1,78 @@
== Schutzbedarfsanalyse
//
// Die Schutzbedarfsanalyse dient dazu,
// den notwendigen Umfang an Sicherheitsmassnahmen zu bestimmen.
// Sie stellt sicher, dass die Vertraulichkeit, Integrität und Verfügbarkeit der Applikationen und der Daten gewährleistet werden.
// Sie basiert auf der "IKT Beurteilung des Schutzbedarfs". @schutzbedarf
//
// Anfang 2025 wurde eine neue Version (5.1) dieser Beurteilung erstellt.
// Diese ist in den zehn verfügbaren Tagen der IPA zu umfangreich.
// Deshalb kann für diese IPA die alte Version (4.6) der Schutzbedarfsanalyse verwendet werden. @alte-schutzbedarfsanalyse
//
// === Einstufung
//
// Wird in der Einstufung erhöter Schutzbedarf festgestellt, so muss ein ISDS-Konzept erarbeitet werden.
// Erhöter Schutzbedarf liegt vor, sobald eines der Felder
// aus dem Bereich der Vertraulichkeit als #text(fill: red, "rot") gekennzeichnet wird
// oder wenn mehr als zwei Kriterien in den Bereichen Verfügbarkeit, Integrität oder Nachvollziehbarkeit als #text(fill: red, "rot") gekennzeichnet werden.
//
// Folgende Tabelle ist ein Auszug aus der Einstufung mithilfe des vorgegebenen Dokuments.
// Die gesamte Einstufung ist im Anhang ersichtlich.
//
// #let good(c) = { table.cell(fill: green.lighten(70%), c) }
// #let bad(c) = { table.cell(fill: red.lighten(70%), c) }
//
// #figure(
// table(
// columns: 3,
// table.header([Kategorie], [Frage], [Antwort]),
//
// table.cell(rowspan: 3, [Vertraulichkeit]),
// [Sollen [mit diesem Schutzobjekt] Personendaten nach der Datenschutzgesetzgebung bearbeitet werden? Wenn ja, ergibt sich daraus ein hohes Risiko für die Grundrechte der betroffenen Personen?],
// good([Keine Personendaten]),
//
// // [Vertraulichkeit],
// [Sollen [mit diesem Schutzobjekt] klassifizierte Informationen nach der Informationsschutzverordnung (ISchV) bearbeitet werden? Wenn ja, Informationen aus welchen Klassifizierungsstufen (vgl. Art. 5 bis 7 ISchV) sind betroffen?],
// good([Klassifizierung: Intern]),
//
// // [Vertraulichkeit],
// [Sollen [mit diesem Schutzobjekt] Informationen oder Daten bearbeitet werden, die aus einem sonstigen Grund (spezielle Gesetzgebungen) besonders geschützt werden müssen? Wenn ja, wie hoch sind die Schutzanforderungen?],
// good([Keine erhöhten Anforderungen an die Vertraulichkeit]),
//
// table.cell(rowspan: 3, [Verfügbarkeit]),
// [Max. zulässige Ausfalldauer?],
// good([Ausfalldauer max. 8 Std.]),
//
// // [Verfügbarkeit],
// [Servicezeiten?],
// good([Servicezeiten Standard (11/5)]),
//
// // [Verfügbarkeit],
// [IT Service Continuity Management (ITSCM) relevant [für dieses Schutzobjekt] als Teil des Business Continuity Management (BCM) für geschäftskritische Prozesse?],
// good([ITSCM / BCM nicht notwendig]),
//
// table.cell(rowspan: 2, [Integrität]),
// [Muss die Echtheit, Korrektheit und/oder Unversehrtheit der Daten nachgewiesen werden können?],
// good([Keine speziellen Anforderungen]),
//
// // [Integrität],
// [Müssen bestimmte Arbeitsvorgänge nachgewiesen werden können?],
// good([Keine speziellen Anforderungen]),
//
// [RINA-Relevanz],
// [Ist dieses Schutzobjekt durch nachrichtendienstliche Ausspähung (oder ähnliche) erheblich gefährdet und/oder werden dafür sensitive Beschaffungen notwendig?],
// good([Nein - Nicht RINA-relevant]),
// ),
// caption: [Einstufung Schutzbedarf],
// )
//
// Die Kommentare zu den jeweiligen Kategorien sind im Anhang ersichtlich.
//
// === Entscheid ISDS-Konzept
//
// In der Einstufung wurden keine erhöhten Anforderungen festgestellt.
// Da alle Felder grün sind, ist das Ergebnis der Schutzbedarfsanalyse "kein erhöter Schutzbedarf notwendig" und es muss kein ISDS-Konzept erstellt werden.
//
// === Massnahmen
//
//
@@ -0,0 +1,58 @@
#import "@preview/wrap-it:0.1.1": wrap-content
#import "@local/chfoit-ipa:0.0.0": palette
#let risiko(lvl) = {
if lvl >= 3 {
return table.cell(fill: palette.red.at("300"))[Sehr Hoch]
}
if lvl >= 2 {
return table.cell(fill: palette.orange.at("300"))[Hoch]
}
if lvl >= 1 {
return table.cell(fill: palette.yellow.at("300"))[Mittel]
}
return table.cell(fill: palette.green.at("300"))[Tief]
}
== Risiko Analyse
#lorem(200)
// === Risiken
//
// Folgende Risiken sind in der Probe-IPA relevant:
// #figure(
// table(
// columns: (auto, 1fr, 1fr, auto, auto),
// [Nr.], [Beschreibung], [Auswirkung], [E #footnote[Eintretenswahrscheinlichkeit]], [A #footnote[Auswirkungsgrad]],
//
// // [1], [Ausfall vom Arbeitsgerät], [Entwicklung nicht mehr möglich], risiko(0), risiko(3),
// ),
// caption: [Risiken],
// )<fig-risks>
//
// Um diese Risiken zu mindern, werden Folgende Massnahmen ergriffen:
//
// #figure(
// table(
// columns: (auto, auto, 1fr, auto, auto),
// [Nr.], [Risiko Nr.], [Beschreibung], [E Danach], [A Danach],
//
// // [1], [1], [Ausweichen auf Warp-Devspaces], risiko(0), risiko(1),
// ),
// caption: [Massnahmen um Risiken zu mindern],
// )
// === Auswertung
//
// #wrap-content(
// figure(
// image("../../../02_Grafiken/risiko_matrix.pdf"),
// caption: [Risikomatrix],
// ),
// [
//
// ],
// align: top + right,
// column-gutter: 2em,
// )
@@ -0,0 +1,13 @@
#pagebreak(weak: true)
== Projektauftrag
// Bei justin steht:
Basierend auf den Erkenntnissen der Studie und den Variantenentscheiden wird das Projekt in
die nächste Phase übergeben.
_Das Projekt wurde am \<datum> durch den Auftraggeber \<Auftraggeber> mündlich
freigegeben._
@@ -0,0 +1,44 @@
// Auf der Grundlage des Projektinitialisierungsauftrags gibt der Auftraggeber die Ressourcen für die Phase Initialisierung frei.
// Er beauftragt den Projektleiter mit der Durchführung der Phase Initialisierung.
//
// Die Phase Initialisierung wird aus Projektmanagementsicht stets klassisch abgewickelt.
// Dennoch können auch agile Werkzeuge zum Einsatz kommen.
//
// Die Studie wird erarbeitet.
// - Die Arbeiten starten mit einer ersten Standortbestimmung, Zielen und groben Anforderungen.
// - Die Lösungsvarianten werden erarbeitet. Die Beschreibung der Lösungsvarianten erfolgt so detailliert,
// dass sie nachvollziehbar und transparent bewertet werden können.
// - Unter anderem werden auch die Projekt- und Betriebsrisiken ermittelt.
// - Parallel zur Studie werden die Rechtsgrundlagenanalyse und die Schutzbedarfsanalyse
// erarbeitet und in die Entscheidung einbezogen.
// - Zusätzlich wird festgelegt und nachvollziehbar dokumentiert, wie im Rahmen jeder Lösungsvariante
// weiter vorgegangen wird: entweder klassisch oder agil.
// - Der Entscheid Weiteres Vorgehen wird getroffen.
//
// Für die allfällige Beschaffung eines Produkts bzw. eines Systems wird parallel
// zur Studie eine Beschaffungsanalyse durchgeführt.
//
// Das für die Lösungsentstehung passende Szenario wird ausgewählt und bei Bedarf individualisiert.
//
// Auf der Basis der gewählten Variante und des Vorgehens werden der Projektmanagementplan
// und der Durchführungsauftrag erarbeitet und mit den Strategien, Vorgaben und übergeordneten
// Zielen der Stammorganisation abgeglichen. Die Stakeholderinteressen werden
// analysiert und Zielkonflikte bereinigt.
//
// Bei anvisierter agiler Vorgehensweise wird festgelegt, ob der optionale Entscheid
// Releasefreigabe treffen im Projekt als erforderlich vorgeschrieben ist.
//
// Der Entscheid Durchführungsfreigabe wird getroffen und der Durchführungsauftrag unterschrieben.
// Die Freigabe erfolgt durch die Stammorganisation und den Auftraggeber.
= Initialisierung
In diesem Kapitel sind die Arbeitsergebnisse der Initialisierungsphase.
#include "./01_Studie.typ"
#include "./02_Ziele.typ"
#include "./03_Anforderungen.typ"
#include "./04_Varianten.typ"
#include "./05_Schutzbedarfsanalyse.typ"
#include "./06_Risikoanalyse.typ"
#include "./07_Projektauftrag.typ"
@@ -0,0 +1,23 @@
#import "@local/chfoit-ipa:0.0.0": usecase
== Anwendungsfälle
// Zweck: Er dient dazu, funktionale Anforderungen aus der Perspektive des Nutzers zu definieren und sicherzustellen,
// dass das System die Erwartungen der Stakeholder erfült.
// Bestandteile: Ein Anwendungsfall besteht in der Regel aus einer:
// - Spezifikation (textliche Beschreibung der Schritte)
// - und einem Diagramm (grafische Übersicht der Akteure und Beziehungen).
// Abgrenzung: Im Gegensatz zu einer User Story, die nur kurz den Nutzerwunsch beschreibt,
// geht ein Use Case detailliert auf Abläufe, Vorbedingungen und Fehlerfälle ein.
#usecase(
name: "Beispiel",
actors: ("a", "b", "c"),
diagramm: [basdhf dsfsadfh],
precondition: [bsp.],
process: [
bsp.
],
acceptanceCriteria: (
"bla bla bla",
),
)
@@ -0,0 +1,11 @@
Die Benutzerschnittstelle besteht aus den folgenden Seiten:
// This template can be used to generate new Mockups.
= Template
Das Template kann benützt werden um die wichtigsten Elemente bereits zur Verfügung zu haben.
#figure(
image("../../../02_Grafiken/mockup_template.pdf"),
caption: [Template für Mockups],
)
@@ -0,0 +1,54 @@
#import "@preview/wrap-it:0.1.1": wrap-content
#import "@local/chfoit-ipa:0.0.0": acronym, term
#pagebreak(weak: true)
== Anwendungsarchitektur
#lorem(200)
// #figure(
// image("../../../02_Grafiken/placeholder.png", height: 25em),
// caption: [Architekturskizze],
// )<fig-architekturskizze>
//
// === Technologien
//
// ==== Frontend
// #wrap-content(
// figure(
// image("../../../02_Grafiken/frontend_logo.png", height: 4em),
// caption: [Frontend-Technologie],
// ),
// [
// // Beschreibung der Frontend-Technologien.
// ],
// align: top + right,
// column-gutter: 2em,
// )
//
// ==== Backend
// #wrap-content(
// figure(
// image("../../../02_Grafiken/backend_logo.png", height: 4em),
// caption: [Backend-Technologie],
// ),
// [
// // Beschreibung der Backend-Technologien.
// ],
// align: top + right,
// column-gutter: 2em,
// )
//
// === Datenbank
//
// #wrap-content(
// figure(
// image("../../../02_Grafiken/db_logo.png", height: 4em),
// caption: [Datenbank-Technologie],
// ),
// [
// // Beschreibung der Datenbank-Technologien.
// ],
// align: top + right,
// column-gutter: 2em,
// )
@@ -0,0 +1,11 @@
== Rollenkonzept
#figure(
image("../../../02_Grafiken/placeholder.png", width: 80%),
caption: [Rollen- und Zugriffdiagram],
) <fig-rolle_diagram>
@@ -0,0 +1,8 @@
== Daten Schema
#lorem(200)
// #figure(
// image("../../../02_Grafiken/placeholder.png", width: 80%),
// caption: [Datenbankdiagramm],
// )<fig-datenbankdiagramm>
@@ -0,0 +1,8 @@
#import "@local/chfoit-ipa:0.0.0": oasviz
== API Spezifikation
#{
set heading(offset: 2)
oasviz.render(yaml("swagger.yaml"))
}
@@ -0,0 +1,116 @@
#import "@local/chfoit-ipa:0.0.0": term, test
== Testkonzept
#lorem(200)
// Das Testkonzept beschreibt die Testziele, Testobjekte, Testarten, Testinfrastruktur sowie Testorganisation. Es umfasst ebenfalls die Testplanung und die Testfallbeschreibungen. Für jeden Testfall wird eine detaillierte Testfallbeschreibung erstellt. Diese stellt die Spezifikati-on des Tests dar. Die Testplanung legt den logischen und zeitlichen Ablauf der Tests fest. Das Testkonzept bildet die Grundlage, auf der die Testorganisation und die Testinfrastruktur bereitgestellt und die Tests durchgeführt werden. Tauchen neue Erkenntnisse auf, wird das Testkonzept nachgeführt. @hermes-online
//
// === Testziele
// #figure(
// table(
// columns: (auto, 1fr, 1fr, auto),
// [Nr.], [Beschreibung], [Messgrösse], [Priorität #footnote[Priorität: M = Muss / 1 = hoch, 2 = mittel, 3 = tief]],
// [01], [Benutzeroberflächentest Erfolgreich], [Anwendungsfälle reproduzierbar], [M],
// [02], [Businesslogiktest Erfolgreich], [Datenprüfung korrekt], [1],
// ),
// )
//
//
// === Teststrategien und Teststufen
//
// Im rahmen der Probe-IPA werden hauptsächlich
// #term("Unit-Test", "Tests die als Code geschrieben werden und es zum Ziel haben einen einzelnen Komponenten (Unit) zu testen.")s und
// #term("Akzeptanz-Test", "Testverfahren mit dem Hauptzweck die Funktionalität einer Software aus der Sicht des Endbenutzers zu überprüfen.")s
// durchgeführt.
//
// Die Unit-Tests haben zum ziel die einzelnen Komponente zu testen. Dabei wird eine Fokus auf die Geschäftslogik gelegt.
// Im Frontend sowie im Backend #term("Backend", "Die Komponente der Applikation, die auf dem Server läuft und dem Frontend die Daten zur verfügung stellt und die eingaben des Frontends prüft.")
// ist die #term("Testabdeckung", "Verhältniss von Codedezeilen die existieren zu Codedezeilen die wärend dem Testen durchlaufen wurden.") mindestens *60%*.
//
// Die Akzeptanz-Tests haben zum Ziel, die gesamte Applikation zu testen. Sie werden am ende der Umsetzungsphase manuell getestet um diese abzuschliessen.
// Um die Umsetzungsphase abzuschliessen müssen alle Akzeptanz-Test funktionieren.
//
// === Mängelklassifizierung
//
// Die festgestellten Mängel, bzw. die nicht erfüllten Anforderungen (Erwartungen), werden in Klassen von 1 bis 4 eingestuft.
// Die Klasse 0 findet nur dann Verwendung, wenn ein einwand-freies Ergebnis gesondert ausgewiesen werden soll:
//
// #figure(
// table(
// columns: (auto, auto, 1fr),
// [Nr.], [Mängelklassen], [Beschreibung],
// text(fill: green)[0],
// text(fill: green)[mängelfrei],
// [Einwandfrei und
// anforderungsgerecht],
//
// [1],
// [belangloser Mangel],
// [Verwendung möglich, Brauchbarkeit
// ist vorhanden, Mängel sollte dennoch nicht vorkommen],
//
// [2],
// [leichter Mangel],
// [Verwendung möglich, Brauchbarkeit
// ist nur wenig beeinträchtigt],
//
// [3],
// [schwerer Mangel],
// [Verwendung ist noch möglich,
// Brauchbarkeit ist stark verringert],
//
// text(fill: red)[4],
// text(fill: red)[kritischer Mangel],
// [
// Unbrauchbar; \
//
// Wesentliche Funktionalität ist nicht gegeben; \
//
// Betrieb ist nicht verantwortbar (z.B. sicherheitsspezifisch)
// ],
// ),
// caption: [Mängelklassen],
// )
//
// Die Klassifizierung spiegelt die Folgenschwere und den Aufwand zur Behebung der möglich feststellbaren Mängel. Die Zuordnung der festgestellten Mängel zu einer Mängelklasse gibt
// grob auch die Priorität vor, in welcher Reihenfolge die Behebung der Mängel angegangen werden soll.
//
// Wird eine Mängelklasse zwischen 1-3 erreicht, kann das System/Produkt unter Vorbehalt abgenommen werden. Zur Behebung der Mängel sind jedoch Massnahmen zu definieren. Eine Nachprüfung ist zwingend.
//
// Werden hingegen Mängel der Klasse 4 festgestellt, kann das System/Produkt nicht abge-nommen werden und der Auftragnehmer muss umgehend Massnahmen treffen, um diese Mängel zu beheben.
// Der Auftragnehmer hat zudem die erneute Abnahme zu veranlassen.
// @hermes-online
//
//
=== Testfälle
Die Testfälle sind auf Grund der Anwendungsfälle beschrieben. Es wird vorausgesetzt, dass jeweils die Passenden Daten schon vorliegen.
Die hier aufgeführten Testfälle begrenzen sich auf die Akzeptanz-Tests. Die unit tests werden im code definiert.
#test.case(
requirement: [Usecase 1],
description: [Foo Bar],
precondition: [
- bla bla
],
steps: [
],
result: [
],
)
// === Beurteilung zur Testabdeckung
//
// Die Testabdeckung bei den Unit-Tests ist gut angesetzt. 60% ist viel und trotz dem realistisch.
// Hingegen die Akzeptanz-Tests haben nur Testfälle zum "Happy Case". Das währe in einer Grösseren und Kritischeren
// Applikation sicherlich ungünstig. Im Rahmen der Probe-IPA ist dies aber in Ordnung, da auch auf die Zeitlichen Ressourcen geachtet
// werden muss. Mehr Akzeptanz-Tests würden den Zeitrahmen sprengen.
//
// === Testorganisation und Zuständigkeiten
//
// Die Tests finden auf dem Entwickler gerät statt jeweils auf dem sauberen #footnote[sauber im sinne von keine Änderung seit dem letzten commit] Entwicklungsbranch statt.
//
// Die tests werden vom Kandidaten selbst durchgeführt und Protokolliert.
@@ -0,0 +1,10 @@
== Phasenfreigabe
// Bei justin steht:
//
Basierend auf den Erkenntnissen wird das Projekt in
die nächste Phase übergeben.
_Die Phase wurde am \<datum> durch den Auftraggeber \<name> mündlich
freigegeben._
@@ -0,0 +1,37 @@
// Die in der Phase Initialisierung gewählte Variante wird konkretisiert. Die Ergebnisse werden so detailliert
// erarbeitet, dass die Projektbeteiligten die Lösung auf einer verlässlichen Grundlage
// planen, offerieren und realisieren können.
= Konzept
// In diesem Kapitel, wird die in der Initialisierung gewählte Variante konkretisiert.
// Die Ergebnisse werden so detailliert erarbeitet, dass alle Projektbeteiligten die
// Lösung auf einer verlässlichen Grundlage planen, offerieren und realisieren können.
//
// Hierzu werden zunächst die Use Cases und Mockups zur Definition der Anforderungen erarbeitet.
// Darauf aufbauend werden die technische Architektur, das Rollenkonzept sowie das Datenschema und die API-Spezifikationen festgelegt.
// Ausserdem, wird ein Testkonzept zur Sicherstellung der einhaltung des eben genannten erarbeitet.
#include "./01_Usecases.typ"
#pagebreak(weak: true)
#include "./02_Mockups.typ"
#pagebreak(weak: true)
#include "./03_Architektur.typ"
#pagebreak(weak: true)
#include "./04_Rollenkonzept.typ"
#pagebreak(weak: true)
#include "./05_Datenschema.typ"
#pagebreak(weak: true)
#include "./06_APISpec.typ"
#pagebreak(weak: true)
#include "./07_Testkonzept.typ"
#pagebreak(weak: true)
#include "./08_Phasenfreigabe.typ"
@@ -0,0 +1,630 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/OAI/OpenAPI-Specification/refs/heads/main/_archive_/schemas/v3.0/schema.yaml
openapi: 3.0.0
info:
title: Example App Backend API
version: "1.0.0"
description: API for the Example App backend service.
servers:
- url: http://localhost:8080
description: Local development server
components:
securitySchemes:
cookieAuth:
type: apiKey
in: cookie
name: auth
description: Cookie-based authentication
schemas:
Error:
type: object
properties:
type:
type: string
description: Categorization of the error for programmatic handling.
enum:
- AUTHENTICATION_ERROR
- AUTHORIZATION_ERROR
- VALIDATION_ERROR
- BAD_REQUEST_ERROR
- NOT_FOUND_ERROR
- SERVER_ERROR
- UNKNOWN_ERROR
example: AUTHENTICATION_ERROR
key:
description: A unique error code or identifier for specific error cases.
type: string
maxLength: 200
example: example key
message:
type: string
description: A human-readable message explaining the error.
maxLength: 200
example: example message
details:
type: object
description: Additional context or metadata regarding the error (e.g.,
specific field validation failures).
x-go-type-skip-optional-pointer: true
x-go-type: any
additionalProperties: true
example: {}
required:
- type
- key
- message
description: Schema representing Error.
example: {}
UUID:
type: string
format: uuid
x-go-type: uuid.UUID
x-go-type-import:
path: github.com/google/uuid
name: uuid
description: Schema representing UUID.
example: example
User:
type: object
properties:
id:
description: Id of the user.
allOf:
- $ref: "#/components/schemas/UUID"
name:
type: string
description: Full name or display name of the user.
example: "Example User"
required:
- id
- name
description: Schema representing User.
example: {}
Claims:
type: object
properties:
iss:
type: string
description: The issuer of the token.
example: "example-app-backend"
sub:
description: The subject of the token (User ID).
allOf:
- $ref: "#/components/schemas/UUID"
iat:
type: integer
format: int64
description: Issued At timestamp (Unix epoch).
example: 1
exp:
type: integer
format: int64
description: Expiration timestamp (Unix epoch).
example: 1
required:
- iss
- sub
- iat
- exp
description: Schema representing Claims.
example: {}
Scan:
type: object
properties:
isbn:
type: string
x-go-name: ISBN
description: The International Standard Book Number.
example: 978-3-16-148410-0
required:
- isbn
description: Schema representing Scan.
example: {}
Book:
type: object
properties:
id:
type: integer
description: User facing id of the book.
example: 1
title:
type: string
minLength: 1
maxLength: 200
description: The title of the book.
example: example title
authors:
type: string
minLength: 1
maxLength: 500
description: A comma-separated list or string of book authors.
example: example authors
description:
type: string
minLength: 0
maxLength: 2048
description: A brief summary or synopsis of the book.
example: example description
lentBy:
description: The user who has lent the book from the library.
Undefined if the boook is not lent.
allOf:
- $ref: "#/components/schemas/User"
coverUrl:
type: string
format: url
description: Link to the book's cover image.
example: example cover_url
required:
- id
- title
- authors
description: Schema representing Book.
example: {}
BookDetails:
allOf:
- $ref: "#/components/schemas/Book"
- type: object
properties:
isbn:
type: string
x-go-name: ISBN
minLength: 9
maxLength: 20
description: The ISBN of the book.
example: example isbn
publisher:
type: string
minLength: 1
maxLength: 200
description: The entity that published the book.
example: example publisher
publishedAt:
type: integer
multipleOf: 1
minimum: 0
description: The publication year or timestamp.
example: 1
owner:
description: The user who owns the bool. Undefined if the boook is
public.
allOf:
- $ref: "#/components/schemas/User"
required:
- isbn
- publisher
- publishedAt
example: {}
description: Schema representing BookDetails.
BookRequest:
type: object
properties:
title:
type: string
minLength: 1
maxLength: 200
description: "The title of the book. (ctx: schema.BookRequest.properties.title)"
example: example title
authors:
type: string
minLength: 1
maxLength: 500
description: "A comma-separated list or string of book authors. (ctx: schema.BookRequest.properties.authors)"
example: example authors
isbn:
type: string
x-go-name: ISBN
minLength: 9
maxLength: 20
description: "The ISBN of the book. (ctx: schema.BookRequest.properties.isbn)"
example: example isbn
description:
type: string
minLength: 0
maxLength: 2048
description: "A brief summary or synopsis of the book. (ctx: schema.BookRequest.properties.description)"
example: example description
publisher:
type: string
minLength: 1
maxLength: 200
description: "The entity that published the book. (ctx: schema.BookRequest.properties.publisher)"
example: example publisher
publishedAt:
type: integer
multipleOf: 1
minimum: 0
description: "The publication year or timestamp. (ctx: schema.BookRequest.properties.publishedAt)"
example: 1
public:
type: boolean
description: Whether the book is publicly owened or owned by the
creating user.
example: true
coverUrl:
type: string
format: url
minLength: 0
maxLength: 1024
description: "Link to the book's cover image. (ctx: schema.BookRequest.properties.coverUrl)"
example: example cover_url
required:
- title
- authors
- isbn
- publisher
- publishedAt
- public
description: Schema representing BookRequest.
example: {}
Lending:
type: object
properties:
user:
description: The uuid of the user, who has lent the book
allOf:
- $ref: "#/components/schemas/UUID"
startedAt:
type: string
format: date-time
description: The ID of the user borrowing the book.
example: example startedAt
returnedAt:
type: string
format: date-time
description: Timestamp when the book was returned. Undefined if still
out.
example: example returnedAt
required:
- user
- startedAt
description: Schema representing Lending.
example: {}
responses:
Error400:
description: Bad request
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
Error401:
description: Unauthorized – missing or invalid auth cookie
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
Error403:
description: Forbidden – this is probably not your book
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
Error404:
description: Not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
Error500:
description: Server error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
security:
- cookieAuth: []
paths:
/auth/login:
post:
summary: Login a user and receive an authentication cookie
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UUID"
description: Request body for POST /auth/login
responses:
"200":
description: Successful login, authentication cookie set
headers:
Set-Cookie:
schema:
type: string
example: auth=eyJhbGci...; Path=/; HttpOnly
content:
application/json:
schema:
$ref: "#/components/schemas/Claims"
"400":
description: Invalid request payload
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"401":
description: Authentication failed
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"500":
$ref: "#/components/responses/Error500"
security: []
tags:
- Auth
operationId: post_auth_login
description: Log in a user and receive an authentication cookie
/auth/logout:
post:
summary: Logout the current user
responses:
"204":
description: Successful logout and the cookie was removed
headers:
Set-Cookie:
description: >
Sets the auth cookie to an empty value and expires it immediately by
setting Max-Age to 0. The token gets invalidated.
schema:
type: string
example: auth=; Path=/; HttpOnly; Max-Age=0
"401":
description: Authentication failed
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"500":
$ref: "#/components/responses/Error500"
tags:
- Auth
operationId: post_auth_logout
description: Logout the current user
/auth:
get:
summary: Retrieve the claims for the currently authenticated user
responses:
"200":
description: Claims of the authenticated user
content:
application/json:
schema:
$ref: "#/components/schemas/Claims"
"401":
$ref: "#/components/responses/Error401"
"500":
$ref: "#/components/responses/Error500"
security: []
tags:
- Auth
operationId: get_auth
description: Retrieve the claims for the currently authenticated user
/auth/users:
get:
summary: List all users
responses:
"200":
description: Array of user objects
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/User"
example: []
"500":
$ref: "#/components/responses/Error500"
security: []
tags:
- Auth
operationId: get_auth_users
description: List all users
/books:
get:
summary: Get all books
responses:
"200":
description: Array of books
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Book"
example: []
"500":
$ref: "#/components/responses/Error500"
security: [] #no security needed
tags:
- Books
operationId: get_books
description: Get all books
post:
summary: Register a new book
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/BookRequest"
description: Request body for POST /books
responses:
"200":
description: The new book
content:
application/json:
schema:
$ref: "#/components/schemas/Book"
"400":
$ref: "#/components/responses/Error400"
"401":
$ref: "#/components/responses/Error401"
"500":
$ref: "#/components/responses/Error500"
tags:
- Books
operationId: post_books
description: Register a new book
/books/{bookID}:
parameters:
- name: bookID
in: path
required: true
schema:
type: integer
minimum: 0
description: Parameter bookID for /books/{bookID}
get:
summary: Get the details of a book
responses:
"200":
description: BookDetails
content:
application/json:
schema:
$ref: "#/components/schemas/BookDetails"
"404":
$ref: "#/components/responses/Error404"
"500":
$ref: "#/components/responses/Error500"
security: [] #no security needed
tags:
- Books
operationId: get_books_bookID
description: Get the details of a book
put:
summary: Update a book
description: |
The field lentBy will be ignored. If you want to lend a book, please use the corresponding route.
The field owner will only be obeyed if you own the book and set it to null / undefined.
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/BookRequest"
description: Request body for PUT /books/{bookID}
responses:
"200":
description: The new book
content:
application/json:
schema:
$ref: "#/components/schemas/BookDetails"
"400":
$ref: "#/components/responses/Error400"
"401":
$ref: "#/components/responses/Error401"
"403":
$ref: "#/components/responses/Error403"
"404":
$ref: "#/components/responses/Error404"
"500":
$ref: "#/components/responses/Error500"
tags:
- Books
operationId: put_books_bookID
delete:
summary: Delete a book
responses:
"204":
description: Book deleted successfully
"403":
$ref: "#/components/responses/Error403"
"404":
$ref: "#/components/responses/Error404"
"500":
$ref: "#/components/responses/Error500"
tags:
- Books
operationId: delete_books_bookID
description: Delete a book
/books/{bookID}/lending:
parameters:
- name: bookID
in: path
required: true
schema:
type: integer
minimum: 0
description: Parameter bookID for /books/{bookID}/lending
post:
summary: Lend a book
responses:
"204":
description: The book is successfully lent
"400":
description: Bad request - The book is most likely already lent
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"401":
$ref: "#/components/responses/Error401"
"404":
$ref: "#/components/responses/Error404"
"500":
$ref: "#/components/responses/Error500"
tags:
- Lending
operationId: post_books_bookID_lending
description: Lend a book
delete:
summary: End a lending (return a book)
responses:
"204":
description: The lending is successfully ended.
"401":
$ref: "#/components/responses/Error401"
"403":
description: Forbidden – you do not have an open lending
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"404":
$ref: "#/components/responses/Error404"
"500":
$ref: "#/components/responses/Error500"
tags:
- Lending
operationId: delete_books_bookID_lending
description: End a lending (return a book)
/lookup:
post:
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Scan"
description: Request body for POST /lookup
responses:
"200":
description: Looked up book
content:
application/json:
schema:
$ref: "#/components/schemas/BookRequest"
"401":
$ref: "#/components/responses/Error401"
"404":
$ref: "#/components/responses/Error404"
"500":
$ref: "#/components/responses/Error500"
tags:
- Lookup
operationId: post_lookup
description: POST operation for /lookup
@@ -0,0 +1,47 @@
#import "@preview/wrap-it:0.1.1": wrap-content
#import "@local/chfoit-ipa:0.0.0": term
#import "@preview/fletcher:0.5.1": diagram, edge, node
== Backend
#lorem(200)
// Beispiel:
//
//#wrap-content(
// box(inset: (right: 1em, bottom: -4em), width: 16em, figure(
// image("../../../02_Grafiken/placeholder.png"),
// caption: [Übersicht über die Backend-Verzeichnisstruktur],
// )),
// align: top + left,
// )[
// === Unterteilung
//
// Beschreibung der Verzeichnisstruktur und der Unterteilung des Backends.
// Die Struktur folgt den etablierten Konventionen des verwendeten
// Backend-Frameworks.
// ]
//
// === Konfiguration
//
// Das Backend kann über verschiedene Wege konfiguriert werden, z. B. über
// Umgebungsvariablen oder eine Konfigurationsdatei. Sensible Werte wie
// Zugangsdaten oder Secrets dürfen niemals im Quellcode oder in der
// Dokumentation abgelegt werden.
//
// ==== Umgebungsvariablen
//
// In @fig-env-config sind die verfügbaren Umgebungsvariablen aufgelistet.
//
// #figure(
// table(
// columns: (auto, 1fr, 1fr),
// [Name], [Standard Wert], [Beschreibung],
// [APP_PORT], [8080], [Port, auf dem das Backend läuft],
// [APP_LOG_LEVEL], [info], [DEBUG, INFO, WARN oder ERROR],
// [APP_DB_DSN],
// [host=localhost user=postgres password=password dbname=app port=5432 sslmode=disable],
// [Data Source Name / Connection string für die Datenbank],
// ),
// caption: [Übersicht der Umgebungsvariablen],
// )<fig-env-config>
@@ -0,0 +1,37 @@
#import "@preview/wrap-it:0.1.1": wrap-content
#import "@local/chfoit-ipa:0.0.0": acronym
#import "@preview/fletcher:0.5.1": diagram, edge, node
== Frontend
#lorem(200)
//
// #wrap-content(
// box(width: 20em, inset: (right: 1em, bottom: -4em), figure(
// image("../../../02_Grafiken/placeholder.png"),
// caption: [Übersicht über die Frontend-Verzeichnisstruktur],
// )),
//
// align: top + left,
// )[
// === Unterteilung
//
// Das Frontend ist in wiederverwendbare Komponenten, Seiten (Routen) und
// geteilte Services bzw. Interfaces unterteilt.
//
// ==== Komponenten
// Im Verzeichnis `components` befinden sich wiederverwendbare
// GUI-Komponenten.
//
// ==== Routen
// Im Verzeichnis `routes` sind die Seiten der Applikation. Sie sind in
// derselben hierarchischen Struktur wie die Routen der Applikation
// abgelegt, damit es übersichtlich bleibt.
//
// ==== Shared
// Im Verzeichnis `shared` befinden sich die über die Applikation geteilten
// Interfaces, Services und Interceptors.
// ]
@@ -0,0 +1,40 @@
#import "@local/chfoit-ipa:0.0.0": test
#let testProtocol = test.protocoll
== Tests
// Beispiel:
//
//In diesem Abschnitt werden die Resultate der durchgeführten Tests protokolliert.
//
// === Unittests Backend
//
// #figure(table(
// columns: (auto, auto, auto),
// [Status], [Paket], [Abdeckung],
// [ok], [backend/application], [93.6%],
// [ok], [backend/auth], [84.5%],
// [ok], [backend/user], [100.0%],
// ))
//
// Die totale Testabdeckung beträgt: 69.7%
//
// === Unittests Frontend
//
// #figure(table(
// columns: (auto, 1fr, auto),
// [Status], [Paket], [Abdeckung #footnote[ der Statements]],
// [ok], [src/app], [88.88%],
// [ok], [src/app/shared/services], [46.53%],
// [ok], [src/environments], [100%],
// ))
//
// Die totale Testabdeckung beträgt: 64.37%
//
// === Akzeptanztests
//
// #let test = testProtocol.with(tester: "Max Mustermann", date: datetime(day: 1, month: 3, year: 2026))
//
// #test(case: "TC-1")[
// Beschreibung des Akzeptanztestfalls.
// ]
@@ -0,0 +1,50 @@
= Projektschlussbeurteilung
// == Vergleich Ist/Soll
//
// === Anforderungen
//
// #let ok = table.cell(fill: green.lighten(55%))[Erfüllt]
// #let notOk = table.cell(fill: red.lighten(55%))[Nicht Erfüllt]
// #figure(
// table(
// columns: (6.8em, 1fr, 2.5em, auto),
// [ID],
// [Anforderung],
// [Art #footnote[Art = Anforderungsart: G = Geschäftsorganisation, F = Funktional, Q = Qualität, S = Sicherheit, M = Migration,
// A = Architektur, B = Betrieb, K = Konformität (Gesetzgebung, Weisungswesen, Normen und Richtlinien)]],
// [Erfüllungsstatus],
//
// [@req-a1], [Beschreibung der Anforderung], [F], ok,
// ),
// caption: [Erfüllung der Anforderungen],
// )
//
// === Ziele
//
// In der Initialisierungsphase (@ziele) wurden Ziele definiert.
//
// #let ok = table.cell(fill: green.lighten(55%))[Erreicht]
// #let notOk = table.cell(fill: red.lighten(55%))[Nicht Erreicht]
//
// ==== Produktziele
//
// #figure(
// table(
// columns: (2em, 1fr, auto, 1fr),
// [Nr.], [Ziel], [Erreicht], [Kommentar],
// [1], [Beschreibung des Produktziels.], ok, [],
// ),
// caption: [Erfüllung der Produktziele],
// )
//
// ==== Projektvorgehensziele
//
// #figure(
// table(
// columns: (2em, 1fr, auto, 1fr),
// [Nr.], [Ziel], [Erreicht], [Kommentar],
// [1], [Beschreibung des Projektvorgehensziels.], ok, [],
// ),
// caption: [Erfüllung der Projektvorgehensziele],
// )
@@ -0,0 +1,34 @@
#import "@preview/wrap-it:0.1.1": wrap-content
#import "@local/chfoit-ipa:0.0.0": acronym, term
= Realisierung
In diesem Teil wird auf die Umsetzung eingegangen.
#include "01_backend.typ"
#include "02_frontend.typ"
== Entwicklung
=== Abhängigkeiten
// Beschreibe die tools die zum entwickeln gebraucht werden.
=== Applikation starten
// Beschreibe folgendes:
// - Herunterladen ders repository
// - Starten der Applikation
//
// Am besten mit code bläcken
#figure(
```sh
echo "example"
```,
caption: [Applikation starten],
)
=== Applikation stoppen
#include "03_tests.typ"
Whitespace-only changes.
@@ -0,0 +1,7 @@
#include "./01_Kurzfassung.typ"
#include "./02_Einführung.typ"
#include "./03_Initialisierung/index.typ"
#include "./04_Konzept/index.typ"
#include "./05_Realisierung/index.typ"
#include "./06_Abschluss/index.typ"
@@ -0,0 +1,13 @@
= YYYY-MM-DD Daily
Ort: Teams \
Datum: YYYY-MM-DD \
Beteiligte Personen: MM, VF
Besprochene Punkte:
- Aktueller Projektstand
- Termine machen
- Daily
- Expertenbesuche
- Nächste Schritte festlegen
@@ -0,0 +1,39 @@
= Expertenbesuch 1
Ort: Eichenweg 1\
Datum: 2026-XX-XX \
Beteiligte Personen: MM, VF
- Vorstellung der Expertin / des Experten
- Präsentation des Arbeitsplatzes
== Protokollführung
- Muss nicht wortwörtlich sein.
- Muss enthalten:
- Abmachungen
- Entscheide
- Tipps
- Protokoll für Expertenbesuch & Zeitplan -> auf PkOrg hochladen
== Infos fürs weitere Vorgehen:
- Ausdrucken der Kriterien
- Auch schon vor der IPA
- Abgabe der IPA muss frühzeitig passieren.
- Eventuell wollen Expert:innen den Code als ZIP
- 2. Expertenbesuch
- Präsentationstermin fixen
== Anpassungen für den Zeitplan
- Meilensteine in einer anderen Farbe
== Anpassungen für Tagesjournale
- Sollten sachlicher sein.
- Hilfestellungen sollten detaillierter sein als bis anhin.
- Sollten alles beinhalten (wie auf dem Zeitplan).
@@ -0,0 +1,24 @@
= Expertenbesuch 2
== Projektstand und Fachgespräch
Die Abnahmetermine liegen aktuell im vorgegebenen Zeitplan. Im Hinblick auf das kommende Fachgespräch wurden zentrale Aspekte des Projektmanagements erörtert. Ein Fokus liegt dabei auf dem Vergleich zwischen den HERMES-Versionen *5.1* und *2022* sowie der Identifikation der obligatorischen Phasen.
Auf technischer Ebene wurden verschiedene Konzepte diskutiert, insbesondere:
- *Locking-Strategien:* Der Unterschied zwischen pessimistischem und optimistischem Locking.
- *YAGNI-Prinzip:* Vermeidung von Overengineering.
- *Administration:* Die korrekte Führung von Journal und Zeiterfassung.
== Reflexion: Erfolge und Misserfolge
In der bisherigen Projektphase konnte verzeichnet werden, dass die bestehende Infrastruktur stabil blieb. Demgegenüber steht ein aktueller Zeitrückstand, der vor allem auf die unzureichende Einplanung von Recherchezeiten zurückzuführen ist.
== Anforderungen an Präsentation und Demo
Die Präsentation wird standardmässig mit *PowerPoint* erwartet, kann jedoch durch analoge Medien ergänzt werden. Methodisch wird der Einsatz von Hilfsmitteln wie Pointern und "Black Pages" empfohlen, um den Fokus auf den Vergleich zwischen dem Ist-Zustand ("Heute") und dem Soll-Zustand ("Morgen") zu lenken.
Die anschliessende System-Demo ist auf eine Dauer von *fünf Minuten* begrenzt.
== Wichtige Hinweise für den Abschluss
- *Konsistenz:* Es ist zwingend darauf zu achten, dass das Arbeitsjournal und der Zeitplan keine Widersprüche aufweisen.
- *Kommunikation:* Im Fachgespräch ist direkt auf die Fragen der Experten zu antworten; subjektive Meinungsäusserungen sollten vermieden werden.
- *Toolchain:* Die Verwendung von *Typst* für die Dokumentation muss explizit mit der Expertin oder dem Experten abgestimmt werden.
Die abschliessende Bewertung erfolgt im Dialog mit dem Praxisbildner.
@@ -0,0 +1,7 @@
== Gesprächsprotokolle
#{
set heading(offset: 2)
include "Daily.typ"
include "Expertenbesuch1.typ"
include "Expertenbesuch2.typ"
}
@@ -0,0 +1,8 @@
#include "./01_GespraechsProtokolle/index.typ"
== Versionierung des Codes:
// #figure(
// image("../../02_Grafiken/versions_code.png", width: 80%),
// caption: [Code Versionierung],
// ) <fig-versions_code>
+16
View File
@@ -0,0 +1,16 @@
API: Application Programming Interface
CI/CD: Continuous Integration / Continuous Deployment
CLI: Command Line Interface
CSS: Cascading Style Sheets
HTML: Hypertext Markup Language
IDE: Integrated Development Environment
JS: JavaScript
JSON: JavaScript Object Notation
MVP: Minimum Viable Product
POSIX: Portable Operating System Interface
REST: Representational State Transfer
SQL: Structured Query Language
TS: TypeScript
UML: Unified Modeling Language
YAML: YAML Ain't Markup Language
BAB: Büro-Arbeitsplatz Bund
+8
View File
@@ -0,0 +1,8 @@
Anforderung: Aussage über eine Eigenschaft oder Leistung eines Produktes, Prozesses oder der am Prozess beteiligten Personen.
Anwendungsfall: (Use Case) Beschreibt die Interaktion zwischen einem Akteur und einem System zur Erreichung eines fachlichen Ziels.
Git: Ein verteiltes Versionsverwaltungssystem zur Verfolgung von Änderungen im Quellcode.
HERMES: Die Projektmanagementmethode für IT, Organisation und Dienstleistung im schweizerischen Umfeld.
Phasen: Zeitliche Abschnitte eines Projekts (z. B. Initialisierung, Konzept, Realisierung, Einführung).
Projektziel: Ein definierter Endzustand, der durch das Projekt erreicht werden soll (terminlich, finanziell, inhaltlich).
Produktziel: Die angestrebten Eigenschaften und der Nutzen des Endergebnisses nach Projektabschluss.
Typst: Ein modernes, Markup-basiertes Satzsystem, das als Alternative zu LaTeX entwickelt wurde.
+87
View File
@@ -0,0 +1,87 @@
bit:
type: Web
title: Bundesamt für Informatik und Telekommunikation BIT
url: https://bit.admin.ch
date: 2026-01-20
conventional-commits:
type: Web
title: Conventional Commits
url: https://www.conventionalcommits.org/en/v1.0.0/
date: 2025-11-27
hermes-online:
type: Web
title: HERMES Online
url: https://www.hermes.admin.ch/
date: 2025-11-27
schutzbedarf:
type: Web
title: Beurteilung des Schutzbedarfs
url: https://www.ncsc.admin.ch/ncsc/de/home/dokumentation/sicherheitsvorgaben-bund/sicherheitsverfahren/beurteilung-schutzbedarf.html
date: 2025-11-27
alte-schutzbedarfsanalyse:
type: Web
title: Alte Schutzbedarfsanalyse
url: https://web.archive.org/web/20231206144843/https://www.ncsc.admin.ch/ncsc/de/home/dokumentation/sicherheitsvorgaben-bund/sicherheitsverfahren/beurteilung-schutzbedarf.html
date: 2023-12-6
grundschutz:
type: Web
title: IKT Grundschutz
url: https://www.ncsc.admin.ch/ncsc/de/home/dokumentation/sicherheitsvorgaben-bund/sicherheitsverfahren/grundschutz.html
date: 2025-11-27
git:
type: Web
title: Git
url: https://git-scm.com/
date: 2025-11-27
bitbucket:
type: Web
title: Bitbucket
url: https://bitbucket.org/product/
date: 2025-02-10
typst:
type: Web
title: Typst
url: https://typst.app/
date: 2025-11-27
libreoffice:
type: Web
title: LibreOffice
url: https://www.libreoffice.org/download/download-libreoffice/
date: 2025-11-28
skim:
type: Web
title: Skim
url: https://skim-app.sourceforge.io/
date: 2025-11-27
drawio:
type: Web
title: Diagrams.net
url: https://app.diagrams.net/
date: 2025-11-27
chatgpt:
type: Web
title: ChatGPT
url: https://chatgpt.com/
date: 2025-02-12
gemini:
type: Web
title: Gemini
url: https://gemini.google.com/app
date: 2025-02-12
openapi-spec:
type: Web
title: OpenAPI Specification
url: https://swagger.io/docs/specification/v3_0/about/
date: 2025-02-21
@@ -0,0 +1,49 @@
<mxfile host="Electron" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/29.3.6 Chrome/140.0.7339.249 Electron/38.8.0 Safari/537.36" version="29.3.6">
<diagram name="Page-1" id="uVEAexy7FDdrR9v7mdBW">
<mxGraphModel dx="930" dy="1430" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="vWHVnHNtM6UVPYdJ8lre-1" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;fontFamily=Fira Mono;fontSize=20;" value="&lt;p class=&quot;p1&quot; style=&quot;margin: 0px; font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-size-adjust: none; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-variant-emoji: normal; font-stretch: normal; line-height: normal;&quot;&gt;&lt;font style=&quot;font-size: 38px;&quot;&gt;&lt;font style=&quot;color: rgb(255, 0, 0);&quot;&gt;feat&lt;/font&gt;&lt;font style=&quot;color: rgb(0, 153, 0);&quot;&gt;(frontend)&lt;/font&gt;&lt;font style=&quot;color: rgb(0, 0, 0);&quot;&gt;:&lt;/font&gt;&lt;font color=&quot;#c2c9d6&quot;&gt; &lt;/font&gt;&lt;font style=&quot;color: rgb(0, 0, 255);&quot;&gt;add foo bar baz&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;" vertex="1">
<mxGeometry height="20" width="810" x="20" y="20" as="geometry" />
</mxCell>
<mxCell id="vWHVnHNtM6UVPYdJ8lre-2" edge="1" parent="1" style="endArrow=none;html=1;rounded=0;startSize=20;endSize=20;strokeWidth=3;strokeColor=#FF0000;startArrow=classic;startFill=1;" value="">
<mxGeometry height="50" relative="1" width="50" as="geometry">
<Array as="points">
<mxPoint x="30" y="300" />
</Array>
<mxPoint x="440" y="300" as="sourcePoint" />
<mxPoint x="30" y="50" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="vWHVnHNtM6UVPYdJ8lre-3" edge="1" parent="1" style="endArrow=none;html=1;rounded=0;startSize=20;endSize=20;strokeWidth=3;strokeColor=#009900;startArrow=classic;startFill=1;" value="">
<mxGeometry height="50" relative="1" width="50" as="geometry">
<Array as="points">
<mxPoint x="120" y="210" />
</Array>
<mxPoint x="440" y="210" as="sourcePoint" />
<mxPoint x="120" y="50" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="vWHVnHNtM6UVPYdJ8lre-4" edge="1" parent="1" style="endArrow=none;html=1;rounded=0;startSize=20;endSize=20;strokeWidth=3;strokeColor=#0000FF;endFill=0;startArrow=classic;startFill=1;" value="">
<mxGeometry height="50" relative="1" width="50" as="geometry">
<Array as="points">
<mxPoint x="390" y="120" />
</Array>
<mxPoint x="440" y="120" as="sourcePoint" />
<mxPoint x="390" y="50" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="vWHVnHNtM6UVPYdJ8lre-5" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;fontSize=20;" value="Zusammenfassung der Änderung auf Englisch im Präsens" vertex="1">
<mxGeometry height="80" width="280" x="450" y="80" as="geometry" />
</mxCell>
<mxCell id="vWHVnHNtM6UVPYdJ8lre-6" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;fontSize=20;" value="Komponente, in der die Änderung stattgefunden hat" vertex="1">
<mxGeometry height="80" width="240" x="450" y="170" as="geometry" />
</mxCell>
<mxCell id="vWHVnHNtM6UVPYdJ8lre-7" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;fontSize=20;" value="Art der Änderung:&amp;nbsp;chore, feat, fix, docs, refactor, style oder test" vertex="1">
<mxGeometry height="80" width="270" x="450" y="260" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.
+204
View File
@@ -0,0 +1,204 @@
<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0" version="26.0.11" pages="2">
<diagram id="pAmi1uS5zqduoQpEX0fZ" name="Page-1">
<mxGraphModel dx="2874" dy="1808" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="0" pageScale="1" pageWidth="850" pageHeight="1100" background="none" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="node18" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;b&gt;app/app.component.ts&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt; AppComponent: &lt;br/&gt; BrowserAnimationsModule: &lt;br/&gt; Component: &lt;br/&gt; ObMasterLayoutModule: &lt;br/&gt; TranslateModule: &lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
<mxGeometry x="960" y="-50" width="176" height="144" as="geometry" />
</mxCell>
<mxCell id="node23" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;b&gt;app/app.routes.ts&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt; APP_ROUTES: &lt;br/&gt; LoginComponent: &lt;br/&gt; RequestsComponent: &lt;br/&gt; Routes: &lt;br/&gt; authGuard: &lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
<mxGeometry x="644" y="290" width="150" height="144" as="geometry" />
</mxCell>
<mxCell id="node13" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;b&gt;auth-form/auth-form.component.ts&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;&lt;br&gt;&lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
<mxGeometry x="600" y="-340" width="238" height="413" as="geometry" />
</mxCell>
<mxCell id="node33" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;b&gt;auth/auth.guard.ts&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt; AuthService: &lt;br/&gt; CanActivateFn: &lt;br/&gt; Router: &lt;br/&gt; authGuard: &lt;br/&gt; inject: &lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
<mxGeometry x="-230" y="-100" width="139" height="144" as="geometry" />
</mxCell>
<mxCell id="node19" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;b&gt;auth/auth.interceptor.ts&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt; AuthService: &lt;br/&gt; HttpInterceptorFn: &lt;br/&gt; authInterceptor: &lt;br/&gt; inject: &lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
<mxGeometry x="440" y="1210" width="170" height="122" as="geometry" />
</mxCell>
<mxCell id="node34" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;b&gt;auth/auth.service.ts&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt; AuthService: &lt;br/&gt; Injectable: &lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
<mxGeometry x="-234" y="150" width="147" height="79" as="geometry" />
</mxCell>
<mxCell id="node0" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;b&gt;login/login.component.ts&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt; AuthFormComponent: &lt;br/&gt; Component: &lt;br/&gt; LoginComponent: &lt;br/&gt; MatFormField: &lt;br/&gt; MatInputModule: &lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
<mxGeometry x="632" y="110" width="174" height="144" as="geometry" />
</mxCell>
<mxCell id="node1" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;b&gt;request-table/request-table.component.ts&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt; Component: &lt;br/&gt; FormatQueryPipe: &lt;br/&gt; MatTableModule: &lt;br/&gt; OnInit: &lt;br/&gt; Request: &lt;br/&gt; RequestService: &lt;br/&gt; RequestTableComponent: &lt;br/&gt; inject: &lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
<mxGeometry x="578" y="630" width="282" height="193" as="geometry" />
</mxCell>
<mxCell id="node20" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;b&gt;request/format-query.pipe.ts&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt; FormatQueryPipe: &lt;br/&gt; Pipe: &lt;br/&gt; PipeTransform: &lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
<mxGeometry x="644" y="920" width="204" height="100" as="geometry" />
</mxCell>
<mxCell id="node39" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;b&gt;request/request.model.ts&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt; Request: &lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
<mxGeometry x="400" y="860" width="180" height="57" as="geometry" />
</mxCell>
<mxCell id="node38" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;b&gt;request/request.service.ts&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt; HttpClient: &lt;br/&gt; Injectable: &lt;br/&gt; Observable: &lt;br/&gt; Request: &lt;br/&gt; RequestService: &lt;br/&gt; inject: &lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
<mxGeometry x="210" y="650" width="187" height="153" as="geometry" />
</mxCell>
<mxCell id="node31" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;b&gt;requests/requests.component.ts&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt; Component: &lt;br/&gt; RequestTableComponent: &lt;br/&gt; RequestsComponent: &lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
<mxGeometry x="626" y="446.5" width="224" height="100" as="geometry" />
</mxCell>
<mxCell id="node4" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;b&gt;src/main.ts&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt; APP_ROUTES: &lt;br/&gt; AppComponent: &lt;br/&gt; HTTP_INTERCEPTORS: &lt;br/&gt; LOCALE_ID: &lt;br/&gt; ObHttpApiInterceptor: &lt;br/&gt; ObIconModule: &lt;br/&gt; ObMasterLayoutConfig: &lt;br/&gt; PreloadAllModules: &lt;br/&gt; TranslateModule: &lt;br/&gt; WINDOW: &lt;br/&gt; authInterceptor: &lt;br/&gt; bootstrapApplication: &lt;br/&gt; importProvidersFrom: &lt;br/&gt; localeDECH: &lt;br/&gt; localeENGB: &lt;br/&gt; localeFRCH: &lt;br/&gt; localeITCH: &lt;br/&gt; multiTranslateLoader: &lt;br/&gt; provideAnimations: &lt;br/&gt; provideHttpClient: &lt;br/&gt; provideRouter: &lt;br/&gt; registerLocaleData: &lt;br/&gt; withDebugTracing: &lt;br/&gt; withInterceptors: &lt;br/&gt; withInterceptorsFromDi: &lt;br/&gt; withPreloading: &lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
<mxGeometry x="1050" y="220" width="163" height="553" as="geometry" />
</mxCell>
<mxCell id="edge15" value="" style="html=1;rounded=1;;dashed=0;startArrow=classic;startSize=12;endArrow=none;strokeColor=#595959;" parent="1" source="node23" target="node33" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<Array as="points" />
</mxGeometry>
</mxCell>
<mxCell id="edge50" value="" style="html=1;rounded=1;;dashed=0;startArrow=classic;startSize=12;endArrow=none;strokeColor=#595959;" parent="1" source="node23" target="node0" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<Array as="points" />
</mxGeometry>
</mxCell>
<mxCell id="edge19" value="" style="html=1;rounded=1;;dashed=0;startArrow=classic;startSize=12;endArrow=none;strokeColor=#595959;" parent="1" source="node23" target="node31" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<Array as="points" />
</mxGeometry>
</mxCell>
<mxCell id="edge3" value="" style="html=1;rounded=1;;dashed=0;startArrow=classic;startSize=12;endArrow=none;strokeColor=#595959;" parent="1" source="node13" target="node34" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<Array as="points" />
</mxGeometry>
</mxCell>
<mxCell id="edge56" value="" style="html=1;rounded=1;;dashed=0;startArrow=classic;startSize=12;endArrow=none;strokeColor=#595959;" parent="1" source="node33" target="node34" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<Array as="points" />
</mxGeometry>
</mxCell>
<mxCell id="edge74" value="" style="html=1;rounded=1;;dashed=0;startArrow=classic;startSize=12;endArrow=none;strokeColor=#595959;entryX=0.628;entryY=1.000;entryDx=0;entryDy=0;" parent="1" source="node19" target="node34" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<Array as="points" />
</mxGeometry>
</mxCell>
<mxCell id="edge94" value="" style="html=1;rounded=1;;dashed=0;startArrow=classic;startSize=12;endArrow=none;strokeColor=#595959;" parent="1" source="node0" target="node13" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<Array as="points" />
</mxGeometry>
</mxCell>
<mxCell id="edge48" value="" style="html=1;rounded=1;;dashed=0;startArrow=classic;startSize=12;endArrow=none;strokeColor=#595959;" parent="1" source="node1" target="node20" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<Array as="points" />
</mxGeometry>
</mxCell>
<mxCell id="edge14" value="" style="html=1;rounded=1;;dashed=0;startArrow=classic;startSize=12;endArrow=none;strokeColor=#595959;exitX=0.486;exitY=1.000;exitDx=0;exitDy=0;entryX=0.506;entryY=0.000;entryDx=0;entryDy=0;" parent="1" source="node1" target="node39" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<Array as="points" />
</mxGeometry>
</mxCell>
<mxCell id="edge17" value="" style="html=1;rounded=1;;dashed=0;startArrow=classic;startSize=12;endArrow=none;strokeColor=#595959;" parent="1" source="node1" target="node38" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<Array as="points" />
</mxGeometry>
</mxCell>
<mxCell id="edge9" value="" style="html=1;rounded=1;;dashed=0;startArrow=classic;startSize=12;endArrow=none;strokeColor=#595959;" parent="1" source="node38" target="node39" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<Array as="points" />
</mxGeometry>
</mxCell>
<mxCell id="edge103" value="" style="html=1;rounded=1;;dashed=0;startArrow=classic;startSize=12;endArrow=none;strokeColor=#595959;exitX=0.516;exitY=1.000;exitDx=0;exitDy=0;entryX=0.473;entryY=0.000;entryDx=0;entryDy=0;" parent="1" source="node31" target="node1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<Array as="points" />
</mxGeometry>
</mxCell>
<mxCell id="edge27" value="" style="html=1;rounded=1;;dashed=0;startArrow=classic;startSize=12;endArrow=none;strokeColor=#595959;" parent="1" source="node4" target="node18" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<Array as="points" />
</mxGeometry>
</mxCell>
<mxCell id="edge83" value="" style="html=1;rounded=1;;dashed=0;startArrow=classic;startSize=12;endArrow=none;strokeColor=#595959;entryX=1.000;entryY=0.611;entryDx=0;entryDy=0;" parent="1" source="node4" target="node23" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<Array as="points" />
</mxGeometry>
</mxCell>
<mxCell id="edge98" value="" style="html=1;rounded=1;;dashed=0;startArrow=classic;startSize=12;endArrow=none;strokeColor=#595959;" parent="1" source="node4" target="node19" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<Array as="points" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
<diagram id="0URLcGRnFhtR-RyUwsXH" name="Page-2">
<mxGraphModel dx="575" dy="327" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="67QRgPurPpMTbLIEhQDk-1" value="" style="endArrow=none;html=1;rounded=0;" edge="1" parent="1" source="67QRgPurPpMTbLIEhQDk-10">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="120" y="59.79" as="sourcePoint" />
<mxPoint x="390" y="60" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="67QRgPurPpMTbLIEhQDk-2" value="main" style="text;align=center;verticalAlign=middle;resizable=1;points=[];autosize=1;strokeColor=none;fillColor=none;html=1;movable=1;rotatable=1;deletable=1;editable=1;locked=0;connectable=1;" vertex="1" parent="1">
<mxGeometry x="55" y="40" width="40" height="40" as="geometry" />
</mxCell>
<mxCell id="67QRgPurPpMTbLIEhQDk-4" value="feat/PBI-XYZ" style="text;align=center;verticalAlign=middle;resizable=1;points=[];autosize=1;strokeColor=none;fillColor=none;html=1;movable=1;rotatable=1;deletable=1;editable=1;locked=0;connectable=1;" vertex="1" parent="1">
<mxGeometry x="30" y="85" width="90" height="30" as="geometry" />
</mxCell>
<mxCell id="67QRgPurPpMTbLIEhQDk-5" value="" style="endArrow=none;html=1;rounded=0;" edge="1" parent="1" source="67QRgPurPpMTbLIEhQDk-9">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="120" y="99.78999999999999" as="sourcePoint" />
<mxPoint x="390" y="100" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="67QRgPurPpMTbLIEhQDk-8" value="" style="endArrow=none;html=1;rounded=0;" edge="1" parent="1" target="67QRgPurPpMTbLIEhQDk-7">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="120" y="99.78999999999999" as="sourcePoint" />
<mxPoint x="440" y="99.78999999999999" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="67QRgPurPpMTbLIEhQDk-7" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="200" y="85" width="30" height="30" as="geometry" />
</mxCell>
<mxCell id="67QRgPurPpMTbLIEhQDk-10" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="340" y="45" width="30" height="30" as="geometry" />
</mxCell>
<mxCell id="67QRgPurPpMTbLIEhQDk-11" value="" style="endArrow=none;html=1;rounded=0;" edge="1" parent="1" source="67QRgPurPpMTbLIEhQDk-6" target="67QRgPurPpMTbLIEhQDk-10">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="120" y="59.79" as="sourcePoint" />
<mxPoint x="440" y="59.79" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="67QRgPurPpMTbLIEhQDk-12" value="" style="endArrow=none;html=1;rounded=0;" edge="1" parent="1" target="67QRgPurPpMTbLIEhQDk-6">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="120" y="59.79" as="sourcePoint" />
<mxPoint x="360" y="60" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="67QRgPurPpMTbLIEhQDk-6" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="130" y="45" width="30" height="30" as="geometry" />
</mxCell>
<mxCell id="67QRgPurPpMTbLIEhQDk-13" value="" style="endArrow=none;html=1;rounded=0;" edge="1" parent="1" source="67QRgPurPpMTbLIEhQDk-7" target="67QRgPurPpMTbLIEhQDk-9">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="230" y="100" as="sourcePoint" />
<mxPoint x="440" y="99.78999999999999" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="67QRgPurPpMTbLIEhQDk-9" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="270" y="85" width="30" height="30" as="geometry" />
</mxCell>
<mxCell id="67QRgPurPpMTbLIEhQDk-14" value="" style="endArrow=classic;html=1;rounded=0;" edge="1" parent="1" source="67QRgPurPpMTbLIEhQDk-6" target="67QRgPurPpMTbLIEhQDk-7">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="400" y="200" as="sourcePoint" />
<mxPoint x="450" y="150" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="67QRgPurPpMTbLIEhQDk-15" value="" style="endArrow=classic;html=1;rounded=0;" edge="1" parent="1" source="67QRgPurPpMTbLIEhQDk-7" target="67QRgPurPpMTbLIEhQDk-9">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="400" y="200" as="sourcePoint" />
<mxPoint x="450" y="150" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="67QRgPurPpMTbLIEhQDk-17" value="" style="endArrow=classic;html=1;rounded=0;" edge="1" parent="1" source="67QRgPurPpMTbLIEhQDk-9" target="67QRgPurPpMTbLIEhQDk-10">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="280" y="160" as="sourcePoint" />
<mxPoint x="330" y="110" as="targetPoint" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

@@ -0,0 +1,49 @@
<mxfile host="Electron" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/29.0.3 Chrome/140.0.7339.249 Electron/38.7.0 Safari/537.36" version="29.0.3">
<diagram name="Page-1" id="Ad3Y4pDfi6w6hhVWHxiW">
<mxGraphModel dx="1452" dy="967" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="5ghOR48_7e4-En-8zQ27-6" value="Set Authorisations" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="5ghOR48_7e4-En-8zQ27-2" target="5ghOR48_7e4-En-8zQ27-4">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="5ghOR48_7e4-En-8zQ27-2" value="AMS" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="30" y="150" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="5ghOR48_7e4-En-8zQ27-12" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.75;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="5ghOR48_7e4-En-8zQ27-4" target="5ghOR48_7e4-En-8zQ27-10">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="5ghOR48_7e4-En-8zQ27-13" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="5ghOR48_7e4-En-8zQ27-4" target="5ghOR48_7e4-En-8zQ27-8">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="5ghOR48_7e4-En-8zQ27-14" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="5ghOR48_7e4-En-8zQ27-4" target="5ghOR48_7e4-En-8zQ27-9">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="5ghOR48_7e4-En-8zQ27-15" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="5ghOR48_7e4-En-8zQ27-4" target="5ghOR48_7e4-En-8zQ27-11">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="5ghOR48_7e4-En-8zQ27-16" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="5ghOR48_7e4-En-8zQ27-4" target="5ghOR48_7e4-En-8zQ27-7">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="5ghOR48_7e4-En-8zQ27-4" value="Translation&lt;div&gt;App&lt;/div&gt;" style="ellipse;shape=cloud;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="290" y="140" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="5ghOR48_7e4-En-8zQ27-7" value="App" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="300" y="270" width="70" height="60" as="geometry" />
</mxCell>
<mxCell id="5ghOR48_7e4-En-8zQ27-8" value="App" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="390" y="10" width="70" height="60" as="geometry" />
</mxCell>
<mxCell id="5ghOR48_7e4-En-8zQ27-9" value="App" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="440" y="100" width="70" height="60" as="geometry" />
</mxCell>
<mxCell id="5ghOR48_7e4-En-8zQ27-10" value="App" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="270" y="40" width="70" height="60" as="geometry" />
</mxCell>
<mxCell id="5ghOR48_7e4-En-8zQ27-11" value="App" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="430" y="220" width="70" height="60" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

@@ -0,0 +1,76 @@
<mxfile host="Electron" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/29.3.6 Chrome/140.0.7339.249 Electron/38.8.0 Safari/537.36" version="29.3.6">
<diagram name="Page-1" id="jQV6cwFY6zP2osKTjZPb">
<mxGraphModel dx="930" dy="1430" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0" adaptiveColors="none">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="2T8pLJCnUbLX6XRgvneg-3" connectable="0" parent="1" style="group" value="" vertex="1">
<mxGeometry height="30" width="850" y="760" as="geometry" />
</mxCell>
<mxCell id="2T8pLJCnUbLX6XRgvneg-1" parent="2T8pLJCnUbLX6XRgvneg-3" style="rounded=0;whiteSpace=wrap;html=1;strokeColor=#2F4356;fillColor=#263645;" value="" vertex="1">
<mxGeometry height="30" width="850" as="geometry" />
</mxCell>
<mxCell id="2T8pLJCnUbLX6XRgvneg-2" parent="2T8pLJCnUbLX6XRgvneg-3" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=right;verticalAlign=middle;rounded=0;fontColor=#FFFFFF;" value="&lt;div class=&quot;ob-footer-item ob-footer-item-info&quot;&gt;&lt;p&gt;&amp;nbsp;© 2024 Bundesamt für Informatik und Telekommunikation BIT&amp;nbsp;&lt;/p&gt;&lt;/div&gt;" vertex="1">
<mxGeometry height="20" width="360" x="484" y="5" as="geometry" />
</mxCell>
<mxCell id="2T8pLJCnUbLX6XRgvneg-4" connectable="0" parent="1" style="group" value="" vertex="1">
<mxGeometry height="60" width="850" as="geometry" />
</mxCell>
<mxCell id="rtz8Y9xaqakTJDMib3S0-2" parent="2T8pLJCnUbLX6XRgvneg-4" style="rounded=0;whiteSpace=wrap;html=1;strokeColor=#DFE4E9;" value="" vertex="1">
<mxGeometry height="60" width="850" as="geometry" />
</mxCell>
<mxCell id="rtz8Y9xaqakTJDMib3S0-1" parent="2T8pLJCnUbLX6XRgvneg-4" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=default;verticalAlign=top;aspect=fixed;imageAspect=0;image=data:image/png,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 truncated
<mxGeometry height="38" width="156" x="10" y="11" as="geometry" />
</mxCell>
<mxCell id="rtz8Y9xaqakTJDMib3S0-3" parent="2T8pLJCnUbLX6XRgvneg-4" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;rotation=-45;" value="TEST" vertex="1">
<mxGeometry height="20" width="54.39" x="10.82" y="21.98" as="geometry" />
</mxCell>
<mxCell id="rtz8Y9xaqakTJDMib3S0-4" parent="2T8pLJCnUbLX6XRgvneg-4" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="user@bit.admin.ch" vertex="1">
<mxGeometry height="30" width="120" x="620" y="15" as="geometry" />
</mxCell>
<mxCell id="rtz8Y9xaqakTJDMib3S0-6" parent="2T8pLJCnUbLX6XRgvneg-4" style="shape=image;verticalLabelPosition=bottom;labelBackgroundColor=default;verticalAlign=top;aspect=fixed;imageAspect=0;image=data:image/png,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 truncated
<mxGeometry height="49.16" width="64" x="780" y="5.42" as="geometry" />
</mxCell>
<mxCell id="rtz8Y9xaqakTJDMib3S0-7" parent="2T8pLJCnUbLX6XRgvneg-4" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;fontSize=24;" value="Application Name" vertex="1">
<mxGeometry height="30" width="244" x="166" y="15" as="geometry" />
</mxCell>
<mxCell id="AzEw4Oox6lWZ0eyIWecD-1" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#596978;strokeColor=none;fontColor=#FFFFFF;verticalAlign=middle;arcSize=5;" value="Primary" vertex="1">
<mxGeometry height="30" width="80" x="89" y="159" as="geometry" />
</mxCell>
<mxCell id="AzEw4Oox6lWZ0eyIWecD-2" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#2F4356;fontColor=#2F4356;verticalAlign=middle;arcSize=5;" value="Secondary" vertex="1">
<mxGeometry height="30" width="80" x="89" y="199" as="geometry" />
</mxCell>
<mxCell id="AzEw4Oox6lWZ0eyIWecD-3" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=none;fontColor=#2F4356;verticalAlign=middle;arcSize=5;" value="Tertiary" vertex="1">
<mxGeometry height="30" width="80" x="89" y="239" as="geometry" />
</mxCell>
<mxCell id="AzEw4Oox6lWZ0eyIWecD-4" parent="1" style="rounded=0;whiteSpace=wrap;html=1;strokeColor=#DFE4E9;" value="" vertex="1">
<mxGeometry height="31" width="850" y="60" as="geometry" />
</mxCell>
<mxCell id="AzEw4Oox6lWZ0eyIWecD-5" parent="1" style="rounded=0;whiteSpace=wrap;html=1;strokeColor=none;" value="Home" vertex="1">
<mxGeometry height="30" width="51" x="2" y="60.5" as="geometry" />
</mxCell>
<mxCell id="AzEw4Oox6lWZ0eyIWecD-7" edge="1" parent="1" source="AzEw4Oox6lWZ0eyIWecD-5" style="endArrow=none;html=1;rounded=0;exitX=0;exitY=1;exitDx=0;exitDy=0;entryX=1;entryY=1;entryDx=0;entryDy=0;strokeColor=#E53940;" target="AzEw4Oox6lWZ0eyIWecD-5" value="">
<mxGeometry height="50" relative="1" width="50" as="geometry">
<mxPoint x="16" y="140" as="sourcePoint" />
<mxPoint x="66" y="90" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="AzEw4Oox6lWZ0eyIWecD-8" parent="1" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;fontSize=24;fontStyle=1" value="Buttons" vertex="1">
<mxGeometry height="30" width="287" x="89" y="110" as="geometry" />
</mxCell>
<mxCell id="AzEw4Oox6lWZ0eyIWecD-9" parent="1" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;fontSize=24;fontStyle=1" value="Form Elements" vertex="1">
<mxGeometry height="30" width="287" x="89" y="316" as="geometry" />
</mxCell>
<mxCell id="AzEw4Oox6lWZ0eyIWecD-10" parent="1" style="rounded=1;whiteSpace=wrap;html=1;arcSize=14;align=left;fontColor=#757575;" value="Default Input" vertex="1">
<mxGeometry height="31" width="182" x="89" y="346" as="geometry" />
</mxCell>
<mxCell id="AzEw4Oox6lWZ0eyIWecD-11" parent="1" style="rounded=1;whiteSpace=wrap;html=1;arcSize=14;align=left;fontColor=#757575;" value="Default Select" vertex="1">
<mxGeometry height="31" width="182" x="89" y="396" as="geometry" />
</mxCell>
<mxCell id="AzEw4Oox6lWZ0eyIWecD-12" parent="1" style="triangle;whiteSpace=wrap;html=1;fillColor=#000000;rotation=90;" value="" vertex="1">
<mxGeometry height="7" width="4" x="253" y="408" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

@@ -0,0 +1,54 @@
<mxfile host="Electron" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/29.0.3 Chrome/140.0.7339.249 Electron/38.7.0 Safari/537.36" version="29.0.3">
<diagram name="Page-1" id="7D5BdODsFkr--MVPVFyz">
<mxGraphModel dx="1234" dy="822" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="wi_dSl8dindKQ-yHHEx_-13" value=" Ausführung" style="whiteSpace=wrap;strokeWidth=2;align=left;verticalAlign=top;" parent="1" vertex="1">
<mxGeometry x="30" y="398" width="690" height="128" as="geometry" />
</mxCell>
<mxCell id="wi_dSl8dindKQ-yHHEx_-14" value=" Führung" style="whiteSpace=wrap;strokeWidth=2;verticalAlign=top;align=left;" parent="1" vertex="1">
<mxGeometry x="30" y="270" width="690" height="128" as="geometry" />
</mxCell>
<mxCell id="wi_dSl8dindKQ-yHHEx_-15" value=" Steuerung" style="whiteSpace=wrap;strokeWidth=2;verticalAlign=top;align=left;" parent="1" vertex="1">
<mxGeometry x="30" y="10" width="690" height="260" as="geometry" />
</mxCell>
<mxCell id="wi_dSl8dindKQ-yHHEx_-29" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=none;endFill=0;strokeWidth=2;" parent="1" source="wi_dSl8dindKQ-yHHEx_-16" target="wi_dSl8dindKQ-yHHEx_-19" edge="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="260" y="159" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="wi_dSl8dindKQ-yHHEx_-16" value="Qualitäts- &amp; Risikomanager:&#xa;&#xa;Experten" style="whiteSpace=wrap;strokeWidth=2;fontStyle=0" parent="1" vertex="1">
<mxGeometry x="50" y="120" width="190" height="78" as="geometry" />
</mxCell>
<mxCell id="wi_dSl8dindKQ-yHHEx_-26" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=none;endFill=0;" parent="1" source="wi_dSl8dindKQ-yHHEx_-17" target="wi_dSl8dindKQ-yHHEx_-19" edge="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="260" y="70" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="wi_dSl8dindKQ-yHHEx_-17" value="Auftraggeber" style="whiteSpace=wrap;strokeWidth=2;" parent="1" vertex="1">
<mxGeometry x="300" y="30" width="190" height="78" as="geometry" />
</mxCell>
<mxCell id="wi_dSl8dindKQ-yHHEx_-28" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=none;endFill=0;" parent="1" source="wi_dSl8dindKQ-yHHEx_-18" target="wi_dSl8dindKQ-yHHEx_-17" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="wi_dSl8dindKQ-yHHEx_-18" value="Projektausschuss" style="whiteSpace=wrap;strokeWidth=2;" parent="1" vertex="1">
<mxGeometry x="510" y="30" width="190" height="78" as="geometry" />
</mxCell>
<mxCell id="wi_dSl8dindKQ-yHHEx_-25" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=none;endFill=0;" parent="1" source="wi_dSl8dindKQ-yHHEx_-19" target="wi_dSl8dindKQ-yHHEx_-20" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="wi_dSl8dindKQ-yHHEx_-19" value="Projektleiter" style="whiteSpace=wrap;strokeWidth=2;" parent="1" vertex="1">
<mxGeometry x="165" y="295" width="190" height="78" as="geometry" />
</mxCell>
<mxCell id="wi_dSl8dindKQ-yHHEx_-20" value="Entwickler" style="whiteSpace=wrap;strokeWidth=2;" parent="1" vertex="1">
<mxGeometry x="165" y="423" width="190" height="78" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.
+103
View File
@@ -0,0 +1,103 @@
<mxfile host="Electron" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/29.0.3 Chrome/140.0.7339.249 Electron/38.7.0 Safari/537.36" version="29.0.3">
<diagram name="Page-1" id="U7J_GXlxRuu4X2-k22wt">
<mxGraphModel dx="1234" dy="822" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="m71VkXbNBEnwbt8ubw_r-1" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#6EE7B7;strokeColor=#10B981;" parent="1" vertex="1">
<mxGeometry x="50" y="280" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-15" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#A9E785;strokeColor=#119119;" parent="1" vertex="1">
<mxGeometry x="130" y="280" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-16" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#A9E785;strokeColor=#119119;" parent="1" vertex="1">
<mxGeometry x="50" y="200" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-18" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FCFC71;strokeColor=#F59E0B;" parent="1" vertex="1">
<mxGeometry x="50" y="120" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-20" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FCFC71;strokeColor=#F59E0B;" parent="1" vertex="1">
<mxGeometry x="210" y="280" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-11" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FCFC71;strokeColor=#F59E0B;" parent="1" vertex="1">
<mxGeometry x="130" y="200" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-19" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FCD34D;strokeColor=#F59E0B;" parent="1" vertex="1">
<mxGeometry x="50" y="40" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-21" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FCD34D;strokeColor=#F59E0B;" parent="1" vertex="1">
<mxGeometry x="290" y="280" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-13" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FCD34D;strokeColor=#F59E0B;" parent="1" vertex="1">
<mxGeometry x="210" y="200" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-22" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FCD34D;strokeColor=#F59E0B;" parent="1" vertex="1">
<mxGeometry x="130" y="120" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-12" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FDD474;strokeColor=#F97316;" parent="1" vertex="1">
<mxGeometry x="130" y="40" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-14" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FDD474;strokeColor=#F97316;" parent="1" vertex="1">
<mxGeometry x="290" y="200" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-23" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FDD474;strokeColor=#F97316;" parent="1" vertex="1">
<mxGeometry x="210" y="120" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-17" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FDBA74;strokeColor=#F97316;" parent="1" vertex="1">
<mxGeometry x="290" y="120" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-24" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FDBA74;strokeColor=#F97316;" parent="1" vertex="1">
<mxGeometry x="210" y="40" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-9" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FA9DA1;strokeColor=#E53940;" parent="1" vertex="1">
<mxGeometry x="290" y="40" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-25" value="" style="endArrow=classic;startArrow=none;html=1;rounded=0;startFill=0;fontColor=#1F2937;strokeColor=#1F2937;" parent="1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="30" y="360" as="sourcePoint" />
<mxPoint x="30" y="50" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-26" value="Eintretenswahrscheinlichkeit" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;rotation=-90;fontColor=#1F2937;" parent="1" vertex="1">
<mxGeometry x="-120" y="180" width="280" height="30" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-27" value="" style="endArrow=classic;html=1;rounded=0;fontColor=#1F2937;strokeColor=#1F2937;" parent="1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="50" y="380" as="sourcePoint" />
<mxPoint x="370" y="380" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-28" value="Auswirkungsgrad" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;fontColor=#1F2937;" parent="1" vertex="1">
<mxGeometry x="80" y="380" width="260" height="30" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-34" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontColor=#1F2937;strokeColor=#1F2937;" parent="1" source="m71VkXbNBEnwbt8ubw_r-29" target="m71VkXbNBEnwbt8ubw_r-33" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-29" value="1" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#E5E7EB;fontColor=#6B7280;strokeColor=#6B7280;labelBorderColor=none;" parent="1" vertex="1">
<mxGeometry x="310" y="300" width="40" height="40" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-38" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontColor=#1F2937;strokeColor=#1F2937;" parent="1" source="m71VkXbNBEnwbt8ubw_r-30" target="m71VkXbNBEnwbt8ubw_r-35" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-30" value="2" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#E5E7EB;fontColor=#6B7280;strokeColor=#6B7280;labelBorderColor=none;" parent="1" vertex="1">
<mxGeometry x="230" y="220" width="40" height="40" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-37" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontColor=#1F2937;strokeColor=#1F2937;" parent="1" source="m71VkXbNBEnwbt8ubw_r-31" target="m71VkXbNBEnwbt8ubw_r-36" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-31" value="3" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#E5E7EB;fontColor=#6B7280;strokeColor=#6B7280;labelBorderColor=none;" parent="1" vertex="1">
<mxGeometry x="150" y="140" width="40" height="40" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-33" value="1" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fontColor=#1F2937;strokeColor=#1F2937;" parent="1" vertex="1">
<mxGeometry x="150" y="300" width="40" height="40" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-35" value="2" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fontColor=#1F2937;strokeColor=#1F2937;" parent="1" vertex="1">
<mxGeometry x="80" y="230" width="40" height="40" as="geometry" />
</mxCell>
<mxCell id="m71VkXbNBEnwbt8ubw_r-36" value="3" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fontColor=#1F2937;strokeColor=#1F2937;" parent="1" vertex="1">
<mxGeometry x="60" y="210" width="40" height="40" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.
+90
View File
@@ -0,0 +1,90 @@
import pandas as pd
import numpy as np
import datetime
# Load the file
file_path = 'Zeitplan.xlsx'
df = pd.read_excel(file_path, header=[1, 2, 3], index_col=0)
# 1. Tag Rows as 'should' (Plan) or 'is' (Actual)
# Assumption: Rows with a Task Name are 'should'. Empty rows below them are 'is'.
df['Type'] = np.where(df.index.notna(), 'should', 'is')
# 2. Propagate Task Names to the 'is' rows
# We forward-fill the index so the empty 'IST' row gets the name of the task above it
df.index = df.index.to_series().ffill()
# 3. Cleanup Garbage
# Remove the header row itself ('Tätigkeit') and any top-level empty rows
df = df.drop('Tätigkeit', errors='ignore')
df = df[df.index.notna()] # Drop rows that were empty at the very top before any task
# 4. Save Original Order
original_task_order = df.index.unique().tolist()
# 5. Separate Time Grid
# We take columns from index 4 onwards. We must exclude our new 'Type' column from the data grid.
# (The 'Type' column is at the very end now)
time_grid = df.iloc[:, 4:-1]
row_types = df['Type'] # Save the types to re-attach
# 6. Unpivot (Melt)
# We add 'Type' to the index temporarily so it sticks with the data during stacking
time_grid = time_grid.set_index(row_types, append=True)
df_long = time_grid.stack(level=[0, 1, 2], future_stack=True).reset_index()
# Rename columns (Index 0=Task, Index 1=Type, followed by the stacked levels)
df_long.columns = ['Task', 'Type', 'KW', 'Date', 'Halbtag', 'Hours']
# 7. Clean Data
df_long['Hours'] = pd.to_numeric(df_long['Hours'], errors='coerce').fillna(0)
# 8. Pivot to get 'should' and 'is' side-by-side
# We group by Date/Task and sum up the 'should' hours and 'is' hours separately
df_pivot = df_long.pivot_table(
index=['Date', 'KW', 'Task'],
columns='Type',
values='Hours',
aggfunc='sum'
).reset_index()
# Ensure we have both columns (fill with 0 if missing)
for col in ['should', 'is']:
if col not in df_pivot.columns:
df_pivot[col] = 0.0
df_pivot = df_pivot.fillna(0)
# 9. Apply Order & Sort
df_pivot['Task'] = pd.Categorical(df_pivot['Task'], categories=original_task_order, ordered=True)
df_pivot['Date'] = pd.to_datetime(df_pivot['Date'], errors='coerce')
# Remove the 'Total' row and any rows where Task might be 'Total' or NaN
df_final = df_pivot[
(df_pivot['Task'].notna()) &
(~df_pivot['Task'].astype(str).str.contains('Total|Summe|Tätigkeit', case=False))
].copy()
# Filter out rows where NOTHING happened (neither plan nor actual)
df_final = df_pivot[(df_pivot['should'] > 0) | (df_pivot['is'] > 0)].copy()
df_final = df_final.sort_values(by=['Date', 'Task'])
# --- YAML Output ---
print("### last updated at: " + datetime.datetime.now().strftime("%d-%m-%Y %H:%M:%S") + " ###\n")
current_date = None
for _, row in df_final.iterrows():
# Format date
date_str = f"{row['Date'].day}-{row['Date'].month}-{row['Date'].year}"
if date_str != current_date:
current_date = date_str
print(f"{date_str}:")
# Convert Hours to Minutes
should_min = int(row['should'] * 60)
is_min = int(row['is'] * 60)
print(f" - name: {row['Task']}")
print(f" should: {should_min}")
print(f" is: {is_min}")
+3
View File
@@ -0,0 +1,3 @@
pandas>=2.1.0
openpyxl
numpy
View File
Whitespace-only changes.
+50
View File
@@ -0,0 +1,50 @@
CANDIDATE = Max_Mustermann
OUTPUT_NAME = IPA_Dokumentation_$(CANDIDATE).pdf
## drawio variables
DRAWIO_CMD = drawio
DRAWIO_DIR = 02_Grafiken
DIAGRAM_SRCS= $(wildcard $(DRAWIO_DIR)/*.drawio)
DIAGRAMS = $(DIAGRAM_SRCS:drawio=pdf)
# Find files specifically inside the subdirectory
.PHONY: build-all
build-all: $(OUTPUT_NAME)
$(OUTPUT_NAME): $(DIAGRAMS) 03_Zeitplan/Zeitplan.pdf sourcepaths.txt versionhistory.csv
typst compile ./main.typ $(OUTPUT_NAME) --root .
.PHONY: watch
watch: $(DIAGRAMS) 03_Zeitplan/Zeitplan.pdf sourcepaths.txt versionhistory.csv timetable.yaml
typst watch ./main.typ $(OUTPUT_NAME) --root .
03_Zeitplan/Zeitplan.pdf: 03_Zeitplan/Zeitplan.xlsx
soffice --headless --convert-to 'pdf:calc_pdf_Export:{}' 03_Zeitplan/Zeitplan.xlsx --outdir 03_Zeitplan/
.PHONY: sourcepaths.txt
sourcepaths.txt:
bash -c "cd 04_Code && git ls-files > ../sourcepaths.txt"
.PHONY: versionhistory.csv
versionhistory.csv:
git log --pretty=format:"%ad,%an,%s" --date=short > ./versionhistory.csv
timetable.yaml: 03_Zeitplan/Zeitplan.xlsx
bash -c "cd 03_Zeitplan && python3 export.py > ../timetable.yaml"
.PHONY: drawio
drawio: $(DIAGRAMS)
# Pattern rule to convert .drawio -> .pdf inside the subdirectory
# $(DRAWIO_DIR)/%.pdf matches files like "05_Diagrams/network.pdf"
$(DRAWIO_DIR)/%.pdf: $(DRAWIO_DIR)/%.drawio
$(DRAWIO_CMD) -x -f pdf --crop -o $@ $<
+28
View File
@@ -0,0 +1,28 @@
# IPA
This IPA uses the chfoit-ipa template.
## Getting started
1. Add your name in the makefile
2. Adjust the path to your drawio executable
3. Fill in your data in the main.typ
4. Write your IPA using this template.
5. Commit using "chore: foo" for 0.0.x versions and with "feat: foo" for 0.x.0 versions.
## Compiling the document
To build your IPA just run:
```sh
make
```
If you want to have the PDF recompiled on changes you can run:
```sh
make watch
make drawio # to recompile the diagrams
```
+83
View File
@@ -0,0 +1,83 @@
#import "@local/chfoit-ipa:0.0.0": arbeitsjournal, ipa
#arbeitsjournal.addTimetable(yaml("timetable.yaml"))
#let candidate = "Max Mustermann"
#let peoples = (
(
name: "Kandidat*in",
email: "kandidat.in@bit.admin.ch",
phone: "+41 79 420 42 42",
shorthand: "KI",
function: "Kandidat",
git: "xxxxxxxxx",
),
(
name: "Praxisbildnre*in",
email: "praxis.bildner@bit.admin.ch",
shorthand: "PB",
function: "Verantwortliche Fachkraft",
),
(
name: "Hauptexpert*in",
email: "haupt.expert@example",
shorthand: "HE",
function: "Hauptexperte",
),
(
name: "Nebenexpert*in",
email: "nebenexpert.in@example",
shorthand: "NE",
function: "Nebenexperte",
),
(
name: "Validierungsexpert*in",
email: "valid.expert@example",
shorthand: "VE",
function: "Validierungsexperte",
),
(
name: "Berufsbildner*in",
email: "berufs.bilder@example",
shorthand: "BB",
function: "Berufsbildner",
),
)
#show: ipa.with(
title: "Entwicklung einer Applikation.",
company: "Bundesamt für Informatik und Telekommunikation BIT",
departement: "Entwicklung 5 - BS-BSC-EN5",
peoples: peoples,
metadata: (
specialisation: "Applikationsentwicklung",
managementMethod: "HERMES",
// year as in "Jahrgang"
place: "Kanton Bern",
dateOfSubmission: datetime(day: 1, month: 1, year: 2026),
commits: csv("versionhistory.csv"),
),
bibliography: bibliography("01_Dokumentation/works.yaml"),
part1: (
content: [
#include "01_Dokumentation/01_Teil1/index.typ"
],
),
part2: (
content: [
#include "01_Dokumentation/02_Teil2/index.typ"
],
),
appendix: (
files: read("./sourcepaths.txt")
.split("\n")
.filter(it => it != "")
.map(p => (path: p, content: read("./04_Code/" + p))),
body: {
include "./01_Dokumentation/03_Anhang/index.typ"
},
acronyms: yaml("./01_Dokumentation/acronyms.yaml"),
terms: yaml("./01_Dokumentation/terms.yaml"),
),
)
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "chfoit-ipa"
version = "0.0.0"
entrypoint = "lib.typ"
authors = ["niklas.breitenstein@bit.admin.ch"]
license = "MIT-0"
description = "Library and Template for IPA for developer apprenticeship at the Federal Office of Information Technology of Switzerland"
[template]
path = "template"
entrypoint = "main.typ"
+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],
)
}