01 / 10

Energy Analysis Module

IFC-to-Energy Simulation Pipeline for the NBLA Platform

March 26, 2026

intentolabs.io nrc.canada.ca

Press → to begin

Feature Branch · Pull Request

Energy Analysis Module

IFC-to-Energy simulation pipeline integrated into the NBLA platform

Branch: feature/energy-analysis

FastAPI EnergyPlus 9.4 Docker Hub Private IFC Enrichment
Overview

What We Built

A standalone Energy Converter service that takes IFC building models, preprocesses them, runs a full annual EnergyPlus simulation, and writes the results back into the NBLA platform — enriching both the IFC files and the MongoDB database.

📐

IFC Converter

Generates missing IfcSpace and IfcRelSpaceBoundary elements from raw IFC files

EnergyPlus Engine

Full annual heating and cooling simulation with configurable thermal zones

🔄

NBLA Push-Back

Results and enriched IFC files pushed back to the main platform and stored in MongoDB

Pipeline

Energy Analysis — Step by Step

End-to-end flow from IFC upload to enriched results in the database

1

Upload IFC + Weather File

User selects an IFC from the project, sends it to the Energy Converter API with an EPW weather file

2

IFC Preprocessing

Converter generates missing IfcSpace and space boundary elements automatically

3

Configure Thermal Zones

Select storeys, edit heating/cooling setpoints, lighting power, occupancy schedules

4

Run EnergyPlus Simulation

Full annual simulation in background (~1–5 min) with real-time log streaming to the frontend

5

Enrich IFC Files

Writes Pset_EnergyAnalysis properties into both preprocessed and original IFC files

6

Push Back to NBLA

Enriched IFC uploaded to project via Main API → results stored in MongoDB metadata.energyAnalysis

Integration

How It Connects to NBLA

The Energy Converter runs as a standalone container and pushes results back through the Main API

🌐  Web App (React — energy.page.tsx)
IFC + EPW upload · poll status
⚡ Energy Converter
FastAPI · EnergyPlus 9.4 · :8000
Push enriched IFC + results
Download original IFC
📦 Storage Service
gRPC · File System · :4001
_
⚙️ Main API Service (:4005)
Upload preprocessed IFC · Upload enriched original IFC · Update metadata.energyAnalysis
🍃 MongoDB (IFC document updated)
Automated Detection

IFC → Space Boundaries → Simulation

When an IFC file lacks IfcRelSpaceBoundary, the system detects and generates them automatically

📄 IFC File Uploaded to Project
🔍 Has IfcRelSpaceBoundary?
✗ MISSING — Auto-Generation Triggered
⚙️ Generate IfcSpace + Level 2a / 2b Boundaries
🔧 Geometric Corrections & Validation
✅ Preprocessed IFC with Full Boundaries
🧱 Enrich
📝 IDF
⚡ EnergyPlus
📊 Results

What Are Space Boundaries?

Geometric interfaces between thermal zones and building elements (walls, slabs, windows). They define the surfaces through which heat transfer occurs — required for zone-based energy simulation.

Level 2a vs 2b

2a: Connects a space to a specific building element (wall, slab, window). Two opposing boundaries per interior partition.
2b: Virtual surfaces that close geometric gaps where 2a boundaries don't fully enclose a space.

Geometric Corrections

• Fix non-coplanar opening boundaries
• Correct surface normal orientations
• Split non-convex boundaries (EnergyPlus requirement)
• OpenCascade Boolean subtraction for gap detection

Why This Matters

Most IFC files from BIM tools (Revit, ArchiCAD) lack space boundaries. Without them, energy simulation is impossible. This auto-generation step makes any IFC file simulation-ready.

Data Persistence

How Results Get Saved to MongoDB

The full chain from simulation output to persisted database document and back to the UI

⚡ EnergyPlus
🐍 Energy Converter
⚙️ Main API (PATCH)
🍃 MongoDB
🌐 Frontend KPIs

1. Zod Schema Validation

libs/schema/src/main/ifc.validation.ts

energyAnalysis: z.object({ heatingEnergy_kWh: z.number(), coolingEnergy_kWh: z.number(), totalEnergy_kWh: z.number(), zonesCount: z.number(), simulationDate: z.string(), preprocessedIfcId: z.string().optional(), enrichedOriginalIfcId: z.string().optional(), }).optional(),

2. Mongoose Model — markModified

microservices/main/src/models/ifc.model.ts

async update(data): Promise<void> { if (data.metadata) { this.document.metadata = data.metadata; this.document.markModified('metadata'); } await this.document.save(); }

3. Energy Converter Push-Back

Python main_v2.py → _push_results_to_nbla()

# After EnergyPlus completes: # 1. Upload preprocessed IFC → Storage # 2. Upload enriched original IFC → Storage # 3. PATCH Main API with results: metadata.energyAnalysis = { heatingEnergy_kWh: 12450.3, coolingEnergy_kWh: 8720.1, totalEnergy_kWh: 21170.4, zonesCount: 8, preprocessedIfcId: ObjectId, enrichedOriginalIfcId: ObjectId }

4. Frontend Reads Back

IFC document fetched via Main API → metadata.energyAnalysis → displayed as KPI cards (heating kWh, cooling kWh, total, zone count)

