Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
821c531
chore: update postman json
BerriJ Apr 2, 2026
5230985
Merge pull request #137 from BerriJ/automated-postman-update
BerriJ Apr 2, 2026
891fb7b
expose format_entsoe_datetime
simon-hirsch Apr 10, 2026
055b4a5
Handle timezones
simon-hirsch Apr 10, 2026
c076f61
Fix ruff and use astimezone
simon-hirsch Apr 10, 2026
5c10263
Create CITATION
jiedxu Apr 13, 2026
a850a63
Update CITATION
jiedxu Apr 13, 2026
936e3f5
Update CITATION
jiedxu Apr 13, 2026
e5ca6c8
rename
jiedxu Apr 13, 2026
fdc4fe9
Create citation-sync.yml
jiedxu Apr 13, 2026
1d91342
Merge pull request #140 from BerriJ/citation-file
BerriJ Apr 13, 2026
06682c4
Merge branch 'develop' into datetime-tz
BerriJ Apr 13, 2026
86ff203
chore: update postman json
BerriJ Apr 14, 2026
ea9b31f
Extend docstring
simon-hirsch Apr 14, 2026
078b5cd
Add to docs
simon-hirsch Apr 14, 2026
18ded4e
Merge branch 'datetime-tz' of github.com:simon-hirsch/entsoe-apy into…
simon-hirsch Apr 14, 2026
ee8884b
Fix reference to format_entsoe_datetime in docs
BerriJ Apr 15, 2026
64b66dd
Merge pull request #139 from simon-hirsch/datetime-tz
BerriJ Apr 15, 2026
7cce807
Merge branch 'develop' into automated-postman-update
BerriJ Apr 15, 2026
07729fb
Style fixes by ruff
BerriJ Apr 15, 2026
5d45848
Add new ChangesToBidAvailabilityArchives class
BerriJ Apr 15, 2026
8ae38bf
Merge pull request #138 from BerriJ/automated-postman-update
BerriJ Apr 16, 2026
4ec53b2
Fix normalize_to_records function which did not traverse into nested …
BerriJ May 8, 2026
467cb67
Add unit tests for normalize_to_records nested structure coverage
Copilot May 12, 2026
9fbc9fb
Style fixes by ruff
Copilot May 12, 2026
439b528
Merge pull request #142 from BerriJ/fix-normalize_to_records
BerriJ May 12, 2026
b8fa4e5
Bump version to 1.1.0
BerriJ May 12, 2026
8207a79
Address PR review comments
BerriJ May 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/citation-sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Update CITATION.cff on Tag
on:
push:
tags:
- "v*.*.*"

permissions:
contents: write
pull-requests: write

jobs:
update-citation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0

- uses: Adamtaranto/citation-sync-action@v1
with:
use-pull-request: true
target-branch: develop
token: ${{ secrets.GITHUB_TOKEN }}
13 changes: 13 additions & 0 deletions CITATION.cff
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
cff-version: 1.2.0
message: "If you use this software, please cite it as below."
authors:
- family-names: "Berrisch"
given-names: "Jonathan"
orcid: "https://orcid.org/0000-0002-4944-9074"
- family-names: "Xu"
given-names: "Jie"
orcid: "https://orcid.org/0009-0002-1518-8010"
title: "entsoe-apy: Python Package to Query the ENTSO-E API"
version: 1.1.0
date-released: 2026-05-12
url: "https://github.com/BerriJ/entsoe-apy/releases/tag/v1.0.0"
Comment thread
BerriJ marked this conversation as resolved.
31 changes: 31 additions & 0 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,37 @@ df = DataFrame(records)

See also [Utilities](./utilities.md) for more details on the utility functions.

### Working with datetime and timezones

The ENTSO-E API is (usually) expecting UTC timestamps but the European power markets operate on a Europe/Berlin basis. We can use the `format_entsoe_datetime` function to convert `datetime` and `pandas` timestamps in the API interface. The function handles time zone information and parses `datetime` derived types to the respective integer. Timezone-naive timestamps are assumed to be in UTC.

```python
import pandas as pd
from entsoe.Market import EnergyPrices
from entsoe.utils import (
extract_records,
add_timestamps,
format_entsoe_datetime
)

period_start = pd.Timestamp("2024-02-01", tz="Europe/Berlin")
period_end = pd.Timestamp("2024-02-02", tz="Europe/Berlin")

# Query energy prices
result = EnergyPrices(
in_domain="10YNL----------L", # Netherlands
out_domain="10YNL----------L",
period_start=format_entsoe_datetime(period_start),
period_end=format_entsoe_datetime(period_end),
).query_api()

records = extract_records(result)
records = add_timestamps(records)
df = pd.DataFrame(records)
df = df.convert_dtypes()
```


