1000.,'pv'=>5000.,'physical_storage'=>3000.,'controlled_virtual'=>2000.,'external_virtual'=>1000.] as $role=>$value){ $id=101+count($s);$s[]=['key'=>$role,'role'=>$role,'variableId'=>$id,'parentId'=>10,'ident'=>$role,'factorToW'=>1.,'maxAgeSeconds'=>60,'dependsOn'=>[]]; $m[$id]=['value'=>$value,'updated'=>$now,'parentID'=>10,'ident'=>$role]; } $s[3]['dependsOn']=['physical_storage'];$s[4]['dependsOn']=['physical_storage']; return [['version'=>1,'sources'=>$s,'maxSkewSeconds'=>30,'splitToleranceW'=>100.],$m,$now]; } function take(array $c,array $m,int $now):array{return B::aufnehmen($c,static fn($id)=>$m[$id],$now);} [$c,$m,$n]=fixture();$r=take($c,$m,$n); check($r['baseLoadCandidateW']===3000.,'subtract physical battery once, not virtual twice'); check($r['externalEffectW']===1000. && $r['allocationResidualW']===0.,'SDL kept outside household'); check($r['reconstructedGridW']===1000. && $r['arithmeticBalanceErrorW']===0.,'signed PCC balance closes'); check($r['controlEligible']===false && !$r['meteringBoundaryVerified'],'algebra never grants control'); check(!$r['forecastHistoryMigrated'],'old forecasts never relabelled'); $m[101]['value']=-5000.;$m[103]['value']=-3000.;$m[104]['value']=-2000.;$m[105]['value']=-1000.;$r=take($c,$m,$n); check($r['baseLoadCandidateW']===3000. && $r['externalEffectW']===-1000.,'discharge and export signs correct'); [$c,$m,$n]=fixture();$m[103]['value']=2000.;$m[104]['value']=42000.;$m[105]['value']=-40000.;$r=take($c,$m,$n); check($r['allocationResidualW']===0. && $r['baseLoadCandidateW']===4000.,'opposed virtual accounts are not two physical loads'); [$c,$m,$n]=fixture();$m[104]['value']=0.;$m[105]['value']=0.;$r=take($c,$m,$n); check($r['externalEffectW']===3000. && $r['baseLoadCandidateW']===3000.,'held virtual values cannot contaminate base load'); check(in_array('virtual_physical_allocation_mismatch',$r['issues'],true),'split discrepancy explicit'); [$c,$m,$n]=fixture();$m[103]['updated']=$n-120;$r=take($c,$m,$n); check(in_array('stale_origin:physical_storage',$r['issues'],true),'old physical reading rejected despite new acquisition'); check(in_array('stale_origin:controlled_virtual',$r['issues'],true),'fresh virtual timestamp cannot mask old physical input'); check($r['sources']['controlled_virtual']['oldestOriginAt']===$n-120,'original dependency timestamp preserved'); check($r['status']==='quality_hold','bad quality remains a diagnostic, not a training sample'); [$c,$m,$n]=fixture();$m[102]['updated']=$n-31;$r=take($c,$m,$n);check(in_array('source_time_skew',$r['issues'],true),'skew rejected separately from age'); $m[102]['updated']=$n+1;$r=take($c,$m,$n);check(in_array('future_source:pv',$r['issues'],true),'future timestamp rejected'); [$c,$m,$n]=fixture();foreach($m as &$v)$v['value']=0.;unset($v);$r=take($c,$m,$n);check($r['status']==='consistent_candidate' && $r['baseLoadCandidateW']===0.,'true all-zero observation accepted without fake history'); [$c,$m,$n]=fixture();$m[101]['value']=-6000.;$r=take($c,$m,$n);check($r['baseLoadCandidateW']===-4000. && in_array('negative_base_load',$r['issues'],true),'negative residual not silently clipped to zero'); [$c,$m,$n]=fixture();$m[102]['value']=-10.;check(in_array('negative_unsigned_source:pv',take($c,$m,$n)['issues'],true),'PV sign validity'); [$c,$m,$n]=fixture();$m[103]['value']=-3000.;$c['sources'][2]['factorToW']=-1.;check(take($c,$m,$n)['baseLoadCandidateW']===3000.,'explicit vendor sign conversion'); [$c,$m,$n]=fixture();$m[101]['value']=1.;$c['sources'][0]['factorToW']=1000.;check(take($c,$m,$n)['baseLoadCandidateW']===3000.,'kW to W factor applied once'); [$c,$m,$n]=fixture();$c['sources'][]=['key'=>'boiler','role'=>'flexible_load','variableId'=>200,'parentId'=>10,'ident'=>'boiler','factorToW'=>1.,'maxAgeSeconds'=>60,'dependsOn'=>[]];$m[200]=['value'=>500.,'updated'=>$n,'parentID'=>10,'ident'=>'boiler'];$r=take($c,$m,$n);check($r['baseLoadCandidateW']===2500. && $r['reconstructedGridW']===1000.,'future flexible load excluded exactly once'); foreach([true,'1000',NAN,INF] as $bad){[$c,$m,$n]=fixture();$m[101]['value']=$bad;fails(fn()=>take($c,$m,$n),'invalid numeric input '.gettype($bad));} [$c,$m,$n]=fixture();$m[101]['parentID']=99;fails(fn()=>take($c,$m,$n),'different source parent rejected'); [$c,$m,$n]=fixture();$m[101]['ident']='other';fails(fn()=>take($c,$m,$n),'different source ident rejected'); [$c,$m,$n]=fixture();$c['sources'][]=$c['sources'][0];fails(fn()=>take($c,$m,$n),'duplicate physical source rejected'); [$c,$m,$n]=fixture();$c['sources'][3]['dependsOn']=['absent'];fails(fn()=>take($c,$m,$n),'missing origin rejected'); [$c,$m,$n]=fixture();$c['sources'][2]['dependsOn']=['controlled_virtual'];fails(fn()=>take($c,$m,$n),'dependency loop rejected'); [$c,$m,$n]=fixture();$a=take($c,$m,$n)['mappingId'];$c['sources']=array_reverse($c['sources']);check(take($c,$m,$n)['mappingId']===$a,'source order does not change mapping identity'); $c['sources'][0]['factorToW']=2.;check(take($c,$m,$n)['mappingId']!==$a,'factor changes invalidate old mapping identity'); [$c,$m,$n]=fixture();$calls=[]; fails(function()use($c,$m,$n,&$calls){B::aufnehmen($c,function($id)use($m,&$calls){$calls[$id]=($calls[$id]??0)+1;$v=$m[$id];if($calls[$id]>1)$v['value']++;return $v;},$n);},'same-second concurrent value change rejected'); $source=file_get_contents(__DIR__.'/../../libs/NetzfahrplanV4Bilanzierung.php');check(!preg_match('/\b(?:SetValue|IPS_RequestAction|RequestAction|curl_exec|IPS_SetProperty|AC_SetLoggingStatus)\s*\(/',$source),'adapter has no runtime or external write calls'); [$c,$m,$n]=fixture();$integer=$c;$integer['splitToleranceW']=100; check(B::konfigurieren($c)===B::konfigurieren($integer),'integer and float tolerance normalize identically'); check(take($c,$m,$n)['mappingId']===take($integer,$m,$n)['mappingId'],'equal tolerance gives identical mapping hash'); $roundtrip=json_decode(json_encode($c,JSON_THROW_ON_ERROR),true,64,JSON_THROW_ON_ERROR); check(take($roundtrip,$m,$n)['mappingId']===take($c,$m,$n)['mappingId'],'plain JSON property roundtrip preserves mapping'); $normalized=B::konfigurieren($c);check(B::konfigurieren($normalized)===$normalized,'normalization remains idempotent'); check(is_int($integer['splitToleranceW']) && is_float($c['splitToleranceW']),'input configurations are not mutated'); $changed=$c;$changed['splitToleranceW']=101; check(take($changed,$m,$n)['mappingId']!==take($c,$m,$n)['mappingId'],'real tolerance change still changes identity'); foreach([true,'100'] as $invalid){$bad=$c;$bad['splitToleranceW']=$invalid;fails(fn()=>B::konfigurieren($bad),'non-numeric tolerance remains rejected '.gettype($invalid));} echo "TOTAL $count checks passed; synthetic snapshots only.\n";