Key fix: markModified('metadata') is required because Mongoose doesn't auto-detect changes to nested Mixed-type objects

Pull Request

What We Changed

Key files modified in the feature/energy-analysis branch

⚡ docker-compose.energy.yml

New compose file for the Energy Converter container — pulls from Docker Hub, joins NBLA Docker network, configures env vars for Main API push-back

📝 libs/schema/ifc.validation.ts

Added enrichedOriginalIfcId optional field to the energyAnalysis schema — tracks the enriched original IFC document

🛠️ microservices/main/ifc.model.ts

Fixed Mongoose persistence — added markModified('metadata') to ensure nested energy analysis fields are saved correctly

🐍 Energy Converter API (main_v2.py)

Modified _push_results_to_nbla to upload both the preprocessed IFC and the enriched original IFC back to the NBLA project storage

🌐 gateway/conf.d/energy.conf

Nginx vhost for energy.nbla.ca — proxies HTTPS to the Energy Converter on port 8000

🔧 .env + nbla-docker.sh

Added NBLA_ENERGY_IMAGE, container name, port variables. Updated script to include energy service in the build/start loop

Database

What Gets Stored in MongoDB

Energy results are persisted inside the IFC document's metadata

// IFC Document → metadata.energyAnalysis { "status": "completed", "heatingKwh": 12450.3, "coolingKwh": 8720.1, "totalKwh": 21170.4, "zoneCount": 8, "preprocessedIfcId": "ObjectId(...)", "enrichedOriginalIfcId": "ObjectId(...)", "completedAt": "2026-03-07T..." }

New field: enrichedOriginalIfcId — added in this PR to track the enriched version of the original IFC, separate from the preprocessed version

📊 KPI Results

Heating kWh, cooling kWh, total energy, zone count — shown as KPI cards in the UI

📎 File References

Two ObjectIds pointing to enriched IFC files stored as project documents in Storage

🔄 Mongoose Fix

markModified('metadata') ensures Mongoose detects changes to the nested energy object and persists them

Deployment

Why a Private Docker Hub Image?

Every other NBLA service builds from source with docker compose build. The Energy Converter is different:

⏱️

Heavy Build

~15 minutes to compile — EnergyPlus, micromamba, Python scientific stack, runtime patches

🔒

Separate Repository

Source lives in a separate repo — team members shouldn't need to clone or build it

Identical Binary

Everyone runs the exact same image — no "works on my machine" issues with build patches

Registry: hub.docker.com/r/intentolabs/energy-services   ·   Visibility: Private   ·   Owner: intentolabs

Admin Guide

Granting a New Team Member Access

The intentolabs account owns the private image. Here's how to add a new person:

Step-by-step for the admin

  1. The new team member creates a free Docker Hub account at hub.docker.com
  2. They send you their Docker Hub username
  3. Log in to Docker Hub as intentolabs
  4. Go to the repository settings:
    hub.docker.com/r/intentolabs/energy-services/settings
  5. Click Collaborators
  6. Add their Docker Hub username with Read permission
  7. Done — they can now pull the image using their own credentials

⚠️ Important

We do not share the intentolabs Docker Hub password or access tokens with team members.

Each person uses their own Docker Hub account to pull the image. They only get Read access as a collaborator.

This way, access can be revoked individually if someone leaves the team.

Collaborator Permissions

Read — can pull the image (team members)
Admin — can pull and push (builders only)

Team Member Guide

Setting Up Access (New Team Member)

Once the admin has added you as a collaborator, follow these steps:

1

Create Docker Hub Account

Sign up at hub.docker.com (free) and send your username to the admin

2

Generate a Personal Access Token

Go to hub.docker.com/settings/security
New Access Token → name it (e.g. nbla-energy) → Read-only

3

Wait for Admin to Add You

The admin adds your username as a collaborator on the private repo with Read access

4. Log in with YOUR credentials

# Log in with YOUR Docker Hub username docker login -u YOUR_DOCKERHUB_USERNAME # Paste YOUR personal access token # when prompted for password

5. Pull and run the energy service

# Pull the private image docker pull intentolabs/energy-services:latest # Or just start it (auto-pulls) docker compose --env-file .env \ -f docker-compose.energy.yml up -d
Quick Start

Running the Full Stack with Energy

Everything a new team member needs to get up and running

# 1. Clone and switch to the energy branch git clone https://github.com/alisavabnrc/nbla-sync.git cd nbla-sync git checkout feature/energy-analysis # 2. Log in to Docker Hub (your own account) docker login -u YOUR_DOCKERHUB_USERNAME # 3. Build all services and start ./nbla-docker.sh up # Builds 12 services from source # Pulls energy image from Docker Hub # Starts all 13 containers # 4. Initialize mapper cd microservices/mapper npm install cd ../.. ./initialize-mapper.sh # 5. Open the app # https://app.nbla.ca

Prerequisites

Docker Desktop · Git · Docker Hub account with collaborator access to intentolabs/energy-services

.env — Verify this line

NBLA_ENERGY_IMAGE=intentolabs/energy-services:latest

Don't forget: hosts file

Add *.nbla.ca entries to your system hosts file and trust the root CA certificate. See README for full list.

Update the energy image later

docker pull intentolabs/energy-services:latest docker compose -f docker-compose.energy.yml down docker compose -f docker-compose.energy.yml up -d