Skip to content
Updated Oct 6, 2026 by Pablo Coufal · Owner: analysisactivefeatureconfluence-migration Edit on GitHub

Building Management ​

Business Context ​

Business-Level Definition ​

Buildings represent physical and organisational locations whose energy consumption is tracked and managed. The building hierarchy allows organisations to model their real-world structure at any level of detail — a single school, a regional portfolio of municipal buildings, a campus with multiple pavilions, or a combination of these.

Requirements Definition ​

Building management must support:

  • Creating, editing, archiving and restoring buildings at any position in the hierarchy
  • Attaching identifying information: name, type, address, owner, manager, photo
  • Assigning buildings to a parent node to express organisational or physical grouping
  • Storing physical and energy parameters with full historical tracking (changes are versioned, not overwritten)
  • Integrating with external registries (ARES — autocomplete lookup by company name or tax ID for owner, manager and delegated manager fields; supports natural persons without IČO) and linking the selected or newly entered subject to the organisation registry
  • Exporting the building list to a spreadsheet
  • Controlling visibility and editability through a role-based permission model

Acceptance Criteria ​

  • A building can be created, edited, archived and restored through the UI and API
  • The building tree renders correctly at any depth and reflects parent–child relationships accurately
  • Versioned parameters (floor area, user count, reference temperature) store a history of changes; previous values remain accessible for historical reports
  • The owner, manager and delegated manager fields each provide a single autocomplete input combining existing organisation records with live ARES results; selecting a result links the building to the corresponding organisation (creating one first if it does not yet exist); natural persons can be entered as free text without IČO
  • ZIP lookup populates city automatically when a postal code is entered; the populated value can be overridden manually
  • A building's operating schedule can be defined as a structured weekly 24×7 grid; the structured schedule is the authoritative source for anomaly detection and takes precedence over the free-text operatingTime field
  • An archived building moves out of the active list; its gauges are archived automatically; historical data remains accessible
  • A building with gauge readings or invoices cannot be permanently deleted
  • Users without the "View all buildings" permission see only buildings they are assigned to
  • The building list can be exported to XLSX

N/A — not available in the source material.

Technical Context ​

User Stories / Use Cases ​

Browse and find buildings: A user opens the building list → the list shows all buildings the user has access to, with columns for name, type, sector, address, owner, manager and gauge count. The user can filter by type, sector and free text and can customise visible columns. A separate section shows archived buildings.

Create a building: A user with the "Create building" permission opens the creation form → fills in the required fields (name, type, sector) and optional fields (address, owner, manager, GPS coordinates, photo, energy parameters) → selects or creates a parent node → saves. The system creates the building and assigns the current user to it.

Edit a building: A user with the appropriate role edits an existing building. Fields that are versioned (floor area, user count, reference temperature) create a new historical record on save; the previous value is preserved. Type and sector are read-only after creation unless a special role is held.

Manage the building hierarchy: An admin organises buildings into a tree by assigning parent nodes. The UI renders the tree and allows repositioning. The system prevents circular references.

Archive and restore a building: A user archives a building by selecting an archival reason. All gauges of the building are archived automatically. The building moves to the archived section. A user with the restore permission can reactivate it (gauges must be restored separately).

View building detail: A user opens a building detail page showing identification data, parameters, linked gauges, assigned persons (see Client Management, Organisations), active contracts, documents and recent actions. A mini-dashboard shows a consumption summary.

UI/UX Design ​

Pending UI/UX review. Acceptance criteria for this feature describe functional behaviour only. Visual and interaction design will be added once wireframes are approved.

Per-record history wireframes. Three screens carry a "Historie" entry point (see Audit Log — Per-Record History component for the shared destination view): Building Detail and Energy Management — reusing the header-button pattern already approved for this feature in Figma (node 1392-25607 / 583-9668), which also shows a separate, not-yet-extended per-field pattern on select parameters — and Dokumenty. Dokumenty's entry point is confirmed per-document, a row-level "Historie" icon in the document table (between preview and delete) — not the header-button pattern used elsewhere in this doc, and not the disabled toolbar-level "Historie" button found in the shipped frontend (documents-toolbar.tsx), which is a different, separate stub that doesn't match this icon at all. Recreated at documents-knihovna.html, grounded in the real document category enum and columns from the EM3 code, with two open scope questions flagged directly on the page rather than resolved silently: (1) the screenshot shows Dokumenty as its own cross-building top-nav section with an "Objekt" column, while the shipped frontend has Dokumenty only as a tab inside one building's own page, with no cross-building view; (2) the screenshot's category sidebar has an 8th row ("Správa majetku") with no match in the shipped DOCUMENT_CATEGORIES enum (only 7 values).