### Extracting Specific Domains

You can extract specific parts of the result by specifying a domain:
Expand Down
2 changes: 2 additions & 0 deletions docs/utilities.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
::: entsoe.utils.extract_records

::: entsoe.utils.add_timestamps

::: entsoe.utils.format_entsoe_datetime
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "IFs mFRR 9.9, aFRR 9.6&9.8 Changes to Bid Availability Archives",
"method": "GET",
"query": [
{
"key": "documentType",
"value": "B45",
"description": "[M] B45 = Bid Availability Document"
},
{
"key": "processType",
"value": "A47",
"description": "[M] A47"
},
{
"key": "Domain",
"value": "10YDE-VE-------2",
"description": "[M] EIC code of a Scheduling Area or LFA"
},
{
"key": "periodStart",
"value": "202309232200",
"description": "[M] Pattern yyyyMMddHHmm e.g. 201601010000"
},
{
"key": "periodEnd",
"value": "202309242200",
"description": "[M] Pattern yyyyMMddHHmm e.g. 201601010000"
},
{
"key": "storageType",
"value": "archive",
"description": "[M] Used to request archives"
},
{
"key": "businessType",
"value": "C46",
"description": "[O] C40 = Conditional bid; C41 = Thermal limit; C42 = Frequency limit; C43 = Voltage limit; C44 = Current limit; C45 = Short-circuit current limits; C46 = Dynamic stability limit",
"disabled": true
},
{
"key": "offset",
"value": "100",
"description": "[O] Integer: Zero‑based index of the first archive to return. The offset parameter paginates the response in batches of 100 archives (e.g., offset = n returns the archives in the range n+1 to n+100).",
"disabled": true
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,6 @@
"value": "20240515123900",
"description": "[O] For Offered Capacity Evolution can be quried with datetime in numeric. For example 20210803113900000 for evolution update date time 03.08.2021 13:39:00.000. If there is no OC evolution version with requested update_DateAndOrTime the system selects OC version valid in the given time (e.g., closest previous update_DateAndOrTime). The most recent publish OC version is provided in case the update_DateAndOrTime parameter is omitted. The most recent published OC version and intermediate OC versions is distinguished by document type: − Intermediate OC values: A31 − Most recent published OC values: B33",
"disabled": true
},
{
"key": "offset",
"value": "0",
"description": "[O] Integer: Zero‑based index of the first TimeSeries to return. The offset parameter paginates the response in batches of 100 TimeSeries (e.g., offset = n returns the TimeSeries in the range n+1 to n+100).",
"disabled": true
}
]
}
10 changes: 5 additions & 5 deletions misc/endpoints/Market/11.1.B Flow Based Allocations.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,27 @@
},
{
"key": "processType",
"value": "A33",
"value": "A44",
"description": "[M] A43 = Day ahead; A44 = Intraday; A32 = Month-ahead; A33 = Year-ahead"
},
{
"key": "out_Domain",
"value": "10Y1001A1001A91G",
"value": "10YDOM-REGION-1V",
"description": "[M] EIC code of a Region"
},
{
"key": "in_Domain",
"value": "10Y1001A1001A91G",
"value": "10YDOM-REGION-1V",
"description": "[M] EIC code of a Region"
},
{
"key": "periodStart",
"value": "202412312300",
"value": "201402032300",
"description": "[M] Pattern yyyyMMddHHmm e.g. 201601010000"
},
{
"key": "periodEnd",
"value": "202512312300",
"value": "201402040500",
"description": "[M] Pattern yyyyMMddHHmm e.g. 201601010000"
}
]
Expand Down
3 changes: 2 additions & 1 deletion misc/endpoints/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ To regenerate this list, run:
- [12.1.D Energy Prices](Market/12.1.D%20Energy%20Prices.json)

