API Reference¶
This page documents the public API of the KPI Calculator. For usage examples, see Getting Started. For architecture details, see Architecture.
Public API¶
KPI Calculator package for energy systems.
The main entry points are calculate_kpis(), calculate_kpis_from_simulator(),
and KpiManager. To embed results back into an ESDL string, use
build_esdl_string_with_kpis(). All calculate_kpis_from_* functions return a
KpiResults dict. The following types are exported for use in type annotations:
KpiResults— top-level results dictFinancialResults—results["financials"]EnergyResults—results["energy"]EmissionResults—results["emissions"]AssetFinancialResult—results["asset_financials"]["<asset_id>"]
Default parameter values for callers that need to resolve absent config:
DEFAULT_SYSTEM_LIFETIME_YEARS— 30.0DEFAULT_DISCOUNT_RATE_PERCENT— 5.0
- class kpicalculator.Asset(id, name, asset_type, power=0.0, length=0.0, volume=0.0, cop=0.0, investment_cost=0.0, investment_cost_unit='EUR', installation_cost=0.0, installation_cost_unit='EUR', fixed_operational_cost=0.0, fixed_operational_cost_unit='EUR/yr', variable_operational_cost=0.0, variable_operational_cost_unit='EUR/MWh', fixed_maintenance_cost=0.0, fixed_maintenance_cost_unit='EUR/yr', variable_maintenance_cost=0.0, variable_maintenance_cost_unit='EUR/MWh', technical_lifetime=40.0, discount_rate=None, emission_factor=0.0, aggregation_count=1, time_series=<factory>)[source]
Bases:
object- aggregation_count: int = 1
- asset_type: AssetType
- cop: float = 0.0
- emission_factor: float = 0.0
- fixed_maintenance_cost: float = 0.0
- fixed_maintenance_cost_unit: str = 'EUR/yr'
- fixed_operational_cost: float = 0.0
- fixed_operational_cost_unit: str = 'EUR/yr'
- id: str
- installation_cost: float = 0.0
- installation_cost_unit: str = 'EUR'
- investment_cost: float = 0.0
- investment_cost_unit: str = 'EUR'
- length: float = 0.0
- name: str
- power: float = 0.0
- technical_lifetime: float = 40.0
- time_series: dict[str, TimeSeries]
- variable_maintenance_cost: float = 0.0
- variable_maintenance_cost_unit: str = 'EUR/MWh'
- variable_operational_cost: float = 0.0
- variable_operational_cost_unit: str = 'EUR/MWh'
- volume: float = 0.0
- class kpicalculator.AssetFinancialResult[source]
Bases:
TypedDictPer-asset financial KPIs returned in
KpiResults["asset_financials"].System totals in
KpiResults["financials"]are derived by summing the corresponding field across all assets (exceptlcoe— see below).Per-asset discount rate: all discounted KPIs (
annualized_capex,eac,npv, andlcoe) use the discount rate fromcostInformation.discountRatein the ESDL when present; otherwise the system-leveldiscount_rateparameter is used as a fallback.Geothermal COP adjustment: variable operational and maintenance costs in EUR/kWh or EUR/MWh are applied to
energy / COPfor geothermal assets with COP > 0, reflecting that they deliver more heat than they consume as input.- annualized_capex: float
CAPEX spread over the asset’s technical lifetime via the annuity formula, in EUR/year. At discount_rate = 0% this reduces to (investment + installation) / technical_lifetime.
- eac: float
annualized_capex + total annual OPEX, in EUR/year.
- Type:
Equivalent Annual Cost
- fixed_maintenance_cost: float
Annual fixed maintenance cost in EUR/year.
- fixed_operational_cost: float
Annual fixed operational cost in EUR/year.
- installation_cost: float
Installation cost in EUR.
- investment_cost: float
Upfront capital cost in EUR.
- lcoe: float | None
Levelized Cost of Energy in EUR/MWh for this asset, or
None.Nonewhen:the asset is not a generating type (consumers, transport, storage, conversion), or
the asset is a generating type but its annual energy production is zero or unknown (no time series data supplied).
Nonemeans not applicable or not computable — exporters should omit the field or write a format-appropriate null. The system LCOE is computed separately astotal_npv / total_discounted_energyand is not the average of per-asset LCOEs.
- npv: float
Discounted lifecycle cost of this asset in EUR.
- tco: float
Undiscounted total spend on this asset over the system lifetime, in EUR.
- variable_maintenance_cost: float
Annual variable maintenance cost in EUR/year (scales with energy use).
- variable_operational_cost: float
Annual variable operational cost in EUR/year (scales with energy use).
- class kpicalculator.AssetType(value)[source]
Bases:
Enum- CONSUMER = 'Consumer'
- CONVERSION = 'Conversion'
- GEOTHERMAL = 'GeothermalSource'
- PIPE = 'Pipe'
- PRODUCER = 'Producer'
- PUMP = 'Pump'
- STORAGE = 'Storage'
- TRANSPORT = 'Transport'
- exception kpicalculator.CalculationError[source]
Bases:
KpiCalculatorErrorRaised when KPI calculation fails.
- class kpicalculator.ConfigFileCredentialManager(config_path=None)[source]
Bases:
CredentialManagerLoad credentials from secure configuration files.
- get_database_credentials(host, port)[source]
Get credentials from config file.
- exception kpicalculator.CredentialError[source]
Bases:
SecurityErrorRaised when credential loading or validation fails.
- exception kpicalculator.DataSourceError[source]
Bases:
KpiCalculatorErrorRaised when data source loading fails.
- exception kpicalculator.DatabaseError[source]
Bases:
DataSourceErrorRaised when database operations fail.
- class kpicalculator.EmissionResults[source]
Bases:
TypedDictSystem-level emission KPI results.
- per_mwh: float
Emission intensity in kg CO2e/MWh of energy consumed.
- total: float
Total greenhouse gas emissions in tonnes CO2e/year.
- class kpicalculator.EnergyResults[source]
Bases:
TypedDictSystem-level energy KPI results. All values in Joules.
- consumption: float
Total thermal energy consumed by all consumer assets in J.
- demand: float
Total thermal energy demand from all consumer assets in J.
- efficiency: float
consumption / production (0-1). Zero when production is zero.
- Type:
Distribution efficiency
- production: float
Total thermal energy produced by all producer assets in J.
- class kpicalculator.EnergySystem(name, assets, unit_conversion=<factory>, source_metadata=<factory>, esdl_energy_system=None)[source]
Bases:
object- esdl_energy_system: EnergySystem | None = None
- name: str
- class kpicalculator.FinancialResults[source]
Bases:
TypedDictSystem-level financial KPI results.
All monetary values are in EUR or EUR/year. Each field is the sum of the corresponding per-asset value across all assets in the system (except
lcoe, which is computed assum(per-asset NPVs) / total_discounted_energyand is therefore not the average of per-asset LCOEs).capexandopexare broken down by asset category:"Production","Consumption","Storage","Transport","Conversion", and"All"(the system-wide sum).- eac: float
Equivalent Annual Cost — sum of per-asset annualized costs in EUR/year.
- lcoe: float
Levelized Cost of Energy in EUR/MWh (
system_npv / discounted_energy).system_npvis the sum of per-asset NPVs, each discounted at the asset’s own rate (from ESDLcostInformation.discountRate, falling back to the system default). It is not the average of per-asset LCOEs.
- npv: float
Net Present Value — discounted lifecycle cost in EUR.
- tco: float
Total Cost of Ownership — undiscounted lifecycle cost in EUR.
- exception kpicalculator.KpiCalculatorError[source]
Bases:
ExceptionBase exception for KPI Calculator.
- class kpicalculator.KpiManager[source]
Bases:
objectMain class for managing KPI calculations across different data sources.
Cost unit conversion factors (EUR/kW, EUR/MW, EUR/km, EUR/kWh, EUR/MWh, % OF CAPEX, etc.) are built-in and used by the cost calculator when computing KPI values from ESDL costInformation elements.
- build_esdl_string_with_kpis(esdl_string, results, level='system')[source]
Embed KPI results into an ESDL XML string and return the updated string.
This is the preferred integration method for systems that work with ESDL strings (e.g. simulator-worker). It operates entirely on a local
EnergySystemHandlerparsed fromesdl_stringand does not modify any manager state, making it safe to call repeatedly on the same instance.- Parameters:
esdl_string (
str) – Input ESDL XML string to embed KPIs into.results (
KpiResults) – KPI calculation results from calculate_all_kpis()level (
str) – KPI granularity —'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.
- Return type:
- Returns:
ESDL XML string with KPIs embedded.
- Raises:
ValueError – If esdl_string is empty or invalid parameters are provided.
- build_esdl_with_kpis(results, level='system')[source]
Build an ESDL energy system data structure with KPI results embedded.
- Parameters:
results (
KpiResults) – KPI calculation results from calculate_all_kpis()level (
str) – KPI granularity —'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.
- Returns:
ESDL data structure with KPIs
- Return type:
esdl.EnergySystem
- Raises:
ValueError – If no energy system is loaded or invalid parameters
- calculate_all_kpis(system_lifetime=30.0, discount_rate=5.0, round_up_replacement=True)[source]
Calculate all KPIs for the energy system.
- Raises:
ValueError – If no energy system is loaded,
system_lifetime <= 0, ordiscount_rateis outside [0, 100].- Parameters:
system_lifetime (
float) – System lifetime in years. Must be positive. Default:DEFAULT_SYSTEM_LIFETIME_YEARS.discount_rate (
float) – System-wide fallback discount rate in percentage (e.g. 5 for 5%). Must be in [0, 100]. Individual assets may override this viacostInformation.discountRatein the ESDL — this method respects those overrides because it usesget_asset_financial_breakdown()internally. Note that callingFinancialCalculator.calculate_npv()directly does not respect per-asset overrides. Default:DEFAULT_DISCOUNT_RATE_PERCENT.round_up_replacement (
bool) – If True (default), NPV, LCOE, and TCO useceilfor the asset replacement count — the financially exact calculation. If False, uses the continuous factormax(1, system_lifetime / technical_lifetime)for compatibility with MESIDO optimizer output. Only set this to False when comparing results against MESIDO.
- Return type:
- Returns:
KpiResultsdict withfinancials,energy,emissions, andasset_financialskeys.
- energy_system: EnergySystem | None
- export_to_esdl(results, output_file=None, level='system')[source]
Export KPI results to ESDL format.
- Parameters:
results (
KpiResults) – KPI calculation results from calculate_all_kpis()output_file (
str|None) – Output ESDL file path. If None, returns data structure.level (
str) – KPI granularity —'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.
- Returns:
True if file export succeeded (when output_file provided) esdl.EnergySystem: ESDL data structure (when output_file is None)
- Return type:
- Raises:
ValueError – If no energy system is loaded or invalid parameters
- load_from_esdl(esdl_file, time_series_file=None, timeseries_dataframes=None)[source]
Load energy system data from ESDL file.
Cost data is extracted from ESDL costInformation elements.
Note
InfluxDB profile loading is disabled here. To enable it, call
EsdlAdapter().load_data(..., use_database_profiles=True)directly.- Parameters:
esdl_file (
str) – Path to ESDL filetime_series_file (
str|None) – Optional path to time series file (when timeseries_dataframes not provided)timeseries_dataframes (
dict[str,DataFrame] |None) – Optional dict mapping asset IDs to pandas DataFrames with time-indexed energy/power data. When provided, takes precedence over database loading and time_series_file.
- Return type:
- load_from_esdl_string(esdl_string, timeseries_dataframes=None)[source]
Load energy system data from ESDL XML string content.
This method allows loading ESDL data directly from a string without needing a temporary file. Useful for integration with systems that provide ESDL content in memory (e.g., simulator_worker).
Cost data is extracted from ESDL costInformation elements.
- load_from_mesido(_mesido_data)[source]
Load energy system data from mesido data structure.
- load_from_simulator(simulator_result, esdl_string)[source]
Load energy system data from OMOTES Simulator results.
Converts the simulator’s port-indexed DataFrame to the asset-indexed common model and extracts cost data from the supplied ESDL string.
- class kpicalculator.KpiResults[source]
Bases:
TypedDictComplete KPI results returned by
KpiManager.calculate_all_kpis()and the top-levelkpicalculator.calculate_kpis()function.Four top-level keys:
financials: system-level monetary KPIs (CAPEX, OPEX, NPV, LCOE, EAC, TCO)energy: system-level energy totals in Joulesemissions: system-level CO2e emissionsasset_financials: per-asset financial breakdown keyed by asset ID; system totals infinancialsare derived by summing these values
- asset_financials: dict[str, AssetFinancialResult]
- emissions: EmissionResults
- energy: EnergyResults
- financials: FinancialResults
- class kpicalculator.SecureCredentialManager[source]
Bases:
CredentialManagerSecure credential management using environment variables.
- get_database_credentials(host, port)[source]
Load credentials from environment variables.
This method first attempts to load credentials using environment variables with the format KPI_DB_{HOST}_{PORT}_{FIELD}, where HOST is uppercased and dots/hyphens are replaced with underscores. If either the username or password is missing, it falls back to using INFLUXDB_* environment variables for compatibility with simulator-worker setups. The priority order is: 1. KPI_DB_{HOST}_{PORT}_{FIELD} (primary) 2. INFLUXDB_* (fallback if primary is incomplete)
- exception kpicalculator.SecurityError[source]
Bases:
KpiCalculatorErrorRaised when security validation fails.
- exception kpicalculator.ValidationError[source]
Bases:
KpiCalculatorErrorRaised when input validation fails.
- kpicalculator.build_esdl_string_with_kpis(esdl_string, kpi_results)[source]
Embed KPI results into an ESDL XML string and return the updated string.
Data-source-agnostic: works with results from any
calculate_kpis_from_*function. The KPI values are written asDistributionKPIelements on the main area of the energy system, ready for MapEditor visualisation.This is the standalone equivalent of
KpiManager.build_esdl_string_with_kpis()— use this when you do not hold aKpiManagerinstance.- Parameters:
esdl_string (
str) – ESDL XML string to embed KPIs into.kpi_results (
KpiResults) – KPI results from anycalculate_kpis_from_*call.
- Return type:
- Returns:
Updated ESDL XML string with KPIs embedded.
- Raises:
KpiCalculatorError – If embedding fails.
- kpicalculator.calculate_kpis(esdl_file, time_series=None, timeseries_dataframes=None, system_lifetime=30.0, discount_rate=5.0, round_up_replacement=True)[source]
Calculate KPIs from an ESDL file with optional time series data.
Cost data is extracted from ESDL
costInformationelements. Cost unit conversion factors (EUR/kW, EUR/MW, etc.) are built-in; seekpicalculator.common.constants.COST_UNIT_FACTORSfor the full list.- Parameters:
time_series (
str|Path|None) – Optional path to time series XML (whentimeseries_dataframesnot provided).timeseries_dataframes (
dict[str,DataFrame] |None) – Optional dict mapping asset IDs to pandas DataFrames with time-indexed energy/power data. Takes precedence overtime_serieswhen provided.system_lifetime (
float) – System lifetime in years. Default:DEFAULT_SYSTEM_LIFETIME_YEARS.discount_rate (
float) – System-wide fallback discount rate in percent. Default:DEFAULT_DISCOUNT_RATE_PERCENT.round_up_replacement (
bool) – If True (default), NPV, LCOE, and TCO useceilfor the asset replacement count (financially exact). If False, uses the continuous factormax(1, n / technical_lifetime)for optimizer compatibility.
- Return type:
- Returns:
KpiResultscontaining calculated KPIs.- Raises:
KpiCalculatorError – For any calculation or validation errors.
- kpicalculator.calculate_kpis_from_simulator(simulator_result, esdl_string, system_lifetime=30.0, discount_rate=5.0, round_up_replacement=True)[source]
Calculate KPIs from OMOTES simulator results.
- Parameters:
simulator_result (
DataFrame) – DataFrame produced by the simulator, with aDatetimeIndexand(port_id, property_name)tuple columns.esdl_string (
str) – The input ESDL as an XML string, used to resolve port IDs to their owning assets and to extract cost data.system_lifetime (
float) – System lifetime in years. Default:DEFAULT_SYSTEM_LIFETIME_YEARS.discount_rate (
float) – System-wide fallback discount rate in percent. Default:DEFAULT_DISCOUNT_RATE_PERCENT.round_up_replacement (
bool) – If True (default), NPV, LCOE, and TCO useceilfor the asset replacement count (financially exact). If False, uses the continuous factormax(1, n / technical_lifetime)for optimizer compatibility.
- Return type:
- Returns:
KpiResultscontaining calculated KPIs.- Raises:
KpiCalculatorError – For any calculation or validation errors.
- kpicalculator.calculate_kpis(esdl_file, time_series=None, timeseries_dataframes=None, system_lifetime=30.0, discount_rate=5.0, round_up_replacement=True)[source]¶
Calculate KPIs from an ESDL file with optional time series data.
Cost data is extracted from ESDL
costInformationelements. Cost unit conversion factors (EUR/kW, EUR/MW, etc.) are built-in; seekpicalculator.common.constants.COST_UNIT_FACTORSfor the full list.- Parameters:
time_series (
str|Path|None) – Optional path to time series XML (whentimeseries_dataframesnot provided).timeseries_dataframes (
dict[str,DataFrame] |None) – Optional dict mapping asset IDs to pandas DataFrames with time-indexed energy/power data. Takes precedence overtime_serieswhen provided.system_lifetime (
float) – System lifetime in years. Default:DEFAULT_SYSTEM_LIFETIME_YEARS.discount_rate (
float) – System-wide fallback discount rate in percent. Default:DEFAULT_DISCOUNT_RATE_PERCENT.round_up_replacement (
bool) – If True (default), NPV, LCOE, and TCO useceilfor the asset replacement count (financially exact). If False, uses the continuous factormax(1, n / technical_lifetime)for optimizer compatibility.
- Return type:
- Returns:
KpiResultscontaining calculated KPIs.- Raises:
KpiCalculatorError – For any calculation or validation errors.
- class kpicalculator.KpiManager[source]¶
Bases:
objectMain class for managing KPI calculations across different data sources.
Cost unit conversion factors (EUR/kW, EUR/MW, EUR/km, EUR/kWh, EUR/MWh, % OF CAPEX, etc.) are built-in and used by the cost calculator when computing KPI values from ESDL costInformation elements.
- build_esdl_string_with_kpis(esdl_string, results, level='system')[source]¶
Embed KPI results into an ESDL XML string and return the updated string.
This is the preferred integration method for systems that work with ESDL strings (e.g. simulator-worker). It operates entirely on a local
EnergySystemHandlerparsed fromesdl_stringand does not modify any manager state, making it safe to call repeatedly on the same instance.- Parameters:
esdl_string (
str) – Input ESDL XML string to embed KPIs into.results (
KpiResults) – KPI calculation results from calculate_all_kpis()level (
str) – KPI granularity —'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.
- Return type:
- Returns:
ESDL XML string with KPIs embedded.
- Raises:
ValueError – If esdl_string is empty or invalid parameters are provided.
- build_esdl_with_kpis(results, level='system')[source]¶
Build an ESDL energy system data structure with KPI results embedded.
- Parameters:
results (
KpiResults) – KPI calculation results from calculate_all_kpis()level (
str) – KPI granularity —'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.
- Returns:
ESDL data structure with KPIs
- Return type:
esdl.EnergySystem
- Raises:
ValueError – If no energy system is loaded or invalid parameters
- calculate_all_kpis(system_lifetime=30.0, discount_rate=5.0, round_up_replacement=True)[source]¶
Calculate all KPIs for the energy system.
- Raises:
ValueError – If no energy system is loaded,
system_lifetime <= 0, ordiscount_rateis outside [0, 100].- Parameters:
system_lifetime (
float) – System lifetime in years. Must be positive. Default:DEFAULT_SYSTEM_LIFETIME_YEARS.discount_rate (
float) – System-wide fallback discount rate in percentage (e.g. 5 for 5%). Must be in [0, 100]. Individual assets may override this viacostInformation.discountRatein the ESDL — this method respects those overrides because it usesget_asset_financial_breakdown()internally. Note that callingFinancialCalculator.calculate_npv()directly does not respect per-asset overrides. Default:DEFAULT_DISCOUNT_RATE_PERCENT.round_up_replacement (
bool) – If True (default), NPV, LCOE, and TCO useceilfor the asset replacement count — the financially exact calculation. If False, uses the continuous factormax(1, system_lifetime / technical_lifetime)for compatibility with MESIDO optimizer output. Only set this to False when comparing results against MESIDO.
- Return type:
- Returns:
KpiResultsdict withfinancials,energy,emissions, andasset_financialskeys.
- energy_system: EnergySystem | None¶
- export_to_esdl(results, output_file=None, level='system')[source]¶
Export KPI results to ESDL format.
- Parameters:
results (
KpiResults) – KPI calculation results from calculate_all_kpis()output_file (
str|None) – Output ESDL file path. If None, returns data structure.level (
str) – KPI granularity —'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.
- Returns:
True if file export succeeded (when output_file provided) esdl.EnergySystem: ESDL data structure (when output_file is None)
- Return type:
- Raises:
ValueError – If no energy system is loaded or invalid parameters
- load_from_esdl(esdl_file, time_series_file=None, timeseries_dataframes=None)[source]¶
Load energy system data from ESDL file.
Cost data is extracted from ESDL costInformation elements.
Note
InfluxDB profile loading is disabled here. To enable it, call
EsdlAdapter().load_data(..., use_database_profiles=True)directly.- Parameters:
esdl_file (
str) – Path to ESDL filetime_series_file (
str|None) – Optional path to time series file (when timeseries_dataframes not provided)timeseries_dataframes (
dict[str,DataFrame] |None) – Optional dict mapping asset IDs to pandas DataFrames with time-indexed energy/power data. When provided, takes precedence over database loading and time_series_file.
- Return type:
- load_from_esdl_string(esdl_string, timeseries_dataframes=None)[source]¶
Load energy system data from ESDL XML string content.
This method allows loading ESDL data directly from a string without needing a temporary file. Useful for integration with systems that provide ESDL content in memory (e.g., simulator_worker).
Cost data is extracted from ESDL costInformation elements.
Result Types¶
- class kpicalculator.KpiResults[source]¶
Bases:
TypedDictComplete KPI results returned by
KpiManager.calculate_all_kpis()and the top-levelkpicalculator.calculate_kpis()function.Four top-level keys:
financials: system-level monetary KPIs (CAPEX, OPEX, NPV, LCOE, EAC, TCO)energy: system-level energy totals in Joulesemissions: system-level CO2e emissionsasset_financials: per-asset financial breakdown keyed by asset ID; system totals infinancialsare derived by summing these values
- asset_financials: dict[str, AssetFinancialResult]¶
- emissions: EmissionResults¶
- energy: EnergyResults¶
- financials: FinancialResults¶
- class kpicalculator.FinancialResults[source]¶
Bases:
TypedDictSystem-level financial KPI results.
All monetary values are in EUR or EUR/year. Each field is the sum of the corresponding per-asset value across all assets in the system (except
lcoe, which is computed assum(per-asset NPVs) / total_discounted_energyand is therefore not the average of per-asset LCOEs).capexandopexare broken down by asset category:"Production","Consumption","Storage","Transport","Conversion", and"All"(the system-wide sum).
- class kpicalculator.EnergyResults[source]¶
Bases:
TypedDictSystem-level energy KPI results. All values in Joules.
- class kpicalculator.AssetFinancialResult[source]¶
Bases:
TypedDictPer-asset financial KPIs returned in
KpiResults["asset_financials"].System totals in
KpiResults["financials"]are derived by summing the corresponding field across all assets (exceptlcoe— see below).Per-asset discount rate: all discounted KPIs (
annualized_capex,eac,npv, andlcoe) use the discount rate fromcostInformation.discountRatein the ESDL when present; otherwise the system-leveldiscount_rateparameter is used as a fallback.Geothermal COP adjustment: variable operational and maintenance costs in EUR/kWh or EUR/MWh are applied to
energy / COPfor geothermal assets with COP > 0, reflecting that they deliver more heat than they consume as input.- annualized_capex: float¶
CAPEX spread over the asset’s technical lifetime via the annuity formula, in EUR/year. At discount_rate = 0% this reduces to (investment + installation) / technical_lifetime.
- lcoe: float | None¶
Levelized Cost of Energy in EUR/MWh for this asset, or
None.Nonewhen:the asset is not a generating type (consumers, transport, storage, conversion), or
the asset is a generating type but its annual energy production is zero or unknown (no time series data supplied).
Nonemeans not applicable or not computable — exporters should omit the field or write a format-appropriate null. The system LCOE is computed separately astotal_npv / total_discounted_energyand is not the average of per-asset LCOEs.
Calculators¶
- class kpicalculator.calculators.financial_calculator.AssetFinancialResult[source]¶
Bases:
TypedDictPer-asset financial KPIs returned in
KpiResults["asset_financials"].System totals in
KpiResults["financials"]are derived by summing the corresponding field across all assets (exceptlcoe— see below).Per-asset discount rate: all discounted KPIs (
annualized_capex,eac,npv, andlcoe) use the discount rate fromcostInformation.discountRatein the ESDL when present; otherwise the system-leveldiscount_rateparameter is used as a fallback.Geothermal COP adjustment: variable operational and maintenance costs in EUR/kWh or EUR/MWh are applied to
energy / COPfor geothermal assets with COP > 0, reflecting that they deliver more heat than they consume as input.- annualized_capex: float¶
CAPEX spread over the asset’s technical lifetime via the annuity formula, in EUR/year. At discount_rate = 0% this reduces to (investment + installation) / technical_lifetime.
- lcoe: float | None¶
Levelized Cost of Energy in EUR/MWh for this asset, or
None.Nonewhen:the asset is not a generating type (consumers, transport, storage, conversion), or
the asset is a generating type but its annual energy production is zero or unknown (no time series data supplied).
Nonemeans not applicable or not computable — exporters should omit the field or write a format-appropriate null. The system LCOE is computed separately astotal_npv / total_discounted_energyand is not the average of per-asset LCOEs.
- class kpicalculator.calculators.financial_calculator.FinancialCalculator(energy_system)[source]¶
Bases:
objectCalculator for financial KPIs (CAPEX, OPEX, NPV, LCOE, EAC, TCO).
- aggregate_by_category(asset_financials)[source]¶
Derive CAPEX and OPEX category breakdowns from a pre-computed asset breakdown.
- Parameters:
asset_financials (
dict[str,AssetFinancialResult]) – Dict mapping asset ID toAssetFinancialResult, as returned byget_asset_financial_breakdown().- Return type:
- Returns:
Tuple of (capex_by_category, opex_by_category), each a dict with keys
"Production","Consumption","Storage","Transport","Conversion", and"All".
- calculate_eac(discount_rate=5.0)[source]¶
Calculate Equivalent Annual Cost for the energy system.
Sums per-asset annualized costs using each asset’s own
technical_lifetimeanddiscount_rate(from ESDLcostInformation.discountRate, falling back to thediscount_rateparameter). The annuity formula spreads one asset purchase over its technical lifetime, implicitly assuming perpetual replacement — the annual charge is the same regardless of how many replacements occur within the system lifetime. OPEX is already annual and is passed through directly.This matches the approach used in the MESIDO optimizer (
calculate_annuity_factorinfinancial_mixin.py). Seekpi_guide.rstfor the formula and a discussion of the replacement assumption.- Raises:
CalculationError – If
discount_rateis outside [0, 100] or any asset has a non-positivetechnical_lifetime.- Parameters:
discount_rate (
float) – Fallback discount rate in percentage (e.g. 5 for 5%), used whenasset.discount_rate is None(i.e. nocostInformation.discountRatewas present in the ESDL for that asset).- Return type:
- Returns:
Equivalent Annual Cost in EUR/year.
- calculate_lcoe(system_lifetime, discount_rate=5.0, round_up_replacement=True, system_npv=None)[source]¶
Calculate Levelized Cost of Energy.
Divides NPV by discounted energy to put costs and energy on the same present-value basis. Energy discounting uses the same end-of-period convention and fractional-year proration as NPV OPEX. See
kpi_guide.rstfor the formula.Returns 0.0 if annual energy consumption is zero or negative.
When
system_npvis not provided, it is derived by summing per-asset NPVs fromget_asset_financial_breakdown(), which respects per-asset discount rates from ESDLcostInformation.discountRate. This differs fromcalculate_npv(), which applies a uniform rate to all assets.- Raises:
CalculationError – If
system_lifetime <= 0,discount_rateis outside [0, 100], or any asset has a non-positivetechnical_lifetime.- Parameters:
system_lifetime (
float) – System lifetime in years. May be fractional.discount_rate (
float) – System-wide fallback discount rate in percentage (e.g. 5 for 5%). Per-asset overrides from ESDL are applied automatically.round_up_replacement (
bool) – If True (default), useceilfor the replacement count. If False, use the continuous factor for optimizer compatibility.system_npv (
float|None) – Pre-computed system NPV in EUR. When provided, skips the internal asset iteration. Pass this fromKpiManager.calculate_all_kpis()to avoid a redundant computation.
- Return type:
- Returns:
Levelized Cost of Energy in EUR/MWh.
- calculate_npv(system_lifetime, discount_rate=5.0, round_up_replacement=True)[source]¶
Calculate Net Present Value for the energy system.
By default (
round_up_replacement=True) CAPEX uses a start-of-period convention: one discounted payment per replacement cycle, with the number of replacements equal toceil(system_lifetime / technical_lifetime). OPEX uses the standard end-of-period convention (t = 1 … n). A fractional final year is prorated linearly. Seekpi_guide.rstfor the full formula.Set
round_up_replacement=Falseto use the continuous replacement factormax(1, system_lifetime / technical_lifetime)as a scalar multiplier on a single undiscounted CAPEX — the approximation used by optimizers such as MESIDO.- Raises:
CalculationError – If
system_lifetime <= 0,discount_rateis outside [0, 100], or any asset has a non-positivetechnical_lifetime.
Note
This method applies a single uniform
discount_rateto all assets and does not respect per-asset discount rates from ESDLcostInformation.discountRate. It is not used in the main calculation path —KpiManager.calculate_all_kpis()derives system NPV by summing per-asset NPVs fromget_asset_financial_breakdown(), andcalculate_lcoe()does the same when nosystem_npvis supplied. Use this method only when a quick uniform-rate NPV estimate is needed without the full per-asset breakdown.- Parameters:
system_lifetime (
float) – System lifetime in years. May be fractional.discount_rate (
float) – Discount rate in percentage (e.g. 5 for 5%). Applied uniformly to all assets — per-asset overrides are not supported here.round_up_replacement (
bool) – If True (default), useceilfor the replacement count with per-replacement discounting. If False, use the continuous factormax(1, n / technical_lifetime)for optimizer compatibility.
- Return type:
- Returns:
Net Present Value in EUR.
- calculate_tco(system_lifetime, round_up_replacement=True)[source]¶
Calculate Total Cost of Ownership for the energy system.
Undiscounted sum of all costs over the system lifetime:
TCO = Sum over assets of: (investment + installation) * replacement_factor + annual_opex * system_lifetime
By default (
round_up_replacement=True) the replacement factor isceil(system_lifetime / technical_lifetime)— the financially exact count of full asset purchases needed to keep the system operational. This is consistent withcalculate_npv(), which uses the sameceillogic for CAPEX discounting, soTCO == NPVatdiscount_rate=0.Set
round_up_replacement=Falseto use the continuous factormax(1, system_lifetime / technical_lifetime)instead. Optimizers such as MESIDO use this approximation to keep the objective smooth and differentiable. Use this option only when comparing KPI output against optimizer results.Note: MESIDO’s
MinimizeTCOcovers variable and fixed operational costs only. This calculator also includes fixed and variable maintenance costs, so TCO values will differ from MESIDO when maintenance costs are non-zero.- Raises:
CalculationError – If
system_lifetime <= 0or any asset has a non-positivetechnical_lifetime.- Parameters:
- Return type:
- Returns:
Total Cost of Ownership in EUR.
- get_asset_financial_breakdown(system_lifetime, discount_rate=5.0, round_up_replacement=True, annual_energy_mwh_by_asset=None)[source]¶
Compute per-asset financial KPIs.
Returns a dict keyed by
asset.id. System totals for NPV, TCO, EAC are the sum of these values across all assets.lcoesemantics:None— asset is not a generating type (consumer, storage, transport, conversion), or is a generating asset whose annual energy production is zero or unknown.Nonemeans not applicable or not computable, regardless of the output format (ESDL, JSON, or any future schema). Exporters omit the field or write a format-appropriate null forNonevalues.float— EUR/MWh; only set for generating assets with non-zero energy output.
System LCOE must be computed separately as total NPV / total discounted energy output — it is not the sum of per-asset LCOEs (summing ratios with different denominators is mathematically incorrect).
- Raises:
CalculationError – If
system_lifetime <= 0,discount_rateis outside [0, 100], or any asset has a non-positivetechnical_lifetime.- Parameters:
system_lifetime (
float) – System lifetime in years.discount_rate (
float) – System-wide fallback discount rate in percentage (e.g. 5 for 5%). Individual assets override this whenasset.discount_rateis set (from ESDLcostInformation.discountRate).round_up_replacement (
bool) – If True (default), useceilfor the replacement count — the financially exact calculation. If False, uses the continuous factormax(1, system_lifetime / technical_lifetime)for MESIDO optimizer compatibility.annual_energy_mwh_by_asset (
dict[str,float] |None) – Optional mapping of asset ID to annual energy production in MWh. When provided,lcoeis computed for generating assets (those inPRODUCER_ASSET_TYPES) with non-zero energy. WhenNone(default),lcoeisNonefor all assets. Callers that hold an energy calculator should pre-compute this dict and pass it here.
- Return type:
- Returns:
Dict mapping asset ID to its
AssetFinancialResult.
- class kpicalculator.calculators.energy_calculator.EnergyCalculator(energy_system)[source]¶
Bases:
objectCalculator for energy-related KPIs.
- calculate_system_efficiency()[source]¶
Calculate overall system efficiency.
- Return type:
- Returns:
System efficiency as a ratio (0-1)
- get_asset_energy_production_per_year(asset)[source]¶
Calculate annual energy production for a single producing asset.
- get_total_energy_consumption_per_year()[source]¶
Calculate total energy consumption per year.
- Return type:
- Returns:
Total energy consumption in joules per year
- class kpicalculator.calculators.emission_calculator.EmissionCalculator(energy_system)[source]¶
Bases:
objectCalculator for emission-related KPIs.
- get_emissions_per_energy_unit()[source]¶
Calculate CO2 emissions per GJ of energy consumed.
- Return type:
- Returns:
CO2 emissions in kg/GJ
Warning
This method is not yet wired into KpiManager.calculate_all_kpis(). Results are not included in the standard KPI output. Call get_emissions_per_mwh() for the equivalent metric that is part of the public API.
Common Model¶
- class kpicalculator.adapters.common_model.Asset(id, name, asset_type, power=0.0, length=0.0, volume=0.0, cop=0.0, investment_cost=0.0, investment_cost_unit='EUR', installation_cost=0.0, installation_cost_unit='EUR', fixed_operational_cost=0.0, fixed_operational_cost_unit='EUR/yr', variable_operational_cost=0.0, variable_operational_cost_unit='EUR/MWh', fixed_maintenance_cost=0.0, fixed_maintenance_cost_unit='EUR/yr', variable_maintenance_cost=0.0, variable_maintenance_cost_unit='EUR/MWh', technical_lifetime=40.0, discount_rate=None, emission_factor=0.0, aggregation_count=1, time_series=<factory>)[source]
Bases:
object- aggregation_count: int = 1
- asset_type: AssetType
- cop: float = 0.0
- emission_factor: float = 0.0
- fixed_maintenance_cost: float = 0.0
- fixed_maintenance_cost_unit: str = 'EUR/yr'
- fixed_operational_cost: float = 0.0
- fixed_operational_cost_unit: str = 'EUR/yr'
- id: str
- installation_cost: float = 0.0
- installation_cost_unit: str = 'EUR'
- investment_cost: float = 0.0
- investment_cost_unit: str = 'EUR'
- length: float = 0.0
- name: str
- power: float = 0.0
- technical_lifetime: float = 40.0
- time_series: dict[str, TimeSeries]
- variable_maintenance_cost: float = 0.0
- variable_maintenance_cost_unit: str = 'EUR/MWh'
- variable_operational_cost: float = 0.0
- variable_operational_cost_unit: str = 'EUR/MWh'
- volume: float = 0.0
- class kpicalculator.adapters.common_model.AssetType(value)[source]
Bases:
Enum- CONSUMER = 'Consumer'
- CONVERSION = 'Conversion'
- GEOTHERMAL = 'GeothermalSource'
- PIPE = 'Pipe'
- PRODUCER = 'Producer'
- PUMP = 'Pump'
- STORAGE = 'Storage'
- TRANSPORT = 'Transport'
- class kpicalculator.adapters.common_model.EnergySystem(name, assets, unit_conversion=<factory>, source_metadata=<factory>, esdl_energy_system=None)[source]
Bases:
object- esdl_energy_system: EnergySystem | None = None
- name: str
- class kpicalculator.adapters.common_model.TimeSeries(time_step, values)[source]
Bases:
object- time_step: float
- class kpicalculator.adapters.common_model.Asset(id, name, asset_type, power=0.0, length=0.0, volume=0.0, cop=0.0, investment_cost=0.0, investment_cost_unit='EUR', installation_cost=0.0, installation_cost_unit='EUR', fixed_operational_cost=0.0, fixed_operational_cost_unit='EUR/yr', variable_operational_cost=0.0, variable_operational_cost_unit='EUR/MWh', fixed_maintenance_cost=0.0, fixed_maintenance_cost_unit='EUR/yr', variable_maintenance_cost=0.0, variable_maintenance_cost_unit='EUR/MWh', technical_lifetime=40.0, discount_rate=None, emission_factor=0.0, aggregation_count=1, time_series=<factory>)[source]¶
Bases:
object- asset_type: AssetType¶
- time_series: dict[str, TimeSeries]¶
Adapters¶
- class kpicalculator.adapters.esdl_adapter.EsdlAdapter(credential_manager=None)[source]¶
Bases:
BaseAdapterAdapter for loading energy system data from ESDL files with database support.
Supports both XML time series files (for testing) and InfluxDB profiles (for production) following the MESIDO pattern.
- load_data(source, time_series_file=None, timeseries_dataframes=None, use_database_profiles=True)[source]¶
Load energy system data from an ESDL file.
Costs are extracted from ESDL costInformation elements.
- Parameters:
time_series_file (
str|None) – Optional XML time series file path (testing only).timeseries_dataframes (
dict[str,DataFrame] |None) – Optional dict mapping asset IDs to pandas DataFrames with time-indexed energy/power data. When provided, takes precedence over database loading and time_series_file.use_database_profiles (
bool) – Whether to load InfluxDB profiles.
- Return type:
- Returns:
EnergySystem object with costs from ESDL costInformation.
- Raises:
TypeError – If source is not a str or Path.
- load_from_esdl_object(es, timeseries_dataframes=None, use_database_profiles=False)[source]¶
Load from an already-parsed PyESDL EnergySystem object.
Prefer this over
load_from_string()when the caller has already parsed the ESDL XML — for example, when the object must be inspected before loading (e.g. port→asset resolution inSimulatorAdapter). Useload_from_string()when only the raw XML string is available.Avoids a second
EnergySystemHandler.load_from_string()call when the caller has already parsed the ESDL (e.g.SimulatorAdapter).- Parameters:
- Return type:
- Returns:
EnergySystem with costs extracted from the ESDL object.
- load_from_string(esdl_string, timeseries_dataframes=None, use_database_profiles=False)[source]¶
Load energy system data from ESDL XML string content.
This method allows loading ESDL data directly from a string without needing a file. Useful for integration with systems that provide ESDL content in memory (e.g., simulator_worker).
Costs are extracted from ESDL costInformation elements.
Note
The default use_database_profiles=False reflects the typical use case for string loading: in-memory workflows where time series data is provided via timeseries_dataframes rather than fetched from a database.
- Parameters:
- Return type:
- Returns:
EnergySystem object with costs from ESDL costInformation
- Raises:
ValidationError – If esdl_string is empty or cannot be parsed
- validate_source(source)[source]¶
Validate ESDL file path and basic structure.
- Parameters:
source (
str) – Path to ESDL file as a string (Path objects are normalised to str byload_databefore this method is called).- Return type:
ValidationResult- Returns:
ValidationResult indicating if source is valid
Centralized time series loading with multiple source support.
- class kpicalculator.adapters.time_series_manager.TimeSeriesManager(credential_manager=None)[source]¶
Bases:
objectCentralized time series loading with multiple source support.
This class implements the Factory pattern to provide a single entry point for loading time series data from various sources (DataFrame, database, XML) with configurable priority and graceful degradation.
- load_time_series(energy_system, timeseries_dataframes=None, xml_file=None, source_priority=None)[source]¶
Single entry point for all time series loading with source priority.
- Parameters:
energy_system (
EnergySystem) – ESDL energy system containing asset definitionstimeseries_dataframes (
dict[str,DataFrame] |None) – Optional dict mapping asset IDs to pandas DataFrames with time-indexed energy/power data. When provided, takes precedence over database loading.xml_file (
str|None) – Optional XML time series file path (testing only)source_priority (
list[str] |None) – Optional list defining loading priority Default: [“dataframes”, “database”, “xml”, “empty”]
- Return type:
tuple[dict[str,TimeSeries],ValidationResult]- Returns:
Tuple of (time_series_dict, validation_result)
- Raises:
ValidationError – If critical validation fails across all sources
Reporting¶
ESDL KPI exporter for converting KPI results to ESDL format.
- class kpicalculator.reporting.esdl_kpi_exporter.EsdlKpiExporter[source]¶
Bases:
BaseExporterExport KPI calculation results to ESDL (Energy System Description Language) format.
This exporter takes pre-calculated KPI results and integrates them into ESDL files by adding DistributionKPI elements with StringLabelDistribution structures.
The exporter operates in two modes: - File mode: Saves enhanced ESDL file to disk and returns success boolean - Data structure mode: Returns ESDL EnergySystem object with integrated KPIs
Note: This exporter does NOT perform any calculations. All KPI values must be pre-calculated by the appropriate calculator classes.
Note
This is an internal class, not part of the public API. Use
KpiManager.export_to_esdl(),KpiManager.build_esdl_string_with_kpis(), or the top-levelkpicalculator.build_esdl_string_with_kpis()instead.- export(results, esdl_energy_system, destination=None, level='system')[source]¶
Export pre-calculated KPI results to ESDL format.
Takes KPI calculation results and integrates them into an ESDL energy system by adding DistributionKPI elements with StringLabelDistribution structures. Operates in dual mode: file export or data structure return.
- Parameters:
results (
KpiResults) – Pre-calculated KPI results from cost/energy/emission calculators.esdl_energy_system (
EnergySystem) – Parsed PyESDL energy system object to write KPIs into.destination (
str|Path|None) – Output ESDL file path. If None, returns data structure instead.level (
str) – KPI integration level - ‘system’ (main area), ‘area’ (per area), or ‘asset’ (per asset). Currently ‘area’ and ‘asset’ delegate to ‘system’.
- Returns:
Always True on success; raises on failure. When destination is None: esdl.EnergySystem object with integrated KPIs.
- Return type:
When destination provided
- Raises:
ValueError – If esdl_energy_system is None, level is invalid, or results structure is malformed.
OSError – If file operations fail during save.
Exceptions¶
Custom exception hierarchy for KPI Calculator.
- exception kpicalculator.exceptions.CalculationError[source]¶
Bases:
KpiCalculatorErrorRaised when KPI calculation fails.
- exception kpicalculator.exceptions.ConfigurationError[source]¶
Bases:
KpiCalculatorErrorRaised when configuration is invalid.
- exception kpicalculator.exceptions.CredentialError[source]¶
Bases:
SecurityErrorRaised when credential loading or validation fails.
- exception kpicalculator.exceptions.DataSourceError[source]¶
Bases:
KpiCalculatorErrorRaised when data source loading fails.
- exception kpicalculator.exceptions.DatabaseError[source]¶
Bases:
DataSourceErrorRaised when database operations fail.
- exception kpicalculator.exceptions.ExportError[source]¶
Bases:
KpiCalculatorErrorRaised when result export fails.
- exception kpicalculator.exceptions.KpiCalculatorError[source]¶
Bases:
ExceptionBase exception for KPI Calculator.
- exception kpicalculator.exceptions.MathematicalError[source]¶
Bases:
CalculationErrorRaised when mathematical constraints are violated.
- exception kpicalculator.exceptions.SecurityError[source]¶
Bases:
KpiCalculatorErrorRaised when security validation fails.
- exception kpicalculator.exceptions.ValidationError[source]¶
Bases:
KpiCalculatorErrorRaised when input validation fails.