Getting Started

This guide walks you through installing the KPI Calculator, running your first calculation, and understanding the results.

Installation

Install from PyPI:

pip install kpi-calculator

Basic Usage

The simplest way to calculate KPIs is from an ESDL file:

from kpicalculator import calculate_kpis

results = calculate_kpis(esdl_file="path/to/model.esdl")
print(f"Total CAPEX: {results['financials']['capex']['All']} EUR")
print(f"LCOE: {results['financials']['lcoe']} EUR/MWh")

By default, the analysis uses a 30-year system lifetime with a 5% discount rate. Assets default to a 40-year technical lifetime when not specified in the ESDL. Both lifetime and discount rate can be overridden via KpiManager.calculate_all_kpis():

from kpicalculator import KpiManager

manager = KpiManager()
manager.load_from_esdl("model.esdl")
results = manager.calculate_all_kpis(
    system_lifetime=25,   # Overrides the default (30 years)
    discount_rate=3,      # Overrides the default (5 percent)
)

calculate_kpis() exposes system_lifetime but not discount_rate; use KpiManager directly when you need to override the discount rate.

Note

When comparing output against the MESIDO optimizer, pass round_up_replacement=False to calculate_all_kpis(). Use the default (True) for all other purposes. See the calculate_all_kpis docstring for details.

Providing Time Series

Without time series data, energy values (consumption, production, demand) are returned as zero. There is no rated-capacity fallback — you need to supply actual time series for meaningful energy and emission KPIs.

The package supports three time series sources (DataFrames, InfluxDB profiles, and XML files), but the default API exposes two. InfluxDB is available only when using the adapter layer directly; see the Architecture documentation for details.

XML files — pass a path to an XML time series file:

results = calculate_kpis(
    esdl_file="model.esdl",
    time_series="timeseries.xml"
)

pandas DataFrames — pass a dictionary of DataFrames keyed by asset ID. Each DataFrame column represents a time series field; the column name must match a field name recognised by the KPI calculators (ThermalConsumption, ThermalProduction, ThermalDemand, Consumption, Production, Demand, ElectricalConsumption, Energy, or heat_demand). The simulator-specific column heat_supplied is also recognised on producer assets. Unrecognised column names are stored but will not contribute to any KPI — a warning is logged in that case:

import pandas as pd

timeseries_data = {
    "asset_id_1": pd.DataFrame({
        "ThermalConsumption": [100000, 102000, ...],
    }, index=pd.date_range("2024-01-01", periods=24, freq="h")),
}

results = calculate_kpis(
    esdl_file="model.esdl",
    timeseries_dataframes=timeseries_data,
)

For implementation details on the composite key mapping, see the Architecture documentation.

From ESDL String Content

When the ESDL is already in memory (for example, received from an API or generated by another tool), use KpiManager directly instead of writing to a file:

from kpicalculator import KpiManager

manager = KpiManager()
manager.load_from_esdl_string(esdl_string)
results = manager.calculate_all_kpis()

Command Line

The calculator also works from the command line:

kpicalculator model.esdl
kpicalculator model.esdl --time-series data.xml --system-lifetime 25

Exporting Results to ESDL

Export calculated KPIs back into ESDL as DistributionKPI elements for visualization in the ESDL MapEditor.

Export to file:

from kpicalculator import KpiManager

manager = KpiManager()
manager.load_from_esdl("model.esdl", time_series_file="data.xml")
results = manager.calculate_all_kpis()

manager.export_to_esdl(results, output_file="model_with_kpis.esdl")

Get ESDL object (no file):

esdl_with_kpis = manager.build_esdl_with_kpis(results)

Get ESDL string (preferred for in-memory workflows):

When working with ESDL strings end-to-end (for example, in simulator-worker), use build_esdl_string_with_kpis to embed KPIs into an existing ESDL string and get an updated string back. This avoids temporary files and does not require manipulating internal adapter state:

# manager and results from a previous load_from_esdl_string() call (see above)
esdl_string_with_kpis = manager.build_esdl_string_with_kpis(esdl_string, results)