## [Load](Load)
- [6.1.A Actual Total Load](Load/6.1.A%20Actual%20Total%20Load.json)
- [6.1.A Actual Total Load - SECURITY_TOKEN in header](Load/6.1.A%20Actual%20Total%20Load%20-%20SECURITY_TOKEN%20in%20header.json)
- [6.1.A Actual Total Load](Load/6.1.A%20Actual%20Total%20Load.json)
- [6.1.B Day-ahead Total Load Forecast](Load/6.1.B%20Day-ahead%20Total%20Load%20Forecast.json)
- [6.1.C Week-ahead Total Load Forecast](Load/6.1.C%20Week-ahead%20Total%20Load%20Forecast.json)
- [6.1.D Month-ahead Total Load Forecast](Load/6.1.D%20Month-ahead%20Total%20Load%20Forecast.json)
Expand Down Expand Up @@ -66,6 +66,7 @@ To regenerate this list, run:
- [12.3.B&C Balancing energy bids](Balancing/12.3.B%26C%20Balancing%20energy%20bids.json)
- [12.3.B&C Balancing energy bids archives](Balancing/12.3.B%26C%20Balancing%20energy%20bids%20archives.json)
- [IFs mFRR 9.9, aFRR 9.6&9.8 Changes to Bid Availability](Balancing/IFs%20mFRR%209.9%2C%20aFRR%209.6%269.8%20Changes%20to%20Bid%20Availability.json)
- [IFs mFRR 9.9, aFRR 9.6&9.8 Changes to Bid Availability Archives](Balancing/IFs%20mFRR%209.9%2C%20aFRR%209.6%269.8%20Changes%20to%20Bid%20Availability%20Archives.json)
- [12.3.E Aggregated Balancing Energy Bids (GL EB)](Balancing/12.3.E%20Aggregated%20Balancing%20Energy%20Bids%20%28GL%20EB%29.json)
- [IFs 3.10, 3.16 & 3.17 Netted and Exchanged Volumes](Balancing/IFs%203.10%2C%203.16%20%26%203.17%20Netted%20and%20Exchanged%20Volumes.json)
- [IFs 3.10, 3.16 & 3.17 Netted and Exchanged Volumes per Border](Balancing/IFs%203.10%2C%203.16%20%26%203.17%20Netted%20and%20Exchanged%20Volumes%20per%20Border.json)
Expand Down
3 changes: 2 additions & 1 deletion misc/endpoints/all_endpoints.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
{
"name": "Load",
"items": [
"6.1.A Actual Total Load",
"6.1.A Actual Total Load - SECURITY_TOKEN in header",
"6.1.A Actual Total Load",
"6.1.B Day-ahead Total Load Forecast",
"6.1.C Week-ahead Total Load Forecast",
"6.1.D Month-ahead Total Load Forecast",
Expand Down Expand Up @@ -77,6 +77,7 @@
"12.3.B&C Balancing energy bids",
"12.3.B&C Balancing energy bids archives",
"IFs mFRR 9.9, aFRR 9.6&9.8 Changes to Bid Availability",
"IFs mFRR 9.9, aFRR 9.6&9.8 Changes to Bid Availability Archives",
"12.3.E Aggregated Balancing Energy Bids (GL EB)",
"IFs 3.10, 3.16 & 3.17 Netted and Exchanged Volumes",
"IFs 3.10, 3.16 & 3.17 Netted and Exchanged Volumes per Border",
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ authors = [
{name="Jonathan Berrisch", email="jonathan.berrisch@uni-due.de"},
{name="Jie Xu", email="jie.xu@uni-due.de"}
]
version = "1.0.0"
version = "1.1.0"
description = "A Python library for accessing ENTSO-E Transparency Platform API endpoints"
readme = "README.md"
license = "GPL-3.0-or-later"
Expand Down
2 changes: 2 additions & 0 deletions src/entsoe/Balancing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
BalancingEnergyBids,
BalancingEnergyBidsArchives,
ChangesToBidAvailability,
ChangesToBidAvailabilityArchives,
CrossBorderMarginalPricesForAFRR,
CurrentBalancingState,
ElasticDemands,
Expand Down Expand Up @@ -43,6 +44,7 @@
"BalancingEnergyBidsArchives",
"BalancingEnergyBids",
"ChangesToBidAvailability",
"ChangesToBidAvailabilityArchives",
"CrossBorderMarginalPricesForAFRR",
"CurrentBalancingState",
"ElasticDemands",
Expand Down
62 changes: 62 additions & 0 deletions src/entsoe/Balancing/specific_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -1183,6 +1183,68 @@ def __init__(
)


class ChangesToBidAvailabilityArchives(Balancing):
"""Parameters for IFs mFRR 9.9, aFRR 9.6&9.8 Changes to Bid Availability Archives.

Data view:
https://transparency.entsoe.eu/balancing/r2/changesToBidAvailability/show

Fixed parameters:

- documentType: B45 (Bid Availability Document)
- processType: A47 (Manual frequency restoration reserve)
- storageType: archive

Optional parameters:
- businessType: C40=Conditional bid, C41=Thermal limit, C42=Frequency limit,
C43=Voltage limit, C44=Current limit,
C45=Short-circuit current limits,
C46=Dynamic stability limit

Notes:
- This is the archived version of changes to bid availability
- The offset parameter paginates the response in batches of 100 archives
"""

code = "IF_mFRR_aFRR_9.6_9.8_9.9_Archives"

def __init__(
self,
period_start: int,
period_end: int,
domain: str,
# Optional balancing-specific parameters
business_type: Optional[str] = None,
# Additional common parameters
offset: int = 0,
):
"""
Initialize changes to bid availability archives parameters.

Args:
period_start: Start period (YYYYMMDDHHMM format)
period_end: End period (YYYYMMDDHHMM format)
domain: EIC code of Scheduling Area or LFA
business_type: C40=Conditional bid, C41=Thermal limit, C42=Frequency limit,
C43=Voltage limit, C44=Current limit, C45=Short-circuit limit,
C46=Dynamic stability limit
offset: Zero-based index of the first archive to return (batches of 100)
"""
# Initialize with preset and user parameters
super().__init__(
document_type="B45",
period_start=period_start,
period_end=period_end,
domain=domain,
process_type="A47",
business_type=business_type,
offset=offset,
)

# Add the storageType parameter specific to archives endpoint
self.add_optional_param("storageType", "archive")


class BalancingBorderCapacityLimitations(Balancing):
"""Parameters for IFs 4.3 & 4.4 Balancing Border Capacity Limitations.

Expand Down
9 changes: 8 additions & 1 deletion src/entsoe/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
from .mappings_dict import mappings
from .records import extract_records
from .timestamps import add_timestamps, calculate_timestamp
from .utils import format_entsoe_datetime

__all__ = ["mappings", "extract_records", "add_timestamps", "calculate_timestamp"]
__all__ = [
"mappings",
"extract_records",
"add_timestamps",
"calculate_timestamp",
"format_entsoe_datetime",
]
19 changes: 12 additions & 7 deletions src/entsoe/utils/records.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,18 @@ def _filter_ignored_fields(record: Dict[str, Any]) -> Dict[str, Any]:
return {k: v for k, v in record.items() if k not in ignore_fields}

if isinstance(data, dict):
items = {}
items: Dict[str, Any] = {}
expansions: List[List[Dict[str, Any]]] = []
for k, v in data.items():
new_key = f"{parent_key}{sep}{k}" if parent_key else k
if isinstance(v, dict):
sub_records = normalize_to_records(
v, new_key, sep=sep, ignore_fields=ignore_fields
)
items.update(sub_records[0]) # merge dict
if len(sub_records) == 1:
items.update(sub_records[0])
elif len(sub_records) > 1:
expansions.append(sub_records)
elif isinstance(v, list):
# Expand list elements into multiple records
list_records = []
Expand All @@ -88,14 +92,15 @@ def _filter_ignored_fields(record: Dict[str, Any]) -> Dict[str, Any]:
list_records.extend(sub_records)
else:
list_records.append({new_key: elem})
# Cross join if multiple records, else just keep one
if list_records:
return [
_filter_ignored_fields(dict(items, **lr)) for lr in list_records
]
expansions.append(list_records)
else:
items[new_key] = v
return [_filter_ignored_fields(items)]
# Cross-join base items with all collected expansions
result: List[Dict[str, Any]] = [items]
for expansion in expansions:
result = [dict(r, **e) for r in result for e in expansion]
return [_filter_ignored_fields(r) for r in result]
elif isinstance(data, list):
records = []
for elem in data:
Expand Down
15 changes: 13 additions & 2 deletions src/entsoe/utils/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
import inspect
from xml.etree import ElementTree as ET

Expand Down Expand Up @@ -31,13 +31,24 @@ def format_entsoe_datetime(dt: datetime) -> int:
"""
Format datetime object to ENTSOE datetime format (YYYYMMDDHHMM).

Convert a (tz-aware) datetime to UTC and format it as an integer in the
ENTSOE format. If the datetime is naive, it is assumed to be in UTC. This
function can be used to convert pd.Timestamp and pl.Datetime objects to the
required format for ENTSOE API calls. Please have a look at the
documentation for more details and examples.

Args:
dt: datetime object

Returns:
Date in YYYYMMDDHHMM format as integer
"""
return int(dt.strftime("%Y%m%d%H%M"))
if dt.tzinfo is not None:
utc_timestamp = dt.astimezone(timezone.utc)
return int(utc_timestamp.strftime("%Y%m%d%H%M"))
else:
# Assume naive timestamps are in UTC
return int(dt.strftime("%Y%m%d%H%M"))


def check_date_range_limit(
Expand Down
Loading
Loading