Spaces:
Sleeping
Sleeping
File size: 113,575 Bytes
ad05511 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 |
"""
建议模型服务
高精确率模型 + DiCE反事实解释,生成个性化建议
"""
import logging
import time
import sys
import os
import pandas as pd
import numpy as np
from typing import Dict, Any, List, Optional
from pathlib import Path
# 添加DiCE模块路径
dice_path_I = Path("/Users/ning/Desktop/idea/代码forSarcoAdvisor/4.DICE建模/DICE/I")
dice_path_II = Path("/Users/ning/Desktop/idea/代码forSarcoAdvisor/4.DICE建模/DICE/II")
sys.path.append(str(dice_path_I))
sys.path.append(str(dice_path_II))
from schemas.user_input import UserInput, AdvisoryResponse, RecommendationItem
from utils.model_loader import model_manager
from config import DiCEConfig, SystemConfig
# 尝试导入DiCE相关模块
try:
import dice_ml
from dice_ml import Dice
DICE_AVAILABLE = True
except ImportError:
DICE_AVAILABLE = False
logging.warning("DiCE module not available, will use rule-based recommendations")
logger = logging.getLogger(__name__)
class AdvisoryService:
"""建议服务类"""
def __init__(self):
self.model_manager = model_manager
self.dice_explainers = {}
# DiCE初始化将在模型加载后进行
def initialize_dice(self):
"""在模型加载后初始化DiCE解释器"""
self._init_dice_explainers()
def _init_dice_explainers(self):
"""初始化DiCE解释器"""
if not DICE_AVAILABLE:
logger.warning("DiCE不可用,将使用基于规则的建议")
return
try:
# 加载DiCE数据和解释器
self._setup_sarcoI_dice()
self._setup_sarcoII_dice()
logger.info("DiCE解释器初始化完成")
except Exception as e:
logger.error(f"DiCE解释器初始化失败: {str(e)}")
self.dice_explainers = {}
def _setup_sarcoI_dice(self):
"""设置SarcoI DiCE解释器"""
try:
# 动态确定数据路径
app_path = Path(__file__).parent.parent
if (app_path / "data").exists():
# 云端部署路径
data_path = app_path / "data"
else:
# 本地开发路径
data_path = Path('/Users/ning/Desktop/idea/代码forSarcoAdvisor/4.DICE建模/预筛选')
# 🚀 根据配置加载SarcoI数据集
config = DiCEConfig.get_current_config()
train_data = pd.read_csv(data_path / 'SarcoI_train_final.csv')
# 根据配置决定是否合并测试集
if config["combine_train_test"]:
try:
test_data = pd.read_csv(data_path / 'SarcoI_test_final.csv')
all_data = pd.concat([train_data, test_data], ignore_index=True)
logger.info(f"SarcoI DiCE合并训练+测试数据: {len(all_data)} 样本")
except FileNotFoundError:
all_data = train_data
logger.info("SarcoI测试集未找到,仅使用训练集")
else:
all_data = train_data
logger.info(f"SarcoI DiCE使用训练数据: {len(all_data)} 样本")
# 根据配置决定是否采样
if config["max_samples"] and len(all_data) > config["max_samples"]:
all_data = all_data.sample(n=config["max_samples"], random_state=42)
logger.info(f"SarcoI DiCE数据采样至: {len(all_data)} 样本")
logger.info(f"SarcoI DiCE最终数据集: {len(all_data)} 样本 (模式: {DiCEConfig.PRECISION_MODE})")
# 创建DiCE数据对象
dice_data = dice_ml.Data(
dataframe=all_data,
continuous_features=['body_mass_index', 'race_ethnicity', 'WWI', 'age_years',
'Activity_Sedentary_Ratio', 'Total_Moderate_Minutes_week', 'Vigorous_MET_Ratio'],
outcome_name='SarcoI'
)
# 包装模型
wrapped_model = self._wrap_model('sarcoI')
dice_model = dice_ml.Model(model=wrapped_model, backend='sklearn')
# 🚀 创建极致精度解释器 - 使用genetic方法 + 配置文件中的高精度参数
dice_config_params = DiCEConfig.get_current_config()
logger.info(f"SarcoI DiCE使用精度模式: {DiCEConfig.PRECISION_MODE}")
# genetic方法是DiCE中最精确但最慢的方法,完美符合您的需求
self.dice_explainers['sarcoI'] = {
'explainer': Dice(dice_data, dice_model, method='genetic'),
'mutable_features': ['Activity_Sedentary_Ratio', 'Total_Moderate_Minutes_week',
'Vigorous_MET_Ratio', 'body_mass_index', 'WWI'],
# 🎯 使用配置文件中的高精度参数
'precision_config': dice_config_params
}
except Exception as e:
logger.error(f"SarcoI DiCE设置失败: {str(e)}")
def _setup_sarcoII_dice(self):
"""设置SarcoII DiCE解释器"""
try:
# 动态确定数据路径
app_path = Path(__file__).parent.parent
if (app_path / "data").exists():
# 云端部署路径
data_path = app_path / "data"
else:
# 本地开发路径
data_path = Path('/Users/ning/Desktop/idea/代码forSarcoAdvisor/4.DICE建模/预筛选')
# 🚀 根据配置加载SarcoII数据集
config = DiCEConfig.get_current_config()
train_data = pd.read_csv(data_path / 'SarcoII_train_final.csv')
# 根据配置决定是否合并测试集
if config["combine_train_test"]:
try:
test_data = pd.read_csv(data_path / 'SarcoII_test_final.csv')
all_data = pd.concat([train_data, test_data], ignore_index=True)
logger.info(f"SarcoII DiCE合并训练+测试数据: {len(all_data)} 样本")
except FileNotFoundError:
all_data = train_data
logger.info("SarcoII测试集未找到,仅使用训练集")
else:
all_data = train_data
logger.info(f"SarcoII DiCE使用训练数据: {len(all_data)} 样本")
# 根据配置决定是否采样
if config["max_samples"] and len(all_data) > config["max_samples"]:
all_data = all_data.sample(n=config["max_samples"], random_state=42)
logger.info(f"SarcoII DiCE数据采样至: {len(all_data)} 样本")
logger.info(f"SarcoII DiCE最终数据集: {len(all_data)} 样本 (模式: {DiCEConfig.PRECISION_MODE})")
# 创建DiCE数据对象
dice_data = dice_ml.Data(
dataframe=all_data,
continuous_features=['body_mass_index', 'race_ethnicity', 'age_years',
'Activity_Sedentary_Ratio', 'Activity_Diversity_Index', 'WWI',
'Vigorous_MET_Ratio', 'sedentary_minutes'],
outcome_name='SarcoII'
)
# 包装模型
wrapped_model = self._wrap_model('sarcoII')
dice_model = dice_ml.Model(model=wrapped_model, backend='sklearn')
# 🚀 创建极致精度解释器 - 使用genetic方法 + 配置文件中的高精度参数
dice_config_params = DiCEConfig.get_current_config()
logger.info(f"SarcoII DiCE使用精度模式: {DiCEConfig.PRECISION_MODE}")
# genetic方法是DiCE中最精确但最慢的方法,完美符合您的需求
self.dice_explainers['sarcoII'] = {
'explainer': Dice(dice_data, dice_model, method='genetic'),
'mutable_features': ['Activity_Sedentary_Ratio', 'Activity_Diversity_Index', 'WWI',
'Vigorous_MET_Ratio', 'sedentary_minutes', 'body_mass_index'],
# 🎯 使用配置文件中的高精度参数
'precision_config': dice_config_params
}
except Exception as e:
logger.error(f"SarcoII DiCE设置失败: {str(e)}")
def _wrap_model(self, model_type: str):
"""包装模型以适配DiCE"""
class ModelWrapper:
def __init__(self, model, threshold, model_manager_ref, model_type):
self.model = model
self.threshold = threshold
self.model_manager = model_manager_ref
self.model_type = model_type
def predict(self, X):
X_processed = self._process_input(X)
proba = self.model.predict_proba(X_processed)[:, 1]
return (proba >= self.threshold).astype(int)
def predict_proba(self, X):
X_processed = self._process_input(X)
return self.model.predict_proba(X_processed)
def _process_input(self, X):
if isinstance(X, pd.DataFrame):
X_processed = X.copy()
for col in X_processed.columns:
if X_processed[col].dtype.name == 'category':
X_processed[col] = X_processed[col].astype('int64')
return X_processed
else:
return X
# 检查模型是否存在
if model_type not in self.model_manager.advisory_models:
raise KeyError(f"建议模型 '{model_type}' 未找到")
model = self.model_manager.advisory_models[model_type]
threshold = self.model_manager.thresholds[model_type]['advisory']
return ModelWrapper(model, threshold, self.model_manager, model_type)
async def generate_recommendations(self, user_data: UserInput, risk_types: List[str] = None,
num_recommendations: int = 3, language: str = "zh") -> AdvisoryResponse:
"""
生成个性化建议
Args:
user_data: 用户输入数据
risk_types: 需要生成建议的风险类型
num_recommendations: 每种类型的建议数量
Returns:
AdvisoryResponse: 建议结果
"""
start_time = time.time()
fallback_used = False
try:
# 转换用户数据
user_dict = user_data.model_dump()
# 默认为高风险的类型生成建议
if risk_types is None:
risk_types = self._determine_risk_types(user_dict)
# 生成建议
sarcoI_recommendations = []
sarcoII_recommendations = []
if 'sarcoI' in risk_types:
sarcoI_recommendations = await self._generate_sarcoI_recommendations(
user_dict, num_recommendations, language
)
if 'sarcoII' in risk_types:
sarcoII_recommendations = await self._generate_sarcoII_recommendations(
user_dict, num_recommendations, language
)
# 如果没有生成任何建议,使用降级方案
if not sarcoI_recommendations and not sarcoII_recommendations:
logger.warning("DiCE建议生成失败,使用降级方案")
fallback_recs = self._generate_fallback_recommendations(user_dict, risk_types, language)
if 'sarcoI' in risk_types:
sarcoI_recommendations = fallback_recs[:2] # 前2个给SarcoI
if 'sarcoII' in risk_types:
sarcoII_recommendations = fallback_recs[2:4] if len(fallback_recs) > 2 else fallback_recs # 后2个给SarcoII
fallback_used = True
# 合并所有建议用于API返回
all_recommendations = sarcoI_recommendations + sarcoII_recommendations
# 生成优先级行动和目标指标
priority_actions = self._generate_priority_actions(sarcoI_recommendations, sarcoII_recommendations)
target_metrics = self._generate_target_metrics(user_dict, sarcoI_recommendations, sarcoII_recommendations)
processing_time = time.time() - start_time
response = AdvisoryResponse(
sarcoI_recommendations=sarcoI_recommendations,
sarcoII_recommendations=sarcoII_recommendations,
recommendations=all_recommendations, # 添加合并的建议列表
priority_actions=priority_actions,
target_metrics=target_metrics,
processing_time=processing_time,
success=True,
fallback_used=fallback_used
)
logger.info(f"建议生成完成: SarcoI={len(sarcoI_recommendations)}, SarcoII={len(sarcoII_recommendations)}, "
f"fallback={fallback_used}, 耗时={processing_time:.2f}s")
return response
except Exception as e:
logger.error(f"建议生成失败: {str(e)}")
# 返回基础建议
basic_recommendations = self._generate_basic_recommendations(language)
processing_time = time.time() - start_time
if language == 'en':
priority_actions = ["Consult Professional Doctor", "Increase Physical Activity", "Improve Diet"]
target_metrics = {"Recommendation": "Seek professional medical guidance"}
else:
priority_actions = ["咨询专业医生", "增加体力活动", "改善饮食习惯"]
target_metrics = {"建议": "寻求专业医疗指导"}
return AdvisoryResponse(
sarcoI_recommendations=basic_recommendations,
sarcoII_recommendations=[],
recommendations=basic_recommendations, # 添加合并的建议列表
priority_actions=priority_actions,
target_metrics=target_metrics,
processing_time=processing_time,
success=False,
fallback_used=True
)
def _determine_risk_types(self, user_dict: Dict) -> List[str]:
"""确定需要生成建议的风险类型"""
risk_types = []
try:
# 使用建议模型进行高精确率预测
sarcoI_result = self.model_manager.predict_advisory(user_dict, 'sarcoI')
sarcoII_result = self.model_manager.predict_advisory(user_dict, 'sarcoII')
if sarcoI_result['risk_level'] in ['medium', 'high']:
risk_types.append('sarcoI')
if sarcoII_result['risk_level'] in ['medium', 'high']:
risk_types.append('sarcoII')
except Exception as e:
logger.error(f"风险类型判断失败: {str(e)}")
# 默认返回所有类型
risk_types = ['sarcoI', 'sarcoII']
return risk_types if risk_types else ['sarcoI', 'sarcoII']
async def _generate_sarcoI_recommendations(self, user_dict: Dict, num_recs: int, language: str = "zh") -> List[RecommendationItem]:
"""生成SarcoI建议"""
recommendations = []
try:
if 'sarcoI' in self.dice_explainers:
logger.info("SarcoI 尝试使用DiCE生成建议...")
# 使用DiCE生成建议,添加超时保护
import signal
def dice_timeout_handler(signum, frame):
raise TimeoutError("DiCE生成超时")
# 设置2分钟超时
signal.signal(signal.SIGALRM, dice_timeout_handler)
signal.alarm(120)
try:
dice_recs = self._generate_dice_recommendations(user_dict, 'sarcoI', num_recs, language)
recommendations.extend(dice_recs)
logger.info(f"SarcoI DiCE生成成功,获得{len(dice_recs)}个建议")
finally:
signal.alarm(0)
# 如果DiCE建议不足,补充规则建议
if len(recommendations) < num_recs:
logger.info("SarcoI DiCE建议不足,补充规则建议...")
rule_recs = self._generate_sarcoI_rule_recommendations(user_dict, language)
recommendations.extend(rule_recs[:num_recs - len(recommendations)])
except Exception as e:
logger.error(f"SarcoI建议生成失败: {str(e)}")
logger.info("SarcoI 使用规则建议作为降级方案")
recommendations = self._generate_sarcoI_rule_recommendations(user_dict, language)[:num_recs]
return recommendations[:num_recs]
async def _generate_sarcoII_recommendations(self, user_dict: Dict, num_recs: int, language: str = "zh") -> List[RecommendationItem]:
"""生成SarcoII建议"""
recommendations = []
try:
if 'sarcoII' in self.dice_explainers:
# 使用DiCE生成建议
dice_recs = self._generate_dice_recommendations(user_dict, 'sarcoII', num_recs, language)
recommendations.extend(dice_recs)
# 如果DiCE建议不足,补充规则建议
if len(recommendations) < num_recs:
rule_recs = self._generate_sarcoII_rule_recommendations(user_dict, language)
recommendations.extend(rule_recs[:num_recs - len(recommendations)])
except Exception as e:
logger.error(f"SarcoII建议生成失败: {str(e)}")
recommendations = self._generate_sarcoII_rule_recommendations(user_dict, language)[:num_recs]
return recommendations[:num_recs]
def _generate_dice_recommendations(self, user_dict: Dict, model_type: str, num_recs: int, language: str = "zh") -> List[RecommendationItem]:
"""使用DiCE生成反事实建议"""
recommendations = []
import time
try:
dice_start_time = time.time()
logger.info(f"开始{model_type} DiCE分析...")
dice_config = self.dice_explainers[model_type]
explainer = dice_config['explainer']
mutable_features = dice_config['mutable_features']
# 准备查询数据
query_df = self._prepare_dice_query(user_dict, model_type)
logger.info(f"{model_type} DiCE查询数据准备完成: {query_df.shape}")
logger.info(f"{model_type} 查询数据内容: {query_df.iloc[0].to_dict()}")
# 检查用户当前预测结果
wrapped_model = self._wrap_model(model_type)
current_prediction = wrapped_model.predict_proba(query_df)[0]
current_class = wrapped_model.predict(query_df)[0]
logger.info(f"{model_type} 当前预测: 类别={current_class}, 概率={current_prediction}")
# 如果当前预测已经是低风险,生成维持性建议
if current_class == 0:
logger.info(f"{model_type} 当前预测为低风险类别(0),生成维持性建议")
maintenance_recommendations = self._generate_maintenance_recommendations(user_dict, model_type, language)
recommendations.extend(maintenance_recommendations)
return recommendations
# 生成反事实 - 保持原始参数确保精确度
logger.info(f"{model_type} 开始生成反事实,目标类别=0,可变特征={mutable_features}")
# 🚀 使用配置文件中的极致精度参数
dice_config_params = DiCEConfig.get_current_config()
logger.info(f"{model_type} 使用DiCE精度模式: {DiCEConfig.PRECISION_MODE}")
logger.info(f"{model_type} DiCE配置参数: {dice_config_params}")
dice_params_list = [
# 🚀 极致精度配置:追求最高质量
{
"total_CFs": dice_config_params.get('max_counterfactuals', 500),
"desired_class": 0,
"features_to_vary": mutable_features,
"proximity_weight": dice_config_params.get('proximity_weight', 0.01),
"diversity_weight": dice_config_params.get('diversity_weight', 15.0)
},
# 🔥 高精度备用配置
{
"total_CFs": dice_config_params.get('max_counterfactuals', 500) // 2,
"desired_class": 0,
"features_to_vary": mutable_features,
"proximity_weight": dice_config_params.get('proximity_weight', 0.01) * 2,
"diversity_weight": dice_config_params.get('diversity_weight', 15.0) * 0.8
},
# 🛡️ 保守配置(如果前两个失败)
{
"total_CFs": 100,
"desired_class": 0,
"features_to_vary": mutable_features,
"proximity_weight": 0.1,
"diversity_weight": 5.0
}
]
# 🚀 设置DiCE总体超时 - 极致精度模式无时间限制
dice_timeout = dice_config_params.get('timeout_seconds', None)
if dice_timeout:
logger.info(f"{model_type} DiCE设置超时: {dice_timeout}秒")
else:
logger.info(f"{model_type} DiCE无超时限制 (极致精度模式)")
counterfactuals = None
for i, params in enumerate(dice_params_list):
try:
logger.info(f"{model_type} 尝试DiCE参数组合 {i+1}: {params}")
logger.info(f"{model_type} 查询数据形状: {query_df.shape}")
logger.info(f"{model_type} 可变特征: {params.get('features_to_vary', [])}")
# 🚀 生成反事实,添加详细日志和计时
dice_gen_start = time.time()
logger.info(f"{model_type} 开始DiCE生成 (极致精度模式)...")
logger.info(f"{model_type} 预期生成 {params['total_CFs']} 个反事实样本")
logger.info(f"{model_type} 多样性权重: {params['diversity_weight']}, 接近度权重: {params['proximity_weight']}")
counterfactuals = explainer.generate_counterfactuals(query_df, **params)
dice_gen_time = time.time() - dice_gen_start
logger.info(f"{model_type} DiCE生成完成,耗时: {dice_gen_time:.2f}秒")
# 检查是否生成了有效的反事实
if (hasattr(counterfactuals, 'cf_examples_list') and
len(counterfactuals.cf_examples_list) > 0 and
counterfactuals.cf_examples_list[0].final_cfs_df is not None and
len(counterfactuals.cf_examples_list[0].final_cfs_df) > 0):
logger.info(f"{model_type} DiCE参数组合 {i+1} 成功生成反事实")
break
else:
logger.warning(f"{model_type} DiCE参数组合 {i+1} 未生成有效反事实")
counterfactuals = None
except Exception as param_e:
logger.warning(f"{model_type} DiCE参数组合 {i+1} 失败: {str(param_e)}")
counterfactuals = None
if counterfactuals is None:
logger.error(f"{model_type} 所有DiCE参数组合均失败")
dice_time = time.time() - dice_start_time
logger.info(f"{model_type} DiCE分析完成,耗时: {dice_time:.2f}秒")
# 解析反事实结果
logger.info(f"{model_type} 反事实对象类型: {type(counterfactuals)}")
logger.info(f"{model_type} 反事实对象属性: {dir(counterfactuals)}")
if hasattr(counterfactuals, 'cf_examples_list'):
logger.info(f"{model_type} cf_examples_list长度: {len(counterfactuals.cf_examples_list)}")
if len(counterfactuals.cf_examples_list) > 0:
cf_example = counterfactuals.cf_examples_list[0]
logger.info(f"{model_type} cf_example类型: {type(cf_example)}")
logger.info(f"{model_type} cf_example属性: {dir(cf_example)}")
if hasattr(cf_example, 'final_cfs_df'):
cf_df = cf_example.final_cfs_df
logger.info(f"{model_type} final_cfs_df: {cf_df is not None}")
if cf_df is not None:
logger.info(f"{model_type} 反事实DataFrame形状: {cf_df.shape}")
logger.info(f"{model_type} 反事实内容:\n{cf_df}")
# 临时简化:直接使用前N个反事实,避免Series比较问题
logger.info(f"{model_type} 使用简化筛选,直接选择前{num_recs}个反事实")
best_counterfactuals = cf_df.head(num_recs)
logger.info(f"{model_type} 筛选出 {len(best_counterfactuals)} 个最佳反事实")
# 处理筛选后的反事实
for i, (_, cf_row) in enumerate(best_counterfactuals.iterrows()):
try:
logger.info(f"{model_type} 处理第{i+1}个最佳反事实: {cf_row.to_dict()}")
# 安全的特征变化分析
try:
changes = self._analyze_feature_changes(query_df.iloc[0], cf_row, mutable_features)
logger.info(f"{model_type} 特征变化: {changes}")
except Exception as e:
logger.error(f"{model_type} 特征变化分析失败: {str(e)}")
continue
# 安全的建议生成,避免重复
try:
rec = self._changes_to_recommendation(changes, model_type, i + 1, language)
if rec:
# 检查是否与已有建议重复
is_duplicate = False
for existing_rec in recommendations:
if existing_rec.title == rec.title:
is_duplicate = True
break
if not is_duplicate:
recommendations.append(rec)
logger.info(f"{model_type} 生成建议: {rec.title}")
else:
logger.info(f"{model_type} 跳过重复建议: {rec.title}")
else:
logger.warning(f"{model_type} 第{i+1}个反事实未生成有效建议")
except Exception as e:
logger.error(f"{model_type} 建议生成失败: {str(e)}")
continue
except Exception as e:
logger.error(f"{model_type} 处理第{i+1}个反事实时出错: {str(e)}")
import traceback
logger.error(f"{model_type} 错误详情: {traceback.format_exc()}")
continue
else:
logger.error(f"{model_type} final_cfs_df 为 None")
else:
logger.error(f"{model_type} cf_example 没有 final_cfs_df 属性")
else:
logger.error(f"{model_type} cf_examples_list 为空")
else:
logger.error(f"{model_type} counterfactuals 没有 cf_examples_list 属性")
except Exception as e:
dice_time = time.time() - dice_start_time if 'dice_start_time' in locals() else 0
logger.error(f"{model_type} DiCE建议生成失败 (耗时: {dice_time:.2f}秒): {str(e)}")
# 🚀 追求极致精度模式:移除所有超时限制,无论耗时多久都要获得最佳结果
# 不再有任何时间限制,DiCE将运行到完成为止
logger.info(f"{model_type} DiCE分析耗时: {dice_time:.2f}秒 - 极致精度模式,无时间限制")
return recommendations
def _select_best_counterfactuals(self, original_row, cf_df, mutable_features, max_count):
"""🚀 极致精度反事实筛选 - 多维度质量评估,确保最优建议"""
import pandas as pd
import numpy as np
if len(cf_df) <= max_count:
return cf_df
# 🎯 扩展特征分组,更细致的分类确保建议全面性
feature_groups = {
'weight_management': ['body_mass_index', 'WWI'],
'aerobic_exercise': ['Total_Moderate_Minutes_week', 'Vigorous_MET_Ratio'],
'sedentary_behavior': ['Activity_Sedentary_Ratio', 'sedentary_minutes'],
'exercise_diversity': ['Activity_Diversity_Index'],
'lifestyle_balance': [] # 综合性改变
}
# 🏆 多维度质量评分系统
scored_cfs = []
for idx, cf_row in cf_df.iterrows():
# 1. 计算变化幅度合理性 (0-1分)
change_reasonableness = self._calculate_change_reasonableness(original_row, cf_row, mutable_features)
# 2. 计算特征变化多样性 (0-1分)
change_diversity = self._calculate_change_diversity(original_row, cf_row, mutable_features)
# 3. 计算实际可操作性 (0-1分)
actionability = self._calculate_actionability(original_row, cf_row, mutable_features)
# 4. 计算健康改善潜力 (0-1分)
health_impact = self._calculate_health_impact(original_row, cf_row, mutable_features)
# 🎯 综合质量评分 (加权平均)
quality_score = (
change_reasonableness * 0.25 + # 变化合理性
change_diversity * 0.20 + # 多样性
actionability * 0.30 + # 可操作性 (最重要)
health_impact * 0.25 # 健康影响
)
# 获取主要变化特征类别
primary_feature = self._get_primary_change_feature(original_row, cf_row, mutable_features)
feature_category = self._get_feature_category(primary_feature, feature_groups)
scored_cfs.append({
'cf_row': cf_row,
'quality_score': quality_score,
'feature_category': feature_category,
'primary_feature': primary_feature,
'change_reasonableness': change_reasonableness,
'change_diversity': change_diversity,
'actionability': actionability,
'health_impact': health_impact
})
# 🏅 按质量评分排序
scored_cfs.sort(key=lambda x: x['quality_score'], reverse=True)
# 🎪 智能选择策略:优先质量,兼顾多样性
selected_cfs = []
used_categories = set()
# 第一轮:每个类别选择最高质量的建议
for cf_info in scored_cfs:
if cf_info['feature_category'] not in used_categories:
selected_cfs.append(cf_info['cf_row'])
used_categories.add(cf_info['feature_category'])
if len(selected_cfs) >= max_count:
break
# 第二轮:如果还需要更多建议,选择剩余的高质量建议
if len(selected_cfs) < max_count:
for cf_info in scored_cfs:
if len(selected_cfs) >= max_count:
break
# 检查是否已经选择过这个建议
already_selected = False
for selected_cf in selected_cfs:
if self._are_cfs_similar(selected_cf, cf_info['cf_row'], mutable_features):
already_selected = True
break
if not already_selected:
selected_cfs.append(cf_info['cf_row'])
return pd.DataFrame(selected_cfs)
def _calculate_change_reasonableness(self, original_row, cf_row, mutable_features):
"""计算变化幅度的合理性 (0-1分)"""
reasonableness_scores = []
for feature in mutable_features:
if feature in original_row and feature in cf_row:
original_val = original_row[feature]
cf_val = cf_row[feature]
# 计算相对变化幅度
if original_val != 0:
relative_change = abs(cf_val - original_val) / abs(original_val)
else:
relative_change = abs(cf_val)
# 根据特征类型设定合理变化范围
if 'bmi' in feature.lower() or 'weight' in feature.lower():
# 体重相关:10-30%变化较合理
if 0.1 <= relative_change <= 0.3:
score = 1.0
elif relative_change < 0.1:
score = relative_change / 0.1 # 变化太小
else:
score = max(0.3, 1.0 - (relative_change - 0.3) / 0.5) # 变化太大
elif 'activity' in feature.lower() or 'minutes' in feature.lower():
# 运动相关:20-100%变化较合理
if 0.2 <= relative_change <= 1.0:
score = 1.0
elif relative_change < 0.2:
score = relative_change / 0.2
else:
score = max(0.2, 1.0 - (relative_change - 1.0) / 1.0)
else:
# 其他特征:通用评分
if 0.1 <= relative_change <= 0.5:
score = 1.0
elif relative_change < 0.1:
score = relative_change / 0.1
else:
score = max(0.2, 1.0 - (relative_change - 0.5) / 0.5)
reasonableness_scores.append(score)
return np.mean(reasonableness_scores) if reasonableness_scores else 0.5
def _calculate_change_diversity(self, original_row, cf_row, mutable_features):
"""计算特征变化的多样性 (0-1分)"""
changed_features = 0
total_features = len(mutable_features)
for feature in mutable_features:
if feature in original_row and feature in cf_row:
if abs(original_row[feature] - cf_row[feature]) > 1e-6: # 有显著变化
changed_features += 1
# 多样性评分:变化特征数量 / 总特征数量
diversity_ratio = changed_features / total_features if total_features > 0 else 0
# 理想情况是改变2-4个特征,给予最高分
if 2 <= changed_features <= 4:
return 1.0
elif changed_features == 1:
return 0.7 # 单一变化,分数较低
elif changed_features > 4:
return max(0.5, 1.0 - (changed_features - 4) * 0.1) # 变化太多,逐渐降分
else:
return 0.3 # 没有变化,最低分
def _calculate_actionability(self, original_row, cf_row, mutable_features):
"""计算实际可操作性 (0-1分)"""
actionability_scores = []
for feature in mutable_features:
if feature in original_row and feature in cf_row:
original_val = original_row[feature]
cf_val = cf_row[feature]
change = abs(cf_val - original_val)
# 根据特征类型评估可操作性
if 'minutes' in feature.lower():
# 运动时间:每周增加30-150分钟较容易实现
if 30 <= change <= 150:
score = 1.0
elif change < 30:
score = 0.8 # 变化太小,效果有限
else:
score = max(0.4, 1.0 - (change - 150) / 200) # 变化太大,难以实现
elif 'bmi' in feature.lower():
# BMI:减少1-5较容易实现
if 1 <= change <= 5:
score = 1.0
elif change < 1:
score = 0.7
else:
score = max(0.3, 1.0 - (change - 5) / 10)
elif 'ratio' in feature.lower():
# 比例类特征:0.1-0.3的变化较合理
if 0.1 <= change <= 0.3:
score = 1.0
elif change < 0.1:
score = 0.8
else:
score = max(0.4, 1.0 - (change - 0.3) / 0.5)
else:
# 默认评分
score = 0.8
actionability_scores.append(score)
return np.mean(actionability_scores) if actionability_scores else 0.5
def _calculate_health_impact(self, original_row, cf_row, mutable_features):
"""计算健康改善潜力 (0-1分)"""
impact_scores = []
for feature in mutable_features:
if feature in original_row and feature in cf_row:
original_val = original_row[feature]
cf_val = cf_row[feature]
# 根据特征类型和变化方向评估健康影响
if 'bmi' in feature.lower():
# BMI降低有益健康
if cf_val < original_val: # BMI降低
reduction = (original_val - cf_val) / original_val
score = min(1.0, reduction * 5) # BMI每降低20%得满分
else:
score = 0.3 # BMI增加不利健康
elif 'minutes' in feature.lower() and 'moderate' in feature.lower():
# 中等强度运动增加有益健康
if cf_val > original_val: # 运动时间增加
increase = (cf_val - original_val) / max(original_val, 1)
score = min(1.0, increase * 2) # 运动时间每增加50%得满分
else:
score = 0.4 # 运动减少不利健康
elif 'sedentary' in feature.lower():
# 久坐时间减少有益健康
if cf_val < original_val: # 久坐时间减少
reduction = (original_val - cf_val) / original_val
score = min(1.0, reduction * 3) # 久坐时间每减少33%得满分
else:
score = 0.2 # 久坐增加不利健康
elif 'vigorous' in feature.lower():
# 高强度运动比例增加有益健康
if cf_val > original_val:
increase = (cf_val - original_val) / max(original_val, 0.1)
score = min(1.0, increase * 1.5)
else:
score = 0.5
else:
# 默认中等影响
score = 0.6
impact_scores.append(score)
return np.mean(impact_scores) if impact_scores else 0.5
def _are_cfs_similar(self, cf1, cf2, mutable_features, threshold=0.1):
"""判断两个反事实是否相似"""
similarity_count = 0
total_features = 0
for feature in mutable_features:
if feature in cf1 and feature in cf2:
total_features += 1
val1, val2 = cf1[feature], cf2[feature]
# 计算相对差异
if abs(val1) > 1e-6 or abs(val2) > 1e-6:
relative_diff = abs(val1 - val2) / max(abs(val1), abs(val2), 1e-6)
if relative_diff < threshold:
similarity_count += 1
else:
similarity_count += 1 # 都接近0,认为相似
similarity_ratio = similarity_count / total_features if total_features > 0 else 0
return similarity_ratio > 0.8 # 80%以上特征相似则认为是相似的反事实
def _calculate_counterfactual_quality(self, original_row, cf_row, mutable_features):
"""计算反事实的质量分数"""
score = 0
for feature in mutable_features:
if feature in original_row.index and feature in cf_row.index:
# 确保获取标量值
original_val = float(original_row[feature])
cf_val = float(cf_row[feature])
change = abs(cf_val - original_val)
# 根据特征类型评分
if feature == 'body_mass_index':
# BMI变化:合理范围内的变化得分更高
if 18.5 <= cf_val <= 24.9: # 正常BMI范围
score += 10
elif 25 <= cf_val <= 29.9: # 超重范围
score += 5
else: # 过低或过高
score -= 5
# 变化幅度合理性
if change > 50: # BMI变化超过50不合理
score -= 20
elif change > 20: # 变化过大
score -= 10
elif feature in ['Total_Moderate_Minutes_week', 'Vigorous_MET_Ratio', 'Activity_Sedentary_Ratio']:
# 运动相关特征:有改善但不过度
if change > 0 and change <= original_val * 2: # 合理增长
score += 8
elif change > original_val * 2: # 过度增长
score -= 5
elif feature == 'WWI':
# WWI变化:适度减少
if cf_val < original_val and change <= 3: # 适度减少
score += 6
elif change > 5: # 变化过大
score -= 8
return score
def _get_primary_change_feature(self, original_row, cf_row, mutable_features):
"""找到变化最大的特征"""
max_change = 0
primary_feature = None
for feature in mutable_features:
if feature in original_row.index and feature in cf_row.index:
# 确保获取标量值
original_val = float(original_row[feature])
cf_val = float(cf_row[feature])
# 计算相对变化
if abs(original_val) > 1e-10: # 避免除零错误
relative_change = abs((cf_val - original_val) / original_val)
else:
relative_change = abs(cf_val)
if relative_change > max_change:
max_change = relative_change
primary_feature = feature
return primary_feature or mutable_features[0]
def _get_feature_category(self, feature, feature_groups):
"""获取特征所属的类别"""
for category, features in feature_groups.items():
if feature in features:
return category
return 'other'
def _prepare_dice_query(self, user_dict: Dict, model_type: str) -> pd.DataFrame:
"""准备DiCE查询数据"""
if model_type == 'sarcoI':
features = ['body_mass_index', 'race_ethnicity', 'WWI', 'age_years',
'Activity_Sedentary_Ratio', 'Total_Moderate_Minutes_week', 'Vigorous_MET_Ratio']
else: # sarcoII
features = ['body_mass_index', 'race_ethnicity', 'age_years',
'Activity_Sedentary_Ratio', 'Activity_Diversity_Index', 'WWI',
'Vigorous_MET_Ratio', 'sedentary_minutes']
feature_values = []
missing_features = []
for feature in features:
if feature in user_dict and user_dict[feature] is not None:
value = user_dict[feature]
feature_values.append(value)
logger.debug(f"{model_type} 特征 {feature}: {value} (来自用户数据)")
else:
# 默认值
defaults = {
'Total_Moderate_Minutes_week': 150,
'Activity_Diversity_Index': 2,
'sedentary_minutes': 480,
'Activity_Sedentary_Ratio': 0.5,
'Vigorous_MET_Ratio': 0.2
}
default_val = defaults.get(feature, 0)
feature_values.append(default_val)
missing_features.append(feature)
logger.warning(f"{model_type} 特征 {feature}: {default_val} (默认值)")
if missing_features:
logger.info(f"{model_type} DiCE缺失特征使用默认值: {missing_features}")
query_df = pd.DataFrame([feature_values], columns=features)
# 验证数据范围
self._validate_dice_query_data(query_df, model_type)
return query_df
def _validate_dice_query_data(self, query_df: pd.DataFrame, model_type: str):
"""验证DiCE查询数据的合理性"""
data = query_df.iloc[0]
# 检查关键特征的合理性
if data['body_mass_index'] < 10 or data['body_mass_index'] > 50:
logger.warning(f"{model_type} BMI异常: {data['body_mass_index']}")
if data['WWI'] < 5 or data['WWI'] > 20:
logger.warning(f"{model_type} WWI异常: {data['WWI']}")
if data['age_years'] < 18 or data['age_years'] > 80:
logger.warning(f"{model_type} 年龄异常: {data['age_years']}")
# 检查活动相关特征
if 'Activity_Sedentary_Ratio' in data:
if data['Activity_Sedentary_Ratio'] < 0 or data['Activity_Sedentary_Ratio'] > 1:
logger.warning(f"{model_type} 久坐比例异常: {data['Activity_Sedentary_Ratio']}")
logger.info(f"{model_type} DiCE数据验证完成")
def _analyze_feature_changes(self, original, counterfactual, mutable_features) -> List[Dict]:
"""分析特征变化"""
changes = []
for feature in mutable_features:
if feature in original.index and feature in counterfactual.index:
# 确保获取标量值,避免Series比较问题
original_val = float(original[feature])
cf_val = float(counterfactual[feature])
if abs(cf_val - original_val) > 1e-6:
# 安全的百分比计算
if abs(original_val) > 1e-10:
change_pct = ((cf_val - original_val) / original_val) * 100
else:
change_pct = 0.0
changes.append({
'feature': feature,
'original': original_val,
'target': cf_val,
'change': cf_val - original_val,
'change_pct': change_pct
})
return changes
def _changes_to_recommendation(self, changes: List[Dict], model_type: str, rec_id: int, language: str = "zh") -> Optional[RecommendationItem]:
"""将特征变化转换为多样化的精确建议"""
if not changes:
return None
# 按特征类型优先级选择,确保多样性
feature_priority = {
'Total_Moderate_Minutes_week': 1, # 运动优先
'Vigorous_MET_Ratio': 2,
'Activity_Diversity_Index': 3,
'body_mass_index': 4, # 体重其次
'WWI': 5,
'Activity_Sedentary_Ratio': 6
}
# 根据建议ID选择不同类型的特征,确保5个建议的多样性
if rec_id == 1:
# 第一个建议:中等强度运动
preferred_features = ['Total_Moderate_Minutes_week', 'moderate_minutes']
elif rec_id == 2:
# 第二个建议:高强度运动
preferred_features = ['Vigorous_MET_Ratio', 'vigorous_minutes']
elif rec_id == 3:
# 第三个建议:运动多样性
preferred_features = ['Activity_Diversity_Index', 'Activity_Sedentary_Ratio']
elif rec_id == 4:
# 第四个建议:体重管理
preferred_features = ['body_mass_index', 'WWI']
else:
# 第五个建议:久坐行为改善
preferred_features = ['sedentary_minutes', 'Activity_Sedentary_Ratio']
# 寻找首选特征的变化
selected_change = None
for feature in preferred_features:
for change in changes:
if change['feature'] == feature:
selected_change = change
break
if selected_change:
break
# 如果没找到首选特征,选择变化最大的
if not selected_change:
changes.sort(key=lambda x: abs(x['change']), reverse=True)
selected_change = changes[0]
feature = selected_change['feature']
original_val = selected_change['original']
target_val = selected_change['target']
change_val = selected_change['change']
# 生成精确数值建议
title, description, target_change, expected_impact = self._get_precise_feature_recommendation(
feature, original_val, target_val, change_val, model_type, language
)
# 基于变化幅度计算优先级
change_magnitude = abs(change_val / original_val) if original_val != 0 else abs(change_val)
if change_magnitude > 0.3: # 变化超过30%
priority = "High" if language == 'en' else "高"
elif change_magnitude > 0.15: # 变化超过15%
priority = "Medium" if language == 'en' else "中"
else:
priority = "Low" if language == 'en' else "低"
return RecommendationItem(
title=title,
description=description,
priority=priority,
target_change=target_change,
expected_impact=expected_impact
)
def _get_precise_feature_recommendation(self, feature: str, original_val: float, target_val: float,
change_val: float, model_type: str, language: str = "zh") -> tuple:
"""获取基于精确数值的特征建议"""
if feature == 'body_mass_index':
if language == 'en':
title = "Exercise-Based Weight Management Plan"
description = f"Current BMI: {original_val:.1f} → Target: {target_val:.1f}"
if original_val > target_val:
weight_loss = (original_val - target_val) * 2.5 # Assuming height 1.7m, 1 BMI ≈ 2.5kg
description += f"\n• Weight Loss Goal: {weight_loss:.1f} kg"
description += f"\n• Timeline: {weight_loss/0.75:.0f} weeks (0.5-1kg/week)"
description += f"\n• Exercise Plan:"
description += f"\n - Aerobic: 300+ min/week"
description += f"\n - Strength training: 3x/week"
description += f"\n - HIIT: 2x/week"
target_change = f"BMI: {original_val:.1f} → {target_val:.1f} (change: {change_val:+.1f})"
expected_impact = f"Every 1-point BMI reduction decreases sarcopenia risk by 8-12% through exercise"
else:
title = "运动减重精确管理计划"
description = f"当前BMI: {original_val:.1f} → 目标: {target_val:.1f}"
if original_val > target_val:
weight_loss = (original_val - target_val) * 2.5 # 假设身高1.7m,每1BMI约2.5kg
description += f"\n• 减重目标: {weight_loss:.1f} 公斤"
description += f"\n• 时间计划: {weight_loss/0.75:.0f} 周 (每周0.5-1公斤)"
description += f"\n• 运动方案:"
description += f"\n - 有氧运动: 每周300+分钟"
description += f"\n - 力量训练: 每周3次"
description += f"\n - HIIT训练: 每周2次"
target_change = f"BMI: {original_val:.1f} → {target_val:.1f} (变化: {change_val:+.1f})"
expected_impact = f"BMI每降低1点,通过运动肌少症风险降低约8-12%"
elif feature == 'Total_Moderate_Minutes_week':
if language == 'en':
title = "Moderate-Intensity Exercise Precision Plan"
description = f"Current weekly: {original_val:.0f}min → Target: {target_val:.0f}min"
if target_val > original_val:
increase = target_val - original_val
daily_increase = increase / 7
description += f"\n• Increase: {increase:.0f} min/week ({daily_increase:.0f} min/day)"
# Specific exercise recommendations
if daily_increase <= 15:
description += f"\n• Start: Daily brisk walk {daily_increase:.0f}min"
elif daily_increase <= 30:
description += f"\n• Plan: Walk 20min + swim/cycle {daily_increase-20:.0f}min"
else:
description += f"\n• Plan: Walk 30min + other exercises {daily_increase-30:.0f}min"
description += f"\n• Activity Mix:"
description += f"\n - Walking/jogging: {increase*0.4:.0f}min/week"
description += f"\n - Swimming: {increase*0.3:.0f}min/week"
description += f"\n - Cycling: {increase*0.2:.0f}min/week"
description += f"\n - Tai Chi/Yoga: {increase*0.1:.0f}min/week"
# Progressive plan
weeks_to_target = max(4, int(increase / 30))
description += f"\n\nProgressive plan ({weeks_to_target}-week target):"
for week in range(1, min(weeks_to_target + 1, 5)):
weekly_target = original_val + (increase * week / weeks_to_target)
description += f"\nWeek {week}: {weekly_target:.0f}min/week"
target_change = f"Moderate exercise: {original_val:.0f}min/week → {target_val:.0f}min/week"
expected_impact = f"Every 30min/week increase reduces sarcopenia risk by 5-8%, muscle strength improves 10-15%"
else:
title = "中等强度运动精确计划"
description = f"当前每周: {original_val:.0f}分钟 → 目标: {target_val:.0f}分钟"
if target_val > original_val:
increase = target_val - original_val
daily_increase = increase / 7
description += f"\n• 增加: {increase:.0f} 分钟/周 (每天{daily_increase:.0f}分钟)"
# 具体的运动建议
if daily_increase <= 15:
description += f"\n• 开始: 每天快走{daily_increase:.0f}分钟"
elif daily_increase <= 30:
description += f"\n• 方案: 快走20分钟 + 游泳/骑车{daily_increase-20:.0f}分钟"
else:
description += f"\n• 方案: 快走30分钟 + 其他运动{daily_increase-30:.0f}分钟"
description += f"\n• 活动组合:"
description += f"\n - 快走/慢跑: {increase*0.4:.0f}分钟/周"
description += f"\n - 游泳: {increase*0.3:.0f}分钟/周"
description += f"\n - 骑车: {increase*0.2:.0f}分钟/周"
description += f"\n - 太极/瑜伽: {increase*0.1:.0f}分钟/周"
# 渐进式计划
weeks_to_target = max(4, int(increase / 30))
description += f"\n\n渐进式计划({weeks_to_target}周达标):"
for week in range(1, min(weeks_to_target + 1, 5)):
weekly_target = original_val + (increase * week / weeks_to_target)
description += f"\n第{week}周:{weekly_target:.0f}分钟/周"
target_change = f"中等强度运动: {original_val:.0f}分钟/周 → {target_val:.0f}分钟/周"
expected_impact = f"每增加30分钟/周,肌少症风险降低约5-8%,肌肉力量提升10-15%"
elif feature == 'Vigorous_MET_Ratio':
if language == 'en':
title = "High-Intensity Exercise Precision Enhancement Plan"
description = f"Current ratio: {original_val:.3f} → Target ratio: {target_val:.3f}"
if target_val > original_val:
increase = target_val - original_val
description += f"\n📈 Need to increase high-intensity exercise ratio by {increase:.3f}"
# Convert to actual time recommendations
weekly_minutes = increase * 150 # Based on 150min/week baseline
daily_minutes = weekly_minutes / 7
description += f"\n⏱️ Equivalent to adding {weekly_minutes:.0f} min/week high-intensity exercise"
description += f"\n (about {daily_minutes:.0f} min/day)"
# Specific high-intensity exercise plans
description += f"\n🔥 Recommended high-intensity exercise combination:"
if weekly_minutes <= 60:
description += f"\n • Strength training: 3x/week, {weekly_minutes/3:.0f}min each"
description += f"\n • HIIT training: 2x/week, {weekly_minutes/4:.0f}min each"
elif weekly_minutes <= 120:
description += f"\n • Strength training: 3x/week, 25min each"
description += f"\n • Running/sprints: 2x/week, {(weekly_minutes-75)/2:.0f}min each"
description += f"\n • HIIT training: 2x/week, 15min each"
else:
description += f"\n • Strength training: 4x/week, 30min each"
description += f"\n • Running: 3x/week, {(weekly_minutes-120)/3:.0f}min each"
description += f"\n • HIIT training: 2x/week, 20min each"
# Safety reminders and progressive plan
description += f"\n⚠️ Safety reminders:"
description += f"\n • Warm up thoroughly for 5-10 minutes before exercise"
description += f"\n • Cool down and stretch for 10 minutes after exercise"
description += f"\n • Progress gradually to avoid exercise injuries"
# Progressive intensity enhancement
weeks_to_target = max(6, int(weekly_minutes / 20))
description += f"\n📅 {weeks_to_target}-week progressive plan:"
for week in [1, 2, 4, weeks_to_target]:
if week <= weeks_to_target:
weekly_target = weekly_minutes * week / weeks_to_target
description += f"\n Week {week}: {weekly_target:.0f}min/week high-intensity exercise"
target_change = f"High-intensity exercise ratio: {original_val:.3f} → {target_val:.3f}"
expected_impact = f"Every 0.1 increase in high-intensity ratio reduces sarcopenia risk by 10-15%, muscle strength improves 20-30%"
else:
title = "高强度运动精确提升计划"
description = f"当前比例: {original_val:.3f} → 目标比例: {target_val:.3f}"
if target_val > original_val:
increase = target_val - original_val
description += f"\n📈 需要提升 {increase:.3f} 的高强度运动比例"
# 转换为实际时间建议
weekly_minutes = increase * 150 # 假设基于150分钟/周的基础
daily_minutes = weekly_minutes / 7
description += f"\n⏱️ 相当于每周增加 {weekly_minutes:.0f} 分钟高强度运动"
description += f"\n (每天约 {daily_minutes:.0f} 分钟)"
# 具体的高强度运动计划
description += f"\n🔥 推荐高强度运动组合:"
if weekly_minutes <= 60:
description += f"\n • 力量训练:3次/周,每次{weekly_minutes/3:.0f}分钟"
description += f"\n • HIIT训练:2次/周,每次{weekly_minutes/4:.0f}分钟"
elif weekly_minutes <= 120:
description += f"\n • 力量训练:3次/周,每次25分钟"
description += f"\n • 跑步/冲刺:2次/周,每次{(weekly_minutes-75)/2:.0f}分钟"
description += f"\n • HIIT训练:2次/周,每次15分钟"
else:
description += f"\n • 力量训练:4次/周,每次30分钟"
description += f"\n • 跑步:3次/周,每次{(weekly_minutes-120)/3:.0f}分钟"
description += f"\n • HIIT训练:2次/周,每次20分钟"
# 安全提醒和渐进计划
description += f"\n⚠️ 安全提醒:"
description += f"\n • 运动前充分热身5-10分钟"
description += f"\n • 运动后拉伸放松10分钟"
description += f"\n • 循序渐进,避免运动损伤"
# 渐进式强度提升
weeks_to_target = max(6, int(weekly_minutes / 20))
description += f"\n📅 {weeks_to_target}周渐进计划:"
for week in [1, 2, 4, weeks_to_target]:
if week <= weeks_to_target:
weekly_target = weekly_minutes * week / weeks_to_target
description += f"\n 第{week}周:{weekly_target:.0f}分钟/周高强度运动"
target_change = f"高强度运动比例: {original_val:.3f} → {target_val:.3f}"
expected_impact = f"高强度运动比例每提升0.1,肌少症风险降低10-15%,肌肉力量提升20-30%"
elif feature == 'Activity_Sedentary_Ratio':
if language == 'en':
title = "Sedentary Behavior Precision Improvement Plan"
description = f"Current activity/sedentary ratio: {original_val:.3f} → Target ratio: {target_val:.3f}"
if target_val > original_val:
increase = target_val - original_val
# Assuming 16 hours of waking time
daily_activity_increase = increase * 16 * 60 # Convert to minutes
description += f"\n📈 Need to improve activity/sedentary ratio by {increase:.3f}"
description += f"\n⏱️ Equivalent to increasing daily activity by {daily_activity_increase:.0f} minutes"
# Specific activity increase strategies
description += f"\n🎯 Activity increase strategies:"
if daily_activity_increase <= 30:
description += f"\n • Stand and move for 3-5 minutes every hour"
description += f"\n • Take stairs instead of elevators"
description += f"\n • Stand during phone calls/meetings"
elif daily_activity_increase <= 60:
description += f"\n • Stand and move for 5 minutes every 45 minutes"
description += f"\n • Take a 15-minute walk during lunch break"
description += f"\n • Use standing desk for 2-3 hours"
description += f"\n • Park farther away/get off transit early to walk"
else:
description += f"\n • Stand and move for 5 minutes every 30 minutes"
description += f"\n • Walk/bike to work for 30 minutes"
description += f"\n • Use standing desk for 4-6 hours"
description += f"\n • Actively engage in household activities"
# Different strategies for weekdays and weekends
description += f"\n📅 Time-specific strategies:"
description += f"\n Weekdays:"
description += f"\n • Set 30-minute activity reminders"
description += f"\n • Do stretching exercises between meetings"
description += f"\n • Take 10-15 minute walks after lunch"
description += f"\n Weekends:"
description += f"\n • Increase outdoor activities to {daily_activity_increase*1.5:.0f} minutes"
description += f"\n • Engage in housework, gardening, walking shopping"
description += f"\n • Reduce TV/phone screen time"
# Practical tools recommendations
description += f"\n🛠️ Practical tools:"
description += f"\n • Set sedentary reminders on phone"
description += f"\n • Use pedometer/fitness tracker"
description += f"\n • Adjustable height desk"
description += f"\n • Exercise ball instead of office chair"
target_change = f"Activity/sedentary ratio: {original_val:.3f} → {target_val:.3f}"
expected_impact = f"Every 0.1 increase in activity ratio reduces sarcopenia risk by 6-10%, metabolism improves 15-20%"
else:
title = "久坐行为精确改善计划"
description = f"当前活动/久坐比例: {original_val:.3f} → 目标比例: {target_val:.3f}"
if target_val > original_val:
increase = target_val - original_val
# 假设16小时清醒时间
daily_activity_increase = increase * 16 * 60 # 转换为分钟
description += f"\n📈 需要提升 {increase:.3f} 的活动/久坐比例"
description += f"\n⏱️ 相当于每天增加活动 {daily_activity_increase:.0f} 分钟"
# 具体的活动增加策略
description += f"\n🎯 活动增加策略:"
if daily_activity_increase <= 30:
description += f"\n • 每小时起身活动3-5分钟"
description += f"\n • 走楼梯代替电梯"
description += f"\n • 站立接电话/开会"
elif daily_activity_increase <= 60:
description += f"\n • 每45分钟起身活动5分钟"
description += f"\n • 午休时间散步15分钟"
description += f"\n • 使用站立办公桌2-3小时"
description += f"\n • 远距离停车/提前下车步行"
else:
description += f"\n • 每30分钟起身活动5分钟"
description += f"\n • 上下班步行/骑车30分钟"
description += f"\n • 站立办公4-6小时"
description += f"\n • 主动做家务活动"
# 工作日和周末的不同策略
description += f"\n📅 分时段策略:"
description += f"\n 工作日:"
description += f"\n • 设置每30分钟活动提醒"
description += f"\n • 会议间隙做伸展运动"
description += f"\n • 午餐后散步10-15分钟"
description += f"\n 周末:"
description += f"\n • 户外活动增加到{daily_activity_increase*1.5:.0f}分钟"
description += f"\n • 家务活动、园艺、购物步行"
description += f"\n • 减少看电视/手机时间"
# 实用工具推荐
description += f"\n🛠️ 实用工具:"
description += f"\n • 手机设置久坐提醒"
description += f"\n • 使用计步器/智能手环"
description += f"\n • 可调节高度办公桌"
description += f"\n • 瑜伽球代替办公椅"
target_change = f"活动/久坐比例: {original_val:.3f} → {target_val:.3f}"
expected_impact = f"活动比例每提升0.1,肌少症风险降低6-10%,新陈代谢提升15-20%"
elif feature == 'WWI':
if language == 'en':
title = "Core-Focused Waist Management Plan"
description = f"Current WWI: {original_val:.2f} → Target WWI: {target_val:.2f}"
if original_val > target_val:
decrease = original_val - target_val
# WWI = waist/height^0.5, assuming height 1.7m
waist_reduction = decrease * (1.7 ** 0.5)
description += f"\n📉 Need to reduce WWI by {decrease:.2f}"
description += f"\n📏 Equivalent to waist reduction of approximately {waist_reduction:.1f} cm"
description += f"\n💪 Exercise recommendations:"
description += f"\n • Core strengthening: Planks, crunches, Russian twists"
description += f"\n • Aerobic exercise: Focus on fat burning"
description += f"\n • HIIT training: Target abdominal fat reduction"
target_change = f"WWI: {original_val:.2f} → {target_val:.2f}"
expected_impact = f"Every 0.5 WWI reduction decreases sarcopenia risk by 7-12%"
else:
title = "核心运动腰围精确管理计划"
description = f"当前WWI: {original_val:.2f} → 目标WWI: {target_val:.2f}"
if original_val > target_val:
decrease = original_val - target_val
# WWI = 腰围/身高^0.5,假设身高1.7m
waist_reduction = decrease * (1.7 ** 0.5)
description += f"\n📉 需要减少WWI {decrease:.2f}"
description += f"\n📏 相当于腰围减少约 {waist_reduction:.1f} 厘米"
description += f"\n💪 运动建议:"
description += f"\n • 核心训练:平板支撑、卷腹、俄式转体"
description += f"\n • 有氧运动:专注脂肪燃烧"
description += f"\n • HIIT训练:针对腹部脂肪减少"
target_change = f"WWI: {original_val:.2f} → {target_val:.2f}"
expected_impact = f"WWI每降低0.5,肌少症风险降低约7-12%"
elif feature == 'Activity_Diversity_Index':
if language == 'en':
title = "Exercise Diversity Precision Enhancement Plan"
description = f"Current diversity index: {original_val:.1f} → Target: {target_val:.1f}"
if target_val > original_val:
increase = target_val - original_val
target_types = int(target_val) + 2
description += f"\n📈 Need to increase activity diversity by {increase:.1f}"
description += f"\n🎯 Goal: Engage in {target_types} different types of exercise weekly"
# Exercise type classification and recommendations
description += f"\n🏃♂️ Recommended exercise type combinations:"
description += f"\n Aerobic exercises (2-3 types):"
description += f"\n • Brisk walking/jogging - 3-4x/week, 30min each"
description += f"\n • Swimming/water exercise - 2x/week, 45min each"
description += f"\n • Cycling - 2-3x/week, 40min each"
description += f"\n 💪 Strength training (1-2 types):"
description += f"\n • Weight training - 2-3x/week, 45min each"
description += f"\n • Bodyweight training - 3x/week, 30min each"
description += f"\n • Resistance band training - 2x/week, 25min each"
description += f"\n 🧘♀️ Flexibility & balance (1-2 types):"
description += f"\n • Yoga - 2-3x/week, 60min each"
description += f"\n • Tai Chi - 2x/week, 45min each"
description += f"\n • Pilates - 2x/week, 50min each"
description += f"\n 🏐 Recreational sports (1 type):"
description += f"\n • Ball sports - 1-2x/week"
description += f"\n • Dancing - 1-2x/week"
description += f"\n • Hiking/mountain climbing - 1x/week"
# Weekly plan example
description += f"\n📅 Diverse weekly plan example:"
description += f"\n Monday: Strength training (45min)"
description += f"\n Tuesday: Brisk walking (30min) + Yoga (30min)"
description += f"\n Wednesday: Swimming (45min)"
description += f"\n Thursday: Bodyweight training (30min) + Tai Chi (30min)"
description += f"\n Friday: Cycling (40min)"
description += f"\n Saturday: Ball sports (60min)"
description += f"\n Sunday: Hiking/climbing (90min)"
# Progressive increase recommendations
description += f"\n🔄 Progressive increase strategy:"
current_types = max(1, int(original_val))
for week in range(1, 5):
week_types = min(current_types + week, target_types)
description += f"\n Week {week}: {week_types} exercise types"
target_change = f"Activity diversity: {original_val:.1f} → {target_val:.1f}"
expected_impact = f"Every 1-point increase in exercise diversity reduces sarcopenia risk by 4-8%, whole-body coordination improves 25%"
else:
title = "运动多样性精确提升计划"
description = f"当前多样性指数: {original_val:.1f} → 目标: {target_val:.1f}"
if target_val > original_val:
increase = target_val - original_val
target_types = int(target_val) + 2
description += f"\n📈 需要增加 {increase:.1f} 的活动多样性"
description += f"\n🎯 目标:每周进行 {target_types} 种不同类型的运动"
# 运动类型分类和建议
description += f"\n🏃♂️ 推荐运动类型组合:"
description += f"\n 有氧运动类 (2-3种):"
description += f"\n • 快走/慢跑 - 每周3-4次,每次30分钟"
description += f"\n • 游泳/水中运动 - 每周2次,每次45分钟"
description += f"\n • 骑自行车 - 每周2-3次,每次40分钟"
description += f"\n 💪 力量训练类 (1-2种):"
description += f"\n • 器械训练 - 每周2-3次,每次45分钟"
description += f"\n • 自重训练 - 每周3次,每次30分钟"
description += f"\n • 弹力带训练 - 每周2次,每次25分钟"
description += f"\n 🧘♀️ 柔韧平衡类 (1-2种):"
description += f"\n • 瑜伽 - 每周2-3次,每次60分钟"
description += f"\n • 太极 - 每周2次,每次45分钟"
description += f"\n • 普拉提 - 每周2次,每次50分钟"
description += f"\n 🏐 趣味运动类 (1种):"
description += f"\n • 球类运动 - 每周1-2次"
description += f"\n • 舞蹈 - 每周1-2次"
description += f"\n • 爬山/徒步 - 每周1次"
# 周计划示例
description += f"\n📅 多样性周计划示例:"
description += f"\n 周一:力量训练 (45分钟)"
description += f"\n 周二:快走 (30分钟) + 瑜伽 (30分钟)"
description += f"\n 周三:游泳 (45分钟)"
description += f"\n 周四:自重训练 (30分钟) + 太极 (30分钟)"
description += f"\n 周五:骑车 (40分钟)"
description += f"\n 周六:球类运动 (60分钟)"
description += f"\n 周日:徒步/爬山 (90分钟)"
# 渐进式增加建议
description += f"\n🔄 渐进式增加策略:"
current_types = max(1, int(original_val))
for week in range(1, 5):
week_types = min(current_types + week, target_types)
description += f"\n 第{week}周:{week_types}种运动类型"
target_change = f"活动多样性: {original_val:.1f} → {target_val:.1f}"
expected_impact = f"运动多样性每提升1点,肌少症风险降低4-8%,全身协调性提升25%"
elif feature == 'sedentary_minutes':
if language == 'en':
title = "Daily Sedentary Time Precision Control Plan"
description = f"Current daily sedentary: {original_val:.0f}min → Target: {target_val:.0f}min"
if original_val > target_val:
decrease = original_val - target_val
hours_decrease = decrease / 60
description += f"\n📉 Need to reduce daily sedentary time by {decrease:.0f} minutes ({hours_decrease:.1f} hours)"
# Time-segmented reduction strategies
description += f"\n🕐 Time-segmented reduction strategies:"
if decrease <= 60:
description += f"\n • Stand and move for 5 minutes every hour"
description += f"\n • Stand or walk during phone meetings"
description += f"\n • Stand for 10 minutes after meals"
elif decrease <= 120:
description += f"\n • Stand and move for 5-8 minutes every 45 minutes"
description += f"\n • Take 15-20 minute walks during lunch break"
description += f"\n • Do simple exercises while watching TV"
description += f"\n • Use standing desk for 1-2 hours"
else:
description += f"\n • Stand and move for 5-10 minutes every 30 minutes"
description += f"\n • Walk/bike to work instead of driving"
description += f"\n • Use standing desk for 3-4 hours"
description += f"\n • Actively take on tasks requiring movement"
# Specific alternative activities
description += f"\n🚶♂️ Sedentary replacement activities:"
description += f"\n Work hours:"
description += f"\n • Stand during meetings/phone calls"
description += f"\n • Walk to colleagues' desks for communication"
description += f"\n • Take stairs instead of elevators"
description += f"\n • Walk farther for water/restroom breaks"
description += f"\n Leisure time:"
description += f"\n • Stretch while watching TV"
description += f"\n • Do housework while listening to music"
description += f"\n • Stand while reading/studying"
description += f"\n • Take walks instead of scrolling phone"
# Practical tools and reminders
description += f"\n🛠️ Practical tools and reminders:"
description += f"\n • Set {30 if decrease > 120 else 45 if decrease > 60 else 60}-minute sedentary reminders on phone"
description += f"\n • Use standing desk or adjustable desk"
description += f"\n • Wear fitness tracker to monitor sedentary time"
description += f"\n • Place reminder notes on desk"
# Progressive reduction plan
description += f"\n📅 4-week progressive reduction plan:"
for week in range(1, 5):
week_target = original_val - (decrease * week / 4)
daily_reduction = decrease * week / 4
description += f"\n Week {week}: Daily sedentary {week_target:.0f}min (reduce {daily_reduction:.0f}min)"
else:
title = "每日久坐时间精确控制计划"
description = f"当前每日久坐: {original_val:.0f}分钟 → 目标: {target_val:.0f}分钟"
if original_val > target_val:
decrease = original_val - target_val
hours_decrease = decrease / 60
description += f"\n📉 需要每天减少久坐 {decrease:.0f} 分钟 ({hours_decrease:.1f}小时)"
# 分时段减少策略
description += f"\n🕐 分时段减少策略:"
if decrease <= 60:
description += f"\n • 每小时起身活动5分钟"
description += f"\n • 电话会议时站立或走动"
description += f"\n • 用餐后站立10分钟"
elif decrease <= 120:
description += f"\n • 每45分钟起身活动5-8分钟"
description += f"\n • 午休时间散步15-20分钟"
description += f"\n • 看电视时做简单运动"
description += f"\n • 站立办公1-2小时"
else:
description += f"\n • 每30分钟起身活动5-10分钟"
description += f"\n • 上下班步行/骑车代替开车"
description += f"\n • 站立办公3-4小时"
description += f"\n • 主动承担需要走动的任务"
# 具体的替代活动
description += f"\n🚶♂️ 久坐替代活动:"
description += f"\n 工作时间:"
description += f"\n • 站立开会/打电话"
description += f"\n • 走到同事桌前交流"
description += f"\n • 爬楼梯代替电梯"
description += f"\n • 远距离取水/上厕所"
description += f"\n 休闲时间:"
description += f"\n • 看电视时做拉伸"
description += f"\n • 边听音乐边做家务"
description += f"\n • 站立阅读/学习"
description += f"\n • 散步代替刷手机"
# 实用工具和提醒
description += f"\n🛠️ 实用工具和提醒:"
description += f"\n • 手机设置每{30 if decrease > 120 else 45 if decrease > 60 else 60}分钟久坐提醒"
description += f"\n • 使用站立办公桌或升降桌"
description += f"\n • 佩戴智能手环监测久坐"
description += f"\n • 在办公桌放置提醒标语"
# 渐进式减少计划
description += f"\n📅 4周渐进减少计划:"
for week in range(1, 5):
week_target = original_val - (decrease * week / 4)
daily_reduction = decrease * week / 4
description += f"\n 第{week}周:每日久坐{week_target:.0f}分钟 (减少{daily_reduction:.0f}分钟)"
target_change = f"每日久坐时间: {original_val:.0f}分钟 → {target_val:.0f}分钟"
expected_impact = f"每日久坐时间每减少60分钟,肌少症风险降低约5-9%"
else:
title = f"{feature}优化计划"
description = f"当前值: {original_val:.3f} → 目标值: {target_val:.3f}"
target_change = f"{feature}: {original_val:.3f} → {target_val:.3f}"
expected_impact = "遵循专业指导,预期改善肌少症风险"
return title, description, target_change, expected_impact
def _get_recommendation_texts(self, language: str = "zh") -> Dict:
"""获取双语建议文本"""
texts = {
"zh": {
"increase_vigorous": {
"title": "增加高强度运动",
"description": "每周至少进行75分钟高强度运动,如跑步、游泳或力量训练\n\n具体建议:\n• 跑步:每次20-30分钟,每周3次\n• 力量训练:每次45分钟,每周2次\n• 游泳:每次30分钟,每周2次",
"priority": "高",
"target_change": "高强度运动比例提升至0.3以上",
"expected_impact": "显著改善肌肉力量和心肺功能"
},
"increase_moderate": {
"title": "增加中等强度运动",
"description": "每周进行至少150分钟中等强度运动\n\n推荐活动:\n• 快走:每次30分钟,每周5次\n• 骑车:每次40分钟,每周3次\n• 太极:每次45分钟,每周2次",
"priority": "中",
"target_change": "中等强度运动增至200分钟/周",
"expected_impact": "提升整体体能和肌肉耐力"
},
"reduce_sedentary": {
"title": "减少久坐时间",
"description": "控制每日久坐时间,增加活动频率\n\n实施方案:\n• 每30分钟起身活动5分钟\n• 站立办公2-3小时\n• 步行会议和电话\n• 使用提醒工具",
"priority": "高",
"target_change": "久坐比例降至0.4以下",
"expected_impact": "改善肌肉活性,降低肌少症风险"
},
"weight_management": {
"title": "体重管理",
"description": "通过运动控制体重在健康范围\n\n目标设定:\n• BMI控制在18.5-24.9\n• 每周减重0.5-1公斤\n• 有氧运动300分钟/周\n• 力量训练3次/周",
"priority": "中",
"target_change": "BMI控制在18.5-24.9范围内",
"expected_impact": "减轻关节负担,改善身体成分"
},
"reduce_sedentary": {
"title": "减少久坐时间",
"description": "每小时起身活动5-10分钟,减少连续久坐时间",
"priority": "高",
"target_change": "久坐比例降至0.4以下",
"expected_impact": "改善肌肉活性,降低肌少症风险"
},
"increase_moderate": {
"title": "增加中等强度运动",
"description": "每周进行至少150分钟中等强度运动,如快走、骑车",
"priority": "中",
"target_change": "中等强度运动增至200分钟/周",
"expected_impact": "提升整体体能和肌肉耐力"
},
"weight_management": {
"title": "体重管理",
"description": "通过合理饮食和运动,维持健康体重",
"priority": "中",
"target_change": "BMI控制在18.5-24.9范围内",
"expected_impact": "减轻关节负担,改善身体成分"
},
"protein_intake": {
"title": "增加蛋白质摄入",
"description": "每日摄入1.2-1.6g/kg体重的优质蛋白质",
"priority": "高",
"target_change": "蛋白质摄入达到推荐标准",
"expected_impact": "促进肌肉合成,维持肌肉质量"
},
"regular_monitoring": {
"title": "定期健康监测",
"description": "每6个月进行一次肌肉质量和功能评估",
"priority": "中",
"target_change": "建立定期监测计划",
"expected_impact": "早期发现肌肉质量变化,及时调整干预策略"
},
"increase_diversity": {
"title": "增加运动多样性",
"description": "尝试多种不同类型的体力活动,如有氧运动、力量训练、柔韧性练习",
"priority": "高",
"target_change": "活动类型增加至4种以上",
"expected_impact": "全面改善身体功能,降低SarcoII风险"
},
"strength_training": {
"title": "加强力量训练",
"description": "每周进行2-3次力量训练,重点锻炼大肌群",
"priority": "高",
"target_change": "建立规律的力量训练计划",
"expected_impact": "增强肌肉力量,改善身体功能"
},
"increase_activity": {
"title": "增加体力活动",
"description": "每周至少进行150分钟中等强度或75分钟高强度运动",
"priority": "高",
"target_change": "达到WHO推荐的运动量标准",
"expected_impact": "全面改善身体健康状况"
},
"balanced_nutrition": {
"title": "均衡营养摄入",
"description": "确保充足的蛋白质、维生素D和钙的摄入",
"priority": "中",
"target_change": "建立健康的饮食习惯",
"expected_impact": "支持肌肉健康和骨骼强度"
}
},
"en": {
"increase_vigorous": {
"title": "Increase High-Intensity Exercise",
"description": "Engage in at least 75 minutes of vigorous exercise weekly\n\nSpecific recommendations:\n• Running: 20-30 minutes per session, 3 times weekly\n• Strength training: 45 minutes per session, 2 times weekly\n• Swimming: 30 minutes per session, 2 times weekly",
"priority": "High",
"target_change": "Increase vigorous exercise ratio to above 0.3",
"expected_impact": "Significantly improve muscle strength and cardiopulmonary function"
},
"reduce_sedentary": {
"title": "Reduce Sedentary Time",
"description": "Control daily sedentary time and increase activity frequency\n\nImplementation plan:\n• Stand and move for 5 minutes every 30 minutes\n• Use standing desk for 2-3 hours\n• Walking meetings and phone calls\n• Use reminder tools",
"priority": "High",
"target_change": "Reduce sedentary ratio to below 0.4",
"expected_impact": "Improve muscle activity and reduce sarcopenia risk"
},
"increase_moderate": {
"title": "Increase Moderate-Intensity Exercise",
"description": "Engage in at least 150 minutes of moderate exercise weekly\n\nRecommended activities:\n• Brisk walking: 30 minutes per session, 5 times weekly\n• Cycling: 40 minutes per session, 3 times weekly\n• Tai Chi: 45 minutes per session, 2 times weekly",
"priority": "Medium",
"target_change": "Increase moderate exercise to 200 minutes/week",
"expected_impact": "Enhance overall fitness and muscle endurance"
},
"weight_management": {
"title": "Weight Management",
"description": "Maintain healthy weight through balanced diet and exercise",
"priority": "Medium",
"target_change": "Keep BMI within 18.5-24.9 range",
"expected_impact": "Reduce joint burden and improve body composition"
},
"protein_intake": {
"title": "Increase Protein Intake",
"description": "Consume 1.2-1.6g/kg body weight of high-quality protein daily",
"priority": "High",
"target_change": "Meet recommended protein intake standards",
"expected_impact": "Promote muscle synthesis and maintain muscle mass"
},
"regular_monitoring": {
"title": "Regular Health Monitoring",
"description": "Conduct muscle mass and function assessment every 6 months",
"priority": "Medium",
"target_change": "Establish regular monitoring plan",
"expected_impact": "Early detection of muscle mass changes, timely intervention adjustment"
},
"increase_diversity": {
"title": "Increase Exercise Diversity",
"description": "Try various types of physical activities, such as aerobic exercise, strength training, and flexibility exercises",
"priority": "High",
"target_change": "Increase activity types to 4 or more",
"expected_impact": "Comprehensively improve body function and reduce SarcoII risk"
},
"strength_training": {
"title": "Strengthen Resistance Training",
"description": "Perform strength training 2-3 times per week, focusing on major muscle groups",
"priority": "High",
"target_change": "Establish regular strength training plan",
"expected_impact": "Enhance muscle strength and improve body function"
},
"increase_activity": {
"title": "Increase Physical Activity",
"description": "Engage in at least 150 minutes of moderate-intensity or 75 minutes of vigorous-intensity exercise weekly",
"priority": "High",
"target_change": "Meet WHO recommended physical activity standards",
"expected_impact": "Comprehensively improve overall health"
},
"balanced_nutrition": {
"title": "Balanced Nutrition Intake",
"description": "Ensure adequate intake of protein, vitamin D, and calcium",
"priority": "Medium",
"target_change": "Establish healthy eating habits",
"expected_impact": "Support muscle health and bone strength"
}
}
}
return texts
def _generate_sarcoI_rule_recommendations(self, user_dict: Dict, language: str = "zh") -> List[RecommendationItem]:
"""生成SarcoI基于规则的建议"""
recommendations = []
texts = self._get_recommendation_texts(language)
# 基于用户数据生成规则建议
bmi = user_dict.get('body_mass_index', 25)
sedentary_ratio = user_dict.get('Activity_Sedentary_Ratio', 0.5)
moderate_minutes = user_dict.get('Total_Moderate_Minutes_week', 150)
vigorous_ratio = user_dict.get('Vigorous_MET_Ratio', 0.2)
if vigorous_ratio < 0.2:
rec = texts[language]["increase_vigorous"]
recommendations.append(RecommendationItem(
title=rec["title"],
description=rec["description"],
priority=rec["priority"],
target_change=rec["target_change"],
expected_impact=rec["expected_impact"]
))
if moderate_minutes < 150:
rec = texts[language]["increase_moderate"]
recommendations.append(RecommendationItem(
title=rec["title"],
description=rec["description"],
priority=rec["priority"],
target_change=rec["target_change"],
expected_impact=rec["expected_impact"]
))
if bmi > 25:
rec = texts[language]["weight_management"]
recommendations.append(RecommendationItem(
title=rec["title"],
description=rec["description"],
priority=rec["priority"],
target_change=rec["target_change"],
expected_impact=rec["expected_impact"]
))
return recommendations
def _generate_maintenance_recommendations(self, user_dict: Dict, model_type: str, language: str = "zh") -> List[RecommendationItem]:
"""
为低风险用户生成维持性建议
Args:
user_dict: 用户数据字典
model_type: 模型类型 ('sarcoI' 或 'sarcoII')
Returns:
维持性建议列表
"""
recommendations = []
# 基础维持建议
if language == 'en':
recommendations.append(RecommendationItem(
title="Maintain Current Health Status",
description="Your current sarcopenia risk is low, continue maintaining your existing healthy lifestyle",
priority="Medium",
target_change="Maintain current exercise and dietary habits",
expected_impact="Continue reducing sarcopenia risk, maintain muscle health"
))
else:
recommendations.append(RecommendationItem(
title="保持当前健康状态",
description="您当前的肌少症风险较低,建议继续保持现有的健康生活方式",
priority="中",
target_change="维持当前的运动和饮食习惯",
expected_impact="持续降低肌少症风险,保持肌肉健康"
))
# 基于当前活动水平的维持建议
if model_type == 'sarcoI':
current_moderate = user_dict.get('Total_Moderate_Minutes_week', 150)
current_vigorous = user_dict.get('Vigorous_MET_Ratio', 0.2)
if current_moderate >= 150:
if language == 'en':
recommendations.append(RecommendationItem(
title="Continue Moderate-Intensity Exercise",
description=f"You currently engage in {current_moderate:.0f} minutes of moderate exercise weekly, continue maintaining this",
priority="Medium",
target_change="Maintain at least 150 minutes of moderate exercise weekly",
expected_impact="Maintain cardiopulmonary function and muscle endurance"
))
else:
recommendations.append(RecommendationItem(
title="继续中等强度运动",
description=f"您当前每周进行{current_moderate:.0f}分钟中等强度运动,建议继续保持",
priority="中",
target_change="维持每周至少150分钟中等强度运动",
expected_impact="保持心肺功能和肌肉耐力"
))
if current_vigorous >= 0.2:
if language == 'en':
recommendations.append(RecommendationItem(
title="Maintain High-Intensity Exercise",
description="Your high-intensity exercise ratio is good, continue maintaining strength training and high-intensity exercise",
priority="Medium",
target_change="Maintain current high-intensity exercise level",
expected_impact="Maintain muscle mass and strength"
))
else:
recommendations.append(RecommendationItem(
title="保持高强度运动",
description="您的高强度运动比例良好,建议继续保持力量训练和高强度运动",
priority="中",
target_change="维持当前的高强度运动水平",
expected_impact="保持肌肉质量和力量"
))
# 预防性建议
age = user_dict.get('age_years', 45)
if age > 50:
if language == 'en':
recommendations.append(RecommendationItem(
title="Age-Related Prevention",
description="As you age, it's recommended to regularly monitor muscle mass and appropriately increase protein intake",
priority="Medium",
target_change="Conduct muscle mass assessment every 6 months",
expected_impact="Early detection of muscle mass changes, timely intervention adjustment"
))
else:
recommendations.append(RecommendationItem(
title="年龄相关预防",
description="随着年龄增长,建议定期监测肌肉质量,适当增加蛋白质摄入",
priority="中",
target_change="每6个月进行一次肌肉质量评估",
expected_impact="早期发现肌肉质量变化,及时调整干预策略"
))
return recommendations
def _generate_sarcoII_rule_recommendations(self, user_dict: Dict, language: str = "zh") -> List[RecommendationItem]:
"""生成SarcoII基于规则的建议"""
recommendations = []
texts = self._get_recommendation_texts(language)
diversity_index = user_dict.get('Activity_Diversity_Index', 2)
sedentary_minutes = user_dict.get('sedentary_minutes', 480)
sedentary_ratio = user_dict.get('Activity_Sedentary_Ratio', 0.5)
if diversity_index < 3:
rec = texts[language]["increase_diversity"]
recommendations.append(RecommendationItem(
title=rec["title"],
description=rec["description"],
priority=rec["priority"],
target_change=rec["target_change"],
expected_impact=rec["expected_impact"]
))
if sedentary_minutes > 480:
rec = texts[language]["reduce_sedentary"]
recommendations.append(RecommendationItem(
title=rec["title"],
description=rec["description"],
priority=rec["priority"],
target_change=rec["target_change"],
expected_impact=rec["expected_impact"]
))
if sedentary_ratio > 0.6:
if language == 'en':
recommendations.append(RecommendationItem(
title="Reduce Sedentary Activity Ratio",
description="Increase standing and light activity time, reduce sedentary activity proportion",
priority="Medium",
target_change="Reduce sedentary activity ratio to below 50%",
expected_impact="Improve daily activity level, promote muscle health"
))
else:
recommendations.append(RecommendationItem(
title="降低久坐活动比例",
description="增加站立和轻度活动时间,减少久坐活动占比",
priority="中",
target_change="久坐活动比例降至50%以下",
expected_impact="提高日常活动水平,促进肌肉健康"
))
return recommendations
def _generate_fallback_recommendations(self, user_dict: Dict, risk_types: List[str] = None, language: str = "zh") -> List[RecommendationItem]:
"""生成降级建议"""
recommendations = []
texts = self._get_recommendation_texts(language)
# 基础建议
rec = texts[language]["increase_activity"]
recommendations.append(RecommendationItem(
title=rec["title"],
description=rec["description"],
priority=rec["priority"],
target_change=rec["target_change"],
expected_impact=rec["expected_impact"]
))
rec = texts[language]["increase_diversity"]
recommendations.append(RecommendationItem(
title=rec["title"],
description=rec["description"],
priority=rec["priority"],
target_change=rec["target_change"],
expected_impact=rec["expected_impact"]
))
# 基于用户数据的个性化建议
bmi = user_dict.get('body_mass_index', 25)
if bmi > 25:
if language == 'en':
recommendations.append(RecommendationItem(
title="Weight Management",
description="Control weight through balanced diet and exercise, target BMI between 18.5-24.9",
priority="Medium",
target_change=f"Reduce weight to BMI below {bmi-2:.1f}",
expected_impact="Reduce sarcopenia risk, improve overall health"
))
else:
recommendations.append(RecommendationItem(
title="体重管理",
description="通过合理饮食和运动控制体重,目标BMI在18.5-24.9之间",
priority="中",
target_change=f"减重至BMI {bmi-2:.1f}以下",
expected_impact="降低肌少症风险,改善整体健康"
))
sedentary_minutes = user_dict.get('sedentary_minutes', 480)
if sedentary_minutes > 480: # 超过8小时
if language == 'en':
recommendations.append(RecommendationItem(
title="Reduce Sedentary Time",
description="Stand and move for 5-10 minutes every hour, reduce continuous sitting time",
priority="Medium",
target_change="Control daily sedentary time within 8 hours",
expected_impact="Improve blood circulation, maintain muscle vitality"
))
else:
recommendations.append(RecommendationItem(
title="减少久坐时间",
description="每小时起身活动5-10分钟,减少连续久坐时间",
priority="中",
target_change="每日久坐时间控制在8小时以内",
expected_impact="改善血液循环,维持肌肉活力"
))
return recommendations
def _generate_basic_recommendations(self, language: str = "zh") -> List[RecommendationItem]:
"""生成基础建议"""
if language == 'en':
return [
RecommendationItem(
title="Consult Professional Doctor",
description="Seek professional medical guidance and conduct detailed health assessment",
priority="High",
target_change="Obtain personalized medical advice",
expected_impact="Develop scientific health management plan"
),
RecommendationItem(
title="Regular Exercise",
description="Maintain regular physical activity, including aerobic exercise and strength training",
priority="Medium",
target_change="Establish long-term exercise habits",
expected_impact="Maintain and improve physical health"
)
]
else:
return [
RecommendationItem(
title="咨询专业医生",
description="建议寻求专业医疗指导,进行详细的健康评估",
priority="高",
target_change="获得个性化医疗建议",
expected_impact="制定科学的健康管理方案"
),
RecommendationItem(
title="规律运动",
description="保持规律的体力活动,包括有氧运动和力量训练",
priority="中",
target_change="建立长期运动习惯",
expected_impact="维持和改善身体健康"
)
]
def _generate_priority_actions(self, sarcoI_recs: List[RecommendationItem],
sarcoII_recs: List[RecommendationItem]) -> List[str]:
"""生成优先级行动列表"""
actions = []
# 收集高优先级建议
for rec in sarcoI_recs + sarcoII_recs:
if rec.priority == "高":
actions.append(rec.title)
# 如果没有高优先级建议,添加基础建议
if not actions:
actions = ["增加体力活动", "改善生活方式", "定期健康检查"]
return actions[:3] # 最多返回3个优先行动
def _generate_target_metrics(self, user_dict: Dict, sarcoI_recs: List[RecommendationItem],
sarcoII_recs: List[RecommendationItem]) -> Dict[str, Any]:
"""生成目标指标"""
metrics = {}
# 基于用户当前状态和建议生成目标
current_bmi = user_dict.get('body_mass_index', 25)
current_activity = user_dict.get('Total_Moderate_Minutes_week', 150)
if current_bmi > 25:
metrics['目标BMI'] = f"{current_bmi - 2:.1f} - {current_bmi - 1:.1f}"
if current_activity < 150:
metrics['每周运动时间'] = "150分钟以上"
metrics['评估周期'] = "4-6周"
metrics['下次检查'] = "3个月后"
return metrics
# 创建全局建议服务实例
advisory_service = AdvisoryService() |