Files
zyt/server/app/api/logic/tcm/GiKnowledge.php
T
2026-05-29 09:28:38 +08:00

231 lines
11 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace app\api\logic\tcm;
/**
* 霍大夫升糖指数(GI)知识库
*/
class GiKnowledge
{
public const LEVEL_LOW = 'low';
public const LEVEL_MEDIUM = 'medium';
public const LEVEL_HIGH = 'high';
/** @var array<string, array<string, list<string>>> */
private static array $foods = [
self::LEVEL_LOW => [
'五谷类' => ['全蛋面', '荞麦面', '粉丝', '黑米', '黑米粥', '通心粉', '藕粉'],
'蔬菜类' => ['魔芋', '粟米', '大白菜', '黄瓜', '芹菜', '茄子', '青椒', '海带', '金针菇', '香菇', '菠菜', '番茄', '豆芽', '芦笋', '花椰菜', '洋葱', '生菜'],
'豆类' => ['黄豆', '眉豆', '鸡心豆', '豆腐', '豆角', '绿豆', '扁豆', '四季豆'],
'水果类' => ['苹果', '水梨', '橙子', '桃', '提子', '沙田柚', '雪梨', '车厘子', '柚子', '草莓', '樱桃', '金桔', '葡萄'],
'奶类' => ['牛奶', '低脂奶', '脱脂奶', '低脂乳酪', '红茶', '优格', '无糖豆浆'],
],
self::LEVEL_MEDIUM => [
'主食类' => ['红米饭', '糙米饭', '西米', '乌冬面', '面包', '麦片', '番薯', '芋头'],
'蔬菜类' => ['薯片', '番茄', '莲藕', '牛蒡'],
'肉类' => ['鱼肉', '鸡肉', '鸭肉', '猪肉', '羊肉', '牛肉', '虾子', '蟹'],
'水果类' => ['木瓜', '提子干', '菠萝', '香蕉', '芒果', '哈密瓜', '奇异果', '柳丁'],
'其他' => ['蔗糖', '蜂蜜', '红酒', '啤酒', '可乐', '咖啡'],
],
self::LEVEL_HIGH => [
'主食类' => ['白饭', '馒头', '油条', '糯米饭', '白面包', '燕麦片', '拉面', '炒饭', '爆米花'],
'肉类加工品' => ['贡丸', '肥肠', '蛋饺'],
'蔬菜类' => ['薯蓉', '南瓜', '焗薯'],
'水果类' => ['西瓜', '荔枝', '龙眼', '凤梨', '枣'],
'其他' => ['葡萄糖', '砂糖', '麦芽糖', '汽水', '柳橙汁', '蜂蜜'],
],
];
/** @var list<array{breakfast:string,lunch:string,dinner:string,tips:string}> */
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)优先;中 GI56-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<string>,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;
}
}