diff --git a/.dockerignore b/.dockerignore index abd6bc2..80054d0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,6 +6,3 @@ data *.db .env .env.* -# Lokale Werkzeug- und Agenten-Artefakte gehoeren nicht ins Image. -.codex -.agents diff --git a/.gitignore b/.gitignore index e0ef1d2..ed5ffe6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,6 @@ node_modules/ dist/ .next/ -# Streuprodukt von tsc; drizzle-kit liest drizzle.config.ts. -/drizzle.config.js data/*.db data/backups/*.db .codex/*.log diff --git a/AGENTS.md b/AGENTS.md index 31e70c0..f1a509d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -250,10 +250,8 @@ not change the project revision or undo/redo stacks. Restoring a server-stored snapshot verifies its checksum and the current-state hash, replaces supported project data atomically and records a new `restore` revision with a complete inverse command. Restore can therefore be undone and redone after a restart. -Current snapshot schema version 6 additionally stores the distribution-board -info fields `boardNumber`, `location`, `fedFrom` and `remark`; all older -supported versions are upgraded with `null` for these fields. Version 5 stores -the manual quantity share of every CircuitDeviceRow. Version 4 snapshots are upgraded with +Current snapshot schema version 5 additionally stores the manual quantity share +of every CircuitDeviceRow. Version 4 snapshots are upgraded with `manualQuantity = quantity`; they already contain the complete confirmed external-model source, import batches, room mappings and objects. Version 3 snapshots are upgraded with an empty external-model state; version 2 snapshots @@ -267,7 +265,7 @@ project graph. Project-wide Revit CSV configuration uses `external-csv-configuration.update`. Its complete expected/target snapshot is persisted through the shared project-command transaction, so create, update, -delete, Undo and Redo are revision-safe. Snapshot schema 5/6, restore and portable +delete, Undo and Redo are revision-safe. Snapshot schema 5, restore and portable project duplication preserve the configuration; duplication remaps its internal id and project link. The project page exposes the Revit CSV modal. It edits the complete @@ -281,7 +279,7 @@ immutable import batches with original bytes plus the classified cell matrix, normalized source-room mappings and external objects unique by `(sourceId, ifcGuid)`. Source values, planning values and overrides are separate. The general external-model repository is read-only; runtime writes belong only -to typed command adapters. Snapshot/restore and portable project transfer schema version 5/6 +to typed command adapters. Snapshot/restore and portable project transfer schema version 5 capture the complete external state and remap every internal UUID and link while preserving IFCGUIDs, source values, original bytes and the classified matrix. Import batches additionally store the monotonic project CSV configuration @@ -424,18 +422,6 @@ the only supported circuit-protection source; no flat compatibility fields remai Distribution-board floor assignment and a project-enabled supply type use `distribution-board.update`; both values are snapshot/export fields and one persistent undo step. -The same command (still schema version 1, additively extended) carries the -board info-card fields `boardNumber`, `location`, `fedFrom` and `remark` -(migration `0008`). They are nullable, trimmed planner values; the PUT route -only includes submitted info fields in the command and its inverse. An empty -board with info values is no longer an unchanged setup, so history deletion of -it is rejected like a changed simultaneity factor. Populated-subtree commands -stored before these fields existed are normalized to `null` values -(`normalizeDistributionBoardSubtreeSnapshot`). `fedFrom` is deliberately free -text: the logical link to the feeding board/circuit is an open design decision. -These fields are intended to be prefillable from Revit parameters later, but -only through the Phase 14 rules (preview, explicit confirmation, no silent -overwrite). Floor and room setup uses persistent insert, update and delete commands with complete stable snapshots. Their inverses remove or restore only unchanged records and reject floors with assigned rooms/distribution boards or rooms referenced by @@ -457,32 +443,6 @@ Required operations: - synchronization changes -## Print View (Seitenansicht) - -- Route `src/app/projects/[projectId]/print/page.tsx`, opened from the project - page button "Seitenansicht". Read-only: it only uses existing GET endpoints. -- Pure read model `src/frontend/print/print-document-model.ts` (tested in - `tests/print-document-model.test.ts`); React sheets in - `src/frontend/print/print-document.tsx`; styles scoped to `.print-view` in - `src/frontend/print/print-document.css`. -- Pages: cover (A4 portrait, tabular), board overview with structure and - power sums, one plan per board (A4/A3 landscape) whose title block sits in - `` so it repeats on printed pages. PDF export uses the browser print - dialog; page size, margins and "Seite x von y" come from injected named - `@page` rules (`lb-portrait`, `lb-landscape`). -- The board hierarchy and "Σ inkl. unterlagert" in the overview are - PRELIMINARY (text match of `fedFrom` against board number/name, plain sum, - no upstream simultaneity factor). Replace this once the feed link is modeled. -- Colors/fonts are the provisional kbp CD (`--pd-*` variables) until the - official CD specification exists. Cover fields shown as "[ergänzen]" - (address, Leistungsphase, office staff) have no data model yet. -- The document logo is global office branding: table - `document_branding_assets` (migration `0008`), endpoints - `GET|PUT|DELETE /api/document-branding/logo` and `GET /api/document-branding`. - It is intentionally outside project revisions, snapshots and transfer. - Uploads are PNG/JPEG/SVG up to 2 MB with a content signature check; the logo - is served with `nosniff` and a restrictive CSP. - ## Revit / CSV External Model Work When explicitly working on Phase 14, use @@ -578,11 +538,6 @@ Users must be able to override sizing suggestions. - full electrical sizing - multi-user/PostgreSQL operation - supported production deployment -- logical feed link between distribution boards (`fedFrom` stays free text - until the project owner decides whether it references a board, a feeding - circuit or both) and the resulting hierarchical power summation -- data model for the remaining print cover fields (address, Leistungsphase, - project lead/editor) and the official kbp CD values Do not implement these while working on an unrelated phase. diff --git a/Dockerfile b/Dockerfile index 66147e7..31d28c4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,56 +1,21 @@ -# syntax=docker/dockerfile:1 - -# --------------------------------------------------------------------------- -# deps: vollstaendiger Abhaengigkeitsbaum, gemeinsam genutzt von "dev" und -# "build". Bleibt auf dem vollen node-Image, weil better-sqlite3 beim -# Installieren eine Toolchain braucht. -# --------------------------------------------------------------------------- -FROM node:24 AS deps +FROM node:24 WORKDIR /app + COPY package*.json ./ RUN npm ci -# --------------------------------------------------------------------------- -# dev: Ziel fuer compose.dev.yaml. Behaelt devDependencies, damit tsx, -# drizzle-kit und "next dev" verfuegbar sind. Die Quellen kommen zur Laufzeit -# ueber Bind-Mounts, das COPY hier ist nur der Ausgangsstand. -# --------------------------------------------------------------------------- -FROM deps AS dev -ENV NODE_ENV=development COPY . . -RUN mkdir -p data -EXPOSE 3000 3001 -CMD ["npm", "run", "dev:api"] -# --------------------------------------------------------------------------- -# build: API nach dist/ und Frontend nach .next/ uebersetzen, danach die -# devDependencies entfernen, damit das Laufzeit-Image nur noch traegt, was es -# auch ausfuehrt. Die Migrationsskripte kommen mit drizzle-orm und -# better-sqlite3 aus, beides sind regulaere dependencies. -# --------------------------------------------------------------------------- -FROM deps AS build -ENV NEXT_TELEMETRY_DISABLED=1 -# next build schreibt die Rewrite-Ziele aus next.config.mjs fest in -# .next/routes-manifest.json, "next start" kann eine andere API-Adresse -# spaeter nicht mehr uebernehmen. Der Wert muss hier bekannt sein, nicht erst -# zur Laufzeit. Server-Komponenten lesen die Variable zur Laufzeit. +# next build writes the rewrite destinations from next.config.mjs into +# .next/routes-manifest.json, so "next start" cannot pick up a different +# API URL later. The value has to be known here, not just at runtime. ARG API_INTERNAL_URL=http://localhost:3000 ENV API_INTERNAL_URL=$API_INTERNAL_URL -COPY . . -RUN npm run build:api && npm run build:web -RUN npm prune --omit=dev -# --------------------------------------------------------------------------- -# prod: Laufzeit-Image. node:24-slim teilt sich das Debian-Release mit der -# build-Stufe, damit das kompilierte better-sqlite3-Binding ladbar bleibt. -# Laeuft als "node" (UID 1000) statt als root. -# --------------------------------------------------------------------------- -FROM node:24-slim AS prod -ENV NODE_ENV=production -ENV NEXT_TELEMETRY_DISABLED=1 -WORKDIR /app -COPY --from=build --chown=node:node /app ./ -RUN mkdir -p data && chown node:node data -USER node -EXPOSE 3000 3001 -CMD ["node", "dist/server/index.js"] +RUN npm run build:api && npm run build:web + +RUN mkdir -p data && chmod +x scripts/docker-start.sh + +EXPOSE 3001 + +CMD ["sh", "scripts/docker-start.sh"] diff --git a/README.md b/README.md index 58f1832..fc82fca 100644 --- a/README.md +++ b/README.md @@ -50,20 +50,12 @@ docker compose up --build --detach Danach: - Frontend: -- API-Healthcheck: (über den Next.js-Rewrite) - -Die Express-API ist nicht mehr auf dem Host veröffentlicht. Sie hat keine -Authentifizierung und ist nur innerhalb des Compose-Netzes erreichbar; der -Zugriff läuft über den Rewrite im Web-Dienst. +- API-Healthcheck: Der API-Container führt ausstehende Migrationen und die Schemaprüfung beim Start automatisch aus. Die SQLite-Datei liegt auf dem Host unter `data/leistungsbilanz.db` und bleibt beim Stoppen erhalten. -Das Laufzeit-Image läuft als Benutzer `node` (UID 1000), nicht als root. Ein -`data/`-Verzeichnis, das ein früherer root-Container angelegt hat, braucht -deshalb einmalig `sudo chown -R 1000:1000 data` auf dem Host. - ```powershell docker compose ps docker compose logs --follow @@ -71,8 +63,7 @@ docker compose down ``` `compose.yaml` startet den Produktionsstand: gebautes `dist/` und `next start`, -ohne Quellcode-Mounts und ohne Datei-Watcher, mit Speichergrenzen und ohne -root-Rechte im Container. Details stehen in +ohne Quellcode-Mounts und ohne Datei-Watcher. Details stehen in [Deployment und Betrieb](docs/deployment.md). Für die Entwicklung mit Hot Reload gibt es einen eigenen Stack mit diff --git a/compose.dev.yaml b/compose.dev.yaml index b9c64b4..86e42e2 100644 --- a/compose.dev.yaml +++ b/compose.dev.yaml @@ -1,47 +1,35 @@ -# Entwicklungs-Stack: Quellcode-Mounts, laufende Dev-Server, Hot Reload. +# Development stack: source mounts, watching dev servers, hot reload. # docker compose -f compose.dev.yaml up --build # -# Die Polling-Watcher unten sind fuer Bind-Mounts unter Windows und macOS -# noetig, weil inotify-Events die VM-Grenze nicht passieren. Sie kosten -# dauerhaft CPU; genau deshalb laufen im Server-Stack (compose.yaml) gar -# keine Watcher. -# -# Der Projektname muss sich von dem des Server-Stacks unterscheiden, damit -# "docker compose down" in diesem Verzeichnis niemals einen laufenden -# Server-Stack abraeumen kann. +# The polling watchers below are needed for bind mounts on Windows and +# macOS, where inotify events do not cross the VM boundary. They cost +# continuous CPU, which is why the production stack in compose.yaml does +# not run watchers at all. name: leistungsbilanz-dev -x-service-defaults: &service-defaults - init: true - restart: unless-stopped - stop_grace_period: 20s - logging: - driver: json-file - options: - max-size: "20m" - max-file: "10" +x-logging: &logging + driver: json-file + options: + max-size: "20m" + max-file: "10" services: api: - <<: *service-defaults build: context: . - target: dev command: - sh - -c - - npm run db:migrate && npm run db:verify:circuit-schema && exec npm run dev:api + - npm run db:migrate && npm run db:verify:circuit-schema && npm run dev:api environment: PORT: "3000" CHOKIDAR_USEPOLLING: "true" LOG_LEVEL: "${LOG_LEVEL:-debug}" - # Nur auf dem Loopback: nichts davon ist authentifiziert, die API darf - # nicht aus dem LAN erreichbar sein. + init: true + restart: unless-stopped + logging: *logging ports: - - "127.0.0.1:3000:3000" - # Kein memswap_limit wie im Server-Stack: beim Entwickeln ist Swap als - # Puffer erwuenscht, die Grenze soll nur den Host vor Dauerlast schuetzen. - mem_limit: 1g + - "3000:3000" volumes: - ./src:/app/src - ./scripts:/app/scripts @@ -53,17 +41,15 @@ services: - CMD - node - -e - - fetch('http://127.0.0.1:3000/health').then(response=>{if(!response.ok)process.exit(1)}).catch(()=>process.exit(1)) + - fetch('http://localhost:3000/health').then(response=>{if(!response.ok)process.exit(1)}).catch(()=>process.exit(1)) interval: 30s timeout: 3s retries: 5 start_period: 20s web: - <<: *service-defaults build: context: . - target: dev command: - npm - run @@ -76,14 +62,14 @@ services: WATCHPACK_POLLING: "true" NEXT_TELEMETRY_DISABLED: "1" LOG_LEVEL: "${LOG_LEVEL:-debug}" + init: true + restart: unless-stopped + logging: *logging depends_on: api: condition: service_healthy ports: - - "127.0.0.1:3001:3001" - # "next dev" haelt Kompilierzustand und waechst im Dauerbetrieb stetig; - # die Grenze macht daraus einen Container-Neustart statt Host-Swapping. - mem_limit: 2g + - "3001:3001" volumes: - ./src:/app/src - ./next.config.mjs:/app/next.config.mjs:ro @@ -94,7 +80,7 @@ services: - CMD - node - -e - - fetch('http://127.0.0.1:3001/web-health').then(response=>{if(!response.ok)process.exit(1)}).catch(()=>process.exit(1)) + - fetch('http://localhost:3001/web-health').then(response=>{if(!response.ok)process.exit(1)}).catch(()=>process.exit(1)) interval: 30s timeout: 3s retries: 5 diff --git a/compose.yaml b/compose.yaml index 3438f90..503a4b2 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,59 +1,33 @@ -# Server-Stack: gebauter Stand, keine Quellcode-Mounts, keine Datei-Watcher. -# docker compose up --build --detach -# -# Fuer die Entwicklung mit Hot Reload compose.dev.yaml verwenden. name: leistungsbilanz x-build: &build context: . - target: prod args: - # Wird von next build in .next/routes-manifest.json eingebacken, siehe - # Dockerfile. Muss der API-Adresse in diesem Netz entsprechen. + # Baked into .next/routes-manifest.json by next build; see Dockerfile. API_INTERNAL_URL: http://api:3000 -x-service-defaults: &service-defaults - init: true - restart: unless-stopped - # Der API-Prozess beendet HTTP-Server und SQLite-Handle auf SIGTERM; die - # Frist gibt laufenden Requests Zeit, statt sie nach 10s in ein SIGKILL - # laufen zu lassen. - stop_grace_period: 20s - # Das Image laeuft als "node" (UID 1000); zusaetzliche Rechte braucht - # weder der API- noch der Web-Prozess. - security_opt: - - no-new-privileges:true - cap_drop: - - ALL - logging: - driver: json-file - options: - max-size: "20m" - max-file: "10" +x-logging: &logging + driver: json-file + options: + max-size: "20m" + max-file: "10" services: api: - <<: *service-defaults build: *build - # "exec" beim letzten Schritt: sonst bleibt sh der Vordergrundprozess und - # SIGTERM erreicht node nie. command: - sh - -c - - node scripts/run-migrations.js && node scripts/db-verify-circuit-schema.js && exec node dist/server/index.js + - node scripts/run-migrations.js && node scripts/db-verify-circuit-schema.js && node dist/server/index.js environment: NODE_ENV: production PORT: "3000" LOG_LEVEL: "${LOG_LEVEL:-info}" - # Nicht auf dem Host veroeffentlicht: die API hat keine Authentifizierung - # und wird ueber den Next.js-Rewrite im Web-Dienst erreicht. - expose: - - "3000" - # Harte Obergrenze statt unbegrenztem Wachstum. memswap_limit == mem_limit - # schaltet Swap fuer den Container ab, damit ein Leck den Container trifft - # und nicht den Host. - mem_limit: 512m - memswap_limit: 512m + init: true + restart: unless-stopped + logging: *logging + ports: + - "3000:3000" volumes: - ./data:/app/data healthcheck: @@ -61,43 +35,38 @@ services: - CMD - node - -e - - fetch('http://127.0.0.1:3000/health').then(response=>{if(!response.ok)process.exit(1)}).catch(()=>process.exit(1)) + - fetch('http://localhost:3000/health').then(response=>{if(!response.ok)process.exit(1)}).catch(()=>process.exit(1)) interval: 30s timeout: 3s retries: 5 start_period: 20s web: - <<: *service-defaults build: *build command: - node_modules/.bin/next - start - -p - "3001" - - --hostname - - 0.0.0.0 environment: NODE_ENV: production API_INTERNAL_URL: http://api:3000 NEXT_TELEMETRY_DISABLED: "1" LOG_LEVEL: "${LOG_LEVEL:-info}" + init: true + restart: unless-stopped + logging: *logging depends_on: api: condition: service_healthy ports: - "3001:3001" - mem_limit: 1g - memswap_limit: 1g healthcheck: - # /web-health statt /health: next.config.mjs leitet /health an die API - # weiter, und "/" wuerde wegen des Redirects auf /projects bei jeder - # Probe zwei Seiten rendern. test: - CMD - node - -e - - fetch('http://127.0.0.1:3001/web-health').then(response=>{if(!response.ok)process.exit(1)}).catch(()=>process.exit(1)) + - fetch('http://localhost:3001/web-health').then(response=>{if(!response.ok)process.exit(1)}).catch(()=>process.exit(1)) interval: 30s timeout: 3s retries: 5 diff --git a/docs/README.md b/docs/README.md index 395b1ac..69a2115 100644 --- a/docs/README.md +++ b/docs/README.md @@ -12,8 +12,6 @@ ausdrücklich getrennt und dürfen nicht als bereits implementiert verstanden we fachliches Modell und Grid-Aufteilung - [Editor-Interaktionen](circuit-list-editor-interactions.md) – Tastatur, Drag-and-drop, Sortierung, Filterung und Undo/Redo -- [Seitenansicht und PDF-Export](print-view.md) – Deckblatt, - Verteilerübersicht, Verteilerpläne, Büro-Logo - [Editor-API](circuit-list-editor-api.md) – unterstützte Circuit-First-Endpunkte - [Bekannte Einschränkungen](circuit-list-editor-known-limitations.md) – bewusst noch nicht implementierte oder begrenzte Funktionen diff --git a/docs/cable-sizing-module.md b/docs/cable-sizing-module.md deleted file mode 100644 index 80b7cc8..0000000 --- a/docs/cable-sizing-module.md +++ /dev/null @@ -1,243 +0,0 @@ -# Cable Sizing Module (Proposal) - -**Status: proposal, not yet reviewed or merged by the project owner.** This -document and the accompanying `src/cable-sizing/`, `src/db/schema/cable-sizing-calculations.ts`, -`src/server/{controllers,routes}/cable-sizing.*` and `src/frontend/components/cable-sizing-*` -files were written by a third party (see git history/authorship) against the -project's own stated direction in `docs/spec/06-future-sizing-and-calculations.md` -("The app should later support rule-based protection and cable sizing") and -`AGENTS.md` ("... and later electrical sizing logic"). Nothing here has been -pushed to the project's own repository; it exists as a local branch for -review, testing and discussion. - -## Why a separate module instead of extending core domain code - -`AGENTS.md` is explicit that critical multi-write commands, the revision/undo -system and the Circuit-First domain model are the supported architecture, and -that changes should be small and reviewable. A cable-sizing calculation is -**not** a project mutation - it doesn't need `expectedRevision`, doesn't -belong in the undo/redo stack, and shouldn't grow the `circuit.update` -command's `switch` statement or the DTOs it doesn't already have. - -So this module is built exactly like the existing `src/external-model/` -adapter (the Revit/CSV import foundation): a one-way dependency boundary. - -``` -src/cable-sizing/domain/ <- pure functions and types, zero imports from - db/, server/ or frontend/ -src/db/schema/ <- one new, fully additive table -src/db/repositories/ <- one plain repository (no revision semantics) -src/server/{controllers,routes}/cable-sizing.* <- one new, isolated route - group, mounted with a single app.use() line -src/frontend/components/cable-sizing-* <- one new modal + one small - API client, following the existing - FormModal / CircuitProtectionModal pattern -``` - -Nothing outside these files imports from them except the two required -one-line hooks described below. If this module is rejected or needs to move -out again, removing it is a matter of deleting these files, the two -one-line hooks, and running a `DROP TABLE` migration - it never touches -`circuits`, `project_revisions` or any command/history table. - -## The two required hooks into existing code - -1. `src/server/index.ts`: one import + one `app.use("/api/cable-sizing", cableSizingRouter)` - line, next to the existing `app.use("/api/projects", ...)` etc. -2. `src/frontend/components/circuit-tree-editor.tsx`: one new `useState` for - the open modal, one new trigger condition (mirroring the existing - `isProtectionTrigger` / `protectionEditorCircuit` pattern almost exactly), - and one new conditionally-rendered `` alongside the - existing ``. See the diff for the exact lines. - -No changes to `circuit-project-command.model.ts`, `project-command.service.ts`, -any Zod command schema, any migration for `circuits`/`project_revisions`, or -any existing test. - -## Coupling to the grid UI (the one fragile point) - -Everything described above - the calculation, the audit table, applying a -result via `circuit.update` - depends only on stable, explicitly documented -domain fields (`cableType`/`cableCrossSection`/`cableLength`/ -`circuitTotalPower`/`voltage`/`protectionDevice.ratedCurrentA`), the same -ones `AGENTS.md` already treats as protected ("Protection and cable data -belong to the circuit"). None of that breaks if the grid UI changes. - -The one place that *is* coupled to a UI implementation detail: the click -trigger in `circuit-tree-editor.tsx` matches grid **column keys** -(`cableSummary` and `cableCrossSection`, defined in -`circuit-grid-model.ts`) to decide where to show the calculator icon. -Column visibility and order in this app are a per-browser user preference, -not fixed - so this is checked against both known cable-related columns to -reduce (not eliminate) the chance a user's personal column layout hides the -trigger entirely. If a future column rename removes both keys, the failure -mode is purely cosmetic: the calculator icon/click stops appearing on that -cell, nothing crashes, no data is affected, and the rest of the app -(including manual cable-field editing) is completely unaffected - the -module's actual logic and persistence never depend on this column key. - -## Feature overview - -- **Calculate a recommendation**: laying method, insulation, conductor - material, ambient temperature, grouping, cos phi and max voltage drop - - same VDE 0298-4 reference-method model as the sibling Kabelliste tool - (see Verification status below). -- **Breaker-aware sizing**: if the circuit already has a protection device - (`circuit.protectionDevice`), the cross-section is selected against - `designCurrentA = max(operatingCurrentA, protectionDevice.ratedCurrentA)`, - not the load current alone - the standard `In <= Iz` rule (a breaker only - trips at its own rated current, so a cable sized for the actual load - alone could overheat under sustained current below that threshold). If no - cross-section can cover an oversized breaker, that is reported as the - limiting factor by name, not a generic capacity error. -- **Maximum length for the voltage-drop limit**: each cross-section option - also reports the longest single run that still meets the requested - `maxVoltageDropPercent` at the given load - the inverse of the - voltage-drop check, shown for the recommended cross-section. -- **Practical minimum for socket circuits**: `single_phase`-category - circuits are raised to at least 2.5 mm² if the calculation alone would - recommend less - see the dedicated note under Verification status. Never - lowers a calculation that already needs more. -- **Manual entry, always available**: the modal has a plain text Kabeltyp/ - Querschnitt field. Running a calculation pre-fills it as a suggestion, - but the field is the single source of truth for what gets applied, and - stays freely editable - satisfying - `docs/spec/06-future-sizing-and-calculations.md`'s explicit requirement - that "users must remain able to manually override suggestions" without - requiring a calculation to have been run first. A non-blocking warning - appears if the typed value is not a standard cross-section, is smaller - than the last calculation, or is below the practical minimum above - - informational only, never forced, matching the same spec doc's "shown as - a warning or status indicator, not as an automatic forced change." - -## How a suggestion or manual entry is applied - -The module never writes to `circuits` directly. The modal's "Übernehmen" -button calls the **existing, unmodified** frontend helper with whatever is -currently in the Kabeltyp/Querschnitt fields, calculated or hand-typed: - -```ts -updateCircuitById(projectId, expectedRevision, circuitId, { - cableCrossSection: "4 mm²", - cableType: "NYM-J 3x2.5", - cableLength: 30, -}); -``` - -which is the same `circuit.update` command the grid already uses for manual -cell edits. This means: - -- optimistic concurrency (`expectedRevision`) is respected automatically -- the change appears in the project's revision history and is undoable/ - redoable exactly like a manual edit -- nothing is ever written automatically - calculating only fills the modal's - own fields, applying is always an explicit, separate click - -## API contract - -`POST /api/cable-sizing/calculate` - -```jsonc -{ - "phase": 1, // 1 | 3 - "mode": "power", // "power" | "current" - "powerKw": 4.2, // circuit.circuitTotalPower, when mode="power" - "cosPhi": 1, - "voltage": 230, // circuit.voltage (already project-derived, read-only) - "lengthM": 23.5, // circuit.cableLength - "layingMethod": "C", // DIN VDE 0298-4 reference method, A1|A2|B1|B2|C|D1|D2|E|F|G - "conductorMaterial": "copper", // "copper" | "aluminum" - "insulation": "pvc", // "pvc" | "xlpe" - "ambientTemperatureC": 30, - "groupingCircuits": 1, - "maxVoltageDropPercent": 3, - "harmonicNeutralLoad": "none", // "none" | "15to33Percent" | "over33Percent", three-phase only - "existingProtectionRatedCurrentA": 16, // optional, from circuit.protectionDevice - "circuitCategory": "single_phase", // optional, from the circuit's section.category - - // enables the practical-minimum convention below - "context": { "projectId": "...", "circuitId": "...", "equipmentIdentifier": "-1F1.1" } -} -``` - -Response: `{ calculationId, result: CableSizingResult, alerts: CableSizingAlert[] }` -(see `src/cable-sizing/domain/cable-sizing-calculation.ts` for the exact -shape). `GET /api/cable-sizing/laying-methods` and `.../insulation-materials` -expose the pick-list metadata (including which combinations are verified) so -a future non-modal UI could build its own form without hard-coding the enum. - -## Verification status - important - -The reference current-carrying-capacity tables are ported from a sibling -project's Kabelliste module, which was itself only verified against ~8 -public sources for **six** of the ten DIN VDE 0298-4 reference laying -methods (A1, B2, C, E, D1, D2) and **PVC insulation only**. The other four -methods (A2, B1, F, G) and XLPE/VPE insulation could not be verified without -contradiction across sources during that earlier work, so -`calculateCableSizing` deliberately returns `dataVerified: false` and no -numeric recommendation for those combinations, rather than a guessed value. -The UI surfaces this as a plain critical alert. Anyone with access to the -actual norm text can extend `LAYING_METHOD_VERIFIED` and -`CURRENT_CAPACITY_A` in `src/cable-sizing/domain/cable-sizing-calculation.ts` -once the missing tables are confirmed. - -The protection-coordination check (`existingProtectionRatedCurrentA` vs. the -recommended cross-section's corrected capacity) is a **simplified** `In <= -Iz` check only - it is not a full IEC 60364-4-43 overload (`I2 <= 1.45 x -Iz`) or short-circuit withstand check. - -`PRACTICAL_MINIMUM_CROSS_SECTION_MM2` (2.5 mm² for `single_phase`) is -**not** a verified norm value at all - it is a named planning convention, -sourced directly from this project's own -`docs/spec/06-future-sizing-and-calculations.md` ("Standard Single-Phase -Circuits ... usually use ... cable cross-section: 2.5 mm²"), applied as a -floor on top of the calculated recommendation. It intentionally only -covers the one category and one convention that document already states; -it is not a general substitute for norm-compliant calculation. - -## Relationship to the project's own future-sizing spec - -`docs/spec/06-future-sizing-and-calculations.md` separately describes simple -category-based defaults (e.g. lighting circuits -> 10 A / 1.5 mm²) as -"common planning defaults, not a replacement for full norm-compliant -calculation". This module is the latter: an on-demand, norm-referenced -calculation for one circuit at a time, not a bulk default-filling tool. The -two are complementary and could later be wired together (e.g. the category -defaults pre-fill this module's laying-method/insulation fields), but that -integration is out of scope here. - -The spec's `isPublicBuilding` halogen-free rule and the `Control Requirement` -(DALI/KNX/core-count) field are **not** implemented by this module - both -concern cable *type* selection, not cross-section sizing, and the latter -isn't in the current schema yet. They're natural follow-ups once this module -is reviewed. - -## Maintenance plan - -The `src/cable-sizing/domain/` folder has zero imports from the rest of the -app by design (see the dependency-direction note above), so it can be -extracted into an independently versioned/published package later (e.g. a -private npm package or a git subtree) without touching anything outside the -five files/folders listed above - "separately maintained but part of the -app for now", per the intent of this proposal. - -## Local testing - -Deployed and manually exercised end-to-end on an internal test host at -`http://192.168.0.133:3220` (own docker deployment, own subnet/ports to -avoid clashing with ~50 other containers on that host - see the two -commits marked "Local-only" in this branch, which are not part of this -proposal and should not be carried over if it is ever proposed upstream). -Exercised against seeded real project/circuit data created through the -actual command API (not direct DB writes), including a deliberately long -(85 m) circuit to trigger the voltage-drop-critical path. - -`npm test`, `npm run build:api`, `npm run build:web` and -`npm run typecheck:scripts` all pass with this module included at every -commit in this branch. - -This branch (`feature/cable-sizing-module`) is pushed to a mirror of this -repository under this homelab's own Forgejo instance, not to -`git.jappel.io` - no write access to the upstream repository was available -or used. See the branch's commit history for the incremental history of -this module, including fixes made during manual testing (trigger column, -icon rendering, breaker-aware sizing). diff --git a/docs/circuit-list-editor-api.md b/docs/circuit-list-editor-api.md index 5234670..9ed9e0a 100644 --- a/docs/circuit-list-editor-api.md +++ b/docs/circuit-list-editor-api.md @@ -230,13 +230,9 @@ returns HTTP `409` with `PROJECT_HISTORY_OPERATION_UNAVAILABLE`. - stale revisions return `409 PROJECT_REVISION_CONFLICT` - `PUT /projects/:projectId/distribution-boards/:distributionBoardId` - body: - `{ "floorId": null, "supplyType": "AV", "simultaneityFactor": 1, "expectedRevision": 13 }` - - optional info-card fields: `boardNumber` (max. 50), `location`, - `fedFrom` (max. 200 each) and `remark` (max. 2000); strings are trimmed - and an empty string is stored as `null` - - executes `distribution-board.update`; all submitted values, including the - submitted info fields, are restored together by persistent Undo/Redo - - `fedFrom` is free text until the logical feed link is modeled + `{ "floorId": null, "supplyType": "AV", "expectedRevision": 13 }` + - executes `distribution-board.update`; floor and supply type are restored + together by persistent Undo/Redo - `POST /projects/:projectId/distribution-boards/:distributionBoardId/copy` - body: `{ "name": "UV-02 Kopie", "expectedRevision": 14 }` - duplicates the complete distribution-board subtree with new owning UUIDs diff --git a/docs/circuit-list-editor-known-limitations.md b/docs/circuit-list-editor-known-limitations.md index bea8b87..4f7f660 100644 --- a/docs/circuit-list-editor-known-limitations.md +++ b/docs/circuit-list-editor-known-limitations.md @@ -27,11 +27,3 @@ - Protection and cable values are planner-owned manual selections. The later sizing/warning engine does not yet validate them against load, length or voltage-drop rules. -- Distribution-board `fedFrom` is free text. The print overview derives its - supply structure and "Σ inkl. unterlagert" only from exact text matches with - board numbers/names and adds values without an upstream simultaneity factor - (see `docs/print-view.md`). -- The print view cover page shows placeholders for address, Leistungsphase and - office staff, and uses a provisional kbp corporate design. On screen, long - board plans are not split into pages; pagination happens in the browser print - dialog. diff --git a/docs/current-architecture.md b/docs/current-architecture.md index 1bf0437..92efc98 100644 --- a/docs/current-architecture.md +++ b/docs/current-architecture.md @@ -416,13 +416,7 @@ wieder her. verteilerweiten Gleichzeitigkeitsfaktor gemeinsam und stellt alle Werte über dauerhaftes Undo/Redo wieder her. Der Faktor liegt zwischen `0` und `1` und ist für bestehende sowie neu angelegte Verteilungen standardmäßig `1`. -Derselbe Befehl versioniert die Felder der Verteiler-Infokarte -(`boardNumber`, `location`, `fedFrom`, `remark`, Migration `0008`). Die -Infokarte liegt im Bearbeiten-Dialog der Verteilung; die Werte sind lokale -Planungswerte und sollen später über den Revit-Abgleich nur nach Vorschau und -ausdrücklicher Bestätigung vorbelegt werden. `fedFrom` ist vorerst Freitext. -Snapshot-Schema 6 enthält diese Felder; ältere Versionen werden mit `null` -hochgestuft. Snapshot-Schema 5 enthält zusätzlich den manuellen Mengenanteil jeder +Snapshot-Schema 5 enthält zusätzlich den manuellen Mengenanteil jeder Gerätezeile. Version 4 wird mit `manualQuantity = quantity` hochgestuft und enthält bereits den vollständigen externen Modellzustand aus Quelle, Batches, Raum-Mappings und Objekten. Es enthält außerdem die diff --git a/docs/deployment.md b/docs/deployment.md index 909c2c0..06289a7 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -6,9 +6,7 @@ Es gibt zwei Compose-Stacks. `compose.yaml` startet den gebauten Stand: `node dist/server/index.js` und `next start`, ohne Quellcode-Mounts und ohne Datei-Watcher. Das ist der Stack -für einen Server. Beide Dienste laufen aus der `prod`-Stufe des Dockerfiles, -also als Benutzer `node` statt als root, ohne devDependencies, mit -`cap_drop: ALL`, `no-new-privileges` und harten Speichergrenzen. +für einen Server. `compose.dev.yaml` startet `tsx watch` und `next dev` und bindet Quellcode vom Host ein. Die Watcher laufen im Polling-Modus, weil Bind-Mounts unter Windows @@ -22,37 +20,11 @@ erreichbar gemacht werden. ## Topologie -| Komponente | Port | Auf dem Host | Healthcheck | Persistenz | -| --- | ---: | --- | --- | --- | -| Next.js Web | 3001 | `3001` (Server) / `127.0.0.1:3001` (Dev) | `GET /web-health` | keine | -| Express API | 3000 | nicht veröffentlicht (Server) / `127.0.0.1:3000` (Dev) | `GET /health` | `./data:/app/data` | -| SQLite | Datei | – | Integritäts-/FK-Prüfung via Backup und Skript | `data/leistungsbilanz.db` | - -Die Express-API wird im Server-Stack nicht mehr auf dem Host veröffentlicht -(`expose` statt `ports`). Sie hat keine Authentifizierung; jeder, der sie -erreicht, kann jedes Projekt lesen und ändern. Erreichbar ist sie nur aus dem -Compose-Netz, praktisch also über den Next.js-Rewrite. Im Entwicklungs-Stack -hängen beide Ports am Loopback und sind nicht aus dem LAN erreichbar. - -### Speichergrenzen - -Jeder Dienst hat ein `mem_limit`. Im Server-Stack ist zusätzlich -`memswap_limit` auf denselben Wert gesetzt, womit Swap für den Container -abgeschaltet ist: ein Speicherleck lässt dann den Container am OOM-Killer -sterben und `restart: unless-stopped` greift, statt dass der Host in den Swap -läuft. Aktuell 512 MB für die API und 1 GB für das Frontend. Der -Entwicklungs-Stack bekommt 1 GB bzw. 2 GB und behält Swap, weil `next dev` -Kompilierzustand hält und im Dauerbetrieb stetig wächst. - -### Rechte im Container - -Das Laufzeit-Image läuft als `node` (UID 1000). Ein `data/`-Verzeichnis, das -ein früherer root-Container angelegt hat, ist dann nicht beschreibbar und -braucht einmalig auf dem Host: - -```bash -sudo chown -R 1000:1000 data -``` +| Komponente | Port | Healthcheck | Persistenz | +| --- | ---: | --- | --- | +| Next.js Web | 3001 | `GET /web-health` | keine | +| Express API | 3000 | `GET /health` | `./data:/app/data` | +| SQLite | Datei | Integritäts-/FK-Prüfung via Backup und Skript | `data/leistungsbilanz.db` | Verwendete Umgebungsvariablen: @@ -111,25 +83,11 @@ einem unbekannten Zustand weiterzulaufen. Beide Dienste laufen deshalb mit `restart: unless-stopped`, damit Docker sie danach automatisch neu startet; ohne diese Policy würde ein Crash den Dienst dauerhaft unerreichbar lassen. -## Herunterfahren - -`SIGTERM` und `SIGINT` schließen in der API den HTTP-Server und anschließend -das SQLite-Handle und beenden den Prozess. Offene Keep-Alive-Verbindungen – -vor allem die des Next.js-Servers – werden dabei über -`server.closeIdleConnections()` freigegeben, laufende Requests dürfen zu Ende -laufen. Vorher wurde das Signal nur geloggt, der Prozess lief weiter und -Docker beendete ihn nach zehn Sekunden mit `SIGKILL`, mitten in einem -möglichen Schreibvorgang. Greift der Abbau nicht innerhalb von 15 Sekunden, -beendet sich der Prozess selbst mit Exit-Code 1; `stop_grace_period: 20s` in -beiden Compose-Dateien liegt bewusst darüber. - -Die Startbefehle in `compose.yaml` enden mit `exec node …`. Ohne das `exec` -bleibt `sh` der Vordergrundprozess, und `SIGTERM` erreicht Node nie. - ## Voraussetzungen für ein späteres Produktionssetup Vor einer produktiven Installation werden mindestens benötigt: +- reproduzierbare Produktionsimages und ein Next.js-Produktionsstartskript - TLS-Termination und Reverse Proxy - Authentifizierung, Autorisierung und Benutzer-/Rollenmodell - definierte Secrets- und Konfigurationsverwaltung diff --git a/docs/print-view.md b/docs/print-view.md deleted file mode 100644 index b258d7b..0000000 --- a/docs/print-view.md +++ /dev/null @@ -1,80 +0,0 @@ -# Seitenansicht und PDF-Export - -Stand: erste Ausbaustufe (Beispiel zur Abstimmung). Die Seitenansicht ist -rein lesend und verändert keine Projektdaten. - -## Aufruf - -- Projektseite → Button **Seitenansicht** öffnet - `/projects/:projectId/print` in einem neuen Tab. -- **Als PDF speichern / Drucken** nutzt den Druckdialog des Browsers - (Ziel „Als PDF speichern“). Der Dokumenttitel `Leistungsbilanz – ` - wird als Dateiname vorgeschlagen. -- **Planformat** schaltet die Verteilerpläne zwischen A4 quer und A3 quer um. - Deckblatt bleibt A4 hoch. -- **Logo hinterlegen / ersetzen / entfernen** verwaltet das Büro-Logo. - -## Seiten - -1. **Deckblatt (A4 hoch)** – tabellarisch: Projekt, Planverfasser, Dokument, - Planstände (aus benannten Sicherungspunkten) und Inhalt. Angaben ohne - Datenmodell erscheinen als orangefarbenes „[ergänzen]“ (Anschrift, - Leistungsphase, Projektleitung, Bearbeitung). -2. **Verteilerübersicht (quer)** – Versorgungsstruktur und Tabelle je - Verteiler mit Stromkreisanzahl, Pinst (Anzahl × Leistung je - Stück), PGeräte (mit Geräte-GZF), Verteiler-GZF, - Pgl, IB und „Σ inkl. unterlagert“. -3. **Verteilerpläne (quer, je Verteiler)** – Titelleiste mit - Verteilernummer, Bezeichnung, Örtlichkeit, Netzart, „Gespeist von“ und - Pgl; darunter Kopfkomponenten, Gruppen, Stromkreise - (mehrzeilige mit Sammelzeile und eingerückten Gerätezeilen), - Gruppensummen und Verteilersumme. Fehlende Kabelangaben sind als „offen“ - markiert. Die Titelleiste steht im Tabellenkopf und wiederholt sich beim - Drucken auf Folgeseiten. - -Seitenformat, Ränder und „Seite x von y“ kommen beim Drucken aus benannten -`@page`-Regeln (`lb-portrait`, `lb-landscape`), die die Seite zur Laufzeit -einfügt. Die Bildschirmansicht zeigt Blätter in Papiergröße; die exakte -Seitenaufteilung langer Verteilerpläne entsteht erst beim Drucken. - -## Vorläufige Punkte - -- **Einspeisung / Hierarchie:** `fedFrom` ist Freitext. Die Übersicht ordnet - einen Verteiler nur dann unter, wenn der Text genau der Nummer oder - Bezeichnung eines anderen Verteilers entspricht; „Σ inkl. unterlagert“ ist - eine einfache Summe ohne übergeordneten GZF. Die fachliche Verknüpfung - (Verteiler und/oder speisender Stromkreis) und die Summierungslogik sind - offen. -- **IB** ist ein symmetrischer Drehstromwert - S / (√3 · U3~) mit S aus P / cos φ je Gerätezeile - (fehlendes cos φ = 1) und Verteiler-GZF. -- **Corporate Design:** Farben (`--pd-*` in - `src/frontend/print/print-document.css`) und Schrift (Arial) sind aus Logo - und kbp-Präsentationsmaterial abgeleitet und werden durch die offizielle - CD-Vorgabe ersetzt. Der Büroname steht vorläufig als Konstante - `printOfficeName` in `src/frontend/print/print-document.tsx`. - -## Logo - -- Globale Büro-Einstellung, nicht Teil von Projekten, Revisionen, Snapshots - oder Projektexport. -- Tabelle `document_branding_assets` (Migration `0008`). -- `GET /api/document-branding` → `{ logo: { mimeType, fileName, sizeBytes, updatedAtIso } | null }` -- `GET /api/document-branding/logo` → Bilddaten (`404` ohne Logo), mit - `X-Content-Type-Options: nosniff` und restriktiver CSP. -- `PUT /api/document-branding/logo` mit - `{ "fileName", "mimeType": "image/png" | "image/jpeg" | "image/svg+xml", "contentBase64" }`, - maximal 2 MB; der Dateiinhalt wird gegen das Format geprüft. -- `DELETE /api/document-branding/logo` → `204`. - -## Code - -- `src/app/projects/[projectId]/print/page.tsx` – Datenladen, Werkzeugleiste, - `@page`-Regeln -- `src/frontend/print/print-document-model.ts` – reine Berechnungen und - Formatierung (Tests: `tests/print-document-model.test.ts`) -- `src/frontend/print/print-document.tsx` – Blätter und Tabellen -- `src/frontend/print/print-document.css` – Layout, auf `.print-view` - begrenzt -- `src/frontend/components/distribution-board-info-card.tsx` – Infokarte im - Bearbeiten-Dialog der Verteilung diff --git a/drizzle.config.js b/drizzle.config.js new file mode 100644 index 0000000..004045a --- /dev/null +++ b/drizzle.config.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const drizzle_kit_1 = require("drizzle-kit"); +exports.default = (0, drizzle_kit_1.defineConfig)({ + dialect: "sqlite", + schema: "./src/db/schema/*.ts", + out: "./src/db/migrations", + dbCredentials: { + url: "./data/leistungsbilanz.db", + }, +}); diff --git a/index.html b/index.html new file mode 100644 index 0000000..dcb632f --- /dev/null +++ b/index.html @@ -0,0 +1,17 @@ + + + + + + Leistungsbilanz API + + + +
+