Dokumenty → detail. A single-document detail screen, distinct from the Knihovna overview above: document-detail.html, wired from Knihovna's own "Náhled" icon on its first row. No Figma frame or shipped screen exists for this yet — grounded directly in the document entity's own fields (category, medium — invoices only, labels, note, size, mime type, registration status, uploader) rather than any existing design, with the same two open scope questions from Knihovna flagged on the page too. Header "Historie" button, matching the convention used for every other "→ Detail" screen.

Functional Requirements ​

Building types ​

Each building has a type (enum — BuildingType) that controls which form sections are displayed:

ValueDescription
buildingStandard building — school, office, residential block, etc.
publicLightingStreet lighting system — address fields are hidden; lighting-specific fields appear (number of control cabinets, light points)
otherInfrastructure, technology nodes or any object that does not fit the above categories

Building type cannot be changed after creation without a special administrative role.

Building hierarchy ​

Buildings are organised into a tree using two attributes:

  • building.parentId (nullable) — reference to the parent building node. A null value marks a root node (top of the hierarchy for this tenant).
  • building.levelType (optional hint, enum — BuildingLevelType) — describes what the node represents in organisational terms. Used by the UI for display and filtering; not enforced as a structural constraint.
ValueTypical use
portfolioTop-level grouping across multiple locations
regionGeographic or organisational region
campusGroup of buildings on one site
buildingA single physical building
floorA floor or zone within a building
unitAn individual unit or space

The tree is flexible — a campus node can contain unit nodes directly without intermediate building or floor levels. The hierarchy is driven by real-world structure, not by a fixed schema.

Gauge outputMode — context for building total ​

Each gauge attached to a building carries a gauge.outputMode attribute that expresses the user's intent about how the gauge contributes to the building's total consumption. This is defined in full detail in Gauge Management. The relevant context for buildings is:

  • include — gauge consumption is added to the building total
  • subtract — gauge consumption is subtracted from the building total (e.g. energy consumed by a third-party tenant)
  • exclude — gauge is not part of any building total
  • reportOnly — not included in building total, but visible in reports and available for use in Virtual Gauge formulas (e.g. FVE production meter)

The system derives the arithmetic sign in the building total formula by combining gauge.outputMode with gauge.direction. Full rules are defined in Gauge Management.

Building total as a Virtual Gauge ​

The building total consumption is never stored as a simple sum. It is always a Virtual Gauge with an explicit, auditable formula. This ensures the total is transparent, traceable and auditable — every number in a building total chart can be traced back to a specific formula and its source gauges.

Two modes of formula creation:

  • Auto-generated formula — for simple topologies, the system generates a suggested formula automatically from gauge.outputMode and gauge.direction of all Standard and Sub-gauge gauges attached to the building → user reviews and confirms it.
  • Manually defined formula — required for complex topologies: FVE gauges, gauges referencing nodes from other building levels, percentage-based formulas, etc.

Virtual gauges themselves are never included in auto-generation — they have no outputMode and always enter building totals only through explicit formulas.

Auto-generation is triggered when:

  • a new gauge with outputMode include or subtract is added to a building
  • gauge.outputMode is changed
  • a gauge is archived or removed

In each case the system offers an updated suggested formula. The user must confirm any change — the formula is never updated silently.

Before the final consumption figure is written, the raw total is adjusted by the gauge-level unit conversion coefficient. Building-level climate correction (degree-day normalisation) is applied separately, at read time — it is not stored on the consumption record and is computed on demand from climateData/climateNormal whenever normalised consumption is requested (reports, charts or the snapshot action). See Consumption Normalisation for the full specification.

Versioned (historical) parameters ​

The following parameters are tracked with full history, held on the buildingParameter entity. Each change creates a new record with a validFrom date; the previous value is retained and used for any reports covering the period it was valid:

  • buildingParameter.userCount — number of active users
  • buildingParameter.areaEnergyReference — total energy-reference floor area (m²)
  • buildingParameter.areaUsable — total usable floor area (m²)
  • buildingParameter.volumeEnclosed — enclosed volume (m³)
  • buildingParameter.temperatureReference — reference internal temperature (°C)
  • buildingParameter.lightPoints — number of light points (publicLighting type only)
  • buildingParameter.lightPointsPv — light points with photovoltaic panels (publicLighting type only)

