diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index f091c5c..5147f5e 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -8,7 +8,21 @@ on: permissions: contents: write jobs: + build-arc42: + runs-on: ubuntu-latest + container: texlive/texlive:latest + steps: + - uses: actions/checkout@v4 + - name: Build arc42 documentation + working-directory: documentation + run: make + - uses: actions/upload-artifact@v4 + with: + name: arc42-pdf + path: documentation/documentation.pdf + deploy: + needs: build-arc42 runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -27,4 +41,12 @@ jobs: restore-keys: | mkdocs-material- - run: pip install -r docs/requirements.txt - - run: mkdocs gh-deploy --force \ No newline at end of file + - run: mkdocs build + - uses: actions/download-artifact@v4 + with: + name: arc42-pdf + path: site/ + - uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./site diff --git a/docs/api.md b/docs/api.md index 91ba587..bdbc46b 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1,186 +1,43 @@ # API reference -Base URL: `http://localhost:8080/api/v1` - -All responses are JSON. All endpoints are read-only — ignis does not modify the database. - ---- - -## Health check - -``` -GET /health -``` - -Returns `200 OK` when the server is running. Does not check the database connection. - -**Response** - -```json -{ "status": "OK" } -``` - ---- - -## List variants - -``` -GET /api/v1/variants/{country_iso2} -``` - -Returns all available building variant codes for a country. - -**Path parameters** - -| Parameter | Description | Example | -|---|---|---| -| `country_iso2` | ISO 3166-1 alpha-2 country code | `DE` | - -**Response** - -```json -{ - "country": "germany", - "data": ["DE.N.SFH.01.Gen", "DE.N.SFH.01.ReEx", ...] -} -``` - ---- - -## Match variants - -``` -GET /api/v1/variants/{country_iso2}/match?type={type}&period={period} -``` - -Returns all refurbishment levels for a specific building type and construction period, ordered from existing state to most-refurbished. - -**Path parameters** - -| Parameter | Description | Example | -|---|---|---| -| `country_iso2` | ISO 3166-1 alpha-2 country code | `DE` | - -**Query parameters** - -| Parameter | Description | Example | -|---|---|---| -| `type` | Building type code | `SFH`, `MFH`, `TH`, `AB` | -| `period` | Construction period code | `01`, `02`, `03` | - -**Response** - -```json -{ - "country": "germany", - "prefix": "DE.N.SFH.01", - "data": [ - { "code": "DE.N.SFH.01.Gen", "label": "Existing state" }, - { "code": "DE.N.SFH.01.ReEx", "label": "Medium refurbishment" }, - { "code": "DE.N.SFH.01.ReAd", "label": "Advanced refurbishment" } - ] -} -``` - ---- - -## Get variant data - -``` -GET /api/v1/data/{code} -``` - -Returns the raw TABULA parameters for a building variant. Used by client applications to populate their own forms or models. - -**Path parameters** - -| Parameter | Description | Example | -|---|---|---| -| `code` | TABULA variant code | `DE.N.SFH.01.Gen` | - -**Response** - -```json -{ - "country": "germany", - "variant_code": "DE.N.SFH.01.Gen", - "tabula_data": { ... }, - "expected_q_h_nd": 123.45 -} -``` - -!!! note - `tabula_data` contains the full set of ~200 TABULA parameters. `expected_q_h_nd` is the reference value from the workbook, used for validation. - ---- - -## List field metadata - -``` -GET /api/v1/fields -``` - -Returns a static description of every TABULA input field used by ignis's clients: where to find it in a `GET /api/v1/data/:code` response (`path`), its unit, a short label, and two descriptions — a plain-language one for non-experts and a technical one. This list is identical for every country, since the underlying database schema is uniform across all 20 TABULA countries. - -**Response** - -```json -{ - "data": [ - { - "key": "HeatingDays", - "group": "ClimateConditions", - "path": "AdvancedParameters.ClimateConditions.HeatingDays", - "unit": "days/year", - "label": "Heating days", - "simple_description": "How many days a year the building typically needs heating, based on local climate.", - "expert_description": "Number of heating days per year." - } - ] -} -``` - -!!! note - This endpoint is intended to power a future interactive building-description questionnaire in client applications: `simple_description` becomes the question text, and the field's own value from the matched TABULA variant (via `/api/v1/data/:code`) becomes the suggested default a user can accept if they don't know the answer. - ---- - -## Calculate heat demand - -``` -POST /api/v1/calculate/{code} -``` - -Runs the 17-level ISO 13790 pipeline for the specified building variant and returns the annual heating energy demand. - -**Path parameters** - -| Parameter | Description | Example | -|---|---|---| -| `code` | TABULA variant code | `DE.N.SFH.01.Gen` | - -**Request body** (optional) - -```json -{ "A_ref": 150.0 } -``` - -`A_ref` overrides the reference floor area stored in the TABULA record. Omit the body to use the TABULA default. - -**Response** - -```json -{ - "variant_code": "DE.N.SFH.01.Gen", - "q_h_nd": 123.45, - "unit": "kWh/(m2.a)" -} -``` - -**Error responses** - -| Status | Condition | -|---|---| -| `400` | Invalid variant code format, unknown country, invalid `A_ref` | -| `404` | Variant code not found in database | -| `500` | Pipeline execution failed or returned a non-finite result | +The interactive reference below is generated from the OpenAPI spec +([`openapi.yaml`](openapi.yaml)), the machine-readable source of truth for +every endpoint, schema, and error. Download it to generate a client, load it +into Postman, or import it into another tool. + +## How to consume the API + +ignis has no authentication of its own. It runs behind a reverse proxy, and +that proxy is the only way in: any backend service that wants to use ignis +sends a valid `X-Api-Key` header, and the proxy rejects anything else with +`403` before ignis ever sees the request. ignis is meant to be called by a +trusted server-side caller, never directly by a browser or an end user's +client, since the key must not be visible outside that caller. If the system +in front of ignis has its own user-facing login (EnerPlanET, for example, +uses Keycloak), that login authenticates the user **to that system**, not to +ignis; that system's own backend then calls ignis on the user's behalf, using +the API key. The full model is in the spec's description. + +!!! note "Base URL" + All paths are served through the reverse proxy. In local development that is + `https://localhost`; in a deployment it is whatever host the proxy is + published on. + +## Testing it yourself + +The Swagger UI below can call a locally running ignis directly. + +1. Start the stack, from `environment/`: `docker compose up -d`. On a first + run, load the TABULA data once: `docker compose exec ignis-app ./bin/build_db`. +2. Serve these docs locally with `mkdocs serve`. The reverse proxy already + allows requests from `http://localhost:8000` (its default port). +3. If your browser has never trusted the local proxy's certificate, open + `https://localhost` directly once and accept it (or run `caddy trust`). +4. Click **Authorize** below and enter the API key checked by the reverse + proxy (`X-Api-Key`; the prototype default is set in + `environment/Caddyfile`). It applies to every **Try it out** call from + then on. +5. Expand an endpoint, click **Try it out**, fill in the parameters, and + **Execute**. + + diff --git a/docs/getting-started.md b/docs/getting-started.md index 80d4697..16ad83a 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -23,10 +23,10 @@ cp .env.example .env | `DB_HOST` | PostgreSQL host | `localhost` | | `DB_PORT` | PostgreSQL port | `5432` | | `DB_USER` | Database user | `postgres` | -| `DB_PASSWORD` | Database password | — required, no default | +| `DB_PASSWORD` | Database password | required, no default | | `DB_NAME` | Database name | `ignis` | | `DB_SSL_MODE` | TLS mode (`require` / `disable`) | `require` | -| `ALLOWED_ORIGINS` | Comma-separated list of allowed CORS origins | — unset rejects all cross-origin requests | +| `ALLOWED_ORIGINS` | Comma-separated list of allowed CORS origins | unset rejects all cross-origin requests | !!! info "ALLOWED_ORIGINS" Only needed when a browser-based client calls ignis directly. For server-to-server calls (the intended deployment model), leave it unset. @@ -44,7 +44,7 @@ go build -o bin/validate cmd/validate/main.go ## Load the database -Loads the TABULA workbook into PostgreSQL. This is destructive — it drops and recreates the `tabula` schema. +Loads the TABULA workbook into PostgreSQL. This is destructive: it drops and recreates the `tabula` schema. ```bash ./bin/build_db @@ -68,7 +68,5 @@ See the [validation report](validation.md) for current results. ## Deployment -ignis is a microservice — it should run on a private Docker network with no public port exposed. The parent application (e.g. EnerPlanET) proxies requests to it. Authentication and rate limiting are the parent application's responsibility. - !!! danger "Do not expose ignis directly to the internet" - The API has no authentication. Restrict network access at the infrastructure level. + The API has no authentication of its own. Run it behind a reverse proxy on a private network, with no public port exposed on ignis itself. See the [arc42 architecture documentation](https://github.com/thd-spatial-ai/ignis/tree/main/documentation) for the deployment topology, reverse-proxy setup, and access-control design. diff --git a/docs/index.md b/docs/index.md index de8a1d8..867fc45 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,7 +4,7 @@ Go microservice implementing the ISO 13790 annual heating energy demand calculat ignis is designed as an internal microservice. Given a TABULA building variant code, it returns the annual heating energy demand in kWh/(m²·a) via a simple REST API. -**19/20 countries at 100% accuracy — 2,091 / 2,147 buildings validated.** See the [validation report](validation.md). +**19/20 countries at 100% accuracy: 2,091 / 2,147 buildings validated.** See the [validation report](validation.md). --- @@ -14,8 +14,8 @@ ignis is designed as an internal microservice. Given a TABULA building variant c |---|---| | [Getting started](getting-started.md) | Installation, configuration, building, running | | [API reference](api.md) | All endpoints with request/response examples | -| [Pipeline](pipeline.md) | How the 17-level ISO 13790 calculation works | | [Validation](validation.md) | Per-country accuracy results | +| [Architecture (PDF)](documentation.pdf) | Full arc42 architecture documentation | --- diff --git a/docs/open-source-checklist.md b/docs/open-source-checklist.md index 05bcc75..fabf2d1 100644 --- a/docs/open-source-checklist.md +++ b/docs/open-source-checklist.md @@ -29,7 +29,7 @@ Quick links: [LICENSE](#license) · [README](#readme) · [CONTRIBUTING](#contrib ### LICENSE - [x] `LICENSE` file present in repository root -- [x] Appropriate license chosen — MIT +- [x] Appropriate license chosen: MIT - [x] License committed to repository ### README @@ -71,7 +71,7 @@ If your repository contains large files (e.g. datasets, binaries, media, generat - [x] Git LFS installed locally - [x] Git LFS initialised in repository (`git lfs install`) -- [x] Large file types tracked — `*.xlsx`, `*.csv`, archives, media (`data/tabula-calculator.xlsx`) +- [x] Large file types tracked: `*.xlsx`, `*.csv`, archives, media (`data/tabula-calculator.xlsx`) - [x] `.gitattributes` committed to repository - [x] Large files added and committed properly - [ ] Release strategy for LFS files documented (if applicable) @@ -90,7 +90,7 @@ If your project uses third-party components, assets, or generated code that requ - [x] `ATTRIBUTIONS.md` file created in repository root - [x] TABULA building typology data (IWU) listed - [x] Source platform or author identified -- [x] License type specified — CC BY 4.0 +- [x] License type specified: CC BY 4.0 - [x] Links to original sources included - [x] Go dependencies noted @@ -110,10 +110,10 @@ For full specification details, see [Citation File Format](https://citation-file - [x] `CITATION.cff` file created in repository root - [x] `cff-version` set to `1.2.0` -- [x] Project title specified — ignis -- [x] Author listed — THD-Spatial-AI Research Group, Technische Hochschule Deggendorf +- [x] Project title specified: ignis +- [x] Author listed: THD-Spatial-AI Research Group, Technische Hochschule Deggendorf - [ ] ORCID included for each author (add when individual authors are listed) -- [x] License field matches `LICENSE` — MIT +- [x] License field matches `LICENSE`: MIT - [x] Repository URL included - [x] Version and release date set - [ ] File validates against the CFF schema (use [cff-validator](https://github.com/citation-file-format/cff-initializer-javascript)) diff --git a/docs/openapi.yaml b/docs/openapi.yaml new file mode 100644 index 0000000..b9e5e97 --- /dev/null +++ b/docs/openapi.yaml @@ -0,0 +1,412 @@ +openapi: 3.0.3 + +info: + title: ignis API + version: 1.0.0 + description: | + ignis calculates annual heating energy demand (`q_h_nd`, in kWh/(m²·a)) + for European building typologies, following EN ISO 13790 over the TABULA + building dataset. Given a TABULA variant code, it returns the demand and the + underlying parameters. All endpoints are read-only. + + ## Authentication + + ignis has no authentication of its own. It runs behind a reverse proxy, + and the proxy is the only way to reach it. Any backend service that wants + to use ignis sends a valid `X-Api-Key` header on every request; the proxy + checks that header and rejects anything else with `403` before the + request ever reaches ignis. + + ignis is meant to be called by a trusted, server-side caller — never + directly by a browser or an end user's client, since the key must not be + visible outside that caller. If the system calling ignis has its own + user-facing login (EnerPlanET, for example, uses Keycloak), that login + authenticates the user *to that system*; ignis never sees it. That + system's own backend then calls ignis on the user's behalf, using the API + key described below. + + The API key is a prototype-stage credential, suitable for local + development and evaluation. A production deployment should replace it + with a short-lived token or mutual TLS between the caller and ignis; the + request/response contract of the endpoints does not change if it does. + license: + name: MIT + url: https://github.com/thd-spatial-ai/ignis/blob/main/LICENSE + contact: + name: THD-Spatial-AI + url: https://github.com/thd-spatial-ai/ignis + +servers: + - url: https://localhost + description: Local development, via the reverse proxy (ignis-reverse-proxy) + - url: https://{host} + description: Deployed behind the platform's reverse proxy + variables: + host: + default: ignis.example.org + description: The public host the reverse proxy is published on + +tags: + - name: Health + description: Liveness check + - name: Variants + description: Look up TABULA building variant codes + - name: Data + description: Read building parameters and field metadata + - name: Calculation + description: Run the heating-demand pipeline + +security: + - ApiKeyAuth: [] + +paths: + /health: + get: + tags: [Health] + summary: Liveness check + description: Returns 200 while the server is running. Does not check the database. + responses: + "200": + description: Server is up + content: + application/json: + schema: + $ref: "#/components/schemas/HealthResponse" + "403": + $ref: "#/components/responses/Forbidden" + + /api/v1/variants/{country_iso2}: + get: + tags: [Variants] + summary: List all variant codes for a country + parameters: + - $ref: "#/components/parameters/CountryIso2" + responses: + "200": + description: Variant codes for the country + content: + application/json: + schema: + $ref: "#/components/schemas/VariantsResponse" + "400": + $ref: "#/components/responses/BadRequest" + "403": + $ref: "#/components/responses/Forbidden" + + /api/v1/variants/{country_iso2}/match: + get: + tags: [Variants] + summary: Match refurbishment variants for a building type and period + description: > + Returns the refurbishment levels for one building type and construction + period, ordered from existing state to most refurbished. + parameters: + - $ref: "#/components/parameters/CountryIso2" + - name: type + in: query + required: true + description: TABULA building type code + schema: + type: string + enum: [SFH, TH, MFH, AB] + example: SFH + - name: period + in: query + required: true + description: TABULA construction-period code + schema: + type: string + example: "01" + responses: + "200": + description: Matched refurbishment levels + content: + application/json: + schema: + $ref: "#/components/schemas/MatchResponse" + "400": + $ref: "#/components/responses/BadRequest" + "403": + $ref: "#/components/responses/Forbidden" + + /api/v1/data/{code}: + get: + tags: [Data] + summary: Get raw TABULA parameters for a variant + parameters: + - $ref: "#/components/parameters/VariantCode" + responses: + "200": + description: Full parameter set for the variant + content: + application/json: + schema: + $ref: "#/components/schemas/DataResponse" + "400": + $ref: "#/components/responses/BadRequest" + "403": + $ref: "#/components/responses/Forbidden" + "404": + $ref: "#/components/responses/NotFound" + + /api/v1/fields: + get: + tags: [Data] + summary: List field metadata + description: > + Static description of every TABULA input field: where it sits in a + /data/{code} response, its unit, a short label, and plain and technical + descriptions. Identical for every country. + responses: + "200": + description: Field metadata + content: + application/json: + schema: + $ref: "#/components/schemas/FieldsResponse" + "403": + $ref: "#/components/responses/Forbidden" + + /api/v1/calculate/{code}: + post: + tags: [Calculation] + summary: Run the heating-demand pipeline for a variant + parameters: + - $ref: "#/components/parameters/VariantCode" + requestBody: + required: false + description: Optional overrides. Omit the body to use the TABULA defaults. + content: + application/json: + schema: + $ref: "#/components/schemas/CalculateRequest" + responses: + "200": + description: Calculated annual heating demand + content: + application/json: + schema: + $ref: "#/components/schemas/CalculateResponse" + "400": + $ref: "#/components/responses/BadRequest" + "403": + $ref: "#/components/responses/Forbidden" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/ServerError" + +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-Api-Key + description: > + Static key checked by the reverse proxy. Sent by the trusted platform + backend, not by an end user's browser. A request without a valid key is + rejected with 403 before it reaches ignis. + + parameters: + CountryIso2: + name: country_iso2 + in: path + required: true + description: ISO 3166-1 alpha-2 country code + schema: + type: string + minLength: 2 + maxLength: 2 + example: DE + VariantCode: + name: code + in: path + required: true + description: > + TABULA variant code. The first two characters are the ISO 3166-1 alpha-2 + country code, which selects the database table. + schema: + type: string + example: DE.N.SFH.01.Gen.ReEx.001.001 + + responses: + BadRequest: + description: Invalid input (unknown country, malformed code, invalid A_ref) + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + example: + error: no TABULA dataset configured for XX + Forbidden: + description: Missing or invalid API key (enforced by the reverse proxy) + content: + text/plain: + schema: + type: string + example: "Forbidden: missing or invalid API key" + NotFound: + description: Variant code not found + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + example: + error: Variant not found + ServerError: + description: Pipeline failed or returned a non-finite result + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + + schemas: + HealthResponse: + type: object + properties: + status: + type: string + example: OK + required: [status] + + VariantsResponse: + type: object + properties: + country: + type: string + description: Country name for the queried ISO code + example: germany + data: + type: array + items: + type: string + description: All variant codes for the country + required: [country, data] + + MatchResponse: + type: object + properties: + country: + type: string + example: germany + prefix: + type: string + description: Shared code prefix for the matched building type and period + example: DE.N.SFH.01 + data: + type: array + items: + $ref: "#/components/schemas/VariantLevel" + required: [country, prefix, data] + + VariantLevel: + type: object + properties: + code: + type: string + example: DE.N.SFH.01.Gen.ReEx.001.001 + label: + type: string + example: Existing state + required: [code, label] + + DataResponse: + type: object + properties: + country: + type: string + example: germany + variant_code: + type: string + example: DE.N.SFH.01.Gen.ReEx.001.001 + expected_q_h_nd: + type: number + format: double + description: TABULA reference value for this variant, used for validation + example: 282.7 + tabula_data: + $ref: "#/components/schemas/TabulaData" + required: [country, variant_code, tabula_data] + + TabulaData: + type: object + description: > + Full TABULA parameter set (~200 fields), nested under two groups. Use + /api/v1/fields to resolve each field's path, unit, and label. + properties: + BasicParameters: + type: object + additionalProperties: true + AdvancedParameters: + type: object + additionalProperties: true + + FieldsResponse: + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/FieldMetadata" + required: [data] + + FieldMetadata: + type: object + properties: + key: + type: string + example: A_C_Ref_Input + group: + type: string + example: Envelope + path: + type: string + description: Dotted path into a /data/{code} response + example: BasicParameters.Envelope.A_C_Ref_Input + unit: + type: string + example: m² + label: + type: string + example: Reference floor area + simple_description: + type: string + example: The total heated floor area of the building. + expert_description: + type: string + example: Reference floor area from input dataset, used for calculating area fractions for measures. + required: [key, group, path, unit, label] + + CalculateRequest: + type: object + properties: + A_ref: + type: number + format: double + description: > + Overrides the reference floor area from the TABULA record. Must be + greater than 0. + example: 150.0 + + CalculateResponse: + type: object + properties: + variant_code: + type: string + example: DE.N.SFH.01.Gen.ReEx.001.001 + q_h_nd: + type: number + format: double + description: Annual net heating energy demand + example: 282.72 + unit: + type: string + example: kWh/(m2.a) + required: [variant_code, q_h_nd, unit] + + ErrorResponse: + type: object + properties: + error: + type: string + required: [error] diff --git a/docs/pipeline.md b/docs/pipeline.md deleted file mode 100644 index afeeda6..0000000 --- a/docs/pipeline.md +++ /dev/null @@ -1,71 +0,0 @@ -# Pipeline - -ignis implements the ISO 13790 monthly calculation method as a sequential 17-level pipeline. Each level is a separate Go struct that receives the outputs of earlier levels as constructor arguments — there is no shared mutable state between levels. - -## Architecture - -```mermaid -flowchart TD - IN[TabulaBuildingParameters
from PostgreSQL] - L1[Level 1
Area estimates · Insulation measures
Ventilation · Solar transmittance] - L2[Level 2
Reference area · Storey count
Thermal resistances after measures] - L3[Level 3
Storey area · Volume estimate
U-values before measures] - L4[Level 4
Estimated envelope areas
U-values after measures] - L5[Level 5
Actual U-values
Calc areas for all elements] - L6[Level 6
Wall areas · Floor/window checks
Heat transmission: roofs, floors, windows] - L7[Level 7
Envelope sum · Wall areas
Solar gains by orientation] - L8[Level 8
Envelope ratio
Refurbished fraction · Total solar gain] - L9[Level 9
Envelope plausibility check
Thermal bridging type · ΔU] - L10[Level 10
Area plausibility check
Thermal bridging transmission] - L11[Level 11
Total transmission heat transfer
coefficient H_Tr W/m²K] - L12[Level 12
Temperature reduction factor
Time constant τ] - L13[Level 13
Transmission + ventilation heat
transfer · Heat adaptation factor] - L14[Level 14
Total heat transfer q_ht] - L15[Level 15
Gain–loss ratio γ] - L16[Level 16
Gain utilisation factor η] - L17[Level 17
Net heating demand
q_h_nd kWh/m²·a] - - IN --> L1 --> L2 --> L3 --> L4 --> L5 --> L6 --> L7 --> L8 - L8 --> L9 --> L10 --> L11 --> L12 --> L13 --> L14 --> L15 --> L16 --> L17 -``` - -## Key outputs - -| Level | Key output | Unit | -|---|---|---| -| 11 | `H_Transmission` — heat transfer coefficient of building envelope | W/(m²·K) | -| 12 | `F_red_temp` — temperature reduction factor; `τ` — time constant | —; h | -| 14 | `q_ht` — total heat transfer (transmission + ventilation) | kWh/(m²·a) | -| 15 | `γ_H,gn` — gain–loss ratio (solar + internal gains vs heat transfer) | — | -| 16 | `η_H,gn` — gain utilisation factor | — | -| **17** | **`q_h_nd`** — **net annual heating energy demand** | **kWh/(m²·a)** | - -## Final formula - -``` -q_h_nd = q_ht − η_H,gn × (q_sol + q_int) -``` - -Where: -- `q_ht` — total heat transfer (transmission + ventilation losses) -- `η_H,gn` — gain utilisation factor (how much of the free heat is actually useful) -- `q_sol` — solar heat gains per m² -- `q_int` — internal heat gains per m² - -## Source files - -Each level is a separate file in `internal/calc/`: - -``` -internal/ -├── calc/ -│ ├── calc_level_01.go … calc_level_17.go # calculation levels -└── hdcp/ - └── pipeline.go # orchestrates all 17 levels -``` - -`pipeline.go` constructs each level in order, passing the outputs of earlier levels into later constructors. The pipeline returns the `q_h_nd` value from Level 17. - -## Data source - -All building parameters (U-values, areas, insulation thicknesses, climate conditions, solar gains, thermal bridges) come from the TABULA Excel workbook, loaded into PostgreSQL by `cmd/build_db`. The methodology follows the TABULA/EPISCOPE calculation approach defined in IEE Project TABULA (2009–2012). diff --git a/docs/requirements.txt b/docs/requirements.txt index 1261fa2..8f0df11 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,4 @@ mkdocs>=1.6.0 mkdocs-material>=9.5.0 pymdown-extensions>=10.0 +mkdocs-swagger-ui-tag>=0.6.0 diff --git a/docs/validation.md b/docs/validation.md index 5b2b374..ce6525a 100644 --- a/docs/validation.md +++ b/docs/validation.md @@ -33,9 +33,9 @@ Pipeline validated against the TABULA reference values (±2% tolerance on `q_h_n --- -## Spain — known issue +## Spain: known issue -56 of 72 buildings fail (77.8%). The failures are systematic — calculated values consistently deviate from the TABULA reference — which points to a Spain-specific parameter rather than a general pipeline bug. +56 of 72 buildings fail (77.8%). The failures are systematic (calculated values consistently deviate from the TABULA reference), which points to a Spain-specific parameter rather than a general pipeline bug. **Status:** under investigation. @@ -53,7 +53,7 @@ Pipeline validated against the TABULA reference values (±2% tolerance on `q_h_n - **Tolerance:** ±2% on `q_h_nd` (annual heating energy demand, kWh/(m²·a)) - **Reference:** TABULA Excel workbook (`data/tabula-calculator.xlsx`) -- **Pipeline:** 17-level cascading calculation — geometry, envelope, U-values, climate, solar gains, thermal bridges, heat transfer coefficients +- **Pipeline:** 17-level cascading calculation: geometry, envelope, U-values, climate, solar gains, thermal bridges, heat transfer coefficients --- diff --git a/documentation/.gitignore b/documentation/.gitignore new file mode 100644 index 0000000..15fc7c9 --- /dev/null +++ b/documentation/.gitignore @@ -0,0 +1,48 @@ +*.acn +*.acr +*.alg +*.aux +*.bak +*.bbl +*.bcf +*.blg +*.brf +*.bst +*.dvi +*.fdb_latexmk +*.fls +*.glg +*.glo +*.gls +*.idx +*.ilg +*.ind +*.ist +*.lof +*.log +*.lol +*.lot +*.maf +*.mtc +*.mtc1 +*.nav +*.nlo +*.nls +*.out +/documentation.pdf +*.pyg +*.run.xml +*.snm +*.synctex.gz +*.tex.backup +*.tex~ +*.thm +*.toc +*.vrb +*.xdy +*.xml +*blx.bib +.bak +.mtc +build/ +figures/.$*.bkp \ No newline at end of file diff --git a/documentation/.gitkeep b/documentation/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/documentation/Makefile b/documentation/Makefile new file mode 100644 index 0000000..8bdb5f1 --- /dev/null +++ b/documentation/Makefile @@ -0,0 +1,16 @@ +DIR=$(notdir ${PWD}) + +.PHONY: all compile clean rename + +all: compile rename clean + +compile: + pdflatex --shell-escape main.tex + bibtex main + pdflatex --shell-escape main.tex + pdflatex --shell-escape main.tex +clean: + rm *.aux *.log *.out *.bbl *.blg *.toc *.lua *.brf + rm -rf _markdown_* +rename: + mv main.pdf ${DIR}.pdf \ No newline at end of file diff --git a/documentation/content/01-introduction-goals.md b/documentation/content/01-introduction-goals.md new file mode 100644 index 0000000..3230e42 --- /dev/null +++ b/documentation/content/01-introduction-goals.md @@ -0,0 +1,2 @@ +# Introduction & Goals + diff --git a/documentation/content/02-constraints-table.tex b/documentation/content/02-constraints-table.tex new file mode 100644 index 0000000..8a98dda --- /dev/null +++ b/documentation/content/02-constraints-table.tex @@ -0,0 +1,14 @@ +\vfill +\begin{table}[h!] + \centering + \caption{External and organizational constrains.} + \begin{tabular}{c l} + \toprule + Nr. & Constraint \\ [0.5ex] + \midrule + 01 & - \\ [1ex] + \bottomrule + \end{tabular} + \label{table:2} +\end{table} +\vfill \ No newline at end of file diff --git a/documentation/content/02-constraints.md b/documentation/content/02-constraints.md new file mode 100644 index 0000000..c66e858 --- /dev/null +++ b/documentation/content/02-constraints.md @@ -0,0 +1 @@ +# Constraints diff --git a/documentation/content/03-context-scope.md b/documentation/content/03-context-scope.md new file mode 100644 index 0000000..aecd23e --- /dev/null +++ b/documentation/content/03-context-scope.md @@ -0,0 +1 @@ +# Context & Scope diff --git a/documentation/content/04-solution-strategy.md b/documentation/content/04-solution-strategy.md new file mode 100644 index 0000000..39ced56 --- /dev/null +++ b/documentation/content/04-solution-strategy.md @@ -0,0 +1 @@ +# Solution Strategy \ No newline at end of file diff --git a/documentation/content/05-building-block-view.md b/documentation/content/05-building-block-view.md new file mode 100644 index 0000000..021322d --- /dev/null +++ b/documentation/content/05-building-block-view.md @@ -0,0 +1 @@ +# Building Block View diff --git a/documentation/content/06-runtime-view-diagram.tex b/documentation/content/06-runtime-view-diagram.tex new file mode 100644 index 0000000..220e96f --- /dev/null +++ b/documentation/content/06-runtime-view-diagram.tex @@ -0,0 +1,5 @@ +\begin{figure}[htbp] +\centering +\includegraphics[width=\textwidth]{figures/06-runtime-view-sequence.pdf} +\caption{Runtime View: sequence of a preflight, an authorized request, and a rejected request. ignis's request handling is identical whether or not Caddy is in front of it; only Caddy's own behaviour differs across these three cases.} +\end{figure} diff --git a/documentation/content/06-runtime-view.md b/documentation/content/06-runtime-view.md new file mode 100644 index 0000000..868d7f1 --- /dev/null +++ b/documentation/content/06-runtime-view.md @@ -0,0 +1,21 @@ +# Runtime View + +A browser client calling ignis through the proxy produces two requests: a CORS preflight, then the real request. This section traces both, plus a rejected request. + +## Step 1: preflight (OPTIONS) + +Building Configurator calls `GET /api/v1/fields` from a page on `http://localhost:5173`. The target is a different origin (`https://localhost`) and the request carries a custom header (`X-Api-Key`), so the browser first sends an automatic `OPTIONS` preflight to ask permission. A preflight never carries the custom header itself. + +Caddy matches the preflight on method alone, before the API-key check, and answers it: it echoes the allowed origin, lists the allowed methods and headers, and returns `204 No Content`. The preflight never reaches ignis. + +## Step 2: the real request + +The browser now sends `GET /api/v1/fields` with `X-Api-Key` attached. Caddy checks the key. On a match, it forwards the request to `ignis-app:8080` over the internal Docker network as plain HTTP (encryption already ended at Caddy). ignis handles it as if no proxy existed: it checks the `Origin` against `ALLOWED_ORIGINS` and adds `Access-Control-Allow-Origin` to the response. Caddy passes the response back unchanged. + +## Step 3: rejected request + +A request with a missing or wrong `X-Api-Key` hits Caddy's fallback rule and gets `403 Forbidden`. It never reaches ignis. ignis publishes no host port, so the proxy is the only way in. + +## Why this matters + +ignis behaves identically with or without the proxy in front of it. Caddy adds the TLS, the key check, and the preflight response; ignis's code knows nothing about any of it. diff --git a/documentation/content/07-deployment-view-diagram.tex b/documentation/content/07-deployment-view-diagram.tex new file mode 100644 index 0000000..bdd4dc9 --- /dev/null +++ b/documentation/content/07-deployment-view-diagram.tex @@ -0,0 +1,5 @@ +\begin{figure}[htbp] +\centering +\includegraphics[width=\textwidth]{figures/07-deployment-view.drawio.pdf} +\caption{Deployment View: the three containers, all on one Docker Compose network in the \texttt{building-simulation} namespace. Only \texttt{ignis-reverse-proxy} publishes a host port; \texttt{ignis-app} and \texttt{ignis-db} are reachable only by service name from within the network. The stack needs nothing on the host except Docker.} +\end{figure} diff --git a/documentation/content/07-deployment-view.md b/documentation/content/07-deployment-view.md new file mode 100644 index 0000000..bb3d042 --- /dev/null +++ b/documentation/content/07-deployment-view.md @@ -0,0 +1,43 @@ +# Deployment View + +## The three containers + +ignis runs as three containers, defined in `environment/docker-compose.yml`. They form the `building-simulation` namespace (the Docker Compose project name), shared with other building-modelling services such as buem. The stack needs nothing on the host except Docker. + +1. **ignis-reverse-proxy**: Caddy, from the `caddy:2.11-alpine` image. The only container that publishes a host port. Every request passes through it first. + +2. **ignis-app**: the `bin/app` binary, built from `environment/Dockerfile`. Listens on the internal port (default 8080), publishes no host port, and reaches the database at `ignis-db`. + +3. **ignis-db**: PostgreSQL, from `postgres:17-alpine`. Publishes no host port. Its data lives in a named volume (`ignis-db-data`) that survives `docker compose down`/`up`, but not `down -v`. + +Every component is a container, so the whole stack can be built into images, pushed to a registry, and run elsewhere with the same compose file. There is no host-installed database to set up separately. + +## Ports + +Two ports, with different rules. + +- **Internal port** (`APP_PORT`, default 8080): the port `ignis-app` listens on inside its container. Container isolation means it never clashes with other services, so it stays the same everywhere. It is set once in `.env` and passed to the app, its health check, and the proxy's upstream, rather than hardcoded in each. + +- **Host port** (`HOST_HTTPS_PORT`, default 443): the port `ignis-reverse-proxy` publishes. This is the only port that can clash: two services cannot both take host 443. An orchestration layer assigns a free port here per service. + +## First-run data load + +A fresh `ignis-db` volume is empty. Load the TABULA data once, after first start: `docker compose exec ignis-app ./bin/build_db`. The data persists afterward. `build_db` drops and recreates all tables, so it is a manual step, not part of startup. + +## No host port on app or database + +`ignis-app` and `ignis-db` declare no `ports:`, so nothing outside the Docker network can connect to them. `ignis-app` is reachable only from `ignis-reverse-proxy`, and `ignis-db` only from `ignis-app`, each by its service name. The proxy is not an add-on in front of an open service: it is the only way in. + +## Startup order + +`depends_on: condition: service_healthy` chains startup: `ignis-db` must accept connections (checked with `pg_isready`) before `ignis-app` starts, and `ignis-app` must be healthy before `ignis-reverse-proxy` starts. The app's health check makes a real `GET /health` call (not `HEAD`, which the router does not register). Nothing starts serving before what it depends on is ready. + +## Certificate trust across recreation + +A fresh Caddy container would generate a new, untrusted certificate authority, breaking any trust the browser already had. `ignis-reverse-proxy` avoids this by mounting the host's Caddy data directory (`~/.local/share/caddy`, set via `CADDY_DATA_DIR`) into the container, so it reuses the same CA. A browser that ran `caddy trust` once keeps trusting it. + +This is a local-development convenience, tied to one machine. A real deployment replaces it with either public HTTPS (Caddy's built-in Let's Encrypt) or a shared internal CA every client trusts. + +## Network + +All three containers share one Docker Compose network (`building-simulation_default`). Docker's DNS resolves each service name to its container: the proxy reaches the app at `ignis-app`, the app reaches the database at `ignis-db`. This only works within the same network, which is why keeping everything on it (and nothing on a host port except the proxy) keeps the stack self-contained and closed. diff --git a/documentation/content/08-crosscutting-concepts.md b/documentation/content/08-crosscutting-concepts.md new file mode 100644 index 0000000..a0e805e --- /dev/null +++ b/documentation/content/08-crosscutting-concepts.md @@ -0,0 +1,31 @@ +# Crosscutting Concepts + +## TLS termination + +ignis's code has no TLS, no certificates, no HTTPS server. Encryption ends at the proxy: it holds the certificate, decrypts the request, and forwards it as plain HTTP over the internal Docker network. Any service behind the proxy gets this for free: certificates live in one place, not in every service. + +The internal hop from proxy to app is plain HTTP, so it is only as safe as the network it crosses. That is fine while the network is one we control (a single Docker network). It stops being fine if that hop ever crosses infrastructure we don't control, which is why mutual TLS on that hop is listed as future work. + +## Trust follows the CA, not the certificate + +A client doesn't trust a certificate on its own. It trusts the certificate authority (CA) that signed it, and that trust then covers every certificate the same CA signs. This is why per-service proxies scale: sign each one's certificate with the same CA (a public CA for internet-facing services, a shared internal CA for service-to-service), and a client establishes trust once, not once per service. + +Locally this is proven directly: `caddy trust` trusts the local CA once, and every certificate it signs afterward is trusted, including from a freshly recreated container. + +## The API-key gate + +Caddy checks for a valid `X-Api-Key` before forwarding to ignis. ignis knows nothing about this key: it never reads it. + +This is a prototype stand-in, not production auth. The current caller, Building Configurator, runs in the browser, so any key it sends is visible in its page source to anyone with developer tools. A static key shipped to the browser stops casual access but keeps no real secret. It stands in for the real caller in the target architecture, an orchestration layer running as a backend service, which can hold a credential the client never sees. What replaces the static key is settled in ADR-4. + +## CORS: two concerns kept apart + +A cross-origin request with a custom header triggers a preflight `OPTIONS` before the real request. ignis's own CORS middleware only knows `Content-Type`, correctly, since `X-Api-Key` is not part of ignis's API; it exists only because of the proxy. Teaching ignis about it would tie ignis's code to its deployment. + +So Caddy answers the preflight itself, advertising the headers and origins it accepts. The real request still passes ignis's own origin check (`ALLOWED_ORIGINS`) when it arrives. Each layer handles its own concern. + +## Open items + +- Mutual TLS on the proxy-to-app hop, for deployments where that hop leaves infrastructure we control. + +- Replacing the static API key with a credential issued by the real orchestration layer, once it exists. diff --git a/documentation/content/09-architectural-decisions.md b/documentation/content/09-architectural-decisions.md new file mode 100644 index 0000000..e94714a --- /dev/null +++ b/documentation/content/09-architectural-decisions.md @@ -0,0 +1,65 @@ +# Architectural Decisions + +## ADR-001: One reverse proxy per service, not a shared gateway + +**Status:** Decided + +**Context:** ignis needs TLS and an access check in front of it. Other services (buem and more) will need the same. + +**Decision:** Each service gets its own proxy, deployed with it. No shared gateway. + +**Reason:** A shared gateway ties every service's uptime to one process. Reconfiguring the proxy for one service (as we did to fix its CORS handling) would risk every other service behind it. A per-service proxy limits that to one service, and the same Caddy setup drops straight onto the next service. + +**Rejected:** A single shared API gateway. Reasonable at a different scale, but the wrong fit for per-service ownership. + +## ADR-002: Proxy and app in separate containers + +**Status:** Decided + +**Context:** Caddy and the app could run as two processes in one container. + +**Decision:** They run as separate containers. + +**Reason:** Docker expects one process per container. Two processes under a wrapper script lose supervision: if the app crashed, Docker wouldn't notice, since the script keeps running. Separate containers also let the proxy be reloaded or recreated without touching the app, which we relied on repeatedly during this work. + +**Rejected:** One container with a process supervisor (e.g. `supervisord`). Extra complexity for nothing this deployment needs. + +## ADR-003: Reuse one CA across container recreation + +**Status:** Decided for local development; production open + +**Context:** A fresh Caddy container generates a new, untrusted CA, breaking trust the browser already had. + +**Decision:** Mount the host's Caddy CA storage into the container so it reuses the same CA. + +**Reason:** Trust attaches to the CA, not the individual certificate (see Crosscutting Concepts). Reuse one CA and trust survives recreation, reloads, and adding more services signed by the same CA. + +**Rejected:** Re-running `caddy trust` after every recreation. Defeats the point and doesn't scale past one machine. + +**Open for production:** Mounting one developer's local CA is local-only. Production needs public HTTPS (Let's Encrypt) or a shared internal CA. Which one depends on where ignis sits relative to the internet, not yet decided. + +## ADR-004: Static API key as a prototype, not production auth + +**Status:** Open + +**Context:** ignis should be reachable only by a trusted caller. The real caller, an orchestration layer, doesn't exist yet. Building Configurator stands in for it. + +**Decision (provisional):** Caddy gates on a static, shared `X-Api-Key`. + +**Reason:** Enough to prove the proxy can enforce "only a known caller gets in." Not enough for production: Building Configurator runs in the browser, so the key is visible in its page source. + +**Rejected for now, not yet chosen between:** a short-lived token from the real orchestration layer, or mutual TLS giving each service its own identity. Both wait until that layer exists. Revisit then. + +## ADR-005: Group containers into per-concern namespaces, named - + +**Status:** Decided + +**Context:** The energy-modelling system is several services (ignis, buem, and more), each with a proxy, app, and sometimes a database. + +**Decision:** Group containers by concern into a Docker Compose namespace (project name), here `building-simulation`, shared by all building-modelling services. Name each container `-`: `ignis-app`, `ignis-db`, `ignis-reverse-proxy`, and later `buem-app`, `buem-reverse-proxy`, and so on. + +**Reason:** A container name then says both which service it belongs to and what role it plays, and the namespace groups related services together. The host port is the only thing that can clash between them, so it is set per service via `HOST_HTTPS_PORT`; the internal port stays fixed, since container isolation keeps it from clashing. + +**Rejected:** Directory-derived project names and ad-hoc container names. They don't convey role or concern and don't group cleanly as more services are added. + +**Open:** When buem joins, two repos each declaring the same namespace will coexist but Compose may warn about "orphan" containers. A single top-level compose (`include:`) or a shared external network resolves it. Decide when buem is wired in. diff --git a/documentation/content/10-quality-requirements.md b/documentation/content/10-quality-requirements.md new file mode 100644 index 0000000..6d6cd98 --- /dev/null +++ b/documentation/content/10-quality-requirements.md @@ -0,0 +1 @@ +# Quality Requirements \ No newline at end of file diff --git a/documentation/content/11-risks-technical-debt.md b/documentation/content/11-risks-technical-debt.md new file mode 100644 index 0000000..018cd48 --- /dev/null +++ b/documentation/content/11-risks-technical-debt.md @@ -0,0 +1 @@ +# Risks & Technical Debt diff --git a/documentation/content/12-glossary.md b/documentation/content/12-glossary.md new file mode 100644 index 0000000..1d77039 --- /dev/null +++ b/documentation/content/12-glossary.md @@ -0,0 +1 @@ +# Glossary \ No newline at end of file diff --git a/documentation/figures/06-runtime-view-sequence.pdf b/documentation/figures/06-runtime-view-sequence.pdf new file mode 100644 index 0000000..fbb3d29 Binary files /dev/null and b/documentation/figures/06-runtime-view-sequence.pdf differ diff --git a/documentation/figures/07-deployment-view.drawio.pdf b/documentation/figures/07-deployment-view.drawio.pdf new file mode 100644 index 0000000..0a59381 Binary files /dev/null and b/documentation/figures/07-deployment-view.drawio.pdf differ diff --git a/documentation/figures/DIT-Logo.png b/documentation/figures/DIT-Logo.png new file mode 100644 index 0000000..7bb2654 Binary files /dev/null and b/documentation/figures/DIT-Logo.png differ diff --git a/documentation/figures/diagrams.drawio b/documentation/figures/diagrams.drawio new file mode 100644 index 0000000..9e6e3af --- /dev/null +++ b/documentation/figures/diagrams.drawio @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/main.tex b/documentation/main.tex new file mode 100644 index 0000000..62e29eb --- /dev/null +++ b/documentation/main.tex @@ -0,0 +1,104 @@ +\documentclass[12pt,twoside]{report} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% Definitions for the title page +% Edit these to provide the correct information +% e.g. \newcommand{\reportauthor}{Timothy Kimber} + +\newcommand{\reporttitle}{Software Architecture} +\newcommand{\reportauthor}{Jay Ravani} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% load some definitions and default packages +\input{styles/includes} + +% load some macros +\input{styles/extentions} + +\date{\today} + +\begin{document} + +% load title page +\input{styles/titlepage} + + +% page numbering etc. +\pagenumbering{roman} +\clearpage{\pagestyle{empty}\cleardoublepage} +\setcounter{page}{1} +\pagestyle{fancy} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%--- table of contents +\fancyhead[RE,LO]{\sffamily {Table of Contents}} +\tableofcontents + +\pagenumbering{arabic} +\setcounter{page}{1} +% Both header slots show the chapter title. Previously the right-hand slot +% showed \rightmark (the current section title), which collided with +% \leftmark (the chapter title) on the opposite margin whenever a section +% heading was long enough for the two to run into each other mid-line. +\fancyhead[LE,RO]{\slshape \leftmark} +\fancyhead[LO,RE]{\slshape \leftmark} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Introduction & Goals +\markdownInput{content/01-introduction-goals.md} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Constraints +\markdownInput{content/02-constraints.md} +\input{content/02-constraints-table} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Context & Scope +\markdownInput{content/03-context-scope.md} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Solution Strategy +\markdownInput{content/04-solution-strategy.md} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Building Block View +\markdownInput{content/05-building-block-view.md} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Runtime View +\markdownInput{content/06-runtime-view.md} +\input{content/06-runtime-view-diagram} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Deployment View +\markdownInput{content/07-deployment-view.md} +\input{content/07-deployment-view-diagram} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Crosscutting Concepts +\markdownInput{content/08-crosscutting-concepts.md} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Architectural Decisions +\markdownInput{content/09-architectural-decisions.md} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Quality Requirements +\markdownInput{content/10-quality-requirements.md} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Risks \& Technical Debt +\markdownInput{content/11-risks-technical-debt.md} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Glossary +\markdownInput{content/12-glossary.md} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% References +\bibliographystyle{abbrvnat} +\bibliography{references} + +\end{document} diff --git a/documentation/references.bib b/documentation/references.bib new file mode 100644 index 0000000..b0828c2 --- /dev/null +++ b/documentation/references.bib @@ -0,0 +1,5 @@ +@misc{arc42website, + author = "Dr. Peter Hruschka and Dr. Gernot Starke", + title = "Arc42", + url = "http://www.arc42.org" +} \ No newline at end of file diff --git a/documentation/styles/extentions.tex b/documentation/styles/extentions.tex new file mode 100644 index 0000000..2e3ced4 --- /dev/null +++ b/documentation/styles/extentions.tex @@ -0,0 +1,8 @@ +% Here, you can define your own macros. Some examples are given below. + +\newcommand{\R}[0]{\mathds{R}} % real numbers +\newcommand{\Z}[0]{\mathds{Z}} % integers +\newcommand{\N}[0]{\mathds{N}} % natural numbers +\newcommand{\C}[0]{\mathds{C}} % complex numbers +\renewcommand{\vec}[1]{{\boldsymbol{{#1}}}} % vector +\newcommand{\mat}[1]{{\boldsymbol{{#1}}}} % matrix diff --git a/documentation/styles/includes.tex b/documentation/styles/includes.tex new file mode 100644 index 0000000..9329eb3 --- /dev/null +++ b/documentation/styles/includes.tex @@ -0,0 +1,89 @@ +%---------------------------------------------------------------------------------------- +% PACKAGES AND OTHER DOCUMENT CONFIGURATIONS +%---------------------------------------------------------------------------------------- +\usepackage[a4paper,hmargin=2.8cm,vmargin=2.0cm,includeheadfoot,headheight=42pt]{geometry} +\usepackage{textpos} +\usepackage[square,numbers]{natbib} % for bibliography +\usepackage{tabularx,longtable,multirow,subfigure,caption}%hangcaption +%\usepackage{fncylab} %formatting of labels +\usepackage{fancyhdr} % page layout +\usepackage{url} % URLs +\usepackage[english]{babel} +\usepackage{amsmath} +\usepackage{graphicx} +\usepackage{dsfont} +\usepackage{epstopdf} % automatically replace .eps with .pdf in graphics +\usepackage{backref} % needed for citations +\usepackage{array} +\usepackage{latexsym} +\usepackage[pdftex,pagebackref,hypertexnames=false,colorlinks]{hyperref} % provide links in pdf +\hypersetup{pdftitle={}, + pdfsubject={}, + pdfauthor={}, + pdfkeywords={}, + pdfstartview=FitH, + pdfpagemode={UseOutlines},% None, FullScreen, UseOutlines + bookmarksnumbered=true, bookmarksopen=true, colorlinks, + citecolor=black,% + filecolor=black,% + linkcolor=black,% + urlcolor=black} + +\usepackage[all]{hypcap} +\usepackage{booktabs} + +%\usepackage{color} +%\usepackage[tight,ugly]{units} +%\usepackage{float} +%\usepackage{tcolorbox} +%\usepackage[colorinlistoftodos]{todonotes} +% \usepackage{ntheorem} +% \theoremstyle{break} +% \newtheorem{lemma}{Lemma} +% \newtheorem{theorem}{Theorem} +% \newtheorem{remark}{Remark} +% \newtheorem{definition}{Definition} +% \newtheorem{proof}{Proof} + +\usepackage{markdown} + +%%% Default fonts +\renewcommand*{\rmdefault}{bch} +\renewcommand*{\ttdefault}{cmtt} + + + +%%% Default settings (page layout) +\setlength{\parindent}{0em} % indentation of paragraph + +\pagestyle{fancy} +\renewcommand{\chaptermark}[1]{\markboth{\chaptername\ \thechapter.\ #1}{}} + +\fancyfoot[ER,OL]{\sffamily\textbf{\thepage}}%Page no. in the left on odd pages and on right on even pages +\fancyfoot[OC,EC]{\sffamily } +\renewcommand{\headrulewidth}{0.1pt} +\renewcommand{\footrulewidth}{0.1pt} +\captionsetup{margin=10pt,font=small,labelfont=bf} + + +%--- chapter heading + +\def\@makechapterhead#1{% + \vspace*{10\p@}% + {\parindent \z@ \raggedright \sffamily + \interlinepenalty\@M + \Huge\bfseries \thechapter \space\space #1\par\nobreak + \vskip 30\p@ + }} + +%---chapter heading for \chapter* +\def\@makeschapterhead#1{% + \vspace*{10\p@}% + {\parindent \z@ \raggedright + \sffamily + \interlinepenalty\@M + \Huge \bfseries #1\par\nobreak + \vskip 30\p@ + }} + +\allowdisplaybreaks \ No newline at end of file diff --git a/documentation/styles/titlepage.tex b/documentation/styles/titlepage.tex new file mode 100644 index 0000000..2458863 --- /dev/null +++ b/documentation/styles/titlepage.tex @@ -0,0 +1,55 @@ +% Last modification: 2015-08-17 (Marc Deisenroth) +\begin{titlepage} + +\newcommand{\HRule}{\rule{\linewidth}{0.5mm}} % Defines a new command for the horizontal lines, change thickness here + + +%---------------------------------------------------------------------------------------- +% LOGO SECTION +%---------------------------------------------------------------------------------------- + +\includegraphics[width = 5.5cm]{./figures/DIT-Logo}\\[1.5cm] + +\center % Center remainder of the page + +%---------------------------------------------------------------------------------------- +% HEADING SECTIONS +%---------------------------------------------------------------------------------------- + +\textsc{\Large Deggendorf Institute of Technology}\\[0.5cm] +\textsc{\large School of Applied Computer Science}\\[0.5cm] + +%---------------------------------------------------------------------------------------- +% TITLE SECTION +%---------------------------------------------------------------------------------------- + +\HRule \\[0.4cm] +{ \huge \bfseries \reporttitle}\\ % Title of your document +\HRule \\[0.8cm] + +%---------------------------------------------------------------------------------------- +% AUTHOR SECTION +%---------------------------------------------------------------------------------------- + +\begin{minipage}{0.4\textwidth} +\begin{center} \large +\textcopyright Arc42 inspired \cite{arc42website} \\ [1.5cm] +\emph{Authors:}\\ +\reportauthor % Your name +\end{center} +\end{minipage} +~ + + + +%---------------------------------------------------------------------------------------- +% FOOTER & DATE SECTION +%---------------------------------------------------------------------------------------- +\vfill % Fill the rest of the page with whitespace + +\makeatletter +Last Modifications: \today +\makeatother + + +\end{titlepage} diff --git a/mkdocs.yml b/mkdocs.yml index 7b98c88..647c4cf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,7 +2,7 @@ site_name: ignis site_author: THD-Spatial-AI site_description: >- - ignis — Heat Demand Calculation Pipeline for European building energy performance estimation. + ignis: Heat Demand Calculation Pipeline for European building energy performance estimation. site_url: https://thd-spatial-ai.github.io/ignis # Repository @@ -58,6 +58,8 @@ theme: plugins: - search: separator: '[\s\u200b\-_,:!=\[\]()"`/]+|\.(?!\d)|&[lg]t;|(?!\b)(?=[A-Z][a-z])' + - swagger-ui-tag: + tryItOutEnabled: true # Extensions markdown_extensions: @@ -89,9 +91,8 @@ nav: - Home: index.md - Getting started: getting-started.md - API reference: api.md - - Pipeline: pipeline.md - Validation: validation.md # Copyright copyright: | - © 2026 BigGeoData & Spatial AI, Technische Hochschule Deggendorf — MIT License + © 2026 BigGeoData & Spatial AI, Technische Hochschule Deggendorf · MIT License