feat(workflow): publish verified forecast controls and consolidated docs
Tests / test (push) Successful in 1m1s
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:
@@ -29,6 +29,13 @@ final class NetzfahrplanV4Bilanzierung
|
||||
public static function konfigurieren(array $c): array
|
||||
{
|
||||
$fields = ['version','sources','maxSkewSeconds','splitToleranceW'];
|
||||
$physicalOnly = ($c['storageMode'] ?? null) === 'physical_only';
|
||||
if (array_key_exists('storageMode', $c)) {
|
||||
if (!$physicalOnly) {
|
||||
throw new InvalidArgumentException('Unbekannte Speicherbilanzierung.');
|
||||
}
|
||||
$fields[] = 'storageMode';
|
||||
}
|
||||
if (count($c) !== count($fields) || array_diff($fields,array_keys($c)) || $c['version'] !== 1
|
||||
|| !is_array($c['sources']) || !self::listArray($c['sources']) || count($c['sources']) < 3 || count($c['sources']) > 80
|
||||
|| !is_int($c['maxSkewSeconds']) || $c['maxSkewSeconds'] < 0 || $c['maxSkewSeconds'] > 60) {
|
||||
@@ -57,9 +64,13 @@ final class NetzfahrplanV4Bilanzierung
|
||||
sort($s['dependsOn']);$keys[$s['key']]=true;$seen[$s['variableId']]=true;$roles[$s['role']]=($roles[$s['role']]??0)+1;
|
||||
}
|
||||
unset($s);
|
||||
if (($roles['grid']??0)!==1 || !isset($roles['pv'],$roles['physical_storage'],$roles['controlled_virtual'])) {
|
||||
if (($roles['grid']??0)!==1 || !isset($roles['pv'],$roles['physical_storage'])
|
||||
|| (!$physicalOnly && !isset($roles['controlled_virtual']))) {
|
||||
throw new InvalidArgumentException('Netz, PV, physische und steuerbare virtuelle Batterie muessen explizit zugeordnet sein.');
|
||||
}
|
||||
if ($physicalOnly && (isset($roles['controlled_virtual']) || isset($roles['external_virtual']))) {
|
||||
throw new InvalidArgumentException('Physische Speicherbilanz darf keine virtuellen Anteile enthalten.');
|
||||
}
|
||||
$graph=[];foreach ($c['sources'] as $s) $graph[$s['key']]=$s['dependsOn'];
|
||||
$visit=function($key,$stack=[]) use (&$visit,$graph): void {
|
||||
if (!array_key_exists($key,$graph) || isset($stack[$key])) throw new InvalidArgumentException('Fehlende oder zyklische Ursprungsquelle.');
|
||||
@@ -101,11 +112,13 @@ final class NetzfahrplanV4Bilanzierung
|
||||
$load=$totals['grid']+$totals['pv']-$totals['physical_storage'];
|
||||
$base=$load-$totals['flexible_load'];
|
||||
// This channel preserves unallocated dynamics instead of hiding them in house load.
|
||||
$residual=$totals['physical_storage']-$totals['controlled_virtual']-$totals['external_virtual'];
|
||||
$external=$totals['external_virtual']+$residual;
|
||||
$physicalOnly = ($c['storageMode'] ?? null) === 'physical_only';
|
||||
$residual=$physicalOnly ? 0.0 : $totals['physical_storage']-$totals['controlled_virtual']-$totals['external_virtual'];
|
||||
$external=$physicalOnly ? 0.0 : $totals['external_virtual']+$residual;
|
||||
if ($base<0) $issues[]='negative_base_load';
|
||||
if (abs($residual)>$c['splitToleranceW']) $issues[]='virtual_physical_allocation_mismatch';
|
||||
$reconstructed=$base+$totals['flexible_load']+$totals['controlled_virtual']+$external-$totals['pv'];
|
||||
$storage=$physicalOnly ? $totals['physical_storage'] : $totals['controlled_virtual']+$external;
|
||||
$reconstructed=$base+$totals['flexible_load']+$storage-$totals['pv'];
|
||||
$identity=hash('sha256',json_encode($c,JSON_THROW_ON_ERROR|JSON_PRESERVE_ZERO_FRACTION));
|
||||
return ['version'=>1,'status'=>$issues===[]?'consistent_candidate':'quality_hold',
|
||||
'capturedAt'=>gmdate('c',$now),'sourceObservedFrom'=>gmdate('c',min($times)),'sourceObservedUntil'=>gmdate('c',max($times)),
|
||||
|
||||
Reference in New Issue
Block a user