>> */ private static array $foods = [ self::LEVEL_LOW => [ '五谷类' => ['全蛋面', '荞麦面', '粉丝', '黑米', '黑米粥', '通心粉', '藕粉'], '蔬菜类' => ['魔芋', '粟米', '大白菜', '黄瓜', '芹菜', '茄子', '青椒', '海带', '金针菇', '香菇', '菠菜', '番茄', '豆芽', '芦笋', '花椰菜', '洋葱', '生菜'], '豆类' => ['黄豆', '眉豆', '鸡心豆', '豆腐', '豆角', '绿豆', '扁豆', '四季豆'], '水果类' => ['苹果', '水梨', '橙子', '桃', '提子', '沙田柚', '雪梨', '车厘子', '柚子', '草莓', '樱桃', '金桔', '葡萄'], '奶类' => ['牛奶', '低脂奶', '脱脂奶', '低脂乳酪', '红茶', '优格', '无糖豆浆'], ], self::LEVEL_MEDIUM => [ '主食类' => ['红米饭', '糙米饭', '西米', '乌冬面', '面包', '麦片', '番薯', '芋头'], '蔬菜类' => ['薯片', '番茄', '莲藕', '牛蒡'], '肉类' => ['鱼肉', '鸡肉', '鸭肉', '猪肉', '羊肉', '牛肉', '虾子', '蟹'], '水果类' => ['木瓜', '提子干', '菠萝', '香蕉', '芒果', '哈密瓜', '奇异果', '柳丁'], '其他' => ['蔗糖', '蜂蜜', '红酒', '啤酒', '可乐', '咖啡'], ], self::LEVEL_HIGH => [ '主食类' => ['白饭', '馒头', '油条', '糯米饭', '白面包', '燕麦片', '拉面', '炒饭', '爆米花'], '肉类加工品' => ['贡丸', '肥肠', '蛋饺'], '蔬菜类' => ['薯蓉', '南瓜', '焗薯'], '水果类' => ['西瓜', '荔枝', '龙眼', '凤梨', '枣'], '其他' => ['葡萄糖', '砂糖', '麦芽糖', '汽水', '柳橙汁', '蜂蜜'], ], ]; /** @var list */ private static array $mealTemplates = [ [ 'breakfast' => '玉米碴子粥、煮鸡蛋、拌黄瓜', 'lunch' => '清炖鲤鱼、豆腐炖白菜、清炒豆角、米饭小半碗', 'dinner' => '玉米面饼一个、瘦肉丝、凉拌茄子', 'tips' => '中午鱼豆肉吃足;早中晚淀粉别重复,一顿一种主食就够。', ], [ 'breakfast' => '小米粥、煮鸡蛋、小碟咸菜(少盐)', 'lunch' => '去皮炖鸡肉、芹菜炒豆干、木耳炒鸡蛋', 'dinner' => '蒸红薯(小半块)、清炒菠菜、豆腐汤', 'tips' => '午餐最讲究蛋白;晚上才吃薯,别跟中午再配大米饭。', ], [ 'breakfast' => '豆腐脑(少卤)、鸡蛋、一个苹果', 'lunch' => '清炖鸡肉、炖芸豆、番茄炒蛋、贴饼子一个', 'dinner' => '挂面一小碗、清蒸小鱼、拌海带丝', 'tips' => '中午饼配肉豆,蛋白管够;面条放晚上,别三顿都是粥饭。', ], [ 'breakfast' => '高粱米粥、茶叶蛋、拌萝卜丝', 'lunch' => '炖牛肉(瘦)、豆腐炒韭菜、清炒油菜、杂豆饭小半碗', 'dinner' => '蒸南瓜(小半碗)、鸡蛋羹、拍黄瓜', 'tips' => '中午肉蛋豆要多吃;南瓜算晚饭主食,别再加馒头稀饭。', ], [ 'breakfast' => '棒子面粥、水煮蛋、大拌菜(少油)', 'lunch' => '清炖鲫鱼、番茄炒蛋、炖扁豆、糙米饭小半碗', 'dinner' => '玉米面饼一个、清炒豆芽、豆腐脑', 'tips' => '午饭鱼蛋豆齐上;玉米面放晚上,跟中午米饭分开。', ], [ 'breakfast' => '小米粥、煮鸡蛋、拌生菜', 'lunch' => '白切鸡(去皮)、炖豆腐、清炒豆角、二米饭小半碗', 'dinner' => '杂面馒头一个、瘦肉炒芹菜、凉拌黄瓜', 'tips' => '中午蛋白打主力;三顿别都喝粥吃面,一顿一种淀粉。', ], ]; public static function summaryText(): string { $path = root_path() . 'app/api/logic/tcm/data/gi_huo_summary.txt'; if (is_file($path)) { $text = trim((string) file_get_contents($path)); if ($text !== '') { return $text; } } return '低 GI(≤55)优先;中 GI(56-69)适量;高 GI(≥70)尽量避免。多选全谷物、蔬菜、豆类,少加工、少精制糖。'; } /** 农村/口语别名 → 标准名(便于「能不能吃」匹配) */ private static array $aliases = [ '棒子面' => '荞麦面', '玉米碴' => '粟米', '地瓜' => '番薯', '红薯' => '番薯', '洋芋' => '芋头', '土豆' => '芋头', '窝头' => '通心粉', '贴饼子' => '通心粉', '二米饭' => '红米饭', '稀饭' => '白饭', '白稀饭' => '白饭', '糖糕' => '麦芽糖', ]; /** * @return array{level:string,level_label:string,category:string,advice:string}|null */ public static function lookupFood(string $name): ?array { $name = trim($name); if ($name === '') { return null; } $canonical = self::$aliases[$name] ?? $name; foreach ([self::LEVEL_LOW, self::LEVEL_MEDIUM, self::LEVEL_HIGH] as $level) { foreach (self::$foods[$level] as $category => $items) { foreach ($items as $item) { if ($canonical === $item || $name === $item || mb_strpos($item, $canonical) !== false || mb_strpos($canonical, $item) !== false || mb_strpos($item, $name) !== false || mb_strpos($name, $item) !== false) { return [ 'level' => $level, 'level_label' => self::levelLabel($level), 'category' => $category, 'food' => $item, 'advice' => self::adviceForLevel($level, $name), ]; } } } } return null; } /** 供 AI 参考:老农民家常、集市易得食材示例 */ public static function ruralMealHints(): string { return implode("\n", [ '【午餐】蛋白质要最高:至少 2 样高蛋白(鱼/鸡鸭/瘦肉/蛋/豆腐/豆干),肉蛋豆是午饭主角。', '【淀粉不重复】粥/饭/面/饼/窝头/红薯/土豆/玉米/南瓜等算淀粉;早中晚各最多 1 种,且三顿不能同一种、不要顿顿大主食。', '推荐蛋白:鸡蛋、豆腐、豆干、自家鸡鸭蛋、瘦肉、鲫鱼鲤鱼;', '推荐蔬菜:白菜、萝卜、黄瓜、豆角、茄子、菠菜、芹菜;', '淀粉示例(全天选 3 种不同的):玉米碴粥、二米饭小半碗、贴饼子、杂面窝头、挂面小碗、蒸红薯;', '少吃:白馒头、油条、粘豆包、糯米饭、西瓜、含糖饮料、糕点。', '说话要土话、短句,像村里大夫叮嘱。', ]); } public static function levelLabel(string $level): string { return match ($level) { self::LEVEL_LOW => '低升糖', self::LEVEL_MEDIUM => '中升糖', default => '高升糖', }; } public static function adviceForLevel(string $level, string $food): string { return match ($level) { self::LEVEL_LOW => "「{$food}」升糖慢,老农民家常能吃,当菜当饭都行,有助于稳血糖。", self::LEVEL_MEDIUM => "「{$food}」升糖中等,可以吃但要少搁点,别当主食猛吃,配着蔬菜鸡蛋更好。", default => "「{$food}」升糖快,血糖高时尽量别吃或少吃,尤其别空腹猛吃。", }; } /** * @param int|null $templateIndex 指定模板下标(换一换时轮换) * @return array{breakfast:string,lunch:string,dinner:string,tips:string,avoid:list,source:string} */ public static function fallbackDailyMeals(int $diagnosisId, ?int $templateIndex = null): array { $templates = self::$mealTemplates; $count = count($templates); if ($templateIndex !== null) { $idx = (($templateIndex % $count) + $count) % $count; } else { $idx = abs(crc32(date('Y-m-d') . ':' . $diagnosisId)) % $count; } $meal = $templates[$idx]; $plan = [ 'breakfast' => $meal['breakfast'], 'lunch' => $meal['lunch'], 'dinner' => $meal['dinner'], 'tips' => $meal['tips'], 'avoid' => ['白面馒头', '油条', '粘豆包', '西瓜', '含糖饮料', '稀饭配糖'], 'source' => 'rule', ]; $checked = DietMealValidator::validateAndNormalize($plan); $out = $checked['plan']; $out['source'] = 'rule'; $out['rules_summary'] = DietMealValidator::metaSummary($checked['meta']); return $out; } /** * @return array{food:string,level:string,level_label:string,advice:string,source:string} */ public static function fallbackAsk(string $question): array { $food = self::extractFoodName($question); $hit = self::lookupFood($food); if ($hit) { return [ 'food' => $hit['food'], 'level' => $hit['level'], 'level_label' => $hit['level_label'], 'advice' => $hit['advice'], 'source' => 'rule', ]; } return [ 'food' => $food, 'level' => '', 'level_label' => '未知', 'advice' => '库里没查到「' . $food . '」。一般多吃小米玉米、白菜豆角豆腐鸡蛋,少吃白馒头油条甜口;拿不准问村医。', 'source' => 'rule', ]; } public static function extractFoodName(string $question): string { $q = trim($question); $q = preg_replace('/^(能不能吃|可以吃|能吃|可不可以吃|请问|我想问)/u', '', $q) ?? $q; $q = preg_replace('/(吗|呢|?|\?)+$/u', '', $q) ?? $q; $q = trim($q); return $q !== '' ? $q : $question; } }