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
+214
View File
@@ -0,0 +1,214 @@
"""Authenticated data onboarding and observed workflow, never actuator authority."""
from datetime import timedelta
from uuid import UUID
import json
from . import measurement_pipeline
from .domain import utc
from .receiver_contract import control_context
REPORT_MAX_AGE_SECONDS = 120
def canonical(value):
return json.dumps(value, sort_keys=True, separators=(',', ':'), allow_nan=False)
def schema(con):
con.executescript('''
CREATE TABLE IF NOT EXISTS planner_enrollment(
plant TEXT PRIMARY KEY, dataset TEXT NOT NULL, learning_enabled INTEGER NOT NULL,
created_at TEXT NOT NULL, updated_at TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS planner_workflow_report(
plant TEXT PRIMARY KEY, observed_at TEXT NOT NULL, received_at TEXT NOT NULL,
value TEXT NOT NULL);
''')
def identity(plant, value):
if not isinstance(plant, str) or str(UUID(plant)) != plant:
raise ValueError('Canonical installation UUID required')
if type(value.get('version')) is not int or value['version'] != 1 or value.get('installationId') != plant:
raise ValueError('Installation path/payload mismatch or unsupported version')
def enrolled(con, plant):
return con.execute('SELECT * FROM planner_enrollment WHERE plant=?', (plant,)).fetchone()
def learning_enabled(con, plant):
row = enrolled(con, plant)
if row is not None:
return bool(row['learning_enabled'])
report_row = con.execute('SELECT value FROM planner_workflow_report WHERE plant=?', (plant,)).fetchone()
return report_row is None or json.loads(report_row['value'])['learningEnabled']
def setup(store, plant, value, now):
identity(plant, value)
if set(value) != {'version', 'installationId', 'dataset', 'learningEnabled'} or type(value['learningEnabled']) is not bool:
raise ValueError('Explicit dataset and boolean learningEnabled required')
config = measurement_pipeline.validate_config(value['dataset'])
# Compatibility proofs and relaxed history-timing policies remain operator-only.
existing = store.con.execute('SELECT config FROM planner_data_sets WHERE plant=? AND dataset=?', (plant, config['datasetId'])).fetchone()
if not existing and (config.get('sourceDatasetId') or config.get('historyTimingPolicy')):
raise ValueError('Device setup cannot grant history compatibility or timing exceptions')
if not existing and (config['minimumCoverage'] < .95 or config['maximumGapSeconds'] > 5 or config['minimumTrainingHours'] < 24):
raise ValueError('New datasets require coverage >= .95, gap <= 5s and training >= 24h')
con = store.con
con.execute('BEGIN IMMEDIATE')
try:
measurement_pipeline.register_dataset(con, plant, config, int(now.timestamp()), own_transaction=False)
current = store.settings(plant)
initialized = con.execute('SELECT 1 FROM planner_settings WHERE plant=?', (plant,)).fetchone() is None
if initialized:
# Native interval estimates retain explicit provenance and gap checks;
# this is a planning policy, never billing or actuator evidence.
current.update(forecastSource='corrected_profile', measurementDataset=config['datasetId'], trainingCadence='daily', measurementPolicy='allow_estimates')
current.pop('revision')
store._validate_settings(current)
con.execute('INSERT INTO planner_settings VALUES(?,?,?)', (plant, 1, canonical(current)))
store._request(plant, 1, 'onboarding', now)
con.execute('INSERT INTO planner_audit(plant,at,kind,detail) VALUES(?,?,?,?)',
(plant, utc(now).isoformat(), 'onboarding', canonical({'datasetId': config['datasetId'], 'controlEnabled': False})))
stamp = utc(now).isoformat()
con.execute('''INSERT INTO planner_enrollment VALUES(?,?,?,?,?)
ON CONFLICT(plant) DO UPDATE SET dataset=excluded.dataset,
learning_enabled=excluded.learning_enabled,updated_at=excluded.updated_at''',
(plant, config['datasetId'], int(value['learningEnabled']), stamp, stamp))
con.commit()
except Exception:
con.rollback()
raise
settings = store.settings(plant)
return {'status': 'configured', 'installationId': plant, 'datasetId': config['datasetId'],
'selectedDatasetId': settings['measurementDataset'], 'settingsInitialized': initialized,
'settingsRevision': settings['revision'], 'learningEnabled': value['learningEnabled'],
'controlEnabled': False, 'controlledTrialAuthorized': False}
def report(store, plant, value, now):
identity(plant, value)
if set(value) != {'version', 'installationId', 'observedAt', 'learningEnabled', 'controlRequested', 'controlActive', 'reason'}:
raise ValueError('Explicit workflow report required')
for key in ('learningEnabled', 'controlRequested', 'controlActive'):
if type(value[key]) is not bool:
raise ValueError('Workflow flags must be boolean')
reason = value['reason']
if not isinstance(reason, str) or len(reason) > 300 or any(ord(c) < 32 for c in reason):
raise ValueError('Workflow reason must be plain text up to 300 characters')
observed = measurement_pipeline.epoch(value['observedAt'])
if not -30 <= now.timestamp() - observed <= REPORT_MAX_AGE_SECONDS:
raise ValueError('Fresh whole-second UTC workflow report required')
if value['controlActive'] and not (value['learningEnabled'] and value['controlRequested']):
raise ValueError('Active control requires learning and requested control')
normalized = {**value, 'observedAt': measurement_pipeline.iso(observed)}
data = canonical(normalized)
con = store.con
con.execute('BEGIN IMMEDIATE')
try:
old = con.execute('SELECT observed_at,value FROM planner_workflow_report WHERE plant=?', (plant,)).fetchone()
if old and utc(old['observed_at']).timestamp() > observed:
raise ValueError('Workflow report moved backwards')
if old and utc(old['observed_at']).timestamp() == observed:
if old['value'] != data:
raise ValueError('Conflicting workflow report at the same time')
con.commit()
return {'status': 'duplicate', 'controlEnabled': False}
con.execute('''INSERT INTO planner_workflow_report VALUES(?,?,?,?)
ON CONFLICT(plant) DO UPDATE SET observed_at=excluded.observed_at,
received_at=excluded.received_at,value=excluded.value''',
(plant, normalized['observedAt'], utc(now).isoformat(), data))
# The authenticated first switch also pauses autonomous training. A stop
# remains effective after report expiry; stale telemetry cannot re-enable it.
registration = enrolled(con, plant)
if registration and observed >= int(utc(registration['updated_at']).timestamp()):
con.execute('UPDATE planner_enrollment SET learning_enabled=?,updated_at=? WHERE plant=?',
(int(value['learningEnabled']), utc(now).isoformat(), plant))
con.commit()
except Exception:
con.rollback()
raise
return {'status': 'recorded', 'controlEnabled': False}
def planning_continuity(store, plant, state, now):
"""Keep readiness during routine telemetry replans, never renew a receipt.
This does not change receiver ``fresh``. The Manager must still validate its
originally accepted envelope and stop when that local envelope expires.
"""
if state['fresh']:
return True
try:
pending = state['pending']
plan = state['plan']
settings = state['settings']
run = state['lastRun']
if not pending or not plan or not run:
return False
reasons = json.loads(pending['reasons'])
if not isinstance(reasons, list) or not reasons or not set(reasons) <= {'operation_changed', 'telemetry_changed', 'five_minute_tick'}:
return False
revision = settings['revision']
if pending['revision'] != revision or plan['configRevision'] != revision:
return False
if run['status'] not in ('optimal', 'feasible_time_limit') or run['detail'].get('planId') != plan['planId']:
return False
if run['detail'].get('configRevision') != revision:
return False
if not utc(plan['validFrom']) <= now < utc(plan['validUntil']) or not 0 <= (now-utc(plan['generatedAt'])).total_seconds() <= 900:
return False
rows = store.con.execute('''SELECT i.kind,i.value FROM planner_inputs i
JOIN planner_input_current c ON i.plant=c.plant AND i.kind=c.kind AND i.event_id=c.event_id
WHERE i.plant=? AND i.kind IN ('operation','forecast','tariffs')''', (plant,))
inputs = {row['kind']: json.loads(row['value']) for row in rows}
if any(plan['inputRefs'][kind] != inputs[kind]['eventId'] for kind in ('forecast', 'tariffs')):
return False
operation = inputs['operation']
if not 0 <= (now-utc(operation['observedAt'])).total_seconds() <= 120:
return False
return control_context(operation) == plan['controlContext']
except (KeyError, TypeError, ValueError, AttributeError):
return False
def view(store, plant, state, now):
settings = state['settings']
registration = enrolled(store.con, plant)
row = store.con.execute('SELECT value FROM planner_workflow_report WHERE plant=?', (plant,)).fetchone()
report_value = json.loads(row['value']) if row else None
source = utc(report_value['observedAt']) if report_value else None
report_fresh = bool(source and 0 <= (now-source).total_seconds() <= REPORT_MAX_AGE_SECONDS)
learning = bool(registration['learning_enabled']) if registration else bool(report_fresh and report_value['learningEnabled'])
requested = bool(report_value and report_value['controlRequested'])
dataset = settings['measurementDataset']
model = measurement_pipeline.current_model(store.con, plant, dataset, int(now.timestamp())) if dataset else None
pipeline = next((d for d in state['dataPipeline']['datasets'] if d['datasetId'] == dataset), {})
plan = state.get('plan') or {}
selected = plan.get('inputQuality', {}).get('dataPipeline', {})
ready = bool(settings['forecastSource'] == 'corrected_profile' and model and planning_continuity(store, plant, state, now)
and plan.get('executable') is True and selected.get('datasetId') == dataset
and selected.get('modelId') == model['modelId'])
active = bool(learning and requested and report_fresh and report_value['controlActive'] and ready)
if active:
name, reason = 'active', report_value['reason'] or 'manager_control_active'
elif requested:
name = 'interrupted'
reason = 'manager_report_stale' if not report_fresh else report_value['reason'] or ('planning_not_ready' if not ready else 'manager_control_not_active')
elif not learning:
name, reason = 'disabled', 'learning_disabled'
elif ready:
name, reason = 'ready', 'plan_and_model_ready'
elif model:
name = 'learning'
reason = (state.get('lastRun') or {}).get('detail', {}).get('reason') or 'awaiting_fresh_plan'
elif pipeline.get('status') in ('training', 'candidate_pending'):
name, reason = 'learning', 'model_training'
else:
name, reason = 'collecting', 'collecting_measurements' if registration else 'setup_required'
return {'state': name, 'reason': reason, 'learningEnabled': learning, 'controlRequested': requested,
'controlActive': active, 'planningReady': ready, 'checkedAt': utc(now).isoformat(),
'sourceReportAt': source.isoformat() if source else None,
'sourceFreshUntil': (source+timedelta(seconds=REPORT_MAX_AGE_SECONDS)).isoformat() if source else None,
'reportFresh': report_fresh, 'usingPreviousPlan': bool(ready and not state['fresh'])}