<?php
declare(strict_types=1);
// Independent PDO source oracle; no application/ORM method or HTTP request.
date_default_timezone_set('Asia/Shanghai');require __DIR__.'/common.php';$db=smPdo();$lane=getenv('SM7_LANE');$responses=smLoad('responses-private.json');$wireResponses=json_decode(file_get_contents(getenv('SM7_RUNTIME').'/responses-private.json'),false,512,JSON_THROW_ON_ERROR);$pre=smLoad('preflight-private.json');$meta=smLoad('dishes-metadata-private.json');
$types=[];foreach($meta as $v){$types[strtolower($v['column_name'])]=$v;}
$cast=static function(string $field)use($lane):string{return 'CAST('.$field.' AS '.($lane==='B'?'CHAR':'VARCHAR(80)').')';};
$dec=static function(string $f)use($types,$lane,$cast):string{$m=$types[$f];if(strtolower($m['data_type'])!=='decimal'||(int)($m[$lane==='B'?'numeric_precision':'data_precision'])>10||(int)($m[$lane==='B'?'numeric_scale':'data_scale'])!==2){throw new RuntimeException('Only proven narrow decimals');}return $cast('d.'.$f);};
$canon=static function($v,int $scale):?string{return $v===null?null:bcadd((string)$v,'0',$scale);};
$config=[];foreach(smRows($db,"SELECT config_key,config_value FROM ydy_config WHERE status=1 AND config_key IN ('ossDisjunctor','ossHost','businessIdentify')") as $r){$config[$r['config_key']]=$r['config_value'];}
$prefix=($config['ossHost']??'').'/'.(!empty($config['ossDisjunctor'])?($config['businessIdentify']??'').'/':'');
$url=static function($p)use($prefix):string{return empty($p)?'':str_replace('\\','/',$prefix.$p);};
$tag=static function(array $d,array $cook):array{
    if(!$d['weight']){return [];}$ban=in_array($d['sub_category'],[1,2,3,4,5,6,7,9,10,14])?1:2;$method=[];foreach($cook as $c){$method[$c['cook_method']]=($method[$c['cook_method']]??0)+$c['composition_ratio'];}arsort($method);$top=array_keys($method)[0]??null;$notFried=!in_array($top,[14,15,16,17,18,19]);$per=static function(string $f)use($d){return round($d[$f]/$d['weight'],2)*100;};$found=[];$vit=false;$min=false;$carb=false;$protein=false;
    if($d['energy']&&$per('energy')<=40/$ban&&round($d['fat']*9/($d['energy']*100),2)<=50&&$notFried){$found[]='控体重';}
    if(($d['category']==1||$ban===2)&&$per('carbohydrate')>22.5/$ban){$found[]='提耐力';$carb=true;}
    if($per('protein')>=12/$ban){$found[]='增肌力';$protein=true;}
    if($per('dietary_fiber')>=6/$ban){$found[]='调肠道';}
    if((in_array($d['category'],[1,3])||$ban===2)&&$per('iron')>=4.5/$ban){$found[]='调贫血';$min=true;}
    if($per('calcium')>=240/$ban){$found[]='防抽筋';$min=true;}
    if($per('vb1')>=0.21/$ban){$found[]='促供能';$vit=true;}
    if($per('vc')>=30/$ban){$found[]='调免疫';$vit=true;}
    foreach(['vb2'=>0.42,'va'=>240,'ve'=>4.2,'vpp'=>4.2] as $f=>$limit){if($per($f)>=$limit/$ban){$vit=true;}}
    foreach(['potassium'=>600,'zinc'=>4.5,'magnesium'=>40] as $f=>$limit){if($per($f)>=$limit/$ban){$min=true;}}
    if(($carb||$protein)&&($vit||$min)){$found[]='促恢复';}$out=[];foreach(['促恢复','提耐力','增肌力','调贫血','控体重','调免疫','防抽筋','调肠道','促供能'] as $v){if(in_array($v,$found,true)){$out[]=$v;}}return array_slice($out,0,3);
};
$strict=static function($a,$b,string $p='$')use(&$strict):?string{if(gettype($a)!==gettype($b)){return $p.':type';}if(is_object($a)){$a=get_object_vars($a);$b=get_object_vars($b);ksort($a);ksort($b);}if(is_array($a)){if(array_keys($a)!==array_keys($b)){return $p.':keys';}foreach($a as $k=>$v){if(($err=$strict($v,$b[$k],$p.'.'.$k))!==null){return $err;}}return null;}return $a===$b?null:$p.':value';};
$nutr=['va'=>'total_va','ve'=>'total_ve','dietary_fiber'=>'total_dietary_fiber','retinol'=>'total_retinol','vb1'=>'total_vb1','vb2'=>'total_vb2','vpp'=>'total_vpp','sodium'=>'total_sodium','calcium'=>'total_calcium','iron'=>'total_iron','vc'=>'total_vc','renieratene'=>'total_tenieratene','potassium'=>'total_potassium','magnesium'=>'total_magnesium','manganese'=>'total_manganese','zinc'=>'total_zinc','cuprum'=>'total_cuprum','phosphorus'=>'total_phosphorus','selenium'=>'total_selenium'];
$expected=[];$proof=[];$summary=[];$errors=[];
try{foreach($responses as $name=>$response){$p=$response['params'];$rawSource=[];$exp=null;
 if($name==='DISHES'){
    $sql='SELECT d.uuid,d.uuid AS dishes_uuid,d.name,d.en_name,d.unit,d.file_path,'.$dec('weight').' AS weight,d.category,d.sub_category,'.$cast('ROUND(d.energy)').' AS power,'.$cast('ROUND(d.carbohydrate)').' AS carbohydrate,'.$cast('ROUND(d.protein)').' AS protein,'.$cast('ROUND(d.fat)').' AS fat FROM ydy_dishes d WHERE d.is_del=1 AND d.status=1 AND d.category=? ORDER BY d.en_name ASC';
    $dishes=smRows($db,$sql,[(int)$p['type']]);foreach($dishes as &$d){$d['file_path']=$url($d['file_path']);$d['weight']=$canon($d['weight'],2);foreach(['power','carbohydrate','protein','fat'] as $f){$d[$f]=$canon($d[$f],0);}}unset($d);
    $fields='d.uuid,d.uuid AS dishes_uuid,d.name,d.en_name,d.unit,d.file_path,'.$dec('weight').' AS weight,d.unit_number,d.category,d.sub_category,'.$dec('energy').' AS power,'.$dec('carbohydrate').' AS carbohydrate,'.$dec('protein').' AS protein,'.$dec('fat').' AS fat';foreach($nutr as $f=>$alias){$fields.=','.$dec($f).' AS '.$alias;}
    $recipes=smRows($db,'SELECT '.$fields.' FROM ydy_recipes r JOIN ydy_dishes d ON d.uuid=r.dishes_uuid WHERE r.menu_date=? AND r.meal_times=? AND r.restaurant_id=? AND d.category=? AND d.status=1 AND d.is_del=1',[$p['date'],$p['meal_times'],$pre['recipe']['restaurant'],(int)$p['type']]);
    if(count($recipes)>100){throw new RuntimeException('Do not compare a new random sample');}$rawSource=['dishes'=>$dishes,'recipes'=>$recipes];$units=[1=>'个',2=>'份',3=>'盘',4=>'碗',5=>'片',6=>'块',7=>'杯',8=>'勺',9=>'格',10=>'条',11=>'根',12=>'袋',13=>'瓶',14=>'铲'];
    $names=array_column($recipes,'en_name');array_multisort($names,SORT_ASC,$recipes);
    foreach($recipes as &$r){$raw=smRows($db,'SELECT * FROM ydy_dishes WHERE uuid=?',[$r['uuid']]);$cook=smRows($db,'SELECT cook_method,composition_ratio FROM ydy_dish WHERE dishes_uuid=?',[$r['uuid']]);$r['tag']=$tag($raw[0],$cook);$r['unit']=$units[$r['unit']]??$r['unit'];foreach(['power','carbohydrate','protein','fat'] as $f){$r[$f]=round((float)$r[$f]);}foreach(array_merge(['weight'],array_values($nutr)) as $f){$r[$f]=$canon($r[$f],2);}$r['file_path']=$url($r['file_path']);}unset($r);$exp=['dishes'=>$dishes,'recipes'=>$recipes];
 }elseif($name==='RECIPE_INFO'){
    $map=['dishes_uuid'=>'r.dishes_uuid','uuid'=>'d.uuid','name'=>'d.name','unit'=>'d.unit','weight'=>$dec('weight'),'category'=>'d.category','file_path'=>'d.file_path'];foreach(['energy','carbohydrate','protein','fat'] as $f){$map['total_'.$f]=$dec($f);$map[$f]=$dec($f);}foreach($nutr as $f=>$alias){$map[$alias]=$dec($f);}$fields=[];foreach($map as $alias=>$expr){$fields[]=$expr.' AS '.$alias;}
    $exp=smRows($db,'SELECT '.implode(',',$fields).' FROM ydy_recipes r JOIN ydy_dishes d ON d.uuid=r.dishes_uuid WHERE r.menu_date=? AND r.meal_times=? AND d.status=1 AND d.is_del=1 ORDER BY d.category ASC',[$p['date'],$p['meal']]);foreach($exp as &$r){foreach(array_merge(['weight','total_energy','energy','total_carbohydrate','carbohydrate','total_protein','protein','total_fat','fat'],array_values($nutr)) as $f){$r[$f]=$canon($r[$f],2);}}unset($r);$rawSource=$exp;
    // ORDER BY category specifies no ordering inside ties. Do not reorder the actual wire:
    // match each actual UUID to one independently read source row, with multiplicity.
    $bag=[];foreach($exp as $row){$bag[$row['uuid']][]=$row;}$aligned=[];$last=null;
    foreach($response['body']['data'] as $row){if(!isset($bag[$row['uuid']])||!$bag[$row['uuid']]){throw new RuntimeException('Recipe source membership/multiplicity');}$truth=array_shift($bag[$row['uuid']]);if($last!==null&&$truth['category']<$last){throw new RuntimeException('Actual recipe category order');}$last=$truth['category'];$aligned[]=$truth;}
    foreach($bag as $left){if($left){throw new RuntimeException('Recipe source rows omitted');}}$exp=$aligned;
 }elseif(in_array($name,['VERSION_LIST','VERSION_PAGE2'],true)){
    $all=smRows($db,smTop('SELECT * FROM ydy_equipment_version ORDER BY create_time DESC',10));$total=(int)$db->query('SELECT COUNT(*) FROM ydy_equipment_version')->fetchColumn();$rows=array_slice($all,($p['page']-1)*$p['pageSize'],$p['pageSize']);$exp=['row'=>$rows,'total'=>$total];$rawSource=$rows;
 }elseif(in_array($name,['VERSION_READ','VERSION_MISSING'],true)){
    $rows=smRows($db,'SELECT * FROM ydy_equipment_version WHERE uuid=?',[$p['uuid']]);$exp=$rows[0]??null;$rawSource=$rows;
 }elseif($name==='CONSUME_LIST'){
    $exp=smRows($db,'SELECT id,code,name FROM ydy_equipment WHERE del_flag=0 AND type IN (1,9,40,20,30,60)');$rawSource=$exp;
 }elseif($name==='CHECK_MENU'){
    $dept=smRows($db,'SELECT uuid FROM ydy_department WHERE uuid=?',[$p['department_uuid']]);if(!$dept){$exp=['rows'=>[],'total'=>0];$rawSource=[];}
    else{$departments=smRows($db,'SELECT uuid FROM ydy_department WHERE department_path LIKE ? AND status=1 AND is_show=1',['%'.$p['department_uuid'].'%']);$ids=array_values(array_unique(array_merge(array_column($departments,'uuid'),[$p['department_uuid']])));$ph=implode(',',array_fill(0,count($ids),'?'));$staff=smRows($db,'SELECT DISTINCT staff_uuid FROM ydy_staff_department WHERE department_uuid IN ('.$ph.')',$ids);$sids=array_column($staff,'staff_uuid');
     if(!$sids){$rows=[];}else{$ph=implode(',',array_fill(0,count($sids),'?'));$rows=smRows($db,'SELECT DISTINCT s.uuid AS staff_uuid,s.head_img,s.name AS staff_name,s.sex,i.meal_times FROM ydy_initial_menu i LEFT JOIN ydy_staff s ON s.uuid=i.staff_uuid WHERE s.uuid IN ('.$ph.') AND i.menu_date=? AND i.meal_times=? AND i.meal_type IN (1,4) ORDER BY s.uuid',array_merge($sids,[$p['date'],$p['meal_times']]));}
     if(count($rows)>5){throw new RuntimeException('Unspecified order page source too large');}$total=count($rows);foreach($rows as &$r){$flag=smRows($db,'SELECT id FROM ydy_initial_menu WHERE staff_uuid=? AND menu_date=? AND meal_times=? AND meal_type=4',[$r['staff_uuid'],$p['date'],$p['meal_times']]);$r['is_flag']=(bool)$flag;$r['head_img']=$url($r['head_img']);}unset($r);$exp=['rows'=>$rows,'total'=>$total];$rawSource=$rows;
    }
 }elseif($name==='HARDWARE'){
    $sql='SELECT a.id,a.uuid,a.menu_date,a.meal_times,a.img,b.select_adjust,b.uuid AS date_menu_uuid,b.code,c.name,b.weight,b.dishes_uuid FROM ydy_date_menu_hardware_img a LEFT JOIN ydy_initial_menu b ON a.uuid=b.hardware_img_uuid LEFT JOIN ydy_dishes c ON c.uuid=b.dishes_uuid WHERE a.staff_uuid=? AND a.menu_date=? AND a.meal_times=? AND b.meal_type=? ORDER BY a.id,b.code';$args=[$p['staff_uuid'],$p['menu_date'],$p['meal_times'],4];$rows=smRows($db,$sql,$args);if(!$rows){$args[3]=1;$rows=smRows($db,$sql,$args);}
    $recipeIds=array_column(smRows($db,'SELECT dishes_uuid FROM ydy_recipes WHERE menu_date=? AND meal_times=?',[$p['menu_date'],$p['meal_times']]),'dishes_uuid');$group=[];
    foreach($rows as $r){$uuid=$r['uuid'];if(!isset($group[$uuid])){$group[$uuid]=['id'=>$r['id'],'uuid'=>$uuid,'menu_date'=>$r['menu_date'],'meal_times'=>$r['meal_times'],'img'=>$r['img'],'dishes'=>[]];}
      if($r['weight']===null){continue;}$group[$uuid]['dishes'][]=['hardware_img_uuid'=>'','recommend_uuid'=>'','date_menu_uuid'=>$r['date_menu_uuid'],'select_adjust'=>empty($r['select_adjust'])?'':(string)$r['select_adjust'],'dishes_uuid'=>$r['dishes_uuid'],'code'=>$r['code'],'name'=>$r['name'],'weight'=>$r['weight'],'in_recipes'=>in_array($r['dishes_uuid'],$recipeIds)?1:0,'un_edit'=>(!$r['weight']&&!$r['name'])?0:1];}
    $exp=[];foreach($group as $r){$r['dishes'][]=[];$exp[]=$r;}$log=smRows($db,'SELECT id FROM ydy_initial_menu_log WHERE staff_uuid=? AND menu_date=? AND meal_times=? AND user_type=1 AND terminal=1',[$p['staff_uuid'],$p['menu_date'],$p['meal_times']]);if($response['body']['message']!==(!$log)){throw new RuntimeException('Hardware is_save source');}$rawSource=$rows;
 }else{throw new RuntimeException('Unknown frozen case');}
 $wireExpected=json_decode(json_encode($exp,JSON_UNESCAPED_UNICODE|JSON_THROW_ON_ERROR));$wireActual=$wireResponses->$name->body->data;$difference=$strict($wireExpected,$wireActual);
 $expected[$name]=$wireExpected;$proof[$name]=['rows'=>$rawSource,'expected'=>$wireExpected];$summary[]=['case'=>$name,'pass'=>$difference===null,'difference'=>$difference,'source_rows'=>count($rawSource),'expected_kind'=>gettype($wireExpected),'nonempty'=>$exp!==null&&$exp!==[]&&(!isset($exp['rows'])||$exp['rows']!==[])];
 }}catch(Throwable $e){$errors[]=['class'=>get_class($e),'file'=>basename($e->getFile()),'line'=>$e->getLine(),'message_sha256'=>hash('sha256',$e->getMessage())];smSave('readback-error-private.json',['message'=>$e->getMessage(),'line'=>$e->getLine()]);}
smSave('expected-private.json',$expected);smSave('readback-source-private.json',$proof);smSave('readback-report-private.json',['lane'=>$lane,'cases'=>$summary,'errors'=>$errors,'database_writes'=>0,'new_http'=>0]);
echo json_encode(['lane'=>$lane,'cases'=>$summary,'errors'=>$errors,'database_writes'=>0,'new_http'=>0]),PHP_EOL;exit($errors?1:0);
