From 781c247952e0aee263f7b845fdafcf06e55e08b4 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Fri, 3 Apr 2026 00:57:19 +0800 Subject: [PATCH] Add ops dashboards for training data and model coverage --- artifacts/models/lgbm_daily_high.txt | 1837 ++++++++++------- artifacts/models/lgbm_daily_high_schema.json | 26 +- .../probability_calibration/default.json | 68 +- .../evaluation_report.json | 26 +- .../rollout_report.json | 54 +- .../shadow_report.json | 42 +- .../training_samples.json | 382 +++- frontend/components/ops/OpsDashboard.tsx | 431 ++++ ...ackfill_recent_daily_actuals_from_metar.py | 4 +- scripts/fit_probability_calibration.py | 87 +- scripts/restore_training_feature_history.py | 114 + scripts/restore_training_truth_history.py | 140 ++ src/analysis/deb_algorithm.py | 226 +- src/analysis/probability_snapshot_archive.py | 32 + src/data_collection/wunderground_sources.py | 91 + src/database/runtime_state.py | 361 ++++ src/models/lgbm_features.py | 90 +- tests/test_lgbm_features.py | 34 +- tests/test_probability_snapshot_archive.py | 74 +- tests/test_probability_training_dataset.py | 16 + tests/test_runtime_state_storage.py | 64 + tests/test_web_observability.py | 7 + tests/test_wunderground_history.py | 44 + web/core.py | 291 +++ 24 files changed, 3634 insertions(+), 907 deletions(-) create mode 100644 scripts/restore_training_feature_history.py create mode 100644 scripts/restore_training_truth_history.py create mode 100644 tests/test_wunderground_history.py diff --git a/artifacts/models/lgbm_daily_high.txt b/artifacts/models/lgbm_daily_high.txt index 2fe1b654..7f687ca9 100644 --- a/artifacts/models/lgbm_daily_high.txt +++ b/artifacts/models/lgbm_daily_high.txt @@ -6,1031 +6,1430 @@ label_index=0 max_feature_idx=26 objective=regression feature_names=actual_high_lag_1 actual_high_lag_2 actual_high_lag_3 actual_high_lag_7 actual_high_mean_7 actual_high_mean_14 actual_high_trend_3 open_meteo ecmwf gfs gem jma icon mgm nws deb_prediction model_median model_spread current_temp max_so_far humidity wind_speed_kt visibility_mi local_hour month weekday peak_status_code -feature_infos=[-6:34] [1:27.799999237060547] none none [-6:34] [-6:34] none [5.5:34.700000762939453] [7.5:33.799999237060547] [5.5999999046325684:37.299999237060547] [7.9000000953674316:33.900001525878906] [4:35.099998474121094] [4.5:34.700000762939453] none none [6.4000000953674316:34.5] [6.3000001907348633:34.700000762939453] [0.60000002384185791:4.5] none none none none none none none [0:3] none -tree_sizes=622 591 592 556 591 669 590 593 596 575 671 596 593 597 596 598 580 591 598 597 578 680 600 582 670 596 583 618 594 599 599 596 559 664 583 579 507 578 603 580 606 598 623 693 694 604 696 603 771 694 604 689 677 738 +feature_infos=[-4.4000000953674316:34.400001525878906] [-4.4000000953674316:34.400001525878906] [-2.5:34.400001525878906] [0.40000000596046448:35.799999237060547] [2.8714284896850586:34.128570556640625] [2.9642856121063232:33.392856597900391] [-15.100000381469727:11] [-0.20000000298023224:74.400001525878906] [-1.6000000238418579:74.900001525878906] [-0.20000000298023224:74.400001525878906] [-1.1000000238418579:74.400001525878906] [-1.5:76.400001525878906] [-1.7999999523162842:74.599998474121094] none [37:88] [-0.60000002384185791:75.199996948242188] [-1.1000000238418579:74.400001525878906] [0.60000002384185791:18.600000381469727] none [10:28.899999618530273] none none none none none [0:6] [0:2] +tree_sizes=895 1007 1028 967 1084 1049 871 1120 1105 930 1029 1185 1046 1161 1046 1082 1136 935 1029 879 949 1068 1068 1073 973 1053 972 1137 1051 1135 864 1072 1135 976 1059 1044 1236 1140 980 965 1058 1059 965 1061 1042 1037 1065 1056 1024 1143 1128 1013 1074 1016 1068 1147 987 934 1013 1073 989 1045 1115 1096 1038 943 1069 970 1139 1095 965 1052 988 1151 1221 Tree=0 -num_leaves=6 +num_leaves=9 num_cat=0 -split_feature=0 10 25 8 1 -split_gain=1463.02 75 73.5 41.8613 12.5 -threshold=19.000000000000004 15.949999809265138 2.5000000000000004 25.250000000000004 1.0000000180025095e-35 -decision_type=2 2 2 2 10 -left_child=1 2 -1 -2 -4 -right_child=3 -3 4 -5 -6 -leaf_value=16.104051653372828 16.705301631319109 16.17905165262777 15.779051656353062 16.934051657545155 15.904051650392597 -leaf_weight=4.0000000000000044 4 3.9999999999999991 4 4 4 -leaf_count=4 4 4 4 4 4 -internal_value=16.2676 15.9916 15.9291 16.8197 15.8416 -internal_weight=24 16 12 8 8 -internal_count=24 16 12 8 8 +split_feature=7 14 8 17 2 25 3 10 +split_gain=1660.02 227.601 192.575 165.486 98.2909 39.52 6.48 4.54422 +threshold=20.10000038146973 56.500000000000007 16.099999427795414 4.2500000000000009 23.649999618530277 2.5000000000000004 8.7500000000000018 16.19999980926514 +decision_type=2 10 2 2 2 2 2 2 +left_child=2 3 5 4 -2 -1 -7 -4 +right_child=1 -3 7 -5 -6 6 -8 -9 +leaf_value=19.148928607077828 19.581500047842663 19.441250036160152 19.315000025431317 20.071666705608369 19.881666704018912 18.941250042120618 19.031250039736431 19.386500021802885 +leaf_weight=7.0000000000000009 5.0000000000000053 3.9999999999999991 3.9999999999999982 5.9999999999999991 6 4 4 5 +leaf_count=7 5 4 4 6 6 4 4 5 +internal_value=19.4559 19.7806 19.1719 19.8604 19.7452 19.0622 18.9863 19.3547 +internal_weight=45 21 24 17 11 15 8 9 +internal_count=45 21 24 17 11 15 8 9 is_linear=0 shrinkage=1 Tree=1 -num_leaves=5 +num_leaves=10 num_cat=0 -split_feature=0 8 0 25 -split_gain=1127.66 153.56 57.0182 76.0778 -threshold=19.000000000000004 24.450000762939457 14.500000000000002 2.5000000000000004 -decision_type=2 2 2 2 -left_child=2 -2 3 -1 -right_child=1 -3 -4 -5 -leaf_value=-0.18114008009433719 0.1772349134087563 0.59287240982055656 -0.15145258456468583 -0.45448828935623165 -leaf_weight=4.0000000000000053 3.9999999999999982 5 4.9999999999999991 7 -leaf_count=4 4 5 5 7 -internal_value=-0.0395976 0.408145 -0.291453 -0.355089 -internal_weight=25 9 16 11 -internal_count=25 9 16 11 +split_feature=7 0 8 10 17 2 17 7 1 +split_gain=1659.21 218.294 213.267 81.4644 78.4537 66.244 46.4881 9.56367 5.05143 +threshold=20.10000038146973 31.399999618530277 16.099999427795414 8.7000002861022967 3.8999999761581425 21.649999618530277 1.4499999880790713 14.150000095367433 16.250000000000004 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=2 4 3 -1 5 -2 -5 -8 -4 +right_child=1 -3 8 6 -6 -7 7 -9 -10 +leaf_value=-0.60475444793701072 -0.011071863025426858 0.64966664314270028 -0.13289376646280288 -0.48337945938110355 0.37400348186492921 0.2619199991226196 -0.32928347587585444 -0.21994643211364742 -0.053431242960505179 +leaf_weight=4.0000000000000062 4.0000000000000018 3.9999999999999991 4 3.9999999999999991 5.9999999999999991 5 4 4 4 +leaf_count=4 4 4 4 4 6 5 4 4 4 +internal_value=-0.027599 0.321474 -0.303948 -0.409341 0.233956 0.14059 -0.344203 -0.274615 -0.0931625 +internal_weight=43 19 24 16 15 9 12 8 8 +internal_count=43 19 24 16 15 9 12 8 8 is_linear=0 shrinkage=0.05 Tree=2 -num_leaves=5 +num_leaves=10 num_cat=0 -split_feature=11 8 4 8 -split_gain=1081.38 132.977 65.1979 28.2097 -threshold=16.500000000000004 25.250000000000004 15.750000000000002 14.09999990463257 -decision_type=2 2 2 2 -left_child=2 -2 3 -1 -right_child=1 -3 -4 -5 -leaf_value=-0.41354513367017093 0.21087317466735844 0.5976537895202636 -0.10612917358521373 -0.24212438017129895 -leaf_weight=6.0000000000000018 3.9999999999999982 5 3.9999999999999991 4 -leaf_count=6 4 5 4 4 -internal_value=-0.00184884 0.425751 -0.276735 -0.344977 -internal_weight=23 9 14 10 -internal_count=23 9 14 10 +split_feature=7 14 8 7 17 5 25 8 12 +split_gain=1916.05 245.192 237.559 163.298 79.2897 76.2971 20.2967 16.4425 4.05023 +threshold=20.10000038146973 56.500000000000007 16.099999427795414 28.599999427795414 3.1499999761581425 9.3392858505249041 2.5000000000000004 24.750000000000004 17.149999618530277 +decision_type=2 10 2 2 2 2 2 2 2 +left_child=2 3 5 7 -5 -1 -7 -2 -4 +right_child=1 -3 8 4 -6 6 -8 -9 -10 +leaf_value=-0.59283512830734153 0.19743309140205359 0.0070880746841430675 -0.12313608974218371 0.38919467329978952 0.68785985946655259 -0.28502313409532826 -0.40542930534907751 0.32566149234771724 -0.055634195730090132 +leaf_weight=4.0000000000000062 5.0000000000000053 4.9999999999999991 3.9999999999999982 3.9999999999999982 5 6.9999999999999982 7 5 5 +leaf_count=4 5 5 4 4 5 7 7 5 5 +internal_value=-0.00643591 0.318625 -0.295379 0.400608 0.55512 -0.40025 -0.345226 0.261547 -0.085635 +internal_weight=51 24 27 19 9 18 14 10 9 +internal_count=51 24 27 19 9 18 14 10 9 is_linear=0 shrinkage=0.05 Tree=3 -num_leaves=5 +num_leaves=9 num_cat=0 -split_feature=0 25 10 9 -split_gain=1056.34 71.4123 103.567 43.8079 -threshold=19.000000000000004 2.5000000000000004 15.949999809265138 25.150000572204593 -decision_type=2 2 2 2 -left_child=1 -1 -3 -2 -right_child=3 2 -4 -5 -leaf_value=-0.14301777064800258 0.30870695859193797 -0.53039961814880365 -0.18906025271862742 0.54271523952484124 -leaf_weight=5.0000000000000009 4 5 4 4 -leaf_count=5 4 5 4 4 -internal_value=-0.03262 -0.294523 -0.378693 0.425711 -internal_weight=22 14 9 8 -internal_count=22 14 9 8 +split_feature=7 14 12 11 9 17 12 7 +split_gain=1733.95 247.163 298.65 144.447 68.7075 34.0132 24.3917 10.3114 +threshold=20.10000038146973 56.500000000000007 30.04999923706055 15.750000000000002 10.600000381469728 1.4499999880790713 23.60000038146973 14.150000095367433 +decision_type=2 10 2 2 2 2 2 2 +left_child=3 2 6 4 -1 -6 -2 -7 +right_child=1 -3 -4 -5 5 7 -8 -9 +leaf_value=-0.49268550475438383 0.14314907789230324 0.0067336666584014897 0.6594188077109201 -0.050687082931399341 -0.40944919586181644 -0.29405292272567757 0.29792678356170649 -0.18634806394577025 +leaf_weight=6.0000000000000062 4.0000000000000053 4.9999999999999991 6.9999999999999991 4.9999999999999991 3.9999999999999991 3.9999999999999982 7 5 +leaf_count=6 4 5 7 5 4 4 7 5 +internal_value=0.00749759 0.317725 0.404112 -0.289804 -0.35273 -0.288135 0.241644 -0.234217 +internal_weight=47 23 18 24 19 13 11 9 +internal_count=47 23 18 24 19 13 11 9 is_linear=0 shrinkage=0.05 Tree=4 -num_leaves=5 +num_leaves=11 num_cat=0 -split_feature=0 8 0 25 -split_gain=1056.87 158.111 46.2432 62.5271 -threshold=19.000000000000004 24.450000762939457 14.500000000000002 2.5000000000000004 -decision_type=2 2 2 2 -left_child=2 -2 3 -1 -right_child=1 -3 -4 -5 -leaf_value=-0.14854045137763003 0.1570641821622849 0.52520016261509483 -0.12284998334944248 -0.39635192666734964 -leaf_weight=4.0000000000000053 4.9999999999999982 7 4.9999999999999991 7 -leaf_count=4 5 7 5 7 -internal_value=0.0171017 0.37181 -0.24893 -0.306239 -internal_weight=28 12 16 11 -internal_count=28 12 16 11 +split_feature=7 14 8 7 17 17 3 8 8 10 +split_gain=1383.2 171.542 167.603 127.297 65.2496 52.7069 55.4447 20.3157 18.9273 3.69813 +threshold=20.10000038146973 56.500000000000007 16.099999427795414 28.599999427795414 4.2500000000000009 1.6999999880790713 12.750000000000002 12.150000095367433 24.750000000000004 16.19999980926514 +decision_type=2 10 2 2 2 2 2 2 2 2 +left_child=2 3 5 8 -5 6 -1 -7 -2 -4 +right_child=1 -3 9 4 -6 7 -8 -9 -10 -11 +leaf_value=-0.56991612911224276 0.16804212331771834 0.0063969886302948009 -0.11389921978116038 0.34553068876266474 0.63112121224403372 -0.3458895444869996 -0.30665597915649406 -0.19471013069152832 0.29363174835840855 -0.049398089498281478 +leaf_weight=4.0000000000000053 6.0000000000000053 4.9999999999999991 3.9999999999999982 4 4 3.9999999999999982 4 5 6 5 +leaf_count=4 6 5 4 4 4 4 4 5 6 5 +internal_value=0.00279708 0.268345 -0.252538 0.333833 0.488326 -0.344906 -0.438286 -0.261901 0.230837 -0.0780653 +internal_weight=51 25 26 20 8 17 8 9 12 9 +internal_count=51 25 26 20 8 17 8 9 12 9 is_linear=0 shrinkage=0.05 Tree=5 -num_leaves=6 +num_leaves=10 num_cat=0 -split_feature=0 8 0 25 4 -split_gain=948.797 143.384 49.5846 63.7686 13.1343 -threshold=19.000000000000004 24.450000762939457 14.500000000000002 2.5000000000000004 10.750000000000002 -decision_type=2 2 2 2 2 -left_child=2 -2 3 -1 -5 -right_child=1 -3 -4 4 -6 -leaf_value=-0.14111343137919885 0.14921097218990328 0.51175089677174879 -0.11670748524367809 -0.44968532919883719 -0.32155306190252297 -leaf_weight=4.0000000000000044 4.9999999999999982 6 4.9999999999999991 4 4 -leaf_count=4 5 6 5 4 4 -internal_value=-0.0148709 0.34696 -0.248997 -0.304117 -0.385619 -internal_weight=28 11 17 12 8 -internal_count=28 11 17 12 8 +split_feature=0 8 0 1 10 12 0 25 6 +split_gain=1263.66 258.088 116.005 67.815 45.3947 28.6922 27.5819 11.3736 7.3524 +threshold=21.69999980926514 16.099999427795414 31.399999618530277 12.84999990463257 8.7000002861022967 23.60000038146973 15.000000000000002 2.5000000000000004 -1.0000000180025095e-35 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=1 4 5 -3 -1 -2 -5 -6 -7 +right_child=2 3 -4 6 7 8 -8 -9 -10 +leaf_value=-0.49968813061714085 0.12601964771747579 0.11414172500371933 0.54374278783798224 -0.23115395009517675 -0.25651404460271204 0.22831971049308775 -0.061651375517249099 -0.35386926730473833 0.31329681192125602 +leaf_weight=4.0000000000000062 4.0000000000000027 3.9999999999999991 3.9999999999999991 3.9999999999999982 5.9999999999999982 4 6 6 6.9999999999999982 +leaf_count=4 4 4 4 4 6 4 6 6 7 +internal_value=-0.0145633 -0.216634 0.304495 -0.0598541 -0.353816 0.240696 -0.129452 -0.305192 0.282396 +internal_weight=49 30 19 14 16 15 10 12 11 +internal_count=49 30 19 14 16 15 10 12 11 is_linear=0 shrinkage=0.05 Tree=6 -num_leaves=5 +num_leaves=8 num_cat=0 -split_feature=0 8 7 7 -split_gain=819.851 115.847 59.7187 47.849 -threshold=19.000000000000004 24.450000762939457 12.200000286102297 16.19999980926514 -decision_type=2 2 2 2 -left_child=2 -2 -1 -4 -right_child=1 -3 3 -5 -leaf_value=-0.36468152403831455 0.13604293018579486 0.47335260936192097 -0.046046725511550916 -0.27806002516299483 -leaf_weight=5.0000000000000027 3.9999999999999982 7 4.9999999999999982 4 -leaf_count=5 4 7 5 4 -internal_value=0.0276703 0.350695 -0.226134 -0.149164 -internal_weight=25 11 14 9 -internal_count=25 11 14 9 +split_feature=7 14 7 12 12 8 5 +split_gain=977.42 180.06 126.022 97.1506 37.6761 18.182 2.62891 +threshold=20.10000038146973 56.500000000000007 12.200000286102297 30.04999923706055 16.750000000000004 24.750000000000004 12.242856979370119 +decision_type=2 10 2 2 2 2 2 +left_child=2 3 -1 5 6 -2 -4 +right_child=1 -3 4 -5 -6 -7 -8 +leaf_value=-0.39082636151994959 0.14201588332653023 -0.051153662055730825 -0.23434588015079505 0.46533189501081201 -0.049467771819659639 0.27564727238246367 -0.17996271133422848 +leaf_weight=7.0000000000000027 4.0000000000000053 3.9999999999999991 3.9999999999999982 6.9999999999999991 6.9999999999999991 7 5 +leaf_count=7 4 4 4 7 7 7 5 +internal_value=0.0140233 0.252287 -0.213881 0.319718 -0.136467 0.227054 -0.204133 +internal_weight=45 22 23 18 16 11 9 +internal_count=45 22 23 18 16 11 9 is_linear=0 shrinkage=0.05 Tree=7 -num_leaves=5 +num_leaves=11 num_cat=0 -split_feature=0 8 7 7 -split_gain=657.894 103.374 45.6805 19.6347 -threshold=19.000000000000004 24.450000762939457 16.19999980926514 12.200000286102297 -decision_type=2 2 2 2 -left_child=2 -2 3 -1 -right_child=1 -3 -4 -5 -leaf_value=-0.22679260492324818 0.13494828253984456 0.4427791198094686 -0.35092775225639344 -0.092634215702613182 -leaf_weight=5.0000000000000018 4.9999999999999982 6 3.9999999999999991 6 -leaf_count=5 5 6 4 6 -internal_value=0.00915142 0.302856 -0.206232 -0.153615 -internal_weight=26 11 15 11 -internal_count=26 11 15 11 +split_feature=4 8 0 1 4 0 17 12 17 6 +split_gain=976.829 184.293 102.382 59.2701 40.2686 27.8664 25.5048 20.4345 10.5864 6.25557 +threshold=20.042857170104984 16.099999427795414 31.399999618530277 12.84999990463257 13.32857131958008 15.000000000000002 1.6999999880790713 23.60000038146973 1.6999999880790713 -1.0000000180025095e-35 +decision_type=2 2 2 2 2 2 2 2 2 2 +left_child=1 4 7 -3 6 -5 -1 -2 -6 -9 +right_child=2 3 -4 5 8 -7 -8 9 -10 -11 +leaf_value=-0.47497982382774284 0.11094748079776756 0.10614636689424516 0.49328907728195193 -0.22353678792715079 -0.29215824902057652 -0.046478439271450039 -0.29642728567123411 0.19623036980628963 -0.18302650690078734 0.2769534508387248 +leaf_weight=4.0000000000000053 4.0000000000000027 3.9999999999999991 3.9999999999999991 3.9999999999999982 3.9999999999999982 5 4 4 5 5.9999999999999982 +leaf_count=4 4 4 4 4 4 5 4 4 5 6 +internal_value=-0.020995 -0.195712 0.270199 -0.0539965 -0.304082 -0.125171 -0.385704 0.206459 -0.23153 0.244664 +internal_weight=48 30 18 13 17 9 8 14 9 10 +internal_count=48 30 18 13 17 9 8 14 9 10 is_linear=0 shrinkage=0.05 Tree=8 -num_leaves=5 +num_leaves=11 num_cat=0 -split_feature=0 8 0 17 -split_gain=753.323 94.7491 44.1349 50.5657 -threshold=19.000000000000004 24.450000762939457 14.500000000000002 2.4500000476837163 -decision_type=2 2 2 2 -left_child=2 -2 3 -1 -right_child=1 -3 -4 -5 -leaf_value=-0.14497262001037578 0.1224933743476868 0.42754604135240826 -0.089603995680809026 -0.35316013608660013 -leaf_weight=5.0000000000000053 3.9999999999999982 7 4.9999999999999991 7 -leaf_count=5 4 7 5 7 -internal_value=-0.00579315 0.316618 -0.214412 -0.266415 -internal_weight=28 11 17 12 -internal_count=28 11 17 12 +split_feature=7 6 7 7 17 11 25 25 1 4 +split_gain=939.576 144.49 123.767 83.3406 50.6929 49.0318 13.4102 12.145 1.20175 0.706348 +threshold=20.10000038146973 -6.5499999523162833 7.8499999046325692 28.599999427795414 3.1499999761581425 15.750000000000002 2.5000000000000004 2.5000000000000004 16.250000000000004 13.992856979370119 +decision_type=2 2 2 2 2 2 2 2 2 2 +left_child=2 -2 -1 7 -5 6 9 -3 -7 -4 +right_child=1 3 5 4 -6 8 -8 -9 -10 -11 +leaf_value=-0.45367578864097552 -0.029085667431354496 0.24311604022979727 -0.18485917747020708 0.26301580071449288 0.49280932346979772 -0.072201903909444795 -0.27438492298126221 0.12622660622000692 -0.033443793281912798 -0.15514494776725765 +leaf_weight=4.0000000000000027 4.0000000000000027 5.0000000000000018 4.0000000000000018 3.9999999999999982 6 4 4.9999999999999991 4 4 4 +leaf_count=4 4 5 4 4 6 4 5 4 4 4 +internal_value=0.0134133 0.244046 -0.198769 0.301548 0.400892 -0.150215 -0.210149 0.191165 -0.0528228 -0.170002 +internal_weight=48 23 25 19 10 21 13 9 8 8 +internal_count=48 23 25 19 10 21 13 9 8 8 is_linear=0 shrinkage=0.05 Tree=9 -num_leaves=5 +num_leaves=9 num_cat=0 -split_feature=8 0 25 4 -split_gain=595.458 84.257 29.0752 20.2976 -threshold=18.500000000000004 23.000000000000004 2.5000000000000004 12.500000000000002 -decision_type=2 2 2 2 -left_child=2 -2 -1 -4 -right_child=1 -3 3 -5 -leaf_value=-0.11161282220855351 0.093005212396383319 0.38067240544727871 -0.34078732252120969 -0.1896755730733275 -leaf_weight=4.0000000000000009 3.9999999999999982 7 5 4 -leaf_count=4 4 7 5 4 -internal_value=0.00531823 0.276066 -0.223776 -0.273627 -internal_weight=24 11 13 9 -internal_count=24 11 13 9 +split_feature=4 7 1 3 0 9 8 9 +split_gain=597.402 90.2046 53.6501 45.397 25.8548 15.9599 8.0673 2.01239 +threshold=20.042857170104984 15.449999809265138 12.84999990463257 29.150000572204593 15.000000000000002 10.600000381469728 24.750000000000004 13.650000095367433 +decision_type=2 2 2 2 2 2 2 2 +left_child=1 5 -3 6 -4 -1 -2 -7 +right_child=3 2 4 -5 -6 7 -8 -9 +leaf_value=-0.29316626787185651 0.14320736527442934 0.10175975114107133 -0.21347902305424218 0.38621614277362826 -0.054126968128340586 -0.21036649942398067 0.22636283806392124 -0.16021189987659451 +leaf_weight=6.0000000000000027 4.9999999999999982 3.9999999999999991 3.9999999999999982 3.9999999999999991 7 4 7 4 +leaf_count=6 5 4 4 4 7 4 7 4 +internal_value=-0.00492524 -0.140244 -0.0550511 0.24034 -0.112073 -0.231522 0.191715 -0.185289 +internal_weight=45 29 15 16 11 14 12 8 +internal_count=45 29 15 16 11 14 12 8 is_linear=0 shrinkage=0.05 Tree=10 -num_leaves=6 +num_leaves=10 num_cat=0 -split_feature=0 8 0 25 4 -split_gain=616.333 80.0988 37.2236 44.986 9.21655 -threshold=19.000000000000004 24.450000762939457 14.500000000000002 2.5000000000000004 10.750000000000002 -decision_type=2 2 2 2 2 -left_child=2 -2 3 -1 -5 -right_child=1 -3 -4 4 -6 -leaf_value=-0.10603218208998429 0.10665611773729328 0.38713511058262418 -0.080563141703605665 -0.36506341695785516 -0.25772895738482471 -leaf_weight=4.0000000000000044 3.9999999999999982 7 4.9999999999999991 4 4 -leaf_count=4 4 7 5 4 4 -internal_value=-0.0064837 0.285143 -0.195183 -0.242942 -0.311396 -internal_weight=28 11 17 12 8 -internal_count=28 11 17 12 8 +split_feature=15 6 17 2 5 2 3 17 7 +split_gain=809.45 110.083 123.082 105.962 80.5721 65.1454 31.0043 25.0073 12.5474 +threshold=18.349999427795414 -0.3000000044703483 3.1499999761581425 21.649999618530277 9.3392858505249041 26.649999618530277 21.550000190734867 1.4499999880790713 15.449999809265138 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=4 5 3 -3 -1 -2 -5 -6 -9 +right_child=1 2 -4 6 7 -7 -8 8 -10 +leaf_value=-0.41633368730545012 -0.098223554156720572 -0.042859490960836376 0.43107860939843318 0.17388944253325458 -0.27624683976173375 0.17249510049819944 0.37075325250625601 -0.17876101732254032 -0.080225320160388941 +leaf_weight=4.0000000000000027 4.0000000000000018 4.0000000000000027 6.9999999999999991 4 4.0000000000000027 5 4 6.9999999999999982 6 +leaf_count=4 4 4 7 4 4 5 4 7 6 +internal_value=0.0202299 0.196224 0.264457 0.167261 -0.214429 0.0521757 0.272321 -0.166922 -0.133283 +internal_weight=49 28 19 12 21 9 8 17 13 +internal_count=49 28 19 12 21 9 8 17 13 is_linear=0 shrinkage=0.05 Tree=11 -num_leaves=5 +num_leaves=12 num_cat=0 -split_feature=0 8 0 17 -split_gain=456.23 66.3429 47.9922 41.3672 -threshold=19.000000000000004 24.450000762939457 14.500000000000002 2.4500000476837163 -decision_type=2 2 2 2 -left_child=2 -2 3 -1 -right_child=1 -3 -4 -5 -leaf_value=-0.11966208755969997 0.10632330588996414 0.36920583248138428 -0.023635203391313555 -0.31439250012238817 -leaf_weight=5.0000000000000018 3.9999999999999982 6 3.9999999999999991 6 -leaf_count=5 4 6 4 6 -internal_value=0.00245288 0.264053 -0.171947 -0.225879 -internal_weight=25 10 15 11 -internal_count=25 10 15 11 +split_feature=15 6 17 2 5 2 17 7 11 25 12 +split_gain=806.772 107.955 111.081 95.6303 67.1041 50.4755 35.3944 28.0502 14.4633 8.64677 2.06126 +threshold=18.349999427795414 -0.3000000044703483 3.1499999761581425 21.649999618530277 9.3392858505249041 26.649999618530277 2.1999999284744267 26.550000190734867 12.950000286102297 2.5000000000000004 16.750000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 2 +left_child=4 5 3 -3 -1 -2 9 -5 -8 -6 -10 +right_child=1 2 -4 7 6 -7 8 -9 10 -11 -12 +leaf_value=-0.3955170094966885 -0.093312374502420359 -0.040716514736413918 0.40952467918395996 0.16507994458079334 -0.20283542871475208 0.15787367820739742 -0.18785864830017091 0.35233060717582698 -0.10483449846506117 -0.30679916739463797 -0.05407455638051032 +leaf_weight=4.0000000000000027 4.0000000000000018 4.0000000000000027 6.9999999999999991 4 4.0000000000000018 4 4.9999999999999991 4 4 4 4 +leaf_count=4 4 4 7 4 4 4 5 4 4 4 4 +internal_value=-0.0031508 0.186359 0.251234 0.158898 -0.207821 0.0322807 -0.17207 0.258705 -0.121148 -0.254817 -0.0794545 +internal_weight=52 27 19 12 25 8 21 8 13 8 8 +internal_count=52 27 19 12 25 8 21 8 13 8 8 is_linear=0 shrinkage=0.05 Tree=12 -num_leaves=5 +num_leaves=10 num_cat=0 -split_feature=0 8 8 7 -split_gain=380.135 53.5006 38.7194 43.2394 -threshold=19.000000000000004 24.450000762939457 12.150000095367433 16.19999980926514 -decision_type=2 2 2 2 -left_child=2 -2 -1 -4 -right_child=1 -3 3 -5 -leaf_value=-0.29093345642089824 0.097411302104592354 0.34274399757385249 -0.016552985906600957 -0.22449381887912745 -leaf_weight=5.0000000000000027 3.9999999999999982 5 4.9999999999999982 5 -leaf_count=5 4 5 5 5 -internal_value=-0.023189 0.233707 -0.177327 -0.120523 -internal_weight=24 9 15 10 -internal_count=24 9 15 10 +split_feature=0 17 0 11 8 15 8 25 6 +split_gain=592.448 103.818 61.4503 30.7784 29.111 28.3819 7.07843 4.1684 1.31647 +threshold=21.69999980926514 1.6999999880790713 31.399999618530277 14.84999990463257 36.699998855590827 18.349999427795414 24.750000000000004 2.5000000000000004 2.0000000000000004 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=1 -1 6 7 5 -5 -2 -3 -8 +right_child=2 3 -4 4 -6 -7 8 -9 -10 +leaf_value=-0.32757129470507285 0.12575281262397758 -0.13713828921318047 0.39566740989685062 -0.070497385306017749 -0.16729881912469866 0.096460969280451517 0.18284744739532477 -0.20932206511497495 0.22133158743381498 +leaf_weight=6.0000000000000027 5.0000000000000027 4.0000000000000018 3.9999999999999991 6.9999999999999982 3.9999999999999991 4 4.9999999999999982 4 4 +leaf_count=6 5 4 4 7 4 4 5 4 4 +internal_value=-0.0024916 -0.142348 0.222833 -0.0940293 -0.0517889 -0.00978526 0.173452 -0.17323 0.199952 +internal_weight=47 29 18 23 15 11 14 8 9 +internal_count=47 29 18 23 15 11 14 8 9 is_linear=0 shrinkage=0.05 Tree=13 -num_leaves=5 +num_leaves=11 num_cat=0 -split_feature=0 8 7 7 -split_gain=426.902 60.0551 51.5531 27.1408 -threshold=19.000000000000004 24.450000762939457 12.200000286102297 16.19999980926514 -decision_type=2 2 2 2 -left_child=2 -2 -1 -4 -right_child=1 -3 3 -5 -leaf_value=-0.33684623837470978 0.098979862928390519 0.33360833326975503 -0.055538034935792291 -0.21326912164688108 -leaf_weight=4.0000000000000027 4.9999999999999982 6 5.9999999999999982 5 -leaf_count=4 5 6 6 5 -internal_value=-0.00963113 0.226959 -0.183131 -0.127234 -internal_weight=26 11 15 11 -internal_count=26 11 15 11 +split_feature=15 0 1 0 17 5 7 12 6 12 +split_gain=608.859 89.1942 57.4178 135.02 56.9627 40.3712 23.3725 14.8389 4.18601 3.60785 +threshold=18.349999427795414 31.399999618530277 13.700000286102297 19.149999618530277 1.6999999880790713 11.84999990463257 14.150000095367433 23.60000038146973 -1.0000000180025095e-35 16.750000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 +left_child=4 2 -2 -4 5 -1 -6 -5 -9 -8 +right_child=1 -3 3 7 6 -7 9 8 -10 -11 +leaf_value=-0.40716452002525305 0.29379056114703367 0.37588404417037968 -0.17355345413088785 0.067829093895852566 -0.20945484042167664 -0.18252261877059933 -0.10914997160434725 0.14408879756927473 0.20398880243301409 -0.047845960408449163 +leaf_weight=4.0000000000000018 4.0000000000000062 3.9999999999999991 4.0000000000000027 3.9999999999999991 5.9999999999999991 4 5.9999999999999982 5.0000000000000053 6.9999999999999929 4 +leaf_count=4 4 4 4 4 6 4 6 5 7 4 +internal_value=-0.00110759 0.157292 0.12086 0.0862734 -0.185907 -0.294844 -0.131438 0.15123 0.17903 -0.0846284 +internal_weight=52 28 24 20 24 8 16 16 12 10 +internal_count=52 28 24 20 24 8 16 16 12 10 is_linear=0 shrinkage=0.05 Tree=14 -num_leaves=5 +num_leaves=10 num_cat=0 -split_feature=0 0 25 11 -split_gain=392.675 35.1328 47.1651 27.7482 -threshold=19.000000000000004 14.500000000000002 2.5000000000000004 24.450000762939457 -decision_type=2 2 2 2 -left_child=1 2 -1 -2 -right_child=3 -3 -4 -5 -leaf_value=-0.081762676686048391 0.14590370357036594 -0.05887834966182709 -0.29699014936174661 0.32258629322051996 -leaf_weight=4.0000000000000053 3.9999999999999982 4.9999999999999991 7 5 -leaf_count=4 4 5 7 5 -internal_value=-0.0201531 -0.168773 -0.218726 0.244061 -internal_weight=25 16 11 9 -internal_count=25 16 11 9 +split_feature=4 17 0 7 1 0 8 25 2 +split_gain=474.588 99.217 58.7819 32.0073 35.9101 23.6606 8.24038 5.04228 1.24839 +threshold=20.042857170104984 1.6999999880790713 31.399999618530277 15.449999809265138 12.84999990463257 15.000000000000002 24.750000000000004 2.5000000000000004 23.649999618530277 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=1 -1 6 7 -5 -6 -2 -3 -8 +right_child=2 3 -4 4 5 -7 8 -9 -10 +leaf_value=-0.31856058835983259 0.088287128359079314 -0.12393966615200035 0.35708984136581423 0.081780222058296215 -0.19017077945172792 -0.037730326822825844 0.18916442990303045 -0.19925608396530148 0.15168860793113706 +leaf_weight=6.0000000000000027 5.0000000000000027 4.0000000000000018 3.9999999999999991 3.9999999999999991 3.9999999999999982 7 3.9999999999999982 5 5 +leaf_count=6 5 4 4 4 4 7 4 5 5 +internal_value=-0.0149204 -0.136703 0.18805 -0.0912381 -0.0465116 -0.0931632 0.139753 -0.165782 0.168345 +internal_weight=48 30 18 24 15 11 14 9 9 +internal_count=48 30 18 24 15 11 14 9 9 is_linear=0 shrinkage=0.05 Tree=15 -num_leaves=5 +num_leaves=11 num_cat=0 -split_feature=0 8 0 17 -split_gain=359.462 55.0176 37.0574 31.3127 -threshold=19.000000000000004 24.450000762939457 14.500000000000002 2.4500000476837163 -decision_type=2 2 2 2 -left_child=2 -2 3 -1 -right_child=1 -3 -4 -5 -leaf_value=-0.11428530700504774 0.086735686957836178 0.31130820512771606 -0.055934431552886965 -0.29488812138636905 -leaf_weight=4.0000000000000018 4.9999999999999982 6 4.9999999999999991 6 -leaf_count=4 5 6 5 6 -internal_value=-0.00786979 0.20923 -0.167076 -0.222647 -internal_weight=26 11 15 10 -internal_count=26 11 15 10 +split_feature=15 6 17 2 2 17 3 3 11 12 +split_gain=470.389 78.2824 97.01 65.1193 51.4469 49.3076 30.683 20.461 15.5916 1.74064 +threshold=18.349999427795414 -0.3000000044703483 3.1499999761581425 21.649999618530277 23.649999618530277 2.1999999284744267 12.750000000000002 21.550000190734867 12.950000286102297 16.750000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 +left_child=5 4 3 -3 -2 6 -1 -5 -7 -10 +right_child=1 2 -4 7 -6 8 -8 -9 9 -11 +leaf_value=-0.34325956344604475 -0.11185448113828891 -0.037654340267181369 0.38920300245285033 0.12946434253826736 0.12872360467910765 -0.17160941163698834 -0.1574684530496597 0.28939028680324552 -0.08830714374780653 -0.04166164025664329 +leaf_weight=5.0000000000000018 4.0000000000000018 4.0000000000000027 4.9999999999999991 4 5 5.9999999999999991 4 4 4 4 +leaf_count=5 4 4 5 4 5 6 4 4 4 4 +internal_value=-0.00466708 0.141039 0.204166 0.127067 0.0218 -0.169378 -0.260686 0.209427 -0.110681 -0.0649844 +internal_weight=49 26 17 12 9 23 9 8 14 8 +internal_count=49 26 17 12 9 23 9 8 14 8 is_linear=0 shrinkage=0.05 Tree=16 -num_leaves=5 +num_leaves=11 num_cat=0 -split_feature=7 8 25 17 -split_gain=200.838 20.2155 16.9621 26.0306 -threshold=20.10000038146973 25.250000000000004 2.5000000000000004 3.3500000238418584 -decision_type=2 2 2 2 -left_child=2 -2 -1 -4 -right_child=1 -3 3 -5 -leaf_value=-0.035383382439613309 0.12206844329833987 0.27287452816963192 -0.21010860602060955 -0.055637337118387212 -leaf_weight=6.0000000000000044 4.9999999999999982 4 6 5 -leaf_count=6 5 4 6 5 -internal_value=-0.00189609 0.189093 -0.103008 -0.139894 -internal_weight=26 9 17 11 -internal_count=26 9 17 11 +split_feature=15 1 4 5 4 8 8 12 6 2 +split_gain=414.143 93.6696 134.264 58.7845 25.0352 14.8373 5.13799 3.3751 1.5505 0.478511 +threshold=18.349999427795414 13.700000286102297 20.042857170104984 9.3392858505249041 29.007143020629886 12.150000095367433 24.750000000000004 16.750000000000004 2.0000000000000004 14.049999713897707 +decision_type=2 2 2 2 2 2 2 2 2 2 +left_child=3 -2 -3 -1 6 -5 -4 9 -8 -7 +right_child=1 2 4 5 -6 7 8 -9 -10 -11 +leaf_value=-0.32108619511127445 0.36962105315178606 -0.155440537247341 0.085401098383590501 -0.17337727348009735 0.27513073086738588 -0.083600991964340232 0.1363760739564896 -0.039578558877110474 0.17656437456607815 -0.108057864010334 +leaf_weight=4.0000000000000027 4.0000000000000027 4.0000000000000027 4.0000000000000027 6.0000000000000027 3.9999999999999991 3.9999999999999982 5.9999999999999982 4 4 4 +leaf_count=4 4 4 4 6 4 4 6 4 4 4 +internal_value=0.0119543 0.147052 0.106585 -0.147707 0.164813 -0.109179 0.133294 -0.0770791 0.152451 -0.0958294 +internal_weight=48 26 22 22 18 18 14 12 10 8 +internal_count=48 26 22 22 18 18 14 12 10 8 is_linear=0 shrinkage=0.05 Tree=17 -num_leaves=5 +num_leaves=9 num_cat=0 -split_feature=0 11 7 0 -split_gain=345.166 44.0337 16.0888 13.2211 -threshold=14.500000000000002 25.60000038146973 12.200000286102297 23.000000000000004 -decision_type=2 2 2 2 -left_child=2 3 -1 -2 -right_child=1 -3 -4 -5 -leaf_value=-0.29089830815792067 0.057816250920295735 0.31159715652465825 -0.16144098651905853 0.172799459695816 -leaf_weight=4.0000000000000018 4.9999999999999982 3.9999999999999991 6 5 -leaf_count=4 5 4 6 5 -internal_value=0.0111345 0.171391 -0.213224 0.115308 -internal_weight=24 14 10 10 -internal_count=24 14 10 10 +split_feature=10 7 7 8 5 25 15 8 +split_gain=502.053 101.749 129.729 55.2312 25.4347 7.65009 4.87669 4.28075 +threshold=22.649999618530277 54.800001144409187 31.85000038146973 16.099999427795414 9.3392858505249041 2.5000000000000004 26.599999427795414 19.300000190734867 +decision_type=2 2 2 2 2 2 2 2 +left_child=3 2 6 4 -1 -6 -2 -5 +right_child=1 -3 -4 7 5 -7 -8 -9 +leaf_value=-0.30503188669681497 0.12753766860280707 -0.033250211179256438 0.4598657321929932 -0.072034016251564012 -0.12534081169537137 -0.20228047569592791 0.19674461781978603 0.001116061396896839 +leaf_weight=4.0000000000000062 7.0000000000000053 3.9999999999999991 4.9999999999999991 4 6.9999999999999982 6 4 4 +leaf_count=4 7 4 5 4 7 6 4 4 +internal_value=0.00558227 0.192303 0.248692 -0.143795 -0.194776 -0.160851 0.152704 -0.035459 +internal_weight=45 20 16 25 17 13 11 8 +internal_count=45 20 16 25 17 13 11 8 is_linear=0 shrinkage=0.05 Tree=18 -num_leaves=5 +num_leaves=10 num_cat=0 -split_feature=7 11 10 17 -split_gain=298.769 24.5761 21.9404 12.7549 -threshold=20.10000038146973 25.60000038146973 15.949999809265138 2.4500000476837163 -decision_type=2 2 2 2 -left_child=2 -2 3 -1 -right_child=1 -3 -4 -5 -leaf_value=-0.11951845437288276 0.1406556674412319 0.29601731300354001 -0.060399984121322636 -0.23478476256132122 -leaf_weight=4.0000000000000018 6.9999999999999982 4 4.9999999999999991 6 -leaf_count=4 7 4 5 6 -internal_value=-0.000773977 0.197151 -0.145919 -0.188678 -internal_weight=26 11 15 10 -internal_count=26 11 15 10 +split_feature=15 6 7 17 0 3 7 11 3 +split_gain=425.098 93.1889 131.759 36.8973 36.6458 30.9357 14.6521 12.0967 2.34385 +threshold=18.349999427795414 -0.3000000044703483 31.85000038146973 1.6999999880790713 21.69999980926514 12.750000000000002 14.150000095367433 24.450000762939457 10.949999809265138 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=3 4 7 5 -2 -1 -5 -3 -8 +right_child=1 2 -4 6 -6 -7 8 -9 -10 +leaf_value=-0.33146556317806225 -0.091962986066937383 0.057954321801662462 0.43687245368957522 -0.16069961190223694 0.12206325531005857 -0.13481958806514738 -0.088355843722820307 0.16695308515003748 -0.037005740404129024 +leaf_weight=4.0000000000000018 4.0000000000000018 3.9999999999999982 4.9999999999999991 5.9999999999999991 4 4 3.9999999999999982 7 5 +leaf_count=4 4 4 5 6 4 4 4 7 5 +internal_value=0.00718004 0.154386 0.224053 -0.146426 0.0150501 -0.233143 -0.100177 0.127317 -0.059828 +internal_weight=47 24 16 23 8 8 15 11 9 +internal_count=47 24 16 23 8 8 15 11 9 is_linear=0 shrinkage=0.05 Tree=19 -num_leaves=5 +num_leaves=8 num_cat=0 -split_feature=0 11 7 7 -split_gain=217.533 18.7754 17.9182 5.66246 -threshold=19.000000000000004 24.450000762939457 16.19999980926514 12.200000286102297 -decision_type=2 2 2 2 -left_child=2 -2 3 -1 -right_child=1 -3 -4 -5 -leaf_value=-0.096831277236342314 0.12015924453735355 0.2600081086158752 -0.17168876290321353 -0.024785566329956054 -leaf_weight=5.0000000000000053 3.9999999999999982 6 4.9999999999999991 6 -leaf_count=5 4 6 5 6 -internal_value=0.0211297 0.204069 -0.0932071 -0.0575336 -internal_weight=26 10 16 11 -internal_count=26 10 16 11 +split_feature=7 6 12 6 8 25 11 +split_gain=220.139 47.4967 33.2207 22.0239 10.8401 9.55406 3.78676 +threshold=18.050000190734867 -6.5499999523162833 23.60000038146973 1.2500000000000002 16.099999427795414 2.5000000000000004 12.950000286102297 +decision_type=2 2 2 2 2 2 2 +left_child=4 -2 -3 -4 5 6 -1 +right_child=1 2 3 -5 -6 -7 -8 +leaf_value=-0.14033825099468222 -0.053236567974090536 0.037068102632959662 0.11497655766350885 0.24040119818278721 -0.050627512112259868 -0.18592455216816495 -0.071538217365741716 +leaf_weight=4.0000000000000018 4.0000000000000027 6.0000000000000027 6.9999999999999982 7 3.9999999999999991 6.9999999999999991 4 +leaf_count=4 4 6 7 7 4 7 4 +internal_value=0.00338648 0.104046 0.135503 0.177689 -0.123763 -0.143265 -0.105938 +internal_weight=43 24 20 14 19 15 8 +internal_count=43 24 20 14 19 15 8 is_linear=0 shrinkage=0.05 Tree=20 -num_leaves=5 +num_leaves=9 num_cat=0 -split_feature=7 25 4 8 -split_gain=200.486 30.5483 19.4882 15.5729 -threshold=20.10000038146973 2.5000000000000004 12.500000000000002 25.250000000000004 -decision_type=2 2 2 2 -left_child=1 -1 -3 -2 -right_child=3 2 -4 -5 -leaf_value=-0.021534685914715114 0.11891016662120822 -0.234692770242691 -0.10103578865528105 0.24627477228641506 -leaf_weight=6.0000000000000044 5.9999999999999982 5 6 4 -leaf_count=6 6 5 6 4 -internal_value=-0.00778987 -0.112287 -0.161789 0.169856 -internal_weight=27 17 11 10 -internal_count=27 17 11 10 +split_feature=7 1 0 17 3 12 11 7 +split_gain=287.164 77.4471 124.527 34.6669 22.738 14.9545 6.6591 2.50913 +threshold=18.050000190734867 13.700000286102297 21.69999980926514 2.1999999284744267 12.750000000000002 30.04999923706055 12.950000286102297 24.750000000000004 +decision_type=2 2 2 2 2 2 2 2 +left_child=3 -2 -3 4 -1 7 -5 -4 +right_child=1 2 5 6 -6 -7 -8 -9 +leaf_value=-0.28129011154174788 0.31799243986606573 -0.1725430907681583 0.080615713000297573 -0.12446224451065067 -0.12135186791419982 0.21106316804885866 -0.04891225291149956 0.12857441008090972 +leaf_weight=5.0000000000000018 4.0000000000000027 4.0000000000000027 4.9999999999999982 4.9999999999999982 4 4.9999999999999991 7 6 +leaf_count=5 4 4 5 5 4 5 7 6 +internal_value=-0.00099815 0.117152 0.0769834 -0.136026 -0.210206 0.139365 -0.0803914 0.106775 +internal_weight=45 24 20 21 9 16 12 11 +internal_count=45 24 20 21 9 16 12 11 is_linear=0 shrinkage=0.05 Tree=21 -num_leaves=6 +num_leaves=10 num_cat=0 -split_feature=0 10 8 25 1 -split_gain=162.942 23.4839 22.1296 13.9857 10.354 -threshold=19.000000000000004 15.949999809265138 24.450000762939457 2.5000000000000004 1.0000000180025095e-35 -decision_type=2 2 2 2 10 -left_child=1 3 -2 -1 -5 -right_child=2 -3 -4 4 -6 -leaf_value=-0.067836675234138874 0.063095346242189418 -0.004281503707170487 0.20552252133687335 -0.23922500908374783 -0.12546000257134435 -leaf_weight=4.0000000000000044 4.9999999999999982 3.9999999999999991 6 4 4 -leaf_count=4 5 4 6 4 4 -internal_value=-0.00735559 -0.109201 0.140783 -0.144174 -0.182343 -internal_weight=27 16 11 12 8 -internal_count=27 16 11 12 8 +split_feature=16 1 0 5 0 1 8 11 6 +split_gain=277.049 69.0073 100.174 39.0665 32.3202 26.5079 12.5848 6.07955 0.987508 +threshold=18.60000038146973 13.700000286102297 19.149999618530277 11.84999990463257 31.399999618530277 11.949999809265138 23.200000762939457 12.950000286102297 -1.0000000180025095e-35 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=3 -2 -3 5 6 -1 -4 -5 -8 +right_child=1 2 4 7 -6 -7 8 -9 -10 +leaf_value=-0.13242132186889641 0.30209281146526312 -0.15064009167253958 0.014873445685952875 -0.11347461819648746 0.25058233141899111 -0.30511011779308311 0.10030994772911053 -0.041287091480834141 0.12940351026398814 +leaf_weight=5.0000000000000018 4.0000000000000027 4.0000000000000027 4.0000000000000062 4.9999999999999982 3.9999999999999991 4 5.0000000000000089 7 6.9999999999999893 +leaf_count=5 4 4 4 5 4 4 5 7 7 +internal_value=0.00685874 0.109822 0.0777765 -0.130425 0.12346 -0.209172 0.0916793 -0.0713652 0.117281 +internal_weight=49 28 24 21 20 9 16 12 12 +internal_count=49 28 24 21 20 9 16 12 12 is_linear=0 shrinkage=0.05 Tree=22 -num_leaves=5 +num_leaves=10 num_cat=0 -split_feature=0 8 10 17 -split_gain=195.078 26.4115 18.8741 21.2154 -threshold=19.000000000000004 24.450000762939457 15.949999809265138 2.4500000476837163 -decision_type=2 2 2 2 -left_child=2 -2 3 -1 -right_child=1 -3 -4 -5 -leaf_value=-0.075910968035459436 0.055670683830976501 0.21672922202519007 -0.034816115498542792 -0.2153650792936484 -leaf_weight=5.0000000000000053 3.9999999999999982 7 4.9999999999999991 6 -leaf_count=5 4 7 5 6 -internal_value=-0.00392736 0.158162 -0.115364 -0.151977 -internal_weight=27 11 16 11 -internal_count=27 11 16 11 +split_feature=7 14 12 0 8 8 25 11 4 +split_gain=249.043 34.1278 55.015 24.6382 11.6984 8.92934 4.80004 3.1403 0.689548 +threshold=18.050000190734867 56.500000000000007 30.04999923706055 5.9500000476837167 23.200000762939457 16.099999427795414 2.5000000000000004 11.84999990463257 22.349999427795414 +decision_type=2 10 2 2 2 2 2 2 2 +left_child=3 2 4 -1 -2 6 7 -5 -6 +right_child=1 -3 -4 5 8 -7 -8 -9 -10 +leaf_value=-0.2283394306898115 0.023402696289122064 -0.028492337465286257 0.24665781259536745 -0.12024623155593864 0.095633736252784762 -0.041369960332910224 -0.14808089534441632 -0.057593421638011927 0.1224344770113627 +leaf_weight=4.0000000000000027 6.0000000000000098 3.9999999999999991 6.9999999999999991 4.0000000000000018 3.9999999999999982 5.9999999999999991 5.9999999999999991 4 6 +leaf_count=4 6 4 7 4 4 6 6 4 6 +internal_value=0.00213278 0.106303 0.129746 -0.115059 0.0785974 -0.0924032 -0.114275 -0.0889198 0.111714 +internal_weight=51 27 23 24 16 20 14 8 10 +internal_count=51 27 23 24 16 20 14 8 10 is_linear=0 shrinkage=0.05 Tree=23 -num_leaves=5 +num_leaves=10 num_cat=0 -split_feature=11 8 25 1 -split_gain=152.229 15.3166 12.435 25.0131 -threshold=16.500000000000004 25.250000000000004 2.5000000000000004 1.0000000180025095e-35 -decision_type=2 2 2 10 -left_child=2 -2 -1 -4 -right_child=1 -3 3 -5 -leaf_value=-0.038038628660142411 0.10269814218793599 0.22534844279289243 -0.21093816518783567 -0.04318924918770789 -leaf_weight=5.0000000000000009 6.9999999999999982 4 5 4 -leaf_count=5 7 4 5 4 -internal_value=0.00810559 0.147298 -0.10126 -0.136383 -internal_weight=25 11 14 9 -internal_count=25 11 14 9 +split_feature=7 14 17 5 9 0 17 11 3 +split_gain=224.589 33.726 61.2354 48.528 30.6765 16.4695 5.67142 2.77371 1.02212 +threshold=18.050000190734867 56.500000000000007 4.2500000000000009 21.450000762939457 10.600000381469728 31.399999618530277 1.6999999880790713 12.950000286102297 10.949999809265138 +decision_type=2 10 2 2 2 2 2 2 2 +left_child=4 2 3 -2 -1 -5 -6 -8 -9 +right_child=1 -3 -4 5 6 -7 7 8 -10 +leaf_value=-0.20520703991254161 -0.032734229043126055 -0.027067719399929049 0.26698639591534934 0.094740586727857609 -0.12576725333929051 0.22572032213211057 -0.092327660322189337 -0.061126054823398605 -0.02721611350774765 +leaf_weight=6.0000000000000027 6.0000000000000098 3.9999999999999991 5.9999999999999991 5.9999999999999982 4.0000000000000027 4 3.9999999999999991 3.9999999999999982 5 +leaf_count=6 6 4 6 6 4 4 4 4 5 +internal_value=0.00580327 0.106483 0.130765 0.0796825 -0.108009 0.147132 -0.0737038 -0.0576843 -0.0422872 +internal_weight=49 26 22 16 23 10 17 13 9 +internal_count=49 26 22 16 23 10 17 13 9 is_linear=0 shrinkage=0.05 Tree=24 -num_leaves=6 +num_leaves=9 num_cat=0 -split_feature=7 25 0 11 4 -split_gain=144.15 33.8287 15.5966 11.8087 6.02652 -threshold=20.10000038146973 2.5000000000000004 14.500000000000002 25.60000038146973 10.750000000000002 -decision_type=2 2 2 2 2 -left_child=1 -1 4 -2 -3 -right_child=3 2 -4 -5 -6 -leaf_value=0.027004780080169412 0.11261355678240459 -0.21149568185210224 -0.04717797376215456 0.22352228760719295 -0.12470193803310392 -leaf_weight=5.0000000000000044 5.9999999999999982 4 4.0000000000000009 4 4 -leaf_count=5 6 4 4 4 4 -internal_value=0.00634415 -0.0822634 -0.127792 0.156977 -0.168099 -internal_weight=27 17 12 10 8 -internal_count=27 17 12 10 8 +split_feature=7 1 0 7 3 17 7 8 +split_gain=193.447 63.9033 86.8876 26.3375 13.1918 5.90367 4.21223 2.65743 +threshold=18.050000190734867 13.700000286102297 21.69999980926514 7.8499999046325692 29.150000572204593 1.4499999880790713 24.750000000000004 15.400000095367433 +decision_type=2 2 2 2 2 2 2 2 +left_child=3 -2 -3 -1 6 -5 -4 -7 +right_child=1 2 4 5 -6 7 -8 -9 +leaf_value=-0.22891456931829432 0.27553606778383227 -0.15218570418655861 0.051643347491820639 -0.13943576887249934 0.18622047603130343 -0.090902038131441404 0.10873507601874216 -0.045555135111014042 +leaf_weight=4.0000000000000027 4.0000000000000027 4.0000000000000027 6.0000000000000018 4.0000000000000027 3.9999999999999991 6.9999999999999982 7 6 +leaf_count=4 4 4 6 4 4 7 7 6 +internal_value=-0.00160342 0.0923716 0.0574831 -0.113478 0.106817 -0.086317 0.082385 -0.0699727 +internal_weight=46 25 21 21 17 17 13 13 +internal_count=46 25 21 21 17 17 13 13 is_linear=0 shrinkage=0.05 Tree=25 -num_leaves=5 +num_leaves=10 num_cat=0 -split_feature=0 8 0 17 -split_gain=148.21 21.9847 15.6821 33.9071 -threshold=19.000000000000004 24.450000762939457 14.500000000000002 2.4500000476837163 -decision_type=2 2 2 2 -left_child=2 -2 3 -1 -right_child=1 -3 -4 -5 -leaf_value=-0.03928348690271373 0.05030433788895608 0.18757798671722412 -0.02865193784236908 -0.21558304363667655 -leaf_weight=5.0000000000000053 4.9999999999999982 7 4.9999999999999991 6 -leaf_count=5 5 7 5 6 -internal_value=-0.00245028 0.130381 -0.102073 -0.135447 -internal_weight=28 12 16 11 -internal_count=28 12 16 11 +split_feature=10 7 7 5 11 17 17 7 25 +split_gain=202.985 63.4854 94.6332 32.6307 14.818 6.06843 1.84188 1.30106 0.828382 +threshold=22.649999618530277 54.800001144409187 31.85000038146973 9.3392858505249041 15.750000000000002 1.4499999880790713 2.1999999284744267 18.050000190734867 2.5000000000000004 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=3 2 6 -1 5 -5 -2 -6 -7 +right_child=1 -3 -4 4 7 8 -8 -9 -10 +leaf_value=-0.21746883392333968 0.12447041153907762 -0.032615385055541991 0.38211452364921572 -0.14760514870285976 -0.029905663505196578 -0.060020860433578509 0.084736868313380648 0.0083525069057941419 -0.09054838120937346 +leaf_weight=4.0000000000000027 5.0000000000000053 4.9999999999999991 3.9999999999999991 4.0000000000000027 4.9999999999999982 4.9999999999999982 7 4 4 +leaf_count=4 5 5 4 4 5 5 7 4 4 +internal_value=0.00728041 0.1229 0.171498 -0.0861043 -0.0622199 -0.096363 0.101293 -0.012902 -0.0735886 +internal_weight=47 21 16 26 22 13 12 9 9 +internal_count=47 21 16 26 22 13 12 9 9 is_linear=0 shrinkage=0.05 Tree=26 -num_leaves=5 +num_leaves=9 num_cat=0 -split_feature=0 8 25 4 -split_gain=101.463 12.2381 10.6224 5.71198 -threshold=19.000000000000004 24.450000762939457 2.5000000000000004 12.500000000000002 -decision_type=2 2 2 2 -left_child=2 -2 -1 -4 -right_child=1 -3 3 -5 -leaf_value=-0.033866082131862631 0.044497444108128559 0.15740439295768738 -0.16091174602508543 -0.085334072113037104 -leaf_weight=5.0000000000000009 3.9999999999999982 6 5 5 -leaf_count=5 4 6 5 5 -internal_value=-0.0111257 0.112242 -0.0933706 -0.123123 -internal_weight=25 10 15 10 -internal_count=25 10 15 10 +split_feature=7 17 0 0 7 17 7 1 +split_gain=186.267 70.2403 37.9832 28.0963 10.2722 10.0248 2.64129 1.88785 +threshold=18.050000190734867 4.7000000476837167 21.69999980926514 5.9500000476837167 28.599999427795414 1.4499999880790713 24.750000000000004 12.84999990463257 +decision_type=2 2 2 2 2 2 2 2 +left_child=3 2 -2 -1 6 -5 -4 -7 +right_child=1 -3 4 5 -6 7 -8 -9 +leaf_value=-0.22391267567872986 -0.058933605477213782 0.28988811373710632 0.043050895258784305 -0.13757903635501853 0.16932871043682099 -0.07485138525565467 0.0955041726430257 -0.03663047584039824 +leaf_weight=4.0000000000000027 5.0000000000000062 3.9999999999999991 3.9999999999999982 5.0000000000000027 3.9999999999999991 5.9999999999999982 6 7 +leaf_count=4 5 4 4 5 4 6 6 7 +internal_value=-3.64365e-05 0.0994534 0.0593619 -0.104049 0.10161 -0.077412 0.0745229 -0.0542709 +internal_weight=45 23 19 22 14 18 10 13 +internal_count=45 23 19 22 14 18 10 13 is_linear=0 shrinkage=0.05 Tree=27 -num_leaves=5 +num_leaves=11 num_cat=0 -split_feature=8 25 1 8 -split_gain=107.147 11.5527 12.8558 6.09309 -threshold=18.500000000000004 2.5000000000000004 14.000000000000002 24.450000762939457 -decision_type=2 2 8 2 -left_child=1 -1 -3 -2 -right_child=3 2 -4 -5 -leaf_value=-0.032172779291868202 0.064065082073211682 -0.055823281407356262 -0.17154486353198684 0.13633297596658978 -leaf_weight=5.0000000000000009 4.9999999999999982 3.9999999999999991 6.0000000000000009 7 -leaf_count=5 5 4 6 7 -internal_value=-0.00513963 -0.0942284 -0.125256 0.106221 -internal_weight=27 15 10 12 -internal_count=27 15 10 12 +split_feature=7 14 17 2 5 0 17 11 8 1 +split_gain=168.329 34.308 44.3508 31.9317 21.8008 10.0965 9.42271 3.34042 0.921807 0.494528 +threshold=18.050000190734867 56.500000000000007 4.2500000000000009 19.250000000000004 9.3392858505249041 31.399999618530277 1.4499999880790713 12.950000286102297 24.750000000000004 13.700000286102297 +decision_type=2 10 2 2 2 2 2 2 2 2 +left_child=4 2 3 -2 -1 8 -6 -8 -5 -9 +right_child=1 -3 -4 5 6 -7 7 9 -10 -11 +leaf_value=-0.19912629351019842 -0.034482492953538828 -0.045729408413171774 0.22979687800010049 0.066508460789918913 -0.13070008754730217 0.18077167272567746 -0.076666615264756349 -0.016939473850652573 0.10045339763164518 -0.041802305728197091 +leaf_weight=4.0000000000000027 5.0000000000000098 3.9999999999999991 5.9999999999999991 3.9999999999999982 5.0000000000000027 4 6.9999999999999991 4 4 4 +leaf_count=4 5 4 6 4 5 4 7 4 4 4 +internal_value=0.00377934 0.0894217 0.112926 0.0716777 -0.0925683 0.115911 -0.0712567 -0.0514422 0.0834809 -0.0293709 +internal_weight=51 27 23 17 24 12 20 15 8 8 +internal_count=51 27 23 17 24 12 20 15 8 8 is_linear=0 shrinkage=0.05 Tree=28 -num_leaves=5 +num_leaves=10 num_cat=0 -split_feature=11 11 8 7 -split_gain=95.4773 9.62276 8.25341 18.9375 -threshold=16.500000000000004 25.60000038146973 12.150000095367433 16.19999980926514 -decision_type=2 2 2 2 -left_child=2 -2 -1 -4 -right_child=1 -3 3 -5 -leaf_value=-0.11294722110033029 0.091064527205058532 0.18828040659427639 0.027631362676620488 -0.11832991428673266 -leaf_weight=6.0000000000000027 6.9999999999999982 4 4.9999999999999982 4 -leaf_count=6 7 4 5 4 -internal_value=0.014528 0.126416 -0.0675231 -0.0372403 -internal_weight=26 11 15 9 -internal_count=26 11 15 9 +split_feature=8 1 0 1 0 7 0 2 10 +split_gain=121.544 54.8135 87.2007 16.5496 15.1948 6.85305 1.29822 0.627637 0.0478756 +threshold=17.599999427795414 12.84999990463257 16.599999427795414 13.700000286102297 31.399999618530277 22.85000038146973 11.59999990463257 23.649999618530277 28.000000000000004 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=3 -2 -3 6 5 -4 -1 -7 -9 +right_child=1 2 4 -5 -6 7 -8 8 -10 +leaf_value=-0.079506518159593823 0.24264623671770075 -0.17066565674613215 0.014241659492254239 -0.17191734105348588 0.17173309028148653 0.10008550435304642 -0.047811520844697947 0.0719603806734085 0.079696305096149431 +leaf_weight=7.0000000000000018 4.0000000000000027 4.0000000000000027 5.0000000000000062 4.9999999999999991 3.9999999999999991 3.9999999999999991 6 4 4 +leaf_count=7 4 4 5 5 4 4 6 4 4 +internal_value=0.00744745 0.0707942 0.0432979 -0.0946112 0.0840528 0.0634222 -0.0648781 0.0839141 0.0758283 +internal_weight=47 29 25 18 21 17 13 12 8 +internal_count=47 29 25 18 21 17 13 12 8 is_linear=0 shrinkage=0.05 Tree=29 -num_leaves=5 +num_leaves=11 num_cat=0 -split_feature=0 7 8 7 -split_gain=97.4171 16.9456 15.7058 15.4383 -threshold=19.000000000000004 16.19999980926514 24.450000762939457 12.200000286102297 -decision_type=2 2 2 2 -left_child=1 3 -2 -1 -right_child=2 -3 -4 -5 -leaf_value=-0.1125903564194837 0.042257812693715105 -0.17471121549606325 0.16224527359008789 0.00083466370900471998 -leaf_weight=6.0000000000000053 4.9999999999999982 3.9999999999999991 6 6 -leaf_count=6 5 4 6 6 -internal_value=-0.00683772 -0.0855862 0.107706 -0.0558778 -internal_weight=27 16 11 12 -internal_count=27 16 11 12 +split_feature=4 6 17 0 10 7 12 0 2 9 +split_gain=130.375 42.2166 14.0801 12.7055 11.341 9.23703 5.04019 3.60487 0.566442 0.0432077 +threshold=20.042857170104984 -6.5499999523162833 3.1499999761581425 31.399999618530277 14.84999990463257 16.94999980926514 23.60000038146973 11.59999990463257 23.649999618530277 29.149999618530277 +decision_type=2 2 2 2 2 2 2 2 2 2 +left_child=1 -1 4 6 7 -4 -2 -3 -8 -10 +right_child=3 2 5 -5 -6 -7 8 -9 9 -11 +leaf_value=-0.21466801822185499 0.014909716323018046 -0.090652255713939617 -0.050827408204786484 0.16314643621444702 -0.14611186265945436 0.051112071983516208 0.095081225037574768 -0.0293735921382904 0.068362361192703239 0.075711488723754869 +leaf_weight=5.0000000000000027 4.0000000000000062 4.0000000000000018 4.9999999999999982 3.9999999999999991 4.9999999999999991 4 3.9999999999999991 6 4 4 +leaf_count=5 4 4 5 4 5 4 4 6 4 4 +internal_value=-0.0147672 -0.0824979 -0.0549624 0.0834422 -0.0846273 -0.00552097 0.0635162 -0.0538851 0.0797184 0.0720369 +internal_weight=49 29 24 20 15 9 16 10 12 8 +internal_count=49 29 24 20 15 9 16 10 12 8 is_linear=0 shrinkage=0.05 Tree=30 -num_leaves=5 +num_leaves=8 num_cat=0 -split_feature=9 8 10 17 -split_gain=62.2442 6.93422 5.98096 6.8285 -threshold=21.60000038146973 25.250000000000004 15.949999809265138 2.7500000000000004 -decision_type=2 2 2 2 -left_child=2 -2 3 -1 -right_child=1 -3 -4 -5 -leaf_value=-0.092129348005567199 0.082430855631828337 0.17075412273406979 -0.0032282443451029917 -0.010235727019608018 -leaf_weight=7.0000000000000053 4.9999999999999982 4 6.9999999999999991 4 -leaf_count=7 5 4 7 4 -internal_value=0.0143231 0.121686 -0.0393581 -0.0623498 -internal_weight=27 9 18 11 -internal_count=27 9 18 11 +split_feature=1 17 17 7 0 7 6 +split_gain=77.4056 27.8115 6.13628 13.3916 6.76706 4.87788 1.70949 +threshold=18.750000000000004 1.6999999880790713 3.1499999761581425 15.449999809265138 12.750000000000002 22.85000038146973 1.5500000119209292 +decision_type=2 2 2 2 2 2 2 +left_child=1 -1 4 -4 -3 -2 -7 +right_child=5 2 3 -5 -6 6 -8 +leaf_value=-0.1587386088711874 0.021744909882545457 -0.11095239222049708 -0.080512364208698298 0.042229357324540612 -0.032192359864711757 0.064575988480022981 0.10285492777824401 +leaf_weight=7.0000000000000027 5.0000000000000027 5.0000000000000018 3.9999999999999982 5 6 6.9999999999999982 5 +leaf_count=7 5 5 4 5 6 7 5 +internal_value=-0.02034 -0.0729626 -0.0429409 -0.0123225 -0.0679924 0.0632371 0.0805255 +internal_weight=44 27 20 9 11 17 12 +internal_count=44 27 20 9 11 17 12 is_linear=0 shrinkage=0.05 Tree=31 -num_leaves=5 +num_leaves=10 num_cat=0 -split_feature=0 7 11 11 -split_gain=33.8001 20.4906 7.17044 1.20202 -threshold=19.000000000000004 16.19999980926514 24.450000762939457 11.84999990463257 -decision_type=2 2 2 2 -left_child=1 3 -2 -1 -right_child=2 -3 -4 -5 -leaf_value=0.027611190825700745 0.046303890459239494 -0.1207993793487549 0.13272850215435025 -0.011151227727532385 -leaf_weight=4.0000000000000018 5.9999999999999982 4.9999999999999991 4 4 -leaf_count=4 6 5 4 4 -internal_value=0.0117644 -0.0413967 0.0808737 0.00822998 -internal_weight=23 13 10 8 -internal_count=23 13 10 8 +split_feature=12 1 0 4 2 17 12 1 2 +split_gain=90.0902 57.4109 69.5421 19.3169 8.69942 5.94887 3.91185 0.633122 0.448592 +threshold=17.149999618530277 12.84999990463257 16.599999427795414 11.021428585052492 30.94999980926514 1.4499999880790713 23.60000038146973 12.84999990463257 23.649999618530277 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=3 -2 -3 -1 6 -5 -4 -7 -8 +right_child=1 2 4 5 -6 7 8 -9 -10 +leaf_value=-0.17818713933229433 0.23440613225102402 -0.16051127390237513 0.023345581335680803 -0.10814704447984697 0.13381923735141754 -0.049223094433546076 0.085662905871868159 -0.026253513743480048 0.063198137879371633 +leaf_weight=4.0000000000000027 4.0000000000000027 4.0000000000000027 7.0000000000000062 3.9999999999999991 3.9999999999999991 5.9999999999999982 3.9999999999999982 6 5 +leaf_count=4 4 4 7 4 4 6 4 6 5 +internal_value=0.00114003 0.0590328 0.0298039 -0.0799098 0.0678669 -0.0553405 0.0513788 -0.0377383 0.0731825 +internal_weight=48 28 24 20 20 16 16 12 9 +internal_count=48 28 24 20 20 16 16 12 9 is_linear=0 shrinkage=0.05 Tree=32 -num_leaves=5 +num_leaves=11 num_cat=0 -split_feature=10 8 10 7 -split_gain=139.609 16.2024 2.99663 1.61411 -threshold=15.949999809265138 24.450000762939457 26.050000190734867 17.099999427795414 -decision_type=2 2 2 2 -left_child=-1 3 -3 -2 -right_child=1 2 -4 -5 -leaf_value=-0.19305681660771362 0.0018889637663960454 0.0943771332502365 0.15557999610900874 0.046807073801755895 -leaf_weight=7.0000000000000027 4 4 4 4 -leaf_count=7 4 4 4 4 -internal_value=-0.00681674 0.0746633 0.124979 0.024348 -internal_weight=23 16 8 8 -internal_count=23 16 8 8 +split_feature=12 1 1 6 5 25 11 3 6 1 +split_gain=88.1975 49.8806 59.3144 20.094 10.9426 1.81412 1.66738 1.0644 0.432254 0.0421761 +threshold=17.149999618530277 12.84999990463257 18.750000000000004 -2.3500000238418575 29.742856979370121 2.5000000000000004 24.450000762939457 10.949999809265138 2.0000000000000004 28.10000038146973 +decision_type=2 2 2 2 2 2 2 2 2 2 +left_child=3 -2 -3 -1 6 8 -4 -7 -5 -8 +right_child=1 2 4 5 -6 7 9 -9 -10 -11 +leaf_value=-0.17797004655003534 0.22268583700060829 -0.12151278428733342 0.031697005530198379 -0.047462838888168359 0.14499514102935793 -0.087750758230686177 0.067688805858294177 -0.051274768263101567 -0.024218095652759071 0.061060573160648335 +leaf_weight=4.0000000000000027 4.0000000000000027 5.0000000000000027 6.0000000000000062 3.9999999999999973 3.9999999999999991 4 5.9999999999999982 4 4 4 +leaf_count=4 4 5 6 4 4 4 6 4 4 4 +internal_value=0.00304104 0.0587489 0.032519 -0.0777353 0.0710269 -0.0526766 0.0525348 -0.0695128 -0.0358405 0.0650375 +internal_weight=49 29 25 20 20 16 16 8 8 10 +internal_count=49 29 25 20 20 16 16 8 8 10 is_linear=0 shrinkage=0.05 Tree=33 -num_leaves=6 +num_leaves=9 num_cat=0 -split_feature=11 11 25 1 17 -split_gain=68.2268 6.95329 6.60298 15.0135 2.5217 -threshold=16.500000000000004 25.60000038146973 2.5000000000000004 1.0000000180025095e-35 2.0499999523162846 -decision_type=2 2 2 10 2 -left_child=2 4 -1 -4 -2 -right_child=1 -3 3 -5 -6 -leaf_value=-0.019063188433647151 0.095134383440017734 0.14780099391937254 -0.15070011615753173 -0.028170542903244489 0.03899061046540736 -leaf_weight=5.0000000000000009 3.9999999999999982 4 5 5 4 -leaf_count=5 4 4 5 5 4 -internal_value=0.0051124 0.0939753 -0.0659779 -0.0894353 0.0670625 -internal_weight=27 12 15 10 8 -internal_count=27 12 15 10 8 +split_feature=11 1 0 1 0 8 5 2 +split_gain=80.8584 45.2981 60.5914 15.3001 10.8209 3.44894 0.583811 0.503982 +threshold=15.750000000000002 12.84999990463257 15.000000000000002 13.700000286102297 31.399999618530277 23.200000762939457 12.242856979370119 23.649999618530277 +decision_type=2 2 2 2 2 2 2 2 +left_child=3 -2 -3 6 5 -4 -1 -7 +right_child=1 2 4 -5 -6 7 -8 -9 +leaf_value=-0.034319857358932497 0.21155153103172764 -0.14819554165005672 0.01939167795436721 -0.14739632333318395 0.13774538636207581 0.078445218503475217 -0.058482048511505118 0.056197025520460943 +leaf_weight=4.9999999999999982 4.0000000000000027 4.0000000000000027 7.0000000000000062 5.9999999999999991 3.9999999999999991 3.9999999999999982 5 7 +leaf_count=5 4 4 7 6 4 4 5 7 +internal_value=0.00649826 0.0549102 0.0308116 -0.0842742 0.0633583 0.0468279 -0.046401 0.0642873 +internal_weight=46 30 26 16 22 18 10 11 +internal_count=46 30 26 16 22 18 10 11 is_linear=0 shrinkage=0.05 Tree=34 -num_leaves=5 +num_leaves=10 num_cat=0 -split_feature=8 17 8 4 -split_gain=64.4868 7.99851 5.97112 1.72567 -threshold=18.500000000000004 2.4500000476837163 24.450000762939457 10.750000000000002 -decision_type=2 2 2 2 -left_child=1 -1 -2 -3 -right_child=2 3 -4 -5 -leaf_value=-0.030552486436707618 0.049446627795696273 -0.12696040404262018 0.12342990686496098 -0.080515943840146043 -leaf_weight=7.0000000000000027 4.9999999999999982 4 6 4 -leaf_count=7 5 4 6 4 -internal_value=-0.00215232 -0.0695849 0.0898011 -0.103738 -internal_weight=26 15 11 8 -internal_count=26 15 11 8 +split_feature=6 17 5 12 1 7 6 25 4 +split_gain=68.2838 85.084 34.4475 9.6378 8.90857 7.86418 2.75832 1.19599 0.662363 +threshold=-6.5499999523162833 4.7000000476837167 14.671428680419924 23.60000038146973 13.700000286102297 15.449999809265138 1.2500000000000002 2.5000000000000004 11.021428585052492 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=-1 2 4 7 5 8 -5 -4 -2 +right_child=1 -3 3 6 -6 -7 -8 -9 -10 +leaf_value=-0.15701873352130255 -0.067378482222557032 0.24265630394220353 0.02749406037852167 0.050350362062454239 -0.10308364272117615 0.025181083530187609 0.10395302971204122 -0.0091868376359343518 -0.041111365209023157 +leaf_weight=5.9999999999999991 4.0000000000000018 3.9999999999999991 5.0000000000000018 3.9999999999999982 4.9999999999999991 4.9999999999999991 6 4 6 +leaf_count=6 4 4 5 4 5 5 6 4 6 +internal_value=0.000993071 0.0230412 0.000516607 0.0487286 -0.0452847 -0.0260184 0.082512 0.0111914 -0.0516182 +internal_weight=49 43 39 19 20 15 10 9 10 +internal_count=49 43 39 19 20 15 10 9 10 is_linear=0 shrinkage=0.05 Tree=35 -num_leaves=5 +num_leaves=10 num_cat=0 -split_feature=7 7 0 8 -split_gain=45.7006 9.51823 12.8164 8.09556 -threshold=20.10000038146973 16.19999980926514 12.000000000000002 25.250000000000004 -decision_type=2 2 2 2 -left_child=1 2 -1 -2 -right_child=3 -3 -4 -5 -leaf_value=-0.080500855190413279 0.033643957972526548 -0.11893940009176732 0.024311005473136901 0.13423945009708402 -leaf_weight=7.0000000000000053 4 4.9999999999999991 5 4 -leaf_count=7 4 5 5 4 -internal_value=-0.0146046 -0.0609793 -0.0368292 0.0839417 -internal_weight=25 17 12 8 -internal_count=25 17 12 8 +split_feature=6 12 6 12 17 17 7 7 1 +split_gain=88.615 90.5341 30.2442 25.0412 4.10751 2.44969 1.23715 0.903578 0.297032 +threshold=-6.5499999523162833 30.04999923706055 1.5500000119209292 17.149999618530277 1.4499999880790713 2.1999999284744267 22.85000038146973 15.449999809265138 11.949999809265138 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=-1 3 -3 4 -2 -5 -7 8 -6 +right_child=1 2 -4 5 7 6 -8 -9 -10 +leaf_value=-0.21925786286592486 -0.085613692603073957 0.081316462904214848 0.27575211971998209 0.067203532457351695 -0.050411741137504587 0.0096874383588631957 0.043363152742385863 -0.015777217596769335 -0.032131665945053095 +leaf_weight=3.9999999999999991 5.0000000000000027 4 4 4.9999999999999991 4.9999999999999982 5.9999999999999982 5 4.9999999999999991 4 +leaf_count=4 5 4 4 5 5 6 5 5 4 +internal_value=0.00584385 0.0267835 0.178534 -0.00790234 -0.0467126 0.0381849 0.0249946 -0.0328194 -0.0422873 +internal_weight=47 43 8 35 19 16 11 14 9 +internal_count=47 43 8 35 19 16 11 14 9 is_linear=0 shrinkage=0.05 Tree=36 -num_leaves=4 +num_leaves=12 num_cat=0 -split_feature=0 25 11 -split_gain=61.0685 17.1927 2.87348 -threshold=19.000000000000004 2.5000000000000004 24.450000762939457 -decision_type=2 2 2 -left_child=1 -1 -2 -right_child=2 -3 -4 -leaf_value=-0.023952146060764776 0.043691192865371715 -0.15777686238288879 0.097296019792556754 -leaf_weight=4.0000000000000018 4.9999999999999982 6 5 -leaf_count=4 5 6 5 -internal_value=-0.0168767 -0.104247 0.0704936 -internal_weight=20 10 10 -internal_count=20 10 10 +split_feature=11 1 0 6 0 10 25 6 6 12 10 +split_gain=63.821 45.2014 47.8317 13.7888 8.83634 4.06737 3.78501 1.95653 0.789569 0.689861 0.403423 +threshold=15.750000000000002 12.84999990463257 15.000000000000002 -2.3500000238418575 31.399999618530277 20.000000000000004 2.5000000000000004 1.2500000000000002 2.0000000000000004 23.60000038146973 26.44999980926514 +decision_type=2 2 2 2 2 2 2 2 2 2 2 +left_child=3 -2 -3 -1 5 -4 8 -8 -5 -7 -11 +right_child=1 2 4 6 -6 9 7 -9 -10 10 -12 +leaf_value=-0.15154544040560711 0.20256954953074457 -0.13719076104462136 -0.0030588109791278798 -0.039143719524145144 0.11740378141403199 0.031232410669326784 -0.047346696630120269 -0.096800343692302684 -0.0077277746982872472 0.065969333052635207 0.046739036838213593 +leaf_weight=4.0000000000000027 3.9999999999999991 4.0000000000000027 5.0000000000000062 3.9999999999999973 3.9999999999999991 3.9999999999999991 4 4 4 4.9999999999999982 6 +leaf_count=4 4 4 5 4 4 4 4 4 4 5 6 +internal_value=0.00155367 0.0453452 0.0228846 -0.0685128 0.0495638 0.0359958 -0.0477546 -0.0720735 -0.0234357 0.049014 0.0554801 +internal_weight=52 32 28 20 24 20 16 8 8 15 11 +internal_count=52 32 28 20 24 20 16 8 8 15 11 is_linear=0 shrinkage=0.05 Tree=37 -num_leaves=5 +num_leaves=11 num_cat=0 -split_feature=9 7 0 9 -split_gain=17.8292 9.12726 2.14608 0.37796 -threshold=21.60000038146973 16.19999980926514 12.000000000000002 25.150000572204593 -decision_type=2 2 2 2 -left_child=1 2 -1 -2 -right_child=3 -3 -4 -5 -leaf_value=-0.0086874480048815313 0.059223059564828866 -0.064216595781700955 0.03859366923570632 0.080958966165781007 -leaf_weight=6.0000000000000053 4 6.9999999999999991 4 4 -leaf_count=6 4 7 4 4 -internal_value=0.00853848 -0.0204274 0.010225 0.070091 -internal_weight=25 17 10 8 -internal_count=25 17 10 8 +split_feature=6 17 1 17 10 0 7 17 6 6 +split_gain=55.9554 67.5499 28.5643 7.33507 7.20925 6.63392 4.08043 1.89121 1.53817 0.986712 +threshold=-6.5499999523162833 4.7000000476837167 19.250000000000004 3.1499999761581425 14.84999990463257 31.399999618530277 16.94999980926514 2.1999999284744267 -1.0000000180025095e-35 0.69999998807907116 +decision_type=2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 3 4 8 7 -5 -4 -2 -9 +right_child=1 -3 5 6 -6 -7 -8 9 -10 -11 +leaf_value=-0.13846179073055584 -0.051424680948257473 0.22021680548787118 0.065252692997455605 -0.028269648924469943 -0.10932821203023196 0.11153359562158585 0.04314839150756597 0.040705390274524682 -0.013874886867900674 0.0055857353843748563 +leaf_weight=5.9999999999999991 4.9999999999999964 3.9999999999999991 3.9999999999999991 4 3.9999999999999991 3.9999999999999991 4 4 6.0000000000000053 4 +leaf_count=6 5 4 4 4 4 4 4 4 6 4 +internal_value=0.00457624 0.024535 0.0044651 -0.0312248 -0.0518457 0.0557694 0.00743937 0.0371813 -0.030943 0.0231456 +internal_weight=49 43 39 23 15 16 8 12 11 8 +internal_count=49 43 39 23 15 16 8 12 11 8 is_linear=0 shrinkage=0.05 Tree=38 -num_leaves=5 +num_leaves=9 num_cat=0 -split_feature=0 0 17 8 -split_gain=42.1852 8.11334 22.3295 3.14193 -threshold=19.000000000000004 14.500000000000002 2.4500000476837163 25.250000000000004 -decision_type=2 2 2 2 -left_child=1 2 -1 -2 -right_child=3 -3 -4 -5 -leaf_value=-0.0026285094022750841 0.059161599576473246 0.00066261783242225645 -0.15205909729003905 0.11861472725868223 -leaf_weight=5.0000000000000018 4.9999999999999982 4.9999999999999991 5 4 -leaf_count=5 5 5 5 4 -internal_value=5.91511e-06 -0.0513417 -0.0773438 0.0855852 -internal_weight=24 15 10 9 -internal_count=24 15 10 9 +split_feature=1 1 7 17 0 7 7 10 +split_gain=43.1009 44.777 11.4062 6.76286 6.74624 2.52642 2.27882 0.698696 +threshold=7.5000000000000009 18.750000000000004 18.050000190734867 1.4499999880790713 31.399999618530277 22.85000038146973 14.150000095367433 26.44999980926514 +decision_type=2 2 2 2 2 2 2 2 +left_child=-1 2 3 -2 5 -3 -5 -7 +right_child=1 4 -4 6 -6 7 -8 -9 +leaf_value=0.16896476894617082 -0.093829090893268421 0.0076787020390232316 -0.1308180564083159 -0.042094381401936221 0.10595691353082658 0.061690849661827099 -0.00010185209768159049 0.037218780602727618 +leaf_weight=3.9999999999999991 4.0000000000000062 6.0000000000000062 3.9999999999999991 5.9999999999999982 3.9999999999999991 4.9999999999999982 7 7 +leaf_count=4 4 6 4 6 4 5 7 7 +internal_value=0.0119761 -0.00262749 -0.0548508 -0.0369762 0.0472221 0.0341699 -0.019483 0.0474155 +internal_weight=47 43 21 17 22 18 13 12 +internal_count=47 43 21 17 22 18 13 12 is_linear=0 shrinkage=0.05 Tree=39 -num_leaves=5 +num_leaves=9 num_cat=0 -split_feature=8 0 7 17 -split_gain=42.3517 9.40166 11.1999 0.874838 -threshold=18.500000000000004 12.000000000000002 16.19999980926514 2.0499999523162846 -decision_type=2 2 2 2 -left_child=1 -1 -3 -2 -right_child=3 2 -4 -5 -leaf_value=-0.13055932044982901 0.075182829797267939 0.016001777164638041 -0.1023194123059511 0.04587044503007616 -leaf_weight=5.0000000000000027 3.9999999999999982 4 4 7 -leaf_count=5 4 4 4 7 -internal_value=-0.0156768 -0.0767744 -0.0431588 0.0565295 -internal_weight=24 13 8 11 -internal_count=24 13 8 11 +split_feature=2 0 1 12 0 25 10 11 +split_gain=71.2219 61.6626 33.0931 23.1145 5.49113 3.16596 1.38565 0.437656 +threshold=6.6000001430511483 12.750000000000002 12.84999990463257 17.149999618530277 31.399999618530277 2.5000000000000004 20.000000000000004 24.94999980926514 +decision_type=2 2 2 2 2 2 2 2 +left_child=-1 2 -2 5 6 -3 -5 -8 +right_child=1 3 -4 4 -6 -7 7 -9 +leaf_value=0.20095378942787648 -0.055994172074965078 -0.021698276698589317 -0.23627761229872701 0.0096756342798471288 0.10065906941890718 -0.077460451424121834 0.051726789346763072 0.03235841035842895 +leaf_weight=3.9999999999999991 6.9999999999999982 7.0000000000000018 4 4.0000000000000062 3.9999999999999991 4 6.9999999999999982 5 +leaf_count=4 7 7 4 4 4 4 7 5 +internal_value=-0.000647084 -0.0198472 -0.121552 0.0162416 0.0482609 -0.0419754 0.0351614 0.0436566 +internal_weight=46 42 11 31 20 11 16 12 +internal_count=46 42 11 31 20 11 16 12 is_linear=0 shrinkage=0.05 Tree=40 -num_leaves=5 +num_leaves=10 num_cat=0 -split_feature=10 11 17 11 -split_gain=30.5208 9.56514 5.99462 2.55558 -threshold=15.949999809265138 25.60000038146973 2.4500000476837163 16.500000000000004 -decision_type=2 2 2 2 -left_child=2 3 -1 -2 -right_child=1 -3 -4 -5 -leaf_value=-0.020241806656122199 -0.0061912831664085403 0.11039046943187714 -0.091923368615763515 0.040611458463328226 -leaf_weight=5.0000000000000018 4.9999999999999982 3.9999999999999991 7 7 -leaf_count=5 5 4 7 7 -internal_value=-0.00177811 0.0434304 -0.0620561 0.0211103 -internal_weight=28 16 12 12 -internal_count=28 16 12 12 +split_feature=2 0 3 5 0 7 4 25 4 +split_gain=57.8494 55.8814 26.2998 8.71457 4.36349 1.93396 0.424698 0.374019 0.347821 +threshold=6.6000001430511483 10.050000190734865 19.10000038146973 11.84999990463257 31.399999618530277 16.94999980926514 27.028571128845218 2.5000000000000004 22.349999427795414 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=-1 -2 3 -3 6 7 8 -5 -4 +right_child=1 2 4 5 -6 -7 -8 -9 -10 +leaf_value=0.17990046106278898 -0.17598520994186403 -0.085617155705889014 0.039099220186471953 -0.019118180684745315 0.095626115798950195 0.011071512401103975 0.048627391457557671 -0.038856535777449602 0.018247944349423048 +leaf_weight=3.9999999999999991 4.9999999999999991 6.0000000000000027 3.9999999999999982 5.9999999999999982 3.9999999999999991 4.9999999999999991 4 4 4 +leaf_count=4 5 6 4 6 4 5 4 4 4 +internal_value=-0.00179135 -0.0190953 0.002106 -0.0346896 0.0504002 -0.0143185 0.0353249 -0.0270135 0.0286736 +internal_weight=46 42 37 21 16 15 12 10 8 +internal_count=46 42 37 21 16 15 12 10 8 is_linear=0 shrinkage=0.05 Tree=41 -num_leaves=5 +num_leaves=10 num_cat=0 -split_feature=0 7 8 8 -split_gain=30.9277 6.6852 10.0167 4.88011 -threshold=19.000000000000004 16.19999980926514 12.150000095367433 24.450000762939457 -decision_type=2 2 2 2 -left_child=1 2 -1 -2 -right_child=3 -3 -4 -5 -leaf_value=-0.066878687590360572 0.02567629516124726 -0.090010862350463877 0.024484723185499505 0.090352048618452876 -leaf_weight=6.0000000000000053 4.9999999999999982 4.9999999999999991 6 7 -leaf_count=6 5 5 6 7 -internal_value=0.00194578 -0.0414364 -0.021197 0.0634038 -internal_weight=29 17 12 12 -internal_count=29 17 12 12 +split_feature=2 0 1 17 4 17 1 0 0 +split_gain=54.3193 55.3705 15.6308 3.8238 2.31237 1.91672 1.05737 0.687584 0.352959 +threshold=6.6000001430511483 10.050000190734865 19.250000000000004 1.4499999880790713 27.028571128845218 2.1999999284744267 12.84999990463257 16.599999427795414 22.550000190734867 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=-1 -2 3 -3 5 -4 -5 -8 -7 +right_child=1 2 4 6 -6 8 7 -9 -10 +leaf_value=0.18191108442842963 -0.16718594789505006 -0.06636577695608134 0.052209125757217367 -0.027604606995979948 0.063568712558065149 0.023248434066772457 0.014944476261734965 -0.012867968603968619 0.0022436980972997842 +leaf_weight=3.9999999999999991 4.9999999999999991 4.0000000000000027 5.0000000000000027 5.9999999999999991 6.9999999999999991 4 3.9999999999999982 5 4 +leaf_count=4 5 4 5 6 7 4 4 5 4 +internal_value=0.00550119 -0.0105361 0.00954724 -0.0229291 0.0403998 0.0279242 -0.011346 -0.000506882 0.0127461 +internal_weight=48 44 39 19 20 13 15 9 8 +internal_count=48 44 39 19 20 13 15 9 8 is_linear=0 shrinkage=0.05 Tree=42 -num_leaves=5 +num_leaves=9 num_cat=0 -split_feature=8 11 25 1 -split_gain=28.5355 6.34384 5.30562 10.4336 -threshold=18.500000000000004 24.450000762939457 2.5000000000000004 14.000000000000002 -decision_type=2 2 2 8 -left_child=2 -2 -1 -4 -right_child=1 -3 3 -5 -leaf_value=-0.0040137021243572225 0.019529772975615096 0.086844800199781136 -0.0045439243316650396 -0.10879524437089759 -leaf_weight=5.0000000000000009 6.9999999999999982 7 3.9999999999999991 6.0000000000000009 -leaf_count=5 7 7 4 6 -internal_value=0.00184849 0.0531873 -0.0460677 -0.0670947 -internal_weight=29 14 15 10 -internal_count=29 14 15 10 +split_feature=3 5 0 1 7 0 12 8 +split_gain=47.5313 40.0511 27.3795 17.0632 6.93031 2.7117 0.774069 0.532538 +threshold=10.949999809265138 12.242856979370119 13.299999713897707 13.700000286102297 24.750000000000004 31.399999618530277 17.149999618530277 29.19999980926514 +decision_type=2 2 2 2 2 2 2 2 +left_child=3 -2 -3 -1 6 7 -4 -6 +right_child=1 2 4 -5 5 -7 -8 -9 +leaf_value=-0.030016301359449118 0.17781256288290026 -0.097452983111143046 -0.017384624760597933 -0.14492334102590876 0.047908703131335134 0.087666372954845431 0.010187934378960303 0.025038897246122356 +leaf_weight=6.9999999999999982 3.9999999999999991 5.0000000000000027 4.0000000000000018 6 6.9999999999999982 3.9999999999999991 7 4 +leaf_count=7 4 5 4 6 7 4 7 4 +internal_value=-0.00141062 0.0289127 0.00969981 -0.0830503 0.0303061 0.0524121 0.000161549 0.0395924 +internal_weight=48 35 31 13 26 15 11 11 +internal_count=48 35 31 13 26 15 11 11 is_linear=0 shrinkage=0.05 Tree=43 -num_leaves=6 +num_leaves=10 num_cat=0 -split_feature=0 17 8 11 17 -split_gain=26.6717 12.8972 8.65111 2.70049 0.773571 -threshold=14.500000000000002 2.4500000476837163 18.500000000000004 25.60000038146973 2.0499999523162846 -decision_type=2 2 2 2 2 -left_child=1 -1 -2 4 -4 -right_child=2 -3 3 -5 -6 -leaf_value=0.0060157436132431 -0.022258963063359241 -0.099125871488026207 0.063024595379829421 0.096011102199554443 0.033524291813373561 -leaf_weight=5.0000000000000018 4.0000000000000027 7 3.9999999999999982 3.9999999999999991 5 -leaf_count=5 4 7 4 4 5 -internal_value=0.00175607 -0.0553169 0.0420428 0.061828 0.0466355 -internal_weight=29 12 17 13 9 -internal_count=29 12 17 13 9 +split_feature=2 0 1 12 0 25 17 15 6 +split_gain=50.6105 48.1241 29.3782 14.3797 3.98726 2.82874 1.20494 0.926954 0.391615 +threshold=6.6000001430511483 12.750000000000002 12.84999990463257 17.149999618530277 31.399999618530277 2.5000000000000004 2.1999999284744267 26.599999427795414 0.69999998807907116 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=-1 2 -2 5 6 -3 -5 8 -8 +right_child=1 3 -4 4 -6 -7 7 -9 -10 +leaf_value=0.1717184789478779 -0.042844318891210223 -0.0073149959812872133 -0.21270765513181683 0.052742022275924595 0.083283056318759929 -0.060500855147838588 0.02336887903511525 0.037079530457655593 0.0023791808914393185 +leaf_weight=3.9999999999999991 6.9999999999999982 4.9999999999999982 4 4.0000000000000062 3.9999999999999991 5 3.9999999999999982 5.9999999999999991 5 +leaf_count=4 7 5 4 4 4 5 4 6 5 +internal_value=0.00143756 -0.0140425 -0.104613 0.0161476 0.0379108 -0.0339079 0.0283588 0.0218566 0.0117079 +internal_weight=48 44 11 33 23 10 19 15 9 +internal_count=48 44 11 33 23 10 19 15 9 is_linear=0 shrinkage=0.05 Tree=44 -num_leaves=6 +num_leaves=10 num_cat=0 -split_feature=0 17 8 11 0 -split_gain=24.0712 11.6397 7.80763 2.4372 0.718832 -threshold=14.500000000000002 2.4500000476837163 18.500000000000004 25.60000038146973 27.149999618530277 -decision_type=2 2 2 2 2 -left_child=1 -1 -2 4 -4 -right_child=2 -3 3 -5 -6 -leaf_value=0.0057149547338485691 -0.021146014332771284 -0.094169578381947103 0.056942607164382944 0.091210547089576724 0.028505202569067474 -leaf_weight=5.0000000000000018 4.0000000000000027 7 4.9999999999999982 3.9999999999999991 4 -leaf_count=5 4 7 5 4 4 -internal_value=0.00166827 -0.052551 0.0399407 0.0587366 0.0443038 -internal_weight=29 12 17 13 9 -internal_count=29 12 17 13 9 +split_feature=2 1 1 10 5 7 12 10 25 +split_gain=40.4708 17.6819 4.17026 8.74734 4.09849 2.40353 1.51167 0.793039 0.499588 +threshold=9.0999999046325701 18.750000000000004 13.700000286102297 14.84999990463257 29.742856979370121 15.449999809265138 23.60000038146973 26.44999980926514 2.5000000000000004 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=-1 2 5 -4 6 8 -3 -8 -2 +right_child=1 4 3 -5 -6 -7 7 -9 -10 +leaf_value=-0.13768351723750433 -0.01498590272385626 0.0050463025657726136 -0.0074227269738912569 -0.11198933962732552 0.079118902981281283 0.018425095267593863 0.052586465701460854 0.023844826221466064 -0.038693206608295436 +leaf_weight=5.9999999999999991 4.0000000000000018 7.0000000000000062 4 4 3.9999999999999991 3.9999999999999991 3.9999999999999982 6 5 +leaf_count=6 4 7 4 4 4 4 4 6 5 +internal_value=-0.0162134 0.00113945 -0.0313027 -0.059706 0.0335816 -0.0138238 0.022867 0.0353415 -0.0281566 +internal_weight=48 42 21 8 21 13 17 10 9 +internal_count=48 42 21 8 21 13 17 10 9 is_linear=0 shrinkage=0.05 Tree=45 -num_leaves=5 +num_leaves=10 num_cat=0 -split_feature=0 17 8 11 -split_gain=21.7243 10.5049 7.04638 2.23565 -threshold=14.500000000000002 2.4500000476837163 18.500000000000004 24.450000762939457 -decision_type=2 2 2 2 -left_child=1 -1 -2 -4 -right_child=2 -3 3 -5 -leaf_value=0.0054292047023773169 -0.020088713057339176 -0.089461099249976014 0.033403639619549119 0.074996486306190477 -leaf_weight=5.0000000000000018 4.0000000000000027 7 5.9999999999999982 7 -leaf_count=5 4 7 6 7 -internal_value=0.00158485 -0.0499235 0.0379437 0.0557998 -internal_weight=29 12 17 13 -internal_count=29 12 17 13 +split_feature=3 5 0 1 0 25 12 4 11 +split_gain=42.7817 36.0665 18.1177 13.0981 3.96242 3.28312 1.66306 0.671529 0.547933 +threshold=10.949999809265138 12.542857170104982 15.000000000000002 13.700000286102297 31.399999618530277 2.5000000000000004 23.60000038146973 19.142856597900394 24.94999980926514 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=3 -2 -3 5 6 -1 7 -4 -8 +right_child=1 2 4 -5 -6 -7 8 -9 -10 +leaf_value=0.0010418318677693607 0.17201047688722612 -0.074709361083805506 0.018727065715938788 -0.1287168586316208 0.075162957608699801 -0.063019836694002143 0.049957142025232326 -0.0060835737083107227 0.0260664756099383 +leaf_weight=3.9999999999999982 3.9999999999999991 5.0000000000000027 6.0000000000000053 6 3.9999999999999991 4 3.9999999999999982 5 6 +leaf_count=4 4 5 6 6 4 4 4 5 6 +internal_value=0.000689702 0.03098 0.0121759 -0.0728724 0.029553 -0.030989 0.0208653 0.0074495 0.0356227 +internal_weight=48 34 30 14 25 8 21 11 10 +internal_count=48 34 30 14 25 8 21 11 10 is_linear=0 shrinkage=0.05 Tree=46 -num_leaves=6 +num_leaves=10 num_cat=0 -split_feature=0 17 8 11 17 -split_gain=19.6062 9.48064 6.35936 2.06483 0.674355 -threshold=14.500000000000002 2.4500000476837163 18.500000000000004 25.60000038146973 2.0499999523162846 -decision_type=2 2 2 2 2 -left_child=1 -1 -2 4 -4 -right_child=2 -3 3 -5 -6 -leaf_value=0.0051577466726303077 -0.019084277842193825 -0.08498804313795906 0.055027168244123473 0.082900194823741918 0.027483583539724344 -leaf_weight=5.0000000000000018 4.0000000000000027 7 3.9999999999999982 3.9999999999999991 5 -leaf_count=5 4 7 4 4 5 -internal_value=0.00150561 -0.0474273 0.0360465 0.0530098 0.0397252 -internal_weight=29 12 17 13 9 -internal_count=29 12 17 13 9 +split_feature=1 2 1 1 10 5 7 17 17 +split_gain=47.5672 38.5767 14.4654 2.87881 7.44776 2.7834 1.44551 0.935674 0.678827 +threshold=7.5000000000000009 9.0999999046325701 19.250000000000004 13.700000286102297 14.84999990463257 29.742856979370121 15.449999809265138 2.1999999284744267 3.5499999523162846 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=-1 -2 3 6 -5 7 -3 -4 -9 +right_child=1 2 5 4 -6 -7 -8 8 -10 +leaf_value=0.16642950922250749 -0.14441345751285553 -0.023119221720844501 0.042123507559299445 0.0033045411109924325 -0.088230845928192136 0.071404810249805453 0.014559701923280953 -0.00032209983910433957 0.026269470651944477 +leaf_weight=3.9999999999999991 4.9999999999999991 7.0000000000000018 5.0000000000000027 3.9999999999999982 5 3.9999999999999991 4 3.9999999999999982 6 +leaf_count=4 5 7 5 4 5 4 4 4 6 +internal_value=0.00134759 -0.0136599 0.00310342 -0.0265766 -0.0475485 0.0343455 -0.00941779 0.0244631 0.0156328 +internal_weight=48 44 39 20 9 19 11 15 10 +internal_count=48 44 39 20 9 19 11 15 10 is_linear=0 shrinkage=0.05 Tree=47 -num_leaves=5 +num_leaves=10 num_cat=0 -split_feature=0 17 8 11 -split_gain=17.6946 8.55628 5.73932 1.86565 -threshold=14.500000000000002 2.4500000476837163 18.500000000000004 24.450000762939457 -decision_type=2 2 2 2 -left_child=1 -1 -2 -4 -right_child=2 -3 3 -5 -leaf_value=0.0048998612165451032 -0.01813006475567816 -0.080738642386027737 0.029900218360126025 0.067895670873778208 -leaf_weight=5.0000000000000018 4.0000000000000027 7 5.9999999999999982 7 -leaf_count=5 4 7 6 7 -internal_value=0.00143033 -0.0450559 0.0342442 0.0503593 -internal_weight=29 12 17 13 -internal_count=29 12 17 13 +split_feature=3 4 0 1 8 2 7 0 7 +split_gain=36.0742 31.7353 20.0031 9.15979 3.85811 1.39109 1.26611 0.706361 0.235557 +threshold=10.949999809265138 13.32857131958008 13.299999713897707 13.700000286102297 24.750000000000004 30.94999980926514 16.94999980926514 26.19999980926514 26.550000190734867 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=3 -2 -3 -1 6 8 -4 -8 -6 +right_child=1 2 4 -5 5 -7 7 -9 -10 +leaf_value=-0.034268831568104889 0.15682764463126661 -0.097141244681552052 -0.015431217327713957 -0.1184586852788925 0.040467238426208509 0.066861335933208463 0.026234248280525216 -0.00089125681552104641 0.024188371300697325 +leaf_weight=6.9999999999999982 3.9999999999999991 3.9999999999999991 5.0000000000000027 6 3.9999999999999982 3.9999999999999991 5.9999999999999982 4 5 +leaf_count=7 4 4 5 6 4 4 6 4 5 +internal_value=-0.00173363 0.0240468 0.00744924 -0.0731257 0.0223907 0.0423274 0.00511229 0.015384 0.0314234 +internal_weight=49 36 32 13 28 13 15 10 9 +internal_count=49 36 32 13 28 13 15 10 9 is_linear=0 shrinkage=0.05 Tree=48 -num_leaves=7 +num_leaves=10 num_cat=0 -split_feature=0 8 25 4 11 0 -split_gain=15.9693 5.17974 4.82807 3.49022 1.7502 0.626879 -threshold=14.500000000000002 18.500000000000004 2.5000000000000004 10.750000000000002 25.60000038146973 27.149999618530277 -decision_type=2 2 2 2 2 2 -left_child=2 -2 -1 -4 5 -3 -right_child=1 4 3 -5 -6 -7 -leaf_value=0.0020488075911998739 -0.017223561555147156 0.047413467168807996 -0.098254744708538039 -0.032203470170497892 0.075360403954982766 0.020857126638293262 -leaf_weight=4.0000000000000009 4.0000000000000027 4.9999999999999982 4 4 3.9999999999999991 4 -leaf_count=4 4 5 4 4 4 4 -internal_value=0.00135881 0.032532 -0.0428031 -0.0652291 0.0478413 0.0356106 -internal_weight=29 17 12 8 13 9 -internal_count=29 17 12 8 13 9 +split_feature=17 4 2 1 8 10 8 1 0 +split_gain=44.5553 26.9781 13.5903 8.23669 2.62609 1.43258 1.43357 1.32273 0.510628 +threshold=4.7000000476837167 11.021428585052492 6.6000001430511483 18.750000000000004 24.750000000000004 16.19999980926514 13.949999809265138 25.750000000000004 26.19999980926514 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=1 2 -1 5 8 6 -3 -6 -5 +right_child=-2 3 -4 4 7 -7 -8 -9 -10 +leaf_value=-0.02511097006499767 0.15025456666946413 -0.026970212012529377 -0.15544839352369305 0.02312112435698508 0.025950704142451292 -0.042139166314154868 0.0092804053177436181 0.061993850980486181 -0.00084669392090290767 +leaf_weight=4 4.9999999999999991 4.9999999999999982 4 5.0000000000000018 3.9999999999999982 3.9999999999999991 6 7 4 +leaf_count=4 5 5 4 5 4 4 6 7 4 +internal_value=0.00898518 -0.00744149 -0.0902797 0.011493 0.0324989 -0.016515 -0.00719715 0.0488873 0.0124688 +internal_weight=48 43 8 35 20 15 11 11 9 +internal_count=48 43 8 35 20 15 11 11 9 is_linear=0 shrinkage=0.05 Tree=49 -num_leaves=6 +num_leaves=11 num_cat=0 -split_feature=0 17 8 11 17 -split_gain=14.4123 7.43288 4.67472 1.57956 0.58844 -threshold=14.500000000000002 2.4500000476837163 18.500000000000004 25.60000038146973 2.0499999523162846 -decision_type=2 2 2 2 2 -left_child=1 -1 -2 4 -4 -right_child=2 -3 3 -5 -6 -leaf_value=0.0058979833126068084 -0.016362382937222707 -0.073920809371130811 0.048124146461486828 0.071592382341623309 0.02239489287137985 -leaf_weight=5.0000000000000018 4.0000000000000027 7 3.9999999999999982 3.9999999999999991 5 -leaf_count=5 4 7 4 4 5 -internal_value=0.00129087 -0.040663 0.0309054 0.0454493 0.0338301 -internal_weight=29 12 17 13 9 -internal_count=29 12 17 13 9 +split_feature=6 17 2 17 2 7 8 1 2 2 +split_gain=32.6637 44.6025 10.7639 2.7944 3.39266 2.45297 2.34173 1.17646 0.498776 0.32786 +threshold=-6.5499999523162833 4.7000000476837167 17.800000190734867 2.7500000000000004 9.0999999046325701 15.449999809265138 24.750000000000004 25.750000000000004 19.250000000000004 14.049999713897707 +decision_type=2 2 2 2 2 2 2 2 2 2 +left_child=-1 2 3 4 -2 -5 8 -8 -4 -6 +right_child=1 -3 6 5 9 -7 7 -9 -10 -11 +leaf_value=-0.10985328257083893 -0.078155462071299489 0.17371168760582806 0.023385098818107491 -0.026944997459650046 -0.014277017563581471 0.025586775876581664 0.024653169140219696 0.061033346652984613 0.00059128403663635245 -0.033482284098863599 +leaf_weight=5.9999999999999991 4.0000000000000027 3.9999999999999991 4.0000000000000018 4.9999999999999982 4.9999999999999982 4 3.9999999999999982 5 6 4 +leaf_count=6 4 4 4 5 5 4 4 5 6 4 +internal_value=-0.000269008 0.0143422 -0.00120601 -0.0250143 -0.0398412 -0.00359754 0.0263614 0.0448644 0.00970881 -0.0228127 +internal_weight=51 45 41 22 13 9 19 9 10 9 +internal_count=51 45 41 22 13 9 19 9 10 9 is_linear=0 shrinkage=0.05 Tree=50 -num_leaves=5 +num_leaves=11 num_cat=0 -split_feature=0 17 8 11 -split_gain=13.0071 6.70817 4.21893 1.44844 -threshold=14.500000000000002 2.4500000476837163 18.500000000000004 24.450000762939457 -decision_type=2 2 2 2 -left_child=1 -1 -2 -4 -right_child=2 -3 3 -5 -leaf_value=0.0056030857563018769 -0.015544263552874313 -0.070224771329334787 0.025149877386866144 0.058628469705581658 -leaf_weight=5.0000000000000018 4.0000000000000027 7 5.9999999999999982 7 -leaf_count=5 4 7 6 7 -internal_value=0.00122633 -0.0386298 0.0293601 0.0431768 -internal_weight=29 12 17 13 -internal_count=29 12 17 13 +split_feature=2 4 1 0 25 10 10 10 6 1 +split_gain=44.4197 31.7136 7.83221 2.39563 2.05664 1.5749 0.811824 0.564286 0.475409 0.345343 +threshold=6.6000001430511483 11.021428585052492 18.750000000000004 31.399999618530277 2.5000000000000004 14.84999990463257 22.649999618530277 28.000000000000004 2.0000000000000004 25.750000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 +left_child=-1 -2 4 6 8 -6 -4 9 -3 -8 +right_child=1 2 3 -5 5 -7 7 -9 -10 -11 +leaf_value=0.16528864316642286 -0.14297575429081918 -0.017503553442656992 -0.00099398629739880424 0.058873554319143297 -0.017313231155276297 -0.061682402063161126 0.021238924066225691 0.0066050773137249054 0.005622939318418502 0.040205536782741538 +leaf_weight=3.9999999999999991 3.9999999999999991 4 5.0000000000000062 3.9999999999999991 4 4 5.9999999999999982 3.9999999999999991 5 4 +leaf_count=4 4 4 5 4 4 4 6 4 5 4 +internal_value=0.00576187 -0.00874056 0.00468296 0.0237044 -0.0210519 -0.0394978 0.0163003 0.0224769 -0.0046555 0.0288256 +internal_weight=48 44 40 23 17 8 19 14 9 10 +internal_count=48 44 40 23 17 8 19 14 9 10 is_linear=0 shrinkage=0.05 Tree=51 -num_leaves=6 +num_leaves=10 num_cat=0 -split_feature=10 11 1 7 1 -split_gain=11.7643 3.67241 2.87184 11.2515 0.582723 -threshold=15.949999809265138 24.450000762939457 25.000000000000004 12.200000286102297 1.0000000180025095e-35 -decision_type=2 2 8 2 10 -left_child=2 4 -1 -4 -2 -right_child=1 -3 3 -5 -6 -leaf_value=-0.0021472714841365803 0.018332585987324509 0.055697047923292435 -0.11333190500736234 0.0052616555942222466 -0.0063048541545867908 -leaf_weight=4.0000000000000009 6.0000000000000018 6.9999999999999991 4 4 4 -leaf_count=4 6 7 4 4 4 -internal_value=0.00116501 0.0279209 -0.0367392 -0.0540351 0.00847761 -internal_weight=29 17 12 8 10 -internal_count=29 17 12 8 10 +split_feature=2 14 12 25 4 17 4 5 0 +split_gain=25.4919 24.339 9.52996 1.8803 1.82281 0.899286 0.881229 0.588173 0.415727 +threshold=6.6000001430511483 73.500000000000014 17.149999618530277 2.5000000000000004 27.028571128845218 2.1999999284744267 22.349999427795414 11.84999990463257 15.000000000000002 +decision_type=2 8 2 2 2 2 2 2 2 +left_child=-1 -2 3 8 5 -4 -7 -5 -3 +right_child=1 2 4 7 -6 6 -8 -9 -10 +leaf_value=0.12369037829339505 -0.12572371596470475 -0.017821835130453108 0.034467599689960458 -0.055082815140485755 0.04976234063506127 0.02061619559923808 -0.0096814347663894268 -0.027967933937907214 0.0038043824955821034 +leaf_weight=3.9999999999999991 4 5 5.0000000000000027 4 5.9999999999999991 5.9999999999999982 4 4 4 +leaf_count=4 4 5 5 4 6 6 4 4 4 +internal_value=0.00307947 -0.00840729 0.00394181 -0.0238879 0.0264706 0.017154 0.00849714 -0.0415254 -0.00821018 +internal_weight=46 42 38 17 21 15 10 8 9 +internal_count=46 42 38 17 21 15 10 8 9 is_linear=0 shrinkage=0.05 Tree=52 -num_leaves=6 +num_leaves=10 num_cat=0 -split_feature=0 7 4 17 11 -split_gain=10.7969 7.03751 8.34685 4.42031 2.13665 -threshold=19.000000000000004 16.19999980926514 8.7500000000000018 2.7500000000000004 24.450000762939457 -decision_type=2 2 2 2 2 -left_child=1 2 -1 -4 -2 -right_child=4 -3 3 -5 -6 -leaf_value=-0.062733021378517037 0.016320943273603918 -0.07436351835727692 -0.011439524823799728 0.062893457710742937 0.058517393966515854 -leaf_weight=4.0000000000000062 5.9999999999999982 4.9999999999999991 4 4 6 -leaf_count=4 6 5 4 4 6 -internal_value=0.00110676 -0.0245255 -0.0037597 0.025727 0.0374192 -internal_weight=29 17 12 8 12 -internal_count=29 17 12 8 12 +split_feature=1 0 3 7 0 0 11 3 9 +split_gain=41.7247 37.9248 7.73163 3.36853 1.59972 0.915443 0.368271 0.665566 0.361431 +threshold=7.5000000000000009 10.050000190734865 10.949999809265138 16.94999980926514 31.399999618530277 26.19999980926514 24.94999980926514 17.60000038146973 13.650000095367433 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=-1 -2 -3 8 5 6 7 -5 -4 +right_child=1 2 3 4 -6 -7 -8 -9 -10 +leaf_value=0.15620313957333565 -0.12942329198122024 -0.042685734480619436 -0.020173422098159796 0.019779158532619454 0.053441760689020158 0.0024352997851868471 0.015948729387794933 0.047142675518989555 -8.8460743427276608e-06 +leaf_weight=3.9999999999999991 5.9999999999999991 6.9999999999999991 4.9999999999999982 5.0000000000000053 3.9999999999999991 5.9999999999999991 5.9999999999999991 4 4 +leaf_count=4 6 7 5 5 4 6 6 4 4 +internal_value=0.00117859 -0.012015 0.00516671 0.0150187 0.0244615 0.0189415 0.0255439 0.0319407 -0.0112114 +internal_weight=51 47 41 34 25 21 15 9 9 +internal_count=51 47 41 34 25 21 15 9 9 is_linear=0 shrinkage=0.05 Tree=53 -num_leaves=7 +num_leaves=10 num_cat=0 -split_feature=10 8 1 7 25 7 -split_gain=10.114 2.78729 2.48769 10.1288 1.37513 0.370549 -threshold=15.949999809265138 24.450000762939457 25.000000000000004 12.200000286102297 2.5000000000000004 17.099999427795414 -decision_type=2 2 8 2 2 2 -left_child=2 5 -1 -4 -3 -2 -right_child=1 4 3 -5 -6 -7 -leaf_value=-0.0018985368311405177 0.018114815652370442 0.026604005694389338 -0.1064521662890911 0.0060691058868542307 0.068063744157552716 -0.0023025212436914438 -leaf_weight=4.0000000000000009 4.0000000000000018 4 4 4 4 5 -leaf_count=4 4 4 4 4 4 5 -internal_value=0.00105142 0.0258599 -0.0340939 -0.0501915 0.0473339 0.00677185 -internal_weight=29 17 12 8 8 9 -internal_count=29 17 12 8 8 9 +split_feature=17 2 1 25 7 0 17 6 6 +split_gain=37.4669 18.4585 5.40853 1.45337 1.44414 1.39694 0.633695 0.398881 0.351183 +threshold=4.7000000476837167 9.0999999046325701 18.750000000000004 2.5000000000000004 24.750000000000004 11.59999990463257 2.1999999284744267 2.0000000000000004 1.5500000119209292 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=1 -1 3 7 6 -5 -4 -3 -6 +right_child=-2 2 4 5 8 -7 -8 -9 -10 +leaf_value=-0.10172486037015915 0.13315886005759239 -0.014699266105890271 0.021758447214961039 -0.053594700992107384 0.025116630643606194 -0.016554263287356919 -0.0039339390893777205 0.0064842320978641503 0.044993260502815241 +leaf_weight=4.9999999999999991 4.9999999999999991 4 4.0000000000000018 4 4.9999999999999982 7 6 5 4 +leaf_count=5 5 4 4 4 5 7 6 5 4 +internal_value=0.00345963 -0.0112789 0.000316713 -0.0178317 0.0194203 -0.0300235 0.00634302 -0.00293066 0.0339507 +internal_weight=49 44 39 20 19 11 10 9 9 +internal_count=49 44 39 20 19 11 10 9 9 +is_linear=0 +shrinkage=0.05 + + +Tree=54 +num_leaves=10 +num_cat=0 +split_feature=6 5 5 7 0 17 4 9 25 +split_gain=27.6922 38.8409 9.42744 3.5031 1.65263 0.669616 0.626076 0.342945 0.151308 +threshold=-6.5499999523162833 9.3392858505249041 11.84999990463257 16.94999980926514 31.399999618530277 2.1999999284744267 22.349999427795414 13.650000095367433 2.5000000000000004 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=-1 -2 -3 7 5 -5 -7 -4 -8 +right_child=1 2 3 4 -6 6 8 -9 -10 +leaf_value=-0.10105030313134195 0.16203562282025816 -0.059046607340375583 -0.020551807433366778 0.02965711236000057 0.049016924202442171 0.019448967597314293 0.0058497640304267396 -0.0034067513048648829 -0.0079028930049389583 +leaf_weight=5.9999999999999991 3.9999999999999991 5.9999999999999991 6.9999999999999982 5.0000000000000062 3.9999999999999991 6.9999999999999991 4 5 4 +leaf_count=6 4 6 7 5 4 7 4 5 4 +internal_value=-2.02282e-05 0.0131576 -0.00102125 0.00864964 0.0196785 0.0138108 0.00852868 -0.013408 -0.00102656 +internal_weight=52 46 42 36 24 20 15 12 8 +internal_count=52 46 42 36 24 20 15 12 8 +is_linear=0 +shrinkage=0.05 + + +Tree=55 +num_leaves=11 +num_cat=0 +split_feature=6 5 3 0 10 9 2 25 6 5 +split_gain=30.0577 36.5495 8.72637 2.63493 1.94768 1.14346 0.542436 0.367098 0.179134 0.0362129 +threshold=-6.5499999523162833 9.3392858505249041 10.949999809265138 31.399999618530277 14.84999990463257 13.650000095367433 19.250000000000004 2.5000000000000004 2.0000000000000004 21.939285278320316 +decision_type=2 2 2 2 2 2 2 2 2 2 +left_child=-1 -2 4 5 -3 -4 8 9 -7 -8 +right_child=1 2 3 -5 -6 6 7 -9 -10 -11 +leaf_value=-0.11955666840076447 0.15393383856862786 -0.027004826371558016 -0.01616737364480891 0.046566077321767811 -0.072047393396496767 0.026308426260948165 0.012285289354622368 -0.0083491335809230804 0.011344562546582891 0.0055572756566107263 +leaf_weight=4.9999999999999991 3.9999999999999991 5.9999999999999982 6.0000000000000062 3.9999999999999991 4 4.0000000000000018 3.9999999999999982 5 4 4 +leaf_count=5 4 6 6 4 4 4 4 5 4 4 +internal_value=-0.0032554 0.00966697 -0.00440785 0.00869344 -0.0450219 0.00308268 0.00858269 0.00227881 0.0188265 0.00892128 +internal_weight=50 45 41 31 10 27 21 13 8 8 +internal_count=50 45 41 31 10 27 21 13 8 8 +is_linear=0 +shrinkage=0.05 + + +Tree=56 +num_leaves=9 +num_cat=0 +split_feature=2 5 7 7 25 3 2 17 +split_gain=23.3625 19.1962 7.61866 4.34079 1.23818 1.04596 0.740924 0.275835 +threshold=6.6000001430511483 10.114285945892336 36.350000381469734 16.94999980926514 2.5000000000000004 23.000000000000004 23.649999618530277 2.7500000000000004 +decision_type=2 2 2 2 2 2 2 2 +left_child=-1 -2 3 4 -3 6 7 -5 +right_child=1 2 -4 5 -6 -7 -8 -9 +leaf_value=0.10156402617692949 -0.1047289824485779 -0.0011890296824276451 -0.058549868315458303 0.029472361505031547 -0.032142488161722819 0.038770350317160293 -0.004345403239130974 0.014096078143588132 +leaf_weight=4.9999999999999991 4.9999999999999991 6.9999999999999982 4.9999999999999991 5.0000000000000053 6 5.9999999999999991 3.9999999999999991 7 +leaf_count=5 5 7 5 5 6 6 4 7 +internal_value=-0.000969548 -0.0123622 -0.000816316 0.00743133 -0.0154752 0.020967 0.0142908 0.0205029 +internal_weight=50 45 40 35 13 22 16 12 +internal_count=50 45 40 35 13 22 16 12 +is_linear=0 +shrinkage=0.05 + + +Tree=57 +num_leaves=9 +num_cat=0 +split_feature=6 0 5 25 17 17 4 15 +split_gain=31.6657 28.7109 3.75494 1.71113 1.7165 1.11399 0.715138 0.602462 +threshold=4.1499998569488534 10.050000190734865 21.450000762939457 2.5000000000000004 3.1499999761581425 4.7000000476837167 13.992856979370119 26.599999427795414 +decision_type=2 2 2 2 2 2 2 2 +left_child=1 -1 3 6 -5 7 -3 -4 +right_child=-2 2 5 4 -6 -7 -8 -9 +leaf_value=-0.11402166957656544 0.11003612691226106 -0.010644206195138393 0.020890073943883189 -0.037063005992344446 0.0012943863868713377 0.0036409085616469386 0.015858009244714463 0.046924082636833182 +leaf_weight=5.9999999999999991 5.9999999999999991 4 3.9999999999999982 7 5 3.9999999999999991 7 5 +leaf_count=6 6 4 4 7 5 4 7 5 +internal_value=0.0025894 -0.0127601 0.00411679 -0.00802347 -0.0210808 0.0255957 0.00622084 0.0353534 +internal_weight=48 42 36 23 12 13 11 9 +internal_count=48 42 36 23 12 13 11 9 +is_linear=0 +shrinkage=0.05 + + +Tree=58 +num_leaves=10 +num_cat=0 +split_feature=0 1 12 10 4 2 17 0 25 +split_gain=19.9498 15.0491 3.94755 1.22607 1.04886 0.840346 0.460479 0.395398 0.212162 +threshold=12.750000000000002 11.949999809265138 17.149999618530277 14.84999990463257 27.028571128845218 23.649999618530277 2.7500000000000004 15.000000000000002 2.5000000000000004 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=1 -1 3 7 5 6 -4 -2 -6 +right_child=2 -3 4 -5 8 -7 -8 -9 -10 +leaf_value=-0.018460253175968926 -0.017264856398105623 -0.13591245785355566 0.031723332405090308 -0.040052463114261617 0.024500569421797985 -0.0064870091155171394 0.008962876833975314 0.0049668337509501713 0.040785611607134335 +leaf_weight=5.9999999999999982 3.9999999999999982 5 4.0000000000000018 4 4 4.9999999999999991 5 4 4 +leaf_count=6 4 5 4 4 4 5 5 4 4 +internal_value=-0.013318 -0.0718476 0.0056181 -0.0174502 0.0182008 0.00994805 0.0190786 -0.00614901 0.0326431 +internal_weight=45 11 34 12 22 14 9 8 8 +internal_count=45 11 34 12 22 14 9 8 8 +is_linear=0 +shrinkage=0.05 + + +Tree=59 +num_leaves=10 +num_cat=0 +split_feature=6 5 3 2 0 9 3 17 25 +split_gain=23.6634 34.5955 7.03237 1.74336 1.68602 0.908173 0.883281 0.422619 0.340782 +threshold=-6.5499999523162833 9.3392858505249041 10.949999809265138 9.0999999046325701 31.399999618530277 13.650000095367433 18.750000000000004 3.5499999523162846 2.5000000000000004 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=-1 -2 3 -3 5 -4 7 -7 -8 +right_child=1 2 4 -5 -6 6 8 -9 -10 +leaf_value=-0.10469932556152345 0.1503155801910907 -0.065763427130877988 -0.013617988955229504 -0.023148915660567579 0.038320897519588476 0.027586778129140524 0.0066958625645687194 0.0066051589790731662 -0.010978512540459631 +leaf_weight=4.9999999999999991 3.9999999999999991 3.9999999999999982 6.0000000000000062 6 3.9999999999999991 6.0000000000000018 5.9999999999999982 4 5 +leaf_count=5 4 4 6 6 4 6 6 4 5 +internal_value=-0.00150764 0.0099581 -0.00373531 -0.0401947 0.00802579 0.00353763 0.00843923 0.0191941 -0.00133794 +internal_weight=50 45 41 10 31 27 21 10 11 +internal_count=50 45 41 10 31 27 21 10 11 +is_linear=0 +shrinkage=0.05 + + +Tree=60 +num_leaves=9 +num_cat=0 +split_feature=17 2 3 1 17 25 2 6 +split_gain=36.5916 13.4303 3.70556 1.23539 1.30445 0.639818 0.41242 0.37417 +threshold=4.7000000476837167 9.0999999046325701 23.000000000000004 12.84999990463257 1.6999999880790713 2.5000000000000004 19.250000000000004 2.9500000476837163 +decision_type=2 2 2 2 2 2 2 2 +left_child=1 -1 3 5 -5 -3 7 -6 +right_child=-2 2 -4 4 6 -7 -8 -9 +leaf_value=-0.074225777973021781 0.1472099009435624 -0.0070182520896196381 0.033942899267588346 -0.0201159793883562 0.024952181987464413 -0.033847257420420641 -0.0035433312878012657 0.0057821892201900477 +leaf_weight=6.9999999999999991 3.9999999999999991 3.9999999999999982 6.9999999999999991 6.0000000000000027 4.0000000000000018 5 5.9999999999999991 7 +leaf_count=7 4 4 7 6 4 5 6 7 +internal_value=0.00215757 -0.0104557 0.000990236 -0.00621816 -7.26875e-05 -0.0219233 0.00700142 0.0127531 +internal_weight=50 46 39 32 23 9 17 11 +internal_count=50 46 39 32 23 9 17 11 +is_linear=0 +shrinkage=0.05 + + +Tree=61 +num_leaves=10 +num_cat=0 +split_feature=2 14 7 5 0 25 5 6 3 +split_gain=28.9075 17.2775 3.67533 1.07556 0.912803 0.685637 0.427932 0.165385 0.217714 +threshold=6.6000001430511483 73.500000000000014 16.94999980926514 27.082142829895023 26.19999980926514 2.5000000000000004 11.84999990463257 3.6499999761581425 18.750000000000004 +decision_type=2 8 2 2 2 2 2 2 2 +left_child=-1 -2 5 4 7 -3 -7 8 -4 +right_child=1 2 3 -5 -6 6 -8 -9 -10 +leaf_value=0.13434467976912856 -0.10510580278933046 -0.0032156622569475867 0.025155607548852738 0.03419206713636716 -0.01131359599530697 -0.036269760970026252 -0.014328397959470746 0.00686189488042146 0.011028641909360884 +leaf_weight=3.9999999999999991 4 6.9999999999999973 6.0000000000000053 5.9999999999999991 3.9999999999999991 4 5 3.9999999999999991 5 +leaf_count=4 4 7 6 6 4 4 5 4 5 +internal_value=0.00553362 -0.00591625 0.00376078 0.0157369 0.00990895 -0.0149519 -0.0240801 0.0155683 0.0187343 +internal_weight=49 45 41 25 19 16 9 15 11 +internal_count=49 45 41 25 19 16 9 15 11 +is_linear=0 +shrinkage=0.05 + + +Tree=62 +num_leaves=11 +num_cat=0 +split_feature=1 0 5 10 4 17 4 12 25 0 +split_gain=22.7295 20.8216 4.69083 2.7708 1.22551 0.835868 0.507351 0.372469 0.246479 0.0572401 +threshold=7.5000000000000009 10.050000190734865 12.542857170104982 14.84999990463257 27.028571128845218 2.1999999284744267 22.349999427795414 17.149999618530277 2.5000000000000004 19.149999618530277 +decision_type=2 2 2 2 2 2 2 2 2 2 +left_child=-1 -2 3 -3 5 -4 7 -7 -6 -9 +right_child=1 2 4 -5 8 6 -8 9 -10 -11 +leaf_value=0.10003318741917611 -0.091582624561020315 -0.0023346262052655218 0.023206885010004012 -0.061186103150248521 0.019312194979283954 -0.0069315351545810649 -0.013430933132767678 0.015984458290040492 0.036864931136369704 0.0075257279677316538 +leaf_weight=4.9999999999999991 6.9999999999999991 4 5.0000000000000062 4 4 4.0000000000000027 4.9999999999999991 4 4 4 +leaf_count=5 7 4 5 4 4 4 5 4 4 4 +internal_value=-0.00110185 -0.0123391 0.00225842 -0.0317604 0.0113301 0.00523611 -4.94154e-05 0.00552622 0.0280886 0.0117551 +internal_weight=50 45 38 8 30 22 17 12 8 8 +internal_count=50 45 38 8 30 22 17 12 8 8 +is_linear=0 +shrinkage=0.05 + + +Tree=63 +num_leaves=11 +num_cat=0 +split_feature=2 0 3 7 4 17 3 3 8 0 +split_gain=20.1842 20.3188 3.79703 1.61956 1.59976 0.766983 0.790725 1.01889 0.289064 0.0771315 +threshold=6.6000001430511483 10.050000190734865 10.949999809265138 16.300000190734867 27.028571128845218 2.1999999284744267 18.750000000000004 21.550000190734867 16.099999427795414 31.399999618530277 +decision_type=2 2 2 2 2 2 2 2 2 2 +left_child=-1 -2 3 -3 5 -4 8 -8 -7 -6 +right_child=1 2 4 -5 9 6 7 -9 -10 -11 +leaf_value=0.09536244608461858 -0.11809593681246043 -0.0086565872654318799 0.015263910325510152 -0.053650512265448917 0.021774593344889577 -0.0061205462552607025 -0.033901484310626978 0.0017862435430288312 0.01072885568386742 0.031593676656484597 +leaf_weight=4.9999999999999991 3.9999999999999991 4 7.0000000000000027 4 4 4.0000000000000018 4 4 7 4 +leaf_count=5 4 4 7 4 4 4 4 4 7 4 +internal_value=-4.55024e-05 -0.0104159 -0.000160693 -0.0311535 0.00713174 0.00111562 -0.0040969 -0.0160576 0.0046018 0.0266841 +internal_weight=51 46 42 8 34 26 19 8 11 8 +internal_count=51 46 42 8 34 26 19 8 11 8 +is_linear=0 +shrinkage=0.05 + + +Tree=64 +num_leaves=10 +num_cat=0 +split_feature=1 8 12 25 3 3 0 6 17 +split_gain=18.6372 7.99511 3.52437 1.79665 1.22558 0.950261 0.685949 0.251214 0.222804 +threshold=7.5000000000000009 32.899999618530281 17.149999618530277 2.5000000000000004 23.000000000000004 10.949999809265138 26.19999980926514 -0.65000000596046437 2.7500000000000004 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=-1 2 3 7 6 -5 8 -2 -4 +right_child=1 -3 4 5 -6 -7 -8 -9 -10 +leaf_value=0.09473082081414759 -0.0091435916721820821 -0.074671152886003261 0.020569648345311463 -0.051470080763101569 0.038722724840044978 -0.017005233839154241 -0.010152041260153056 0.0076675904914736736 0.006943582937431832 +leaf_weight=4.9999999999999991 5 3.9999999999999991 6.0000000000000053 4 3.9999999999999991 4 3.9999999999999991 4 6 +leaf_count=5 5 4 6 4 4 4 4 4 6 +internal_value=0.00359528 -0.00751881 -0.000259101 -0.016997 0.0139681 -0.0342377 0.00777945 -0.00167196 0.0137566 +internal_weight=46 41 37 17 20 8 16 9 12 +internal_count=46 41 37 17 20 8 16 9 12 +is_linear=0 +shrinkage=0.05 + + +Tree=65 +num_leaves=9 +num_cat=0 +split_feature=17 2 1 25 17 12 17 9 +split_gain=17.8131 12.1971 3.33211 0.773036 1.6004 0.715874 0.376006 0.307236 +threshold=4.7000000476837167 9.0999999046325701 19.250000000000004 2.5000000000000004 3.1499999761581425 27.050000190734867 2.1999999284744267 13.650000095367433 +decision_type=2 2 2 2 2 2 2 2 +left_child=1 -1 3 7 -5 6 -4 -3 +right_child=-2 2 5 4 -6 -7 -8 -9 +leaf_value=-0.081431748494505893 0.10229314751923085 -0.01235358342528343 0.022747441940009581 -0.041719787120819085 -0.0034179383267958956 0.031988895187775299 0.0035304853226989503 0.0055360215405623114 +leaf_weight=4.9999999999999991 3.9999999999999991 4 4.0000000000000018 5 6 5.9999999999999991 7 6 +leaf_count=5 4 4 4 5 6 6 7 6 +internal_value=0.00136906 -0.00801923 0.00164031 -0.0116812 -0.0208279 0.0180963 0.0105185 -0.00161982 +internal_weight=47 43 38 21 11 17 11 10 +internal_count=47 43 38 21 11 17 11 10 +is_linear=0 +shrinkage=0.05 + + +Tree=66 +num_leaves=10 +num_cat=0 +split_feature=2 0 3 5 8 9 3 9 6 +split_gain=19.1657 15.4634 4.0574 1.91324 0.817859 0.743227 0.337767 0.444756 0.116456 +threshold=6.6000001430511483 10.050000190734865 10.949999809265138 27.082142829895023 29.19999980926514 13.650000095367433 18.750000000000004 25.150000572204593 1.2500000000000002 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=-1 -2 -3 4 5 -4 8 -8 -7 +right_child=1 2 3 -5 -6 6 7 -9 -10 +leaf_value=0.092054878138005738 -0.09403305932879448 -0.038458197129269442 -0.012510571181774122 0.034730319082736973 -0.019778876379132272 0.020476144254207601 -0.010451265145093201 0.013127229176461695 0.010144079502545839 +leaf_weight=4.9999999999999991 4.9999999999999991 5.9999999999999991 5.0000000000000062 4.9999999999999991 3.9999999999999991 5.0000000000000018 4 4 6 +leaf_count=5 5 6 5 5 4 5 4 4 6 +internal_value=-0.000708322 -0.0112496 -0.000636331 0.00624037 0.00115288 0.00464151 0.00915521 0.00133798 0.0148405 +internal_weight=49 44 39 33 28 24 19 8 11 +internal_count=49 44 39 33 28 24 19 8 11 +is_linear=0 +shrinkage=0.05 + + +Tree=67 +num_leaves=9 +num_cat=0 +split_feature=2 0 3 7 4 2 17 6 +split_gain=21.678 21.5093 2.35977 1.05122 0.476941 0.577216 0.167126 0.121161 +threshold=6.6000001430511483 11.59999990463257 10.949999809265138 16.94999980926514 27.028571128845218 23.649999618530277 3.5499999523162846 1.2500000000000002 +decision_type=2 2 2 2 2 2 2 2 +left_child=-1 -2 -3 7 5 6 -5 -4 +right_child=1 2 3 4 -6 -7 -8 -9 +leaf_value=0.11076071057468653 -0.10844894379377366 -0.024593401274510793 -0.014394238591194151 0.019627985810594883 0.023671939809407509 -0.0043101567775011068 0.0068162218783982091 -0.0020877071656286712 +leaf_weight=3.9999999999999991 4.9999999999999991 6.9999999999999991 4 7.0000000000000053 6.9999999999999991 5.9999999999999991 4 4 +leaf_count=4 5 7 4 7 7 6 4 4 +internal_value=-0.000682918 -0.0108142 0.00170312 0.00745549 0.0126876 0.0081647 0.0149692 -0.00824097 +internal_weight=48 44 39 32 24 17 11 8 +internal_count=48 44 39 32 24 17 11 8 +is_linear=0 +shrinkage=0.05 + + +Tree=68 +num_leaves=11 +num_cat=0 +split_feature=6 1 0 7 4 10 15 8 3 3 +split_gain=15.1358 2.55718 1.08712 0.451526 0.56001 0.424299 0.351377 0.199619 0.0580653 0.0513116 +threshold=-6.5499999523162833 8.4499998092651385 31.399999618530277 16.94999980926514 22.349999427795414 14.84999990463257 13.400000095367433 19.300000190734867 23.000000000000004 18.750000000000004 +decision_type=2 2 2 2 2 2 2 2 2 2 +left_child=-1 -2 3 5 7 6 -3 -5 -6 -9 +right_child=1 2 -4 4 8 -7 -8 9 -10 -11 +leaf_value=-0.09459852367639543 -0.036300248652696612 -0.010399328246712688 0.026393146440386775 0.019608330726623519 -0.010849833115935324 -0.019729004204273225 0.0094828109256923174 0.0019239610061049458 -0.0023303477093577383 0.0099326721392571909 +leaf_weight=4.9999999999999991 4.9999999999999991 4.9999999999999982 3.9999999999999991 4.0000000000000027 4 4.9999999999999991 4 4 4 4 +leaf_count=5 5 5 4 4 4 5 4 4 4 4 +internal_value=-0.0122603 -0.00268605 0.00173688 -0.00116386 0.00365696 -0.00805074 -0.00156282 0.0104883 -0.00659009 0.00592832 +internal_weight=48 43 38 34 20 14 9 12 8 8 +internal_count=48 43 38 34 20 14 9 12 8 8 +is_linear=0 +shrinkage=0.05 + + +Tree=69 +num_leaves=11 +num_cat=0 +split_feature=5 14 3 25 8 17 0 25 7 2 +split_gain=24.2602 11.8997 3.13278 1.86825 0.73681 0.614454 0.340213 0.253493 0.114456 0.106397 +threshold=9.3392858505249041 73.500000000000014 12.750000000000002 2.5000000000000004 24.750000000000004 4.2500000000000009 19.149999618530277 2.5000000000000004 22.85000038146973 30.94999980926514 +decision_type=2 8 2 2 2 2 2 2 2 2 +left_child=-1 -2 3 -3 6 9 7 -4 -8 -6 +right_child=1 2 4 -5 5 -7 8 -9 -10 -11 +leaf_value=0.10944691546261312 -0.088965256046503766 -0.0026307621970772747 0.018614224623888703 -0.044013831615447994 0.02143491059541703 0.0060341724970688418 -0.010848689591512082 0.00081347990781068793 0.0011125172721222043 0.032967305183410643 +leaf_weight=4.9999999999999991 4 5.9999999999999982 4.0000000000000053 5 3.9999999999999982 6 4 4 4 4 +leaf_count=5 4 6 4 5 4 6 4 4 4 4 +internal_value=0.004962 -0.00664744 0.00138357 -0.0214412 0.00975267 0.0181296 0.00242288 0.00971385 -0.00486809 0.0272011 +internal_weight=50 45 41 11 30 14 16 8 8 8 +internal_count=50 45 41 11 30 14 16 8 8 8 +is_linear=0 +shrinkage=0.05 + + +Tree=70 +num_leaves=9 +num_cat=0 +split_feature=1 0 4 9 8 2 1 7 +split_gain=22.4001 16.1702 1.8788 0.96099 0.353299 0.573651 0.427298 0.270691 +threshold=7.5000000000000009 10.050000190734865 13.32857131958008 13.650000095367433 24.750000000000004 18.500000000000004 25.750000000000004 31.85000038146973 +decision_type=2 2 2 2 2 2 2 2 +left_child=-1 -2 -3 -4 5 -5 -6 -8 +right_child=1 2 3 4 6 -7 7 -9 +leaf_value=0.11456910856068135 -0.078701286443642215 -0.02144033635656039 -0.010947994813323014 0.019626845158636563 0.0082968943286687146 -0.0025474789020206246 0.035145617276430126 0.016750962194055318 +leaf_weight=3.9999999999999991 6.9999999999999991 5.9999999999999991 5.0000000000000027 5.0000000000000018 5.9999999999999991 7 4 4 +leaf_count=4 7 6 5 5 6 7 4 4 +internal_value=0.00128448 -0.00901412 0.00416993 0.00912676 0.0129873 0.00669182 0.0183834 0.0259483 +internal_weight=48 44 37 31 26 12 14 8 +internal_count=48 44 37 31 26 12 14 8 +is_linear=0 +shrinkage=0.05 + + +Tree=71 +num_leaves=10 +num_cat=0 +split_feature=1 0 2 17 10 4 17 25 4 +split_gain=16.6985 14.062 1.67627 0.696996 2.00633 0.381055 0.424473 0.265983 0.264185 +threshold=7.5000000000000009 11.59999990463257 17.800000190734867 3.1499999761581425 14.84999990463257 27.028571128845218 2.7500000000000004 2.5000000000000004 22.349999427795414 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=-1 -2 3 4 -3 6 -4 -7 -8 +right_child=1 2 5 -5 -6 7 8 -9 -10 +leaf_value=0.086672998480498795 -0.07476621855582509 -0.00100899056664535 0.01802794240415095 0.0072218184592202309 -0.045399298518896097 0.010528491809964178 0.0068210128694772736 0.028762487228959795 -0.0097679286263883097 +leaf_weight=4.9999999999999991 6.9999999999999991 6.9999999999999982 5.0000000000000027 3.9999999999999991 4 4 5.9999999999999982 4 4 +leaf_count=5 7 7 5 4 4 4 6 4 4 +internal_value=-1.2268e-05 -0.00964396 0.00235224 -0.0106515 -0.0171509 0.010833 0.00613294 0.0196455 0.000185436 +internal_weight=50 45 38 15 11 23 15 8 10 +internal_count=50 45 38 15 11 23 15 8 10 +is_linear=0 +shrinkage=0.05 + + +Tree=72 +num_leaves=10 +num_cat=0 +split_feature=5 17 7 8 25 12 17 1 17 +split_gain=9.3446 6.10893 3.9223 1.24967 0.503792 0.624676 0.492101 0.286593 0.0894105 +threshold=10.114285945892336 3.5499999523162846 36.350000381469734 24.750000000000004 2.5000000000000004 17.149999618530277 4.2500000000000009 16.250000000000004 2.4500000476837163 +decision_type=2 2 2 2 2 2 2 2 2 +left_child=1 -1 3 4 7 -6 8 -2 -5 +right_child=2 -3 -4 6 5 -7 -8 -9 -10 +leaf_value=-0.10529112871736286 0.011787899707754452 -0.017905994877219198 -0.043826798014342784 0.018469447921961549 -0.021801466246445973 0.0010143940647443135 0.0022764256689697501 -0.0044204517186153675 0.029041258618235585 +leaf_weight=4 6 4 4.9999999999999991 3.9999999999999982 6 6 4 5 4 +leaf_count=4 6 4 5 4 6 6 4 5 4 +internal_value=-0.0122682 -0.0615986 -0.00240207 0.00351575 -0.00330858 -0.0103935 0.0165957 0.00442047 0.0237554 +internal_weight=48 8 40 35 23 12 12 11 8 +internal_count=48 8 40 35 23 12 12 11 8 +is_linear=0 +shrinkage=0.05 + + +Tree=73 +num_leaves=11 +num_cat=0 +split_feature=6 0 2 5 1 17 0 17 12 4 +split_gain=16.2815 10.6055 3.33677 1.08116 1.0485 0.706907 0.358389 0.320935 0.253859 0.185781 +threshold=4.1499998569488534 10.050000190734865 6.6000001430511483 21.450000762939457 11.949999809265138 1.4499999880790713 19.149999618530277 4.7000000476837167 27.050000190734867 13.992856979370119 +decision_type=2 2 2 2 2 2 2 2 2 2 +left_child=1 2 -1 4 -3 -6 9 8 -5 -7 +right_child=-2 3 -4 7 5 6 -8 -9 -10 -11 +leaf_value=-0.029915834730491038 0.077667161899929255 -0.028343342756852481 -0.094498871080577362 0.010689748972654345 -0.018854500586166962 0.0035308680807550753 -0.0080143572296947248 0.0011790601129177958 0.027589195594191547 0.016580754891037938 +leaf_weight=4 5.9999999999999991 4.0000000000000027 4 4.9999999999999982 4.0000000000000027 5.9999999999999982 3.9999999999999991 3.9999999999999991 4 5 +leaf_count=4 6 4 4 5 4 6 4 4 4 5 +internal_value=0.000402122 -0.010134 -0.0622074 0.00143783 -0.00507651 -0.000178234 0.0048021 0.0129632 0.0182006 0.00946263 +internal_weight=50 44 8 36 23 19 15 13 9 11 +internal_count=50 44 8 36 23 19 15 13 9 11 +is_linear=0 +shrinkage=0.05 + + +Tree=74 +num_leaves=12 +num_cat=0 +split_feature=2 0 2 3 25 17 2 3 3 0 17 +split_gain=19.9083 9.24093 2.00627 0.906871 0.538062 0.389537 0.764903 0.264204 0.296006 0.123535 0.110182 +threshold=6.6000001430511483 10.050000190734865 11.050000190734865 23.000000000000004 2.5000000000000004 3.1499999761581425 18.500000000000004 16.35000038146973 18.750000000000004 16.599999427795414 4.2500000000000009 +decision_type=2 2 2 2 2 2 2 2 2 2 2 +left_child=-1 -2 -3 4 7 6 -6 9 -9 -4 -5 +right_child=1 2 3 10 5 -7 -8 8 -10 -11 -12 +leaf_value=0.10757777839899063 -0.072860270133242019 -0.034766422252869238 -0.0085607353597879406 0.019685196628173196 -0.03168413508683441 0.0033655822277069095 -0.0023495866358280177 0.020120526989921922 0.00088494652882218348 0.0038658092729747292 0.0089719646421144699 +leaf_weight=3.9999999999999991 4.9999999999999991 3.9999999999999991 4 5.9999999999999982 4.0000000000000018 3.9999999999999991 5 4 4 4 4 +leaf_count=4 5 4 4 6 4 4 5 4 4 4 4 +internal_value=0.000406984 -0.00852392 -0.00104294 0.00241587 -0.00206138 -0.00961709 -0.0153872 0.00407764 0.0105027 -0.00234746 0.0153999 +internal_weight=52 48 43 39 29 13 9 16 8 8 10 +internal_count=52 48 43 39 29 13 9 16 8 8 10 is_linear=0 shrinkage=0.05 @@ -1038,16 +1437,24 @@ shrinkage=0.05 end of trees feature_importances: -actual_high_lag_1=52 -ecmwf=46 -open_meteo=28 -jma=27 -model_spread=24 -weekday=20 -gem=11 -actual_high_mean_7=10 -actual_high_lag_2=9 -gfs=4 +actual_high_lag_1=80 +model_spread=77 +open_meteo=68 +actual_high_lag_2=60 +actual_high_lag_3=51 +actual_high_trend_3=44 +weekday=39 +actual_high_lag_7=38 +ecmwf=38 +icon=35 +actual_high_mean_14=34 +actual_high_mean_7=32 +gem=25 +jma=19 +gfs=13 +nws=11 +deb_prediction=11 +model_median=1 parameters: [boosting: gbdt] @@ -1058,7 +1465,7 @@ parameters: [data_sample_strategy: bagging] [data: ] [valid: ] -[num_iterations: 54] +[num_iterations: 75] [learning_rate: 0.05] [num_leaves: 15] [num_threads: 0] diff --git a/artifacts/models/lgbm_daily_high_schema.json b/artifacts/models/lgbm_daily_high_schema.json index 0349e472..59ead30a 100644 --- a/artifacts/models/lgbm_daily_high_schema.json +++ b/artifacts/models/lgbm_daily_high_schema.json @@ -42,25 +42,25 @@ "nws" ], "model_path": "artifacts\\models\\lgbm_daily_high.txt", - "sample_count": 29, - "train_count": 17, + "sample_count": 54, + "train_count": 42, "validation_count": 12, "metrics": { "validation": { "sample_count": 12, - "lgbm_mae": 2.975, - "deb_mae": 2.267, - "best_single_mae": 1.167, - "median_mae": 2.483 + "lgbm_mae": 1.349, + "deb_mae": 0.875, + "best_single_mae": 0.325, + "median_mae": 0.758 }, "full_sample": { - "sample_count": 29, - "lgbm_mae": 1.442, - "deb_mae": 3.448, - "best_single_mae": 2.486, - "median_mae": 3.628 + "sample_count": 54, + "lgbm_mae": 0.691, + "deb_mae": 6.287, + "best_single_mae": 5.431, + "median_mae": 6.265 } }, - "generated_at": "2026-03-29T14:57:35.832414Z", - "trained_at": "2026-03-29T14:57:35.832414Z" + "generated_at": "2026-04-02T16:27:44.816882Z", + "trained_at": "2026-04-02T16:27:44.816882Z" } \ No newline at end of file diff --git a/artifacts/probability_calibration/default.json b/artifacts/probability_calibration/default.json index 390b241d..4ee955e5 100644 --- a/artifacts/probability_calibration/default.json +++ b/artifacts/probability_calibration/default.json @@ -1,20 +1,20 @@ { - "version": "emos-20260402151301", - "trained_at": "2026-04-02T15:13:01.073253+00:00", + "version": "emos-20260402162744", + "trained_at": "2026-04-02T16:27:44.114836+00:00", "global": { "mu": { - "intercept": 1.52725527, - "raw_mu_coef": 2.96318139, - "deb_coef": -1.54490032, - "ens_median_coef": -0.71820793, - "max_so_far_gap_coef": 10.22802132 + "intercept": 1.54512641, + "raw_mu_coef": 2.96105052, + "deb_coef": -1.53260815, + "ens_median_coef": -0.72849343, + "max_so_far_gap_coef": 9.52557689 }, "sigma": { - "intercept": 0.67227661, - "raw_sigma_coef": 0.69444674, - "spread_coef": 0.09134583, - "peak_flag_coef": -0.58201761, - "max_so_far_gap_coef": -0.81482466 + "intercept": 0.67432479, + "raw_sigma_coef": 0.6936692, + "spread_coef": 0.08877484, + "peak_flag_coef": -0.58374835, + "max_so_far_gap_coef": -0.8172477 } }, "sigma_constraints": { @@ -30,60 +30,60 @@ }, "blending": { "alpha_mu": 0.0, - "alpha_sigma": 0.45 + "alpha_sigma": 0.05 }, "cities": { "ankara": { "samples": 3, - "mu_bias": 1.283918, - "sigma_scale": 1.484056, + "mu_bias": 1.271844, + "sigma_scale": 1.477644, "confidence": 0.375 }, "hong kong": { "samples": 4, - "mu_bias": 1.329042, - "sigma_scale": 1.045379, + "mu_bias": 1.32008, + "sigma_scale": 1.04023, "confidence": 0.5 }, "milan": { "samples": 3, - "mu_bias": -3.932299, + "mu_bias": -3.935178, "sigma_scale": 2.0, "confidence": 0.375 }, "shanghai": { "samples": 3, - "mu_bias": 1.827618, + "mu_bias": 1.810495, "sigma_scale": 2.0, "confidence": 0.375 }, "taipei": { "samples": 3, - "mu_bias": 3.387896, + "mu_bias": 3.577828, "sigma_scale": 2.0, "confidence": 0.375 }, "warsaw": { "samples": 3, - "mu_bias": -0.614031, - "sigma_scale": 1.252706, + "mu_bias": -0.625333, + "sigma_scale": 1.25968, "confidence": 0.375 } }, "metrics": { "sample_count": 54, - "mean_crps": 3.78558, - "legacy_mean_crps": 4.326547, - "legacy_mean_mae": 4.528889, - "legacy_bucket_hit_rate": 0.518519, - "legacy_bucket_brier": 0.83496, - "selected_mean_crps": 4.239815, - "selected_mean_mae": 4.528889, - "selected_bucket_hit_rate": 0.537037, - "selected_bucket_brier": 0.853485, - "selected_score": 6.027262, - "legacy_score": 6.127879, - "filled_actual_from_history": 2, + "mean_crps": 3.792563, + "legacy_mean_crps": 4.308029, + "legacy_mean_mae": 4.51037, + "legacy_bucket_hit_rate": 0.537037, + "legacy_bucket_brier": 0.833294, + "selected_mean_crps": 4.249828, + "selected_mean_mae": 4.51037, + "selected_bucket_hit_rate": 0.555556, + "selected_bucket_brier": 0.831872, + "selected_score": 5.991436, + "legacy_score": 6.078481, + "filled_actual_from_history": 0, "settlement_history_city_count": 30 }, "source": "artifacts\\probability_calibration\\default.json" diff --git a/artifacts/probability_calibration/evaluation_report.json b/artifacts/probability_calibration/evaluation_report.json index faea812f..fe114bbb 100644 --- a/artifacts/probability_calibration/evaluation_report.json +++ b/artifacts/probability_calibration/evaluation_report.json @@ -3,17 +3,17 @@ "sample_count": 54, "filled_actual_from_history": 2, "legacy": { - "mean_crps": 4.287017, - "mean_mae": 4.488148, + "mean_crps": 4.300621, + "mean_mae": 4.502963, "bucket_hit_rate": 0.537037 }, "emos": { - "mean_crps": 4.201116, - "mean_mae": 4.488148, + "mean_crps": 4.213889, + "mean_mae": 4.502963, "bucket_hit_rate": 0.537037 }, "delta": { - "crps": -0.085901, + "crps": -0.086732, "mae": 0.0, "bucket_hit_rate": 0.0 } @@ -183,12 +183,12 @@ }, "shenzhen": { "samples": 1, - "legacy_mean_crps": 0.063696, - "emos_mean_crps": 0.073016, - "legacy_mean_mae": 0.1, - "emos_mean_mae": 0.1, - "legacy_bucket_hit_rate": 1.0, - "emos_bucket_hit_rate": 1.0 + "legacy_mean_crps": 0.798351, + "emos_mean_crps": 0.762787, + "legacy_mean_mae": 0.9, + "emos_mean_mae": 0.9, + "legacy_bucket_hit_rate": 0.0, + "emos_bucket_hit_rate": 0.0 }, "singapore": { "samples": 2, @@ -205,8 +205,8 @@ "emos_mean_crps": 0.472738, "legacy_mean_mae": 0.1, "emos_mean_mae": 0.1, - "legacy_bucket_hit_rate": 0.666667, - "emos_bucket_hit_rate": 0.666667 + "legacy_bucket_hit_rate": 1.0, + "emos_bucket_hit_rate": 1.0 }, "tel aviv": { "samples": 2, diff --git a/artifacts/probability_calibration/rollout_report.json b/artifacts/probability_calibration/rollout_report.json index 8dcb6a73..3eafc829 100644 --- a/artifacts/probability_calibration/rollout_report.json +++ b/artifacts/probability_calibration/rollout_report.json @@ -17,55 +17,57 @@ "max_delta_bucket_brier_observe": 0.15 }, "evaluation": { - "sample_count": 105, - "delta_crps": -0.093663, + "sample_count": 54, + "delta_crps": -0.086732, "delta_mae": 0.0, "delta_bucket_hit_rate": 0.0 }, "shadow": { - "sample_count": 103, - "delta_mae": 0.012708, - "delta_bucket_hit_rate": 0.009709, - "delta_bucket_brier": 0.293835 + "sample_count": 48, + "delta_mae": 0.0, + "delta_bucket_hit_rate": 0.041666, + "delta_bucket_brier": 0.123252 }, "blocking_reasons": [ - "shadow bucket brier 退化超限:delta=0.293835" + "离线评估样本不足:54 < 80", + "shadow 样本不足:48 < 50", + "shadow bucket brier 退化超限:delta=0.123252" ], "worst_shadow_regressions": [ { "city": "dallas", - "samples": 4, - "delta_mae": 0.114807, + "samples": 1, + "delta_mae": 0.0, "delta_bucket_hit_rate": 0.0, - "delta_bucket_brier": 0.778678 + "delta_bucket_brier": 0.792585 }, { "city": "chicago", - "samples": 4, - "delta_mae": 0.075265, + "samples": 1, + "delta_mae": 0.0, "delta_bucket_hit_rate": 0.0, - "delta_bucket_brier": 0.746156 + "delta_bucket_brier": 0.791878 }, { "city": "seattle", - "samples": 4, - "delta_mae": 0.11262, + "samples": 1, + "delta_mae": 0.0, "delta_bucket_hit_rate": 0.0, - "delta_bucket_brier": 0.692003 + "delta_bucket_brier": 0.61609 }, { - "city": "atlanta", - "samples": 4, - "delta_mae": 0.293028, - "delta_bucket_hit_rate": -0.25, - "delta_bucket_brier": 0.601425 + "city": "wellington", + "samples": 2, + "delta_mae": 0.0, + "delta_bucket_hit_rate": 0.0, + "delta_bucket_brier": 0.509203 }, { - "city": "miami", - "samples": 4, - "delta_mae": 0.241559, - "delta_bucket_hit_rate": -0.5, - "delta_bucket_brier": 0.478245 + "city": "tel aviv", + "samples": 2, + "delta_mae": 0.0, + "delta_bucket_hit_rate": 0.0, + "delta_bucket_brier": 0.439879 } ] } diff --git a/artifacts/probability_calibration/shadow_report.json b/artifacts/probability_calibration/shadow_report.json index f1a1f574..485edd41 100644 --- a/artifacts/probability_calibration/shadow_report.json +++ b/artifacts/probability_calibration/shadow_report.json @@ -1,16 +1,16 @@ { - "generated_at": "2026-04-02T15:13:02.010825Z", + "generated_at": "2026-04-02T16:23:24.376528Z", "summary": { "samples": 48, "legacy_mean_mae": 3.04125, "shadow_mean_mae": 3.04125, - "legacy_bucket_hit_rate": 0.479167, - "shadow_bucket_hit_rate": 0.520833, - "legacy_bucket_brier": 0.688077, - "shadow_bucket_brier": 0.811329, + "legacy_bucket_hit_rate": 0.5, + "shadow_bucket_hit_rate": 0.5, + "legacy_bucket_brier": 0.68666, + "shadow_bucket_brier": 0.814079, "delta_mae": 0.0, - "delta_bucket_hit_rate": 0.041666, - "delta_bucket_brier": 0.123252 + "delta_bucket_hit_rate": 0.0, + "delta_bucket_brier": 0.127419 }, "by_city": { "ankara": { @@ -245,13 +245,13 @@ "samples": 3, "legacy_mean_mae": 0.1, "shadow_mean_mae": 0.1, - "legacy_bucket_hit_rate": 0.666667, - "shadow_bucket_hit_rate": 0.666667, - "legacy_bucket_brier": 0.217927, - "shadow_bucket_brier": 0.640998, + "legacy_bucket_hit_rate": 1.0, + "shadow_bucket_hit_rate": 0.333333, + "legacy_bucket_brier": 0.195261, + "shadow_bucket_brier": 0.684998, "delta_mae": 0.0, - "delta_bucket_hit_rate": 0.0, - "delta_bucket_brier": 0.423071 + "delta_bucket_hit_rate": -0.666667, + "delta_bucket_brier": 0.489737 }, "tel aviv": { "samples": 2, @@ -343,13 +343,13 @@ "samples": 19, "legacy_mean_mae": 4.299474, "shadow_mean_mae": 4.299474, - "legacy_bucket_hit_rate": 0.315789, - "shadow_bucket_hit_rate": 0.473684, - "legacy_bucket_brier": 0.85177, - "shadow_bucket_brier": 0.928731, + "legacy_bucket_hit_rate": 0.368421, + "shadow_bucket_hit_rate": 0.421053, + "legacy_bucket_brier": 0.848191, + "shadow_bucket_brier": 0.935678, "delta_mae": 0.0, - "delta_bucket_hit_rate": 0.157895, - "delta_bucket_brier": 0.076961 + "delta_bucket_hit_rate": 0.052632, + "delta_bucket_brier": 0.087487 } }, "recent_observations": [ @@ -416,8 +416,8 @@ { "city": "taipei", "date": "2026-03-19", - "actual_high": 21.4, - "actual_bucket": 21, + "actual_high": 22.0, + "actual_bucket": 22, "legacy_mu": 21.7, "shadow_mu": 21.7, "legacy_top_bucket": 22, diff --git a/artifacts/probability_calibration/training_samples.json b/artifacts/probability_calibration/training_samples.json index b2fe4ac9..4dd50c76 100644 --- a/artifacts/probability_calibration/training_samples.json +++ b/artifacts/probability_calibration/training_samples.json @@ -8,7 +8,7 @@ "city": "shenzhen", "date": "2026-03-25", "timestamp": "2026-03-25T08:57:11.783182+00:00", - "actual_high": 29.0, + "actual_high": 28.0, "raw_mu": 26.7, "raw_sigma": 0.18016764322916676, "deb_prediction": 28.1, @@ -16,7 +16,12 @@ "ensemble_spread": 0.5078125000000002, "max_so_far_gap": 1.4000000000000021, "peak_flag": 1.0, - "sample_source": "snapshot" + "sample_source": "snapshot", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "ankara", @@ -29,7 +34,12 @@ "ensemble_spread": 1.2000000000000002, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "ankara", @@ -42,7 +52,12 @@ "ensemble_spread": 2.05, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "ankara", @@ -55,7 +70,12 @@ "ensemble_spread": 0.9000000000000004, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "atlanta", @@ -68,7 +88,12 @@ "ensemble_spread": 4.0, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "atlanta", @@ -81,7 +106,12 @@ "ensemble_spread": 1.5500000000000007, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "buenos aires", @@ -94,7 +124,12 @@ "ensemble_spread": 2.0, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "buenos aires", @@ -107,7 +142,12 @@ "ensemble_spread": 2.0999999999999996, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "chicago", @@ -120,7 +160,12 @@ "ensemble_spread": 5.350000000000001, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "dallas", @@ -133,7 +178,12 @@ "ensemble_spread": 9.299999999999997, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "hong kong", @@ -146,7 +196,12 @@ "ensemble_spread": 1.9000000000000004, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "hong kong", @@ -159,7 +214,12 @@ "ensemble_spread": 0.6, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "hong kong", @@ -172,7 +232,12 @@ "ensemble_spread": 0.6, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "hong kong", @@ -185,7 +250,12 @@ "ensemble_spread": 1.6999999999999993, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "london", @@ -198,7 +268,12 @@ "ensemble_spread": 1.299999999999999, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "london", @@ -211,7 +286,12 @@ "ensemble_spread": 0.6, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "lucknow", @@ -224,7 +304,12 @@ "ensemble_spread": 1.3000000000000007, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "lucknow", @@ -237,7 +322,12 @@ "ensemble_spread": 1.75, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "madrid", @@ -250,7 +340,12 @@ "ensemble_spread": 1.8499999999999996, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "madrid", @@ -263,7 +358,12 @@ "ensemble_spread": 1.8999999999999995, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "miami", @@ -276,7 +376,12 @@ "ensemble_spread": 2.8999999999999986, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "miami", @@ -289,7 +394,12 @@ "ensemble_spread": 2.5500000000000043, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "milan", @@ -302,7 +412,12 @@ "ensemble_spread": 9.1, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "milan", @@ -315,7 +430,12 @@ "ensemble_spread": 0.6, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "milan", @@ -328,7 +448,12 @@ "ensemble_spread": 1.25, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "munich", @@ -341,7 +466,12 @@ "ensemble_spread": 0.6, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "munich", @@ -354,7 +484,12 @@ "ensemble_spread": 1.3000000000000007, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "new york", @@ -367,7 +502,12 @@ "ensemble_spread": 2.25, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "paris", @@ -380,7 +520,12 @@ "ensemble_spread": 0.9000000000000004, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "paris", @@ -393,7 +538,12 @@ "ensemble_spread": 0.6, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "sao paulo", @@ -406,7 +556,12 @@ "ensemble_spread": 2.450000000000001, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "sao paulo", @@ -419,7 +574,12 @@ "ensemble_spread": 1.200000000000001, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "seattle", @@ -432,7 +592,12 @@ "ensemble_spread": 1.3500000000000014, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "seoul", @@ -445,7 +610,12 @@ "ensemble_spread": 0.7999999999999998, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "seoul", @@ -458,7 +628,12 @@ "ensemble_spread": 1.7999999999999998, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "shanghai", @@ -471,7 +646,12 @@ "ensemble_spread": 1.1500000000000004, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "shanghai", @@ -484,7 +664,12 @@ "ensemble_spread": 0.7999999999999998, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "shanghai", @@ -497,7 +682,12 @@ "ensemble_spread": 1.6999999999999993, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "singapore", @@ -510,7 +700,12 @@ "ensemble_spread": 0.9500000000000011, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "singapore", @@ -523,7 +718,12 @@ "ensemble_spread": 1.3499999999999996, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "taipei", @@ -536,7 +736,12 @@ "ensemble_spread": 2.25, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "taipei", @@ -549,12 +754,17 @@ "ensemble_spread": 1.0500000000000007, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "taipei", "date": "2026-03-19", - "actual_high": 21.4, + "actual_high": 22.0, "raw_mu": 21.7, "raw_sigma": 1.1500000000000004, "deb_prediction": 21.5, @@ -562,7 +772,12 @@ "ensemble_spread": 1.1500000000000004, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "tel aviv", @@ -575,7 +790,12 @@ "ensemble_spread": 2.1500000000000004, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "tel aviv", @@ -588,7 +808,12 @@ "ensemble_spread": 1.5, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "tokyo", @@ -601,7 +826,12 @@ "ensemble_spread": 1.5499999999999998, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "tokyo", @@ -614,7 +844,12 @@ "ensemble_spread": 2.0999999999999996, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "toronto", @@ -627,7 +862,12 @@ "ensemble_spread": 0.8, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "toronto", @@ -640,7 +880,12 @@ "ensemble_spread": 2.1500000000000004, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "warsaw", @@ -653,7 +898,12 @@ "ensemble_spread": 0.6499999999999995, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "warsaw", @@ -666,7 +916,12 @@ "ensemble_spread": 1.4000000000000004, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "warsaw", @@ -679,7 +934,12 @@ "ensemble_spread": 1.5999999999999996, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "wellington", @@ -692,7 +952,12 @@ "ensemble_spread": 0.9500000000000011, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null }, { "city": "wellington", @@ -705,7 +970,12 @@ "ensemble_spread": 2.0999999999999996, "max_so_far_gap": null, "peak_flag": 0.0, - "sample_source": "daily_record" + "sample_source": "daily_record", + "settlement_source": null, + "settlement_station_code": null, + "truth_version": null, + "truth_updated_by": null, + "truth_updated_at": null } ] } \ No newline at end of file diff --git a/frontend/components/ops/OpsDashboard.tsx b/frontend/components/ops/OpsDashboard.tsx index 414ee238..98f199e4 100644 --- a/frontend/components/ops/OpsDashboard.tsx +++ b/frontend/components/ops/OpsDashboard.tsx @@ -31,6 +31,96 @@ type SystemStatusPayload = { }; }; integrations?: Record; + training_data?: { + db_ok?: boolean; + db_path?: string; + error?: string; + truth_records?: { + row_count?: number; + cities_count?: number; + min_date?: string | null; + max_date?: string | null; + source_counts?: Record; + }; + truth_revisions?: { + row_count?: number; + last_updated_at?: number | null; + }; + training_features?: { + row_count?: number; + cities_count?: number; + min_date?: string | null; + max_date?: string | null; + }; + city_coverage?: { + total_cities?: number; + with_truth_rows?: number; + with_feature_rows?: number; + highlighted?: Array<{ + city: string; + name?: string; + settlement_source?: string; + settlement_station_code?: string; + truth_rows?: number; + feature_rows?: number; + truth_min_date?: string | null; + truth_max_date?: string | null; + feature_min_date?: string | null; + feature_max_date?: string | null; + }>; + top_gaps?: Array<{ + city: string; + name?: string; + settlement_source?: string; + settlement_station_code?: string; + truth_rows?: number; + feature_rows?: number; + truth_min_date?: string | null; + truth_max_date?: string | null; + feature_min_date?: string | null; + feature_max_date?: string | null; + }>; + }; + model_city_coverage?: { + cities_with_emos_training?: number; + cities_with_lgbm_candidates?: number; + weakest?: Array<{ + city: string; + name?: string; + settlement_source?: string; + truth_rows?: number; + feature_rows?: number; + emos_training_samples?: number; + emos_snapshot_samples?: number; + emos_evaluation_samples?: number; + lgbm_candidate_rows?: number; + }>; + strongest?: Array<{ + city: string; + name?: string; + settlement_source?: string; + truth_rows?: number; + feature_rows?: number; + emos_training_samples?: number; + emos_snapshot_samples?: number; + emos_evaluation_samples?: number; + lgbm_candidate_rows?: number; + }>; + }; + artifacts?: { + emos_training_samples?: number; + emos_snapshot_samples?: number; + emos_daily_record_samples?: number; + emos_evaluation_samples?: number; + emos_shadow_samples?: number; + emos_delta_crps?: number | null; + lgbm_sample_count?: number; + lgbm_train_count?: number; + lgbm_validation_count?: number; + lgbm_validation_mae?: number | null; + lgbm_validation_deb_mae?: number | null; + }; + }; }; type PaymentRuntimePayload = { @@ -140,6 +230,16 @@ function formatDateTime(value?: string | null) { return date.toLocaleString("zh-CN", { hour12: false }); } +function formatUnixDateTime(value?: number | null) { + if (!value) return "-"; + return formatDateTime(new Date(value * 1000).toISOString()); +} + +function formatMetric(value?: number | null, digits = 3) { + if (value === null || value === undefined || Number.isNaN(value)) return "-"; + return Number(value).toFixed(digits); +} + function maskUrl(value?: string | null) { if (!value) return "-"; if (value.length <= 40) return value; @@ -313,6 +413,36 @@ export function OpsDashboard() { }, [incidentReasonFilter, load, loadPaymentIncidents]); const rolloutDecision = status?.probability?.rollout?.decision; + const trainingData = status?.training_data; + const truthRecords = trainingData?.truth_records; + const truthRevisions = trainingData?.truth_revisions; + const trainingFeatures = trainingData?.training_features; + const cityCoverage = trainingData?.city_coverage; + const modelCityCoverage = trainingData?.model_city_coverage; + const trainingArtifacts = trainingData?.artifacts; + const truthSources = Object.entries(truthRecords?.source_counts || {}); + const cityCoverageRows = useMemo(() => { + const modelIndex = new Map( + [...(modelCityCoverage?.strongest || []), ...(modelCityCoverage?.weakest || [])].map((entry) => [entry.city, entry]), + ); + const rows: Array> = []; + for (const entry of [...(cityCoverage?.top_gaps || []), ...(cityCoverage?.highlighted || [])]) { + if (!entry || rows.some((row) => row.city === entry.city)) continue; + const modelEntry = modelIndex.get(entry.city); + rows.push({ + ...entry, + emos_training_samples: modelEntry?.emos_training_samples ?? 0, + emos_evaluation_samples: modelEntry?.emos_evaluation_samples ?? 0, + lgbm_candidate_rows: modelEntry?.lgbm_candidate_rows ?? entry.feature_rows ?? 0, + }); + } + rows.sort((a, b) => { + const aScore = Number(a.truth_rows || 0) + Number(a.feature_rows || 0) + Number(a.emos_training_samples || 0); + const bScore = Number(b.truth_rows || 0) + Number(b.feature_rows || 0) + Number(b.emos_training_samples || 0); + return aScore - bScore || String(a.city || "").localeCompare(String(b.city || "")); + }); + return rows; + }, [cityCoverage?.highlighted, cityCoverage?.top_gaps, modelCityCoverage?.strongest, modelCityCoverage?.weakest]); const funnelSteps = useMemo(() => { const events = analyticsFunnel?.events || {}; @@ -682,6 +812,307 @@ export function OpsDashboard() { +
+ + + 历史真值主表 + 永久监督真值,不再受 14 天运行态缓存裁剪影响。 + + +
+ + + + +
+
+
来源分布
+
+ {truthSources.length ? ( + truthSources.map(([source, count]) => ( + + {source}: {count} + + )) + ) : ( + 暂无来源统计 + )} +
+
+
+
+ revision rows + {truthRevisions?.row_count ?? 0} +
+
+ last revision + {formatUnixDateTime(truthRevisions?.last_updated_at)} +
+
+
+
+ + + + 长期训练特征 + 概率快照与训练特征长期归档,避免未来样本继续被裁掉。 + + +
+ + + + +
+
+ 当前这张表代表“从现在开始不再继续丢训练特征”。如果历史样本仍偏少,通常说明旧日期本身没有被长期归档,而不是现在的写入链坏了。 +
+
+ DB: {trainingData?.db_ok ? "ok" : "error"} · {trainingData?.db_path || "-"} + {trainingData?.error ?
{trainingData.error}
: null} +
+
+
+ + + + 模型样本状态 + 直接看当前 EMOS / LGBM 可用样本与最新验证结果。 + + +
+
EMOS
+
+
training samples{trainingArtifacts?.emos_training_samples ?? 0}
+
evaluation samples{trainingArtifacts?.emos_evaluation_samples ?? 0}
+
shadow samples{trainingArtifacts?.emos_shadow_samples ?? 0}
+
delta CRPS{formatMetric(trainingArtifacts?.emos_delta_crps)}
+
+
+
+
LGBM
+
+
sample count{trainingArtifacts?.lgbm_sample_count ?? 0}
+
train / val{trainingArtifacts?.lgbm_train_count ?? 0} / {trainingArtifacts?.lgbm_validation_count ?? 0}
+
val mae{formatMetric(trainingArtifacts?.lgbm_validation_mae)}
+
DEB val mae{formatMetric(trainingArtifacts?.lgbm_validation_deb_mae)}
+
+
+
+
+
+ +
+ + + Wunderground 回填观察 + 先盯已经切到 WU 结算的关键城市,确认真值和特征都在持续积累。 + + +
+ truth cities {cityCoverage?.with_truth_rows ?? 0}/{cityCoverage?.total_cities ?? 0} + feature cities {cityCoverage?.with_feature_rows ?? 0}/{cityCoverage?.total_cities ?? 0} +
+ {(cityCoverage?.highlighted || []).map((entry) => ( +
+
+
{entry.name || entry.city}
+ {entry.settlement_source || "-"} +
+
+ + + + +
+
+ ))} + {!(cityCoverage?.highlighted || []).length ? ( +
暂无 Wunderground 重点城市状态。
+ ) : null} +
+
+ + + + 城市覆盖缺口 + 按 truth / feature 覆盖从弱到强排序,优先看哪些城市还没形成长期训练样本。 + + + {(cityCoverage?.top_gaps || []).length ? ( +
+ {(cityCoverage?.top_gaps || []).map((entry) => ( +
+
+
+
{entry.name || entry.city}
+
+ {entry.city} · {entry.settlement_source || "-"} · {entry.settlement_station_code || "-"} +
+
+
+ truth {entry.truth_rows ?? 0} + feature {entry.feature_rows ?? 0} +
+
+
+
truth range: {entry.truth_min_date || "-"} → {entry.truth_max_date || "-"}
+
feature range: {entry.feature_min_date || "-"} → {entry.feature_max_date || "-"}
+
+
+ ))} +
+ ) : ( +
当前无城市覆盖缺口摘要。
+ )} +
+
+
+ +
+ + + 模型城市覆盖 + 直接看当前哪些城市已经开始积累 EMOS 训练样本,哪些城市只有真值没有特征。 + + +
+ EMOS cities {modelCityCoverage?.cities_with_emos_training ?? 0} + LGBM candidate cities {modelCityCoverage?.cities_with_lgbm_candidates ?? 0} +
+
+ {(modelCityCoverage?.strongest || []).map((entry) => ( +
+
+
+
{entry.name || entry.city}
+
{entry.city} · {entry.settlement_source || "-"}
+
+
+ EMOS {entry.emos_training_samples ?? 0} + LGBM {entry.lgbm_candidate_rows ?? 0} +
+
+
+ ))} + {!(modelCityCoverage?.strongest || []).length ? ( +
暂无模型覆盖摘要。
+ ) : null} +
+
+
+ + + + 模型样本缺口 + 优先处理长期真值有了但 EMOS/LGBM 样本仍然很薄的城市。 + + + {(modelCityCoverage?.weakest || []).length ? ( +
+ {(modelCityCoverage?.weakest || []).map((entry) => ( +
+
+
+
{entry.name || entry.city}
+
{entry.city} · {entry.settlement_source || "-"}
+
+
+ truth {entry.truth_rows ?? 0} + EMOS {entry.emos_training_samples ?? 0} + LGBM {entry.lgbm_candidate_rows ?? 0} +
+
+
+
feature rows: {entry.feature_rows ?? 0}
+
snapshot rows: {entry.emos_snapshot_samples ?? 0}
+
eval rows: {entry.emos_evaluation_samples ?? 0}
+
+
+ ))} +
+ ) : ( +
当前无模型样本缺口摘要。
+ )} +
+
+
+ + + + 城市覆盖矩阵 + 把 truth / feature / EMOS / LGBM 放到一张表里,快速判断哪些城市还只能靠 DEB。 + + +
+ {cityCoverageRows.map((entry) => ( +
+
+
+
{String(entry.name || entry.city || "-")}
+
+ {String(entry.city || "-")} · {String(entry.settlement_source || "-")} · {String(entry.settlement_station_code || "-")} +
+
+
+
+ + + + +
+
+ ))} +
+
+ + + + + + + + + + + + + + {cityCoverageRows.map((entry) => ( + + + + + + + + + + ))} + {!cityCoverageRows.length ? ( + + + + ) : null} + +
CitySourceStationTruthFeatureEMOSLGBM
+
{String(entry.name || entry.city || "-")}
+
{String(entry.city || "-")}
+
{String(entry.settlement_source || "-")}{String(entry.settlement_station_code || "-")} + 0 ? "success" : "warning"}>{String(entry.truth_rows || 0)} + + 0 ? "success" : "warning"}>{String(entry.feature_rows || 0)} + + 0 ? "success" : "warning"}>{String(entry.emos_training_samples || 0)} + + 0 ? "success" : "warning"}>{String(entry.lgbm_candidate_rows || 0)} +
+ 暂无城市覆盖矩阵数据 +
+
+
+
+
diff --git a/scripts/backfill_recent_daily_actuals_from_metar.py b/scripts/backfill_recent_daily_actuals_from_metar.py index 1c095d5f..98bfd1b7 100644 --- a/scripts/backfill_recent_daily_actuals_from_metar.py +++ b/scripts/backfill_recent_daily_actuals_from_metar.py @@ -25,12 +25,12 @@ def _target_dates(city_info: dict, lookback_days: int) -> list[str]: def _is_metar_city(city_info: dict) -> bool: source = str(city_info.get("settlement_source") or "metar").strip().lower() - return source == "metar" + return source in {"metar", "hko", "noaa", "wunderground"} def main() -> None: parser = argparse.ArgumentParser( - description="Seed recent daily_records rows and backfill actual_high from aviationweather METAR history." + description="Seed recent runtime daily_records rows and backfill actual_high from the city's settlement source." ) parser.add_argument( "--cities", diff --git a/scripts/fit_probability_calibration.py b/scripts/fit_probability_calibration.py index 5d42ddda..31518ce0 100644 --- a/scripts/fit_probability_calibration.py +++ b/scripts/fit_probability_calibration.py @@ -15,9 +15,12 @@ from src.analysis.probability_calibration import ( # noqa: E402 ) from src.analysis.deb_algorithm import load_history # noqa: E402 from src.database.runtime_state import ( # noqa: E402 + DailyRecordRepository, ProbabilitySnapshotRepository, STATE_STORAGE_FILE, STATE_STORAGE_SQLITE, + TrainingFeatureRecordRepository, + TruthRecordRepository, get_state_storage_mode, ) @@ -56,12 +59,34 @@ def _default_snapshot_arg(): def _load_history_with_fallback(path): + if not path: + if get_state_storage_mode() == STATE_STORAGE_SQLITE: + return DailyRecordRepository().load_all() + return {} data = load_history(path) if data: return data return _load_json_if_exists(path) +def _load_truth_history(): + if get_state_storage_mode() != STATE_STORAGE_SQLITE: + return {} + try: + return TruthRecordRepository().load_all() + except Exception: + return {} + + +def _load_training_feature_history(): + if get_state_storage_mode() != STATE_STORAGE_SQLITE: + return {} + try: + return TrainingFeatureRecordRepository().load_all() + except Exception: + return {} + + def _load_snapshot_rows(path): if get_state_storage_mode() == STATE_STORAGE_SQLITE: return ProbabilitySnapshotRepository().load_all_rows() @@ -82,18 +107,28 @@ def _load_snapshot_rows(path): return rows -def _actual_high_for(history, settlement_history, city, date_str): +def _actual_high_for(history, truth_history, settlement_history, city, date_str): city_rows = (history or {}).get(city) or {} record = city_rows.get(date_str) or {} actual_high = _sf(record.get("actual_high")) if isinstance(record, dict) else None + truth_record = ((truth_history.get(city) or {}).get(date_str) or {}) + if actual_high is None and isinstance(truth_record, dict): + actual_high = _sf(truth_record.get("actual_high")) filled = False if actual_high is None: actual_high = _sf(((settlement_history.get(city) or {}).get(date_str) or {}).get("max_temp")) filled = actual_high is not None - return actual_high, filled + metadata = { + "settlement_source": truth_record.get("settlement_source"), + "settlement_station_code": truth_record.get("settlement_station_code"), + "truth_version": truth_record.get("truth_version"), + "truth_updated_by": truth_record.get("updated_by"), + "truth_updated_at": truth_record.get("truth_updated_at"), + } + return actual_high, filled, metadata -def _extract_snapshot_samples(history, snapshot_rows, settlement_history=None): +def _extract_snapshot_samples(history, truth_history=None, snapshot_rows=None, settlement_history=None): samples = [] filled_actual_from_history = 0 today = datetime.utcnow().strftime("%Y-%m-%d") @@ -105,7 +140,13 @@ def _extract_snapshot_samples(history, snapshot_rows, settlement_history=None): if not city or not date_str or date_str == today: continue - actual_high, filled = _actual_high_for(history, settlement_history, city, date_str) + actual_high, filled, truth_meta = _actual_high_for( + history, + truth_history or {}, + settlement_history, + city, + date_str, + ) if actual_high is None: continue if filled: @@ -167,13 +208,20 @@ def _extract_snapshot_samples(history, snapshot_rows, settlement_history=None): "max_so_far_gap": max_so_far_gap, "peak_flag": peak_flag, "sample_source": "snapshot", + **truth_meta, } ) return samples, filled_actual_from_history -def _extract_daily_record_samples(history, settlement_history=None, excluded_keys=None): +def _extract_daily_record_samples( + history, + training_feature_history=None, + truth_history=None, + settlement_history=None, + excluded_keys=None, +): samples = [] filled_actual_from_history = 0 today = datetime.utcnow().strftime("%Y-%m-%d") @@ -189,13 +237,18 @@ def _extract_daily_record_samples(history, settlement_history=None, excluded_key if (city, date_str) in excluded_keys: continue actual_high = _sf(record.get("actual_high")) + truth_meta = ((truth_history or {}).get(city) or {}).get(date_str) or {} + if actual_high is None: + actual_high = _sf(truth_meta.get("actual_high")) if actual_high is None: actual_high = _sf((city_settlement.get(date_str) or {}).get("max_temp")) if actual_high is not None: filled_actual_from_history += 1 - deb_prediction = _sf(record.get("deb_prediction")) - raw_mu = _sf(record.get("mu")) or deb_prediction - forecasts = record.get("forecasts") or {} + feature_record = ((training_feature_history or {}).get(city) or {}).get(date_str) or {} + source_record = feature_record if isinstance(feature_record, dict) and feature_record else record + deb_prediction = _sf(source_record.get("deb_prediction")) + raw_mu = _sf(source_record.get("mu")) or deb_prediction + forecasts = source_record.get("forecasts") or {} if not isinstance(forecasts, dict): forecasts = {} forecast_values = [val for val in (_sf(v) for v in forecasts.values()) if val is not None] @@ -203,7 +256,7 @@ def _extract_daily_record_samples(history, settlement_history=None, excluded_key forecast_median = ( forecast_values[len(forecast_values) // 2] if forecast_values else None ) - feature_snapshot = record.get("probability_features") or {} + feature_snapshot = source_record.get("probability_features") or {} if not isinstance(feature_snapshot, dict): feature_snapshot = {} @@ -243,15 +296,21 @@ def _extract_daily_record_samples(history, settlement_history=None, excluded_key "max_so_far_gap": max_so_far_gap, "peak_flag": peak_flag, "sample_source": "daily_record", + "settlement_source": truth_meta.get("settlement_source"), + "settlement_station_code": truth_meta.get("settlement_station_code"), + "truth_version": truth_meta.get("truth_version"), + "truth_updated_by": truth_meta.get("updated_by"), + "truth_updated_at": truth_meta.get("truth_updated_at"), } ) return samples, filled_actual_from_history -def _extract_samples(history, settlement_history=None, snapshot_rows=None): +def _extract_samples(history, training_feature_history=None, truth_history=None, settlement_history=None, snapshot_rows=None): snapshot_samples, snapshot_filled = _extract_snapshot_samples( history, - snapshot_rows or [], + truth_history=truth_history, + snapshot_rows=snapshot_rows or [], settlement_history=settlement_history, ) excluded_keys = { @@ -260,6 +319,8 @@ def _extract_samples(history, settlement_history=None, snapshot_rows=None): } daily_samples, daily_filled = _extract_daily_record_samples( history, + training_feature_history=training_feature_history, + truth_history=truth_history, settlement_history=settlement_history, excluded_keys=excluded_keys, ) @@ -301,10 +362,14 @@ def main(): args = parser.parse_args() history = _load_history_with_fallback(args.history_file) + training_feature_history = _load_training_feature_history() + truth_history = _load_truth_history() settlement_history = _load_json_if_exists(args.settlement_history) snapshot_rows = _load_snapshot_rows(args.snapshot_file) samples, filled_actual_from_history = _extract_samples( history, + training_feature_history=training_feature_history, + truth_history=truth_history, settlement_history=settlement_history, snapshot_rows=snapshot_rows, ) diff --git a/scripts/restore_training_feature_history.py b/scripts/restore_training_feature_history.py new file mode 100644 index 00000000..8a29e99e --- /dev/null +++ b/scripts/restore_training_feature_history.py @@ -0,0 +1,114 @@ +import argparse +import json +import os +import sys + +PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if PROJECT_ROOT not in sys.path: + sys.path.insert(0, PROJECT_ROOT) + +from src.database.runtime_state import ( # noqa: E402 + ProbabilitySnapshotRepository, + TrainingFeatureRecordRepository, + get_state_storage_mode, +) + + +def _load_legacy_snapshot_rows(path: str): + rows = [] + if not path or not os.path.exists(path): + return rows + with open(path, "r", encoding="utf-8") as fh: + for line in fh: + line = line.strip() + if not line: + continue + try: + row = json.loads(line) + except Exception: + continue + if isinstance(row, dict): + rows.append(row) + return rows + + +def _spread_from_ensemble(ensemble: dict): + if not isinstance(ensemble, dict): + return None + try: + p10 = float(ensemble.get("p10")) + p90 = float(ensemble.get("p90")) + except Exception: + return None + if p90 < p10: + return None + return max(0.1, round((p90 - p10) / 2.56, 3)) + + +def main(): + parser = argparse.ArgumentParser( + description="Restore permanent training feature history from snapshot archives." + ) + parser.add_argument( + "--snapshot-file", + default=os.path.join(PROJECT_ROOT, "data", "probability_training_snapshots.jsonl"), + ) + args = parser.parse_args() + + rows = [] + if get_state_storage_mode() == "sqlite": + rows.extend(ProbabilitySnapshotRepository().load_all_rows()) + rows.extend(_load_legacy_snapshot_rows(args.snapshot_file)) + + latest = {} + for row in rows: + city = str(row.get("city") or "").strip().lower() + date_str = str(row.get("date") or "").strip() + ts = str(row.get("timestamp") or "") + if not city or not date_str: + continue + key = (city, date_str) + current = latest.get(key) + if current is None or ts >= str(current.get("timestamp") or ""): + latest[key] = row + + repo = TrainingFeatureRecordRepository() + restored = 0 + for (city, date_str), row in latest.items(): + repo.upsert_record( + city, + date_str, + { + "forecasts": row.get("multi_model") or {}, + "deb_prediction": row.get("deb_prediction"), + "mu": row.get("raw_mu"), + "probability_features": { + "raw_mu": row.get("raw_mu"), + "raw_sigma": row.get("raw_sigma"), + "deb_prediction": row.get("deb_prediction"), + "ens_median": ((row.get("ensemble") or {}).get("median")), + "ensemble_spread": _spread_from_ensemble(row.get("ensemble") or {}), + "max_so_far": row.get("max_so_far"), + "peak_status": row.get("peak_status"), + }, + "prob_snapshot": row.get("prob_snapshot") or [], + "shadow_prob_snapshot": row.get("shadow_prob_snapshot") or [], + "probability_calibration": { + "engine": row.get("probability_engine"), + "mode": row.get("probability_mode"), + "calibration_version": row.get("calibration_version"), + "calibration_source": row.get("calibration_source"), + "calibrated_mu": row.get("calibrated_mu"), + "calibrated_sigma": row.get("calibrated_sigma"), + }, + "observation": row.get("observation") or {}, + "snapshot_timestamp": row.get("timestamp"), + }, + ) + restored += 1 + + print(json.dumps({"restored_feature_records": restored}, ensure_ascii=False)) + + +if __name__ == "__main__": + main() diff --git a/scripts/restore_training_truth_history.py b/scripts/restore_training_truth_history.py new file mode 100644 index 00000000..d4cf68f4 --- /dev/null +++ b/scripts/restore_training_truth_history.py @@ -0,0 +1,140 @@ +import argparse +import json +import os +import sys + +PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if PROJECT_ROOT not in sys.path: + sys.path.insert(0, PROJECT_ROOT) + +from src.data_collection.city_registry import CITY_REGISTRY # noqa: E402 +from src.database.runtime_state import TruthRecordRepository # noqa: E402 +from scripts.fit_probability_calibration import ( # noqa: E402 + _default_history_arg, + _load_history_with_fallback, + _load_json_if_exists, +) + + +def _sf(value): + if value is None: + return None + try: + return float(value) + except Exception: + return None + + +def _truth_meta(city: str) -> dict: + city_meta = CITY_REGISTRY.get(city) or {} + return { + "settlement_source": str(city_meta.get("settlement_source") or "metar").strip().lower(), + "settlement_station_code": str( + city_meta.get("settlement_station_code") or city_meta.get("icao") or "" + ).strip().upper() + or None, + "settlement_station_label": str( + city_meta.get("settlement_station_label") + or city_meta.get("airport_name") + or city_meta.get("name") + or "" + ).strip() + or None, + } + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Restore permanent training truth history from settlement history and recent runtime cache." + ) + parser.add_argument( + "--history-file", + default=_default_history_arg(), + ) + parser.add_argument( + "--settlement-history", + default=os.path.join( + PROJECT_ROOT, + "artifacts", + "probability_calibration", + "settlement_history.json", + ), + ) + parser.add_argument( + "--truth-version", + default="v1", + ) + args = parser.parse_args() + + repo = TruthRecordRepository() + settlement_history = _load_json_if_exists(args.settlement_history) + runtime_history = _load_history_with_fallback(args.history_file) + restored = 0 + + for city, city_rows in (settlement_history or {}).items(): + if not isinstance(city_rows, dict): + continue + meta = _truth_meta(city) + for date_str, payload in city_rows.items(): + if not isinstance(payload, dict): + continue + actual_high = _sf(payload.get("max_temp")) + if actual_high is None: + continue + repo.upsert_truth( + city=city, + target_date=str(date_str), + actual_high=actual_high, + settlement_source=meta["settlement_source"], + settlement_station_code=meta["settlement_station_code"], + settlement_station_label=meta["settlement_station_label"], + truth_version=args.truth_version, + updated_by="restore:settlement_history", + source_payload=payload, + is_final=True, + reason="restore_training_truth_history", + ) + restored += 1 + + merged_recent = 0 + for city, city_rows in (runtime_history or {}).items(): + if not isinstance(city_rows, dict): + continue + meta = _truth_meta(city) + for date_str, payload in city_rows.items(): + if not isinstance(payload, dict): + continue + actual_high = _sf(payload.get("actual_high")) + if actual_high is None: + continue + repo.upsert_truth( + city=city, + target_date=str(date_str), + actual_high=actual_high, + settlement_source=meta["settlement_source"], + settlement_station_code=meta["settlement_station_code"], + settlement_station_label=meta["settlement_station_label"], + truth_version=args.truth_version, + updated_by="restore:runtime_daily_records", + source_payload={ + "actual_high": actual_high, + "payload_json": payload, + }, + is_final=True, + reason="restore_training_truth_history", + ) + merged_recent += 1 + + print( + json.dumps( + { + "restored_from_settlement_history": restored, + "merged_recent_runtime_records": merged_recent, + }, + ensure_ascii=False, + ) + ) + + +if __name__ == "__main__": + main() diff --git a/src/analysis/deb_algorithm.py b/src/analysis/deb_algorithm.py index a67599c9..92b24c1a 100644 --- a/src/analysis/deb_algorithm.py +++ b/src/analysis/deb_algorithm.py @@ -1,13 +1,17 @@ import os import json from datetime import datetime, timedelta +from typing import Optional import requests from src.analysis.settlement_rounding import apply_city_settlement +from src.data_collection.wunderground_sources import fetch_wunderground_historical_high from loguru import logger from src.database.runtime_state import ( DailyRecordRepository, STATE_STORAGE_DUAL, STATE_STORAGE_SQLITE, + TrainingFeatureRecordRepository, + TruthRecordRepository, get_state_storage_mode, ) @@ -44,6 +48,9 @@ else: _history_cache = {} _history_mtime = 0 _daily_record_repo = DailyRecordRepository() +_training_feature_repo = TrainingFeatureRecordRepository() +_truth_record_repo = TruthRecordRepository() +_TRUTH_VERSION = "v1" def _sf(value): @@ -168,6 +175,80 @@ def _resolve_city_history_context(city_name: str): return city_key, city_meta +def _truth_meta_for_city(city_meta: dict) -> dict: + if not isinstance(city_meta, dict): + city_meta = {} + return { + "settlement_source": str(city_meta.get("settlement_source") or "metar").strip().lower(), + "settlement_station_code": str(city_meta.get("settlement_station_code") or city_meta.get("icao") or "").strip().upper() or None, + "settlement_station_label": str( + city_meta.get("settlement_station_label") + or city_meta.get("airport_name") + or city_meta.get("name") + or "" + ).strip() + or None, + } + + +def _persist_truth_record( + city_name: str, + date_str: str, + actual_high: float, + *, + city_meta: Optional[dict] = None, + updated_by: str, + reason: str, + source_payload: Optional[dict] = None, + is_final: bool = True, +) -> None: + city_key, resolved_meta = _resolve_city_history_context(city_name) + meta = city_meta if isinstance(city_meta, dict) else resolved_meta + if not city_key or not isinstance(meta, dict): + return + truth_meta = _truth_meta_for_city(meta) + _truth_record_repo.upsert_truth( + city=city_key, + target_date=date_str, + actual_high=float(actual_high), + settlement_source=truth_meta["settlement_source"], + settlement_station_code=truth_meta["settlement_station_code"], + settlement_station_label=truth_meta["settlement_station_label"], + truth_version=_TRUTH_VERSION, + updated_by=updated_by, + source_payload=source_payload, + is_final=is_final, + reason=reason, + ) + + +def _persist_training_feature_record( + city_name: str, + date_str: str, + *, + forecasts: Optional[dict], + deb_prediction: Optional[float], + mu: Optional[float], + probability_features: Optional[dict], + probabilities: Optional[list], + shadow_probabilities: Optional[list], + probability_calibration: Optional[dict], +) -> None: + city_key, _ = _resolve_city_history_context(city_name) + if not city_key: + return + payload = { + "forecasts": forecasts or {}, + "deb_prediction": deb_prediction, + "mu": mu, + "probability_features": probability_features or {}, + "prob_snapshot": probabilities or [], + "shadow_prob_snapshot": shadow_probabilities or [], + "probability_calibration": probability_calibration or {}, + } + _training_feature_repo.upsert_record(city_key, date_str, payload) + + def _parse_hko_ryes_max_temp(payload): if not isinstance(payload, dict): return None @@ -275,6 +356,15 @@ def _reconcile_recent_metar_actual_highs(city_name: str, lookback_days: int = 7) if t_c is None: continue corrected = round(t_c * 9 / 5 + 32, 1) if use_fahrenheit else round(t_c, 1) + _persist_truth_record( + city_key, + d, + corrected, + city_meta=city_meta, + updated_by="backfill:metar_history", + reason="reconcile_recent_actual_highs", + source_payload={"icao": icao, "actual_high": corrected, "source": "metar"}, + ) rec = city_data.get(d) or {} old = rec.get("actual_high") try: @@ -367,6 +457,19 @@ def _reconcile_recent_hko_actual_highs(city_name: str, lookback_days: int = 14): if use_fahrenheit else round(max_temp_c, 1) ) + _persist_truth_record( + city_key, + date_str, + corrected, + city_meta=city_meta, + updated_by="backfill:hko_history", + reason="reconcile_recent_actual_highs", + source_payload={ + "station_code": station_code, + "actual_high": corrected, + "source": "hko", + }, + ) rec = city_data.get(date_str) or {} old = rec.get("actual_high") try: @@ -486,6 +589,19 @@ def _reconcile_recent_noaa_actual_highs(city_name: str, lookback_days: int = 14) if use_fahrenheit else int(corrected) ) + _persist_truth_record( + city_key, + date_key, + next_value, + city_meta=city_meta, + updated_by="backfill:noaa_history", + reason="reconcile_recent_actual_highs", + source_payload={ + "station_code": station_code, + "actual_high": next_value, + "source": "noaa", + }, + ) rec = city_data.get(date_key) or {} old = rec.get("actual_high") try: @@ -513,6 +629,79 @@ def _reconcile_recent_noaa_actual_highs(city_name: str, lookback_days: int = 14) return {"ok": False, "reason": str(e), "updated": 0} +def _reconcile_recent_wunderground_actual_highs(city_name: str, lookback_days: int = 14): + try: + city_key, city_meta = _resolve_city_history_context(city_name) + if not city_key or not isinstance(city_meta, dict): + return {"ok": False, "reason": "unknown_city", "updated": 0} + + settlement_url = str(city_meta.get("settlement_url") or "").strip() + if not settlement_url: + return {"ok": False, "reason": "missing_settlement_url", "updated": 0} + + tz_offset = int(city_meta.get("tz_offset") or 0) + history_file = _get_history_file_path() + data = load_history(history_file) + city_data = data.get(city_key) or {} + if not isinstance(city_data, dict) or not city_data: + return {"ok": True, "reason": "no_city_history", "updated": 0} + + local_now = datetime.utcnow() + timedelta(seconds=tz_offset) + local_today = local_now.strftime("%Y-%m-%d") + cutoff = (local_now - timedelta(days=max(lookback_days, 1) + 1)).strftime( + "%Y-%m-%d" + ) + target_dates = sorted( + d for d in city_data.keys() if isinstance(d, str) and cutoff <= d < local_today + ) + if not target_dates: + return {"ok": True, "reason": "no_target_dates", "updated": 0} + + updated = 0 + scanned_dates = 0 + for date_str in target_dates: + result = fetch_wunderground_historical_high(city_key, date_str, url=settlement_url) + if not result.get("ok"): + continue + scanned_dates += 1 + corrected = _sf(result.get("actual_high")) + if corrected is None: + continue + rec = city_data.get(date_str) or {} + old = rec.get("actual_high") + try: + old_val = float(old) if old is not None else None + except Exception: + old_val = None + if old_val is None or abs(old_val - corrected) >= 0.1: + rec["actual_high"] = corrected + city_data[date_str] = rec + updated += 1 + _persist_truth_record( + city_key, + date_str, + corrected, + city_meta=city_meta, + updated_by="backfill:wunderground_history", + reason="reconcile_recent_actual_highs", + source_payload=result, + ) + + if updated > 0: + data[city_key] = city_data + save_history(history_file, data) + + return { + "ok": True, + "updated": updated, + "scanned_dates": scanned_dates, + "station_code": city_meta.get("settlement_station_code"), + "source": "wunderground", + } + except Exception as e: + return {"ok": False, "reason": str(e), "updated": 0} + + def reconcile_recent_actual_highs(city_name: str, lookback_days: int = 7): """ Reconcile recent `actual_high` values using the city's official settlement source. @@ -526,6 +715,8 @@ def reconcile_recent_actual_highs(city_name: str, lookback_days: int = 7): return _reconcile_recent_hko_actual_highs(city_key, lookback_days=lookback_days) if settlement_source == "noaa": return _reconcile_recent_noaa_actual_highs(city_key, lookback_days=lookback_days) + if settlement_source == "wunderground": + return _reconcile_recent_wunderground_actual_highs(city_key, lookback_days=lookback_days) return _reconcile_recent_metar_actual_highs(city_key, lookback_days=lookback_days) @@ -541,14 +732,14 @@ def bootstrap_recent_daily_history_if_missing(city_name: str, lookback_days: int return {"ok": False, "reason": "unknown_city", "seeded": 0, "updated": 0} settlement_source = str(city_meta.get("settlement_source") or "metar").strip().lower() - if settlement_source not in {"metar", "hko", "noaa"}: + if settlement_source not in {"metar", "hko", "noaa", "wunderground"}: return {"ok": True, "reason": "unsupported_settlement_source", "seeded": 0, "updated": 0} icao = str(city_meta.get("icao") or "").strip().upper() station_code = str(city_meta.get("settlement_station_code") or "").strip().upper() if settlement_source == "metar" and not icao: return {"ok": False, "reason": "missing_icao", "seeded": 0, "updated": 0} - if settlement_source in {"hko", "noaa"} and not station_code: + if settlement_source in {"hko", "noaa", "wunderground"} and not station_code: return {"ok": False, "reason": "missing_station_code", "seeded": 0, "updated": 0} tz_offset = int(city_meta.get("tz_offset") or 0) @@ -726,6 +917,37 @@ def update_daily_record( if compact_calibration is not None: existing["probability_calibration"] = compact_calibration + if actual_high is not None: + try: + _persist_truth_record( + city_name, + date_str, + float(actual_high), + updated_by="runtime:update_daily_record", + reason="update_daily_record", + source_payload={ + "actual_high": actual_high, + "deb_prediction": deb_prediction, + "mu": next_mu, + }, + ) + except Exception as e: + logger.error(f"Error persisting truth record city={city_name} date={date_str}: {e}") + try: + _persist_training_feature_record( + city_name, + date_str, + forecasts=merged_forecasts, + deb_prediction=existing.get("deb_prediction"), + mu=existing.get("mu"), + probability_features=existing.get("probability_features"), + probabilities=existing.get("prob_snapshot"), + shadow_probabilities=existing.get("shadow_prob_snapshot"), + probability_calibration=existing.get("probability_calibration"), + ) + except Exception as e: + logger.error(f"Error persisting training feature record city={city_name} date={date_str}: {e}") + # 自动清理:只保留最近 14 天的记录(DEB 只用 7 天,14 天留足余量) cutoff = (datetime.now() - timedelta(days=14)).strftime("%Y-%m-%d") for city in list(data.keys()): diff --git a/src/analysis/probability_snapshot_archive.py b/src/analysis/probability_snapshot_archive.py index c51c3cf7..332ccf96 100644 --- a/src/analysis/probability_snapshot_archive.py +++ b/src/analysis/probability_snapshot_archive.py @@ -9,6 +9,7 @@ from src.database.runtime_state import ( ProbabilitySnapshotRepository, STATE_STORAGE_DUAL, STATE_STORAGE_SQLITE, + TrainingFeatureRecordRepository, get_state_storage_mode, ) @@ -17,6 +18,7 @@ MU_THRESHOLD = 0.2 SIGMA_THRESHOLD = 0.15 MAX_SO_FAR_THRESHOLD = 0.2 _snapshot_repo = ProbabilitySnapshotRepository() +_training_feature_repo = TrainingFeatureRecordRepository() def _sf(value: Any) -> Optional[float]: @@ -260,6 +262,36 @@ def append_probability_snapshot( mode = get_state_storage_mode() if mode in {STATE_STORAGE_DUAL, STATE_STORAGE_SQLITE}: _snapshot_repo.append_snapshot(payload) + _training_feature_repo.upsert_record( + city_key, + local_date, + { + "forecasts": payload.get("multi_model") or {}, + "deb_prediction": payload.get("deb_prediction"), + "mu": payload.get("raw_mu"), + "probability_features": { + "raw_mu": payload.get("raw_mu"), + "raw_sigma": payload.get("raw_sigma"), + "deb_prediction": payload.get("deb_prediction"), + "ens_median": (payload.get("ensemble") or {}).get("median"), + "ensemble_spread": None, + "max_so_far": payload.get("max_so_far"), + "peak_status": payload.get("peak_status"), + }, + "prob_snapshot": payload.get("prob_snapshot") or [], + "shadow_prob_snapshot": payload.get("shadow_prob_snapshot") or [], + "probability_calibration": { + "engine": payload.get("probability_engine"), + "mode": payload.get("probability_mode"), + "calibration_version": payload.get("calibration_version"), + "calibration_source": payload.get("calibration_source"), + "calibrated_mu": payload.get("calibrated_mu"), + "calibrated_sigma": payload.get("calibrated_sigma"), + }, + "observation": payload.get("observation") or {}, + "snapshot_timestamp": payload.get("timestamp"), + }, + ) if mode != STATE_STORAGE_SQLITE: with open(path, "a", encoding="utf-8") as fh: diff --git a/src/data_collection/wunderground_sources.py b/src/data_collection/wunderground_sources.py index 77996297..027c6303 100644 --- a/src/data_collection/wunderground_sources.py +++ b/src/data_collection/wunderground_sources.py @@ -5,8 +5,10 @@ import re from datetime import datetime, timedelta, timezone from typing import Any, Dict, Optional +import requests from loguru import logger from src.data_collection.city_registry import CITY_REGISTRY +from src.analysis.settlement_rounding import apply_city_settlement class WundergroundSourceMixin: @@ -502,3 +504,92 @@ class WundergroundSourceMixin: } self._set_settlement_cache(cache_key, payload) return payload + + +def _normalize_wu_history_date_url(url: str, target_date: str) -> str: + normalized = str(url or "").strip().rstrip("/") + normalized = re.sub(r"/date/\d{4}-\d{2}-\d{2}$", "", normalized, flags=re.IGNORECASE) + return f"{normalized}/date/{target_date}" + + +def fetch_wunderground_historical_high( + city: str, + target_date: str, + *, + url: Optional[str] = None, + timeout: int = 15, + session: Optional[requests.Session] = None, +) -> Dict[str, Any]: + city_key = str(city or "").strip().lower() + city_meta = CITY_REGISTRY.get(city_key) or {} + history_url = _normalize_wu_history_date_url( + url or str(city_meta.get("settlement_url") or "").strip(), + target_date, + ) + if not history_url: + return {"ok": False, "reason": "missing_history_url", "city": city_key, "date": target_date} + + requester = session or requests.Session() + try: + response = requester.get( + history_url, + headers={ + "User-Agent": "Mozilla/5.0", + "Referer": history_url, + }, + timeout=timeout, + ) + response.raise_for_status() + html = str(response.text or "") + except Exception as exc: + logger.warning(f"Wunderground history fetch failed city={city_key} date={target_date}: {exc}") + return { + "ok": False, + "reason": "fetch_failed", + "city": city_key, + "date": target_date, + "history_url": history_url, + "error": str(exc), + } + + app_state = WundergroundSourceMixin._wu_extract_app_state(html) + if not isinstance(app_state, dict): + return { + "ok": False, + "reason": "missing_app_state", + "city": city_key, + "date": target_date, + "history_url": history_url, + } + + utc_offset_seconds = int(city_meta.get("tz_offset") or 0) + obs = WundergroundSourceMixin._wu_extract_history_observations( + app_state, + utc_offset_seconds=utc_offset_seconds, + ) + if not obs: + return { + "ok": False, + "reason": "missing_observations", + "city": city_key, + "date": target_date, + "history_url": history_url, + } + + raw_max_temp_c = max(float(point.get("temp")) for point in obs if point.get("temp") is not None) + settled_actual_high = apply_city_settlement(city_key, raw_max_temp_c) + station_code = str(city_meta.get("settlement_station_code") or "").strip().upper() or None + station_label = str(city_meta.get("settlement_station_label") or "").strip() or None + return { + "ok": True, + "city": city_key, + "date": target_date, + "history_url": history_url, + "raw_max_temp_c": round(raw_max_temp_c, 1), + "actual_high": float(settled_actual_high), + "settlement_source": "wunderground", + "settlement_station_code": station_code, + "settlement_station_label": station_label, + "observation_count": len(obs), + "observations": obs, + } diff --git a/src/database/runtime_state.py b/src/database/runtime_state.py index 3896ae51..41b1c8de 100644 --- a/src/database/runtime_state.py +++ b/src/database/runtime_state.py @@ -74,6 +74,48 @@ class RuntimeStateDB: ) """ ) + conn.execute( + """ + CREATE TABLE IF NOT EXISTS truth_records_store ( + city TEXT NOT NULL, + target_date TEXT NOT NULL, + actual_high REAL NOT NULL, + settlement_source TEXT, + settlement_station_code TEXT, + settlement_station_label TEXT, + truth_version TEXT, + updated_by TEXT, + updated_at REAL NOT NULL, + source_payload_json TEXT, + is_final INTEGER NOT NULL DEFAULT 1, + PRIMARY KEY (city, target_date) + ) + """ + ) + conn.execute( + """ + CREATE TABLE IF NOT EXISTS truth_revisions_store ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + city TEXT NOT NULL, + target_date TEXT NOT NULL, + previous_actual_high REAL, + next_actual_high REAL NOT NULL, + previous_source TEXT, + next_source TEXT, + truth_version TEXT, + updated_by TEXT, + updated_at REAL NOT NULL, + reason TEXT, + payload_json TEXT + ) + """ + ) + conn.execute( + "CREATE INDEX IF NOT EXISTS idx_truth_records_city_date ON truth_records_store(city, target_date)" + ) + conn.execute( + "CREATE INDEX IF NOT EXISTS idx_truth_revisions_city_date ON truth_revisions_store(city, target_date, id DESC)" + ) conn.execute( """ CREATE TABLE IF NOT EXISTS telegram_alert_last_by_city ( @@ -117,6 +159,20 @@ class RuntimeStateDB: conn.execute( "CREATE INDEX IF NOT EXISTS idx_probability_snapshot_city_date ON probability_training_snapshots_store(city, target_date, id DESC)" ) + conn.execute( + """ + CREATE TABLE IF NOT EXISTS training_feature_records_store ( + city TEXT NOT NULL, + target_date TEXT NOT NULL, + updated_at REAL NOT NULL, + payload_json TEXT NOT NULL, + PRIMARY KEY (city, target_date) + ) + """ + ) + conn.execute( + "CREATE INDEX IF NOT EXISTS idx_training_feature_records_city_date ON training_feature_records_store(city, target_date)" + ) conn.execute( """ CREATE TABLE IF NOT EXISTS open_meteo_cache_store ( @@ -238,6 +294,251 @@ class DailyRecordRepository: return int(cur.rowcount or 0) +class TruthRecordRepository: + def __init__(self, db: Optional[RuntimeStateDB] = None): + self.db = db or RuntimeStateDB.instance() + + def load_all(self) -> Dict[str, Dict[str, Dict[str, Any]]]: + out: Dict[str, Dict[str, Dict[str, Any]]] = {} + with self.db.connect() as conn: + rows = conn.execute( + """ + SELECT city, target_date, actual_high, settlement_source, settlement_station_code, + settlement_station_label, truth_version, updated_by, updated_at, + source_payload_json, is_final + FROM truth_records_store + ORDER BY city, target_date + """ + ).fetchall() + for row in rows: + payload: Dict[str, Any] = { + "actual_high": float(row["actual_high"]), + "settlement_source": row["settlement_source"], + "settlement_station_code": row["settlement_station_code"], + "settlement_station_label": row["settlement_station_label"], + "truth_version": row["truth_version"], + "updated_by": row["updated_by"], + "truth_updated_at": float(row["updated_at"]), + "is_final": bool(row["is_final"]), + } + if row["source_payload_json"]: + try: + payload["source_payload"] = json.loads(row["source_payload_json"]) + except Exception: + pass + out.setdefault(str(row["city"]), {})[str(row["target_date"])] = payload + return out + + def get_record(self, city: str, target_date: str) -> Optional[Dict[str, Any]]: + with self.db.connect() as conn: + row = conn.execute( + """ + SELECT actual_high, settlement_source, settlement_station_code, + settlement_station_label, truth_version, updated_by, updated_at, + source_payload_json, is_final + FROM truth_records_store + WHERE city = ? AND target_date = ? + """, + (city, target_date), + ).fetchone() + if not row: + return None + payload: Dict[str, Any] = { + "actual_high": float(row["actual_high"]), + "settlement_source": row["settlement_source"], + "settlement_station_code": row["settlement_station_code"], + "settlement_station_label": row["settlement_station_label"], + "truth_version": row["truth_version"], + "updated_by": row["updated_by"], + "truth_updated_at": float(row["updated_at"]), + "is_final": bool(row["is_final"]), + } + if row["source_payload_json"]: + try: + payload["source_payload"] = json.loads(row["source_payload_json"]) + except Exception: + pass + return payload + + def upsert_truth( + self, + *, + city: str, + target_date: str, + actual_high: float, + settlement_source: Optional[str], + settlement_station_code: Optional[str], + settlement_station_label: Optional[str], + truth_version: str, + updated_by: str, + source_payload: Optional[Dict[str, Any]] = None, + is_final: bool = True, + reason: Optional[str] = None, + ) -> bool: + updated_at = time.time() + payload_json = ( + json.dumps(source_payload, ensure_ascii=False) if source_payload is not None else None + ) + with self.db.connect() as conn: + current = conn.execute( + """ + SELECT actual_high, settlement_source, source_payload_json + FROM truth_records_store + WHERE city = ? AND target_date = ? + """, + (city, target_date), + ).fetchone() + changed = True + if current: + prev_actual = float(current["actual_high"]) + prev_source = str(current["settlement_source"] or "") + next_source = str(settlement_source or "") + changed = ( + abs(prev_actual - float(actual_high)) >= 0.0001 + or prev_source != next_source + or str(current["source_payload_json"] or "") != str(payload_json or "") + ) + if changed: + conn.execute( + """ + INSERT INTO truth_revisions_store ( + city, target_date, previous_actual_high, next_actual_high, + previous_source, next_source, truth_version, updated_by, + updated_at, reason, payload_json + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + city, + target_date, + prev_actual, + float(actual_high), + prev_source or None, + next_source or None, + truth_version, + updated_by, + updated_at, + reason, + payload_json, + ), + ) + conn.execute( + """ + INSERT INTO truth_records_store ( + city, target_date, actual_high, settlement_source, + settlement_station_code, settlement_station_label, truth_version, + updated_by, updated_at, source_payload_json, is_final + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(city, target_date) DO UPDATE SET + actual_high = excluded.actual_high, + settlement_source = excluded.settlement_source, + settlement_station_code = excluded.settlement_station_code, + settlement_station_label = excluded.settlement_station_label, + truth_version = excluded.truth_version, + updated_by = excluded.updated_by, + updated_at = excluded.updated_at, + source_payload_json = excluded.source_payload_json, + is_final = excluded.is_final + """, + ( + city, + target_date, + float(actual_high), + settlement_source, + settlement_station_code, + settlement_station_label, + truth_version, + updated_by, + updated_at, + payload_json, + 1 if is_final else 0, + ), + ) + conn.commit() + return changed + + def replace_all(self, rows: Dict[str, Dict[str, Dict[str, Any]]]) -> int: + count = 0 + with self.db.connect() as conn: + conn.execute("DELETE FROM truth_records_store") + conn.execute("DELETE FROM truth_revisions_store") + for city, city_rows in (rows or {}).items(): + if not isinstance(city_rows, dict): + continue + for target_date, record in city_rows.items(): + if not isinstance(record, dict): + continue + actual_high = record.get("actual_high") + if actual_high is None: + continue + payload_json = ( + json.dumps(record.get("source_payload"), ensure_ascii=False) + if record.get("source_payload") is not None + else None + ) + conn.execute( + """ + INSERT INTO truth_records_store ( + city, target_date, actual_high, settlement_source, + settlement_station_code, settlement_station_label, truth_version, + updated_by, updated_at, source_payload_json, is_final + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + city, + target_date, + float(actual_high), + record.get("settlement_source"), + record.get("settlement_station_code"), + record.get("settlement_station_label"), + record.get("truth_version") or "v1", + record.get("updated_by") or "replace_all", + float(record.get("truth_updated_at") or time.time()), + payload_json, + 1 if record.get("is_final", True) else 0, + ), + ) + count += 1 + conn.commit() + return count + + +class TruthRevisionRepository: + def __init__(self, db: Optional[RuntimeStateDB] = None): + self.db = db or RuntimeStateDB.instance() + + def load_revisions(self, city: str, target_date: str) -> List[Dict[str, Any]]: + with self.db.connect() as conn: + rows = conn.execute( + """ + SELECT previous_actual_high, next_actual_high, previous_source, next_source, + truth_version, updated_by, updated_at, reason, payload_json + FROM truth_revisions_store + WHERE city = ? AND target_date = ? + ORDER BY id ASC + """, + (city, target_date), + ).fetchall() + out: List[Dict[str, Any]] = [] + for row in rows: + entry: Dict[str, Any] = { + "previous_actual_high": row["previous_actual_high"], + "next_actual_high": row["next_actual_high"], + "previous_source": row["previous_source"], + "next_source": row["next_source"], + "truth_version": row["truth_version"], + "updated_by": row["updated_by"], + "updated_at": float(row["updated_at"]), + "reason": row["reason"], + } + if row["payload_json"]: + try: + entry["payload"] = json.loads(row["payload_json"]) + except Exception: + pass + out.append(entry) + return out + + class TelegramAlertStateRepository: def __init__(self, db: Optional[RuntimeStateDB] = None): self.db = db or RuntimeStateDB.instance() @@ -429,6 +730,66 @@ class ProbabilitySnapshotRepository: return count +class TrainingFeatureRecordRepository: + def __init__(self, db: Optional[RuntimeStateDB] = None): + self.db = db or RuntimeStateDB.instance() + + def upsert_record(self, city: str, target_date: str, payload: Dict[str, Any]) -> None: + with self.db.connect() as conn: + conn.execute( + """ + INSERT INTO training_feature_records_store ( + city, target_date, updated_at, payload_json + ) VALUES (?, ?, ?, ?) + ON CONFLICT(city, target_date) DO UPDATE SET + updated_at = excluded.updated_at, + payload_json = excluded.payload_json + """, + ( + city, + target_date, + time.time(), + json.dumps(payload, ensure_ascii=False), + ), + ) + conn.commit() + + def load_all(self) -> Dict[str, Dict[str, Dict[str, Any]]]: + out: Dict[str, Dict[str, Dict[str, Any]]] = {} + with self.db.connect() as conn: + rows = conn.execute( + """ + SELECT city, target_date, payload_json + FROM training_feature_records_store + ORDER BY city, target_date + """ + ).fetchall() + for row in rows: + try: + payload = json.loads(row["payload_json"]) + except Exception: + continue + out.setdefault(str(row["city"]), {})[str(row["target_date"])] = payload + return out + + def get_record(self, city: str, target_date: str) -> Optional[Dict[str, Any]]: + with self.db.connect() as conn: + row = conn.execute( + """ + SELECT payload_json + FROM training_feature_records_store + WHERE city = ? AND target_date = ? + """, + (city, target_date), + ).fetchone() + if not row: + return None + try: + return json.loads(row["payload_json"]) + except Exception: + return None + + class OpenMeteoCacheRepository: def __init__(self, db: Optional[RuntimeStateDB] = None): self.db = db or RuntimeStateDB.instance() diff --git a/src/models/lgbm_features.py b/src/models/lgbm_features.py index 4417394e..cba5fd46 100644 --- a/src/models/lgbm_features.py +++ b/src/models/lgbm_features.py @@ -13,6 +13,8 @@ from src.database.runtime_state import ( ProbabilitySnapshotRepository, STATE_STORAGE_FILE, STATE_STORAGE_SQLITE, + TrainingFeatureRecordRepository, + TruthRecordRepository, get_state_storage_mode, ) @@ -288,23 +290,93 @@ def build_training_samples( snapshot_index: Optional[Dict[Tuple[str, str], Dict[str, Any]]] = None, ) -> List[Dict[str, Any]]: if isinstance(history_data, dict): - data = history_data + runtime_history = history_data elif get_state_storage_mode() == STATE_STORAGE_SQLITE: - data = DailyRecordRepository().load_all() + runtime_history = DailyRecordRepository().load_all() else: - data = load_history(_history_file_path()) + runtime_history = load_history(_history_file_path()) + if get_state_storage_mode() == STATE_STORAGE_SQLITE: + truth_history = TruthRecordRepository().load_all() + training_feature_history = TrainingFeatureRecordRepository().load_all() + else: + truth_history = runtime_history + training_feature_history = {} snapshots = snapshot_index if isinstance(snapshot_index, dict) else load_snapshot_index() samples: List[Dict[str, Any]] = [] + excluded_keys: set[tuple[str, str]] = set() - for city_name, city_records in (data or {}).items(): + for (city_name, date_str), snapshot in (snapshots or {}).items(): + if not isinstance(snapshot, dict): + continue + truth_row = ((truth_history.get(city_name) or {}).get(str(date_str)) or {}) + target = _sf(truth_row.get("actual_high")) + if target is None: + runtime_record = ((runtime_history.get(city_name) or {}).get(str(date_str)) or {}) + target = _sf(runtime_record.get("actual_high")) + if target is None: + continue + observation = snapshot.get("observation") if isinstance(snapshot.get("observation"), dict) else {} + current_forecasts = snapshot.get("multi_model") if isinstance(snapshot.get("multi_model"), dict) else {} + local_hour = _sf(observation.get("local_hour")) + if local_hour is None: + timestamp = _parse_timestamp(snapshot.get("timestamp")) + local_hour = float(timestamp.hour) if timestamp is not None else 12.0 + feature_map, meta = build_runtime_feature_map( + city_name=city_name, + current_forecasts=current_forecasts, + deb_prediction=_sf(snapshot.get("deb_prediction")) or _sf(snapshot.get("raw_mu")), + current_temp=_sf(observation.get("current_temp")), + max_so_far=_sf(snapshot.get("max_so_far")), + humidity=_sf(observation.get("humidity")), + wind_speed_kt=_sf(observation.get("wind_speed_kt")), + visibility_mi=_sf(observation.get("visibility_mi")), + local_hour=int(local_hour), + local_date=str(date_str), + peak_status=str(snapshot.get("peak_status") or "before"), + history_data=truth_history, + ) + if not feature_map: + continue + samples.append( + { + "city": _normalized_city_key(city_name), + "date": str(date_str), + "target": float(target), + "features": feature_map, + "vector": _features_to_vector(feature_map), + "history_count": int(meta.get("history_count") or 0), + "deb_prediction": _sf(snapshot.get("deb_prediction")) or _sf(snapshot.get("raw_mu")), + "forecasts": { + key: _sf(value) + for key, value in current_forecasts.items() + if _sf(value) is not None + }, + "sample_source": "snapshot", + "settlement_source": truth_row.get("settlement_source"), + "settlement_station_code": truth_row.get("settlement_station_code"), + "truth_version": truth_row.get("truth_version"), + "truth_updated_by": truth_row.get("updated_by"), + "truth_updated_at": truth_row.get("truth_updated_at"), + } + ) + excluded_keys.add((_normalized_city_key(city_name), str(date_str))) + + training_source = training_feature_history or runtime_history or {} + for city_name, city_records in training_source.items(): if not isinstance(city_records, dict): continue ordered_dates = sorted(city_records.keys()) for date_str in ordered_dates: + normalized_city = _normalized_city_key(city_name) + if (normalized_city, str(date_str)) in excluded_keys: + continue record = city_records.get(date_str) if not isinstance(record, dict): continue - target = _sf(record.get("actual_high")) + truth_row = ((truth_history.get(normalized_city) or {}).get(str(date_str)) or {}) + target = _sf(truth_row.get("actual_high")) + if target is None: + target = _sf(((runtime_history.get(normalized_city) or {}).get(str(date_str)) or {}).get("actual_high")) forecasts = record.get("forecasts") if isinstance(record.get("forecasts"), dict) else {} if target is None or not forecasts: continue @@ -321,7 +393,7 @@ def build_training_samples( local_hour=12, local_date=str(date_str), peak_status="before", - history_data=data, + history_data=truth_history, ) if not feature_map: continue @@ -361,6 +433,12 @@ def build_training_samples( for key, value in forecasts.items() if _sf(value) is not None }, + "sample_source": "daily_record", + "settlement_source": truth_row.get("settlement_source"), + "settlement_station_code": truth_row.get("settlement_station_code"), + "truth_version": truth_row.get("truth_version"), + "truth_updated_by": truth_row.get("updated_by"), + "truth_updated_at": truth_row.get("truth_updated_at"), } ) samples.sort(key=lambda row: (row["date"], row["city"])) diff --git a/tests/test_lgbm_features.py b/tests/test_lgbm_features.py index 58fc7655..e3e64698 100644 --- a/tests/test_lgbm_features.py +++ b/tests/test_lgbm_features.py @@ -1,4 +1,4 @@ -from src.models.lgbm_features import build_runtime_feature_map +from src.models.lgbm_features import build_runtime_feature_map, build_training_samples def test_build_runtime_feature_map_derives_history_and_model_summary(): @@ -59,3 +59,35 @@ def test_build_runtime_feature_map_returns_none_without_history(): assert feature_map is None assert meta["reason"] == "no_history" + + +def test_build_training_samples_prefers_truth_history_for_target(): + history_data = { + "ankara": { + "2026-03-20": {"actual_high": 10.0}, + "2026-03-21": {"actual_high": 11.0}, + "2026-03-22": {"actual_high": 13.0}, + "2026-03-23": { + "actual_high": 12.0, + "deb_prediction": 12.3, + "forecasts": {"Open-Meteo": 12.4, "ECMWF": 12.1}, + }, + } + } + snapshot_index = { + ("ankara", "2026-03-23"): { + "city": "ankara", + "date": "2026-03-23", + "timestamp": "2026-03-23T10:00:00+03:00", + "raw_mu": 12.2, + "deb_prediction": 12.3, + "max_so_far": 11.8, + "peak_status": "before", + "multi_model": {"Open-Meteo": 12.4, "ECMWF": 12.1}, + "observation": {"current_temp": 11.5, "humidity": 60.0, "wind_speed_kt": 8.0, "local_hour": 10}, + } + } + + samples = build_training_samples(history_data=history_data, snapshot_index=snapshot_index) + assert len(samples) == 1 + assert samples[0]["sample_source"] == "snapshot" diff --git a/tests/test_probability_snapshot_archive.py b/tests/test_probability_snapshot_archive.py index d45c452b..4534e085 100644 --- a/tests/test_probability_snapshot_archive.py +++ b/tests/test_probability_snapshot_archive.py @@ -2,7 +2,8 @@ import json from pathlib import Path import pytest -from src.analysis.probability_snapshot_archive import append_probability_snapshot +import src.analysis.probability_snapshot_archive as snapshot_archive +from src.database.runtime_state import RuntimeStateDB, TrainingFeatureRecordRepository @pytest.fixture(autouse=True) @@ -10,10 +11,22 @@ def _force_file_mode(monkeypatch): monkeypatch.setenv("POLYWEATHER_STATE_STORAGE_MODE", "file") -def test_append_probability_snapshot_writes_jsonl(tmp_path: Path): +def test_append_probability_snapshot_writes_jsonl(tmp_path: Path, monkeypatch): archive_path = tmp_path / "probability_training_snapshots.jsonl" - append_probability_snapshot( + db = RuntimeStateDB(str(tmp_path / "polyweather.db")) + monkeypatch.setattr( + snapshot_archive, + "_training_feature_repo", + TrainingFeatureRecordRepository(db), + ) + monkeypatch.setattr( + snapshot_archive, + "_snapshot_repo", + snapshot_archive.ProbabilitySnapshotRepository(db), + ) + + snapshot_archive.append_probability_snapshot( city_name="ankara", local_date="2026-03-20", observation_time="2026-03-20T12:00:00+03:00", @@ -77,8 +90,8 @@ def test_append_probability_snapshot_skips_near_duplicate(tmp_path: Path): archive_path=str(archive_path), ) - append_probability_snapshot(**kwargs) - append_probability_snapshot(**kwargs) + snapshot_archive.append_probability_snapshot(**kwargs) + snapshot_archive.append_probability_snapshot(**kwargs) lines = archive_path.read_text(encoding="utf-8").strip().splitlines() assert len(lines) == 1 @@ -110,14 +123,61 @@ def test_append_probability_snapshot_writes_on_bucket_change(tmp_path: Path): archive_path=str(archive_path), ) - append_probability_snapshot( + snapshot_archive.append_probability_snapshot( probabilities=[{"value": 15, "probability": 0.552}], **base_kwargs, ) - append_probability_snapshot( + snapshot_archive.append_probability_snapshot( probabilities=[{"value": 16, "probability": 0.552}], **base_kwargs, ) lines = archive_path.read_text(encoding="utf-8").strip().splitlines() assert len(lines) == 2 + + +def test_append_probability_snapshot_dual_writes_training_feature_store(tmp_path: Path, monkeypatch): + monkeypatch.setenv("POLYWEATHER_STATE_STORAGE_MODE", "sqlite") + monkeypatch.setenv("POLYWEATHER_DB_PATH", str(tmp_path / "polyweather.db")) + db = RuntimeStateDB(str(tmp_path / "polyweather.db")) + + monkeypatch.setattr( + snapshot_archive, + "_training_feature_repo", + TrainingFeatureRecordRepository(db), + ) + monkeypatch.setattr( + snapshot_archive, + "_snapshot_repo", + snapshot_archive.ProbabilitySnapshotRepository(db), + ) + + snapshot_archive.append_probability_snapshot( + city_name="ankara", + local_date="2026-03-20", + observation_time="2026-03-20T12:00:00+03:00", + temp_symbol="°C", + raw_mu=15.2, + raw_sigma=1.2, + deb_prediction=15.4, + ens_data={"p10": 14.8, "median": 15.8, "p90": 17.9}, + current_forecasts={"ECMWF": 15.8, "GFS": 14.1}, + max_so_far=15.0, + peak_status="before", + probabilities=[{"value": 15, "probability": 0.552}], + shadow_probabilities=[{"value": 15, "probability": 0.324}], + calibration_summary={ + "engine": "legacy", + "mode": "emos_shadow", + "calibration_version": "emos-test", + "calibration_source": "artifacts/probability_calibration/default.json", + "calibrated_mu": 15.1, + "calibrated_sigma": 1.25, + }, + ) + + payload = TrainingFeatureRecordRepository(db).get_record("ankara", "2026-03-20") + assert payload is not None + assert payload["mu"] == 15.2 + assert payload["forecasts"]["ECMWF"] == 15.8 + assert payload["probability_features"]["ens_median"] == 15.8 diff --git a/tests/test_probability_training_dataset.py b/tests/test_probability_training_dataset.py index 68b4602c..c5bffb8d 100644 --- a/tests/test_probability_training_dataset.py +++ b/tests/test_probability_training_dataset.py @@ -31,9 +31,22 @@ def test_extract_samples_prefers_snapshot_rows_for_same_city_day(): "peak_status": "in_window", } ] + truth_history = { + "ankara": { + "2026-03-19": { + "actual_high": 11.0, + "settlement_source": "metar", + "settlement_station_code": "LTAC", + "truth_version": "v1", + "updated_by": "test", + "truth_updated_at": 123.0, + } + } + } samples, filled = _extract_samples( history, + truth_history=truth_history, settlement_history={}, snapshot_rows=snapshot_rows, ) @@ -43,3 +56,6 @@ def test_extract_samples_prefers_snapshot_rows_for_same_city_day(): assert samples[0]["sample_source"] == "snapshot" assert samples[0]["raw_mu"] == 11.2 assert samples[0]["peak_flag"] == 0.5 + assert samples[0]["settlement_source"] == "metar" + assert samples[0]["settlement_station_code"] == "LTAC" + assert samples[0]["truth_version"] == "v1" diff --git a/tests/test_runtime_state_storage.py b/tests/test_runtime_state_storage.py index 8a469371..16b13ee6 100644 --- a/tests/test_runtime_state_storage.py +++ b/tests/test_runtime_state_storage.py @@ -7,6 +7,9 @@ from src.database.runtime_state import ( ProbabilitySnapshotRepository, RuntimeStateDB, TelegramAlertStateRepository, + TrainingFeatureRecordRepository, + TruthRecordRepository, + TruthRevisionRepository, ) @@ -72,3 +75,64 @@ def test_open_meteo_cache_repository_roundtrip(tmp_path, monkeypatch): loaded = repo.load_payload(86400) assert loaded['forecast']['ankara']['temp'] == 15 assert loaded['ensemble']['ankara']['spread'] == 1.5 + + +def test_truth_record_repository_tracks_revisions(tmp_path, monkeypatch): + monkeypatch.setenv('POLYWEATHER_DB_PATH', str(tmp_path / 'polyweather.db')) + db = RuntimeStateDB(str(tmp_path / 'polyweather.db')) + truth_repo = TruthRecordRepository(db) + revision_repo = TruthRevisionRepository(db) + + changed = truth_repo.upsert_truth( + city='taipei', + target_date='2026-04-01', + actual_high=18.0, + settlement_source='wunderground', + settlement_station_code='RCSS', + settlement_station_label='Taipei Songshan Airport Station', + truth_version='v1', + updated_by='test:first', + source_payload={'raw_max_temp_c': 18.4}, + reason='initial', + ) + assert changed is True + assert revision_repo.load_revisions('taipei', '2026-04-01') == [] + + changed = truth_repo.upsert_truth( + city='taipei', + target_date='2026-04-01', + actual_high=19.0, + settlement_source='wunderground', + settlement_station_code='RCSS', + settlement_station_label='Taipei Songshan Airport Station', + truth_version='v1', + updated_by='test:second', + source_payload={'raw_max_temp_c': 19.1}, + reason='correction', + ) + assert changed is True + loaded = truth_repo.get_record('taipei', '2026-04-01') + assert loaded['actual_high'] == 19.0 + revisions = revision_repo.load_revisions('taipei', '2026-04-01') + assert len(revisions) == 1 + assert revisions[0]['previous_actual_high'] == 18.0 + assert revisions[0]['next_actual_high'] == 19.0 + + +def test_training_feature_record_repository_roundtrip(tmp_path, monkeypatch): + monkeypatch.setenv('POLYWEATHER_DB_PATH', str(tmp_path / 'polyweather.db')) + db = RuntimeStateDB(str(tmp_path / 'polyweather.db')) + repo = TrainingFeatureRecordRepository(db) + repo.upsert_record( + 'ankara', + '2026-03-20', + { + 'forecasts': {'ECMWF': 12.3}, + 'deb_prediction': 12.1, + 'mu': 12.0, + 'probability_features': {'ens_median': 12.2}, + }, + ) + loaded = repo.get_record('ankara', '2026-03-20') + assert loaded['forecasts']['ECMWF'] == 12.3 + assert loaded['deb_prediction'] == 12.1 diff --git a/tests/test_web_observability.py b/tests/test_web_observability.py index 99dcbf56..2e305949 100644 --- a/tests/test_web_observability.py +++ b/tests/test_web_observability.py @@ -23,12 +23,19 @@ def test_system_status_returns_summary_shape(): assert response.status_code == 200 payload = response.json() assert 'db' in payload + assert 'state_storage_mode' in payload assert 'features' in payload assert 'integrations' in payload assert 'cache' in payload assert 'probability' in payload assert 'rollout' in payload['probability'] assert payload['probability']['rollout']['decision']['decision'] in {'hold', 'observe', 'promote'} + assert 'training_data' in payload + assert 'truth_records' in payload['training_data'] + assert 'training_features' in payload['training_data'] + assert 'city_coverage' in payload['training_data'] + assert 'model_city_coverage' in payload['training_data'] + assert 'artifacts' in payload['training_data'] assert 'cities_count' in payload diff --git a/tests/test_wunderground_history.py b/tests/test_wunderground_history.py new file mode 100644 index 00000000..e40ecb85 --- /dev/null +++ b/tests/test_wunderground_history.py @@ -0,0 +1,44 @@ +from src.data_collection.wunderground_sources import fetch_wunderground_historical_high + + +class _FakeResponse: + def __init__(self, text: str): + self.text = text + + def raise_for_status(self): + return None + + +class _FakeSession: + def __init__(self, text: str): + self._text = text + + def get(self, url, headers=None, timeout=None): + return _FakeResponse(self._text) + + +def test_fetch_wunderground_historical_high_parses_daily_max(): + html = """ + + + + """ + result = fetch_wunderground_historical_high( + "taipei", + "2026-04-05", + session=_FakeSession(html), + ) + assert result["ok"] is True + assert result["settlement_source"] == "wunderground" + assert result["settlement_station_code"] == "RCSS" + assert result["actual_high"] == 29.0 diff --git a/web/core.py b/web/core.py index 977a60c5..81384145 100644 --- a/web/core.py +++ b/web/core.py @@ -2,6 +2,7 @@ PolyWeather Web Core Context """ +import json import os import sqlite3 import time @@ -135,6 +136,18 @@ _PROBABILITY_SHADOW_REPORT = os.path.join( "probability_calibration", "shadow_report.json", ) +_PROBABILITY_TRAINING_SAMPLES = os.path.join( + _PROJECT_ROOT, + "artifacts", + "probability_calibration", + "training_samples.json", +) +_LGBM_SCHEMA_REPORT = os.path.join( + _PROJECT_ROOT, + "artifacts", + "models", + "lgbm_daily_high_schema.json", +) def _env_bool(name: str, default: bool = False) -> bool: @@ -436,6 +449,282 @@ def _probability_summary() -> Dict[str, Any]: } +def _read_json_file(path: str) -> Optional[Dict[str, Any]]: + if not os.path.exists(path): + return None + try: + with open(path, "r", encoding="utf-8") as fh: + payload = json.load(fh) + if isinstance(payload, dict): + return payload + except Exception: + return None + return None + + +def _table_date_summary(conn: sqlite3.Connection, table_name: str) -> Dict[str, Any]: + try: + row = conn.execute( + f""" + SELECT COUNT(*) AS row_count, + COUNT(DISTINCT city) AS cities_count, + MIN(target_date) AS min_date, + MAX(target_date) AS max_date + FROM {table_name} + """ + ).fetchone() + except Exception as exc: + return {"ok": False, "error": str(exc), "row_count": 0, "cities_count": 0} + + return { + "ok": True, + "row_count": int(row["row_count"] or 0), + "cities_count": int(row["cities_count"] or 0), + "min_date": row["min_date"], + "max_date": row["max_date"], + } + + +def _truth_source_counts(conn: sqlite3.Connection) -> Dict[str, int]: + try: + rows = conn.execute( + """ + SELECT COALESCE(NULLIF(TRIM(settlement_source), ''), 'unknown') AS settlement_source, + COUNT(*) AS row_count + FROM truth_records_store + GROUP BY COALESCE(NULLIF(TRIM(settlement_source), ''), 'unknown') + ORDER BY row_count DESC, settlement_source ASC + """ + ).fetchall() + except Exception: + return {} + return {str(row["settlement_source"]): int(row["row_count"] or 0) for row in rows} + + +def _truth_revisions_summary(conn: sqlite3.Connection) -> Dict[str, Any]: + try: + row = conn.execute( + """ + SELECT COUNT(*) AS row_count, + MAX(updated_at) AS last_updated_at + FROM truth_revisions_store + """ + ).fetchone() + except Exception as exc: + return {"ok": False, "error": str(exc), "row_count": 0} + return { + "ok": True, + "row_count": int(row["row_count"] or 0), + "last_updated_at": row["last_updated_at"], + } + + +def _city_coverage_summary(conn: sqlite3.Connection) -> Dict[str, Any]: + truth_rows = conn.execute( + """ + SELECT city, COUNT(*) AS row_count, MIN(target_date) AS min_date, MAX(target_date) AS max_date + FROM truth_records_store + GROUP BY city + """ + ).fetchall() + feature_rows = conn.execute( + """ + SELECT city, COUNT(*) AS row_count, MIN(target_date) AS min_date, MAX(target_date) AS max_date + FROM training_feature_records_store + GROUP BY city + """ + ).fetchall() + + truth_index = { + str(row["city"]): { + "truth_rows": int(row["row_count"] or 0), + "truth_min_date": row["min_date"], + "truth_max_date": row["max_date"], + } + for row in truth_rows + } + feature_index = { + str(row["city"]): { + "feature_rows": int(row["row_count"] or 0), + "feature_min_date": row["min_date"], + "feature_max_date": row["max_date"], + } + for row in feature_rows + } + + entries = [] + for city, meta in CITY_REGISTRY.items(): + truth_payload = truth_index.get(city, {}) + feature_payload = feature_index.get(city, {}) + entries.append( + { + "city": city, + "name": str(meta.get("name") or city), + "settlement_source": str(meta.get("settlement_source") or "metar"), + "settlement_station_code": str(meta.get("settlement_station_code") or meta.get("icao") or ""), + "truth_rows": int(truth_payload.get("truth_rows") or 0), + "feature_rows": int(feature_payload.get("feature_rows") or 0), + "truth_min_date": truth_payload.get("truth_min_date"), + "truth_max_date": truth_payload.get("truth_max_date"), + "feature_min_date": feature_payload.get("feature_min_date"), + "feature_max_date": feature_payload.get("feature_max_date"), + } + ) + + highlighted = [ + entry + for entry in entries + if entry["city"] in {"taipei", "shenzhen"} + ] + gaps = sorted( + entries, + key=lambda entry: ( + entry["feature_rows"] > 0, + entry["truth_rows"] > 0, + entry["truth_rows"], + entry["feature_rows"], + entry["city"], + ), + )[:10] + return { + "total_cities": len(entries), + "with_truth_rows": sum(1 for entry in entries if entry["truth_rows"] > 0), + "with_feature_rows": sum(1 for entry in entries if entry["feature_rows"] > 0), + "entries": entries, + "highlighted": highlighted, + "top_gaps": gaps, + } + + +def _model_city_coverage_summary( + city_entries: Any, + training_samples_payload: Dict[str, Any], + evaluation_report: Dict[str, Any], +) -> Dict[str, Any]: + training_samples = training_samples_payload.get("samples") or [] + emos_training_counts: Dict[str, int] = {} + emos_snapshot_counts: Dict[str, int] = {} + for item in training_samples: + if not isinstance(item, dict): + continue + city = str(item.get("city") or "").strip().lower() + if not city: + continue + emos_training_counts[city] = emos_training_counts.get(city, 0) + 1 + if str(item.get("sample_source") or "").strip().lower() == "snapshot": + emos_snapshot_counts[city] = emos_snapshot_counts.get(city, 0) + 1 + + evaluation_by_city = (evaluation_report.get("by_city") or {}) if isinstance(evaluation_report, dict) else {} + rows = [] + for entry in city_entries or []: + city = str(entry.get("city") or "").strip().lower() + emos_eval = evaluation_by_city.get(city) or {} + rows.append( + { + "city": city, + "name": entry.get("name") or city, + "settlement_source": entry.get("settlement_source"), + "truth_rows": int(entry.get("truth_rows") or 0), + "feature_rows": int(entry.get("feature_rows") or 0), + "emos_training_samples": int(emos_training_counts.get(city, 0)), + "emos_snapshot_samples": int(emos_snapshot_counts.get(city, 0)), + "emos_evaluation_samples": int(emos_eval.get("samples") or 0), + "emos_delta_crps": emos_eval.get("emos_mean_crps"), + "lgbm_candidate_rows": int(entry.get("feature_rows") or 0), + } + ) + + weakest = sorted( + rows, + key=lambda row: ( + row["emos_training_samples"] > 0, + row["lgbm_candidate_rows"] > 0, + row["truth_rows"] > 0, + row["emos_training_samples"], + row["lgbm_candidate_rows"], + row["truth_rows"], + row["city"], + ), + )[:12] + strongest = sorted( + rows, + key=lambda row: ( + -row["emos_training_samples"], + -row["lgbm_candidate_rows"], + -row["truth_rows"], + row["city"], + ), + )[:8] + return { + "cities_with_emos_training": sum(1 for row in rows if row["emos_training_samples"] > 0), + "cities_with_lgbm_candidates": sum(1 for row in rows if row["lgbm_candidate_rows"] > 0), + "weakest": weakest, + "strongest": strongest, + } + + +def _training_data_summary() -> Dict[str, Any]: + db_path = _account_db.db_path + truth_records = {"ok": False, "row_count": 0, "cities_count": 0} + truth_revisions = {"ok": False, "row_count": 0} + training_features = {"ok": False, "row_count": 0, "cities_count": 0} + try: + with sqlite3.connect(db_path) as conn: + conn.row_factory = sqlite3.Row + truth_records = _table_date_summary(conn, "truth_records_store") + if truth_records.get("ok"): + truth_records["source_counts"] = _truth_source_counts(conn) + truth_revisions = _truth_revisions_summary(conn) + training_features = _table_date_summary(conn, "training_feature_records_store") + city_coverage = _city_coverage_summary(conn) + except Exception as exc: + return { + "db_path": db_path, + "db_ok": False, + "error": str(exc), + "truth_records": truth_records, + "truth_revisions": truth_revisions, + "training_features": training_features, + "city_coverage": {}, + "artifacts": {}, + } + + evaluation_report = _read_json_file(_PROBABILITY_EVALUATION_REPORT) or {} + shadow_report = _read_json_file(_PROBABILITY_SHADOW_REPORT) or {} + training_samples = _read_json_file(_PROBABILITY_TRAINING_SAMPLES) or {} + lgbm_report = _read_json_file(_LGBM_SCHEMA_REPORT) or {} + evaluation_summary = (evaluation_report.get("summary") or {}) if isinstance(evaluation_report, dict) else {} + shadow_summary = shadow_report.get("summary") or {} + lgbm_validation = ((lgbm_report.get("metrics") or {}).get("validation") or {}) + + return { + "db_path": db_path, + "db_ok": True, + "truth_records": truth_records, + "truth_revisions": truth_revisions, + "training_features": training_features, + "city_coverage": city_coverage, + "model_city_coverage": _model_city_coverage_summary( + city_coverage.get("entries") or [], + training_samples, + evaluation_report, + ), + "artifacts": { + "emos_training_samples": training_samples.get("sample_count"), + "emos_snapshot_samples": training_samples.get("snapshot_sample_count"), + "emos_daily_record_samples": training_samples.get("daily_record_sample_count"), + "emos_evaluation_samples": evaluation_summary.get("sample_count"), + "emos_shadow_samples": shadow_summary.get("sample_count"), + "emos_delta_crps": (evaluation_summary.get("delta") or {}).get("crps"), + "lgbm_sample_count": lgbm_report.get("sample_count"), + "lgbm_train_count": lgbm_report.get("train_count"), + "lgbm_validation_count": lgbm_report.get("validation_count"), + "lgbm_validation_mae": lgbm_validation.get("lgbm_mae"), + "lgbm_validation_deb_mae": lgbm_validation.get("deb_mae"), + }, + } + + def build_health_payload() -> Dict[str, Any]: db = _sqlite_health() return { @@ -451,11 +740,13 @@ def build_system_status_payload() -> Dict[str, Any]: return { "status": build_health_payload()["status"], "time_utc": datetime.now(timezone.utc).isoformat(), + "state_storage_mode": get_state_storage_mode(), "db": _sqlite_health(), "features": _feature_flags_summary(), "integrations": _integration_summary(), "cache": _cache_summary(), "metrics": build_metrics_summary(), "probability": _probability_summary(), + "training_data": _training_data_summary(), "cities_count": len(CITIES), }