Correcting individual periods in buildingCalculatedConsumption ​

The buildingCalculatedConsumption entity (calculated/target consumption):

  • follows the append-only validFrom pattern
  • resolves "current" independently per buildingId + usageType + periodStart/periodEnd combination

Correcting a subset of already-entered periods requires writing new rows for those specific periods, each with its own validFrom. A superseded row is never returned by default queries or reports for that period once corrected — it is retained only for audit purposes.

Energy Baseline reference year ​

All buildingEnergyBaseline rows for a given building must use the same referenceYear — different media cannot report different baseline years for the same building. This is enforced at write time (see the buildingEnergyBaseline entity, Validation column, once migrated into this repo's Entity DAT catalog).

The UI may pre-fill referenceYear from the client's typical/most common reference year across its buildings, as a convenience default — this is a suggestion only, not a hard constraint. A building may still use a different reference year than its client's default when a legitimate exception exists (e.g. it joined the EnMS programme in a different year than the rest of the portfolio).

Difference: buildingParameter vs buildingEnergyProfile ​

Both entities carry versioned data about a building, but they serve different roles and are maintained by different people.

buildingParameter holds physical and operational measurements:

  • floor areas, user count, enclosed volume, reference temperature, light points, etc.
  • values are typically maintained by the energy manager and change in response to physical events (e.g. a reconstruction, change in building use, new measurement)
  • direct inputs to consumption calculations and normalisation

buildingEnergyProfile holds administrative and certification metadata:

  • PENB data, programme enrolments (EPC, ISO 50001, OPZP), installed power, and building envelope coefficients
  • values are typically maintained by the building administrator and change as a result of administrative events (a new energy audit, a certification being granted or renewed, an updated technical specification)

Keeping the two entities separate preserves clean audit trails for each lifecycle and avoids mixing data that is updated at different frequencies by different roles.

ARES integration ​

Owner, manager and delegated manager fields use the shared ARES autocomplete integration. Full specification: see External Integrations — ARES.

Selections and manual entries are backed by building.ownerId/.managerId/.delegatedManagerId (FK → organisation); the legacy *Name/*Ico text fields are retained as a read-only snapshot.

ZIP-to-city lookup ​

When a ZIP code is entered in building.zip, the system automatically populates building.city from a postal code–city mapping. The lookup is triggered on field blur. The populated value can be overridden manually.

Client-side validation enforces the ZIP format before the lookup or save can proceed:

  • the zip field accepts only numeric characters, capped at 5
  • a value that is not exactly 5 digits shows an inline error under the field ("PSČ musí obsahovat přesně 5 číslic." — "ZIP must contain exactly 5 digits.") and suppresses the lookup call while invalid

On save: an invalid zip field receives focus instead of surfacing only a generic form-level error. A syntactically valid 5-digit ZIP that is not found in the dataset still follows the existing behaviour above — the city field is left empty with no error.

A building stores two pairs of GPS coordinates:

  • building.lat / building.lng — geocoded automatically from the building address.
  • building.latCustom / building.lngCustom — optional user-defined override. When set, the custom pair takes precedence over the geocoded pair for all display purposes.

The effective coordinates (custom if set, geocoded otherwise) are used to:

  • position the building pin on the map view,
  • generate the Mapy.com deep-link (building.mapyComUrl) displayed in the building detail.

The Mapy.com URL is a computed (read-only) field — generated on-the-fly, not stored in the database. It is null when no coordinates are available.

Operating schedule ​

A building's operating hours are stored in two complementary places:

  • buildingEnergyProfile.operatingTime — a free-text human-readable description (e.g. "Mo–Fr 07:00–18:00"). Exists primarily to carry over the legacy operating_time text values from EM2 and to give users a quick orientation label. Non-binding in all system logic.
  • buildingSchedule — a structured weekly 24×7 grid of 15-minute slots (672 slots per week, starting Monday 00:00). This is the authoritative source used by anomaly detection and control regime evaluation. A building may have multiple buildingSchedule records versioned via validFrom; the record with the highest validFrom not exceeding the evaluated timestamp is active for that period.

The two fields are independent — updating operatingTime does not affect the structured schedule and vice versa. When a structured schedule exists, it governs system behaviour regardless of what operatingTime says.

The UI presents the structured schedule as a 24×7 grid (rows = hours 00–23, columns = days Mon–Sun) where the user activates or deactivates individual slots or ranges. The operatingTime text field is a separate optional input.

Archival behaviour ​

  • Archiving a building automatically archives all its gauges, in the same atomic transaction. The archiveReason selected for the building is copied directly to each gauge — all four values of enum BuildingArchiveReason have a matching value in enum GaugeArchiveReason, so no conversion is needed. The cascade archival logic lives in the building archive endpoint (POST /v1/buildings/:id/archive) and is completed as part of the Gauge Management implementation.
  • An archival reason must be selected from enum BuildingArchiveReason.
  • The building's archiveReason is directly mapped to each cascaded gauge's archiveReason — all four values of BuildingArchiveReason (saleDemolition, transfer, outOfScope, other) have a matching value in GaugeArchiveReason. No conversion logic is needed.
  • An optional flag (building.showArchivedInReports) controls whether historical data from the archived building appears in reports and charts.
  • Archived buildings (building.isArchived = true) are listed separately from active buildings.
  • A building with gauge readings or invoices cannot be permanently deleted — archival is the correct end-of-life action.

Per-record history ​

Building Detail, Energy Management and the Dokumenty tab each carry a "Historie" entry point onto their own record's change history (building, document), reached in place — the same audit trail as Audit Log. Dokumenty's button already exists in the shipped frontend (documents-toolbar.tsx) but is disabled, deferred out of scope rather than wired up. See UI/UX Design above for the wireframes.

Permissions model ​

Pending — to be defined during Users & Access design. Access to individual sections of the building form (identification, parameters, energy performance data, archival, export) is controlled by roles. The exact role names and their permission matrix will be specified as part of the Users & Access domain. Until then, acceptance criteria reference permissions by their functional description only (e.g. "user with permission to archive buildings").

Non-Functional Requirements ​

Performance ​

  • The building list must render in under 1 second for tenants with up to 10,000 buildings
  • The full building tree must load in under 2 seconds for trees up to 500 nodes deep

Transactional Operations ​

  • Building creation (including creation of linked buildingParameter and buildingEnergyProfile records) must be atomic — all succeed or all roll back
  • Building archival (including cascading archival of all child gauges) must be atomic

Implementation Notes ​

Tenant provisioning — root node ​

When a new tenant is provisioned, the system must automatically create one root building node as part of the tenant setup process (not via the buildings API). The root node has:

  • name = tenant name (can be renamed by the user)
  • levelType = portfolio
  • parentId = null
  • type = other
  • Linked buildingParameter and buildingEnergyProfile records created in the same transaction

This node is the default parent offered to users when creating their first buildings. It cannot be permanently deleted as long as it has children.

Archiving cascade ​

When a building is archived, all its gauges are archived too. The cascade archival logic is implemented as part of the Gauge Management epic — Building Management was implemented first (no gauge entities were available yet at that point).

API Analysis ​

GET    /v1/buildings                      List buildings (filterable, paginated)
GET    /v1/buildings/tree                 Full building tree for tenant
GET    /v1/buildings/:id                  Building detail
POST   /v1/buildings                      Create building
PATCH  /v1/buildings/:id                  Update building fields
DELETE /v1/buildings/:id                  Permanent delete (only if no readings/invoices)
POST   /v1/buildings/:id/archive          Archive building (with reason)
POST   /v1/buildings/:id/restore          Restore archived building
GET    /v1/buildings/:id/parameters       Current + historical versioned parameters
POST   /v1/buildings/:id/parameters       Add new versioned parameter record
GET    /v1/buildings/:id/schedule         Current + historical versioned schedules
POST   /v1/buildings/:id/schedule         Add new versioned schedule record
GET    /v1/buildings/export               Export building list to XLSX
GET    /v1/geo/city-by-zip?zip=:zip       City name lookup by Czech postal code

Building creation transaction ​

POST /v1/buildings must create three records atomically in a single DB transaction:

  1. building — the building node itself
  2. buildingParameter — first versioned parameter record with validFrom = today and all fields null (populated later by the user)
  3. buildingEnergyProfile — empty placeholder record (all fields null), so the 1:1 relationship is always satisfied

If any of the three inserts fails, the entire transaction must roll back. Never leave a building row without its corresponding buildingParameter and buildingEnergyProfile.

Cycle detection on parentId update ​

On every POST /v1/buildings and PATCH /v1/buildings/:id where parentId is set or changed, the handler must synchronously verify that the proposed parentId does not create a cycle before writing to the DB:

  1. Walk the tree upward from the proposed parentId, following parentId at each step
  2. If the building's own id appears anywhere in the chain → reject with 400 Bad Request
  3. If null is reached without finding the building's id → the assignment is safe, proceed

Return body on cycle detected:

json
{ "error": "CIRCULAR_REFERENCE", "message": "The selected parent would create a cycle in the building hierarchy." }

Domain Model (ER diagram) & Data Attribute Table ​

No ER diagram in source material. Related entity pages (Confluence, not yet migrated into this repo's Entity DAT catalog):

  • building — the building node itself (hierarchy, identification, GPS, archival state)
  • buildingParameter — versioned physical/operational parameters
  • buildingEnergyProfile — versioned administrative/certification metadata
  • buildingSchedule — versioned 24×7 structured operating schedule
  • buildingEnergyBaseline — energy baseline reference year data (see Consumption Normalisation)
  • buildingCalculatedConsumption — calculated/target consumption, versioned by period

Data ​

No seed data is required for this feature. Tenant administrators create buildings as part of initial setup. The system creates one root building node automatically when a new tenant is provisioned, which acts as the default parent for subsequently created buildings.

Test Data ​

N/A — not covered in source material; no Test Data location was specified in the source page.

Logging ​

.info

  • building created, updated, archived, restored, deleted
  • versioned parameter record created (including which field changed and previous value)
  • schedule record created (buildingId, validFrom)

.debug

  • ARES lookup triggered, ARES lookup result (success / timeout / not found) — see External Integrations — ARES
  • ZIP lookup triggered, ZIP lookup result (success / not found)

Monitoring ​

N/A — not covered in source material.

Caching ​

N/A — not covered in source material.

Backward Compatibility and Migration ​

The legacy system used a flat two-level model: Client → Ground (Areál) → Building (Objekt), backed by the PHP entities Building, BuildingProperties and BuildingEnergyManagement. The versioned parameters were spread across 6 separate tables with no single BuildingHistoryParam entity.

The new model consolidates all of this into a unified building tree with buildingParameter and buildingEnergyProfile.

The complete field-by-field migration map, including exact legacy column names verified from source code and all open decisions, was maintained in a child Confluence page ("Building Migration Map") that is out of scope for this migration pass.

Versioned parameters. Each legacy table keeps its own valid_from; rows are consolidated by building and validFrom without forward fill.

Legacy tablebuildingParameter field
building_heated_area.areaareaEnergyReference — labelled Celková energeticky vztažná plocha in EM2; only the table name says "heated"
building_usable_area.areaareaUsable
building_built_up_area.areavolumeEnclosed — labelled Obestavěný prostor (m³) in EM2; only the table name says "built-up area"
building_users_quantity.quantityuserCount
building_templerature.temperaturetemperatureReference
building_public_light_points(_fve).pointslightPoints, lightPointsPv (publicLighting only)
  • A re-run updates the consolidated row in place; it never creates a second row for the same building and validFrom.
  • Parameter values held in EM3 under earlier field names (total area, heated area, built-up area, occupancy) are moved to areaEnergyReference, volumeEnclosed and userCount where those are empty. A value that would overwrite a different one, and a total-area value that has no target, is listed in the reconciliation report by building and validFrom instead of being changed silently.
  • Reconciliation per client: the count of areaEnergyReference values equals the count of building_heated_area rows, the count of volumeEnclosed values equals the count of building_built_up_area rows, and the count of userCount values equals the count of building_users_quantity rows.

N/A — not addressed in the source Confluence page; migrated as a reference copy without new legal analysis.

Cybersecurity Considerations ​

N/A — not addressed in the source Confluence page; migrated as a reference copy without new security analysis. Note: building owner/manager fields carry personal-data-adjacent information (natural persons without IČO) — this should be reviewed when Cybersecurity/Data Privacy is formally analysed for this feature.

Risk Assessment ​

N/A — not addressed in the source Confluence page; migrated as a reference copy without new risk analysis.

Auditing, Reporting & Measurement ​

N/A — not addressed in the source Confluence page; migrated as a reference copy without new analysis. Note: versioned parameter changes and archival actions are logged (see Logging above), which is adjacent to auditing but was not framed as such in the source.