BodyAiReport.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. <?php
  2. namespace app\common\model;
  3. use think\Model;
  4. /**
  5. * AI身体测试报告模型
  6. */
  7. class BodyAiReport extends Model
  8. {
  9. // 表名
  10. protected $table = 'fa_body_ai_report';
  11. // 开启自动写入时间戳字段
  12. protected $autoWriteTimestamp = 'int';
  13. // 定义时间戳字段名
  14. protected $createTime = 'createtime';
  15. protected $updateTime = 'updatetime';
  16. protected $deleteTime = false;
  17. // 追加属性
  18. protected $append = [
  19. 'report_type_text',
  20. 'ai_analysis_data',
  21. 'recommendations_data',
  22. 'report_images_data',
  23. 'generated_time_text',
  24. 'health_level_text'
  25. ];
  26. /**
  27. * 关联身体档案
  28. */
  29. public function profile()
  30. {
  31. return $this->belongsTo('BodyProfile', 'profile_id', 'id', [], 'LEFT')->setEagerlyType(0);
  32. }
  33. /**
  34. * 获取报告类型文本
  35. */
  36. public function getReportTypeTextAttr($value, $data)
  37. {
  38. $typeMap = [
  39. 'comprehensive' => '综合分析报告',
  40. 'partial' => '局部分析报告',
  41. 'comparison' => '对比分析报告'
  42. ];
  43. return isset($typeMap[$data['report_type']]) ? $typeMap[$data['report_type']] : '未知类型';
  44. }
  45. /**
  46. * 获取AI分析数据
  47. */
  48. public function getAiAnalysisDataAttr($value, $data)
  49. {
  50. return !empty($data['ai_analysis']) ? json_decode($data['ai_analysis'], true) : [];
  51. }
  52. /**
  53. * 设置AI分析数据
  54. */
  55. public function setAiAnalysisAttr($value)
  56. {
  57. return is_array($value) ? json_encode($value, JSON_UNESCAPED_UNICODE) : $value;
  58. }
  59. /**
  60. * 获取建议数据
  61. */
  62. public function getRecommendationsDataAttr($value, $data)
  63. {
  64. return !empty($data['recommendations']) ? json_decode($data['recommendations'], true) : [];
  65. }
  66. /**
  67. * 设置建议数据
  68. */
  69. public function setRecommendationsAttr($value)
  70. {
  71. return is_array($value) ? json_encode($value, JSON_UNESCAPED_UNICODE) : $value;
  72. }
  73. /**
  74. * 获取报告图片数据
  75. */
  76. public function getReportImagesDataAttr($value, $data)
  77. {
  78. return !empty($data['report_images']) ? json_decode($data['report_images'], true) : [];
  79. }
  80. /**
  81. * 设置报告图片数据
  82. */
  83. public function setReportImagesAttr($value)
  84. {
  85. return is_array($value) ? json_encode($value) : $value;
  86. }
  87. /**
  88. * 获取生成时间文本
  89. */
  90. public function getGeneratedTimeTextAttr($value, $data)
  91. {
  92. return $data['generated_time'] ? date('Y-m-d H:i:s', $data['generated_time']) : '';
  93. }
  94. /**
  95. * 获取健康等级文本
  96. */
  97. public function getHealthLevelTextAttr($value, $data)
  98. {
  99. $score = $data['health_score'];
  100. if ($score >= 90) {
  101. return '优秀';
  102. } elseif ($score >= 80) {
  103. return '良好';
  104. } elseif ($score >= 70) {
  105. return '一般';
  106. } elseif ($score >= 60) {
  107. return '较差';
  108. } else {
  109. return '差';
  110. }
  111. }
  112. /**
  113. * 生成AI分析报告
  114. */
  115. public static function generateReport($profileId, $reportType = 'comprehensive')
  116. {
  117. $profile = BodyProfile::with(['latestMeasurement', 'bodyTypeSelections.typeConfig'])->find($profileId);
  118. if (!$profile) {
  119. return false;
  120. }
  121. // 获取基础数据
  122. $measurements = $profile->latestMeasurement;
  123. $bodyTypes = $profile->bodyTypeSelections;
  124. // 生成AI分析结果
  125. $aiAnalysis = self::generateAiAnalysis($profile, $measurements, $bodyTypes);
  126. // 生成建议内容
  127. $recommendations = self::generateRecommendations($profile, $measurements, $aiAnalysis);
  128. // 计算健康评分
  129. $healthScore = self::calculateHealthScore($profile, $measurements);
  130. // 计算身体年龄
  131. $bodyAge = self::calculateBodyAge($profile, $measurements, $healthScore);
  132. // 创建报告记录
  133. $report = new self();
  134. $report->profile_id = $profileId;
  135. $report->report_type = $reportType;
  136. $report->ai_analysis = $aiAnalysis;
  137. $report->recommendations = $recommendations;
  138. $report->health_score = $healthScore;
  139. $report->body_age = $bodyAge;
  140. $report->generated_time = time();
  141. if ($report->save()) {
  142. return $report;
  143. }
  144. return false;
  145. }
  146. /**
  147. * 生成AI分析内容
  148. */
  149. private static function generateAiAnalysis($profile, $measurements, $bodyTypes)
  150. {
  151. $analysis = [
  152. 'basic_info' => [
  153. 'bmi' => $profile->calculateBMI(),
  154. 'bmi_level' => $profile->getBMILevel(),
  155. 'age' => $profile->age,
  156. 'gender' => $profile->gender_text
  157. ],
  158. 'body_composition' => [],
  159. 'body_proportions' => [],
  160. 'body_types' => [],
  161. 'risk_assessment' => []
  162. ];
  163. // 身体成分分析
  164. if ($measurements) {
  165. $analysis['body_composition'] = [
  166. 'muscle_mass_estimate' => self::estimateMuscleMass($profile, $measurements),
  167. 'fat_percentage_estimate' => self::estimateFatPercentage($profile, $measurements),
  168. 'bone_density_level' => self::estimateBoneDensity($profile->age, $profile->gender)
  169. ];
  170. // 身体比例分析
  171. $ratios = $measurements->calculateBodyRatios();
  172. $analysis['body_proportions'] = [
  173. 'waist_hip_ratio' => $ratios['waist_hip_ratio'] ?? 0,
  174. 'waist_hip_assessment' => self::assessWaistHipRatio($ratios['waist_hip_ratio'] ?? 0, $profile->gender),
  175. 'symmetry_score' => self::calculateSymmetryScore($measurements)
  176. ];
  177. }
  178. // 体型分析
  179. $typeAnalysis = [];
  180. foreach ($bodyTypes as $selection) {
  181. $typeAnalysis[$selection->type_category] = [
  182. 'selected_type' => $selection->typeConfig->type_name,
  183. 'suitability' => self::assessTypeSuitability($selection, $measurements)
  184. ];
  185. }
  186. $analysis['body_types'] = $typeAnalysis;
  187. // 风险评估
  188. $analysis['risk_assessment'] = self::assessHealthRisks($profile, $measurements);
  189. return $analysis;
  190. }
  191. /**
  192. * 生成建议内容
  193. */
  194. private static function generateRecommendations($profile, $measurements, $analysis)
  195. {
  196. $recommendations = [
  197. 'exercise' => [],
  198. 'nutrition' => [],
  199. 'lifestyle' => [],
  200. 'clothing' => []
  201. ];
  202. // 运动建议
  203. $bmi = $profile->calculateBMI();
  204. if ($bmi < 18.5) {
  205. $recommendations['exercise'][] = '增加力量训练,促进肌肉增长';
  206. $recommendations['exercise'][] = '适量有氧运动,提高体能';
  207. } elseif ($bmi > 24) {
  208. $recommendations['exercise'][] = '增加有氧运动,控制体重';
  209. $recommendations['exercise'][] = '结合力量训练,提高代谢';
  210. } else {
  211. $recommendations['exercise'][] = '保持规律运动,维持现有状态';
  212. }
  213. // 营养建议
  214. if ($profile->age < 30) {
  215. $recommendations['nutrition'][] = '保证充足蛋白质摄入,支持肌肉发育';
  216. } elseif ($profile->age >= 50) {
  217. $recommendations['nutrition'][] = '增加钙质摄入,预防骨质疏松';
  218. }
  219. // 生活方式建议
  220. $recommendations['lifestyle'][] = '保持充足睡眠,每天7-8小时';
  221. $recommendations['lifestyle'][] = '减少久坐时间,每小时活动一次';
  222. // 服装搭配建议
  223. $waistHipRatio = $analysis['body_proportions']['waist_hip_ratio'] ?? 0;
  224. if ($waistHipRatio > 0.8 && $profile->gender == 2) {
  225. $recommendations['clothing'][] = 'A字裙装修饰腰臀比例';
  226. $recommendations['clothing'][] = '高腰设计拉长腿部线条';
  227. }
  228. return $recommendations;
  229. }
  230. /**
  231. * 计算健康评分
  232. */
  233. private static function calculateHealthScore($profile, $measurements)
  234. {
  235. $score = 0;
  236. $factors = 0;
  237. // BMI评分 (30分)
  238. $bmi = $profile->calculateBMI();
  239. if ($bmi >= 18.5 && $bmi < 24) {
  240. $score += 30;
  241. } elseif ($bmi >= 17 && $bmi < 28) {
  242. $score += 20;
  243. } else {
  244. $score += 10;
  245. }
  246. $factors++;
  247. // 腰臀比评分 (20分)
  248. if ($measurements && $measurements->waist > 0 && $measurements->hip > 0) {
  249. $whr = $measurements->waist / $measurements->hip;
  250. $idealWhr = $profile->gender == 1 ? 0.9 : 0.8;
  251. if (abs($whr - $idealWhr) <= 0.05) {
  252. $score += 20;
  253. } elseif (abs($whr - $idealWhr) <= 0.1) {
  254. $score += 15;
  255. } else {
  256. $score += 10;
  257. }
  258. $factors++;
  259. }
  260. // 年龄调整评分 (10分)
  261. if ($profile->age <= 30) {
  262. $score += 10;
  263. } elseif ($profile->age <= 50) {
  264. $score += 8;
  265. } else {
  266. $score += 6;
  267. }
  268. $factors++;
  269. return $factors > 0 ? round($score / $factors * 100 / 60, 1) : 0;
  270. }
  271. /**
  272. * 计算身体年龄
  273. */
  274. private static function calculateBodyAge($profile, $measurements, $healthScore)
  275. {
  276. $baseAge = $profile->age;
  277. // 根据健康评分调整
  278. if ($healthScore >= 90) {
  279. $adjustment = -5;
  280. } elseif ($healthScore >= 80) {
  281. $adjustment = -2;
  282. } elseif ($healthScore >= 70) {
  283. $adjustment = 0;
  284. } elseif ($healthScore >= 60) {
  285. $adjustment = 3;
  286. } else {
  287. $adjustment = 8;
  288. }
  289. // 根据BMI调整
  290. $bmi = $profile->calculateBMI();
  291. if ($bmi < 18.5 || $bmi > 28) {
  292. $adjustment += 2;
  293. }
  294. return max(18, min(80, $baseAge + $adjustment));
  295. }
  296. // 辅助方法
  297. private static function estimateMuscleMass($profile, $measurements)
  298. {
  299. // 简化的肌肉量估算
  300. return $profile->gender == 1 ? '中等' : '一般';
  301. }
  302. private static function estimateFatPercentage($profile, $measurements)
  303. {
  304. $bmi = $profile->calculateBMI();
  305. if ($bmi < 18.5) return '偏低';
  306. if ($bmi > 25) return '偏高';
  307. return '正常';
  308. }
  309. private static function estimateBoneDensity($age, $gender)
  310. {
  311. if ($age < 30) return '良好';
  312. if ($age > 50 && $gender == 2) return '需关注';
  313. return '一般';
  314. }
  315. private static function assessWaistHipRatio($ratio, $gender)
  316. {
  317. if ($ratio == 0) return '无法评估';
  318. $ideal = $gender == 1 ? 0.9 : 0.8;
  319. if (abs($ratio - $ideal) <= 0.05) return '理想';
  320. if ($ratio > $ideal + 0.1) return '偏高';
  321. return '一般';
  322. }
  323. private static function calculateSymmetryScore($measurements)
  324. {
  325. return mt_rand(75, 95); // 简化实现
  326. }
  327. private static function assessTypeSuitability($selection, $measurements)
  328. {
  329. return '适合'; // 简化实现
  330. }
  331. private static function assessHealthRisks($profile, $measurements)
  332. {
  333. $risks = [];
  334. $bmi = $profile->calculateBMI();
  335. if ($bmi > 28) {
  336. $risks[] = '肥胖相关疾病风险';
  337. }
  338. if ($profile->age > 50) {
  339. $risks[] = '骨质疏松风险';
  340. }
  341. return $risks;
  342. }
  343. }