60 lines
6.7 KiB
PHP
60 lines
6.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
require_once __DIR__.'/../../libs/NetzfahrplanV4Messsicht.php';
|
|
require_once __DIR__.'/fixture.php';
|
|
use Belevo\EnelixEMS\NetzfahrplanV4Messaufnahme as Capture;
|
|
use Belevo\EnelixEMS\NetzfahrplanV4Messsicht as View;
|
|
$n=0;
|
|
function ck(bool $v,string $label):void{global $n;if(!$v)throw new RuntimeException('FAIL '.$label);$n++;echo 'PASS '.$label."\n";}
|
|
function refuses(callable $f,string $label):void{try{$f();}catch(Throwable $e){ck(true,$label);return;}throw new RuntimeException('Expected refusal: '.$label);}
|
|
function take($c,$p,$r,$now):array{return View::calculate(Capture::capture($c,static fn($id)=>$r[$id],static fn()=>$now),$c,$p);}
|
|
[$c,$p,$r,$now]=separatedFixture();$x=take($c,$p,$r,$now);
|
|
ck($x['physicalLoad']['loadCandidateW']===6000.,'physical sources subtracted once');
|
|
ck($x['solarAcAlternative']['loadCandidateW']===6000.,'solar AC terminal used without second battery subtraction');
|
|
ck($x['solarOrigin']['reconstructedPvCandidateW']===1000.,'original mantissa scale and battery retained');
|
|
ck($x['allocationModel']['unfilteredModelEvW']===2500.&&$x['allocationModel']['unfilteredModelSdlW']===500.,'unfiltered partition uses physical feedback');
|
|
ck(!$x['canDispatch']&&!$x['trainingEligible']&&!$x['allocationModel']['separatelyMeasured'],'no measurement or control permission invented');
|
|
$a=$r;$a[106]['value']=-9000;$x=take($c,$p,$a,$now);
|
|
ck($x['physicalLoad']['status']==='consistent_candidate'&&$x['physicalLoad']['loadCandidateW']===6000.,'held virtual output cannot contaminate physical load');
|
|
ck($x['allocationModel']['unfilteredModelEvW']===2500.&&!$x['allocationModel']['reportedAllocationWithinTolerance'],'held EV value only compared not reused');
|
|
$a=$r;$a[107]['value']='never-export-this';$x=take($c,$p,$a,$now);
|
|
ck($x['physicalLoad']['status']==='consistent_candidate','invalid SDL output does not veto physical load');
|
|
ck(!str_contains(json_encode($x),'never-export-this'),'unknown string is not propagated');
|
|
$a=$r;$a[108]['value']=42000;$a[109]['value']=-40000;$a[104]['value']=2000;$a[105]['value']=0;$x=take($c,$p,$a,$now);
|
|
ck($x['allocationModel']['unfilteredModelEvW']===42000.&&$x['allocationModel']['unfilteredModelSdlW']===-40000.,'opposed commands do not divide by near-zero net');
|
|
$a=$r;$a[108]['value']=0;$a[109]['value']=0;$x=take($c,$p,$a,$now);
|
|
ck($x['allocationModel']['unfilteredModelEvW']===null&&$x['allocationModel']['unallocatedPhysicalW']===3000.,'idle physical power kept unallocated not invented as EV');
|
|
$a=$r;$a[104]['value']=-3125;$a[105]['value']=0;$a[108]['value']=-10500;$a[109]['value']=0;$a[106]['value']=-8334;$a[107]['value']=0;$x=take($c,$p,$a,$now);
|
|
ck($x['allocationModel']['unfilteredModelEvW']===-3125.,'real export-example candidate is physical -3125 not held -8334');
|
|
ck($x['allocationModel']['status']==='quality_hold'&&in_array('physical_request_tracking_error',$x['allocationModel']['issues'],true),'tracking disagreement remains visible');
|
|
$a=$r;$a[108]['changed']=$now;$a[104]['updated']=$now-10;$x=take($c,$p,$a,$now);
|
|
ck(in_array('request_newer_than_feedback:req_ev',$x['allocationModel']['issues'],true),'post-command feedback not pretended');
|
|
$a=$r;unset($a[108]['changed']);$x=take($c,$p,$a,$now);
|
|
ck(in_array('request_change_time_unknown:req_ev',$x['allocationModel']['issues'],true),'unknown command change time explicit');
|
|
$a=$r;$a[105]['updated']=$now-70;$x=take($c,$p,$a,$now);
|
|
ck($x['storageFeedback']['status']==='quality_hold'&&$x['physicalLoad']['status']==='quality_hold','old physical source blocks physical feedback');
|
|
ck($x['solarAcAlternative']['status']==='consistent_candidate','AC alternative does not inherit unrelated solar battery age');
|
|
$a=$r;$a[111]['updated']=$now-70;$x=take($c,$p,$a,$now);
|
|
ck($x['solarAcAlternative']['status']==='quality_hold'&&$x['physicalLoad']['status']==='quality_hold','fresh solar derived output cannot hide old scale origin');
|
|
$a=$r;$a[110]['value']=500;$a[111]['value']=1;$a[103]['value']=6000;$x=take($c,$p,$a,$now);
|
|
ck($x['solarOrigin']['acTerminalCandidateW']===5000.&&$x['solarAcAlternative']['loadCandidateW']===11000.,'signed scale applied exactly once');
|
|
$a=$r;$a[110]['value']=-500;$a[111]['value']=0;$a[105]['value']=0;$a[103]['value']=0;$x=take($c,$p,$a,$now);
|
|
ck($x['solarOrigin']['reconstructedPvCandidateW']===-500.&&$x['solarAcAlternative']['loadCandidateW']===5500.,'negative AC not clamped or disguised as PV');
|
|
foreach([true,'0',INF,NAN,-32768]as$invalid){$a=$r;$a[111]['value']=$invalid;$x=take($c,$p,$a,$now);ck($x['solarOrigin']['acTerminalCandidateW']===null,'invalid scale has no numeric fallback');}
|
|
$a=$r;$a[110]['value']=-32768;$x=take($c,$p,$a,$now);ck($x['solarOrigin']['acTerminalCandidateW']===null,'SunSpec invalid power sentinel rejected');
|
|
$a=$r;$a[102]['updated']=$now-40;$x=take($c,$p,$a,$now);
|
|
ck($x['physicalLoad']['status']==='quality_hold'&&$x['storageFeedback']['status']==='consistent_candidate','slow PV does not veto independent storage feedback');
|
|
$a=$r;$a[108]['updated']=$now-90;$x=take($c,$p,$a,$now);ck($x['allocationModel']['status']==='quality_hold','old request timestamp visible');
|
|
$a=$r;$a[104]['updated']=$now+1;$x=take($c,$p,$a,$now);ck($x['storageFeedback']['status']==='quality_hold','future physical value refused');
|
|
$a=$r;$a[104]['parentID']=999;$x=take($c,$p,$a,$now);ck($x['storageFeedback']['physicalStorageCandidateW']===null,'reused measurement identity not accepted');
|
|
$a=$r;$a[101]['value']=-20000;$x=take($c,$p,$a,$now);ck($x['physicalLoad']['loadCandidateW']<0&&$x['physicalLoad']['status']==='quality_hold','negative load candidate retained as invalid not zero');
|
|
$a=$r;foreach($a as &$v)$v['value']=0;unset($v);$x=take($c,$p,$a,$now);ck($x['physicalLoad']['loadCandidateW']===0.&&$x['physicalLoad']['status']==='consistent_candidate','real zero observation accepted');
|
|
$raw=Capture::capture($c,static fn($id)=>$r[$id],static fn()=>$now);$raw['assessment']['baseLoadCandidateW']=999999.;$x=View::calculate($raw,$c,$p);ck($x['physicalLoad']['loadCandidateW']===6000.,'assessment independently recalculated');
|
|
foreach(['installationId','mappingSha256','reportedInventorySha256']as$key){$bad=$raw;$bad[$key]='wrong';refuses(fn()=>View::calculate($bad,$c,$p),'wrong identity '.$key);}
|
|
$bad=$p;$bad['sdlRequestKey']='req_ev';refuses(fn()=>View::calculate($raw,$c,$bad),'duplicate request channels refused');
|
|
$bad=$p;$bad['solarReference']['scaleKey']='soc';refuses(fn()=>View::calculate($raw,$c,$bad),'scale cannot be an arbitrary percent channel');
|
|
$before=json_encode([$raw,$c,$p]);View::calculate($raw,$c,$p);ck(json_encode([$raw,$c,$p])===$before,'inputs not mutated');
|
|
$src=file_get_contents(__DIR__.'/../../libs/NetzfahrplanV4Messsicht.php');ck(!preg_match('/\b(?:IPS_|GetValue\(|SetValue\(|RequestAction\(|curl_|exec\s*\()/',$src),'pure model has no control or native calls');
|
|
echo "TOTAL $n checks passed. Synthetic observations only.\n";
|