Multi-Series Updates

Multi-Series Push Subscription API

This is a push-based service that delivers real-time updates for multiple Enact data series via a single method call.

Each call to subscribe_to_multiple_series_updates counts as 10 API calls towards your monthly usage quota — regardless of how many series or option sets you subscribe to.



🔹

❗️❗️ IMPORTANT ❗️❗️

  • The DPSHelper class includes built-in logic to automatically reconnect if the underlying SignalR connection unexpectedly drops. These reconnections do not increase your API usage.
  • However, any subscription made via subscribe_to_multiple_series_updates will stop receiving data after 14 days
  • To continue receiving data beyond 14 days, you must create a new instance of DPSHelper and re-subscribe. Simply calling subscribe_to_multiple_series_updates again on an existing DPSHelper will not resume the data push.
  • Please refer to the python example below to see how to safely reinitialise your subscription every 14 days.

How to Subscribe

The method subscribe_to_multiple_series_updates takes in

  • handle_data_method - Callable[[pd.DataFrame], None]: the function invoked on any series pushes
  • series_requests - list[dict]: A list of dictionaries, with each element detailing a series subscription. Each element needs a countryId, seriesId, and if relevant, optionIds.
  • parse_datetimes - bool: Parse returned DataFrame index to DateTime (UTC). Defaults to False

Each subscription is defined by the following fields:

  • seriesId: Identifier of the Enact series.
  • countryId (optional): Country code (defaults to "Gb" if omitted).
  • optionIds: A list of options, where each option is itself a list of strings. Each inner list creates a separate subscription to the specified series.

🔹

Each list inside optionIds is treated as a standalone subscription, not as a combination of filters.

For example for a plant series subscription:
optionIds: [["Battery"], ["Z4"]] means:

  • One subscription for all Battery-fuelled plants
  • Another subscription for all plants in Zone 4

This does not mean "batteries in Zone 4".


📚 Options behaviour by Series Type

System Series

  • optionIds can be:
    • Omitted entirely if the series requires no options.
    • A list of single or multi-part option sets, e.g.:
      • [["CCGT"], ["Wind"]] for series taking a single option, this is two subscriptions one for each option
      • [["10m", "Z4", "Median"]] for series taking multiple options, this is one subscription specified by multiple options
  • You can use %ALL% in any position to subscribe to all available values of that specific option:
    • [["10m", "%ALL%", "%ALL%"]] → all zones and aggregation types, but only at 10m height.
    • [["%ALL%"]] → all available single-option values for that series (for example all fuel types).

Plant Series

  • Each option set must begin with one of the following:
    • A specific Enact plant ID
    • A specified fuel type, owner, zone to get all plants of specific fuel type, owner or zone
    • The wildcard %ALL% for all plants
  • Some plant series support multiple options, where the first still identifies the plant group, and subsequent values define modifiers:
    • e.g. [["Battery", "HalfHourly"]] → all battery plants, half-hourly granularity

Using the LCPDelta Python Package

Something like:

import time
from datetime import datetime, timedelta
from lcp_delta import enact

USERNAME = "your_username"
API_KEY = "your_api_key"

def handle_updates(x):
    # A callback function that will be invoked with the received series updates.
    # The function should accept one argument, which will be the data received from the series updates.
    print(x)

multi_series_request = [
    # 1. System series with no options
    {"seriesId": "RealtimeFrequency", "countryId": "Gb"},

    # 2. Plant series subscriptions
    #    a) Two separate subscriptions:
    #       - All plants with fuel type "Battery"
    #       - All plants in zone "Z4"
    {"seriesId": "Mel", "optionIds": [["Battery"], ["Z4"]]},

    #    b) One subscription for all plants
    {"seriesId": "Mil", "optionIds": [["%ALL%"]]},

    #    c) One subscription for all plants owned by EDF
    {"seriesId": "Pn", "optionIds": [["EDF"]]},

    # 3. System series
    #    a) Two separate subscriptions:
    #       - CCGT generation outturn
    #       - Wind generation outturn
    {"seriesId": "OutturnFuel", "optionIds": [["CCGT"], ["Wind"]]},

    #    b) One subscription for all fuel types
    {"seriesId": "OutturnFuel", "optionIds": [["%ALL%"]]},

    # 4. System series that takes multiple-options
    #    a) Two seperate subscriptions:
    #				- One for wind at 10m, Zone 2, median aggregation
    #				- One for wind at 100m, Zone 5, median aggregation
    {"seriesId": "ERA5WindByZone", "optionIds": [["AboveGround10M", "Z2", "Median"],
                                                 ["AboveGround100M", "Z5", "Median"]]},

    #    b) One subscription for all heights, Zone 1, median aggregation
    {"seriesId": "ERA5WindByZone", "optionIds": [["%ALL%", "Z1", "Median"]]},

    #    c) One subscription for all heights, all zones, all aggregation types
    {"seriesId": "ERA5WindByZone", "optionIds": [["%ALL%", "%ALL%", "%ALL%"]]},
]

def start_subscription():
    print(f"[{datetime.now()}] Starting new DPSHelper instance and subscribing...")
    dps_helper = enact.DPSHelper(USERNAME, API_KEY)
    dps_helper.subscribe_to_multiple_series_updates(
        handle_updates,
        multi_series_request,
        parse_datetimes=True
    )
    return dps_helper

# Main loop with auto-restart after 14 days
def run_with_auto_restart(days_valid=14):
    dps_helper = start_subscription()
    expiry = datetime.now() + timedelta(days=days_valid - 0.1)

    try:
        while True:
            time.sleep(60 * 60)  # Check every hour
            if datetime.now() >= expiry:
                print(f"[{datetime.now()}] Reconnecting after {days_valid} days...")
                dps_helper = start_subscription()
                expiry = datetime.now() + timedelta(days=days_valid - 0.1)
    except KeyboardInterrupt:
        print("Shutting down...")
        dps_helper.terminate_hub_connection()

if __name__ == "__main__":
    run_with_auto_restart()


Using our DPS Directly

To receive updates to multiple series in Enact, you will need to follow the pattern detailed in Get Started with the Enact DPS in the language of your choice. You can subscribe to multiple series using the corresponding join object formats:

// join object
[
  {
    "seriesId": "BmRollingOfferAcceptancesSummary",
    "optionIds": [["Battery"], ["CCGT"]],
    "countryId": "Gb", // defaults to GB if not given
  },
  ...
]

Push responses will arrive in the same format as in Series Updates. See examples in the python example for how to setup this join object for a variety of different series subscriptions.