Skip to content

Architecture

The Building Configurator is a single-page React application. All application state lives in one component (BuildingConfigurator), which renders one of two workspace layers depending on the user's current view.

Two-layer structure

graph TD
    App["App<br>(map shell + data seeding)"]
    BC["BuildingConfigurator<br>State owner"]
    OV["Overview layer<br>workspaceView = overview"]
    CF["Configure layer<br>workspaceView = configure"]

    App --> BC
    BC --> OV
    BC --> CF

The header toggle (Overview ↔ Configure) switches workspaceView in BuildingConfigurator. Both layers read from the same state; neither owns its own copy.


Overview layer

Displayed when workspaceView === 'overview'. Split into two fixed columns.

graph TD
    BC["BuildingConfigurator"]

    subgraph OV["Overview layout — grid-cols: 430px | flex"]
        BSA["BuildingSnapshotAside<br>(left column)"]
        EEC["EnergyEnvelopeColumn<br>(right column)"]

        BSA --> N1["Data quality notice"]
        BSA --> N2["Energy hero<br>Heating / Electricity / Hot Water / Thermal efficiency"]
        BSA --> N3["Building parameters table<br>snapshotRows (type, area, U-value, storeys …)"]

        EEC --> LPV["LoadProfileViewer<br>Recharts line chart — hourly → monthly"]
        EEC --> ECS["ElementCompositionSection<br>Envelope accordion per surface group"]
        EEC --> TS["TechnologiesSection<br>4 tech cards"]

        TS --> T1["Solar PV card<br>per-surface scope — not togglable"]
        TS --> T2["Battery card<br>togglable install toggle"]
        TS --> T3["Heat Pump card<br>togglable install toggle"]
        TS --> T4["EV Charger card<br>togglable install toggle"]
    end

    BC --> OV

Data flowing into Overview

Prop Source Destination
energyTotals computeEnergyTotals(timeseries, thermalSummary) Energy hero numbers
snapshotRows buildSnapshotRows(general, elements) Parameters table
thermalRating getThermalRating(avgUValue) Thermal efficiency badge
installedTechIds otherTechIds + batteryConfig.installed Technology cards
pvSummary derived from surfacePvConfigs Solar PV card
initialTimeseries buildingData.thematic.timeseries Load profile chart
elements surface state Envelope composition

Configure layer

Displayed when workspaceView === 'configure'. Also split into two columns; the right column is further divided.

graph TD
    BC["BuildingConfigurator"]

    subgraph CF["Configure layout — grid-cols: 430px | flex"]
        LA["Left aside"]
        RS["Right section"]

        LA --> BV["BuildingVisualization<br>Clickable SVG 3D preview<br>Rotates to face direction on element select"]
        LA --> ED["Energy demand mini-panel<br>Same energyTotals as Overview — read-only"]

        RS --> CP["Center panel<br>switches on panelView"]
        RS --> SC["Selector column (w-72)<br>SurfaceGroupSelector"]

        CP --> PB["panelView = building<br>BuildingEditor<br>Type / area / height / storeys"]
        CP --> PSG["panelView = surface-group<br>(non-roof, element selected)<br>SurfaceGroupEditor<br>Geometry tab + Thermal tab + PV tab"]
        CP --> PRG["panelView = surface-group (roof)<br>SurfaceGroupGrid (type picker)<br>+ embedded SurfaceGroupEditor"]
        CP --> PPV["panelView = technology-pv<br>PvSurfaceManager<br>List of PV-enabled surfaces"]
        CP --> PBT["panelView = technology-battery<br>BatteryEditor<br>Capacity / efficiency / cost params"]

        SC --> SNav["Building nav item → panelView = building"]
        SC --> SGNav["Surface group nav items<br>Wall / Roof / Floor / Window / Door"]
        SC --> STNav["Technology nav items<br>Solar PV → technology-pv<br>Battery → technology-battery"]
    end

    BC --> CF

Panel navigation state machine

panelView is driven by user interaction. The transitions are:

stateDiagram-v2
    [*] --> building : initial / reset

    building --> surface_group : click surface group in selector
    building --> technology_pv : click Solar PV in selector
    building --> technology_battery : click Battery in selector

    surface_group --> building : click Building in selector
    surface_group --> surface_group : click different group or surface
    surface_group --> technology_pv : click Solar PV
    surface_group --> technology_battery : click Battery

    technology_pv --> surface_group : click a surface to configure its PV tab
    technology_pv --> building : click Building
    technology_battery --> building : click Building

State owned by BuildingConfigurator