Leistungsbilanz Backend

+

Der aktuelle Stand laeuft als TypeScript Node/SQLite API.

+

Serverstart: npm run dev

+

Healthcheck: GET /health

+
+ + diff --git a/scripts/db-verify-circuit-schema.js b/scripts/db-verify-circuit-schema.js index 558178e..8680f59 100644 --- a/scripts/db-verify-circuit-schema.js +++ b/scripts/db-verify-circuit-schema.js @@ -12,7 +12,6 @@ const requiredTables = [ "circuit_protection_devices", "distribution_board_components", "distribution_board_component_protection_devices", - "document_branding_assets", ]; const tablePlaceholders = requiredTables.map(() => "?").join(", "); @@ -99,10 +98,6 @@ const requiredDistributionBoardColumns = [ "floor_id", "supply_type", "simultaneity_factor", - "board_number", - "location", - "fed_from", - "remark", ]; const distributionBoardColumns = new Set( db diff --git a/scripts/docker-start.sh b/scripts/docker-start.sh new file mode 100644 index 0000000..bf0d856 --- /dev/null +++ b/scripts/docker-start.sh @@ -0,0 +1,16 @@ +#!/bin/sh +set -e + +echo "Running migrations..." +node scripts/run-migrations.js + +echo "Starting API server on :3000..." +node dist/server/index.js & + +echo "Waiting for API..." +until node -e "require('http').get('http://localhost:3000/health', r => process.exit(r.statusCode===200?0:1)).on('error',()=>process.exit(1))" 2>/dev/null; do + sleep 1 +done + +echo "Starting Next.js on :3001..." +exec node_modules/.bin/next start -p 3001 diff --git a/src/app/globals.css b/src/app/globals.css index 88e1c35..22da5df 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1209,48 +1209,6 @@ a.kpi:hover { box-shadow: inset 0 0 0 1px var(--color-signal); } -.tree-grid .cell-cable-sizing-trigger { - color: var(--color-primary); - cursor: pointer; - font-weight: 600; - transition: - background-color 0.12s ease, - box-shadow 0.12s ease; -} - -:root[data-bs-theme="dark"] .tree-grid .cell-cable-sizing-trigger { - color: var(--color-accent-pale); -} - -.tree-grid .cell-cable-sizing-trigger:hover { - background: rgba(63, 130, 166, 0.15); - box-shadow: inset 0 0 0 1px var(--color-primary); -} - -.tree-grid .cell-cable-sizing-trigger { - position: relative; - padding-left: 1.4em; -} - -.tree-grid .cell-cable-sizing-trigger::before { - content: ""; - position: absolute; - left: 0.2em; - top: 50%; - transform: translateY(-50%); - width: 0.9em; - height: 0.9em; - opacity: 0.85; - background-repeat: no-repeat; - background-position: center; - background-size: contain; - background-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%233f82a6' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='4' y='2' width='16' height='20' rx='2'/%3E%3Cline x1='8' y1='6' x2='16' y2='6'/%3E%3Cline x1='8' y1='10' x2='8' y2='10.01'/%3E%3Cline x1='12' y1='10' x2='12' y2='10.01'/%3E%3Cline x1='16' y1='10' x2='16' y2='10.01'/%3E%3Cline x1='8' y1='14' x2='8' y2='14.01'/%3E%3Cline x1='12' y1='14' x2='12' y2='14.01'/%3E%3Cline x1='16' y1='14' x2='16' y2='14.01'/%3E%3Cline x1='8' y1='18' x2='8' y2='18.01'/%3E%3Cline x1='12' y1='18' x2='12' y2='18.01'/%3E%3Cline x1='16' y1='18' x2='16' y2='18.01'/%3E%3C/svg%3E"); -} - -:root[data-bs-theme="dark"] .tree-grid .cell-cable-sizing-trigger::before { - background-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a9d2e6' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='4' y='2' width='16' height='20' rx='2'/%3E%3Cline x1='8' y1='6' x2='16' y2='6'/%3E%3Cline x1='8' y1='10' x2='8' y2='10.01'/%3E%3Cline x1='12' y1='10' x2='12' y2='10.01'/%3E%3Cline x1='16' y1='10' x2='16' y2='10.01'/%3E%3Cline x1='8' y1='14' x2='8' y2='14.01'/%3E%3Cline x1='12' y1='14' x2='12' y2='14.01'/%3E%3Cline x1='16' y1='14' x2='16' y2='14.01'/%3E%3Cline x1='8' y1='18' x2='8' y2='18.01'/%3E%3Cline x1='12' y1='18' x2='12' y2='18.01'/%3E%3Cline x1='16' y1='18' x2='16' y2='18.01'/%3E%3C/svg%3E"); -} - .tree-grid .device-drag-handle { cursor: grab; } diff --git a/src/app/projects/[projectId]/page.tsx b/src/app/projects/[projectId]/page.tsx index 73f9460..fa342ea 100644 --- a/src/app/projects/[projectId]/page.tsx +++ b/src/app/projects/[projectId]/page.tsx @@ -54,12 +54,6 @@ import { type DistributionBoardSupplyType, } from "../../../shared/constants/distribution-board"; import { ProjectVersionHistory } from "../../../frontend/components/project-version-history"; -import { - DistributionBoardInfoCard, - fromDistributionBoardInfoDraft, - toDistributionBoardInfoDraft, - type DistributionBoardInfoDraft, -} from "../../../frontend/components/distribution-board-info-card"; import { ProjectSettingsModal, type ProjectSettingsInput, @@ -107,15 +101,6 @@ export default function ProjectDetailPage() { setEditingBoardSimultaneityFactor, ] = useState("1"); const [editingBoardCopyName, setEditingBoardCopyName] = useState(""); - const [editingBoardInfo, setEditingBoardInfo] = - useState( - toDistributionBoardInfoDraft({ - boardNumber: null, - location: null, - fedFrom: null, - remark: null, - }) - ); const [floorName, setFloorName] = useState(""); const [editingFloor, setEditingFloor] = useState(null); const [editingRoom, setEditingRoom] = useState(null); @@ -439,7 +424,6 @@ export default function ProjectDetailPage() { String(board.simultaneityFactor) ); setEditingBoardCopyName(`${board.name} Kopie`); - setEditingBoardInfo(toDistributionBoardInfoDraft(board)); } function openBoardCreator() { @@ -475,7 +459,6 @@ export default function ProjectDetailPage() { floorId: editingBoardFloorId || null, supplyType: editingBoardSupplyType, simultaneityFactor, - ...fromDistributionBoardInfoDraft(editingBoardInfo), }, project.currentRevision ); @@ -866,13 +849,6 @@ export default function ProjectDetailPage() {

- - Seitenansicht -
-
- -

\r\n]/g, " ")); -} - -function pageRules(project: ProjectDto, stand: string, format: PlanFormat) { - const marginBoxes = ` - @bottom-left { content: ${cssString(`${project.name} · Leistungsbilanz`)}; font: 7.5pt Arial, sans-serif; color: #6b7280; } - @bottom-center { content: ${cssString(`Stand ${stand} · Datenstand Rev. ${project.currentRevision}`)}; font: 7.5pt Arial, sans-serif; color: #6b7280; } - @bottom-right { content: "Seite " counter(page) " von " counter(pages); font: 7.5pt Arial, sans-serif; color: #6b7280; }`; - return ` - @page lb-portrait { size: A4 portrait; margin: 14mm 16mm 16mm; ${marginBoxes} } - @page lb-landscape { size: ${format === "a3" ? "A3" : "A4"} landscape; margin: 10mm 10mm 13mm; ${marginBoxes} }`; -} - -export default function ProjectPrintPage() { - const params = useParams<{ projectId: string }>(); - const projectId = params.projectId; - const [data, setData] = useState(null); - const [logo, setLogo] = useState(null); - const [format, setFormat] = useState("a4"); - const [error, setError] = useState(null); - const [message, setMessage] = useState(null); - const [isBusy, setIsBusy] = useState(false); - const [printedAt] = useState(() => new Date()); - - useEffect(() => { - if (!projectId) { - return; - } - let cancelled = false; - async function load() { - const [project, boards, circuitLists, floors, snapshots, branding] = - await Promise.all([ - getProject(projectId), - listDistributionBoards(projectId), - listCircuitLists(projectId), - listFloors(projectId), - listProjectSnapshots(projectId), - getDocumentBranding(), - ]); - const floorNameById = new Map(floors.map((floor) => [floor.id, floor.name])); - const summaries = await Promise.all( - [...boards] - .sort( - (left, right) => - boardSortCollator.compare(left.boardNumber ?? "", right.boardNumber ?? "") || - boardSortCollator.compare(left.name, right.name) - ) - .flatMap((board) => { - const circuitList = circuitLists.find( - (list) => list.distributionBoardId === board.id - ); - return circuitList ? [{ board, circuitList }] : []; - }) - .map(async ({ board, circuitList }) => - summarizePrintBoard({ - board, - circuitListId: circuitList.id, - tree: await getCircuitTree(projectId, circuitList.id), - floorName: board.floorId ? floorNameById.get(board.floorId) ?? null : null, - }) - ) - ); - if (cancelled) { - return; - } - setData({ - project, - summaries, - namedSnapshots: snapshots - .filter((snapshot) => snapshot.kind === "named") - .sort((left, right) => left.createdAtIso.localeCompare(right.createdAtIso)), - }); - setLogo(branding.logo); - document.title = `Leistungsbilanz – ${project.name}`; - } - load().catch((err: unknown) => { - if (!cancelled) { - setError(err instanceof Error ? err.message : "Die Seitenansicht konnte nicht geladen werden."); - } - }); - return () => { - cancelled = true; - }; - }, [projectId]); - - const entries = useMemo( - () => (data ? buildPreliminaryBoardHierarchy(data.summaries) : []), - [data] - ); - const stand = useMemo( - () => - new Intl.DateTimeFormat("de-DE", { - day: "2-digit", - month: "2-digit", - year: "numeric", - }).format(printedAt), - [printedAt] - ); - const logoSrc = logo - ? `${documentLogoUrl}?v=${encodeURIComponent(logo.updatedAtIso)}` - : null; - - async function handleLogoUpload(event: ChangeEvent) { - const file = event.target.files?.[0]; - event.target.value = ""; - if (!file) { - return; - } - if (!(documentLogoMimeTypes as readonly string[]).includes(file.type)) { - setError("Bitte ein PNG-, JPG- oder SVG-Bild wählen."); - return; - } - if (file.size > maxDocumentLogoBytes) { - setError("Das Logo darf höchstens 2 MB groß sein."); - return; - } - setIsBusy(true); - setError(null); - try { - const result = await uploadDocumentLogo(file); - setLogo(result.logo); - setMessage("Logo hinterlegt. Es gilt für alle Projekte."); - } catch (err) { - setError(err instanceof Error ? err.message : "Das Logo konnte nicht gespeichert werden."); - } finally { - setIsBusy(false); - } - } - - async function handleLogoDelete() { - if (!window.confirm("Hinterlegtes Logo für alle Projekte entfernen?")) { - return; - } - setIsBusy(true); - setError(null); - try { - await deleteDocumentLogo(); - setLogo(null); - setMessage("Logo entfernt."); - } catch (err) { - setError(err instanceof Error ? err.message : "Das Logo konnte nicht entfernt werden."); - } finally { - setIsBusy(false); - } - } - - return ( - // Paper output: always light, independent of the app's dark mode. -
- {data ? : null} -
- - Seitenansicht{data ? ` · ${data.project.name}` : ""} - - {message ? {message} : null} - {error ? {error} : null} - - - {logo ? ( - - ) : null} - - - Zum Projekt - -
- {data ? ( - - ) : error ? null : ( -

Seitenansicht wird geladen …

- )} -
- ); -} diff --git a/src/cable-sizing/domain/cable-sizing-calculation.ts b/src/cable-sizing/domain/cable-sizing-calculation.ts deleted file mode 100644 index 3f228f4..0000000 --- a/src/cable-sizing/domain/cable-sizing-calculation.ts +++ /dev/null @@ -1,547 +0,0 @@ -// Pure cable sizing domain logic. No imports from db/, server/ or frontend/ - -// mirrors the dependency direction of src/external-model/ (see -// docs/cable-sizing-module.md). Everything here is a stateless function on -// plain data; persistence and the versioned circuit.update command live -// outside this module. -// -// The numeric reference tables below (cross-section current-carrying -// capacity, temperature/grouping correction factors) are ported from a -// sibling project's already-verified Kabelliste module -// (elt-planung-suite/src/tools/kabel/index.ts), not re-derived. See -// docs/cable-sizing-module.md for the verification history and the explicit -// "not yet verified" methods, which intentionally return no numeric result -// instead of a guessed one (dataVerified: false). - -// -- Laying methods (DIN VDE 0298-4 reference installation methods) -------- - -export const LAYING_METHODS = [ - "A1", - "A2", - "B1", - "B2", - "C", - "D1", - "D2", - "E", - "F", - "G", -] as const; -export type LayingMethod = (typeof LAYING_METHODS)[number]; - -export const LAYING_METHOD_GROUP: Record = { - A1: "air", - A2: "air", - B1: "air", - B2: "air", - C: "air", - E: "air", - F: "air", - D1: "ground", - D2: "ground", - G: "ground", -}; - -// A1, B2, C, E, D1, D2: verified against elt-planung-suite's ported original -// tool. A2, B1, F, G: no verified capacity table available yet (see -// docs/cable-sizing-module.md) - calculateCableSizing returns -// dataVerified: false for these instead of a guessed number. -export const LAYING_METHOD_VERIFIED: Record = { - A1: true, - B2: true, - C: true, - E: true, - D1: true, - D2: true, - A2: false, - B1: false, - F: false, - G: false, -}; - -export const LAYING_METHOD_LABELS: Record = { - A1: "A1 - Rohr in wärmegedämmter Wand", - A2: "A2 - Mehradriges Kabel im Rohr in wärmegedämmter Wand (Werte nicht hinterlegt)", - B1: "B1 - Einzeladern im Rohr auf/unter Putz (Werte nicht hinterlegt)", - B2: "B2 - Rohr auf/unter Putz", - C: "C - Kabel direkt auf Wand verlegt", - E: "E - Kabelpritsche / frei in Luft", - F: "F - Kabel frei in Luft, einzeln mit Abstand (Werte nicht hinterlegt)", - D1: "D1 - Kabel direkt im Erdreich", - D2: "D2 - Kabel im Rohr im Erdreich", - G: "G - Erdverlegung, Sonderfall (Werte nicht hinterlegt)", -}; - -// -- Insulation / conductor material ---------------------------------------- - -export const INSULATION_MATERIALS = ["pvc", "xlpe"] as const; -export type InsulationMaterial = (typeof INSULATION_MATERIALS)[number]; - -export const INSULATION_MATERIAL_LABELS: Record = { - pvc: "PVC (Grenztemperatur 70 °C)", - xlpe: "VPE/XLPE (Grenztemperatur 90 °C) - Strombelastbarkeitswerte nicht hinterlegt", -}; - -export const CONDUCTOR_MATERIALS = ["copper", "aluminum"] as const; -export type ConductorMaterial = (typeof CONDUCTOR_MATERIALS)[number]; - -// Only PVC combined with one of the six verified laying methods returns a -// numeric result. XLPE has no verified capacity table for any method yet. -export function isCableSizingDataVerified( - layingMethod: LayingMethod, - insulation: InsulationMaterial -): boolean { - return insulation === "pvc" && LAYING_METHOD_VERIFIED[layingMethod]; -} - -// -- Harmonic neutral loading (IEC 60364-5-52, three-phase only) ----------- - -export const HARMONIC_NEUTRAL_LOAD_OPTIONS = [ - "none", - "15to33Percent", - "over33Percent", -] as const; -export type HarmonicNeutralLoad = (typeof HARMONIC_NEUTRAL_LOAD_OPTIONS)[number]; - -export const HARMONIC_NEUTRAL_LOAD_LABELS: Record = { - none: "Keine nennenswerte 3. Harmonische (< 15 %) - Standardfall", - "15to33Percent": - "3. Harmonische 15-33 % - Reduktionsfaktor 0,86 (IEC 60364-5-52)", - over33Percent: - "3. Harmonische > 33 % - Neutralleiter wie Außenleiter dimensionieren", -}; - -export const HARMONIC_REDUCTION_FACTOR = 0.86; - -// -- Reference tables (ported, verified subset only) ------------------------ - -export const CROSS_SECTIONS_MM2 = [ - 1.5, 2.5, 4, 6, 10, 16, 25, 35, 50, 70, 95, 120, 150, 185, 240, -] as const; - -type CoreCount = 2 | 3; - -export const CURRENT_CAPACITY_A: Partial< - Record> -> = { - A1: { - 2: [15.5, 21, 28, 36, 50, 68, 89, 110, 134, 171, 207, 239, null, null, null], - 3: [13.5, 18, 24, 31, 42, 56, 73, 89, 108, 136, 164, 188, null, null, null], - }, - B2: { - 2: [16.5, 23, 30, 38, 52, 69, 90, 111, 134, 171, 207, 239, null, null, null], - 3: [15, 20, 27, 34, 46, 62, 80, 99, 118, 149, 179, 206, null, null, null], - }, - C: { - 2: [19.5, 27, 36, 46, 63, 85, 112, 138, 168, 213, 258, 299, 344, 392, 461], - 3: [17.5, 24, 32, 41, 57, 76, 96, 119, 144, 184, 223, 259, 299, 341, 403], - }, - E: { - 2: [22, 30, 40, 51, 70, 94, 119, 148, 180, 232, 282, 328, 379, 434, 514], - 3: [18.5, 25, 34, 43, 60, 80, 101, 126, 153, 196, 238, 276, 319, 364, 430], - }, - D1: { - 2: [26, 34, 44, 56, 74, 96, 123, 147, 174, 216, 256, 290, 328, 367, 424], - 3: [22, 29, 38, 47, 63, 81, 104, 125, 148, 183, 216, 246, 278, 312, 361], - }, - D2: { - 2: [22, 29, 38, 47, 63, 81, 104, 125, 148, 183, 216, 246, 278, 312, 360], - 3: [18.5, 24, 31, 39, 52, 67, 86, 103, 122, 151, 179, 203, 230, 258, 297], - }, -}; - -export const TEMPERATURE_FACTOR_AIR: Record = { - 10: 1.29, - 15: 1.22, - 20: 1.15, - 25: 1.08, - 30: 1.0, - 35: 0.91, - 40: 0.82, - 45: 0.71, - 50: 0.58, -}; - -export const TEMPERATURE_FACTOR_GROUND: Record = { - 10: 1.1, - 15: 1.05, - 20: 1.0, - 25: 0.95, - 30: 0.89, - 35: 0.84, - 40: 0.77, -}; - -export const GROUPING_FACTOR_AIR: Record = { - 1: 1.0, - 2: 0.8, - 3: 0.7, - 4: 0.65, - 5: 0.6, - 6: 0.57, - 8: 0.54, - 10: 0.5, -}; - -export const GROUPING_FACTOR_GROUND: Record = { - 1: 1.0, - 2: 0.75, - 3: 0.65, - 4: 0.6, - 5: 0.55, - 6: 0.52, - 8: 0.49, - 10: 0.46, -}; - -export const ALUMINUM_CAPACITY_FACTOR = 0.78; -export const KAPPA: Record = { copper: 48, aluminum: 30 }; - -export const GROUPING_OPTIONS = [1, 2, 3, 4, 5, 6, 8, 10] as const; - -export const GROUPING_LABELS: Record = { - 1: "1 (keine Häufung)", - 2: "2", - 3: "3", - 4: "4", - 5: "5", - 6: "6", - 8: "7-9", - 10: "≥10", -}; - -// -- Practical minimum cross-sections by circuit category ------------------ -// Not a thermal/voltage-drop calculation result - a widely used planning -// convention for margin against future load growth, mechanical robustness -// and fault-current withstand, beyond what a pure Ib-based calculation -// would give. Matches this project's own docs/spec/06-future-sizing-and- -// calculations.md ("Standard Single-Phase Circuits ... usually use ... -// cable cross-section: 2.5 mm²"). Only applied for the "single_phase" -// circuit category (general sockets and similar loads); lighting and -// three-phase circuits are not covered by this specific convention. -export const PRACTICAL_MINIMUM_CROSS_SECTION_MM2: Partial> = { - single_phase: 2.5, -}; - -export type CircuitCategory = "lighting" | "single_phase" | "three_phase"; - -// -- Input / result ----------------------------------------------------- - -export interface CableSizingInput { - phase: 1 | 3; - mode: "power" | "current"; - /** kW, only used when mode === "power" */ - powerKw?: number; - /** A, only used when mode === "current" */ - currentA?: number; - cosPhi: number; - /** Line voltage in V. The circuit-list app derives this from project - * settings (singlePhaseVoltageV / threePhaseVoltageV); pass it through - * rather than hard-coding 230/400 here. */ - voltage: number; - /** Single-run cable length in meters (circuits.cableLength). */ - lengthM: number; - layingMethod: LayingMethod; - conductorMaterial: ConductorMaterial; - insulation: InsulationMaterial; - ambientTemperatureC: number; - groupingCircuits: number; - maxVoltageDropPercent: number; - harmonicNeutralLoad: HarmonicNeutralLoad; - /** Optional: existing protection device rated current (LS/circuit - * breaker), for the simplified coordination check below. */ - existingProtectionRatedCurrentA?: number; - /** Optional: enables the practical-minimum-cross-section convention for - * "single_phase" circuits, see PRACTICAL_MINIMUM_CROSS_SECTION_MM2. */ - circuitCategory?: CircuitCategory; -} - -export interface CrossSectionRow { - crossSectionMm2: number; - ratedCurrentA: number | null; - correctedCurrentA: number | null; - currentSufficient: boolean | null; - voltageDropPercent: number | null; - voltageDropSufficient: boolean | null; - /** Longest single-run length (m) at which this cross-section still meets - * the requested max voltage drop, at the given load/cosPhi/phase - the - * inverse of the voltage-drop formula used for voltageDropPercent above. - * null when the operating current is zero (division by zero). */ - maxLengthForVoltageDropM: number | null; - recommended: boolean; -} - -export interface ProtectionCoordinationResult { - ratedCurrentA: number; - /** Simplified In <= Iz check only - not full overload (I2 <= 1.45 x Iz) - * or short-circuit coordination. */ - coordinated: boolean; -} - -export interface CableSizingResult { - /** false: no verified capacity table for this layingMethod/insulation - * combination - every field below is null/empty, never a guess. */ - dataVerified: boolean; - operatingCurrentA: number; - /** max(operatingCurrentA, existingProtectionRatedCurrentA). The cable - * capacity (Iz) must cover the protective device's rated current (In), - * not just the actual load current - a breaker only trips at In, so a - * cable sized for Ib alone could overheat under sustained load below the - * trip threshold. This is the value cross-section selection actually - * uses; operatingCurrentA is kept for display/voltage-drop only. */ - designCurrentA: number; - crossSectionByCapacityMm2: number | null; - crossSectionByVoltageDropMm2: number | null; - recommendedCrossSectionMm2: number | null; - voltageDropAtRecommendedPercent: number | null; - combinedDerationFactor: number; - harmonicReductionApplied: boolean; - /** true if the recommendation was raised to satisfy - * PRACTICAL_MINIMUM_CROSS_SECTION_MM2 (a convention, not a thermal/ - * voltage-drop requirement of this specific circuit). */ - practicalMinimumApplied: boolean; - rows: CrossSectionRow[]; - protectionCoordination: ProtectionCoordinationResult | null; -} - -function operatingCurrentA(input: CableSizingInput): number { - if (input.mode === "current") { - return input.currentA ?? 0; - } - const powerW = (input.powerKw ?? 0) * 1000; - return input.phase === 1 - ? powerW / (input.voltage * input.cosPhi) - : powerW / (Math.sqrt(3) * input.voltage * input.cosPhi); -} - -export function calculateCableSizing(input: CableSizingInput): CableSizingResult { - const ib = operatingCurrentA(input); - - const designCurrentA = - input.existingProtectionRatedCurrentA != null - ? Math.max(ib, input.existingProtectionRatedCurrentA) - : ib; - - if (!isCableSizingDataVerified(input.layingMethod, input.insulation)) { - return { - dataVerified: false, - operatingCurrentA: ib, - designCurrentA, - crossSectionByCapacityMm2: null, - crossSectionByVoltageDropMm2: null, - recommendedCrossSectionMm2: null, - voltageDropAtRecommendedPercent: null, - combinedDerationFactor: 1, - harmonicReductionApplied: false, - practicalMinimumApplied: false, - rows: [], - protectionCoordination: null, - }; - } - - const coreCount: CoreCount = input.phase === 1 ? 2 : 3; - const aluminumFactor = - input.conductorMaterial === "aluminum" ? ALUMINUM_CAPACITY_FACTOR : 1; - const kappa = KAPPA[input.conductorMaterial]; - const group = LAYING_METHOD_GROUP[input.layingMethod]; - const temperatureTable = - group === "air" ? TEMPERATURE_FACTOR_AIR : TEMPERATURE_FACTOR_GROUND; - const groupingTable = group === "air" ? GROUPING_FACTOR_AIR : GROUPING_FACTOR_GROUND; - const temperatureFactor = temperatureTable[input.ambientTemperatureC] ?? 1; - const groupingFactor = groupingTable[input.groupingCircuits] ?? 1; - - const harmonicReductionApplied = - input.phase === 3 && input.harmonicNeutralLoad === "15to33Percent"; - const harmonicFactor = harmonicReductionApplied ? HARMONIC_REDUCTION_FACTOR : 1; - - const combinedDerationFactor = temperatureFactor * groupingFactor * harmonicFactor; - const voltageDropCoefficient = input.phase === 1 ? 2 : Math.sqrt(3); - - const ratedCurrents = CURRENT_CAPACITY_A[input.layingMethod]![coreCount]; - const correctedCurrents = ratedCurrents.map((value) => - value == null ? null : value * aluminumFactor * combinedDerationFactor - ); - const voltageDrops = CROSS_SECTIONS_MM2.map( - (crossSection) => - ((voltageDropCoefficient * input.lengthM * ib * input.cosPhi) / - (kappa * crossSection) / - input.voltage) * - 100 - ); - - const indexByCapacity = correctedCurrents.findIndex( - (value) => value != null && value >= designCurrentA - ); - const indexByVoltageDrop = voltageDrops.findIndex( - (value) => value <= input.maxVoltageDropPercent - ); - let indexRecommended = -1; - if (indexByCapacity >= 0 && indexByVoltageDrop >= 0) { - indexRecommended = Math.max(indexByCapacity, indexByVoltageDrop); - } - - // Practical-minimum convention (see PRACTICAL_MINIMUM_CROSS_SECTION_MM2): - // only ever raises the recommendation, never lowers what the thermal/ - // voltage-drop calculation already required. - const practicalMinimumMm2 = input.circuitCategory - ? PRACTICAL_MINIMUM_CROSS_SECTION_MM2[input.circuitCategory] - : undefined; - let practicalMinimumApplied = false; - if ( - indexRecommended >= 0 && - practicalMinimumMm2 != null && - CROSS_SECTIONS_MM2[indexRecommended] < practicalMinimumMm2 - ) { - const minimumIndex = CROSS_SECTIONS_MM2.indexOf( - practicalMinimumMm2 as (typeof CROSS_SECTIONS_MM2)[number] - ); - if (minimumIndex >= 0) { - indexRecommended = minimumIndex; - practicalMinimumApplied = true; - } - } - - const voltageDropCurrentBasis = ib * input.cosPhi; - const rows: CrossSectionRow[] = CROSS_SECTIONS_MM2.map((crossSection, i) => { - const rated = ratedCurrents[i]; - const corrected = correctedCurrents[i]; - const currentSufficient = corrected == null ? null : corrected >= designCurrentA; - const voltageDropSufficient = voltageDrops[i] <= input.maxVoltageDropPercent; - const maxLengthForVoltageDropM = - rated == null || voltageDropCurrentBasis <= 0 - ? null - : (input.maxVoltageDropPercent * kappa * crossSection * input.voltage) / - (100 * voltageDropCoefficient * voltageDropCurrentBasis); - return { - crossSectionMm2: crossSection, - ratedCurrentA: rated, - correctedCurrentA: corrected, - currentSufficient, - voltageDropPercent: rated == null ? null : voltageDrops[i], - voltageDropSufficient: rated == null ? null : voltageDropSufficient, - maxLengthForVoltageDropM, - recommended: i === indexRecommended, - }; - }); - - let protectionCoordination: ProtectionCoordinationResult | null = null; - if (input.existingProtectionRatedCurrentA != null && indexRecommended >= 0) { - const recommendedRow = rows[indexRecommended]; - protectionCoordination = { - ratedCurrentA: input.existingProtectionRatedCurrentA, - coordinated: - recommendedRow.correctedCurrentA != null && - input.existingProtectionRatedCurrentA <= recommendedRow.correctedCurrentA, - }; - } - - return { - dataVerified: true, - operatingCurrentA: ib, - designCurrentA, - crossSectionByCapacityMm2: - indexByCapacity >= 0 ? CROSS_SECTIONS_MM2[indexByCapacity] : null, - crossSectionByVoltageDropMm2: - indexByVoltageDrop >= 0 ? CROSS_SECTIONS_MM2[indexByVoltageDrop] : null, - recommendedCrossSectionMm2: - indexRecommended >= 0 ? CROSS_SECTIONS_MM2[indexRecommended] : null, - voltageDropAtRecommendedPercent: - indexRecommended >= 0 ? voltageDrops[indexRecommended] : null, - combinedDerationFactor, - harmonicReductionApplied, - practicalMinimumApplied, - rows, - protectionCoordination, - }; -} - -export interface CableSizingAlert { - kind: "critical" | "warn" | "info" | "ok"; - text: string; -} - -export function buildCableSizingAlerts( - input: CableSizingInput, - result: CableSizingResult -): CableSizingAlert[] { - const alerts: CableSizingAlert[] = []; - - if (!result.dataVerified) { - alerts.push({ - kind: "critical", - text: `Für Verlegeart ${input.layingMethod} mit Isolierstoff ${input.insulation.toUpperCase()} liegen keine geprüften Strombelastbarkeits-Tabellenwerte vor - keine Dimensionierung möglich.`, - }); - return alerts; - } - - const byCapacity = result.crossSectionByCapacityMm2; - const byDrop = result.crossSectionByVoltageDropMm2; - const recommended = result.recommendedCrossSectionMm2; - - if (byCapacity == null) { - const dueToBreaker = - input.existingProtectionRatedCurrentA != null && - input.existingProtectionRatedCurrentA > result.operatingCurrentA; - alerts.push({ - kind: "critical", - text: dueToBreaker - ? `Vorhandene Sicherung (${input.existingProtectionRatedCurrentA} A) übersteigt die Belastbarkeit aller Standardquerschnitte bei Verlegeart ${input.layingMethod} - nicht der Betriebsstrom (${result.operatingCurrentA.toFixed(1)} A). Größere Verlegeart oder kleinere Sicherung prüfen.` - : `Betriebsstrom ${result.operatingCurrentA.toFixed(1)} A übersteigt die Belastbarkeit aller Standardquerschnitte bei Verlegeart ${input.layingMethod}.`, - }); - } else if (byDrop != null && byDrop > byCapacity) { - alerts.push({ - kind: "warn", - text: `Spannungsfall ist maßgeblich, nicht die Belastbarkeit: ${byCapacity} mm² würde thermisch reichen, ${byDrop} mm² ist wegen ΔU ≤ ${input.maxVoltageDropPercent}% nötig.`, - }); - } - if (result.harmonicReductionApplied) { - alerts.push({ - kind: "info", - text: "Reduktionsfaktor 0,86 wegen Oberschwingungsanteil 15-33 % im Neutralleiter angewendet (IEC 60364-5-52).", - }); - } - if (input.phase === 3 && input.harmonicNeutralLoad === "over33Percent") { - alerts.push({ - kind: "warn", - text: "3. Harmonische > 33 %: Neutralleiter muss wie ein Außenleiter dimensioniert werden - hier nicht automatisch berücksichtigt.", - }); - } - if (result.practicalMinimumApplied) { - alerts.push({ - kind: "info", - text: `Auf ${result.recommendedCrossSectionMm2} mm² angehoben (Praxis-Mindestquerschnitt für 1-phasige Stromkreise, keine reine Berechnungsanforderung).`, - }); - } - if (result.combinedDerationFactor < 1) { - alerts.push({ - kind: "info", - text: `Korrekturfaktor angewendet: ${result.combinedDerationFactor.toFixed(2)} (Temperatur × Häufung${result.harmonicReductionApplied ? " × Oberschwingungen" : ""}).`, - }); - } - if (result.protectionCoordination) { - alerts.push( - result.protectionCoordination.coordinated - ? { - kind: "info", - text: `Vorhandene Sicherung (${result.protectionCoordination.ratedCurrentA} A) ist bei der Dimensionierung berücksichtigt (In ≤ Iz, vereinfachte Prüfung, ersetzt keine vollständige Überlast-/Kurzschlussprüfung).`, - } - : { - kind: "warn", - text: `Vorhandene Sicherung (${result.protectionCoordination.ratedCurrentA} A) übersteigt die Belastbarkeit des empfohlenen Querschnitts - Koordination prüfen.`, - } - ); - } - if (alerts.length === 0 && recommended != null) { - alerts.push({ - kind: "ok", - text: `${recommended} mm² (${input.conductorMaterial === "aluminum" ? "Alu" : "Cu"}) deckt bei ${input.lengthM} m Länge sowohl Belastbarkeit (${result.designCurrentA.toFixed(1)} A${input.existingProtectionRatedCurrentA != null ? ", inkl. vorhandener Sicherung" : ""}) als auch Spannungsfall (≤ ${input.maxVoltageDropPercent}%) ab.`, - }); - } - const recommendedRow = result.rows.find((row) => row.recommended); - if (recommendedRow?.maxLengthForVoltageDropM != null) { - alerts.push({ - kind: "info", - text: `Maximale Länge bei ${recommendedRow.crossSectionMm2} mm² und ΔU ≤ ${input.maxVoltageDropPercent}%: ${recommendedRow.maxLengthForVoltageDropM.toFixed(0)} m.`, - }); - } - return alerts; -} diff --git a/src/cable-sizing/domain/cable-sizing-contracts.ts b/src/cable-sizing/domain/cable-sizing-contracts.ts deleted file mode 100644 index 5648d8d..0000000 --- a/src/cable-sizing/domain/cable-sizing-contracts.ts +++ /dev/null @@ -1,49 +0,0 @@ -// Request/response validation for the cable-sizing HTTP API. Pure - depends -// only on zod and the sibling calculation module, never on db/server. -import { z } from "zod"; -import { - CONDUCTOR_MATERIALS, - HARMONIC_NEUTRAL_LOAD_OPTIONS, - INSULATION_MATERIALS, - LAYING_METHODS, - type CableSizingInput, -} from "./cable-sizing-calculation.js"; - -export const cableSizingRequestSchema = z - .object({ - phase: z.union([z.literal(1), z.literal(3)]), - mode: z.enum(["power", "current"]), - powerKw: z.number().min(0).optional(), - currentA: z.number().min(0).optional(), - cosPhi: z.number().min(0.6).max(1), - voltage: z.number().positive(), - lengthM: z.number().min(0), - layingMethod: z.enum(LAYING_METHODS), - conductorMaterial: z.enum(CONDUCTOR_MATERIALS), - insulation: z.enum(INSULATION_MATERIALS), - ambientTemperatureC: z.number(), - groupingCircuits: z.number().int().min(1), - maxVoltageDropPercent: z.number().min(1).max(5), - harmonicNeutralLoad: z.enum(HARMONIC_NEUTRAL_LOAD_OPTIONS), - existingProtectionRatedCurrentA: z.number().positive().optional(), - circuitCategory: z.enum(["lighting", "single_phase", "three_phase"]).optional(), - // Optional context, persisted with the audit-log entry only - never - // used for the calculation itself. - context: z - .object({ - projectId: z.string().min(1).optional(), - circuitId: z.string().min(1).optional(), - equipmentIdentifier: z.string().min(1).optional(), - }) - .optional(), - }) - .refine((v) => (v.mode === "power" ? v.powerKw != null : v.currentA != null), { - message: "powerKw is required for mode 'power', currentA for mode 'current'", - }); - -export type CableSizingRequest = z.infer; - -// Structural check that the request schema stays a superset of the domain -// input shape (minus context, which is API-only metadata). -type _AssertAssignable = T; -type _Check = _AssertAssignable>; diff --git a/src/db/client.ts b/src/db/client.ts index 4d0c97b..02cfc7e 100644 --- a/src/db/client.ts +++ b/src/db/client.ts @@ -7,10 +7,3 @@ const defaultDatabaseContext = createDatabaseContext( export const db = defaultDatabaseContext.db; -/** - * Schliesst das SQLite-Handle des Prozesses. Nur beim Herunterfahren - * aufrufen: danach schlaegt jede Abfrage ueber {@link db} fehl. - */ -export function closeDefaultDatabase(): void { - defaultDatabaseContext.close(); -} diff --git a/src/db/migrations/0007_watery_kingpin.sql b/src/db/migrations/0007_watery_kingpin.sql deleted file mode 100644 index a4f22bf..0000000 --- a/src/db/migrations/0007_watery_kingpin.sql +++ /dev/null @@ -1,14 +0,0 @@ -CREATE TABLE `cable_sizing_calculations` ( - `id` text PRIMARY KEY NOT NULL, - `project_id` text, - `circuit_id` text, - `equipment_identifier` text, - `input` text NOT NULL, - `result` text NOT NULL, - `applied_to_circuit` integer DEFAULT 0 NOT NULL, - `created_at` integer DEFAULT (unixepoch()) NOT NULL, - FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE set null, - FOREIGN KEY (`circuit_id`) REFERENCES `circuits`(`id`) ON UPDATE no action ON DELETE set null -); ---> statement-breakpoint -CREATE INDEX `cable_sizing_calculations_circuit_id_idx` ON `cable_sizing_calculations` (`circuit_id`); \ No newline at end of file diff --git a/src/db/migrations/0008_colossal_terrax.sql b/src/db/migrations/0008_colossal_terrax.sql deleted file mode 100644 index 5f27040..0000000 --- a/src/db/migrations/0008_colossal_terrax.sql +++ /dev/null @@ -1,12 +0,0 @@ -CREATE TABLE `document_branding_assets` ( - `key` text PRIMARY KEY NOT NULL, - `mime_type` text NOT NULL, - `file_name` text, - `data` blob NOT NULL, - `updated_at` integer DEFAULT (unixepoch()) NOT NULL -); ---> statement-breakpoint -ALTER TABLE `distribution_boards` ADD `board_number` text;--> statement-breakpoint -ALTER TABLE `distribution_boards` ADD `location` text;--> statement-breakpoint -ALTER TABLE `distribution_boards` ADD `fed_from` text;--> statement-breakpoint -ALTER TABLE `distribution_boards` ADD `remark` text; \ No newline at end of file diff --git a/src/db/migrations/meta/0007_snapshot.json b/src/db/migrations/meta/0007_snapshot.json deleted file mode 100644 index 8a67832..0000000 --- a/src/db/migrations/meta/0007_snapshot.json +++ /dev/null @@ -1,2522 +0,0 @@ -{ - "version": "6", - "dialect": "sqlite", - "id": "98546058-8ad8-4aa1-99ad-f4e915bea89c", - "prevId": "b25e1a40-c400-4848-bc24-a04bda7a8209", - "tables": { - "cable_sizing_calculations": { - "name": "cable_sizing_calculations", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "circuit_id": { - "name": "circuit_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "equipment_identifier": { - "name": "equipment_identifier", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "input": { - "name": "input", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "result": { - "name": "result", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "applied_to_circuit": { - "name": "applied_to_circuit", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(unixepoch())" - } - }, - "indexes": { - "cable_sizing_calculations_circuit_id_idx": { - "name": "cable_sizing_calculations_circuit_id_idx", - "columns": [ - "circuit_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "cable_sizing_calculations_project_id_projects_id_fk": { - "name": "cable_sizing_calculations_project_id_projects_id_fk", - "tableFrom": "cable_sizing_calculations", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "cable_sizing_calculations_circuit_id_circuits_id_fk": { - "name": "cable_sizing_calculations_circuit_id_circuits_id_fk", - "tableFrom": "cable_sizing_calculations", - "tableTo": "circuits", - "columnsFrom": [ - "circuit_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "circuit_device_rows": { - "name": "circuit_device_rows", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "circuit_id": { - "name": "circuit_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "linked_project_device_id": { - "name": "linked_project_device_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "sort_order": { - "name": "sort_order", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "display_name": { - "name": "display_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "phase_type": { - "name": "phase_type", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "connection_kind": { - "name": "connection_kind", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "cost_group": { - "name": "cost_group", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "category": { - "name": "category", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "level": { - "name": "level", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "room_id": { - "name": "room_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "room_number_snapshot": { - "name": "room_number_snapshot", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "room_name_snapshot": { - "name": "room_name_snapshot", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "quantity": { - "name": "quantity", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "manual_quantity": { - "name": "manual_quantity", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - }, - "power_per_unit": { - "name": "power_per_unit", - "type": "real", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "simultaneity_factor": { - "name": "simultaneity_factor", - "type": "real", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "cos_phi": { - "name": "cos_phi", - "type": "real", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "remark": { - "name": "remark", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "overridden_fields": { - "name": "overridden_fields", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "circuit_device_rows_circuit_id_idx": { - "name": "circuit_device_rows_circuit_id_idx", - "columns": [ - "circuit_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "circuit_device_rows_circuit_id_circuits_id_fk": { - "name": "circuit_device_rows_circuit_id_circuits_id_fk", - "tableFrom": "circuit_device_rows", - "tableTo": "circuits", - "columnsFrom": [ - "circuit_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "circuit_device_rows_linked_project_device_id_project_devices_id_fk": { - "name": "circuit_device_rows_linked_project_device_id_project_devices_id_fk", - "tableFrom": "circuit_device_rows", - "tableTo": "project_devices", - "columnsFrom": [ - "linked_project_device_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "circuit_device_rows_room_id_rooms_id_fk": { - "name": "circuit_device_rows_room_id_rooms_id_fk", - "tableFrom": "circuit_device_rows", - "tableTo": "rooms", - "columnsFrom": [ - "room_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "circuit_list_equipment_identifiers": { - "name": "circuit_list_equipment_identifiers", - "columns": { - "owner_type": { - "name": "owner_type", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "owner_id": { - "name": "owner_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "circuit_list_id": { - "name": "circuit_list_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "equipment_identifier": { - "name": "equipment_identifier", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "circuit_list_equipment_identifiers_owner_unique": { - "name": "circuit_list_equipment_identifiers_owner_unique", - "columns": [ - "owner_type", - "owner_id" - ], - "isUnique": true - }, - "circuit_list_equipment_identifiers_list_identifier_unique": { - "name": "circuit_list_equipment_identifiers_list_identifier_unique", - "columns": [ - "circuit_list_id", - "equipment_identifier" - ], - "isUnique": true - } - }, - "foreignKeys": { - "circuit_list_equipment_identifiers_circuit_list_id_circuit_lists_id_fk": { - "name": "circuit_list_equipment_identifiers_circuit_list_id_circuit_lists_id_fk", - "tableFrom": "circuit_list_equipment_identifiers", - "tableTo": "circuit_lists", - "columnsFrom": [ - "circuit_list_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "circuit_lists": { - "name": "circuit_lists", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "distribution_board_id": { - "name": "distribution_board_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "circuit_lists_distribution_board_id_unique": { - "name": "circuit_lists_distribution_board_id_unique", - "columns": [ - "distribution_board_id" - ], - "isUnique": true - } - }, - "foreignKeys": { - "circuit_lists_project_id_projects_id_fk": { - "name": "circuit_lists_project_id_projects_id_fk", - "tableFrom": "circuit_lists", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "circuit_lists_distribution_board_id_distribution_boards_id_fk": { - "name": "circuit_lists_distribution_board_id_distribution_boards_id_fk", - "tableFrom": "circuit_lists", - "tableTo": "distribution_boards", - "columnsFrom": [ - "distribution_board_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "circuit_protection_devices": { - "name": "circuit_protection_devices", - "columns": { - "circuit_id": { - "name": "circuit_id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "rated_current_a": { - "name": "rated_current_a", - "type": "real", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "fuse_utilization_category": { - "name": "fuse_utilization_category", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "trip_characteristic": { - "name": "trip_characteristic", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "rcd_type": { - "name": "rcd_type", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "rated_residual_current_ma": { - "name": "rated_residual_current_ma", - "type": "real", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "circuit_protection_devices_circuit_id_circuits_id_fk": { - "name": "circuit_protection_devices_circuit_id_circuits_id_fk", - "tableFrom": "circuit_protection_devices", - "tableTo": "circuits", - "columnsFrom": [ - "circuit_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "circuit_sections": { - "name": "circuit_sections", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "circuit_list_id": { - "name": "circuit_list_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "key": { - "name": "key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "display_name": { - "name": "display_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "prefix": { - "name": "prefix", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "sort_order": { - "name": "sort_order", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - }, - "category": { - "name": "category", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "group_number": { - "name": "group_number", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "circuit_sections_list_key_unique": { - "name": "circuit_sections_list_key_unique", - "columns": [ - "circuit_list_id", - "key" - ], - "isUnique": true - }, - "circuit_sections_list_prefix_unique": { - "name": "circuit_sections_list_prefix_unique", - "columns": [ - "circuit_list_id", - "prefix" - ], - "isUnique": true - }, - "circuit_sections_list_category_group_unique": { - "name": "circuit_sections_list_category_group_unique", - "columns": [ - "circuit_list_id", - "category", - "group_number" - ], - "isUnique": true - } - }, - "foreignKeys": { - "circuit_sections_circuit_list_id_circuit_lists_id_fk": { - "name": "circuit_sections_circuit_list_id_circuit_lists_id_fk", - "tableFrom": "circuit_sections", - "tableTo": "circuit_lists", - "columnsFrom": [ - "circuit_list_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "circuits": { - "name": "circuits", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "circuit_list_id": { - "name": "circuit_list_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "section_id": { - "name": "section_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "equipment_identifier": { - "name": "equipment_identifier", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "display_name": { - "name": "display_name", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "sort_order": { - "name": "sort_order", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - }, - "cable_type": { - "name": "cable_type", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "cable_cross_section": { - "name": "cable_cross_section", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "cable_length": { - "name": "cable_length", - "type": "real", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "rcd_assignment": { - "name": "rcd_assignment", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "terminal_designation": { - "name": "terminal_designation", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "voltage": { - "name": "voltage", - "type": "real", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "control_requirement": { - "name": "control_requirement", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "is_reserve": { - "name": "is_reserve", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - }, - "remark": { - "name": "remark", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "circuits_section_id_idx": { - "name": "circuits_section_id_idx", - "columns": [ - "section_id" - ], - "isUnique": false - }, - "circuits_list_equipment_identifier_unique": { - "name": "circuits_list_equipment_identifier_unique", - "columns": [ - "circuit_list_id", - "equipment_identifier" - ], - "isUnique": true - } - }, - "foreignKeys": { - "circuits_circuit_list_id_circuit_lists_id_fk": { - "name": "circuits_circuit_list_id_circuit_lists_id_fk", - "tableFrom": "circuits", - "tableTo": "circuit_lists", - "columnsFrom": [ - "circuit_list_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "circuits_section_id_circuit_sections_id_fk": { - "name": "circuits_section_id_circuit_sections_id_fk", - "tableFrom": "circuits", - "tableTo": "circuit_sections", - "columnsFrom": [ - "section_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "distribution_board_component_protection_devices": { - "name": "distribution_board_component_protection_devices", - "columns": { - "component_id": { - "name": "component_id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "rated_current_a": { - "name": "rated_current_a", - "type": "real", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "fuse_utilization_category": { - "name": "fuse_utilization_category", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "trip_characteristic": { - "name": "trip_characteristic", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "rcd_type": { - "name": "rcd_type", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "rated_residual_current_ma": { - "name": "rated_residual_current_ma", - "type": "real", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "distribution_board_component_protection_devices_component_id_distribution_board_components_id_fk": { - "name": "distribution_board_component_protection_devices_component_id_distribution_board_components_id_fk", - "tableFrom": "distribution_board_component_protection_devices", - "tableTo": "distribution_board_components", - "columnsFrom": [ - "component_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "distribution_board_components": { - "name": "distribution_board_components", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "circuit_list_id": { - "name": "circuit_list_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "section_id": { - "name": "section_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "equipment_identifier": { - "name": "equipment_identifier", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "role": { - "name": "role", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "placement": { - "name": "placement", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "sort_order": { - "name": "sort_order", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - } - }, - "indexes": { - "distribution_board_components_list_identifier_unique": { - "name": "distribution_board_components_list_identifier_unique", - "columns": [ - "circuit_list_id", - "equipment_identifier" - ], - "isUnique": true - }, - "distribution_board_components_section_role_unique": { - "name": "distribution_board_components_section_role_unique", - "columns": [ - "section_id", - "role" - ], - "isUnique": true - } - }, - "foreignKeys": { - "distribution_board_components_circuit_list_id_circuit_lists_id_fk": { - "name": "distribution_board_components_circuit_list_id_circuit_lists_id_fk", - "tableFrom": "distribution_board_components", - "tableTo": "circuit_lists", - "columnsFrom": [ - "circuit_list_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "distribution_board_components_section_id_circuit_sections_id_fk": { - "name": "distribution_board_components_section_id_circuit_sections_id_fk", - "tableFrom": "distribution_board_components", - "tableTo": "circuit_sections", - "columnsFrom": [ - "section_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "distribution_boards": { - "name": "distribution_boards", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "floor_id": { - "name": "floor_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "supply_type": { - "name": "supply_type", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "simultaneity_factor": { - "name": "simultaneity_factor", - "type": "real", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 1 - } - }, - "indexes": {}, - "foreignKeys": { - "distribution_boards_project_id_projects_id_fk": { - "name": "distribution_boards_project_id_projects_id_fk", - "tableFrom": "distribution_boards", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "distribution_boards_floor_id_floors_id_fk": { - "name": "distribution_boards_floor_id_floors_id_fk", - "tableFrom": "distribution_boards", - "tableTo": "floors", - "columnsFrom": [ - "floor_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "external_csv_configurations": { - "name": "external_csv_configurations", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "configuration_version": { - "name": "configuration_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "configuration": { - "name": "configuration", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "external_csv_configurations_project_id_unique": { - "name": "external_csv_configurations_project_id_unique", - "columns": [ - "project_id" - ], - "isUnique": true - } - }, - "foreignKeys": { - "external_csv_configurations_project_id_projects_id_fk": { - "name": "external_csv_configurations_project_id_projects_id_fk", - "tableFrom": "external_csv_configurations", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "external_import_batches": { - "name": "external_import_batches", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "source_id": { - "name": "source_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "import_kind": { - "name": "import_kind", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "imported_at_iso": { - "name": "imported_at_iso", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "file_name": { - "name": "file_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "sha256": { - "name": "sha256", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "applied_project_revision": { - "name": "applied_project_revision", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "configuration_version": { - "name": "configuration_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "configuration_snapshot": { - "name": "configuration_snapshot", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "original_bytes": { - "name": "original_bytes", - "type": "blob", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "document": { - "name": "document", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "external_import_batches_project_revision_idx": { - "name": "external_import_batches_project_revision_idx", - "columns": [ - "project_id", - "applied_project_revision" - ], - "isUnique": false - }, - "external_import_batches_source_imported_idx": { - "name": "external_import_batches_source_imported_idx", - "columns": [ - "source_id", - "imported_at_iso" - ], - "isUnique": false - } - }, - "foreignKeys": { - "external_import_batches_project_id_projects_id_fk": { - "name": "external_import_batches_project_id_projects_id_fk", - "tableFrom": "external_import_batches", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "external_import_batches_source_id_external_model_sources_id_fk": { - "name": "external_import_batches_source_id_external_model_sources_id_fk", - "tableFrom": "external_import_batches", - "tableTo": "external_model_sources", - "columnsFrom": [ - "source_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "external_model_objects": { - "name": "external_model_objects", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "source_id": { - "name": "source_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "ifc_guid": { - "name": "ifc_guid", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "last_seen_import_batch_id": { - "name": "last_seen_import_batch_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "last_accepted_import_batch_id": { - "name": "last_accepted_import_batch_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "accepted_source_values": { - "name": "accepted_source_values", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "planning_values": { - "name": "planning_values", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "overridden_fields": { - "name": "overridden_fields", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "external_room_mapping_id": { - "name": "external_room_mapping_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "distribution_board_id": { - "name": "distribution_board_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "linked_project_device_id": { - "name": "linked_project_device_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "circuit_device_row_id": { - "name": "circuit_device_row_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "presence_status": { - "name": "presence_status", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'present'" - } - }, - "indexes": { - "external_model_objects_source_ifc_guid_unique": { - "name": "external_model_objects_source_ifc_guid_unique", - "columns": [ - "source_id", - "ifc_guid" - ], - "isUnique": true - }, - "external_model_objects_project_presence_idx": { - "name": "external_model_objects_project_presence_idx", - "columns": [ - "project_id", - "presence_status" - ], - "isUnique": false - }, - "external_model_objects_distribution_board_idx": { - "name": "external_model_objects_distribution_board_idx", - "columns": [ - "distribution_board_id" - ], - "isUnique": false - }, - "external_model_objects_circuit_device_row_idx": { - "name": "external_model_objects_circuit_device_row_idx", - "columns": [ - "circuit_device_row_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "external_model_objects_project_id_projects_id_fk": { - "name": "external_model_objects_project_id_projects_id_fk", - "tableFrom": "external_model_objects", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "external_model_objects_source_id_external_model_sources_id_fk": { - "name": "external_model_objects_source_id_external_model_sources_id_fk", - "tableFrom": "external_model_objects", - "tableTo": "external_model_sources", - "columnsFrom": [ - "source_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "external_model_objects_last_seen_import_batch_id_external_import_batches_id_fk": { - "name": "external_model_objects_last_seen_import_batch_id_external_import_batches_id_fk", - "tableFrom": "external_model_objects", - "tableTo": "external_import_batches", - "columnsFrom": [ - "last_seen_import_batch_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - }, - "external_model_objects_last_accepted_import_batch_id_external_import_batches_id_fk": { - "name": "external_model_objects_last_accepted_import_batch_id_external_import_batches_id_fk", - "tableFrom": "external_model_objects", - "tableTo": "external_import_batches", - "columnsFrom": [ - "last_accepted_import_batch_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - }, - "external_model_objects_external_room_mapping_id_external_room_mappings_id_fk": { - "name": "external_model_objects_external_room_mapping_id_external_room_mappings_id_fk", - "tableFrom": "external_model_objects", - "tableTo": "external_room_mappings", - "columnsFrom": [ - "external_room_mapping_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "external_model_objects_distribution_board_id_distribution_boards_id_fk": { - "name": "external_model_objects_distribution_board_id_distribution_boards_id_fk", - "tableFrom": "external_model_objects", - "tableTo": "distribution_boards", - "columnsFrom": [ - "distribution_board_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "external_model_objects_linked_project_device_id_project_devices_id_fk": { - "name": "external_model_objects_linked_project_device_id_project_devices_id_fk", - "tableFrom": "external_model_objects", - "tableTo": "project_devices", - "columnsFrom": [ - "linked_project_device_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "external_model_objects_circuit_device_row_id_circuit_device_rows_id_fk": { - "name": "external_model_objects_circuit_device_row_id_circuit_device_rows_id_fk", - "tableFrom": "external_model_objects", - "tableTo": "circuit_device_rows", - "columnsFrom": [ - "circuit_device_row_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "external_model_sources": { - "name": "external_model_sources", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "source_type": { - "name": "source_type", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'revit_csv'" - } - }, - "indexes": { - "external_model_sources_project_type_unique": { - "name": "external_model_sources_project_type_unique", - "columns": [ - "project_id", - "source_type" - ], - "isUnique": true - } - }, - "foreignKeys": { - "external_model_sources_project_id_projects_id_fk": { - "name": "external_model_sources_project_id_projects_id_fk", - "tableFrom": "external_model_sources", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "external_room_mappings": { - "name": "external_room_mappings", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "source_id": { - "name": "source_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "normalized_source_room_key": { - "name": "normalized_source_room_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "source_floor_name": { - "name": "source_floor_name", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "source_room_number": { - "name": "source_room_number", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "source_room_name": { - "name": "source_room_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "room_id": { - "name": "room_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "default_distribution_board_id": { - "name": "default_distribution_board_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "external_room_mappings_source_key_unique": { - "name": "external_room_mappings_source_key_unique", - "columns": [ - "source_id", - "normalized_source_room_key" - ], - "isUnique": true - }, - "external_room_mappings_project_room_idx": { - "name": "external_room_mappings_project_room_idx", - "columns": [ - "project_id", - "room_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "external_room_mappings_project_id_projects_id_fk": { - "name": "external_room_mappings_project_id_projects_id_fk", - "tableFrom": "external_room_mappings", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "external_room_mappings_source_id_external_model_sources_id_fk": { - "name": "external_room_mappings_source_id_external_model_sources_id_fk", - "tableFrom": "external_room_mappings", - "tableTo": "external_model_sources", - "columnsFrom": [ - "source_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "external_room_mappings_room_id_rooms_id_fk": { - "name": "external_room_mappings_room_id_rooms_id_fk", - "tableFrom": "external_room_mappings", - "tableTo": "rooms", - "columnsFrom": [ - "room_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "external_room_mappings_default_distribution_board_id_distribution_boards_id_fk": { - "name": "external_room_mappings_default_distribution_board_id_distribution_boards_id_fk", - "tableFrom": "external_room_mappings", - "tableTo": "distribution_boards", - "columnsFrom": [ - "default_distribution_board_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "floors": { - "name": "floors", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "sort_order": { - "name": "sort_order", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - } - }, - "indexes": {}, - "foreignKeys": { - "floors_project_id_projects_id_fk": { - "name": "floors_project_id_projects_id_fk", - "tableFrom": "floors", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "global_devices": { - "name": "global_devices", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "display_name": { - "name": "display_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "category": { - "name": "category", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "quantity": { - "name": "quantity", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "installed_power_per_unit_kw": { - "name": "installed_power_per_unit_kw", - "type": "real", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "demand_factor": { - "name": "demand_factor", - "type": "real", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "voltage_v": { - "name": "voltage_v", - "type": "real", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "phase_count": { - "name": "phase_count", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "power_factor": { - "name": "power_factor", - "type": "real", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "note": { - "name": "note", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "project_change_sets": { - "name": "project_change_sets", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_revision_id": { - "name": "project_revision_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "command_type": { - "name": "command_type", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "payload_schema_version": { - "name": "payload_schema_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "forward_payload_json": { - "name": "forward_payload_json", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "inverse_payload_json": { - "name": "inverse_payload_json", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "project_change_sets_revision_unique": { - "name": "project_change_sets_revision_unique", - "columns": [ - "project_revision_id" - ], - "isUnique": true - } - }, - "foreignKeys": { - "project_change_sets_project_revision_id_project_revisions_id_fk": { - "name": "project_change_sets_project_revision_id_project_revisions_id_fk", - "tableFrom": "project_change_sets", - "tableTo": "project_revisions", - "columnsFrom": [ - "project_revision_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "project_devices": { - "name": "project_devices", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "display_name": { - "name": "display_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "phase_type": { - "name": "phase_type", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'single_phase'" - }, - "connection_kind": { - "name": "connection_kind", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "cost_group": { - "name": "cost_group", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "category": { - "name": "category", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "quantity": { - "name": "quantity", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "power_per_unit": { - "name": "power_per_unit", - "type": "real", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - }, - "simultaneity_factor": { - "name": "simultaneity_factor", - "type": "real", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 1 - }, - "cos_phi": { - "name": "cos_phi", - "type": "real", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "remark": { - "name": "remark", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "voltage_v": { - "name": "voltage_v", - "type": "real", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "project_devices_project_id_projects_id_fk": { - "name": "project_devices_project_id_projects_id_fk", - "tableFrom": "project_devices", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "project_history_stack_entries": { - "name": "project_history_stack_entries", - "columns": { - "change_set_id": { - "name": "change_set_id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "stack": { - "name": "stack", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "position": { - "name": "position", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "project_history_stack_entries_position_unique": { - "name": "project_history_stack_entries_position_unique", - "columns": [ - "project_id", - "stack", - "position" - ], - "isUnique": true - } - }, - "foreignKeys": { - "project_history_stack_entries_change_set_id_project_change_sets_id_fk": { - "name": "project_history_stack_entries_change_set_id_project_change_sets_id_fk", - "tableFrom": "project_history_stack_entries", - "tableTo": "project_change_sets", - "columnsFrom": [ - "change_set_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "project_history_stack_entries_project_id_projects_id_fk": { - "name": "project_history_stack_entries_project_id_projects_id_fk", - "tableFrom": "project_history_stack_entries", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": { - "project_history_stack_entries_stack_check": { - "name": "project_history_stack_entries_stack_check", - "value": "\"project_history_stack_entries\".\"stack\" in ('undo', 'redo')" - } - } - }, - "project_revisions": { - "name": "project_revisions", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "revision_number": { - "name": "revision_number", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at_iso": { - "name": "created_at_iso", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "actor_id": { - "name": "actor_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "source": { - "name": "source", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "project_revisions_project_number_unique": { - "name": "project_revisions_project_number_unique", - "columns": [ - "project_id", - "revision_number" - ], - "isUnique": true - } - }, - "foreignKeys": { - "project_revisions_project_id_projects_id_fk": { - "name": "project_revisions_project_id_projects_id_fk", - "tableFrom": "project_revisions", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "project_snapshots": { - "name": "project_snapshots", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "source_revision": { - "name": "source_revision", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "schema_version": { - "name": "schema_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "kind": { - "name": "kind", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'named'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "payload_json": { - "name": "payload_json", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "payload_sha256": { - "name": "payload_sha256", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at_iso": { - "name": "created_at_iso", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_by_actor_id": { - "name": "created_by_actor_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "project_snapshots_project_created_idx": { - "name": "project_snapshots_project_created_idx", - "columns": [ - "project_id", - "created_at_iso" - ], - "isUnique": false - }, - "project_snapshots_project_kind_revision_idx": { - "name": "project_snapshots_project_kind_revision_idx", - "columns": [ - "project_id", - "kind", - "source_revision" - ], - "isUnique": false - }, - "project_snapshots_project_name_unique": { - "name": "project_snapshots_project_name_unique", - "columns": [ - "project_id", - "name" - ], - "isUnique": true - } - }, - "foreignKeys": { - "project_snapshots_project_id_projects_id_fk": { - "name": "project_snapshots_project_id_projects_id_fk", - "tableFrom": "project_snapshots", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "projects": { - "name": "projects", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "internal_project_number": { - "name": "internal_project_number", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "external_project_number": { - "name": "external_project_number", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "building_owner": { - "name": "building_owner", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "is_public_building": { - "name": "is_public_building", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": false - }, - "single_phase_voltage_v": { - "name": "single_phase_voltage_v", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 230 - }, - "three_phase_voltage_v": { - "name": "three_phase_voltage_v", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 400 - }, - "enabled_distribution_board_supply_types": { - "name": "enabled_distribution_board_supply_types", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'[\"AV\",\"SV\",\"EV\",\"USV\",\"MSR\",\"SiBe\"]'" - }, - "current_revision": { - "name": "current_revision", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "rooms": { - "name": "rooms", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "floor_id": { - "name": "floor_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "room_number": { - "name": "room_number", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "room_name": { - "name": "room_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "rooms_project_id_projects_id_fk": { - "name": "rooms_project_id_projects_id_fk", - "tableFrom": "rooms", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "rooms_floor_id_floors_id_fk": { - "name": "rooms_floor_id_floors_id_fk", - "tableFrom": "rooms", - "tableTo": "floors", - "columnsFrom": [ - "floor_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - } - }, - "views": {}, - "enums": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "indexes": {} - } -} \ No newline at end of file diff --git a/src/db/migrations/meta/0008_snapshot.json b/src/db/migrations/meta/0008_snapshot.json deleted file mode 100644 index b5ef997..0000000 --- a/src/db/migrations/meta/0008_snapshot.json +++ /dev/null @@ -1,2596 +0,0 @@ -{ - "version": "6", - "dialect": "sqlite", - "id": "e2b212f8-6d6c-4096-a983-2195cdf13d6e", - "prevId": "98546058-8ad8-4aa1-99ad-f4e915bea89c", - "tables": { - "cable_sizing_calculations": { - "name": "cable_sizing_calculations", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "circuit_id": { - "name": "circuit_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "equipment_identifier": { - "name": "equipment_identifier", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "input": { - "name": "input", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "result": { - "name": "result", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "applied_to_circuit": { - "name": "applied_to_circuit", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(unixepoch())" - } - }, - "indexes": { - "cable_sizing_calculations_circuit_id_idx": { - "name": "cable_sizing_calculations_circuit_id_idx", - "columns": [ - "circuit_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "cable_sizing_calculations_project_id_projects_id_fk": { - "name": "cable_sizing_calculations_project_id_projects_id_fk", - "tableFrom": "cable_sizing_calculations", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "cable_sizing_calculations_circuit_id_circuits_id_fk": { - "name": "cable_sizing_calculations_circuit_id_circuits_id_fk", - "tableFrom": "cable_sizing_calculations", - "tableTo": "circuits", - "columnsFrom": [ - "circuit_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "circuit_device_rows": { - "name": "circuit_device_rows", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "circuit_id": { - "name": "circuit_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "linked_project_device_id": { - "name": "linked_project_device_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "sort_order": { - "name": "sort_order", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "display_name": { - "name": "display_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "phase_type": { - "name": "phase_type", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "connection_kind": { - "name": "connection_kind", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "cost_group": { - "name": "cost_group", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "category": { - "name": "category", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "level": { - "name": "level", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "room_id": { - "name": "room_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "room_number_snapshot": { - "name": "room_number_snapshot", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "room_name_snapshot": { - "name": "room_name_snapshot", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "quantity": { - "name": "quantity", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "manual_quantity": { - "name": "manual_quantity", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - }, - "power_per_unit": { - "name": "power_per_unit", - "type": "real", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "simultaneity_factor": { - "name": "simultaneity_factor", - "type": "real", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "cos_phi": { - "name": "cos_phi", - "type": "real", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "remark": { - "name": "remark", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "overridden_fields": { - "name": "overridden_fields", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "circuit_device_rows_circuit_id_idx": { - "name": "circuit_device_rows_circuit_id_idx", - "columns": [ - "circuit_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "circuit_device_rows_circuit_id_circuits_id_fk": { - "name": "circuit_device_rows_circuit_id_circuits_id_fk", - "tableFrom": "circuit_device_rows", - "tableTo": "circuits", - "columnsFrom": [ - "circuit_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "circuit_device_rows_linked_project_device_id_project_devices_id_fk": { - "name": "circuit_device_rows_linked_project_device_id_project_devices_id_fk", - "tableFrom": "circuit_device_rows", - "tableTo": "project_devices", - "columnsFrom": [ - "linked_project_device_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "circuit_device_rows_room_id_rooms_id_fk": { - "name": "circuit_device_rows_room_id_rooms_id_fk", - "tableFrom": "circuit_device_rows", - "tableTo": "rooms", - "columnsFrom": [ - "room_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "circuit_list_equipment_identifiers": { - "name": "circuit_list_equipment_identifiers", - "columns": { - "owner_type": { - "name": "owner_type", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "owner_id": { - "name": "owner_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "circuit_list_id": { - "name": "circuit_list_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "equipment_identifier": { - "name": "equipment_identifier", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "circuit_list_equipment_identifiers_owner_unique": { - "name": "circuit_list_equipment_identifiers_owner_unique", - "columns": [ - "owner_type", - "owner_id" - ], - "isUnique": true - }, - "circuit_list_equipment_identifiers_list_identifier_unique": { - "name": "circuit_list_equipment_identifiers_list_identifier_unique", - "columns": [ - "circuit_list_id", - "equipment_identifier" - ], - "isUnique": true - } - }, - "foreignKeys": { - "circuit_list_equipment_identifiers_circuit_list_id_circuit_lists_id_fk": { - "name": "circuit_list_equipment_identifiers_circuit_list_id_circuit_lists_id_fk", - "tableFrom": "circuit_list_equipment_identifiers", - "tableTo": "circuit_lists", - "columnsFrom": [ - "circuit_list_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "circuit_lists": { - "name": "circuit_lists", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "distribution_board_id": { - "name": "distribution_board_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "circuit_lists_distribution_board_id_unique": { - "name": "circuit_lists_distribution_board_id_unique", - "columns": [ - "distribution_board_id" - ], - "isUnique": true - } - }, - "foreignKeys": { - "circuit_lists_project_id_projects_id_fk": { - "name": "circuit_lists_project_id_projects_id_fk", - "tableFrom": "circuit_lists", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "circuit_lists_distribution_board_id_distribution_boards_id_fk": { - "name": "circuit_lists_distribution_board_id_distribution_boards_id_fk", - "tableFrom": "circuit_lists", - "tableTo": "distribution_boards", - "columnsFrom": [ - "distribution_board_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "circuit_protection_devices": { - "name": "circuit_protection_devices", - "columns": { - "circuit_id": { - "name": "circuit_id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "rated_current_a": { - "name": "rated_current_a", - "type": "real", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "fuse_utilization_category": { - "name": "fuse_utilization_category", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "trip_characteristic": { - "name": "trip_characteristic", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "rcd_type": { - "name": "rcd_type", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "rated_residual_current_ma": { - "name": "rated_residual_current_ma", - "type": "real", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "circuit_protection_devices_circuit_id_circuits_id_fk": { - "name": "circuit_protection_devices_circuit_id_circuits_id_fk", - "tableFrom": "circuit_protection_devices", - "tableTo": "circuits", - "columnsFrom": [ - "circuit_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "circuit_sections": { - "name": "circuit_sections", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "circuit_list_id": { - "name": "circuit_list_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "key": { - "name": "key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "display_name": { - "name": "display_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "prefix": { - "name": "prefix", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "sort_order": { - "name": "sort_order", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - }, - "category": { - "name": "category", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "group_number": { - "name": "group_number", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "circuit_sections_list_key_unique": { - "name": "circuit_sections_list_key_unique", - "columns": [ - "circuit_list_id", - "key" - ], - "isUnique": true - }, - "circuit_sections_list_prefix_unique": { - "name": "circuit_sections_list_prefix_unique", - "columns": [ - "circuit_list_id", - "prefix" - ], - "isUnique": true - }, - "circuit_sections_list_category_group_unique": { - "name": "circuit_sections_list_category_group_unique", - "columns": [ - "circuit_list_id", - "category", - "group_number" - ], - "isUnique": true - } - }, - "foreignKeys": { - "circuit_sections_circuit_list_id_circuit_lists_id_fk": { - "name": "circuit_sections_circuit_list_id_circuit_lists_id_fk", - "tableFrom": "circuit_sections", - "tableTo": "circuit_lists", - "columnsFrom": [ - "circuit_list_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "circuits": { - "name": "circuits", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "circuit_list_id": { - "name": "circuit_list_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "section_id": { - "name": "section_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "equipment_identifier": { - "name": "equipment_identifier", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "display_name": { - "name": "display_name", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "sort_order": { - "name": "sort_order", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - }, - "cable_type": { - "name": "cable_type", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "cable_cross_section": { - "name": "cable_cross_section", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "cable_length": { - "name": "cable_length", - "type": "real", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "rcd_assignment": { - "name": "rcd_assignment", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "terminal_designation": { - "name": "terminal_designation", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "voltage": { - "name": "voltage", - "type": "real", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "control_requirement": { - "name": "control_requirement", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "is_reserve": { - "name": "is_reserve", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - }, - "remark": { - "name": "remark", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "circuits_section_id_idx": { - "name": "circuits_section_id_idx", - "columns": [ - "section_id" - ], - "isUnique": false - }, - "circuits_list_equipment_identifier_unique": { - "name": "circuits_list_equipment_identifier_unique", - "columns": [ - "circuit_list_id", - "equipment_identifier" - ], - "isUnique": true - } - }, - "foreignKeys": { - "circuits_circuit_list_id_circuit_lists_id_fk": { - "name": "circuits_circuit_list_id_circuit_lists_id_fk", - "tableFrom": "circuits", - "tableTo": "circuit_lists", - "columnsFrom": [ - "circuit_list_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "circuits_section_id_circuit_sections_id_fk": { - "name": "circuits_section_id_circuit_sections_id_fk", - "tableFrom": "circuits", - "tableTo": "circuit_sections", - "columnsFrom": [ - "section_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "distribution_board_component_protection_devices": { - "name": "distribution_board_component_protection_devices", - "columns": { - "component_id": { - "name": "component_id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "rated_current_a": { - "name": "rated_current_a", - "type": "real", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "fuse_utilization_category": { - "name": "fuse_utilization_category", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "trip_characteristic": { - "name": "trip_characteristic", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "rcd_type": { - "name": "rcd_type", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "rated_residual_current_ma": { - "name": "rated_residual_current_ma", - "type": "real", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "distribution_board_component_protection_devices_component_id_distribution_board_components_id_fk": { - "name": "distribution_board_component_protection_devices_component_id_distribution_board_components_id_fk", - "tableFrom": "distribution_board_component_protection_devices", - "tableTo": "distribution_board_components", - "columnsFrom": [ - "component_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "distribution_board_components": { - "name": "distribution_board_components", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "circuit_list_id": { - "name": "circuit_list_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "section_id": { - "name": "section_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "equipment_identifier": { - "name": "equipment_identifier", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "role": { - "name": "role", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "placement": { - "name": "placement", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "sort_order": { - "name": "sort_order", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - } - }, - "indexes": { - "distribution_board_components_list_identifier_unique": { - "name": "distribution_board_components_list_identifier_unique", - "columns": [ - "circuit_list_id", - "equipment_identifier" - ], - "isUnique": true - }, - "distribution_board_components_section_role_unique": { - "name": "distribution_board_components_section_role_unique", - "columns": [ - "section_id", - "role" - ], - "isUnique": true - } - }, - "foreignKeys": { - "distribution_board_components_circuit_list_id_circuit_lists_id_fk": { - "name": "distribution_board_components_circuit_list_id_circuit_lists_id_fk", - "tableFrom": "distribution_board_components", - "tableTo": "circuit_lists", - "columnsFrom": [ - "circuit_list_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "distribution_board_components_section_id_circuit_sections_id_fk": { - "name": "distribution_board_components_section_id_circuit_sections_id_fk", - "tableFrom": "distribution_board_components", - "tableTo": "circuit_sections", - "columnsFrom": [ - "section_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "distribution_boards": { - "name": "distribution_boards", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "floor_id": { - "name": "floor_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "supply_type": { - "name": "supply_type", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "simultaneity_factor": { - "name": "simultaneity_factor", - "type": "real", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 1 - }, - "board_number": { - "name": "board_number", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "location": { - "name": "location", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "fed_from": { - "name": "fed_from", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "remark": { - "name": "remark", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "distribution_boards_project_id_projects_id_fk": { - "name": "distribution_boards_project_id_projects_id_fk", - "tableFrom": "distribution_boards", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "distribution_boards_floor_id_floors_id_fk": { - "name": "distribution_boards_floor_id_floors_id_fk", - "tableFrom": "distribution_boards", - "tableTo": "floors", - "columnsFrom": [ - "floor_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "document_branding_assets": { - "name": "document_branding_assets", - "columns": { - "key": { - "name": "key", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "mime_type": { - "name": "mime_type", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "file_name": { - "name": "file_name", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "data": { - "name": "data", - "type": "blob", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "updated_at": { - "name": "updated_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "(unixepoch())" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "external_csv_configurations": { - "name": "external_csv_configurations", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "configuration_version": { - "name": "configuration_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "configuration": { - "name": "configuration", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "external_csv_configurations_project_id_unique": { - "name": "external_csv_configurations_project_id_unique", - "columns": [ - "project_id" - ], - "isUnique": true - } - }, - "foreignKeys": { - "external_csv_configurations_project_id_projects_id_fk": { - "name": "external_csv_configurations_project_id_projects_id_fk", - "tableFrom": "external_csv_configurations", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "external_import_batches": { - "name": "external_import_batches", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "source_id": { - "name": "source_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "import_kind": { - "name": "import_kind", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "imported_at_iso": { - "name": "imported_at_iso", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "file_name": { - "name": "file_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "sha256": { - "name": "sha256", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "applied_project_revision": { - "name": "applied_project_revision", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "configuration_version": { - "name": "configuration_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "configuration_snapshot": { - "name": "configuration_snapshot", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "original_bytes": { - "name": "original_bytes", - "type": "blob", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "document": { - "name": "document", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "external_import_batches_project_revision_idx": { - "name": "external_import_batches_project_revision_idx", - "columns": [ - "project_id", - "applied_project_revision" - ], - "isUnique": false - }, - "external_import_batches_source_imported_idx": { - "name": "external_import_batches_source_imported_idx", - "columns": [ - "source_id", - "imported_at_iso" - ], - "isUnique": false - } - }, - "foreignKeys": { - "external_import_batches_project_id_projects_id_fk": { - "name": "external_import_batches_project_id_projects_id_fk", - "tableFrom": "external_import_batches", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "external_import_batches_source_id_external_model_sources_id_fk": { - "name": "external_import_batches_source_id_external_model_sources_id_fk", - "tableFrom": "external_import_batches", - "tableTo": "external_model_sources", - "columnsFrom": [ - "source_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "external_model_objects": { - "name": "external_model_objects", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "source_id": { - "name": "source_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "ifc_guid": { - "name": "ifc_guid", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "last_seen_import_batch_id": { - "name": "last_seen_import_batch_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "last_accepted_import_batch_id": { - "name": "last_accepted_import_batch_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "accepted_source_values": { - "name": "accepted_source_values", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "planning_values": { - "name": "planning_values", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "overridden_fields": { - "name": "overridden_fields", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "external_room_mapping_id": { - "name": "external_room_mapping_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "distribution_board_id": { - "name": "distribution_board_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "linked_project_device_id": { - "name": "linked_project_device_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "circuit_device_row_id": { - "name": "circuit_device_row_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "presence_status": { - "name": "presence_status", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'present'" - } - }, - "indexes": { - "external_model_objects_source_ifc_guid_unique": { - "name": "external_model_objects_source_ifc_guid_unique", - "columns": [ - "source_id", - "ifc_guid" - ], - "isUnique": true - }, - "external_model_objects_project_presence_idx": { - "name": "external_model_objects_project_presence_idx", - "columns": [ - "project_id", - "presence_status" - ], - "isUnique": false - }, - "external_model_objects_distribution_board_idx": { - "name": "external_model_objects_distribution_board_idx", - "columns": [ - "distribution_board_id" - ], - "isUnique": false - }, - "external_model_objects_circuit_device_row_idx": { - "name": "external_model_objects_circuit_device_row_idx", - "columns": [ - "circuit_device_row_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "external_model_objects_project_id_projects_id_fk": { - "name": "external_model_objects_project_id_projects_id_fk", - "tableFrom": "external_model_objects", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "external_model_objects_source_id_external_model_sources_id_fk": { - "name": "external_model_objects_source_id_external_model_sources_id_fk", - "tableFrom": "external_model_objects", - "tableTo": "external_model_sources", - "columnsFrom": [ - "source_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "external_model_objects_last_seen_import_batch_id_external_import_batches_id_fk": { - "name": "external_model_objects_last_seen_import_batch_id_external_import_batches_id_fk", - "tableFrom": "external_model_objects", - "tableTo": "external_import_batches", - "columnsFrom": [ - "last_seen_import_batch_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - }, - "external_model_objects_last_accepted_import_batch_id_external_import_batches_id_fk": { - "name": "external_model_objects_last_accepted_import_batch_id_external_import_batches_id_fk", - "tableFrom": "external_model_objects", - "tableTo": "external_import_batches", - "columnsFrom": [ - "last_accepted_import_batch_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - }, - "external_model_objects_external_room_mapping_id_external_room_mappings_id_fk": { - "name": "external_model_objects_external_room_mapping_id_external_room_mappings_id_fk", - "tableFrom": "external_model_objects", - "tableTo": "external_room_mappings", - "columnsFrom": [ - "external_room_mapping_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "external_model_objects_distribution_board_id_distribution_boards_id_fk": { - "name": "external_model_objects_distribution_board_id_distribution_boards_id_fk", - "tableFrom": "external_model_objects", - "tableTo": "distribution_boards", - "columnsFrom": [ - "distribution_board_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "external_model_objects_linked_project_device_id_project_devices_id_fk": { - "name": "external_model_objects_linked_project_device_id_project_devices_id_fk", - "tableFrom": "external_model_objects", - "tableTo": "project_devices", - "columnsFrom": [ - "linked_project_device_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "external_model_objects_circuit_device_row_id_circuit_device_rows_id_fk": { - "name": "external_model_objects_circuit_device_row_id_circuit_device_rows_id_fk", - "tableFrom": "external_model_objects", - "tableTo": "circuit_device_rows", - "columnsFrom": [ - "circuit_device_row_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "external_model_sources": { - "name": "external_model_sources", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "source_type": { - "name": "source_type", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'revit_csv'" - } - }, - "indexes": { - "external_model_sources_project_type_unique": { - "name": "external_model_sources_project_type_unique", - "columns": [ - "project_id", - "source_type" - ], - "isUnique": true - } - }, - "foreignKeys": { - "external_model_sources_project_id_projects_id_fk": { - "name": "external_model_sources_project_id_projects_id_fk", - "tableFrom": "external_model_sources", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "external_room_mappings": { - "name": "external_room_mappings", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "source_id": { - "name": "source_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "normalized_source_room_key": { - "name": "normalized_source_room_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "source_floor_name": { - "name": "source_floor_name", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "source_room_number": { - "name": "source_room_number", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "source_room_name": { - "name": "source_room_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "room_id": { - "name": "room_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "default_distribution_board_id": { - "name": "default_distribution_board_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "external_room_mappings_source_key_unique": { - "name": "external_room_mappings_source_key_unique", - "columns": [ - "source_id", - "normalized_source_room_key" - ], - "isUnique": true - }, - "external_room_mappings_project_room_idx": { - "name": "external_room_mappings_project_room_idx", - "columns": [ - "project_id", - "room_id" - ], - "isUnique": false - } - }, - "foreignKeys": { - "external_room_mappings_project_id_projects_id_fk": { - "name": "external_room_mappings_project_id_projects_id_fk", - "tableFrom": "external_room_mappings", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "external_room_mappings_source_id_external_model_sources_id_fk": { - "name": "external_room_mappings_source_id_external_model_sources_id_fk", - "tableFrom": "external_room_mappings", - "tableTo": "external_model_sources", - "columnsFrom": [ - "source_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "external_room_mappings_room_id_rooms_id_fk": { - "name": "external_room_mappings_room_id_rooms_id_fk", - "tableFrom": "external_room_mappings", - "tableTo": "rooms", - "columnsFrom": [ - "room_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "external_room_mappings_default_distribution_board_id_distribution_boards_id_fk": { - "name": "external_room_mappings_default_distribution_board_id_distribution_boards_id_fk", - "tableFrom": "external_room_mappings", - "tableTo": "distribution_boards", - "columnsFrom": [ - "default_distribution_board_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "floors": { - "name": "floors", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "sort_order": { - "name": "sort_order", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - } - }, - "indexes": {}, - "foreignKeys": { - "floors_project_id_projects_id_fk": { - "name": "floors_project_id_projects_id_fk", - "tableFrom": "floors", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "global_devices": { - "name": "global_devices", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "display_name": { - "name": "display_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "category": { - "name": "category", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "quantity": { - "name": "quantity", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "installed_power_per_unit_kw": { - "name": "installed_power_per_unit_kw", - "type": "real", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "demand_factor": { - "name": "demand_factor", - "type": "real", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "voltage_v": { - "name": "voltage_v", - "type": "real", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "phase_count": { - "name": "phase_count", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "power_factor": { - "name": "power_factor", - "type": "real", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "note": { - "name": "note", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "project_change_sets": { - "name": "project_change_sets", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_revision_id": { - "name": "project_revision_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "command_type": { - "name": "command_type", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "payload_schema_version": { - "name": "payload_schema_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "forward_payload_json": { - "name": "forward_payload_json", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "inverse_payload_json": { - "name": "inverse_payload_json", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "project_change_sets_revision_unique": { - "name": "project_change_sets_revision_unique", - "columns": [ - "project_revision_id" - ], - "isUnique": true - } - }, - "foreignKeys": { - "project_change_sets_project_revision_id_project_revisions_id_fk": { - "name": "project_change_sets_project_revision_id_project_revisions_id_fk", - "tableFrom": "project_change_sets", - "tableTo": "project_revisions", - "columnsFrom": [ - "project_revision_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "project_devices": { - "name": "project_devices", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "display_name": { - "name": "display_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "phase_type": { - "name": "phase_type", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'single_phase'" - }, - "connection_kind": { - "name": "connection_kind", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "cost_group": { - "name": "cost_group", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "category": { - "name": "category", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "quantity": { - "name": "quantity", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "power_per_unit": { - "name": "power_per_unit", - "type": "real", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - }, - "simultaneity_factor": { - "name": "simultaneity_factor", - "type": "real", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 1 - }, - "cos_phi": { - "name": "cos_phi", - "type": "real", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "remark": { - "name": "remark", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "voltage_v": { - "name": "voltage_v", - "type": "real", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "project_devices_project_id_projects_id_fk": { - "name": "project_devices_project_id_projects_id_fk", - "tableFrom": "project_devices", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "project_history_stack_entries": { - "name": "project_history_stack_entries", - "columns": { - "change_set_id": { - "name": "change_set_id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "stack": { - "name": "stack", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "position": { - "name": "position", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "project_history_stack_entries_position_unique": { - "name": "project_history_stack_entries_position_unique", - "columns": [ - "project_id", - "stack", - "position" - ], - "isUnique": true - } - }, - "foreignKeys": { - "project_history_stack_entries_change_set_id_project_change_sets_id_fk": { - "name": "project_history_stack_entries_change_set_id_project_change_sets_id_fk", - "tableFrom": "project_history_stack_entries", - "tableTo": "project_change_sets", - "columnsFrom": [ - "change_set_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "project_history_stack_entries_project_id_projects_id_fk": { - "name": "project_history_stack_entries_project_id_projects_id_fk", - "tableFrom": "project_history_stack_entries", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": { - "project_history_stack_entries_stack_check": { - "name": "project_history_stack_entries_stack_check", - "value": "\"project_history_stack_entries\".\"stack\" in ('undo', 'redo')" - } - } - }, - "project_revisions": { - "name": "project_revisions", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "revision_number": { - "name": "revision_number", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at_iso": { - "name": "created_at_iso", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "actor_id": { - "name": "actor_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "source": { - "name": "source", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "project_revisions_project_number_unique": { - "name": "project_revisions_project_number_unique", - "columns": [ - "project_id", - "revision_number" - ], - "isUnique": true - } - }, - "foreignKeys": { - "project_revisions_project_id_projects_id_fk": { - "name": "project_revisions_project_id_projects_id_fk", - "tableFrom": "project_revisions", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "project_snapshots": { - "name": "project_snapshots", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "source_revision": { - "name": "source_revision", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "schema_version": { - "name": "schema_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "kind": { - "name": "kind", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'named'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "payload_json": { - "name": "payload_json", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "payload_sha256": { - "name": "payload_sha256", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at_iso": { - "name": "created_at_iso", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_by_actor_id": { - "name": "created_by_actor_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "project_snapshots_project_created_idx": { - "name": "project_snapshots_project_created_idx", - "columns": [ - "project_id", - "created_at_iso" - ], - "isUnique": false - }, - "project_snapshots_project_kind_revision_idx": { - "name": "project_snapshots_project_kind_revision_idx", - "columns": [ - "project_id", - "kind", - "source_revision" - ], - "isUnique": false - }, - "project_snapshots_project_name_unique": { - "name": "project_snapshots_project_name_unique", - "columns": [ - "project_id", - "name" - ], - "isUnique": true - } - }, - "foreignKeys": { - "project_snapshots_project_id_projects_id_fk": { - "name": "project_snapshots_project_id_projects_id_fk", - "tableFrom": "project_snapshots", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "projects": { - "name": "projects", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "internal_project_number": { - "name": "internal_project_number", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "external_project_number": { - "name": "external_project_number", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "building_owner": { - "name": "building_owner", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "is_public_building": { - "name": "is_public_building", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": false - }, - "single_phase_voltage_v": { - "name": "single_phase_voltage_v", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 230 - }, - "three_phase_voltage_v": { - "name": "three_phase_voltage_v", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 400 - }, - "enabled_distribution_board_supply_types": { - "name": "enabled_distribution_board_supply_types", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'[\"AV\",\"SV\",\"EV\",\"USV\",\"MSR\",\"SiBe\"]'" - }, - "current_revision": { - "name": "current_revision", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "rooms": { - "name": "rooms", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "floor_id": { - "name": "floor_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "room_number": { - "name": "room_number", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "room_name": { - "name": "room_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "rooms_project_id_projects_id_fk": { - "name": "rooms_project_id_projects_id_fk", - "tableFrom": "rooms", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "rooms_floor_id_floors_id_fk": { - "name": "rooms_floor_id_floors_id_fk", - "tableFrom": "rooms", - "tableTo": "floors", - "columnsFrom": [ - "floor_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - } - }, - "views": {}, - "enums": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "indexes": {} - } -} \ No newline at end of file diff --git a/src/db/migrations/meta/_journal.json b/src/db/migrations/meta/_journal.json index 2709d5f..eedfc7a 100644 --- a/src/db/migrations/meta/_journal.json +++ b/src/db/migrations/meta/_journal.json @@ -50,20 +50,6 @@ "when": 1786043080323, "tag": "0006_damp_skrulls", "breakpoints": true - }, - { - "idx": 7, - "version": "6", - "when": 1786114831610, - "tag": "0007_watery_kingpin", - "breakpoints": true - }, - { - "idx": 8, - "version": "6", - "when": 1790356758671, - "tag": "0008_colossal_terrax", - "breakpoints": true } ] } \ No newline at end of file diff --git a/src/db/repositories/cable-sizing-calculation.repository.ts b/src/db/repositories/cable-sizing-calculation.repository.ts deleted file mode 100644 index 803965b..0000000 --- a/src/db/repositories/cable-sizing-calculation.repository.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { desc, eq } from "drizzle-orm"; -import type { AppDatabase } from "../database-context.js"; -import { - cableSizingCalculations, - type CableSizingCalculation, - type NewCableSizingCalculation, -} from "../schema/cable-sizing-calculations.js"; - -// Plain repository, no revision/command semantics - mirrors -// application-repositories.ts's flat repository pattern, not the -// project-command-transaction boundary. A calculation is an audit record, -// not a project mutation. -export class CableSizingCalculationRepository { - constructor(private readonly db: AppDatabase) {} - - create(input: NewCableSizingCalculation): CableSizingCalculation { - return this.db.insert(cableSizingCalculations).values(input).returning().get(); - } - - listByCircuit(circuitId: string): CableSizingCalculation[] { - return this.db - .select() - .from(cableSizingCalculations) - .where(eq(cableSizingCalculations.circuitId, circuitId)) - .orderBy(desc(cableSizingCalculations.createdAt)) - .all(); - } - - markApplied(id: string): CableSizingCalculation | undefined { - return this.db - .update(cableSizingCalculations) - .set({ appliedToCircuit: 1 }) - .where(eq(cableSizingCalculations.id, id)) - .returning() - .get(); - } -} diff --git a/src/db/repositories/distribution-board-structure-project-command.repository.ts b/src/db/repositories/distribution-board-structure-project-command.repository.ts index db2cede..29a8f21 100644 --- a/src/db/repositories/distribution-board-structure-project-command.repository.ts +++ b/src/db/repositories/distribution-board-structure-project-command.repository.ts @@ -388,15 +388,10 @@ function structureMatches( ) { const { simultaneityFactor, - boardNumber, - location, - fedFrom, - remark, ...actualDistributionBoard } = actual.board; if ( simultaneityFactor !== 1 || - [boardNumber, location, fedFrom, remark].some((value) => value !== null) || !sameRecord( expected.distributionBoard, actualDistributionBoard diff --git a/src/db/repositories/distribution-board-subtree-project-command.repository.ts b/src/db/repositories/distribution-board-subtree-project-command.repository.ts index 6788dcc..63038eb 100644 --- a/src/db/repositories/distribution-board-subtree-project-command.repository.ts +++ b/src/db/repositories/distribution-board-subtree-project-command.repository.ts @@ -10,7 +10,6 @@ import { } from "../../domain/models/distribution-board-subtree-project-command.model.js"; import { assertDistributionBoardSubtreeSnapshot, - normalizeDistributionBoardSubtreeSnapshot, type DistributionBoardSubtreeSnapshot, } from "../../domain/models/distribution-board-subtree-snapshot.model.js"; import type { @@ -64,20 +63,19 @@ export class DistributionBoardSubtreeProjectCommandRepository projectId: string, command: DistributionBoardSubtreeProjectCommand ) { - const snapshot = normalizeDistributionBoardSubtreeSnapshot( - command.payload.snapshot - ); if (command.type === distributionBoardInsertSubtreeCommandType) { - this.insert(database, projectId, snapshot); - return createDistributionBoardDeleteSubtreeProjectCommand(snapshot); + this.insert(database, projectId, command.payload.snapshot); + return createDistributionBoardDeleteSubtreeProjectCommand( + command.payload.snapshot + ); } if (command.type === distributionBoardDeleteSubtreeCommandType) { const current = this.captureFrom( database, projectId, - snapshot.distributionBoard.id + command.payload.snapshot.distributionBoard.id ); - if (!sameSnapshot(current, snapshot)) { + if (!sameSnapshot(current, command.payload.snapshot)) { throw new Error( "Distribution-board subtree changed before deletion." ); @@ -99,7 +97,9 @@ export class DistributionBoardSubtreeProjectCommandRepository "Distribution-board subtree could not be deleted." ); } - return createDistributionBoardInsertSubtreeProjectCommand(snapshot); + return createDistributionBoardInsertSubtreeProjectCommand( + command.payload.snapshot + ); } throw new Error("Unsupported distribution-board subtree command."); } diff --git a/src/db/repositories/document-branding.repository.ts b/src/db/repositories/document-branding.repository.ts deleted file mode 100644 index 450e8d2..0000000 --- a/src/db/repositories/document-branding.repository.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { eq } from "drizzle-orm"; -import type { AppDatabase } from "../database-context.js"; -import { - documentBrandingAssets, - type DocumentBrandingAsset, -} from "../schema/document-branding-assets.js"; - -// Plain repository without revision semantics: branding is global office -// configuration, not a project mutation. -export class DocumentBrandingRepository { - constructor(private readonly db: AppDatabase) {} - - get(key: string): DocumentBrandingAsset | undefined { - return this.db - .select() - .from(documentBrandingAssets) - .where(eq(documentBrandingAssets.key, key)) - .get(); - } - - upsert(asset: { - key: string; - mimeType: string; - fileName: string | null; - data: Buffer; - }): DocumentBrandingAsset { - const updatedAt = new Date(); - return this.db - .insert(documentBrandingAssets) - .values({ ...asset, updatedAt }) - .onConflictDoUpdate({ - target: documentBrandingAssets.key, - set: { - mimeType: asset.mimeType, - fileName: asset.fileName, - data: asset.data, - updatedAt, - }, - }) - .returning() - .get(); - } - - delete(key: string): boolean { - return ( - this.db - .delete(documentBrandingAssets) - .where(eq(documentBrandingAssets.key, key)) - .run().changes > 0 - ); - } -} diff --git a/src/db/schema/cable-sizing-calculations.ts b/src/db/schema/cable-sizing-calculations.ts deleted file mode 100644 index 511fab3..0000000 --- a/src/db/schema/cable-sizing-calculations.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { sql } from "drizzle-orm"; -import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; -import { circuits } from "./circuits.js"; -import { projects } from "./projects.js"; - -// Audit log for the cable-sizing module (see docs/cable-sizing-module.md). -// Deliberately separate from the revision/command system: a calculation is -// not itself a project mutation, only applying its result via the existing -// circuit.update command is. circuitId/projectId are nullable and -// onDelete "set null" so a deleted circuit or project never blocks or -// cascades into losing the audit trail. -export const cableSizingCalculations = sqliteTable( - "cable_sizing_calculations", - { - id: text("id").primaryKey(), - projectId: text("project_id").references(() => projects.id, { onDelete: "set null" }), - circuitId: text("circuit_id").references(() => circuits.id, { onDelete: "set null" }), - equipmentIdentifier: text("equipment_identifier"), - input: text("input", { mode: "json" }).notNull(), - result: text("result", { mode: "json" }).notNull(), - appliedToCircuit: integer("applied_to_circuit").notNull().default(0), - createdAt: integer("created_at", { mode: "timestamp" }) - .notNull() - .default(sql`(unixepoch())`), - }, - (table) => [index("cable_sizing_calculations_circuit_id_idx").on(table.circuitId)] -); - -export type CableSizingCalculation = typeof cableSizingCalculations.$inferSelect; -export type NewCableSizingCalculation = typeof cableSizingCalculations.$inferInsert; diff --git a/src/db/schema/distribution-boards.ts b/src/db/schema/distribution-boards.ts index eb63486..06fdaf6 100644 --- a/src/db/schema/distribution-boards.ts +++ b/src/db/schema/distribution-boards.ts @@ -16,9 +16,5 @@ export const distributionBoards = sqliteTable("distribution_boards", { simultaneityFactor: real("simultaneity_factor") .notNull() .default(1), - boardNumber: text("board_number"), - location: text("location"), - fedFrom: text("fed_from"), - remark: text("remark"), }); diff --git a/src/db/schema/document-branding-assets.ts b/src/db/schema/document-branding-assets.ts deleted file mode 100644 index 8fb68f8..0000000 --- a/src/db/schema/document-branding-assets.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { sql } from "drizzle-orm"; -import { blob, integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; - -// Office-wide branding for printed documents (currently only the logo shown -// on the cover page and plan title blocks). Global application data, not part -// of any project: it is deliberately outside project revisions, logical -// project snapshots and project transfer. -export const documentBrandingAssets = sqliteTable("document_branding_assets", { - key: text("key").primaryKey(), - mimeType: text("mime_type").notNull(), - fileName: text("file_name"), - data: blob("data", { mode: "buffer" }).notNull(), - updatedAt: integer("updated_at", { mode: "timestamp" }) - .notNull() - .default(sql`(unixepoch())`), -}); - -export type DocumentBrandingAsset = typeof documentBrandingAssets.$inferSelect; diff --git a/src/domain/models/distribution-board-project-command.model.ts b/src/domain/models/distribution-board-project-command.model.ts index 2e917d0..a41be72 100644 --- a/src/domain/models/distribution-board-project-command.model.ts +++ b/src/domain/models/distribution-board-project-command.model.ts @@ -1,21 +1,14 @@ import { - distributionBoardInfoFieldMaxLengths, - distributionBoardInfoFields, distributionBoardSupplyTypes, - type DistributionBoardInfoField, type DistributionBoardSupplyType, } from "../../shared/constants/distribution-board.js"; import type { SerializedProjectCommand } from "./project-command.model.js"; export const distributionBoardUpdateCommandType = "distribution-board.update" as const; -// The info-card fields were added additively to schema version 1: every -// previously stored command remains valid, and the inverse is still derived -// from the persisted row for exactly the changed fields. export const distributionBoardUpdateCommandSchemaVersion = 1 as const; -export interface DistributionBoardUpdateValues - extends Record { +export interface DistributionBoardUpdateValues { floorId: string | null; supplyType: DistributionBoardSupplyType | null; simultaneityFactor: number; @@ -89,20 +82,16 @@ export function assertDistributionBoardUpdateProjectCommand( for (const change of changes) { if ( !isPlainObject(change) || - typeof change.field !== "string" || (change.field !== "floorId" && change.field !== "supplyType" && - change.field !== "simultaneityFactor" && - !isInfoField(change.field)) || + change.field !== "simultaneityFactor") || seen.has(change.field) ) { throw new Error( "Distribution-board update contains an invalid or duplicate field." ); } - if (isInfoField(change.field)) { - assertInfoFieldValue(change.field, change.value); - } else if (change.field === "floorId") { + if (change.field === "floorId") { if ( change.value !== null && (typeof change.value !== "string" || !change.value.trim()) @@ -132,29 +121,6 @@ export function assertDistributionBoardUpdateProjectCommand( } } -function isInfoField(field: string): field is DistributionBoardInfoField { - return (distributionBoardInfoFields as readonly string[]).includes(field); -} - -function assertInfoFieldValue( - field: DistributionBoardInfoField, - value: unknown -) { - if (value === null) { - return; - } - if ( - typeof value !== "string" || - !value.trim() || - value !== value.trim() || - value.length > distributionBoardInfoFieldMaxLengths[field] - ) { - throw new Error( - `${field} must be a trimmed, non-empty string of at most ${distributionBoardInfoFieldMaxLengths[field]} characters or null.` - ); - } -} - function isPlainObject(value: unknown): value is Record { return value !== null && typeof value === "object" && !Array.isArray(value); } diff --git a/src/domain/models/distribution-board-subtree-snapshot.model.ts b/src/domain/models/distribution-board-subtree-snapshot.model.ts index a0269fc..be896a0 100644 --- a/src/domain/models/distribution-board-subtree-snapshot.model.ts +++ b/src/domain/models/distribution-board-subtree-snapshot.model.ts @@ -10,9 +10,7 @@ import { type DistributionBoardComponentRole, } from "../../shared/constants/distribution-board-component.js"; import { - distributionBoardInfoFields, distributionBoardSupplyTypes, - type DistributionBoardInfoField, type DistributionBoardSupplyType, } from "../../shared/constants/distribution-board.js"; import type { @@ -74,7 +72,7 @@ export interface DistributionBoardSubtreeSnapshot { floorId: string | null; supplyType: DistributionBoardSupplyType | null; simultaneityFactor: number; - } & Record; + }; circuitList: { id: string; projectId: string; @@ -171,22 +169,6 @@ export function cloneDistributionBoardSubtree( return clone; } -/** - * Subtree commands stored before the board info fields existed carry no - * info values. They are equivalent to a board whose info fields are empty, - * so comparisons against a freshly captured subtree and re-inserts stay - * exact. - */ -export function normalizeDistributionBoardSubtreeSnapshot( - snapshot: DistributionBoardSubtreeSnapshot -): DistributionBoardSubtreeSnapshot { - const board = { ...snapshot.distributionBoard }; - for (const field of distributionBoardInfoFields) { - board[field] = board[field] ?? null; - } - return { ...snapshot, distributionBoard: board }; -} - export function assertDistributionBoardSubtreeSnapshot( value: unknown ): asserts value is DistributionBoardSubtreeSnapshot { @@ -222,11 +204,6 @@ export function assertDistributionBoardSubtreeSnapshot( if (board.simultaneityFactor < 0 || board.simultaneityFactor > 1) { throw new Error("Distribution-board simultaneity factor is invalid."); } - for (const field of distributionBoardInfoFields) { - if (board[field] !== undefined) { - assertNullableString(board[field], `distributionBoard.${field}`); - } - } for (const field of [ "id", "projectId", diff --git a/src/domain/models/project-state-snapshot.model.ts b/src/domain/models/project-state-snapshot.model.ts index a955540..7172cb7 100644 --- a/src/domain/models/project-state-snapshot.model.ts +++ b/src/domain/models/project-state-snapshot.model.ts @@ -1,9 +1,5 @@ import { z } from "zod"; -import { - distributionBoardInfoFieldMaxLengths, - distributionBoardSupplyTypes, - type DistributionBoardInfoField, -} from "../../shared/constants/distribution-board.js"; +import { distributionBoardSupplyTypes } from "../../shared/constants/distribution-board.js"; import { circuitGroupCategories, } from "../../shared/constants/circuit-group.js"; @@ -31,8 +27,7 @@ import { type ExternalModelStateSnapshot, } from "../../external-model/domain/external-model-contracts.js"; -export const projectStateSnapshotSchemaVersion = 6 as const; -const boardInfoProjectStateSnapshotSchemaVersion = 5 as const; +export const projectStateSnapshotSchemaVersion = 5 as const; const previousProjectStateSnapshotSchemaVersion = 4 as const; const externalModelProjectStateSnapshotSchemaVersion = 3 as const; const legacyProjectStateSnapshotSchemaVersion = 2 as const; @@ -67,7 +62,7 @@ const projectSchema = previousProjectSchema .extend({ isPublicBuilding: z.boolean() }) .strict(); -const legacyDistributionBoardSchema = z +const distributionBoardSchema = z .object({ id: idSchema, projectId: idSchema, @@ -78,35 +73,6 @@ const legacyDistributionBoardSchema = z }) .strict(); -const distributionBoardInfoValueSchema = (field: DistributionBoardInfoField) => - z - .string() - .trim() - .min(1) - .max(distributionBoardInfoFieldMaxLengths[field]) - .nullable(); - -const distributionBoardSchema = legacyDistributionBoardSchema - .extend({ - boardNumber: distributionBoardInfoValueSchema("boardNumber"), - location: distributionBoardInfoValueSchema("location"), - fedFrom: distributionBoardInfoValueSchema("fedFrom"), - remark: distributionBoardInfoValueSchema("remark"), - }) - .strict(); - -function withEmptyDistributionBoardInfo( - boards: Array> -) { - return boards.map((board) => ({ - ...board, - boardNumber: null, - location: null, - fedFrom: null, - remark: null, - })); -} - const circuitListSchema = z .object({ id: idSchema, @@ -491,22 +457,11 @@ export const projectStateSnapshotSchema = z }) .strict(); -const boardInfoProjectStateSnapshotSchema = z - .object({ - schemaVersion: z.literal(boardInfoProjectStateSnapshotSchemaVersion), - project: projectSchema, - distributionBoards: z.array(legacyDistributionBoardSchema), - externalCsvConfiguration: externalCsvConfigurationSnapshotSchema.nullable(), - externalModel: externalModelStateSchema, - ...projectStateSnapshotContents, - }) - .strict(); - const previousProjectStateSnapshotSchema = z .object({ schemaVersion: z.literal(previousProjectStateSnapshotSchemaVersion), project: projectSchema, - distributionBoards: z.array(legacyDistributionBoardSchema), + distributionBoards: z.array(distributionBoardSchema), externalCsvConfiguration: externalCsvConfigurationSnapshotSchema.nullable(), externalModel: externalModelStateSchema, ...projectStateSnapshotContents, @@ -517,7 +472,7 @@ const externalModelProjectStateSnapshotSchema = z .object({ schemaVersion: z.literal(externalModelProjectStateSnapshotSchemaVersion), project: projectSchema, - distributionBoards: z.array(legacyDistributionBoardSchema), + distributionBoards: z.array(distributionBoardSchema), externalCsvConfiguration: externalCsvConfigurationSnapshotSchema.nullable(), ...projectStateSnapshotContents, }) @@ -527,7 +482,7 @@ const legacyProjectStateSnapshotSchema = z .object({ schemaVersion: z.literal(legacyProjectStateSnapshotSchemaVersion), project: projectSchema, - distributionBoards: z.array(legacyDistributionBoardSchema), + distributionBoards: z.array(distributionBoardSchema), ...projectStateSnapshotContents, }) .strict(); @@ -536,7 +491,7 @@ const baselineProjectStateSnapshotSchema = z .object({ schemaVersion: z.literal(baselineProjectStateSnapshotSchemaVersion), project: previousProjectSchema, - distributionBoards: z.array(legacyDistributionBoardSchema), + distributionBoards: z.array(distributionBoardSchema), ...projectStateSnapshotContents, }) .strict(); @@ -566,26 +521,11 @@ function upgradePreviousProjectStateSnapshot(value: unknown): unknown { return value; } const schemaVersion = (value as { schemaVersion: number }).schemaVersion; - // Every supported version before 6 lacks the distribution-board info - // fields; they are upgraded as empty (null) values. - if (schemaVersion === boardInfoProjectStateSnapshotSchemaVersion) { - const previous = boardInfoProjectStateSnapshotSchema.parse(value); - return { - ...previous, - schemaVersion: projectStateSnapshotSchemaVersion, - distributionBoards: withEmptyDistributionBoardInfo( - previous.distributionBoards - ), - }; - } if (schemaVersion === previousProjectStateSnapshotSchemaVersion) { const previous = previousProjectStateSnapshotSchema.parse(value); return { ...previous, schemaVersion: projectStateSnapshotSchemaVersion, - distributionBoards: withEmptyDistributionBoardInfo( - previous.distributionBoards - ), }; } if (schemaVersion === externalModelProjectStateSnapshotSchemaVersion) { @@ -593,9 +533,6 @@ function upgradePreviousProjectStateSnapshot(value: unknown): unknown { return { ...previous, schemaVersion: projectStateSnapshotSchemaVersion, - distributionBoards: withEmptyDistributionBoardInfo( - previous.distributionBoards - ), externalModel: emptyExternalModelState(), }; } @@ -604,9 +541,6 @@ function upgradePreviousProjectStateSnapshot(value: unknown): unknown { return { ...legacy, schemaVersion: projectStateSnapshotSchemaVersion, - distributionBoards: withEmptyDistributionBoardInfo( - legacy.distributionBoards - ), externalCsvConfiguration: null, externalModel: emptyExternalModelState(), }; @@ -617,9 +551,6 @@ function upgradePreviousProjectStateSnapshot(value: unknown): unknown { ...baseline, schemaVersion: projectStateSnapshotSchemaVersion, project: { ...baseline.project, isPublicBuilding: false }, - distributionBoards: withEmptyDistributionBoardInfo( - baseline.distributionBoards - ), externalCsvConfiguration: null, externalModel: emptyExternalModelState(), }; diff --git a/src/frontend/components/cable-sizing-api.ts b/src/frontend/components/cable-sizing-api.ts deleted file mode 100644 index ee52aef..0000000 --- a/src/frontend/components/cable-sizing-api.ts +++ /dev/null @@ -1,71 +0,0 @@ -// Thin, self-contained fetch client for the cable-sizing module. Deliberately -// not merged into ../utils/api.ts to keep this module's frontend footprint a -// single new file rather than growing the existing shared API file - see -// docs/cable-sizing-module.md. -import type { - CableSizingAlert, - CableSizingInput, - CableSizingResult, - InsulationMaterial, - LayingMethod, -} from "../../cable-sizing/domain/cable-sizing-calculation"; - -async function request(url: string, init?: RequestInit): Promise { - const response = await fetch(url, { - ...init, - headers: { "Content-Type": "application/json", ...init?.headers }, - cache: "no-store", - }); - if (!response.ok) { - let details = await response.text(); - try { - const parsed = JSON.parse(details) as { error?: unknown }; - if (typeof parsed.error === "string") details = parsed.error; - } catch { - // keep raw text - } - throw new Error(details || `Anfrage fehlgeschlagen (Status ${response.status})`); - } - return response.json() as Promise; -} - -export interface CalculateCableSizingResponse { - calculationId: string; - result: CableSizingResult; - alerts: CableSizingAlert[]; -} - -export function calculateCableSizing( - input: CableSizingInput, - context?: { projectId?: string; circuitId?: string; equipmentIdentifier?: string } -): Promise { - return request("/api/cable-sizing/calculate", { - method: "POST", - body: JSON.stringify({ ...input, context }), - }); -} - -export function markCableSizingCalculationApplied(calculationId: string): Promise { - return request(`/api/cable-sizing/calculations/${calculationId}/applied`, { - method: "POST", - }); -} - -export interface LayingMethodOption { - method: LayingMethod; - label: string; - dataVerified: boolean; -} - -export function listLayingMethods(): Promise { - return request("/api/cable-sizing/laying-methods"); -} - -export interface InsulationMaterialOption { - insulation: InsulationMaterial; - label: string; -} - -export function listInsulationMaterials(): Promise { - return request("/api/cable-sizing/insulation-materials"); -} diff --git a/src/frontend/components/cable-sizing-modal.tsx b/src/frontend/components/cable-sizing-modal.tsx deleted file mode 100644 index 36b204c..0000000 --- a/src/frontend/components/cable-sizing-modal.tsx +++ /dev/null @@ -1,523 +0,0 @@ -"use client"; - -import { type FormEvent, useMemo, useState } from "react"; -import { - CONDUCTOR_MATERIALS, - CROSS_SECTIONS_MM2, - GROUPING_LABELS, - GROUPING_OPTIONS, - HARMONIC_NEUTRAL_LOAD_LABELS, - HARMONIC_NEUTRAL_LOAD_OPTIONS, - INSULATION_MATERIALS, - INSULATION_MATERIAL_LABELS, - LAYING_METHODS, - LAYING_METHOD_GROUP, - LAYING_METHOD_LABELS, - PRACTICAL_MINIMUM_CROSS_SECTION_MM2, - TEMPERATURE_FACTOR_AIR, - TEMPERATURE_FACTOR_GROUND, - type CableSizingAlert, - type CableSizingInput, - type CableSizingResult, - type CircuitCategory, - type ConductorMaterial, - type HarmonicNeutralLoad, - type InsulationMaterial, - type LayingMethod, -} from "../../cable-sizing/domain/cable-sizing-calculation"; -import { calculateCableSizing } from "./cable-sizing-api"; -import type { CircuitTreeCircuitDto } from "../types"; -import { FormModal } from "./form-modal"; - -interface CableSizingModalProps { - circuit: CircuitTreeCircuitDto; - circuitCategory?: CircuitCategory; - isSaving: boolean; - projectId: string; - onClose: () => void; - onApply: (patch: { - cableCrossSection: string; - cableType?: string; - cableLength?: number; - }) => Promise; -} - -function temperatureOptions(method: LayingMethod) { - const table = - LAYING_METHOD_GROUP[method] === "air" ? TEMPERATURE_FACTOR_AIR : TEMPERATURE_FACTOR_GROUND; - return Object.keys(table).map(Number).sort((a, b) => a - b); -} - -// Extracts the first decimal number from a free-text cross-section entry -// like "2.5 mm²" or "2,5". Returns null if nothing parseable is found - -// used only for the manual-entry sanity check below, never persisted. -function parseCrossSectionMm2(text: string): number | null { - const match = text.match(/([0-9]+(?:[.,][0-9]+)?)/); - if (!match) return null; - const value = Number(match[1].replace(",", ".")); - return Number.isFinite(value) ? value : null; -} - -export function CableSizingModal({ - circuit, - circuitCategory, - isSaving, - projectId, - onClose, - onApply, -}: CableSizingModalProps) { - const [layingMethod, setLayingMethod] = useState("C"); - const [insulation, setInsulation] = useState("pvc"); - const [conductorMaterial, setConductorMaterial] = useState("copper"); - const [ambientTemperatureC, setAmbientTemperatureC] = useState(30); - const [groupingCircuits, setGroupingCircuits] = useState(1); - const [cosPhi, setCosPhi] = useState(1); - const [maxVoltageDropPercent, setMaxVoltageDropPercent] = useState(3); - const [harmonicNeutralLoad, setHarmonicNeutralLoad] = useState("none"); - const [lengthM, setLengthM] = useState(circuit.cableLength ?? 0); - const [phase, setPhase] = useState<1 | 3>(circuit.voltage === 400 ? 3 : 1); - - // Manual override: applying this never requires a calculation. Prefilled - // from the circuit's current values so opening the modal on an already - // specified cable doesn't lose that data. - const [manualCableType, setManualCableType] = useState(circuit.cableType ?? ""); - const [manualCrossSection, setManualCrossSection] = useState(circuit.cableCrossSection ?? ""); - - const [calculating, setCalculating] = useState(false); - const [calculationId, setCalculationId] = useState(null); - const [result, setResult] = useState(null); - const [alerts, setAlerts] = useState([]); - const [error, setError] = useState(null); - - const voltage = circuit.voltage ?? (phase === 1 ? 230 : 400); - - const input: CableSizingInput = useMemo( - () => ({ - phase, - mode: "power", - powerKw: circuit.circuitTotalPower, - cosPhi, - voltage, - lengthM, - layingMethod, - conductorMaterial, - insulation, - ambientTemperatureC, - groupingCircuits, - maxVoltageDropPercent, - harmonicNeutralLoad, - existingProtectionRatedCurrentA: circuit.protectionDevice?.ratedCurrentA, - circuitCategory, - }), - [ - phase, - circuit.circuitTotalPower, - cosPhi, - voltage, - lengthM, - layingMethod, - conductorMaterial, - insulation, - ambientTemperatureC, - groupingCircuits, - maxVoltageDropPercent, - harmonicNeutralLoad, - circuit.protectionDevice?.ratedCurrentA, - circuitCategory, - ] - ); - - const manualCrossSectionWarning = useMemo(() => { - const trimmed = manualCrossSection.trim(); - if (!trimmed) return null; - const parsed = parseCrossSectionMm2(trimmed); - if (parsed == null) return null; - if (!(CROSS_SECTIONS_MM2 as readonly number[]).includes(parsed)) { - return `${parsed} mm² ist kein Standard-Querschnitt (${CROSS_SECTIONS_MM2.join(", ")}).`; - } - if ( - result?.dataVerified && - result.recommendedCrossSectionMm2 != null && - parsed < result.recommendedCrossSectionMm2 - ) { - return `${parsed} mm² ist kleiner als die zuletzt berechnete Empfehlung (${result.recommendedCrossSectionMm2} mm²) - passt nicht zu Last${circuit.protectionDevice ? "/Sicherung" : ""} und Verlegeart.`; - } - const practicalMinimum = circuitCategory - ? PRACTICAL_MINIMUM_CROSS_SECTION_MM2[circuitCategory] - : undefined; - if (practicalMinimum != null && parsed < practicalMinimum) { - return `${parsed} mm² liegt unter dem Praxis-Mindestquerschnitt für 1-phasige Stromkreise (${practicalMinimum} mm²).`; - } - return null; - }, [manualCrossSection, result, circuit.protectionDevice, circuitCategory]); - - function handleLayingMethodChange(method: LayingMethod) { - setLayingMethod(method); - const options = temperatureOptions(method); - const reference = LAYING_METHOD_GROUP[method] === "air" ? 30 : 20; - setAmbientTemperatureC(options.includes(reference) ? reference : options[0]); - setResult(null); - setCalculationId(null); - } - - async function handleCalculate() { - setCalculating(true); - setError(null); - try { - const response = await calculateCableSizing(input, { - projectId, - circuitId: circuit.id, - equipmentIdentifier: circuit.equipmentIdentifier, - }); - setResult(response.result); - setAlerts(response.alerts); - setCalculationId(response.calculationId); - // Calculating fills the manual field as a suggestion, but that field - // stays the single source of truth for what gets applied - the user - // can still edit it by hand before submitting. - if (response.result.dataVerified && response.result.recommendedCrossSectionMm2) { - setManualCrossSection(`${response.result.recommendedCrossSectionMm2} mm²`); - } - } catch (err) { - setError(err instanceof Error ? err.message : "Berechnung fehlgeschlagen"); - } finally { - setCalculating(false); - } - } - - async function handleSubmit(event: FormEvent) { - event.preventDefault(); - if (!manualCrossSection.trim()) return; - await onApply({ - cableCrossSection: manualCrossSection.trim(), - cableType: manualCableType.trim() || undefined, - cableLength: lengthM, - }); - } - - return ( - -
-
- - setLengthM(Number(event.target.value))} - type="number" - value={lengthM} - /> -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- {phase === 3 && ( -
- - -
- )} -
- - setCosPhi(Number(event.target.value))} - step={0.05} - type="range" - value={cosPhi} - /> - {cosPhi.toFixed(2)} -
-
- - setMaxVoltageDropPercent(Number(event.target.value))} - step={0.5} - type="range" - value={maxVoltageDropPercent} - /> - {maxVoltageDropPercent.toFixed(1)} % -
-
- - - {error &&

{error}

} - - {result && !result.dataVerified && ( -
- Für Verlegeart {layingMethod} mit Isolierstoff {insulation.toUpperCase()} liegen keine - geprüften Strombelastbarkeits-Tabellenwerte vor - keine Dimensionierung möglich. Bitte - eine andere Verlegeart/Isolierstoff-Kombination wählen. -
- )} - - {result && result.dataVerified && ( -
-
-
-
Betriebsstrom Ib
-
{result.operatingCurrentA.toFixed(1)} A
-
-
-
- Bemessungsstrom (für Querschnittswahl){circuit.protectionDevice ? " *" : ""} -
-
- {result.designCurrentA.toFixed(1)} A -
-
-
-
Empfohlener Querschnitt
-
- - {result.recommendedCrossSectionMm2 - ? `${result.recommendedCrossSectionMm2} mm²` - : "-"} - -
-
-
-
ΔU bei Empfehlung
-
- {result.voltageDropAtRecommendedPercent != null - ? `${result.voltageDropAtRecommendedPercent.toFixed(2)} %` - : "-"} -
-
-
-
Korrekturfaktor
-
{result.combinedDerationFactor.toFixed(2)}
-
-
-
Max. Länge (ΔU-Grenze)
-
- {result.rows.find((row) => row.recommended)?.maxLengthForVoltageDropM != null - ? `${result.rows - .find((row) => row.recommended)! - .maxLengthForVoltageDropM!.toFixed(0)} m` - : "-"} -
-
-
- {alerts.map((alert, index) => ( -
- {alert.text} -
- ))} - {circuit.protectionDevice && ( -

- * Bemessungsstrom = Maximum aus Betriebsstrom und vorhandener Sicherung - ({circuit.protectionDevice.ratedCurrentA} A) - die Sicherung löst erst bei ihrem - eigenen Bemessungsstrom aus, das Kabel muss also dafür ausgelegt sein, nicht nur - für die tatsächliche Last. -

- )} -
- )} - -
-

