Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
sandbox/
^LICENSE\.md$
^\.github$
.claude/
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
.Rproj.user
sandbox/
test-coverage.yml
.claude/
6 changes: 3 additions & 3 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
Package: entsoeapi
Type: Package
Title: An R Wrapper for the European Network of Transmission System Operators for Electricity Application Programming Interface
Version: 0.9.1.1
Version: 0.9.2.0
Authors@R: c(person("Kenneth", "Rose", role="aut", email="kennethrose82@gmail.com"),
person("Sándor", "Budai", role=c("aut", "cre"), email="sbudai.ga@gmail.com"))
Description: Simple and standardized wrappers around Entso-E API's Load, Generation,
Transmission, Balancing, Outages & Congestion Management endpoints to retrieve common data,
Description: Simple and standardized wrappers around Entso-E API's Market, Load, Generation,
Transmission, Outages & Balancing endpoints to retrieve common data,
and convert them into tabular format.
License: MIT + file LICENSE
Imports:
Expand Down
3 changes: 3 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

export(accounting_point_eic)
export(all_approved_eic)
export(allocated_transfer_capacities_3rd_countries)
export(already_allocated_total_capacity)
export(area_eic)
export(auction_revenue)
export(balancing_border_cap_limit)
export(congestion_income)
export(continuous_offered_transfer_capacity)
export(costs_of_congestion_management)
export(countertrading)
Expand Down Expand Up @@ -34,6 +36,7 @@ export(load_week_ahead_total_forecast)
export(load_year_ahead_forecast_margin)
export(load_year_ahead_total_forecast)
export(location_eic)
export(net_positions)
export(netted_volumes)
export(outages_both)
export(outages_cons_units)
Expand Down
10 changes: 10 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# entsoeapi v0.9.2.0 (2026-02-23)

## New functionality

- The beta versions of `net_positions()`, `congestion_income()` and `allocated_transfer_capacities_3rd_countries()` queries have been introduced.

## Miscellaneous

- Minor under the hood improvements and fixes.

# entsoeapi v0.9.1.1 (2026-02-21)