Note

Both export patterns are validated by test_string_loaded_esdl_export in unit_test/test_examples.py.

All three export methods accept an optional level parameter ('system', 'area', or 'asset'). Currently all levels write system-wide KPIs to the main area — area-level and asset-level placement are not yet implemented.

Export works with both file-loaded and string-loaded ESDL — no temporary files needed for in-memory workflows.

From OMOTES Simulator Results

When integrating with the OMOTES simulator-worker, use load_from_simulator to load the simulator’s port-indexed DataFrame together with the input ESDL string. The adapter resolves port IDs to assets and extracts cost data in one step. Use build_esdl_string_with_kpis to embed the results into the output ESDL string:

from kpicalculator import KpiManager

manager = KpiManager()
# input_esdl: the ESDL string submitted to the simulator (used to resolve port IDs and cost data)
# output_esdl: the ESDL string returned by the simulator (KPIs will be embedded into this)
manager.load_from_simulator(simulator_result_df, esdl_string=input_esdl)
results = manager.calculate_all_kpis(system_lifetime=30)
output_esdl_with_kpis = manager.build_esdl_string_with_kpis(output_esdl, results)

The simulator produces columns named heat_demand and heat_supplied. These are recognised directly by the energy and emission calculators — no column renaming is needed.

Results Structure

calculate_kpis() returns a dictionary with four top-level keys:

{
    "financials": {
        "capex": {"All": 1000000, "Production": 500000, "Transport": 300000, ...},
        "opex": {"All": 50000, "Production": 30000, ...},
        "npv": 850000,
        "lcoe": 45.5,
        "eac": 62000,
        "tco": 1400000,
    },
    "energy": {
        "consumption": 1e10,   # Joules
        "production": 1.1e10,
        "demand": 9.5e9,
        "efficiency": 0.91,
    },
    "emissions": {
        "total": 1200,         # tonnes CO2e/year
        "per_mwh": 178,        # kg CO2e/MWh
    },
    "asset_financials": {
        "<asset_id>": { ... },  # per-asset breakdown — see KPI Guide
        ...
    },
}
  • Financials: Top-level category for all monetary KPIs. This includes cost breakdowns such as CAPEX and OPEX by asset category (Production, Transport, Storage, Conversion, Consumption, and All), as well as derived financial indicators (NPV, LCOE, EAC, TCO). All values are sums over assets.

  • Energy: System-wide totals in Joules. Efficiency is the ratio of consumption to production (0 to 1).

  • Emissions: Total CO2-equivalent emissions in tonnes and emissions intensity in kg CO2e per MWh of energy consumed.

  • Asset financials: Per-asset breakdown of all financial KPIs, keyed by asset ID. For field definitions and interpretation, see Per-asset Financial KPIs in the KPI Guide.

For a detailed explanation of what each KPI means and how to interpret it, see KPI Guide.

Time Series Behavior

The calculator supports three time series sources. The full priority order (when all sources are enabled) is:

  1. pandas DataFrames — passed via timeseries_dataframes

  2. InfluxDB profiles — referenced in the ESDL as InfluxDBProfile elements

  3. XML files — passed via the time_series parameter

The first source that returns data wins; the rest are not tried. If a source fails with an error, the calculator logs a warning and moves to the next.

However, the default API (calculate_kpis()) sets use_database_profiles=False internally, which removes InfluxDB from the priority list entirely — it is not merely skipped, it is excluded. This reduces the effective order to:

  1. pandas DataFrames

  2. XML files

If both a DataFrame and an XML file are provided, the DataFrame source is tried first. If it returns data, XML is not tried.

Without time series from a working source, energy calculators return zero for consumption, production, and demand. There is no rated-capacity fallback (the power × 8760 estimation found in some energy system tools is not implemented here). Providing time series is required for meaningful energy and emission KPIs.

For the full implementation details — including the dynamic priority-building code and how to re-enable InfluxDB — see the Architecture documentation.