Wearable_TimeSeries_Health_Monitor / test_wearable_service.py
kokemn's picture
Duplicate from oscarzhang/Wearable_TimeSeries_Health_Monitor
bae9e74
import argparse
from pathlib import Path
from typing import List, Dict, Tuple
import pandas as pd
from wearable_anomaly_detector import load_detector
from feature_calculator import FeatureCalculator
# 预置案例(来自wearable原始数据)
PREDEFINED_CASES: Dict[str, Dict] = {
"ab60_morning_rest": {
"description": "用户ab60,清晨休息到早餐前的连续12个窗口,用于快速验证服务是否可输出结果",
"user_id": "ab60",
"data_points": [
{"timestamp": "2021-03-04T04:45:20.170000", "deviceId": "ab60", "features": {"hr": 91.65860215053765, "hr_resting": 87.84302108870469, "hrv_rmssd": 73.33511196423747, "hrv_sdnn": 72.35486488414405, "hrv_pnn50": 0.3422818791946309, "sdnn": 72.35486488414405, "sdsd": 55.28945952794972, "rmssd": 73.33511196423747, "pnn20": 0.6677852348993288, "pnn50": 0.3422818791946309, "ibi": 671.2685790942928, "lf/hf": 0.6578861348372742, "acc_x_avg": 4.9294712037533515, "acc_y_avg": -3.1057153652814957, "acc_z_avg": 5.8750820100536005, "grv_x_avg": -0.5497846977211797, "grv_y_avg": 0.0042184631367292, "grv_z_avg": 0.1525969041554961, "grv_w_avg": 0.1771413800268097, "gyr_x_avg": -0.9240750428954416, "gyr_y_avg": 1.1994772238605889, "gyr_z_avg": 0.3142024215817695, "light_avg": 440.066889632107, "time_period_primary": "night", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium", "missingness_score": 0.2886646919083789}},
{"timestamp": "2021-03-04T05:15:20.497000", "deviceId": "ab60", "features": {"hr": 84.15604988203573, "hr_resting": 87.84302108870469, "hrv_rmssd": 73.48193641157741, "hrv_sdnn": 74.51390542231427, "hrv_pnn50": 0.4143302180685358, "sdnn": 74.51390542231427, "sdsd": 51.36438677767364, "rmssd": 73.48193641157741, "pnn20": 0.7538940809968847, "pnn50": 0.4143302180685358, "ibi": 729.7248964415015, "lf/hf": 1.1884595045473076, "acc_x_avg": 5.264426130609508, "acc_y_avg": -2.4949751527126582, "acc_z_avg": 5.638523847957136, "grv_x_avg": -0.2725604313462818, "grv_y_avg": -0.0417301761553922, "grv_z_avg": 0.0857810462156731, "grv_w_avg": 0.2177219383791028, "gyr_x_avg": -0.6168720663094444, "gyr_y_avg": 1.3517548573342275, "gyr_z_avg": 0.3159611446751512, "light_avg": 121.7391304347826, "time_period_primary": "night", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium", "missingness_score": 0.1872242091224755}},
{"timestamp": "2021-03-04T05:35:21.317000", "deviceId": "ab60", "features": {"hr": 82.22642857142857, "hr_resting": 87.84302108870469, "hrv_rmssd": 73.0761458919254, "hrv_sdnn": 81.71063624330793, "hrv_pnn50": 0.45703125, "sdnn": 81.71063624330793, "sdsd": 47.19876056820636, "rmssd": 73.0761458919254, "pnn20": 0.7578125, "pnn50": 0.45703125, "ibi": 799.9492801995793, "lf/hf": 1.7751635086235489, "acc_x_avg": 4.37106831212324, "acc_y_avg": -0.3996270033489605, "acc_z_avg": 6.891127246483591, "grv_x_avg": -0.6645361294433281, "grv_y_avg": -0.3851286666666666, "grv_z_avg": 0.0854148665325285, "grv_w_avg": 0.189048981220657, "gyr_x_avg": -0.1036394095174265, "gyr_y_avg": 1.4141689068364616, "gyr_z_avg": 0.4626943733243945, "light_avg": 603.2266666666667, "time_period_primary": "night", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium", "missingness_score": 0.335979916085374}},
{"timestamp": "2021-03-04T05:55:21.428000", "deviceId": "ab60", "features": {"hr": 86.76466621712744, "hr_resting": 87.84302108870469, "hrv_rmssd": 74.59311564233765, "hrv_sdnn": 87.31034842159234, "hrv_pnn50": 0.391304347826087, "sdnn": 87.31034842159234, "sdsd": 52.14310859847846, "rmssd": 74.59311564233765, "pnn20": 0.7418478260869565, "pnn50": 0.391304347826087, "ibi": 709.0165592504526, "lf/hf": 2.4824085488532703, "acc_x_avg": 6.8974065639651725, "acc_y_avg": -0.6532005197588735, "acc_z_avg": 4.533895625586072, "grv_x_avg": -0.606806606831882, "grv_y_avg": -0.1301535706630945, "grv_z_avg": 0.0961370341594106, "grv_w_avg": 0.4334408365706628, "gyr_x_avg": -0.9999732337575332, "gyr_y_avg": 1.1268921553918274, "gyr_z_avg": 0.0987407849966511, "light_avg": 428.3612040133779, "time_period_primary": "night", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium", "missingness_score": 0.1217623103705546}},
{"timestamp": "2021-03-04T06:00:21.434000", "deviceId": "ab60", "features": {"hr": 86.11454484380249, "hr_resting": 87.84302108870469, "hrv_rmssd": 80.21713778627182, "hrv_sdnn": 72.4260069463789, "hrv_pnn50": 0.4117647058823529, "sdnn": 72.4260069463789, "sdsd": 57.42532753452254, "rmssd": 80.21713778627182, "pnn20": 0.7536764705882353, "pnn50": 0.4117647058823529, "ibi": 711.989004966488, "lf/hf": 0.5778707099781514, "acc_x_avg": 2.6462390616208977, "acc_y_avg": -2.0968226182183503, "acc_z_avg": 7.492496608841257, "grv_x_avg": -0.3523352029470862, "grv_y_avg": -0.3171756162089749, "grv_z_avg": 0.0852522665773609, "grv_w_avg": 0.0408305612860013, "gyr_x_avg": -0.7581300395442356, "gyr_y_avg": 1.7399128639410202, "gyr_z_avg": 0.5810656782841818, "light_avg": 719.866220735786, "time_period_primary": "morning", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium", "missingness_score": 0.3055760776711148}},
{"timestamp": "2021-03-04T06:05:21.530000", "deviceId": "ab60", "features": {"hr": 85.55876427132304, "hr_resting": 87.84302108870469, "hrv_rmssd": 61.68777738949315, "hrv_sdnn": 61.78286083667294, "hrv_pnn50": 0.3786127167630058, "sdnn": 61.78286083667294, "sdsd": 40.15746948668274, "rmssd": 61.68777738949315, "pnn20": 0.7283236994219653, "pnn50": 0.3786127167630058, "ibi": 715.325568241176, "lf/hf": 0.9103296457711838, "acc_x_avg": 3.0594024239785633, "acc_y_avg": -2.580148740120562, "acc_z_avg": 7.2776362170127245, "grv_x_avg": -0.2312993507712944, "grv_y_avg": -0.1165045117370892, "grv_z_avg": 0.1689689483568074, "grv_w_avg": 0.1020506572769953, "gyr_x_avg": -0.2617024135388738, "gyr_y_avg": 1.1428016065683635, "gyr_z_avg": 0.0963672908847178, "light_avg": 665.6321070234113, "time_period_primary": "morning", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium", "missingness_score": 0.1561355447930486}},
{"timestamp": "2021-03-04T06:15:21.623000", "deviceId": "ab60", "features": {"hr": 82.62592343854936, "hr_resting": 87.84302108870469, "hrv_rmssd": 81.00579057638534, "hrv_sdnn": 73.40584581276266, "hrv_pnn50": 0.4357366771159874, "sdnn": 73.40584581276266, "sdsd": 57.10852463732151, "rmssd": 81.00579057638534, "pnn20": 0.7366771159874608, "pnn50": 0.4357366771159874, "ibi": 746.8545044048512, "lf/hf": 2.2582676863270708, "acc_x_avg": 0.1661593281982583, "acc_y_avg": -1.6491836677829892, "acc_z_avg": 9.736414508372398, "grv_x_avg": -0.6639661513730741, "grv_y_avg": 0.2112439484259877, "grv_z_avg": 0.008919145344943, "grv_w_avg": 0.0059900468854654, "gyr_x_avg": -0.3158673777628935, "gyr_y_avg": 0.9963161399866036, "gyr_z_avg": 0.4727930301406551, "light_avg": 802.180602006689, "time_period_primary": "morning", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium", "missingness_score": 0.174593188653174}},
{"timestamp": "2021-03-04T06:40:21.894000", "deviceId": "ab60", "features": {"hr": 83.86055107526882, "hr_resting": 87.84302108870469, "hrv_rmssd": 79.079102105631, "hrv_sdnn": 71.271849209199, "hrv_pnn50": 0.4221556886227545, "sdnn": 71.271849209199, "sdsd": 54.69084849089656, "rmssd": 79.079102105631, "pnn20": 0.7724550898203593, "pnn50": 0.4221556886227545, "ibi": 732.4375213133641, "lf/hf": 0.6677155405784594, "acc_x_avg": 1.7091933630274596, "acc_y_avg": -2.3761690361687893, "acc_z_avg": 8.209631811788354, "grv_x_avg": -0.3922270288010715, "grv_y_avg": 0.2412905398526454, "grv_z_avg": -0.0233138225050234, "grv_w_avg": 0.1066616838580042, "gyr_x_avg": 0.3474681640991295, "gyr_y_avg": 1.1165304762223718, "gyr_z_avg": 0.186041525117213, "light_avg": 729.1103678929766, "time_period_primary": "morning", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium", "missingness_score": 0.162896032760479}},
{"timestamp": "2021-03-04T06:45:21.969000", "deviceId": "ab60", "features": {"hr": 85.06473364801079, "hr_resting": 87.84302108870469, "hrv_rmssd": 73.21565395106201, "hrv_sdnn": 69.24046192941114, "hrv_pnn50": 0.4137931034482758, "sdnn": 69.24046192941114, "sdsd": 49.87687480659143, "rmssd": 73.21565395106201, "pnn20": 0.768025078369906, "pnn50": 0.4137931034482758, "ibi": 719.8668191606536, "lf/hf": 1.6620124103207512, "acc_x_avg": 2.2839716548257365, "acc_y_avg": -2.790153060991958, "acc_z_avg": 8.004115201072393, "grv_x_avg": -0.4554428230563004, "grv_y_avg": -0.1927247533512062, "grv_z_avg": 0.0215668719839142, "grv_w_avg": 0.1034467037533509, "gyr_x_avg": -0.0257573813672921, "gyr_y_avg": 1.0048659463806948, "gyr_z_avg": 0.3241018806970504, "light_avg": 755.3177257525084, "time_period_primary": "morning", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium", "missingness_score": 0.1959064930123424}},
{"timestamp": "2021-03-04T06:50:21.981000", "deviceId": "ab60", "features": {"hr": 84.85642062689585, "hr_resting": 87.84302108870469, "hrv_rmssd": 68.0148861140481, "hrv_sdnn": 76.76458770787994, "hrv_pnn50": 0.3815384615384615, "sdnn": 76.76458770787994, "sdsd": 46.69614203120664, "rmssd": 68.0148861140481, "pnn20": 0.6707692307692308, "pnn50": 0.3815384615384615, "ibi": 729.6592731642268, "lf/hf": 0.9127720565801908, "acc_x_avg": 1.71269043335566, "acc_y_avg": -1.0698834099129273, "acc_z_avg": 9.200909472873422, "grv_x_avg": -0.375676233087742, "grv_y_avg": 0.1822926999330206, "grv_z_avg": 0.0157954038847957, "grv_w_avg": 0.0460910823844608, "gyr_x_avg": -0.1220897675820484, "gyr_y_avg": 0.6891694554588073, "gyr_z_avg": 0.4564768801071655, "light_avg": 847.0335570469799, "time_period_primary": "morning", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium", "missingness_score": 0.1845048437257962}},
{"timestamp": "2021-03-04T06:55:21.985000", "deviceId": "ab60", "features": {"hr": 80.75881760161236, "hr_resting": 87.84302108870469, "hrv_rmssd": 66.04701786885973, "hrv_sdnn": 76.17603294638347, "hrv_pnn50": 0.3684210526315789, "sdnn": 76.17603294638347, "sdsd": 45.0541425499126, "rmssd": 66.04701786885973, "pnn20": 0.7151702786377709, "pnn50": 0.3684210526315789, "ibi": 757.0256501417455, "lf/hf": 1.269681575994282, "acc_x_avg": 3.739257345612857, "acc_y_avg": -2.4820750622906904, "acc_z_avg": 7.388303849966525, "grv_x_avg": -0.5472615157401197, "grv_y_avg": 0.3134806992632281, "grv_z_avg": 0.0378557434695244, "grv_w_avg": 0.2093843141326185, "gyr_x_avg": -0.5253990328638486, "gyr_y_avg": 1.6931924748490956, "gyr_z_avg": 0.665633814889338, "light_avg": 703.4496644295302, "time_period_primary": "morning", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium", "missingness_score": 0.1579866815850661}},
{"timestamp": "2021-03-04T07:00:22.020000", "deviceId": "ab60", "features": {"hr": 83.36467427803895, "hr_resting": 87.84302108870469, "hrv_rmssd": 85.01833953484277, "hrv_sdnn": 76.7068088047925, "hrv_pnn50": 0.4434782608695652, "sdnn": 76.7068088047925, "sdsd": 62.38011995356233, "rmssd": 85.01833953484277, "pnn20": 0.7536231884057971, "pnn50": 0.4434782608695652, "ibi": 724.6919175240898, "lf/hf": 1.3219549787885163, "acc_x_avg": 0.4397476164658638, "acc_y_avg": -1.638112914323962, "acc_z_avg": 9.6665646124498, "grv_x_avg": -0.3218441425702814, "grv_y_avg": 0.1350831372155288, "grv_z_avg": 0.0047377771084337, "grv_w_avg": 0.0104064123159303, "gyr_x_avg": -0.2149196720214184, "gyr_y_avg": 0.9163253052208852, "gyr_z_avg": 0.5661378821954464, "light_avg": 861.3110367892976, "time_period_primary": "morning", "time_period_secondary": "breakfast", "is_weekend": 0, "data_quality": "medium", "missingness_score": 0.1291275275920405}}
]
},
"nd56_low_activity_sparse": {
"description": "nd56:多数传感器字段缺失,只提供心率/时间段等基础信息,验证模型默认填充能力",
"user_id": "nd56",
"data_points": [
{"timestamp": "2021-03-04T03:40:55.745000", "deviceId": "nd56", "features": {"hr": 73.3681592039801, "steps": None, "time_period_primary": "night", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium"}},
{"timestamp": "2021-03-04T03:55:55.963000", "deviceId": "nd56", "features": {"hr": 70.58150365934797, "steps": None, "time_period_primary": "night", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium"}},
{"timestamp": "2021-03-04T04:30:56.879000", "deviceId": "nd56", "features": {"hr": 79.00866089273818, "steps": None, "time_period_primary": "night", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium"}},
{"timestamp": "2021-03-04T05:11:22.339000", "deviceId": "nd56", "features": {"hr": 76.88147410358566, "steps": None, "time_period_primary": "night", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium"}},
{"timestamp": "2021-03-04T06:21:22.941000", "deviceId": "nd56", "features": {"hr": 79.52276503821868, "steps": None, "time_period_primary": "morning", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium"}},
{"timestamp": "2021-03-04T06:36:22.983000", "deviceId": "nd56", "features": {"hr": 75.333, "steps": None, "time_period_primary": "morning", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium"}},
{"timestamp": "2021-03-04T06:41:23.077000", "deviceId": "nd56", "features": {"hr": 75.7566401062417, "steps": None, "time_period_primary": "morning", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium"}},
{"timestamp": "2021-03-04T06:56:23.275000", "deviceId": "nd56", "features": {"hr": 74.49435590969456, "steps": None, "time_period_primary": "morning", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium"}},
{"timestamp": "2021-03-04T07:26:23.418000", "deviceId": "nd56", "features": {"hr": 73.90371845949535, "steps": None, "time_period_primary": "morning", "time_period_secondary": "breakfast", "is_weekend": 0, "data_quality": "medium"}},
{"timestamp": "2021-03-04T07:36:23.463000", "deviceId": "nd56", "features": {"hr": 72.7252911813644, "steps": None, "time_period_primary": "morning", "time_period_secondary": "breakfast", "is_weekend": 0, "data_quality": "medium"}},
{"timestamp": "2021-03-04T07:41:23.508000", "deviceId": "nd56", "features": {"hr": 70.14043824701196, "steps": None, "time_period_primary": "morning", "time_period_secondary": "breakfast", "is_weekend": 0, "data_quality": "medium"}},
{"timestamp": "2021-03-04T07:46:23.547000", "deviceId": "nd56", "features": {"hr": 71.33565737051792, "steps": None, "time_period_primary": "morning", "time_period_secondary": "breakfast", "is_weekend": 0, "data_quality": "medium"}}
]
},
"anon_commuter_minimal": {
"description": "匿名通勤用户:仅提供心率/HRV/时间段等极简指标,deviceId缺失,验证服务可批量处理不同客户",
"user_id": "anon_commuter",
"data_points": [
{"timestamp": "2021-03-09T00:52:04.300000", "deviceId": None, "features": {"hr": 88.77359119706568, "hrv_rmssd": 68.78080551188198, "hrv_sdnn": 68.55954732717733, "steps": 0.0, "time_period_primary": "night", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium", "missingness_score": 0.2340064304816851}},
{"timestamp": "2021-03-09T00:57:04.328000", "deviceId": None, "features": {"hr": 80.68233333333333, "hrv_rmssd": 59.2934623277283, "hrv_sdnn": 73.79953723523498, "steps": 0.0, "time_period_primary": "night", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium", "missingness_score": 0.1442517250480751}},
{"timestamp": "2021-03-09T01:02:04.404000", "deviceId": None, "features": {"hr": 75.80266666666667, "hrv_rmssd": 45.96602248249421, "hrv_sdnn": 86.08949225862196, "steps": None, "time_period_primary": "night", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium", "missingness_score": 0.0475269119819883}},
{"timestamp": "2021-03-09T01:07:04.432000", "deviceId": None, "features": {"hr": 74.08533333333334, "hrv_rmssd": 35.124819470890934, "hrv_sdnn": 76.28600536828999, "steps": None, "time_period_primary": "night", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium", "missingness_score": 0.0362464905334389}},
{"timestamp": "2021-03-09T01:12:04.441000", "deviceId": None, "features": {"hr": 76.9121411276375, "hrv_rmssd": 72.02894057601205, "hrv_sdnn": 87.11453810833142, "steps": 0.0, "time_period_primary": "night", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium", "missingness_score": 0.1366772654292948}},
{"timestamp": "2021-03-09T01:17:04.485000", "deviceId": None, "features": {"hr": 68.27024325224924, "hrv_rmssd": 36.20840500569033, "hrv_sdnn": 61.29313423988413, "steps": None, "time_period_primary": "night", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium", "missingness_score": -0.0048301680504103}},
{"timestamp": "2021-03-09T01:22:04.496000", "deviceId": None, "features": {"hr": 71.42038640906063, "hrv_rmssd": 42.73973712175516, "hrv_sdnn": 100.9222381995624, "steps": 0.0, "time_period_primary": "night", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium", "missingness_score": 0.0282886513311317}},
{"timestamp": "2021-03-09T01:27:04.517000", "deviceId": None, "features": {"hr": 78.02364302364302, "hrv_rmssd": 68.8236455702779, "hrv_sdnn": 91.82364754816273, "steps": 0.0, "time_period_primary": "night", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium", "missingness_score": 0.133595953991592}},
{"timestamp": "2021-03-09T01:32:04.589000", "deviceId": None, "features": {"hr": 74.2271818787475, "hrv_rmssd": 47.71422420363224, "hrv_sdnn": 80.68140559056071, "steps": 0.0, "time_period_primary": "night", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium", "missingness_score": 0.0569492438181573}},
{"timestamp": "2021-03-09T01:37:04.619000", "deviceId": None, "features": {"hr": 66.9780146568954, "hrv_rmssd": 35.74818419244297, "hrv_sdnn": 50.71532677029013, "steps": None, "time_period_primary": "night", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium", "missingness_score": 0.0026578073089701}},
{"timestamp": "2021-03-09T01:47:04.721000", "deviceId": None, "features": {"hr": 74.31312458361093, "hrv_rmssd": 57.44451616196892, "hrv_sdnn": 98.6011860346101, "steps": 0.0, "time_period_primary": "night", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium", "missingness_score": 0.1253227425948505}},
{"timestamp": "2021-03-09T01:52:04.735000", "deviceId": None, "features": {"hr": 71.42538307794804, "hrv_rmssd": 50.01054170733226, "hrv_sdnn": 73.22227779949023, "steps": 0.0, "time_period_primary": "night", "time_period_secondary": "rest_night", "is_weekend": 0, "data_quality": "medium", "missingness_score": 0.0255565038546025}}
]
}
}
def load_raw_data_window(
stage1_file: Path,
feature_names: List[str],
window_size: int = 12,
) -> Tuple[List[Dict], str]:
stage1_path = Path(stage1_file)
if not stage1_path.exists():
raise FileNotFoundError(f"找不到原始数据文件: {stage1_file}")
base_cols = pd.read_csv(stage1_path, nrows=0).columns.tolist()
usecols = ['deviceId', 'ts_start'] + [feat for feat in feature_names if feat in base_cols]
usecols = list(dict.fromkeys(usecols))
buffers: Dict[str, List[Dict]] = {}
reader = pd.read_csv(
stage1_path,
usecols=usecols,
parse_dates=['ts_start'],
chunksize=10000,
)
for chunk in reader:
chunk = chunk.sort_values(['deviceId', 'ts_start'])
for device_id, group in chunk.groupby('deviceId'):
records = group.to_dict('records')
if device_id not in buffers:
buffers[device_id] = []
buffers[device_id].extend(records)
buffers[device_id] = sorted(buffers[device_id], key=lambda r: r['ts_start'])
if len(buffers[device_id]) >= window_size:
segment = buffers[device_id][:window_size]
data_points: List[Dict] = []
for row in segment:
feature_payload = {}
for feat in feature_names:
if feat in row and pd.notna(row[feat]):
feature_payload[feat] = row[feat]
data_points.append({
'timestamp': row['ts_start'].to_pydatetime(),
'deviceId': str(device_id),
'features': feature_payload,
})
return data_points, str(device_id)
if len(buffers[device_id]) > window_size * 4:
buffers[device_id] = buffers[device_id][-window_size*2:]
raise ValueError("没有找到满足窗口长度的用户数据(请检查原始数据是否存在足够连续的记录)")
def load_predefined_case(case_name: str) -> Tuple[List[Dict], str, str]:
if case_name not in PREDEFINED_CASES:
raise ValueError(f"未找到预置案例: {case_name},可选: {list(PREDEFINED_CASES.keys())}")
case = PREDEFINED_CASES[case_name]
data_points = []
for point in case["data_points"]:
converted = dict(point)
converted["timestamp"] = pd.to_datetime(converted["timestamp"])
data_points.append(converted)
return data_points, case["user_id"], case["description"]
def main():
parser = argparse.ArgumentParser(description="使用原始 wearables 数据测试新的推理服务")
parser.add_argument("--model-dir", type=str, default="checkpoints/phase2/exp_factor_balanced")
parser.add_argument("--stage1-file", type=str, default="processed_data/stage1/wearable_processed.csv")
parser.add_argument("--window-size", type=int, default=12)
parser.add_argument("--case", type=str, nargs="+", default=["ab60_morning_rest"], help="使用预置案例名(可多选,all=全部)")
parser.add_argument("--from-raw", action="store_true", help="从stage1原始文件抽样,而不是预置案例")
args = parser.parse_args()
base_dir = Path(__file__).parent
stage1_file = base_dir / args.stage1_file
feature_calculator = FeatureCalculator()
feature_names = feature_calculator.get_enabled_feature_names()
print("\n🚀 加载异常检测器...")
detector = load_detector(base_dir / args.model_dir)
def run_prediction(label: str, data_points: List[Dict], user_id_hint: str):
print(f"\n🧪 执行预测: {label}")
print(f" - 用户: {user_id_hint}")
print(f" - 窗口长度: {len(data_points)} (每个点5分钟)")
result = detector.predict(data_points, return_score=True, return_details=True)
print(" ▸ 是否异常:", "是" if result['is_anomaly'] else "否")
print(f" ▸ 异常分数: {result.get('anomaly_score', 0.0):.4f} (阈值 {result.get('threshold', detector.threshold):.4f})")
if result.get('details'):
print(" ▸ 详情:", result['details'])
if args.from_raw:
print("📥 正在从原始数据抽样窗口...")
if not stage1_file.exists():
raise FileNotFoundError(f"找不到原始数据文件: {stage1_file}")
data_points, device_id = load_raw_data_window(stage1_file, feature_names, window_size=args.window_size)
run_prediction("raw_sample", data_points, device_id)
else:
case_names = args.case
if "all" in case_names:
case_names = list(PREDEFINED_CASES.keys())
for case_name in case_names:
if case_name not in PREDEFINED_CASES:
print(f"⚠️ 跳过未知案例: {case_name}")
continue
data_points, user_id, desc = load_predefined_case(case_name)
print(f"\n🧾 使用预置案例: {case_name}")
print(f" - 描述: {desc}")
run_prediction(case_name, data_points, user_id)
if __name__ == "__main__":
main()