feat(workflow): publish verified forecast controls and consolidated docs
Tests / test (push) Successful in 1m1s

Approved by Daniel Haefliger for develop and beta. Author dh_Agent, authenticated account dh. Preserve published battery, charging and overall Energy Pie changes. No deployment or plant control authorization.
This commit is contained in:
dh
2026-10-08 10:02:58 +00:00
parent af69151165
commit 74a2d6c685
82 changed files with 5054 additions and 617 deletions
@@ -144,7 +144,7 @@ def validate_config(c):
return c
def register_dataset(con, plant, c, now):
def register_dataset(con, plant, c, now, *, own_transaction=True):
"""Operator endpoint only; device append endpoint cannot change units or limits."""
validate_config(c)
if c.get('sourceDatasetId'):
@@ -155,15 +155,16 @@ def register_dataset(con, plant, c, now):
if canonical(comparable(origin)) != canonical(comparable(c)):
raise ValueError('Referenced observations must retain identical measurement meaning')
value = canonical(c)
con.execute('BEGIN IMMEDIATE')
if own_transaction: con.execute('BEGIN IMMEDIATE')
try:
old = con.execute('SELECT config FROM planner_data_sets WHERE plant=? AND dataset=?', (plant,c['datasetId'])).fetchone()
if old and old[0] != value:
raise ValueError('Dataset is immutable; use a new datasetId for changed measurement meaning')
con.execute('INSERT OR IGNORE INTO planner_data_sets VALUES(?,?,?,?)', (plant,c['datasetId'],value,now))
con.commit()
if own_transaction: con.commit()
except Exception:
con.rollback(); raise
if own_transaction: con.rollback()
raise
return {'status':'configured', 'datasetId':c['datasetId'], 'mappingSha256':c['mappingSha256'], 'controlEnabled':False}
@@ -483,11 +484,14 @@ def apply_load_forecast(con,plant,dataset,forecast,decision):
last=json.loads(last[0])
if decision-last['capturedAt']>120: raise ValueError('Corrected measurements older than 120 seconds')
sdl_sources=[s for s in c['sources'] if s['role']=='sdl_request']
if len(sdl_sources)!=1: raise ValueError('Explicit SDL request channel needed for the labelled persistence scenario')
s=sdl_sources[0];r=last['raw'][s['key']]
if not r['valid'] or decision-r['sourceUpdatedAt']>s['maxAgeSeconds']:
raise ValueError('No current external SDL request for the persistence scenario')
sdl=r['value']*s['factorToW']
if len(sdl_sources)>1: raise ValueError('At most one explicit SDL request channel is supported')
sdl=0.; external_observed=None; external_policy='no_external_sdl_channel'
if sdl_sources:
s=sdl_sources[0];r=last['raw'][s['key']]
if not r['valid'] or decision-r['sourceUpdatedAt']>s['maxAgeSeconds']:
raise ValueError('No current external SDL request for the persistence scenario')
sdl=r['value']*s['factorToW'];external_observed=iso(r['sourceUpdatedAt'])
external_policy='last_sdl_request_persistence_estimate'
result=json.loads(canonical(forecast)); result['families']={}
result['observedAt']=iso(max(availability_epoch(forecast['observedAt']),model['trainedAt'],last['capturedAt']))
for family,old in forecast['families'].items():
@@ -501,7 +505,7 @@ def apply_load_forecast(con,plant,dataset,forecast,decision):
'loadMethodVersion':model['methodVersion'],'loadVariant':family,
'loadModelTrainedAt':iso(model['trainedAt']),'pvForecastEventId':forecast.get('eventId'),
'measurementBasis':'configured_physical_estimate','measurementBoundaryVerified':False,
'externalPolicy':'last_sdl_request_persistence_estimate','externalObservedAt':iso(r['sourceUpdatedAt']),
'externalPolicy':external_policy,'externalObservedAt':external_observed,
'externalPowerW':sdl,'futureSdlPublished':False,'validation':model['validation'],
'historyTimingMethod':model.get('historyTimingMethod','strict_expiry'),
'observationDatasetId':model.get('observationDatasetId',dataset)}}