- Querschnitt manuell eingeben oder eine Berechnung oben übernehmen - beides schreibt in - dasselbe Feld, du kannst es vor dem Übernehmen frei anpassen. -

-
-
- - setManualCableType(event.target.value)} - placeholder="z.B. NYM-J 3x1.5" - value={manualCableType} - /> -
-
- - setManualCrossSection(event.target.value)} - placeholder="z.B. 4 mm²" - value={manualCrossSection} - /> -
- {manualCrossSectionWarning && ( -
-
{manualCrossSectionWarning}
-
- )} -
-
- ); -} diff --git a/src/frontend/components/circuit-tree-editor.tsx b/src/frontend/components/circuit-tree-editor.tsx index d113b3b..03aa38d 100644 --- a/src/frontend/components/circuit-tree-editor.tsx +++ b/src/frontend/components/circuit-tree-editor.tsx @@ -137,7 +137,6 @@ import type { import { DistributionBoardComponentModal } from "./distribution-board-component-modal"; import { CircuitGroupModal } from "./circuit-group-modal"; import { CircuitProtectionModal } from "./circuit-protection-modal"; -import { CableSizingModal } from "./cable-sizing-modal"; import { circuitGroupCategoryLabels, type CircuitGroupCategory, @@ -279,8 +278,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str useState(null); const [protectionEditorCircuit, setProtectionEditorCircuit] = useState(null); - const [cableSizingEditorCircuit, setCableSizingEditorCircuit] = - useState(null); const [projectDevices, setProjectDevices] = useState([]); const [isProjectDeviceDrawerOpen, setIsProjectDeviceDrawerOpen] = useState(false); @@ -1401,31 +1398,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str }); } - async function handleApplyCableSizing(patch: { - cableCrossSection: string; - cableType?: string; - cableLength?: number; - }) { - const circuit = cableSizingEditorCircuit; - if (!circuit) { - return; - } - await runCommand({ - label: "Kabel dimensionieren", - redo: async () => { - const result = await updateCircuitById( - projectId, - getExpectedProjectRevision(), - circuit.id, - patch - ); - applyProjectCommandResult(result); - setCableSizingEditorCircuit(null); - return null; - }, - }); - } - async function handleRedo() { if (historyBusy || isSaving || !historyState || historyState.redoDepth === 0) { return; @@ -3167,20 +3139,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str onSave={handleSaveCircuitProtection} /> ) : null} - {cableSizingEditorCircuit ? ( - section.id === cableSizingEditorCircuit.sectionId - )?.category - } - isSaving={isSaving} - projectId={projectId} - onClose={() => setCableSizingEditorCircuit(null)} - onApply={handleApplyCableSizing} - /> - ) : null}