classDiagram
    class BuildingConfigurator {
        workspaceView: overview | configure
        mode: basic | expert
        panelView: building | surface-group | technology-pv | technology-battery
        activeGroupType: ElementGroupKey | null
        selectedId: string | null
        elements: Record~string, BuildingElement~
        general: GeneralConfig
        roofConfig: RoofConfig
        surfacePvConfigs: Record~string, PvConfig~
        batteryConfig: BatteryConfig
        otherTechIds: string[]
        energyTotals: EnergyTotals
        pvInvalidated: boolean
        savedState: snapshot for unsaved-change detection
    }

    class BuildingElement {
        id: string
        label: string
        type: wall | window | door | roof | floor
        area: number
        uValue: number
        gValue: number | null
        tilt: number
        azimuth: number
        source: city | default | custom
        customMode: boolean
    }

    class PvConfig {
        installed: boolean
        geometryMode: surface | manual
        system_capacity: number
        tilt: number
        azimuth: number
        cont_energy_cap_max: number
        cont_energy_eff: number
        inv_eff: number
        cost_energy_cap: number
    }

    class BatteryConfig {
        installed: boolean
        cont_energy_cap_max: number
        cont_storage_cap_max: number
        cont_energy_eff: number
        cont_storage_loss: number
        cost_energy_cap: number
        cost_storage_cap: number
    }

    BuildingConfigurator "1" --> "0..*" BuildingElement : elements
    BuildingConfigurator "1" --> "0..*" PvConfig : surfacePvConfigs
    BuildingConfigurator "1" --> "1" BatteryConfig : batteryConfig

Data model output (exportToBuemGeojson)

The export assembles state into a BUEM GeoJSON FeatureCollection. Which fields appear depends on which technologies are installed.

graph LR
    S["BuildingConfigurator state"]

    S --> ID["identity<br>id / label / coordinates<br>buildingType / constructionPeriod<br>floorArea / roomHeight / storeys"]
    S --> ENV["envelope<br>one feature per BuildingElement<br>area / uValue / tilt / azimuth"]
    S --> PV["techs.pv_supply<br>per-surface PV params<br>only if installed = true"]
    S --> BAT["techs.battery_storage<br>capacity / efficiency / cost<br>only if installed = true"]
    S --> OTH["techs (other)<br>heat_pump / ev_charger<br>only if in installedTechIds"]

Redesign: tech card visibility registry

Problem

Technologies (Solar PV, Battery, Heat Pump, EV Charger) are hardcoded in TechnologiesSection.tsx and SurfaceGroupSelector.tsx. Adding or hiding a technology requires editing multiple files and manually pruning the data model.

Proposed design

Replace the hardcoded lists with a tech registry — a single configuration file that is the only place a developer touches when adding, removing, or hiding a technology.

graph TD
    REG["techRegistry.ts<br>Array of TechCardDefinition"]

    REG --> OV2["Overview: TechnologiesSection<br>renders only visible cards"]
    REG --> CFG2["Configure: SurfaceGroupSelector<br>shows only visible tech nav items"]
    REG --> EXP["exportToBuemGeojson<br>includes params only when<br>visible OR includeInModel = true"]

TechCardDefinition type

interface TechCardDefinition {
  /** Stable identifier used in installedTechIds and GeoJSON output. */
  id: string;

  /** Display name shown on the card. */
  label: string;

  /** Lucide icon component. */
  Icon: React.ElementType;

  /**
   * When false, the card is hidden from Overview and Configure nav.
   * The technology is effectively disabled for users.
   * Default: true.
   */
  visible: boolean;

  /**
   * When true, the tech's parameters are written to the exported data model
   * even if visible = false. Useful for pre-populating params that the
   * simulation engine always expects, regardless of whether the user sees the card.
   * Default: false.
   */
  includeInModel: boolean;

  /**
   * Where the technology is configured.
   * 'per-surface' → PV-style (no install toggle; scope is each surface).
   * 'building'    → building-level toggle + optional detail editor panel.
   * 'none'        → toggle only, no configure panel.
   */
  scope: 'per-surface' | 'building' | 'none';

  /**
   * panelView key to navigate to when the card is opened.
   * Required when scope = 'building'.
   */
  panelView?: string;
}

Example registry (src/app/config/techRegistry.ts)

export const TECH_REGISTRY: TechCardDefinition[] = [
  {
    id:             'solar_pv',
    label:          'Solar PV',
    Icon:           Sun,
    visible:        true,
    includeInModel: false,
    scope:          'per-surface',
  },
  {
    id:             'battery',
    label:          'Battery',
    Icon:           Battery,
    visible:        true,
    includeInModel: false,
    scope:          'building',
    panelView:      'technology-battery',
  },
  {
    id:             'heat_pump',
    label:          'Heat Pump',
    Icon:           Thermometer,
    visible:        true,
    includeInModel: false,
    scope:          'none',
  },
  {
    id:             'ev_charger',
    label:          'EV Charger',
    Icon:           Plug,
    visible:        false,       // hidden — card does not appear in UI
    includeInModel: false,
    scope:          'none',
  },
];

To hide a card: set visible: false. To keep its parameters in the exported JSON even when hidden: also set includeInModel: true. To add a new technology: append one entry and implement the configure panel if scope = 'building'.

Implementation scope

The registry change requires updating three files: TechnologiesSection.tsx (reads TECH_REGISTRY instead of the hardcoded BUILDING_TECHNOLOGIES array), SurfaceGroupSelector.tsx (reads TECH_REGISTRY for nav items), and exportToBuemGeojson in buemAdapter.ts (checks visible || includeInModel before writing tech params).