This commit is contained in:
Your Name
2026-05-29 09:28:38 +08:00
parent 7b46204454
commit 94e787ae80
11 changed files with 1834 additions and 0 deletions
+230
View File
@@ -0,0 +1,230 @@
<?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;
}
}