## New functionality
Expand Down
243 changes: 12 additions & 231 deletions R/en_helpers.R
Original file line number Diff line number Diff line change
Expand Up @@ -451,240 +451,21 @@ all_approved_eic <- function() {
#'
#' @noRd
all_allocated_eic <- function() {
# define those variables as NULL which are used under non-standard evaluation
mRID <- doc_status_value <- NULL

# set the link of the xml file
f <- paste0(
"https://eepublicdownloads.blob.core.windows.net/",
"cio-lio/xml/allocated-eic-codes.xml"
)

# retrieve data from the API
req <- httr2::request(base_url = f) |>
httr2::req_method(method = "GET") |>
httr2::req_verbose(
header_req = FALSE,
header_resp = TRUE,
body_req = FALSE,
body_resp = FALSE
) |>
httr2::req_timeout(seconds = 60)
resp <- "No response."
resp <- tryCatch(
expr = httr2::req_perform(req = req),
httr2_http_404 = \(cnd) cnd,
httr2_http = \(cnd) cnd,
httr2_error = \(cnd) cnd
)

if (inherits(x = resp, what = "httr2_response")) {
message("response has arrived")
} else {
# extract reason from reason text
response_reason <- resp$resp |>
httr2::resp_body_xml(encoding = "utf-8") |>
xmlconvert::xml_to_list() |>
paste(collapse = " - ")

if (lengths(response_reason) > 0) {
stop(response_reason)
} else {
stop(httr2::resp_status(resp$resp))
}
}

# retrieve content-type from response headers
rhct <- httr2::resp_headers(resp = resp)[["content-type"]]
expt_zip <- c(
"application/zip",
"application/octet-stream"
)
expt_xml <- c(
"text/xml",
"application/xml"
)

# if the request is a zipped xml file, then ...
if (rhct %in% expt_zip) {

# read the xml content from each the decompressed files
en_cont <- httr2::resp_body_raw(resp = resp) |>
rawToChar() |>
xml2::as_xml_document()

# convert XML to table
result_tbl <- tryCatch(
expr = {
nodesets <- xml2::xml_contents(en_cont)

# detect the number of children for each element
ns_children <- purrr::map_int(nodesets, number_of_children)

# compose a sub table from the first level data
first_level_tbl <- nodesets[ns_children == 0] |>
xmlconvert::xml_to_list(convert.types = FALSE) |>
data.table::as.data.table() |>
setNames(nm = xml2::xml_name(nodesets[ns_children == 0]))

# remove the not needed columns from the first_level_tbl
not_needed_patt <- paste(
"^(sender|receiver)_MarketParticipant\\.",
"^mRID$|^type$",
sep = "|"
)
first_level_tbl <- first_level_tbl |>
dplyr::select(!dplyr::matches(match = not_needed_patt))

# compose a sub table from the second level data
second_level_tbls <- nodesets[ns_children > 0] |>
purrr::map(
~xmlconvert::xml_to_list(
xml = .x,
convert.types = FALSE
) |>
data.table::as.data.table()
)
second_level_tbl <- second_level_tbls |>
data.table::rbindlist(use.names = TRUE, fill = TRUE)

# paste together multiple Function_Names columns into one.
fn_cols <- stringr::str_subset(
string = names(second_level_tbl),
pattern = "^Function_Names$"
)
if (length(fn_cols) > 0) {
second_level_tbl <- second_level_tbl |>
tidyr::unite(
col = "Function_Names",
dplyr::all_of(fn_cols),
sep = " - ",
remove = TRUE,
na.rm = TRUE
)
}
second_level_tbl <- second_level_tbl |>
dplyr::rename(eic_code = mRID)

# combine the first level and the second levels tables together
dplyr::bind_cols(first_level_tbl, second_level_tbl)
},
error = \(e) {
stop("The XML document has an unexpected tree structure!\n", e)
}
)

} else if (rhct %in% expt_xml) {

# read the xml content from the response
en_cont <- httr2::resp_body_xml(resp = resp, encoding = "UTF-8")

# convert XML to table
result_tbl <- tryCatch(
expr = {
nodesets <- xml2::xml_contents(en_cont)

# detect the number of children for each element
ns_children <- purrr::map_int(nodesets, number_of_children)

# compose a sub table from the first level data
first_level_tbl <- nodesets[ns_children == 0] |>
xmlconvert::xml_to_list() |>
data.table::as.data.table() |>
setNames(nm = xml2::xml_name(nodesets[ns_children == 0]))

# remove the not needed columns from the first_level_tbl
not_needed_patt <- paste(
"^(sender|receiver)_MarketParticipant\\.",
"^mRID$|^type$",
sep = "|"
)
first_level_tbl <- first_level_tbl |>
dplyr::select(!dplyr::matches(match = not_needed_patt))

# compose a sub table from the second level data
second_level_tbls <- nodesets[ns_children > 0] |>
purrr::map(
~xmlconvert::xml_to_list(
xml = .x,
convert.types = FALSE
) |>
data.table::as.data.table()
)
second_level_tbl <- second_level_tbls |>
data.table::rbindlist(use.names = TRUE, fill = TRUE)

# paste together multiple Function_Names columns into one.
fn_cols <- stringr::str_subset(
string = names(second_level_tbl),
pattern = "^Function_Names$"
)
if (length(fn_cols) > 0) {
second_level_tbl <- second_level_tbl |>
tidyr::unite(
col = "Function_Names",
dplyr::all_of(fn_cols),
sep = " - ",
remove = TRUE,
na.rm = TRUE
)
}
second_level_tbl <- second_level_tbl |>
dplyr::rename(eic_code = mRID)

# combine the first level and the second levels tables together
dplyr::bind_cols(first_level_tbl, second_level_tbl)
},
error = \(e) {
stop("The XML document has an unexpected tree structure!\n", e)
}
)
cache_key <- "all_allocated_eic_df_key"

# check if there is any cached value of 'all_allocated_eic'
if (mh$exists(key = cache_key)) {
# recall res_df values
res_df <- mh$get(key = cache_key, missing = get_all_allocated_eic())
message("\npulling all_allocated_eic table from cache")
} else {
# download and import the csv file
message("\ndownloading all_allocated_eic table ...")
res_df <- get_all_allocated_eic()

stop("Not known response content-type: ", rhct)

# cache res_df as cache_key
mh$set(key = cache_key, value = res_df)
}

# rename columns to snakecase
names(result_tbl) <- my_snakecase(result_tbl)

# rename some columns
data.table::setnames(
x = result_tbl,
old = c(
"attribute_instance_component_attribute",
"last_request_date_and_or_time_date",
"eic_responsible_market_participant_mrid",
"eic_code_market_participant_vat_code_name",
"eic_code_market_participant_acer_code_name",
"eic_parent_market_document_mrid"
),
new = c(
"instance_component_attribute",
"last_request_date",
"responsible_market_participant_mrid",
"market_participant_vat_code_name",
"market_participant_acer_code_name",
"parent_market_document_mrid"
)
)

# add eic_code_doc_status definitions to codes
result_tbl <- data.table::merge.data.table(
x = data.table::data.table(result_tbl),
y = data.table::data.table(message_types) |>
subset(select = c("code", "title")) |>
setNames(nm = c("doc_status", "doc_status_value")),
by = "doc_status",
all.x = TRUE
) |>
dplyr::relocate(
doc_status_value,
.after = doc_status
)

# return with the xml content list
tibble::as_tibble(result_tbl)

res_df
}
Loading
Loading