Getting Started
===============
This guide walks you through installing the KPI Calculator, running your first calculation, and understanding the results.
.. |test_examples| raw:: html
unit_test/test_examples.py
Installation
------------
Install from PyPI:
.. code-block:: bash
pip install kpi-calculator
Basic Usage
-----------
The simplest way to calculate KPIs is from an ESDL file:
.. code-block:: python
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")
.. note::
Validated by `test_quick_start `_ in |test_examples|.
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()``:
.. code-block:: python
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::
Validated by `test_basic_usage_with_parameters `_ in |test_examples|.
.. 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 :doc:`dev_documentation/architecture` documentation for details.
**XML files** — pass a path to an XML time series file:
.. code-block:: python
results = calculate_kpis(
esdl_file="model.esdl",
time_series="timeseries.xml"
)
.. note::
Validated by `test_basic_usage_testing_override `_ in |test_examples|.
**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:
.. code-block:: python
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,
)
.. note::
Validated by `test_timeseries_dataframes_produce_valid_kpi_results `_ in |test_examples|.
For implementation details on the composite key mapping, see the :doc:`dev_documentation/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:
.. code-block:: python
from kpicalculator import KpiManager
manager = KpiManager()
manager.load_from_esdl_string(esdl_string)
results = manager.calculate_all_kpis()
.. note::
Validated by `test_string_loaded_esdl_export `_ in |test_examples|.
Command Line
------------
The calculator also works from the command line:
.. code-block:: bash
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:**
.. code-block:: python
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):**
.. code-block:: python
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:
.. code-block:: python
# 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 |test_examples|.
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:
.. code-block:: python
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:
Results Structure
-----------------
``calculate_kpis()`` returns a dictionary with four top-level keys:
.. code-block:: python
{
"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": {
"": { ... }, # 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 :ref:`per-asset-financial-kpis` in the KPI Guide.
For a detailed explanation of what each KPI means and how to interpret it, see :doc:`user_documentation/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 :doc:`dev_documentation/architecture` documentation.