<?php
/** Isolated real-nutrition fixture; no HTTP client, AI service, device service, or production DB. */
error_reporting(E_ALL & ~E_DEPRECATED);
$store = '/Users/wiscourper/Documents/workspace/zhct/zhctproject/store';
ob_start(); require $store . '/vendor/bin/phpunit'; ob_end_clean();
require $store . '/tests/bootstrap.php';
set_exception_handler(function(Throwable $e){fwrite(STDERR,json_encode(['status'=>'failed','error'=>$e->getMessage()],JSON_UNESCAPED_UNICODE).PHP_EOL);exit(1);});
$dbFile = tempnam(sys_get_temp_dir(), 'tchef-nutrition-');
register_shutdown_function(function () use ($dbFile) { @unlink($dbFile); });
\think\Config::set('database', ['type'=>'sqlite','database'=>$dbFile,'hostname'=>'','username'=>'','password'=>'','hostport'=>'','dsn'=>'sqlite:'.$dbFile,'prefix'=>'ydy_','debug'=>true,'fields_strict'=>true,'resultset_type'=>'array','auto_timestamp'=>false]);
\think\Db::clear();
\app\cores\BaseModel::$storeId = 1;
$pdo = new PDO('sqlite:'.$dbFile);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$nutrients = array_column(\app\common\enum\dishes\Nutrient::data(), 'field');
// TEXT numeric fixture columns preserve decimal strings: legacy ThinkPHP binds REAL as PDO::PARAM_INT in SQLite. This is fixture DDL only.
$nutSql = implode(',', array_map(function($f){return '"'.$f.'" TEXT NOT NULL DEFAULT 0';}, $nutrients));
$pdo->exec('CREATE TABLE ydy_dishes (id INTEGER PRIMARY KEY,uuid TEXT,name TEXT,store_id INTEGER DEFAULT 1,is_delete INTEGER DEFAULT 0,category INTEGER DEFAULT 1,category_one INTEGER DEFAULT 1,category_two INTEGER DEFAULT 1,cook_method INTEGER DEFAULT 1,raw_flag INTEGER DEFAULT 1,raw_flag_weight TEXT DEFAULT 0,weight TEXT,'.$nutSql.')');
$pdo->exec('CREATE TABLE ydy_foods_composition (id INTEGER PRIMARY KEY,name TEXT,store_id INTEGER DEFAULT 1,is_delete INTEGER DEFAULT 0,category INTEGER,category_two INTEGER,foods_status INTEGER DEFAULT 1,edible_part TEXT,'.$nutSql.')');
$pdo->exec('CREATE TABLE ydy_foods_ssb (id INTEGER PRIMARY KEY,name TEXT,type INTEGER,category INTEGER DEFAULT 0,category_two INTEGER DEFAULT 0,cook_method INTEGER,foods_status INTEGER DEFAULT 1,is_delete INTEGER DEFAULT 0,exclude TEXT DEFAULT "",ssb TEXT)');
$pdo->exec('CREATE TABLE ydy_dish (id INTEGER PRIMARY KEY AUTOINCREMENT,dishes_id INTEGER,dishes_uuid TEXT,foods_id INTEGER,composition_id INTEGER,foods_weight TEXT,food_weight TEXT,foods_ratio TEXT,composition_ratio TEXT,store_id INTEGER DEFAULT 1,is_delete INTEGER DEFAULT 0,edible_part TEXT,ssb TEXT,raw_flag_weight TEXT,dishes_ssb TEXT,gia TEXT,gib TEXT)');
$pdo->exec('CREATE TABLE ydy_dishes_tag (id INTEGER PRIMARY KEY AUTOINCREMENT,dishes_id INTEGER,nutrient TEXT,level INTEGER,name TEXT,color TEXT,store_id INTEGER DEFAULT 1,is_delete INTEGER DEFAULT 0,create_by TEXT,create_time TEXT,update_time TEXT)');
function insertFixture(PDO $pdo, string $table, array $row): void {
 $q=$pdo->prepare('INSERT INTO '.$table.' ('.implode(',',array_map(function($k){return '"'.$k.'"';},array_keys($row))).') VALUES ('.implode(',',array_fill(0,count($row),'?')).')');$q->execute(array_values($row));
}
$food1=array_fill_keys($nutrients,1.0);$food1=array_merge($food1,['id'=>10,'name'=>'fixture-potato','category'=>2,'category_two'=>20,'foods_status'=>1,'edible_part'=>80,'energy'=>100,'carbohydrate'=>20,'dietary_fiber'=>2,'gi'=>50,'protein'=>4,'fat'=>1]);
$food2=array_fill_keys($nutrients,2.0);$food2=array_merge($food2,['id'=>20,'name'=>'fixture-seasoning','category'=>15,'category_two'=>151,'foods_status'=>1,'edible_part'=>100,'energy'=>200,'carbohydrate'=>30,'dietary_fiber'=>3,'gi'=>30,'protein'=>2,'fat'=>5]);
insertFixture($pdo,'ydy_foods_composition',$food1);insertFixture($pdo,'ydy_foods_composition',$food2);
insertFixture($pdo,'ydy_foods_ssb',['id'=>1,'name'=>'fixture-potato','type'=>1,'cook_method'=>1,'ssb'=>2]);
insertFixture($pdo,'ydy_foods_ssb',['id'=>2,'name'=>'fixture-seasoning','type'=>1,'cook_method'=>1,'ssb'=>0.5]);
$remote=['programList'=>[['actionNo'=>'82','materialList'=>[['materialId'=>'potato','weight'=>200]]],['actionNo'=>'82','materialList'=>[['materialId'=>'potato','weight'=>100]]],['actionNo'=>'seasoning','param5'=>0.02,'param6'=>1]]];
$parsed=\app\common\service\tchef\Recipe::parse($remote,function($kind,$identity,$name){return $kind==='material'&&$identity==='potato'?10:($kind==='condiment'&&$identity==='seasoning'?20:null);},['82','84','92']);
if($parsed!==[10=>300.0,20=>20.0])throw new RuntimeException('Recipe aggregation mismatch');
$manual=[10=>300.0,20=>20.0];
foreach([1=>$manual,2=>$parsed] as $id=>$weights){
 insertFixture($pdo,'ydy_dishes',['id'=>$id,'uuid'=>'fixture-'.$id,'name'=>'fixture-'.$id,'weight'=>array_sum($weights)]);
 foreach($weights as $foodId=>$weight)insertFixture($pdo,'ydy_dish',['dishes_id'=>$id,'dishes_uuid'=>'fixture-'.$id,'foods_id'=>$foodId,'composition_id'=>$foodId,'foods_weight'=>$weight,'food_weight'=>$weight,'foods_ratio'=>$weight/array_sum($weights)*100,'composition_ratio'=>$weight/array_sum($weights)*100]);
 if(!(new \app\store\model\dishes\Dishes())->calculateDishesData($id))throw new RuntimeException('Real calculation failed');
}
function resultFixture(PDO $pdo,int $id,array $nutrients): array {
 $row=$pdo->query('SELECT * FROM ydy_dishes WHERE id='.$id)->fetch(PDO::FETCH_ASSOC);
 $nutrition=array_intersect_key($row,array_flip(array_merge(['weight'],$nutrients)));
 $relations=$pdo->query('SELECT foods_id,composition_id,foods_weight,food_weight,foods_ratio,composition_ratio,edible_part,ssb,raw_flag_weight,dishes_ssb,gia,gib FROM ydy_dish WHERE dishes_id='.$id.' ORDER BY foods_id')->fetchAll(PDO::FETCH_ASSOC);
 $tags=$pdo->query('SELECT nutrient,level,name,color FROM ydy_dishes_tag WHERE dishes_id='.$id.' ORDER BY nutrient,level,name')->fetchAll(PDO::FETCH_ASSOC);
 return ['nutrition'=>$nutrition,'relations'=>$relations,'tags'=>$tags];
}
$a=resultFixture($pdo,1,$nutrients);$b=resultFixture($pdo,2,$nutrients);
if($a!==$b)throw new RuntimeException('Normalized remote input differs from manual baseline');
if(!$a['tags'])throw new RuntimeException('Tags were not calculated');
foreach(['energy'=>471.58,'gi'=>48.18,'gl'=>48.2] as $key=>$expected)if(abs((float)$a['nutrition'][$key]-$expected)>0.00001)throw new RuntimeException('Independent numeric checkpoint failed: '.$key);
if(abs((float)$a['relations'][1]['ssb']-0.5)>0.00001||abs((float)$a['relations'][0]['dishes_ssb']-320/190)>0.00001)throw new RuntimeException('Decimal precision lost in fixture');
if(abs($a['relations'][0]['raw_flag_weight']-190)>0.00001||abs($a['relations'][0]['edible_part']-80)>0.00001)throw new RuntimeException('Ssb/edible part fixture did not exercise required branches');
echo json_encode(['status'=>'passed','database'=>'isolated temporary SQLite','comparison'=>'manual normalized recipe vs Recipe::parse normalized recipe, both real calculateDishesData','nutrient_fields'=>count($nutrients),'raw_recipe_grams'=>320,'estimated_cooked_grams'=>190,'real_result'=>$a,'limits'=>['Does not call DishSync::apply or its transport/notification paths','Does not prove production DB deployment, actual net/gross feed semantics or live recommendation UX']],JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE).PHP_EOL;
