Skip to content

Commit

Permalink
Merge pull request #189 from getorymckeag/forecast-peering
Browse files Browse the repository at this point in the history
Changes for forecast and RT peering
  • Loading branch information
getorymckeag authored Oct 16, 2024
2 parents 04d0227 + 2b74e07 commit 0d1d7df
Show file tree
Hide file tree
Showing 9 changed files with 312 additions and 66 deletions.
2 changes: 2 additions & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
,"Vernova"
,"Vmax"
,"Vmin"
,"RLR"
,"GLR"
],
"ignoreWords": [
"AMPL"
Expand Down
2 changes: 1 addition & 1 deletion docs/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
GEM
remote: https://rubygems.org/
specs:
activesupport (7.2.1)
activesupport (7.2.1.1)
base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
Expand Down
6 changes: 5 additions & 1 deletion docs/_data/openapi-split.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -201,11 +201,15 @@ paths:
$ref: paths/limits_forecast-snapshot.yaml#/current
/limits/forecast-snapshot/{period}:
$ref: paths/limits_forecast-snapshot.yaml#/historical
/limits/regional/forecast-snapshot:
$ref: paths/limits_forecast-snapshot.yaml#/regional
/rating-proposals/realtime:
$ref: paths/rating-proposals_realtime.yaml

/limits/realtime-snapshot:
$ref: paths/limits_realtime-snapshot.yaml
$ref: paths/limits_realtime-snapshot.yaml#/global
/limits/regional/realtime-snapshot:
$ref: paths/limits_realtime-snapshot.yaml#/regional
/rating-proposals/forecast:
$ref: paths/rating-proposals_forecasts.yaml

Expand Down
31 changes: 30 additions & 1 deletion docs/_data/paths/limits_forecast-snapshot.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ current:
summary: Limits Forecast Snapshot
tags:
- Forecasting
parameters:
parameters: &commonParams
- $ref: ../components/parameters/offset-period-start.yaml
- $ref: ../components/parameters/period-end.yaml
- $ref: ../components/parameters/monitoring-set-filter.yaml
Expand Down Expand Up @@ -102,3 +102,32 @@ historical:
<<: *responses
'410':
$ref: '../openapi-split.yaml#/components/responses/410-problem'

regional:
get:
<<: *get
operationId: getRegionalLimitsForecastSnapshot
summary: Regional Limits Forecast Snapshot
parameters: *commonParams
description: |
Similar to [getLimitsForecastSnapshot](#tag/Forecasting/operation/getLimitsForecastSnapshot),
except that it specifically returns only the latest **regionally** limiting ratings
([RLRs](https://trolie.energy/concepts#regionally-limiting-rating))
used by the Transmission Provider.
This is explicitly designed to be used when reconciling forecasts between Transmission Providers
in order to generate globally limiting ratings ([GLRs](https://trolie.energy/concepts#globally-limiting-rating))
for general use. See the article on
[RC-to-RC Reconciliation](https://trolie.energy/articles/RC-to-RC-reconciliation.html) for more details.
Outside of this use case, most users should use
[getLimitsForecastSnapshot](#tag/Forecasting/operation/getLimitsForecastSnapshot) to get globally
limiting ratings.
Clients SHOULD perform Conditional `GET` using the `If-None-Match` header
and the `ETag` of a previous `GET` response to poll this endpoint. Rate
limiting is done on a per Ratings Provider basis, so requests from
independent clients used by the same provider count against the same quota.
responses: *responses
151 changes: 92 additions & 59 deletions docs/_data/paths/limits_realtime-snapshot.yaml
Original file line number Diff line number Diff line change
@@ -1,59 +1,92 @@
get:
operationId: getRealTimeLimits
description: |
Obtain the System Operating Limits in-use by the Transmission Provider.
Clients SHOULD perform Conditional `GET` using the `If-None-Match` header
and the `ETag` of a previous `GET` response to poll this endpoint. Rate
limiting is done on a per Ratings Provider basis, so requests from
independent clients used by the same provider count against the same quota.
summary: Limits Real Time Snapshot
tags:
- Real-Time
parameters:
- $ref: ../components/parameters/monitoring-set-filter.yaml
- $ref: ../components/parameters/facility-filter.yaml
responses:
'200':
description: The System Operating Limits snapshot is returned.
content:
application/vnd.trolie.realtime-limits-snapshot.v1+json:
schema:
$ref: "../components/schemas/array-max-monitored-elements.yaml#/realtime-limits-snapshot"
example:
$ref: '../../example-narratives/examples/realtime-limit-set.json'
application/vnd.trolie.realtime-limits-detailed-snapshot.v1+json:
schema:
$ref: "../components/schemas/array-max-monitored-elements.yaml#/realtime-limits-detailed-snapshot"
example:
$ref: '../../example-narratives/examples/realtime-limit-set-detailed.json'
application/vnd.trolie.realtime-limits-snapshot-slim.v1+json:
schema:
$ref: "../components/schemas/array-max-monitored-elements.yaml#/realtime-limits-snapshot-slim"
example:
$ref: '../../example-narratives/examples/realtime-limit-set-slim.json'
headers:
$ref: '../openapi-split.yaml#/components/responses/204/headers'
'304':
$ref: '../openapi-split.yaml#/components/responses/304'
'400':
$ref: '../openapi-split.yaml#/components/responses/400-problem'
'401':
$ref: '../openapi-split.yaml#/components/responses/401-empty'
'403':
$ref: '../openapi-split.yaml#/components/responses/403-empty'
'404':
$ref: '../openapi-split.yaml#/components/responses/404-empty'
'406':
$ref: '../openapi-split.yaml#/components/responses/406-empty'
'429':
$ref: '../openapi-split.yaml#/components/responses/429-empty'
'500': &unexpected-error-empty
$ref: '../openapi-split.yaml#/components/responses/500-empty'
default: *unexpected-error-empty

security:
- oauth2-primary-flow:
- read:operating-snapshot
global:
get:
operationId: getRealTimeLimits
description: |
Obtain the System Operating Limits in-use by the Transmission Provider.
Clients SHOULD perform Conditional `GET` using the `If-None-Match` header
and the `ETag` of a previous `GET` response to poll this endpoint. Rate
limiting is done on a per Ratings Provider basis, so requests from
independent clients used by the same provider count against the same quota.
summary: Limits Real Time Snapshot
tags:
- Real-Time
parameters: &params
- $ref: ../components/parameters/monitoring-set-filter.yaml
- $ref: ../components/parameters/facility-filter.yaml
responses: &responses
'200':
description: The System Operating Limits snapshot is returned.
content:
application/vnd.trolie.realtime-limits-snapshot.v1+json:
schema:
$ref: "../components/schemas/array-max-monitored-elements.yaml#/realtime-limits-snapshot"
example:
$ref: '../../example-narratives/examples/realtime-limit-set.json'
application/vnd.trolie.realtime-limits-detailed-snapshot.v1+json:
schema:
$ref: "../components/schemas/array-max-monitored-elements.yaml#/realtime-limits-detailed-snapshot"
example:
$ref: '../../example-narratives/examples/realtime-limit-set-detailed.json'
application/vnd.trolie.realtime-limits-snapshot-slim.v1+json:
schema:
$ref: "../components/schemas/array-max-monitored-elements.yaml#/realtime-limits-snapshot-slim"
example:
$ref: '../../example-narratives/examples/realtime-limit-set-slim.json'
headers:
$ref: '../openapi-split.yaml#/components/responses/204/headers'
'304':
$ref: '../openapi-split.yaml#/components/responses/304'
'400':
$ref: '../openapi-split.yaml#/components/responses/400-problem'
'401':
$ref: '../openapi-split.yaml#/components/responses/401-empty'
'403':
$ref: '../openapi-split.yaml#/components/responses/403-empty'
'404':
$ref: '../openapi-split.yaml#/components/responses/404-empty'
'406':
$ref: '../openapi-split.yaml#/components/responses/406-empty'
'429':
$ref: '../openapi-split.yaml#/components/responses/429-empty'
'500': &unexpected-error-empty
$ref: '../openapi-split.yaml#/components/responses/500-empty'
default: *unexpected-error-empty

security: &security
- oauth2-primary-flow:
- read:operating-snapshot

regional:
get:
operationId: getRegionalRealTimeLimits
description: |
Similar to [getRealTimeLimits](#tag/Real-Time/operation/getRealTimeLimits),
except that it specifically returns only the latest **regionally** limiting ratings
([RLRs](https://trolie.energy/concepts#regionally-limiting-rating))
used by the Transmission Provider.
This is explicitly designed to be used when reconciling real-time ratings between Transmission Providers
in order to generate globally limiting ratings ([GLRs](https://trolie.energy/concepts#globally-limiting-rating))
for general use. See the article on
[RC-to-RC Reconciliation](https://trolie.energy/articles/RC-to-RC-reconciliation.html) for more details.
Outside of this use case, most users should use
[getRealTimeLimits](#tag/Real-Time/operation/getRealTimeLimits) to get globally
limiting ratings.
Clients SHOULD perform Conditional `GET` using the `If-None-Match` header
and the `ETag` of a previous `GET` response to poll this endpoint. Rate
limiting is done on a per Ratings Provider basis, so requests from
independent clients used by the same provider count against the same quota.
summary: Regional Limits Real Time Snapshot
tags:
- Real-Time
parameters: *params
responses: *responses
security: *security
1 change: 1 addition & 0 deletions docs/_includes/mermaid_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ sequence: { showSequenceNumbers: true } }
152 changes: 152 additions & 0 deletions docs/articles/RC-to-RC-reconciliation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
---
title: RC-to-RC Reconciliation
parent: Articles
---

# Reconciling AARs on RC-RC Interties

In TROLIE's simplest usage, rating providers send rating proposals to Transmission Providers
using the workflow described in [Forecast Submittal](../example-narratives/submitting-forecasts.md).

However, two neighboring Transmission Providers, each running independent clearinghouses, will also
have to share ratings. Each TROLIE server must be able to act as a client to achieve this coordination,
with each reaching out to one another. These Transmission Providers also serve in the NERC role of
Reliability Coordinator (RC), which will be the term used throughout this article.

This article specifically covers reconciling ratings between two clearinghouses. It builds off of the
[peer-monitoring-sets](./peer-monitoring-sets.md) article and assumes that monitoring sets between peers
have been reconciled.

This article defines a design pattern recommended as a best practice, as it ultimately must
be implemented within TROLIE servers and is only supported by the TROLIE communications specification
rather than mandated by it.

# Globally Limiting Ratings at the Seams
For most facilities in a reliability coordinator (RC) footprint, the output of the clearinghouse represents
Globally Limiting Ratings or [GLRs](../concepts.md#globally-limiting-rating) that may be used in
operations. However, most RCs don't exist on electrical islands by themselves. They will have interties
with neighboring RCs. Assuming each RC is running its own clearinghouse, or is otherwise generating its
ratings independently, GLRs for these facilities must be resolved after clearinghouse runs in a separate phase.

# Reconciling Forecast ClearingHouse Results
In terms of TROLIE mechanics, the initial output of each RC's clearinghouse is considered the
[Regionally Limiting Rating](../concepts.md#regionally-limiting-rating) or RLR. Peer TROLIEs can continuously
poll each other for new RLRs using the
[getRegionalLimitsForecastSnapshot](../spec#tag/Forecasting/operation/getRegionalLimitsForecastSnapshot)
operation.

After receiving RLRs from all neighbors in addition to generating its own, the TROLIE instance can generate a regular
snapshot that consists of [Globally Limiting Ratings](../concepts.md#globally-limiting-rating) or GLRs, that incorporate
the most conservative RLRs for each facility. Only these GLR snapshots should be visible via
[getLimitsForecastSnapshot](../spec#tag/Forecasting/operation/getLimitsForecastSnapshot), which represents the final
ratings that may be used in key decisions such as markets, look-ahead unit commitment and dispatch, and outage
coordination.

Consider the following sequence for a particular forecast interval. Note that certain events can easily occur out of order,
as each neighbor's system will be making the same forecast decisions at slightly different times:

```mermaid
sequenceDiagram
participant Clock
participant RatingProviders
participant RC TROLIE
participant Neighbor RC 1
participant Neighbor RC 2
participant Neighbor RC 3
critical Establish polling for RLR snapshots
RC TROLIE->>Neighbor RC 1:
RC TROLIE->>Neighbor RC 2:
RC TROLIE->>Neighbor RC 3:
end
RatingProviders->>RC TROLIE: Proposals
Neighbor RC 2->>RC TROLIE: Return RLR snapshot
Clock->>RC TROLIE: Trigger for RLR clearinghouse
RC TROLIE->>RC TROLIE: Generate RLR snapshot
Neighbor RC 2->>RC TROLIE: Return RLR snapshot
Clock->>RC TROLIE: Trigger for GLR clearinghouse
RC TROLIE->>RC TROLIE: Generate GLR snapshot
```

Each of the steps above deserves some more context. The following listing describes each event, referencing the
sequence numbers in the diagram above:

1. The RC TROLIE server needs to poll for new RLRs against Neighbor RC 1. This leverages the [Conditional GET](./conditional-GET.md) pattern, against [getRegionalLimitsForecastSnapshot](../spec#tag/Forecasting/operation/getRegionalLimitsForecastSnapshot).
2. Same as #1 for Neighbor RC 2
3. Same as #1 for Neighbor RC 3
4. Ratings Providers submit proposals, or [LLRs](../concepts.md#locally-limiting-rating), to TROLIE to be processed by the clearinghouse.
5. Neighbor RC 2 may clear its RLRs before the RC TROLIE is ready to clear its own RLRs. The RC TROLIE must account for this in its design.
6. At an appointed time, the forecast clearinghouse for RLRs starts. All proposals for that forecast window _should_ have been submitted.
7. RC TROLIE generates a forecast snapshot with RLRs. It is available for other neighbor RCs to query.
8. Neighbor RC2 clears its RLRs, shortly after they are cleared in the RC TROLIE. RC TROLIE becomes aware of this for use in generating GLRs.
9. At an appointed time, the forecast clearinghouse for GLRs starts. All RLR forecasts from neighboring RCs _should_ have been created and be visible to the RC TROLIE.
10. RC TROLIE generates GLR snapshot, that makes deltas to its original RLR snapshot, accommodating the RLR snapshots it received from Neighbor RCs 1 and 2. Note that it never received anything from RC 3. This is a contingency that the RC TROLIE must handle. If RC 3 doesn't generate a forecast on time, then the RC TROLIE should be explicitly aware of that and engage in whatever recourse logic necessary to handle missing data from RC 3.

# Reconciling Real-Time ClearingHouses
Reconciling forecast ratings is particularly challenging, because the difference between RLRs and
GLRs must be reconciled for each discrete [Forecast Window](./forecast-windows.md).

Real-time however is more of an eventually consistent flow, constantly converging. This is because
there is no "target" window for a given rating. Rather, the value of the rating in use _now_ is
constantly being updated, based on the best knowledge a given RC has about the RLRs of the other
RC. Like forecast, there is a dedicated operation
[getRegionalRealTimeLimits](../spec#tag/Real-Time/operation/getRegionalRealTimeLimits) to facilitate
polling by the peers. However, rather than wait for new RLR values, the clearinghouse can
immediately generate new GLRs.

See the following sequence for an example:

```mermaid
sequenceDiagram
participant Clock
participant RatingProviders
participant RC TROLIE
participant Neighbor RC
critical Establish polling for RLR snapshots
RC TROLIE->>Neighbor RC:
end
RatingProviders->>RC TROLIE: Proposals
Neighbor RC->>RC TROLIE: Return RLR snapshot
Clock->>RC TROLIE: Trigger for clearinghouse
Note left of Clock: Time T
RC TROLIE->>RC TROLIE: Generate RLR snapshot based on proposals
RC TROLIE->>RC TROLIE: Generate GLR snapshot based on best known RLRs
RatingProviders->>RC TROLIE: Proposals
Neighbor RC->>RC TROLIE: Return RLR snapshot
Clock->>RC TROLIE: Trigger for clearinghouse
Note left of Clock: Time T + 1
RC TROLIE->>RC TROLIE: Generate RLR snapshot based on proposals
RC TROLIE->>RC TROLIE: Generate GLR snapshot based on best known RLRs
```

At any given time, the clearinghouse is simply using the most recent version of the RLRs
it has from neighbors in determining the GLRs. During each clearinghouse run, the
age of neighboring RLRs, as well as real-time proposals, should be validated for age,
and recourse methodologies triggered if they are too old.

# Monitoring the RC to RC Seam
The interactions described above can and should be monitored for latency, as well as the
absence of expected events such as missing RLR forecast snapshots from a particular peer.

In addition, the derived seam monitoring set between peers,
described [here](./peer-monitoring-sets.md#seam-monitoring-sets), may be used to do more
thorough functional validations that multiple TROLIEs are configured and behaving
correctly. Specifically, the following conditions must be true:

* Since both TROLIEs are generating seam monitoring sets, these monitoring sets should represent the same set of resources. The monitoring sets should have the same size, and each resource should have an alias that maps to the primary identifier or one of the aliases in the other monitoring set.
* For a given forecast window, the result of [getLimitsForecastSnapshot](../spec#tag/Forecasting/operation/getLimitsForecastSnapshot) (the GLRs) should be identical in each TROLIE.
Loading

0 comments on commit 0d1d7df

Please sign in to comment.