Date: 2026-03-10
Scope: Architecture, calculator pipeline, API compliance, deployment
- 1. System Overview
- 2. Backend Architecture
- 3. Calculator Reference
- 4. Database Schema
- 5. API Endpoint Reference
- 6. Backend Field Normalizer
- 7. Issues and Recommendations
- 8. Running the System
- 9. Deployment to Server
- [10. How to]
- [how to add new calculator's method]
- [how to add new calculator]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
- [how to ]
The CIM Wizard system consists of three interconnected projects:
| Project | Role | Technology | Port |
|---|---|---|---|
| cim-database | Dockerized PostgreSQL/PostGIS database | PostgreSQL 15 + PostGIS + TimescaleDB + 3DCityDB | 15432 |
| cim_wizard_integrated_2026 | Backend API server | Python / FastAPI / SQLAlchemy | 8000 |
| coesi-frontend-main_2026 | Frontend web application | React / TypeScript / Vite | 5173 |
Client (Frontend / Postman / Script)
|
| HTTP/JSON
v
+---------------------------+
| FastAPI Endpoints | app/api/
| (vector_routes, | - building_analysis_route.py (22-step pipeline + grid/pv/citydb)
| building_analysis_route, | - network_routes.py (grid network data)
| network_routes, | - pipeline_routes.py (generic pipeline API)
| pipeline_routes) | - vector_routes.py (CRUD + schema)
+---------------------------+
|
| Creates instances per request
v
+---------------------------+ +---------------------------+
| CimWizardPipelineExecutor | ----> | CimWizardDataManager |
| (orchestration, deps, | | (SINGLE DB BOUNDARY) |
| calculator loading, | | - context & features |
| fallback methods) | | - configuration.json |
+---------------------------+ | - Scenario CRUD |
| | - Building CRUD |
| Dynamically loads | - BuildingProperties CRUD |
| from configuration.json | - Raster queries |
v | - Grid / PV / CityDB |
+---------------------------+ +---------------------------+
| BaseCalculator | |
| +-- Calculator (height) | v
| +-- Calculator (area) | +------------------------------------+
| +-- Calculator (volume) | | PostgreSQL 15 + PostGIS |
| +-- Calculator (pop.) | | + TimescaleDB + 3DCityDB |
| +-- Calculator (type) | | |
| +-- Calculator (grid) | | Schemas: |
| +-- Calculator (pv) | | cim_vector, cim_census, |
| +-- Calculator (citydb) | | cim_raster, cim_network, |
| +-- ... (22 total) | | outputs, citydb, citydb_pkg |
+---------------------------+ +------------------------------------+
Key architectural rule: only CimWizardDataManager touches the database. Routes and calculators delegate all persistence to DataManager methods.
The backend enforces a strict separation of concerns across four layers:
Endpoint (route)
|
v
PipelineExecutor ----------> CimWizardDataManager (single DB boundary)
| |
v v
BaseCalculator PostgreSQL / PostGIS
|
v
Calculator (domain logic)
-
CimWizardDataManager -- The single data-access boundary for the entire application. Every database read, write, and delete goes through this class. It also manages context (project/scenario IDs), feature storage, configuration, service access, and field normalization. Routes and calculators never call
db.query()ordb.commit()directly. -
CimWizardPipelineExecutor -- Pure orchestration. It reads
configuration.jsonto know which calculator class to load for each feature, resolves dependencies between features, selects the right method (with fallback), and stores results back through the DataManager. -
BaseCalculator / Calculators -- Domain logic only. Each calculator inherits from BaseCalculator, which provides convenience wrappers for logging, validation, feature access, and a
save_property_batch()helper that delegates to DataManager. Calculators never import SQLAlchemy models or execute raw SQL. -
Endpoint routes -- Thin request/response handlers. Each route creates a DataManager (bound to the current request session), a PipelineExecutor, and orchestrates a calculation chain. All database interaction in the route goes through DataManager methods.
Endpoint function
|
+-- Creates DataManager(db_session)
+-- Creates PipelineExecutor(data_manager)
+-- Sets context (project_id, scenario_id, boundary)
+-- Defines calculation_chain = [{feature, method}, ...]
|
+-- For each step in chain:
| executor.execute_feature(feature_name, method_name)
| |
| +-- Loads calculator class from configuration.json
| +-- Checks dependencies via has_feature() / get_context()
| +-- Calls calculator.method()
| +-- Calculator stores result via data_manager.set_feature()
| +-- Calculator persists to DB via data_manager.upsert_*()
|
+-- Returns JSON response (no separate DB-save loop needed)
File: app/core/data_manager.py
Responsibilities:
- Context storage: project_id, scenario_id, service references
- Feature storage:
calculated_featuresdict holds all computed values - Configuration: Loads and caches
configuration.jsonat init - Feature access:
set_feature(),get_feature(),has_feature() - Config access:
get_feature_config(name),get_pipeline_config(name) - Scenario CRUD:
save_scenario(),get_scenario(),create_scenario_from_baseline(),delete_project_data() - Building CRUD:
save_building(),get_building(),get_buildings_geojson(), spatial queries - BuildingProperties CRUD:
upsert_building_properties_batch(),upsert_building_property_fields(),get_building_properties() - Raster queries:
check_raster_table(),query_raster_value() - Census boundary:
update_census_boundary()
Key methods:
| Method | Purpose |
|---|---|
set_context(**kwargs) |
Set project_id, scenario_id, etc. |
set_feature(name, value) |
Store a calculated feature result |
get_feature(name) |
Retrieve a calculated feature |
has_feature(name) |
Check if a feature has been calculated |
get_feature_config(name) |
Get calculator config for a feature from configuration.json |
save_scenario(...) |
Upsert a ProjectScenario from GeoJSON geometry |
save_building(...) |
Upsert a Building row from a GeoJSON Feature |
upsert_building_properties_batch(...) |
Bulk upsert a single property column across many buildings |
upsert_building_property_fields(...) |
Upsert arbitrary fields on a single BuildingProperties row |
get_buildings_geojson(...) |
Return GeoJSON FeatureCollection with baseline/delta merge |
delete_project_data(...) |
Cascading delete (building, scenario, or project) |
check_raster_table(name) |
Check if a raster table exists and has data |
query_raster_value(table, lon, lat) |
Get raster cell value at a point |
File: app/core/pipeline_executor.py
Responsibilities:
- Calculator loading: Dynamically imports calculator classes using
class_pathandclass_namefrom configuration.json. Caches instances incalculator_cache. - Dependency resolution:
check_dependencies()verifies all inputs are available before running a calculator._topological_sort()can order features by dependency graph. - Method selection with fallback: When
execute_feature()is called without an explicit method, it tries each method defined in configuration.json in priority order until one succeeds. - Pipeline execution:
execute_pipeline(),execute_predefined_pipeline(),execute_explicit_pipeline()for batch execution.
Key methods:
| Method | Purpose |
|---|---|
execute_feature(name, method) |
Run one calculator. If method is specified, only try that. Otherwise try methods in priority order. |
check_dependencies(deps) |
Returns True if all dependency features are available |
get_calculator_instance(name) |
Load and cache a calculator class from configuration.json |
execute_pipeline(features) |
Run multiple features with automatic dependency resolution |
execute_predefined_pipeline(name) |
Run a named pipeline from configuration.json predefined_pipelines |
Fallback mechanism: In configuration.json, each feature can define multiple methods with priorities:
"building_population": {
"methods": [
{"priority": 1, "method_name": "calculate_from_volume_distribution",
"input_dependencies": ["building_volume", "census_population"]},
{"priority": 2, "method_name": "by_census_osm",
"input_dependencies": ["scenario_census_boundary", "building_type", "building_volume"]}
]
}If priority-1 method fails (e.g., missing census_population), the executor falls back to priority-2. This only works when execute_feature() is called without an explicit method name.
Directory: app/calculators/
Every calculator inherits from BaseCalculator (app/calculators/base_calculator.py), which provides:
- Pipeline executor and data manager references
- Logging helpers:
log_info(),log_error(),log_warning(),log_success(),log_failure() - Feature helpers:
get_feature(),set_feature() - Validation helpers:
validate_input(),validate_dict(),validate_geometry(),validate_numeric() - Properties:
project_id,scenario_id - DB persistence:
save_property_batch()-- delegates to DataManager
Each calculator:
- Inherits from
BaseCalculatorand callssuper().__init__(pipeline_executor) - Reads dependencies via
self.get_feature(name)orself.pipeline.get_feature_safely(name) - Performs domain-specific computation
- Stores result via
self.set_feature(name, value) - Persists to DB via
self.data_manager.upsert_building_properties_batch(...)orself.save_property_batch(...) - Returns the result (or None on failure)
Calculators never import SQLAlchemy models or execute raw queries. All database interaction is delegated to DataManager.
Example:
from app.calculators.base_calculator import BaseCalculator
class BuildingVolumeCalculator(BaseCalculator):
def __init__(self, pipeline_executor):
super().__init__(pipeline_executor)
def calculate_from_height_and_area(self):
heights = self.get_feature('building_height')
areas = self.get_feature('building_area')
# ... compute volumes ...
self.set_feature('building_volume', result)
self.save_property_batch(buildings, 'volume', volumes)
return resultFile: app/core/configuration.json
This file is the registry for all calculators. It defines:
- features: Each feature's calculator class, methods, dependencies, and constraints
- predefined_pipelines: Named sequences of features for common tasks
- global_settings: Timeouts, retries, caching, validation flags
The PipelineExecutor reads this at runtime to know how to load calculators and what dependencies to check. Adding a new calculator means adding an entry here and placing the Python class in app/calculators/.
Three route files define different pipeline entry points:
| Route file | Endpoint | Steps | Purpose |
|---|---|---|---|
building_analysis_route.py |
POST /api/v1/building/execute_building_analysis |
16 | Full pipeline: physical properties + demographics + classification + LoD 1.2. Used by frontend. |
complete_chain_route.py |
POST /api/v1/complete/execute_complete_chain |
16 | Deprecated. Older implementation with known bugs (non-UUID IDs, no DB save). Use building_analysis instead. |
pipeline_routes.py |
POST /api/v1/pipeline/execute |
Dynamic | Generic pipeline executor. Client specifies which features to run. |
Each route creates its own DataManager + PipelineExecutor and defines its own calculation chain. The chain is a list of {feature_name, method_name} dicts iterated sequentially.
There are 22 calculators registered in configuration.json. Each one targets specific columns in the database:
| # | Feature Name | Calculator Class | Method(s) | DB Column(s) Populated |
|---|---|---|---|---|
| 1 | scenario_geo |
ScenarioGeoCalculator | calculate_from_scenario_geo, calculate_from_buildings_geo |
project_boundary, project_center on cim_wizard_project_scenario |
| 2 | scenario_census_boundary |
ScenarioCensusBoundaryCalculator | calculate_from_census_api |
census_boundary on cim_wizard_project_scenario |
| 3 | building_geo |
BuildingGeoCalculator | calculate_from_scenario_census_geo, calculate_from_building_geo |
Rows in cim_wizard_building (building_id, geometry, source) |
| 4 | building_props |
BuildingPropsCalculator | init |
Initializes rows in cim_wizard_building_properties (skeleton records) |
| 5 | building_height |
BuildingHeightCalculator | calculate_from_raster_tiles |
height on cim_wizard_building_properties |
| 6 | building_area |
BuildingAreaCalculator | calculate_from_geometry |
area on cim_wizard_building_properties |
| 7 | filter_res |
BuildingResidentialFilterCalculator | calculate_filter_res |
filter_res on cim_wizard_building_properties |
| 8 | building_volume |
BuildingVolumeCalculator | calculate_from_height_and_area |
volume on cim_wizard_building_properties |
| 9 | building_n_floors |
BuildingNFloorsCalculator | estimate_by_height |
number_of_floors on cim_wizard_building_properties |
| 10 | census_population |
CensusPopulationCalculator | calculate_from_census_boundary |
(intermediate -- total population from census sections) |
| 11 | building_type |
BuildingTypeCalculator | by_census_osm |
type on cim_wizard_building_properties (TABULA classification) |
| 12 | building_population |
BuildingPopulationCalculator | calculate_from_volume_distribution, by_census_osm |
n_people on cim_wizard_building_properties |
| 13 | building_n_families |
BuildingNFamiliesCalculator | calculate_from_population, by_census_osm |
n_family on cim_wizard_building_properties |
| 14 | building_construction_year |
BuildingConstructionYearCalculator | by_census_osm |
const_year, const_period_census, const_tabula on cim_wizard_building_properties |
| 15 | building_demographic |
BuildingDemographicCalculator | by_census_osm |
Orchestrates n_people + n_family from census integration |
| 16 | building_geo_lod12 |
BuildingGeoLod12Calculator | by_footprint_height |
building_surfaces_lod12 on cim_wizard_building (3D geometry) |
| 17 | envelope_efficiency |
EnvelopeEfficiencyCalculator | assign_random |
envelope_efficiency on cim_wizard_building_properties |
| 18 | fmu_assign |
FmuAssignCalculator | frassinetto |
fmu_file on cim_wizard_building_properties |
| 19 | building_z_value |
BuildingZValueCalculator | calculate_from_dtm |
z_value on cim_wizard_building |
| 20 | building_name |
BuildingNameCalculator | assign_sequential |
building_name on cim_wizard_building (BUI-0001 format) |
| 21 | grid_generator |
GridGeneratorCalculator | assign_grid |
grid_id on cim_wizard_project_scenario |
| 22 | pv_generator |
PvGeneratorCalculator | assign_pv_to_buildings |
pv_ids on cim_wizard_building |
Additionally, CitydbMapperCalculator provides map_scenario_to_citydb (writes to citydb schema) and generate_cityjson (reads from cim_vector and returns CityJSON v1.1).
Endpoint: POST /api/v1/building/execute_building_analysis
This is the main pipeline used by the frontend and Postman. It runs 22 calculators sequentially and populates every DB column:
Step Feature Method DB Column / Target
---- ------------------------- ------------------------------- -------------------------
1 scenario_geo calculate_from_scenario_geo project_boundary, project_center
2 scenario_census_boundary calculate_from_census_api census_boundary
3 building_geo calculate_from_scenario_census_geo cim_wizard_building rows
4 building_props init building_properties (skeleton)
5 building_height calculate_from_raster_tiles height
6 building_area calculate_from_geometry area
7 filter_res calculate_filter_res filter_res (residential filter)
8 building_volume calculate_from_height_and_area volume
9 building_n_floors estimate_by_height number_of_floors
10 census_population calculate_from_census_boundary (intermediate: total pop)
11 building_type by_census_osm type (TABULA classification)
12 building_population calculate_from_volume_distribution n_people
13 building_n_families calculate_from_population n_family
14 building_construction_year by_census_osm const_year, const_period_census, const_tabula
15 building_demographic by_census_osm (orchestrates n_people + n_family)
16 building_geo_lod12 by_footprint_height building_surfaces_lod12
17 envelope_efficiency assign_random envelope_efficiency
18 fmu_assign frassinetto fmu_file
19 building_z_value calculate_from_dtm z_value (avg DTM under footprint)
20 building_name assign_sequential building_name (BUI-0001, ...)
Steps 21 (grid_generator) and 22 (pv_generator) are not part of the automatic pipeline; they are triggered via their own POST endpoints.
Endpoint: POST /api/v1/complete/execute_complete_chain
This is an older implementation in complete_chain_route.py. It has known issues (non-UUID IDs, wrong method names, no DB saving) and should not be used. Use the building analysis endpoint above instead.
The database image (cim-database/Dockerfile) is built as a multi-stage Docker image combining:
- PostgreSQL 15 with PostGIS 3.4 (base spatial database)
- TimescaleDB (time-series hypertables in the
outputsschema) - 3DCityDB v4 (CityGML schema in the
citydb/citydb_pkgschemas) - Energy ADE and Utility Network ADE (3DCityDB extensions)
Current database schemas as implemented. See paper/generate_data_list_xlsx.py and paper/data-list.md for the target ECDT data inventory.
+-----------------------------------------------+
| cim_vector.cim_wizard_project_scenario |
|-----------------------------------------------|
| PK project_id VARCHAR(100) |
| PK scenario_id VARCHAR(100) |
| project_name VARCHAR(255) |
| scenario_name VARCHAR(255) |
| project_boundary GEOMETRY(POLYGON,4326) |
| project_center GEOMETRY(POINT,4326) |
| project_zoom INTEGER [default: 15] |
| project_crs INTEGER [default: 4326] |
| census_boundary GEOMETRY(MULTIPOLY,4326) |
| FK grid_id UUID --> cim_network.network_scenarios.grid_id
| created_at TIMESTAMP WITH TZ |
| updated_at TIMESTAMP WITH TZ |
+--------------+--------------------------------+
| project_id + scenario_id (logical FK)
v
+-----------------------------------------------+
| cim_vector.cim_wizard_building_properties |
|-----------------------------------------------|
| PK scenario_id VARCHAR(100) |
| PK building_id VARCHAR(100) ----------+ |
| PK lod INTEGER [default: 0] | |
| project_id VARCHAR(100) | |
| height FLOAT | |
| area FLOAT | |
| volume FLOAT | |
| number_of_floors FLOAT | |
| type VARCHAR(50) | |
| envelope_efficiency VARCHAR(20) | |
| fmu_file VARCHAR(255) | |
| const_period_census VARCHAR(10) | |
| const_year INTEGER | |
| const_tabula VARCHAR(15) | |
| n_people INTEGER | |
| n_family INTEGER | |
| created_at TIMESTAMP WITH TZ | |
| updated_at TIMESTAMP WITH TZ | |
+-------------------------------------------+ |
| building_id (logical FK)
v
+-----------------------------------------------+
| cim_vector.cim_wizard_building |
|-----------------------------------------------|
| PK building_id VARCHAR(100) |
| lod INTEGER [default: 0] |
| building_geometry GEOMETRY(GEOM,4326) |
| building_geometry_source VARCHAR(50) |
| census_id BIGINT |
| z_value DOUBLE PRECISION |
| building_name VARCHAR(100) |
| pv_ids UUID[] (GIN indexed) |
| building_surfaces_lod12 JSON |
| created_at TIMESTAMP WITH TZ |
| updated_at TIMESTAMP WITH TZ |
+-----------------------------------------------+
+-----------------------------------------------+
| cim_vector.pv |
|-----------------------------------------------|
| PK pv_id UUID |
| FK building_id UUID --> cim_wizard_building.building_id
| fid BIGINT |
| slope FLOAT |
| num FLOAT |
| area_reale FLOAT |
| number INTEGER |
| s FLOAT |
| index_righ BIGINT |
| id_pod FLOAT |
| pv_geometry GEOMETRY(MULTIPOLY,4326) |
+-----------------------------------------------+
+-----------------------------------------------+
| cim_census.censusgeo |
|-----------------------------------------------|
| PK id SERIAL |
| UQ SEZ2011 BIGINT |
| geometry GEOMETRY(MULTIPOLYGON,4326) |
| P1..P66 (population columns) |
| E1..E31 (building age columns) |
| ST1..ST15 (housing stock columns) |
+-----------------------------------------------+
cim_network schema:
network_scenarios (PK: grid_id UUID)
scenario_buses (grid_id FK, bus_id)
scenario_lines (grid_id FK, line_id)
network_buses (PK: bus_id, geometry POINT)
network_lines (PK: line_id, geometry LINESTRING)
cim_raster schema (height/z calculators use dtm, dsm):
dtm, dsm PostGIS raster (DEM/DSM)
dtm_raster, Alternative storage (RasterService)
dsm_raster
building_height_cache Cached DSM−DTM heights per building
The outputs schema stores simulation time-series data as TimescaleDB hypertables, partitioned on time_step (BIGINT, discrete step index).
| Table | Purpose | Partition key |
|---|---|---|
outputs.simulation_run |
Metadata per run (step_size_ms, simulation_start) | -- |
outputs.building_frassinetto3 |
Building thermal outputs (t_building, heating_load_target) | time_step |
outputs.battery |
Battery outputs (i, v, soc, p_net_batt) | time_step |
outputs.heating_frassinetto_hp2 |
Heat pump outputs (en_el, cop, cr, ...) | time_step |
Each hypertable row references cim_vector.cim_wizard_building_properties via a composite FK on (scenario_id, building_id, lod).
The citydb schema is the standard 3DCityDB v4 schema for CityGML data. It is created by the official create-db.sql script at first container startup (see cim-database/init-db/00_3dcitydb_setup.sh). SRID is set to 4326 (WGS84).
Key tables used by CIM Wizard:
| Table | Purpose | SQLAlchemy Model |
|---|---|---|
citydb.citymodel |
CityGML CityModel (one per scenario) | CityModel |
citydb.cityobject |
Root for all features (buildings, surfaces) | CityObject |
citydb.cityobject_member |
CityModel-to-CityObject link | CityObjectMember |
citydb.building |
CityGML Building (shares id with cityobject) | CityBuilding |
citydb.thematic_surface |
Wall/Roof/Ground surfaces (objectclass 33/34/35) | ThematicSurface |
citydb.surface_geometry |
Hierarchical geometry storage (PolygonZ) | SurfaceGeometry |
citydb.cityobject_genericattrib |
Key-value attributes (U-values, thermal zone data) | CityObjectGenericAttrib |
The citydb_pkg schema contains PL/pgSQL functions for 3DCityDB maintenance.
SQLAlchemy models are in app/models/citydb.py. These models map to pre-existing tables; they are not created by Base.metadata.create_all().
Target schema for Energy Community Digital Twin, derived from paper/data-list.md and paper/generate_data_list_xlsx.py. Not yet implemented; serves as a roadmap.
| Domain | Target tables / concepts | Data source |
|---|---|---|
| Geography | building_footprint, roof_geometry, thermal_zones, openings | cim_vector (extend), CityGML LoD2/3 |
| Grid | substation, feeder_topology, line_geometry, metering_points | cim_network (extend), CIM |
| Envelope | u_values (wall, roof, floor), glazing, infiltration | building_properties (extend), TABULA |
| Systems | hvac, dhw, lighting, appliances | New: cim_systems |
| Consumption | electricity, gas, heat (agg + disaggregated) | outputs (extend), smart meter, simulators |
| Occupant | occupancy_schedule, window_opening, thermostat_override | New: cim_occupant, occupant models |
| Generation | pv (extend), wind, biomass | cim_vector.pv (extend), registry |
| Storage & grid | battery, ev, import_export, tariffs | New: cim_storage, cim_grid_interaction |
| Weather | temperature, irradiance, wind | New: cim_weather (or external TMY) |
| Measurements | P, Q, V, meter_health, pseudo_measurements | New: cim_measurements (TimescaleDB) |
| Metadata | building_id, meter_id, provenance | Extend identifiers, add provenance |
Synthesis note: Consumption and occupant behavior data can be synthesized by simulators and models (e.g. EnergyPlus, COESI, occupant behavior models) when real measurements are unavailable — see generation logic in paper/generate_data_list_xlsx.py.
| Method | Route | Purpose |
|---|---|---|
| GET | /api/v1/vector/projects |
List all projects (with pagination) |
| GET | /api/v1/vector/pscenarios/{project_id} |
Get scenarios for a project |
| GET | /api/v1/vector/project_scenario_details/{project_id}/{scenario_id} |
Get details for a specific project scenario |
| GET | /api/v1/vector/get_buildings_geojson/{project_id}/{scenario_id} |
Get buildings as GeoJSON FeatureCollection (includes building_properties, z_value, building_name, pv_data if available, grid info if available) |
| GET | /api/v1/vector/buildingproperties/{project_id}/{scenario_id} |
Query building properties for a scenario (supports building_id, lod, pagination) |
| PUT | /api/v1/vector/projects/{project_id}/scenarios/{scenario_id}/buildingproperties |
Flexible update/nullify building properties (single, batch, or bulk). See detailed docs below. |
| GET | /api/v1/vector/schema |
Get normalization schema (all entities) |
| GET | /api/v1/vector/schema/{entity_name} |
Get normalization schema for one entity |
| GET | /api/v1/building/lod12_geojson?project_id=...&scenario_id=... |
Get LOD 1.2 surfaces as 3D GeoJSON FeatureCollection |
| POST | /api/v1/building/execute_building_analysis |
Create project + run full 22-step pipeline (physical + demographic + 3D) |
| POST | /api/v1/complete/execute_complete_chain |
Deprecated -- use building_analysis endpoint instead |
| DELETE | /api/v1/vector/delete |
Flexible delete (building, scenario, or entire project) |
| Method | Route | Purpose |
|---|---|---|
| POST | /api/v1/building/assign_grid |
Assign a grid_id to a project scenario. Body: { "project_id": "...", "scenario_id": "...", "grid_id": "..." } |
| GET | /api/v1/building/grid/{project_id}/{scenario_id} |
Get grid data (lines + buses) for a scenario that has a grid_id assigned |
| PUT | /api/v1/vector/projects/{project_id}/scenarios/{scenario_id}/clear-grid |
Clear (set to NULL) the grid_id from a project scenario |
| Method | Route | Purpose |
|---|---|---|
| POST | /api/v1/building/assign_pv |
Spatial-join PV polygons to buildings in a scenario. Updates cim_vector.pv.building_id and cim_wizard_building.pv_ids. Body: { "project_id": "...", "scenario_id": "..." } |
| GET | /api/v1/building/pv_buildings/{project_id}/{scenario_id} |
Get all PV polygons for buildings in the scenario as GeoJSON FeatureCollection, enriched with building_height and z_value |
PV data is also included in the get_buildings_geojson response when a building has pv_ids assigned.
There are two sets of CityJSON/CityDB endpoints:
A) CIM Wizard source (reads from cim_vector tables)
| Method | Route | Purpose |
|---|---|---|
| POST | /api/v1/building/map_to_citydb |
Map all buildings in a scenario to 3DCityDB tables (citymodel, cityobject, building, thematic_surface, surface_geometry, generic attributes). Idempotent. Body: { "project_id": "...", "scenario_id": "..." } |
| GET | /api/v1/building/cityjson/{project_id}/{scenario_id} |
Return CityJSON v1.1 built from CIM Wizard tables. Does NOT require /map_to_citydb. |
B) 3DCityDB source (reads from citydb schema, requires /map_to_citydb first)
All endpoints below use citymodel_id which equals the scenario_id used during POST /map_to_citydb.
| Method | Route | Purpose |
|---|---|---|
| GET | /api/v1/citydb/cityjson/{citymodel_id} |
CityJSON v1.1 built from 3DCityDB tables (citydb schema) |
| GET | /api/v1/citydb/{citymodel_id} |
CityModel metadata: envelope, name, member-count breakdown |
| GET | /api/v1/citydb/{citymodel_id}/cityobjects/{cityobject_id} |
Any CityObject by numeric ID |
| GET | /api/v1/citydb/{citymodel_id}/buildings |
List all buildings in the CityModel with generic attributes |
| GET | /api/v1/citydb/{citymodel_id}/buildings/{building_id} |
Building detail: physical properties, energy system, thermal zone, surfaces |
| GET | /api/v1/citydb/{citymodel_id}/buildings/{building_id}/surfaces |
List thematic surfaces (wall, roof, ground) with U-values |
| GET | /api/v1/citydb/{citymodel_id}/buildings/{building_id}/surfaces/{surface_id} |
Surface detail with GeoJSON geometry and generic attributes |
CityJSON mapping:
| CIM Wizard concept | CityJSON / 3DCityDB representation |
|---|---|
| project-scenario | CityModel (gmlid = scenario_id) |
| building | Building CityObject (gmlid = building_id) |
| LOD 1.2 wall/roof/ground | Semantic surfaces in Solid geometry with TABULA U-values |
| thermal zone (1 per building) | +Energy-ThermalZone child (id = building_id + "-z1") |
| envelope_efficiency, fmu_file | ThermalZone attributes (envelopeEfficiency, energySystemModel) |
TABULA U-values (W/m2K) applied per construction period:
| Period | Years | Wall | Roof | Ground |
|---|---|---|---|---|
| TABULA_1 | pre-1900 | 1.70 | 2.20 | 1.60 |
| TABULA_2 | 1901-1920 | 1.60 | 2.00 | 1.50 |
| TABULA_3 | 1921-1945 | 1.48 | 1.80 | 1.40 |
| TABULA_4 | 1946-1960 | 1.30 | 1.60 | 1.20 |
| TABULA_5 | 1961-1975 | 1.10 | 1.20 | 1.00 |
| TABULA_6 | 1976-1990 | 0.80 | 0.80 | 0.80 |
| TABULA_7 | 1991-2005+ | 0.50 | 0.40 | 0.50 |
| Method | Route | Purpose |
|---|---|---|
| POST | /api/v1/pipeline/execute |
Execute pipeline with feature list |
| POST | /api/v1/pipeline/execute_explicit |
Execute with explicit feature.method plan |
| POST | /api/v1/pipeline/execute_predefined |
Execute a named pipeline from configuration.json |
| POST | /api/v1/pipeline/calculate_feature |
Calculate a single feature |
| GET | /api/v1/pipeline/configuration |
Get full configuration.json |
| GET | /api/v1/pipeline/available_features |
List available calculated features |
| GET | /api/v1/pipeline/predefined_pipelines |
List predefined pipelines |
See below for details.
POST /api/v1/building/execute_building_analysis
Content-Type: application/json
{
"project_boundary": {
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [[[lng1, lat1], [lng2, lat2], ..., [lng1, lat1]]]
},
"properties": {}
},
"project_name": "My Project",
"scenario_name": "baseline",
"save_to_db": true
}
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
project_boundary |
GeoJSON Feature | Yes | -- | Polygon boundary in EPSG:4326 |
project_name |
string | No | "Building_Analysis" |
Human-readable project name |
scenario_name |
string or null | No | "baseline" |
If null, defaults to "baseline" with scenario_id = project_id |
save_to_db |
boolean | No | true |
Whether to persist results to database |
A single flexible endpoint that deletes a building, a scenario, or an entire project depending on which query parameters are provided. The project_id parameter is always required. Adding scenario_id narrows the scope to one scenario, and further adding building_id narrows it to a single building.
DELETE /api/v1/vector/delete?project_id=<id>[&scenario_id=<id>][&building_id=<id>]
| Parameter | Type | Required | Description |
|---|---|---|---|
project_id |
string | Yes | The UUID of the project |
scenario_id |
string | No | The UUID of the scenario within the project |
building_id |
string | No | The UUID of a specific building (requires scenario_id) |
| project_id | scenario_id | building_id | Action |
|---|---|---|---|
| provided | provided | provided | Delete one building -- removes its row from building_properties; also removes the geometry from building if no other scenario references that building |
| provided | provided | -- | Delete one scenario -- removes all building_properties for that scenario, orphaned building geometries, and the project_scenario record |
| provided | -- | -- | Delete entire project -- removes all building_properties, orphaned building geometries, and all project_scenario records for every scenario in the project |
Delete a single building:
DELETE /api/v1/vector/delete?project_id=abc-123&scenario_id=def-456&building_id=ghi-789
Delete a scenario and all its buildings:
DELETE /api/v1/vector/delete?project_id=abc-123&scenario_id=def-456
Delete an entire project:
DELETE /api/v1/vector/delete?project_id=abc-123
All three cases return a JSON object summarizing what was removed:
{
"action": "delete_building | delete_scenario | delete_project",
"project_id": "abc-123",
"scenario_id": "def-456",
"building_id": "ghi-789",
"building_properties_deleted": 1,
"buildings_deleted": 1,
"project_scenarios_deleted": 0
}For delete_project, the response also includes a scenarios_affected array listing all scenario IDs that were removed.
- A
buildinggeometry row is only deleted when no other scenario still references thatbuilding_idinbuilding_properties. This prevents data loss when multiple scenarios share the same physical building. - If a
project_idhas no matchingproject_scenariorecords, the endpoint returns404 Not Found. - On any unexpected error the transaction is rolled back and a
500 Internal Server Erroris returned with a description.
A single flexible endpoint that supports three modes for modifying building property fields.
PUT /api/v1/vector/projects/{project_id}/scenarios/{scenario_id}/buildingproperties
Content-Type: application/json
height, area, volume, number_of_floors, type, const_period_census, const_year, const_tabula, n_people, n_family, envelope_efficiency, fmu_file
Send a flat object with building_id and the fields to update. Setting a value to null clears it in the database.
{
"building_id": "000d3106-3e9f-47b2-8ec6-fbe20edb4a8a",
"height": 14.2,
"n_people": null,
"envelope_efficiency": "high"
}Response:
{
"upserted": 1,
"scenario_id": "def-456"
}Wrap modifications in a modifications array. Each entry requires a building_id.
{
"modifications": [
{ "building_id": "000d3106-...", "height": 14.2, "type": "SFH" },
{ "building_id": "11223344-...", "height": null, "n_people": 3 },
{ "building_id": "55667788-...", "envelope_efficiency": null }
]
}Response:
{
"upserted": 3,
"scenario_id": "def-456"
}Omit building_id and provide a fields object. Every building in the scenario is updated with the given values.
{
"fields": {
"envelope_efficiency": "medium",
"fmu_file": null
}
}Response:
{
"action": "bulk_update",
"project_id": "abc-123",
"scenario_id": "def-456",
"fields_updated": ["envelope_efficiency", "fmu_file"],
"buildings_affected": 54
}Remove the grid association from a project scenario.
PUT /api/v1/vector/projects/{project_id}/scenarios/{scenario_id}/clear-grid
No request body is needed. Response:
{
"success": true,
"project_id": "abc-123",
"scenario_id": "def-456",
"grid_cleared": true
}grid_cleared is true if a grid_id was present and removed, false if the scenario had no grid_id.
Return all PV polygons for buildings in a project scenario as a GeoJSON FeatureCollection, enriched with building height and terrain elevation.
GET /api/v1/building/pv_buildings/{project_id}/{scenario_id}?lod=0
Response:
{
"type": "FeatureCollection",
"project_id": "53985b6a-...",
"scenario_id": "53985b6a-...",
"total_pv": 312,
"features": [
{
"type": "Feature",
"geometry": { "type": "MultiPolygon", "coordinates": [...] },
"properties": {
"pv_id": "8ba5569f-...",
"building_id": "000d3106-...",
"fid": 29116,
"slope": 33.39,
"area_reale": 8.08,
"number": 4,
"s": 6.9,
"building_height": 12.5,
"z_value": 287.3
}
}
]
}The CityDB integration follows a two-step workflow:
Step 1: Map CIM Wizard data to 3DCityDB
POST /api/v1/building/map_to_citydb
Content-Type: application/json
{ "project_id": "53985b6a-...", "scenario_id": "53985b6a-..." }
Response:
{
"citymodel_id": 1,
"scenario_id": "53985b6a-...",
"mapped_buildings": 54,
"total_buildings": 54
}This creates a CityModel in the citydb schema with gmlid = scenario_id. Each building becomes a CityObject with thematic surfaces, geometry, and generic attributes (TABULA U-values, thermal zone data).
Step 2: Query the CityModel via the hierarchical REST API
The citymodel_id in all GET endpoints below is the scenario_id.
Get the CityModel metadata:
GET /api/v1/citydb/53985b6a-...
{
"id": 1,
"gmlid": "53985b6a-...",
"name": "CIM-Scenario-53985b6a",
"members": { "total": 216, "Building": 54, "WallSurface": 108, "RoofSurface": 27, "GroundSurface": 27 }
}List all buildings:
GET /api/v1/citydb/53985b6a-.../buildings
{
"citymodel_id": "53985b6a-...",
"total_buildings": 54,
"buildings": [
{
"gmlid": "000d3106-...",
"name": "BUI-0001",
"measured_height": 12.5,
"storeys_above_ground": 4,
"envelope_efficiency": "medium",
"thermal_zone_volume_m3": 3750.0,
"surface_count": 6
}
]
}Get a single building with all detail:
GET /api/v1/citydb/53985b6a-.../buildings/000d3106-...
{
"gmlid": "000d3106-...",
"name": "BUI-0001",
"building": {
"measured_height": 12.5,
"measured_height_unit": "m",
"storeys_above_ground": 4
},
"energy_system": {
"envelope_efficiency": "medium",
"fmu_file": "frassinetto3"
},
"thermal_zone": {
"volume_m3": 3750.0,
"floor_area_m2": 300.0,
"number_of_floors": 4
},
"surfaces": [
{ "gmlid": "1-wall-N", "surface_type": "WallSurface", "u_value_w_m2k": 1.30 },
{ "gmlid": "1-roof", "surface_type": "RoofSurface", "u_value_w_m2k": 1.60 },
{ "gmlid": "1-ground", "surface_type": "GroundSurface", "u_value_w_m2k": 1.20 }
]
}Get a surface with geometry:
GET /api/v1/citydb/53985b6a-.../buildings/000d3106-.../surfaces/1-wall-N
{
"gmlid": "1-wall-N",
"surface_type": "WallSurface",
"u_value_w_m2k": 1.30,
"geometries": [
{
"gmlid": "poly-2",
"geometry": { "type": "Polygon", "coordinates": [...] }
}
]
}Get CityJSON v1.1 from the 3DCityDB schema:
GET /api/v1/citydb/cityjson/53985b6a-...
This returns a full CityJSON v1.1 document built from the citydb tables, including Building CityObjects with LOD 1.2 Solid geometry, semantic surfaces with TABULA U-values, and +Energy-ThermalZone child objects.
Alternatively, get CityJSON directly from CIM Wizard tables (no map_to_citydb step needed):
GET /api/v1/building/cityjson/53985b6a-.../53985b6a-...
Both endpoints return the same CityJSON v1.1 structure. The citydb variant reads from the mapped 3DCityDB tables; the building variant reads from CIM Wizard tables directly.
Different clients send the same data using different field names:
- Frontend (React):
projectName,scenarioName,n_families - Postman users:
project_name,Project_Name - GIS tools / Italian datasets:
altezza_vo,superficie,epoca_cost
Without centralized normalization, every client must independently figure out the correct field names, leading to drift and silent data loss.
app/core/
+-- normalization_config.json <-- JSON config: canonical names + all known aliases
+-- normalizer.py <-- Python module: normalize, validate, schema export
On input (POST/PUT): The normalizer maps any known alias to the canonical name before processing. A client can send projectName or proj_name or ProjectName -- they all resolve to project_name.
On output (GET): The backend always returns canonical names (matching DB columns). Clients can query the /schema endpoint to discover what those names are.
Any client can fetch the full normalization schema:
GET /api/v1/vector/schema -- Full schema (all entities)
GET /api/v1/vector/schema/building_properties -- Single entity schema
| Entity | DB Table | Fields | Description |
|---|---|---|---|
project_scenario |
cim_vector.cim_wizard_project_scenario |
11 | Project/scenario metadata + boundaries |
building |
cim_vector.cim_wizard_building |
8 | Building geometry records |
building_properties |
cim_vector.cim_wizard_building_properties |
15 | Physical + demographic properties per scenario |
Edit normalization_config.json:
"height": {
"aliases": ["Height", "altezza_vo", "building_height", "h",
"NEW_ALIAS_HERE"]
}No Python code changes needed. Restart the server or call reload_config().
| # | Issue | Location | Impact | Status |
|---|---|---|---|---|
| 1 | complete_chain_route.py is deprecated |
complete_chain_route.py |
Non-UUID IDs, no DB saving, wrong method names | Use building_analysis_route.py which now runs all 22 steps |
| # | Issue | Location | Impact |
|---|---|---|---|
| 2 | No explicit foreign keys in most CIM Wizard tables | cim-database schema | No referential integrity enforcement (by design -- maintained by app logic) |
| 3 | Frontend normalizer field mismatches | normalizers.ts |
Frontend should call GET /schema to get canonical names |
| 4 | Energy ADE / Utility Network ADE may not install cleanly | cim-database/init-db/00_3dcitydb_setup.sh |
The gioagu ADE scripts are designed for 3DCityDB v3/v4 and may produce warnings on the edge image. Core 3DCityDB schema works correctly. |
- Docker and Docker Compose
- Python 3.10+ with conda or pip
- Node.js 18+
chmod +x run-docker.sh
./run-docker.sh up # Start database and backend
./run-docker.sh down # Stop bothcd cim-database
docker compose -f docker-compose.cimdb.yml up -d
# Verify: docker logs cim-integrateddb --tail 20cd cim_wizard_integrated_2026
conda env create -f environment.yml
conda activate cim_wizard
uvicorn main:app --host 0.0.0.0 --port 8000 --reload
# API docs at: http://localhost:8000/docscd coesi-frontend-main_2026
npm install
npm run dev
# Frontend at: http://localhost:5173Frontend (.env in coesi-frontend-main_2026/):
VITE_CIM_WIZARD_BASE=http://localhost:8000
Backend (environment or .env):
DATABASE_URL=postgresql://cim_wizard_user:cim_wizard_password@localhost:15432/cim_wizard_integrated
| Service | Host | Port | Credentials |
|---|---|---|---|
| PostgreSQL/PostGIS + TimescaleDB + 3DCityDB | localhost | 15432 | cim_wizard_user / cim_wizard_password |
| FastAPI Backend | localhost | 8000 | -- |
| React Frontend | localhost | 5173 | -- |
The production server is at 130.192.238.11. The deployment script deploy-to-server.sh uses rsync + sshpass to push local code.
# 1. Deploy code
./deploy-to-server.sh
# 2. On the server
ssh eclabuser@130.192.238.11
cd ~/cim
./run-docker.sh down
# 3. Rebuild DB (destroys data -- only for fresh setup)
cd cim-database
docker compose -f docker-compose.cimdb.yml build --no-cache
docker compose -f docker-compose.cimdb.yml up -d
# 4. Restart backend
cd ~/cim
./run-docker.sh backend-upWhen only schema changes or code updates are needed and existing data must be preserved:
# 1. Deploy code
./deploy-to-server.sh
# 2. On the server: restart backend only (no DB rebuild)
ssh eclabuser@130.192.238.11
cd ~/cim
./run-docker.sh backend-down
./run-docker.sh backend-up
# 3. Apply SQL migrations if needed (additive, IF NOT EXISTS)
docker exec -i cim-integrateddb psql -U cim_wizard_user -d cim_wizard_integrated \
< cim-database/migrations/add_outputs_schema.sql
docker exec -i cim-integrateddb psql -U cim_wizard_user -d cim_wizard_integrated \
< cim-database/migrations/add_grid_id_building_cols_pv_table.sql
# 4. Install 3DCityDB on existing database (if not already done)
docker exec -i cim-integrateddb bash -c '
psql -v ON_ERROR_STOP=1 -U cim_wizard_user -d cim_wizard_integrated \
-f /3dcitydb/create-db.sql \
-v srid=4326 \
-v srs_name="urn:ogc:def:crs:EPSG::4326" \
-v changelog=no
'To load PV GeoJSON data into cim_vector.pv:
conda activate webgis
cd pv/scripts
python load_pv_geojson.py # local DB
python load_pv_geojson.py --server # server DB (130.192.238.11)