Add Deep Learning (LSTM) + Quick Wins automation features

Major improvements:
- Deep Learning integration with PyTorch LSTM (Bidirectional, 128→64 units)
- Hybrid predictor: 40% RandomForest + 60% Deep Learning
- LaunchAgent for automatic weekly tip generation (Tue/Fri 21:00)
- Health-Check system with auto-recovery and Telegram alerts
- Model caching and intelligent retraining logic
- Updated CSV data and generated tips
- Performance reports for recent draws

Technical details:
- PyTorch used instead of TensorFlow (Python 3.14 compatibility)
- Apple Silicon MPS acceleration support
- Sequence learning with 20-draw history
- Early stopping and learning rate scheduling

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-07 09:20:49 +01:00
co-authored by Claude Sonnet 4.5
parent f6106b8333
commit 428403557e
33 changed files with 31776 additions and 62 deletions
+6
View File
@@ -4967,3 +4967,9 @@ datum;Z1;Z2;Z3;Z4;Z5;Z6;SZ
2025-12-06;15;26;27;33;35;37;2.0
2025-12-10;3;11;12;13;17;24;8.0
2025-12-13;6;11;21;32;37;45;3.0
2025-12-17;6;13;25;26;32;37;2.0
2025-12-20;12;23;32;33;40;42;5.0
2025-12-24;7;8;25;32;37;39;8.0
2025-12-27;7;10;27;28;38;46;6.0
2025-12-31;3;5;7;28;36;41;5.0
2026-01-03;3;8;11;12;16;46;8.0
1 datum Z1 Z2 Z3 Z4 Z5 Z6 SZ
4967 2025-12-06 15 26 27 33 35 37 2.0
4968 2025-12-10 3 11 12 13 17 24 8.0
4969 2025-12-13 6 11 21 32 37 45 3.0
4970 2025-12-17 6 13 25 26 32 37 2.0
4971 2025-12-20 12 23 32 33 40 42 5.0
4972 2025-12-24 7 8 25 32 37 39 8.0
4973 2025-12-27 7 10 27 28 38 46 6.0
4974 2025-12-31 3 5 7 28 36 41 5.0
4975 2026-01-03 3 8 11 12 16 46 8.0
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -62,6 +62,62 @@
"file": "weekly_lotto_tips_20251216_090036.csv",
"avg_confidence": 0.2526153083488077,
"avg_quality": 0.4270460107501856
},
{
"timestamp": "2025-12-19T09:00:47.995332",
"num_tips": 10,
"file": "weekly_lotto_tips_20251219_090047.csv",
"avg_confidence": 0.24851173163378942,
"avg_quality": 0.42254649408361306
},
{
"timestamp": "2025-12-23T09:00:39.273434",
"num_tips": 10,
"file": "weekly_lotto_tips_20251223_090039.csv",
"avg_confidence": 0.24564556363017442,
"avg_quality": 0.42297042510202276
},
{
"timestamp": "2025-12-26T09:00:29.472712",
"num_tips": 10,
"file": "weekly_lotto_tips_20251226_090029.csv",
"avg_confidence": 0.24881269580518275,
"avg_quality": 0.4232548718131768
},
{
"timestamp": "2025-12-30T09:00:29.644794",
"num_tips": 10,
"file": "weekly_lotto_tips_20251230_090029.csv",
"avg_confidence": 0.2493553812828854,
"avg_quality": 0.422702688633027
},
{
"timestamp": "2026-01-05T11:57:12.284547",
"num_tips": 10,
"file": "weekly_lotto_tips_20260105_115712.csv",
"avg_confidence": 0.2534916290186907,
"avg_quality": 0.4275052872775139
},
{
"timestamp": "2026-01-05T14:18:43.847636",
"num_tips": 5,
"file": "weekly_lotto_tips_20260105_141843.csv",
"avg_confidence": 0.22396252964735192,
"avg_quality": 0.4176696066987704
},
{
"timestamp": "2026-01-05T14:26:54.619984",
"num_tips": 5,
"file": "weekly_lotto_tips_20260105_142654.csv",
"avg_confidence": 0.38086368558448874,
"avg_quality": 0.5874078043214264
},
{
"timestamp": "2026-01-05T19:31:53.727772",
"num_tips": 10,
"file": "weekly_lotto_tips_20260105_193153.csv",
"avg_confidence": 0.4169894391741245,
"avg_quality": 0.6052850635944613
}
]
}
@@ -0,0 +1,11 @@
Tip_Number,Numbers,Superzahl,Strategy,AI_Score,Pattern_Weight,Confidence,Quality
1,12-14-17-45-46-48,9,HYBRID-OPT,0.2117,0.0976,0.3294,0.4224
2,1-12-23-32-45-46,9,HYBRID-OPT,0.2040,0.1473,0.3434,0.4340
3,3-8-14-17-21-46,9,HYBRID-OPT,0.2185,0.0870,0.3287,0.4242
4,7-8-28-45-46-48,8,HYBRID-OPT,0.2617,0.0976,0.3519,0.4478
5,6-8-14-21-28-46,7,HYBRID-OPT,0.2717,0.0870,0.3527,0.4473
6,5-8-9-12-16-46,5,PURE-AI,0.1900,0.0046,0.1529,0.3874
7,2-3-7-25-32-46,7,PURE-AI,0.1141,0.0870,0.1087,0.3761
8,8-13-21-28-44-46,6,ENSEMBLE,0.2620,0.1473,0.2047,0.4621
9,8-12-13-21-28-46,3,ENSEMBLE,0.2900,0.0870,0.1885,0.4519
10,2-9-24-30-35-49,9,PURE-PATTERN,0.0700,0.1473,0.1241,0.3722
1 Tip_Number Numbers Superzahl Strategy AI_Score Pattern_Weight Confidence Quality
2 1 12-14-17-45-46-48 9 HYBRID-OPT 0.2117 0.0976 0.3294 0.4224
3 2 1-12-23-32-45-46 9 HYBRID-OPT 0.2040 0.1473 0.3434 0.4340
4 3 3-8-14-17-21-46 9 HYBRID-OPT 0.2185 0.0870 0.3287 0.4242
5 4 7-8-28-45-46-48 8 HYBRID-OPT 0.2617 0.0976 0.3519 0.4478
6 5 6-8-14-21-28-46 7 HYBRID-OPT 0.2717 0.0870 0.3527 0.4473
7 6 5-8-9-12-16-46 5 PURE-AI 0.1900 0.0046 0.1529 0.3874
8 7 2-3-7-25-32-46 7 PURE-AI 0.1141 0.0870 0.1087 0.3761
9 8 8-13-21-28-44-46 6 ENSEMBLE 0.2620 0.1473 0.2047 0.4621
10 9 8-12-13-21-28-46 3 ENSEMBLE 0.2900 0.0870 0.1885 0.4519
11 10 2-9-24-30-35-49 9 PURE-PATTERN 0.0700 0.1473 0.1241 0.3722
@@ -0,0 +1,11 @@
Tip_Number,Numbers,Superzahl,Strategy,AI_Score,Pattern_Weight,Confidence,Quality
1,8-21-23-29-33-46,0,HYBRID-OPT,0.1950,0.0909,0.3196,0.4164
2,2-8-14-28-32-45,0,HYBRID-OPT,0.2299,0.0869,0.3339,0.4325
3,12-15-21-28-42-46,0,HYBRID-OPT,0.1900,0.1473,0.3370,0.4310
4,4-8-28-45-46-48,8,HYBRID-OPT,0.2402,0.0976,0.3423,0.4409
5,6-8-14-21-28-46,5,HYBRID-OPT,0.2620,0.0869,0.3483,0.4448
6,1-6-8-23-28-40,7,PURE-AI,0.1775,0.0869,0.1594,0.4091
7,3-12-19-21-46-48,7,PURE-AI,0.1280,0.1473,0.1319,0.4016
8,8-12-13-21-28-46,6,ENSEMBLE,0.2898,0.0869,0.1883,0.4517
9,8-12-13-21-28-46,3,ENSEMBLE,0.2898,0.0869,0.1883,0.4517
10,2-9-24-30-35-49,0,PURE-PATTERN,0.0145,0.1473,0.1074,0.3500
1 Tip_Number Numbers Superzahl Strategy AI_Score Pattern_Weight Confidence Quality
2 1 8-21-23-29-33-46 0 HYBRID-OPT 0.1950 0.0909 0.3196 0.4164
3 2 2-8-14-28-32-45 0 HYBRID-OPT 0.2299 0.0869 0.3339 0.4325
4 3 12-15-21-28-42-46 0 HYBRID-OPT 0.1900 0.1473 0.3370 0.4310
5 4 4-8-28-45-46-48 8 HYBRID-OPT 0.2402 0.0976 0.3423 0.4409
6 5 6-8-14-21-28-46 5 HYBRID-OPT 0.2620 0.0869 0.3483 0.4448
7 6 1-6-8-23-28-40 7 PURE-AI 0.1775 0.0869 0.1594 0.4091
8 7 3-12-19-21-46-48 7 PURE-AI 0.1280 0.1473 0.1319 0.4016
9 8 8-12-13-21-28-46 6 ENSEMBLE 0.2898 0.0869 0.1883 0.4517
10 9 8-12-13-21-28-46 3 ENSEMBLE 0.2898 0.0869 0.1883 0.4517
11 10 2-9-24-30-35-49 0 PURE-PATTERN 0.0145 0.1473 0.1074 0.3500
@@ -0,0 +1,11 @@
Tip_Number,Numbers,Superzahl,Strategy,AI_Score,Pattern_Weight,Confidence,Quality
1,1-12-21-28-39-46,0,HYBRID-OPT,0.2145,0.1475,0.3481,0.4397
2,2-8-14-28-32-45,0,HYBRID-OPT,0.2556,0.0869,0.3454,0.4409
3,3-12-23-28-33-45,0,HYBRID-OPT,0.2199,0.1475,0.3506,0.4416
4,1-2-21-28-42-46,5,HYBRID-OPT,0.1943,0.1475,0.3390,0.4314
5,6-8-14-21-28-46,1,HYBRID-OPT,0.2743,0.0869,0.3538,0.4479
6,1-5-8-12-33-46,7,PURE-AI,0.1998,0.0179,0.1634,0.3950
7,3-17-25-37-39-46,7,PURE-AI,0.1299,0.0861,0.1212,0.3824
8,8-12-13-21-28-37,6,ENSEMBLE,0.2498,0.0869,0.1683,0.4369
9,8-12-13-21-28-37,3,ENSEMBLE,0.2498,0.0869,0.1683,0.4369
10,2-9-24-30-35-49,0,PURE-PATTERN,0.0890,0.1475,0.1299,0.3798
1 Tip_Number Numbers Superzahl Strategy AI_Score Pattern_Weight Confidence Quality
2 1 1-12-21-28-39-46 0 HYBRID-OPT 0.2145 0.1475 0.3481 0.4397
3 2 2-8-14-28-32-45 0 HYBRID-OPT 0.2556 0.0869 0.3454 0.4409
4 3 3-12-23-28-33-45 0 HYBRID-OPT 0.2199 0.1475 0.3506 0.4416
5 4 1-2-21-28-42-46 5 HYBRID-OPT 0.1943 0.1475 0.3390 0.4314
6 5 6-8-14-21-28-46 1 HYBRID-OPT 0.2743 0.0869 0.3538 0.4479
7 6 1-5-8-12-33-46 7 PURE-AI 0.1998 0.0179 0.1634 0.3950
8 7 3-17-25-37-39-46 7 PURE-AI 0.1299 0.0861 0.1212 0.3824
9 8 8-12-13-21-28-37 6 ENSEMBLE 0.2498 0.0869 0.1683 0.4369
10 9 8-12-13-21-28-37 3 ENSEMBLE 0.2498 0.0869 0.1683 0.4369
11 10 2-9-24-30-35-49 0 PURE-PATTERN 0.0890 0.1475 0.1299 0.3798
@@ -0,0 +1,11 @@
Tip_Number,Numbers,Superzahl,Strategy,AI_Score,Pattern_Weight,Confidence,Quality
1,1-12-21-28-38-46,6,HYBRID-OPT,0.2045,0.1476,0.3437,0.4363
2,2-8-21-23-40-45,6,HYBRID-OPT,0.2043,0.1476,0.3436,0.4358
3,8-12-23-25-33-45,6,HYBRID-OPT,0.2097,0.1476,0.3461,0.4382
4,8-28-39-45-46-48,5,HYBRID-OPT,0.2574,0.0392,0.3296,0.4289
5,4-8-21-32-38-45,1,HYBRID-OPT,0.1751,0.1476,0.3305,0.4234
6,1-8-13-28-33-38,7,PURE-AI,0.1995,0.0760,0.1748,0.4145
7,7-10-11-37-46-48,7,PURE-AI,0.1077,0.0312,0.0924,0.3567
8,8-13-21-28-37-46,6,ENSEMBLE,0.2645,0.1476,0.2061,0.4627
9,8-13-21-28-37-46,3,ENSEMBLE,0.2645,0.1476,0.2061,0.4627
10,2-9-24-30-35-49,6,PURE-PATTERN,0.0585,0.1476,0.1209,0.3677
1 Tip_Number Numbers Superzahl Strategy AI_Score Pattern_Weight Confidence Quality
2 1 1-12-21-28-38-46 6 HYBRID-OPT 0.2045 0.1476 0.3437 0.4363
3 2 2-8-21-23-40-45 6 HYBRID-OPT 0.2043 0.1476 0.3436 0.4358
4 3 8-12-23-25-33-45 6 HYBRID-OPT 0.2097 0.1476 0.3461 0.4382
5 4 8-28-39-45-46-48 5 HYBRID-OPT 0.2574 0.0392 0.3296 0.4289
6 5 4-8-21-32-38-45 1 HYBRID-OPT 0.1751 0.1476 0.3305 0.4234
7 6 1-8-13-28-33-38 7 PURE-AI 0.1995 0.0760 0.1748 0.4145
8 7 7-10-11-37-46-48 7 PURE-AI 0.1077 0.0312 0.0924 0.3567
9 8 8-13-21-28-37-46 6 ENSEMBLE 0.2645 0.1476 0.2061 0.4627
10 9 8-13-21-28-37-46 3 ENSEMBLE 0.2645 0.1476 0.2061 0.4627
11 10 2-9-24-30-35-49 6 PURE-PATTERN 0.0585 0.1476 0.1209 0.3677
@@ -0,0 +1,11 @@
Tip_Number,Numbers,Superzahl,Strategy,AI_Score,Pattern_Weight,Confidence,Quality
1,8-13-27-28-33-37,8,HYBRID-OPT,0.2045,0.1476,0.3437,0.4379
2,2-8-14-28-32-45,8,HYBRID-OPT,0.2483,0.0869,0.3421,0.4385
3,3-8-14-17-21-46,8,HYBRID-OPT,0.2177,0.0869,0.3284,0.4239
4,1-11-25-28-45-46,0,HYBRID-OPT,0.2043,0.1476,0.3436,0.4372
5,6-8-14-21-28-46,1,HYBRID-OPT,0.2709,0.0869,0.3523,0.4471
6,8-10-13-25-28-40,2,PURE-AI,0.2045,0.0869,0.1810,0.4197
7,7-9-11-32-38-46,7,PURE-AI,0.1140,0.0762,0.1064,0.3729
8,8-13-21-28-37-46,6,ENSEMBLE,0.2662,0.1476,0.2069,0.4632
9,8-13-21-28-37-46,3,ENSEMBLE,0.2662,0.1476,0.2069,0.4632
10,2-9-24-30-35-49,8,PURE-PATTERN,0.0680,0.1476,0.1237,0.3715
1 Tip_Number Numbers Superzahl Strategy AI_Score Pattern_Weight Confidence Quality
2 1 8-13-27-28-33-37 8 HYBRID-OPT 0.2045 0.1476 0.3437 0.4379
3 2 2-8-14-28-32-45 8 HYBRID-OPT 0.2483 0.0869 0.3421 0.4385
4 3 3-8-14-17-21-46 8 HYBRID-OPT 0.2177 0.0869 0.3284 0.4239
5 4 1-11-25-28-45-46 0 HYBRID-OPT 0.2043 0.1476 0.3436 0.4372
6 5 6-8-14-21-28-46 1 HYBRID-OPT 0.2709 0.0869 0.3523 0.4471
7 6 8-10-13-25-28-40 2 PURE-AI 0.2045 0.0869 0.1810 0.4197
8 7 7-9-11-32-38-46 7 PURE-AI 0.1140 0.0762 0.1064 0.3729
9 8 8-13-21-28-37-46 6 ENSEMBLE 0.2662 0.1476 0.2069 0.4632
10 9 8-13-21-28-37-46 3 ENSEMBLE 0.2662 0.1476 0.2069 0.4632
11 10 2-9-24-30-35-49 8 PURE-PATTERN 0.0680 0.1476 0.1237 0.3715
@@ -0,0 +1,6 @@
Tip_Number,Numbers,Superzahl,Strategy,AI_Score,Pattern_Weight,Confidence,Quality
1,8-13-27-28-33-37,8,HYBRID-OPT,0.2045,0.1476,0.3437,0.4379
2,2-8-14-28-32-45,8,HYBRID-OPT,0.2483,0.0869,0.3421,0.4385
3,3-12-14-37-39-45,8,PURE-AI,0.1644,0.0312,0.1377,0.3816
4,8-13-21-28-37-46,0,ENSEMBLE,0.2662,0.1476,0.2069,0.4632
5,3-22-23-36-39-43,1,PURE-PATTERN,0.0972,0.0860,0.0894,0.3672
1 Tip_Number Numbers Superzahl Strategy AI_Score Pattern_Weight Confidence Quality
2 1 8-13-27-28-33-37 8 HYBRID-OPT 0.2045 0.1476 0.3437 0.4379
3 2 2-8-14-28-32-45 8 HYBRID-OPT 0.2483 0.0869 0.3421 0.4385
4 3 3-12-14-37-39-45 8 PURE-AI 0.1644 0.0312 0.1377 0.3816
5 4 8-13-21-28-37-46 0 ENSEMBLE 0.2662 0.1476 0.2069 0.4632
6 5 3-22-23-36-39-43 1 PURE-PATTERN 0.0972 0.0860 0.0894 0.3672
@@ -0,0 +1,6 @@
Tip_Number,Numbers,Superzahl,Strategy,AI_Score,Pattern_Weight,Confidence,Quality
1,8-12-23-28-39-43,8,HYBRID-OPT,0.5407,0.1476,0.4950,0.6337
2,2-8-14-28-32-45,8,HYBRID-OPT,0.5998,0.0869,0.5003,0.6480
3,7-14-21-23-46-47,8,PURE-AI,0.4403,0.1476,0.3817,0.5620
4,8-13-23-28-37-46,0,ENSEMBLE,0.6773,0.1476,0.4124,0.6830
5,3-22-23-36-39-43,1,PURE-PATTERN,0.1821,0.0860,0.1149,0.4103
1 Tip_Number Numbers Superzahl Strategy AI_Score Pattern_Weight Confidence Quality
2 1 8-12-23-28-39-43 8 HYBRID-OPT 0.5407 0.1476 0.4950 0.6337
3 2 2-8-14-28-32-45 8 HYBRID-OPT 0.5998 0.0869 0.5003 0.6480
4 3 7-14-21-23-46-47 8 PURE-AI 0.4403 0.1476 0.3817 0.5620
5 4 8-13-23-28-37-46 0 ENSEMBLE 0.6773 0.1476 0.4124 0.6830
6 5 3-22-23-36-39-43 1 PURE-PATTERN 0.1821 0.0860 0.1149 0.4103
@@ -0,0 +1,11 @@
Tip_Number,Numbers,Superzahl,Strategy,AI_Score,Pattern_Weight,Confidence,Quality
1,8-12-23-28-39-43,8,HYBRID-OPT,0.5407,0.1476,0.4950,0.6337
2,2-8-14-28-32-45,8,HYBRID-OPT,0.5998,0.0869,0.5003,0.6480
3,2-8-13-21-28-38,8,HYBRID-OPT,0.5219,0.0869,0.4653,0.6054
4,7-8-28-37-45-46,0,HYBRID-OPT,0.6448,0.0975,0.5243,0.6669
5,6-8-14-21-28-46,1,HYBRID-OPT,0.6636,0.0869,0.5290,0.6663
6,8-23-27-28-42-47,2,PURE-AI,0.5046,0.0909,0.4219,0.5999
7,6-19-21-37-46-49,7,PURE-AI,0.3274,0.0860,0.2791,0.4843
8,8-13-23-28-37-46,6,ENSEMBLE,0.6773,0.1476,0.4124,0.6830
9,8-13-23-28-37-46,3,ENSEMBLE,0.6773,0.1476,0.4124,0.6830
10,2-9-24-30-35-49,8,PURE-PATTERN,0.0898,0.1476,0.1302,0.3824
1 Tip_Number Numbers Superzahl Strategy AI_Score Pattern_Weight Confidence Quality
2 1 8-12-23-28-39-43 8 HYBRID-OPT 0.5407 0.1476 0.4950 0.6337
3 2 2-8-14-28-32-45 8 HYBRID-OPT 0.5998 0.0869 0.5003 0.6480
4 3 2-8-13-21-28-38 8 HYBRID-OPT 0.5219 0.0869 0.4653 0.6054
5 4 7-8-28-37-45-46 0 HYBRID-OPT 0.6448 0.0975 0.5243 0.6669
6 5 6-8-14-21-28-46 1 HYBRID-OPT 0.6636 0.0869 0.5290 0.6663
7 6 8-23-27-28-42-47 2 PURE-AI 0.5046 0.0909 0.4219 0.5999
8 7 6-19-21-37-46-49 7 PURE-AI 0.3274 0.0860 0.2791 0.4843
9 8 8-13-23-28-37-46 6 ENSEMBLE 0.6773 0.1476 0.4124 0.6830
10 9 8-13-23-28-37-46 3 ENSEMBLE 0.6773 0.1476 0.4124 0.6830
11 10 2-9-24-30-35-49 8 PURE-PATTERN 0.0898 0.1476 0.1302 0.3824
+34
View File
@@ -0,0 +1,34 @@
{
"checks": [
{
"timestamp": "2026-01-06T10:11:32.908565",
"status": "OK",
"issues": [],
"warnings": [
"Learning State: No learning state found",
"Disk Space: 121.4 GB free (13.1%)"
],
"recovered": []
},
{
"timestamp": "2026-01-07T09:13:37.832795",
"status": "OK",
"issues": [],
"warnings": [
"Learning State: No learning state found",
"Disk Space: 115.3 GB free (12.4%)"
],
"recovered": []
},
{
"timestamp": "2026-01-07T09:17:51.274727",
"status": "OK",
"issues": [],
"warnings": [
"Learning State: No learning state found",
"Disk Space: 115.3 GB free (12.4%)"
],
"recovered": []
}
]
}
+52
View File
@@ -43,6 +43,58 @@
"main": 1.2,
"sz_rate": 0.1
}
},
{
"timestamp": "2025-12-22T21:00:50.378355",
"draw_date": "2025-12-20",
"evaluation": {
"main": 3,
"sz": false,
"tip": 2
},
"avg_matches": {
"main": 0.7,
"sz_rate": 0.1
}
},
{
"timestamp": "2025-12-25T21:00:35.769591",
"draw_date": "2025-12-24",
"evaluation": {
"main": 2,
"sz": false,
"tip": 2
},
"avg_matches": {
"main": 0.8,
"sz_rate": 0.1
}
},
{
"timestamp": "2025-12-29T21:00:36.294861",
"draw_date": "2025-12-27",
"evaluation": {
"main": 2,
"sz": false,
"tip": 1
},
"avg_matches": {
"main": 1.2,
"sz_rate": 0.1
}
},
{
"timestamp": "2026-01-05T11:56:39.905126",
"draw_date": "2026-01-03",
"evaluation": {
"main": 2,
"sz": false,
"tip": 1
},
"avg_matches": {
"main": 1.5,
"sz_rate": 0.0
}
}
]
}
@@ -0,0 +1,21 @@
======================================================================
LOTTO 6AUS49 PERFORMANCE REPORT
======================================================================
Erstellt: 2025-12-22 21:00:50
ZIEHUNG VOM 2025-12-20
----------------------------------------------------------------------
Hauptzahlen: [12, 23, 32, 33, 40, 42]
Superzahl: 5
EVALUATION DER TIPPS
----------------------------------------------------------------------
Datei: weekly_lotto_tips_20251219_090047.csv
Tipps evaluiert: 10
Avg Main Matches: 0.70
SZ Match Rate: 10.0%
Bester Tipp: #2
- Main Treffer: 3
- SZ Match: Nein
======================================================================
@@ -0,0 +1,21 @@
======================================================================
LOTTO 6AUS49 PERFORMANCE REPORT
======================================================================
Erstellt: 2025-12-25 21:00:35
ZIEHUNG VOM 2025-12-24
----------------------------------------------------------------------
Hauptzahlen: [7, 8, 25, 32, 37, 39]
Superzahl: 8
EVALUATION DER TIPPS
----------------------------------------------------------------------
Datei: weekly_lotto_tips_20251223_090039.csv
Tipps evaluiert: 10
Avg Main Matches: 0.80
SZ Match Rate: 10.0%
Bester Tipp: #2
- Main Treffer: 2
- SZ Match: Nein
======================================================================
@@ -0,0 +1,21 @@
======================================================================
LOTTO 6AUS49 PERFORMANCE REPORT
======================================================================
Erstellt: 2025-12-29 21:00:36
ZIEHUNG VOM 2025-12-27
----------------------------------------------------------------------
Hauptzahlen: [7, 10, 27, 28, 38, 46]
Superzahl: 6
EVALUATION DER TIPPS
----------------------------------------------------------------------
Datei: weekly_lotto_tips_20251226_090029.csv
Tipps evaluiert: 10
Avg Main Matches: 1.20
SZ Match Rate: 10.0%
Bester Tipp: #1
- Main Treffer: 2
- SZ Match: Nein
======================================================================
@@ -0,0 +1,21 @@
======================================================================
LOTTO 6AUS49 PERFORMANCE REPORT
======================================================================
Erstellt: 2026-01-05 11:56:39
ZIEHUNG VOM 2026-01-03
----------------------------------------------------------------------
Hauptzahlen: [3, 8, 11, 12, 16, 46]
Superzahl: 8
EVALUATION DER TIPPS
----------------------------------------------------------------------
Datei: weekly_lotto_tips_20251230_090029.csv
Tipps evaluiert: 10
Avg Main Matches: 1.50
SZ Match Rate: 0.0%
Bester Tipp: #1
- Main Treffer: 2
- SZ Match: Nein
======================================================================
@@ -0,0 +1,9 @@
{
"trained_at": "2026-01-05T14:26:53.036260",
"num_samples": 4954,
"num_features": 20,
"final_loss": 0.3721399052489188,
"final_val_loss": 0.3729977588499746,
"best_val_loss": 0.37259673303173435,
"epochs_trained": 30
}
@@ -0,0 +1,61 @@
{
"adjustments": {
"1": -0.019751246878125,
"2": -0.019751246878125,
"3": -0.004826246878125001,
"4": -0.019751246878125,
"5": -0.019751246878125,
"6": -0.019751246878125,
"7": 0.009875251246875,
"8": 0.009949876246875,
"9": -0.019751246878125,
"10": -0.004900871878125002,
"11": -0.004826246878125001,
"12": 0.00987599563125,
"13": -0.019751246878125,
"14": -0.019751246878125,
"15": -0.019751246878125,
"16": -0.004826246878125001,
"17": -0.019751246878125,
"18": -0.019751246878125,
"19": -0.019751246878125,
"20": -0.019751246878125,
"21": -0.019751246878125,
"22": -0.019751246878125,
"23": -0.0050490043687499995,
"24": -0.019751246878125,
"25": -0.004975123753125,
"26": -0.019751246878125,
"27": -0.004900871878125002,
"28": -0.004900871878125002,
"29": -0.019751246878125,
"30": -0.019751246878125,
"31": -0.019751246878125,
"32": 0.00972711875625,
"33": -0.0050490043687499995,
"34": -0.019751246878125,
"35": -0.019751246878125,
"36": -0.019751246878125,
"37": -0.004975123753125,
"38": -0.004900871878125002,
"39": -0.004975123753125,
"40": -0.0050490043687499995,
"41": -0.019751246878125,
"42": -0.0050490043687499995,
"43": -0.019751246878125,
"44": -0.019751246878125,
"45": -0.019751246878125,
"46": 0.010024128121874998,
"47": -0.019751246878125,
"48": -0.019751246878125,
"49": -0.019751246878125
},
"stats": {
"incorrect": 172,
"correct": 24,
"cycles": 4,
"last_update": "2026-01-05T11:56:39.904258"
},
"learning_rate": 0.1,
"last_saved": "2026-01-05T11:56:39.904307"
}
Binary file not shown.
+25 -3
View File
@@ -151,7 +151,7 @@ class AutoUpdateAndLearn:
print("\n🎯 EVALUIERE LETZTE TIPPS")
print("=" * 70)
# Finde neueste Tipps-Datei
# Finde Tipps-Datei die VOR der Ziehung generiert wurde
if not os.path.exists(self.tips_dir):
print(" ⚠️ Keine Tipps zum Evaluieren")
return {}
@@ -165,8 +165,30 @@ class AutoUpdateAndLearn:
print(" ⚠️ Keine Tipps-Dateien gefunden")
return {}
latest_tips_file = os.path.join(self.tips_dir, tip_files[0])
print(f" 📁 Evaluiere: {tip_files[0]}")
# Finde Tip-Datei die vor der Ziehung erstellt wurde
draw_date = new_draw['date']
selected_tip_file = None
for tip_file in tip_files:
# Parse Timestamp aus Dateiname: weekly_lotto_tips_YYYYMMDD_HHMMSS.csv
try:
parts = tip_file.replace('.csv', '').split('_')
tip_date_str = parts[-2] # YYYYMMDD
tip_date = pd.to_datetime(tip_date_str, format='%Y%m%d')
# Nehme erste Datei die vor der Ziehung war
if tip_date < draw_date:
selected_tip_file = tip_file
break
except:
continue
if not selected_tip_file:
# Fallback: nehme älteste Datei
selected_tip_file = tip_files[-1]
latest_tips_file = os.path.join(self.tips_dir, selected_tip_file)
print(f" 📁 Evaluiere: {selected_tip_file}")
try:
tips_df = pd.read_csv(latest_tips_file)
+71
View File
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- Label - Eindeutige Identifier -->
<key>Label</key>
<string>com.lotto.weekly</string>
<!-- Programm-Pfad -->
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>cd "/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Lotto" &amp;&amp; source venv/bin/activate &amp;&amp; python scripts/automation/weekly_tip_generator.py 2>&1 | tee -a logs/launchagent.log</string>
</array>
<!-- Zeitplan: Dienstag und Freitag um 21:00 -->
<key>StartCalendarInterval</key>
<array>
<!-- Dienstag 21:00 (vor Mittwoch-Ziehung) -->
<dict>
<key>Weekday</key>
<integer>2</integer>
<key>Hour</key>
<integer>21</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
<!-- Freitag 21:00 (vor Samstag-Ziehung) -->
<dict>
<key>Weekday</key>
<integer>5</integer>
<key>Hour</key>
<integer>21</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
</array>
<!-- Arbeitsverzeichnis -->
<key>WorkingDirectory</key>
<string>/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Lotto</string>
<!-- Standard Output/Error Logging -->
<key>StandardOutPath</key>
<string>/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Lotto/logs/stdout.log</string>
<key>StandardErrorPath</key>
<string>/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Lotto/logs/stderr.log</string>
<!-- Wichtig: RunAtLoad für sofortigen Test -->
<key>RunAtLoad</key>
<false/>
<!-- Environment Variables -->
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
<key>LANG</key>
<string>de_DE.UTF-8</string>
</dict>
<!-- Wichtig: Auch bei Sleep/Wake ausführen -->
<key>LaunchOnlyOnce</key>
<false/>
<!-- Process Nice Level (niedrigere Priorität) -->
<key>Nice</key>
<integer>10</integer>
</dict>
</plist>
@@ -40,15 +40,34 @@ try:
except ImportError:
ML_AVAILABLE = False
# Deep Learning (optional)
# Deep Learning (optional) - PyTorch or TensorFlow
DEEP_LEARNING_AVAILABLE = False
try:
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Dropout
from tensorflow.keras.optimizers import Adam
import torch
DEEP_LEARNING_AVAILABLE = True
except ImportError:
DEEP_LEARNING_AVAILABLE = False
try:
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Dropout
from tensorflow.keras.optimizers import Adam
DEEP_LEARNING_AVAILABLE = True
except ImportError:
DEEP_LEARNING_AVAILABLE = False
# Import Deep Learning Engine (PyTorch-based for Python 3.14+ compatibility)
try:
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'utils'))
from deep_learning_engine_pytorch import DeepLearningEngine, HybridDeepLearningPredictor
DL_ENGINE_AVAILABLE = True
except ImportError:
try:
from deep_learning_engine import DeepLearningEngine, HybridDeepLearningPredictor
DL_ENGINE_AVAILABLE = True
except ImportError:
DL_ENGINE_AVAILABLE = False
DeepLearningEngine = None
HybridDeepLearningPredictor = None
class UltimateAIMLHybridGenerator:
@@ -159,11 +178,13 @@ class UltimateAIMLHybridGenerator:
"""Zeigt System-Status."""
print("\n📊 SYSTEM STATUS:")
print(f" 🧠 ML Available: {'' if ML_AVAILABLE else '❌ (pip install scikit-learn)'}")
print(f" 🚀 Deep Learning: {'' if DEEP_LEARNING_AVAILABLE else '⚠️ Optional (pip install tensorflow)'}")
print(f" 🚀 Deep Learning: {' ACTIVE' if self.ai_ml_engine.use_deep_learning else '⚠️ Optional (pip install tensorflow)'}")
print(f" 🎯 Models Trained: {'' if self.is_trained else '⚠️ Using frequency-based fallback'}")
print(f" 📁 Data Size: {len(self.df):,} drawings")
print(f" 🎨 Patterns: {len(self.pattern_engine.pattern_frequencies)}")
print(f" ⚡ Fast Mode: {'ON' if self.fast_mode else 'OFF'}")
if self.ai_ml_engine.use_deep_learning:
print(f" 🔮 LSTM: Hybrid Mode (RF 40% + DL 60%)")
def generate_ultimate_tips(self, num_tips=10):
"""
@@ -586,19 +607,26 @@ class UltimateAIMLHybridGenerator:
# ============================================================================
class AIMLEngine:
"""AI/ML Engine - OPTIMIZED."""
"""AI/ML Engine - OPTIMIZED mit Deep Learning Support."""
def __init__(self, fast_mode=True):
self.models = {}
self.trained_models = {}
self.scaler = StandardScaler()
self.is_trained = False
self.fast_mode = fast_mode
# Deep Learning
self.dl_engine = None
self.hybrid_predictor = None
self.use_deep_learning = DL_ENGINE_AVAILABLE and DEEP_LEARNING_AVAILABLE
def initialize(self, df, features_df, cache_path):
"""Initialisiert AI/ML System mit intelligentem Retraining."""
self.cache_path = cache_path
self.data_file = None # Wird vom Generator gesetzt
self.df = df # Store for DL
self.features_df = features_df # Store for DL
if not ML_AVAILABLE:
print(" ⚠️ ML not available - using frequency-based fallback")
@@ -630,6 +658,36 @@ class AIMLEngine:
else:
print(" ⚠️ Insufficient data - using fallback")
# Initialize Deep Learning
if self.use_deep_learning:
print(" 🧠 Initializing Deep Learning (LSTM)...", end=" ", flush=True)
try:
self.dl_engine = DeepLearningEngine(
num_numbers=49,
sequence_length=20,
cache_dir=os.path.join(cache_path, 'deep_learning'),
fast_mode=self.fast_mode
)
# Train or load LSTM
self.dl_engine.train(df, features_df, force_retrain=needs_retrain)
# Create hybrid predictor
self.hybrid_predictor = HybridDeepLearningPredictor(
dl_engine=self.dl_engine,
rf_weight=0.4,
dl_weight=0.6
)
print("")
except Exception as e:
print(f"⚠️ DL init failed: {e}")
self.use_deep_learning = False
else:
if not DEEP_LEARNING_AVAILABLE:
print(" ️ Deep Learning disabled (TensorFlow not installed)")
elif not DL_ENGINE_AVAILABLE:
print(" ️ Deep Learning Engine not available")
def _needs_retraining(self, cache_file):
"""Prüft ob Retraining nötig ist."""
if not os.path.exists(cache_file):
@@ -735,46 +793,57 @@ class AIMLEngine:
return np.array(X), np.array(y)
def predict_all_numbers(self, features_df):
"""Vorhersage für alle Zahlen."""
"""Vorhersage für alle Zahlen - mit Deep Learning Hybrid."""
predictions = {}
# Get RandomForest predictions
if not self.is_trained or not self.trained_models:
# Fallback
for num in range(1, 50):
predictions[num] = 0.2 + random.random() * 0.3
return predictions
current_features = self._get_current_features(features_df)
for number in range(1, 50):
if number not in self.trained_models:
predictions[number] = 0.15 + (number % 10) * 0.03
continue
number_models = self.trained_models[number]
ensemble_pred = 0
total_weight = 0
for model_name, model_data in number_models.items():
try:
model = model_data['model']
scaler = model_data['scaler']
score = model_data['score']
features_scaled = scaler.transform([current_features])
pred = model.predict(features_scaled)[0]
weight = score
ensemble_pred += pred * weight
total_weight += weight
except:
pass
if total_weight > 0:
predictions[number] = np.clip(ensemble_pred / total_weight, 0, 1)
else:
predictions[number] = 0.2 + random.random() * 0.2
else:
current_features = self._get_current_features(features_df)
for number in range(1, 50):
if number not in self.trained_models:
predictions[number] = 0.15 + (number % 10) * 0.03
continue
number_models = self.trained_models[number]
ensemble_pred = 0
total_weight = 0
for model_name, model_data in number_models.items():
try:
model = model_data['model']
scaler = model_data['scaler']
score = model_data['score']
features_scaled = scaler.transform([current_features])
pred = model.predict(features_scaled)[0]
weight = score
ensemble_pred += pred * weight
total_weight += weight
except:
pass
if total_weight > 0:
predictions[number] = np.clip(ensemble_pred / total_weight, 0, 1)
else:
predictions[number] = 0.2 + random.random() * 0.2
# Use Deep Learning Hybrid if available
if self.use_deep_learning and self.hybrid_predictor:
try:
predictions = self.hybrid_predictor.predict(
predictions,
self.df,
self.features_df
)
except Exception as e:
print(f"⚠️ DL prediction failed: {e}, using RF only")
return predictions
def _get_current_features(self, features_df):
+473
View File
@@ -0,0 +1,473 @@
#!/usr/bin/env python3
"""
Deep Learning Engine mit LSTM für Lotto-Vorhersagen
====================================================
Implementiert LSTM-basierte Modelle zur Vorhersage von Lotto-Zahlen
basierend auf historischen Sequenzen und Features.
Features:
- LSTM-Netzwerk für zeitliche Sequenzen
- Sequence-to-Probability Mapping
- Feature Engineering Integration
- Model Persistence & Caching
- Hybrid mit RandomForest
"""
import numpy as np
import pandas as pd
import os
import json
import pickle
from datetime import datetime
from typing import Dict, List, Tuple, Optional
import warnings
warnings.filterwarnings('ignore')
# TensorFlow/Keras Imports
try:
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.models import Sequential, load_model
from tensorflow.keras.layers import LSTM, Dense, Dropout, Bidirectional, BatchNormalization
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau
from tensorflow.keras.regularizers import l2
TENSORFLOW_AVAILABLE = True
except ImportError:
TENSORFLOW_AVAILABLE = False
print("⚠️ TensorFlow not available. Install with: pip install tensorflow")
class DeepLearningEngine:
"""
LSTM-basierter Deep Learning Engine für Lotto-Vorhersagen.
Architecture:
- Input: Sequence of historical draws + features
- LSTM layers: Learn temporal patterns
- Dense layers: Map to probability distribution
- Output: Probability for each number
"""
def __init__(
self,
num_numbers: int = 49,
sequence_length: int = 20,
cache_dir: str = None,
fast_mode: bool = True
):
"""
Args:
num_numbers: Maximum number (49 for Lotto, 50 for Eurojackpot)
sequence_length: How many past draws to consider
cache_dir: Directory for model persistence
fast_mode: Use faster training (fewer epochs)
"""
self.num_numbers = num_numbers
self.sequence_length = sequence_length
self.cache_dir = cache_dir
self.fast_mode = fast_mode
# Model components
self.model = None
self.is_trained = False
self.training_history = {}
# Configuration
self.config = {
'lstm_units_1': 128,
'lstm_units_2': 64,
'dense_units': 128,
'dropout_rate': 0.3,
'learning_rate': 0.001,
'batch_size': 32,
'epochs': 30 if fast_mode else 100,
'validation_split': 0.2
}
if cache_dir:
os.makedirs(cache_dir, exist_ok=True)
self.model_path = os.path.join(cache_dir, f'lstm_model_{num_numbers}.h5')
self.config_path = os.path.join(cache_dir, f'lstm_config_{num_numbers}.json')
else:
self.model_path = None
self.config_path = None
print(f"🧠 Deep Learning Engine initialized")
print(f" Numbers: 1-{num_numbers}")
print(f" Sequence Length: {sequence_length}")
print(f" Fast Mode: {fast_mode}")
def _build_model(self, num_features: int) -> Sequential:
"""
Builds LSTM architecture.
Architecture:
Input (sequence_length, num_features)
Bidirectional LSTM(128) + Dropout(0.3)
Bidirectional LSTM(64) + Dropout(0.3)
Dense(128, relu) + BatchNorm + Dropout(0.3)
Dense(num_numbers, sigmoid)
"""
model = Sequential([
# First Bidirectional LSTM layer
Bidirectional(
LSTM(
self.config['lstm_units_1'],
return_sequences=True,
kernel_regularizer=l2(0.01)
),
input_shape=(self.sequence_length, num_features)
),
Dropout(self.config['dropout_rate']),
BatchNormalization(),
# Second Bidirectional LSTM layer
Bidirectional(
LSTM(
self.config['lstm_units_2'],
return_sequences=False,
kernel_regularizer=l2(0.01)
)
),
Dropout(self.config['dropout_rate']),
BatchNormalization(),
# Dense layers
Dense(
self.config['dense_units'],
activation='relu',
kernel_regularizer=l2(0.01)
),
BatchNormalization(),
Dropout(self.config['dropout_rate']),
# Output layer - probability for each number
Dense(self.num_numbers, activation='sigmoid')
])
# Compile
model.compile(
optimizer=Adam(learning_rate=self.config['learning_rate']),
loss='binary_crossentropy',
metrics=['accuracy', 'AUC']
)
return model
def _prepare_sequences(
self,
df: pd.DataFrame,
features_df: pd.DataFrame
) -> Tuple[np.ndarray, np.ndarray]:
"""
Prepares sequences for LSTM training.
Args:
df: Historical draws (with columns Z1-Z6)
features_df: Engineered features
Returns:
X: (num_samples, sequence_length, num_features)
y: (num_samples, num_numbers) - binary matrix
"""
print(f" Preparing sequences (length={self.sequence_length})...")
# Ensure data is sorted by date
if 'datum' in df.columns:
df = df.sort_values('datum').reset_index(drop=True)
# Extract number columns
num_cols = [col for col in df.columns if col.startswith('Z')]
# Normalize features to [0, 1]
features_normalized = features_df.copy()
for col in features_normalized.columns:
min_val = features_normalized[col].min()
max_val = features_normalized[col].max()
if max_val > min_val:
features_normalized[col] = (features_normalized[col] - min_val) / (max_val - min_val)
else:
features_normalized[col] = 0.5
X_sequences = []
y_targets = []
# Create sequences
for i in range(self.sequence_length, len(df)):
# Get sequence of features
sequence = features_normalized.iloc[i - self.sequence_length:i].values
X_sequences.append(sequence)
# Target: next draw as binary vector
target = np.zeros(self.num_numbers)
next_draw = df.iloc[i][num_cols].values
for num in next_draw:
if 1 <= num <= self.num_numbers:
target[int(num) - 1] = 1
y_targets.append(target)
X = np.array(X_sequences)
y = np.array(y_targets)
print(f" ✅ Created {len(X)} sequences")
print(f" Shape: X={X.shape}, y={y.shape}")
return X, y
def train(
self,
df: pd.DataFrame,
features_df: pd.DataFrame,
force_retrain: bool = False
) -> bool:
"""
Trains LSTM model on historical data.
Args:
df: Historical draws
features_df: Engineered features
force_retrain: Retrain even if cached model exists
Returns:
Success status
"""
if not TENSORFLOW_AVAILABLE:
print("❌ TensorFlow not available")
return False
# Check for cached model
if not force_retrain and self.model_path and os.path.exists(self.model_path):
print(" 📦 Loading cached LSTM model...")
try:
self.model = load_model(self.model_path)
self.is_trained = True
# Load config
if os.path.exists(self.config_path):
with open(self.config_path, 'r') as f:
self.training_history = json.load(f)
print(f" ✅ Loaded cached model")
return True
except Exception as e:
print(f" ⚠️ Failed to load cached model: {e}")
print(" 🔄 Training new model...")
print(f"\n🧠 TRAINING DEEP LEARNING MODEL (LSTM)")
print("=" * 70)
# Prepare data
X, y = self._prepare_sequences(df, features_df)
if len(X) < 100:
print(" ⚠️ Not enough data for training (need >100 sequences)")
return False
# Build model
print(f" Building LSTM architecture...")
num_features = X.shape[2]
self.model = self._build_model(num_features)
# Show summary
print(f"\n 📊 Model Summary:")
total_params = self.model.count_params()
print(f" Total parameters: {total_params:,}")
# Callbacks
callbacks = [
EarlyStopping(
monitor='val_loss',
patience=10,
restore_best_weights=True,
verbose=0
),
ReduceLROnPlateau(
monitor='val_loss',
factor=0.5,
patience=5,
verbose=0
)
]
# Train
print(f"\n 🚀 Training for {self.config['epochs']} epochs...")
print(f" Batch size: {self.config['batch_size']}")
print(f" Validation split: {self.config['validation_split']:.1%}")
try:
history = self.model.fit(
X, y,
batch_size=self.config['batch_size'],
epochs=self.config['epochs'],
validation_split=self.config['validation_split'],
callbacks=callbacks,
verbose=1
)
# Store training history
self.training_history = {
'trained_at': datetime.now().isoformat(),
'num_samples': len(X),
'num_features': num_features,
'final_loss': float(history.history['loss'][-1]),
'final_val_loss': float(history.history['val_loss'][-1]),
'final_accuracy': float(history.history['accuracy'][-1]),
'final_val_accuracy': float(history.history['val_accuracy'][-1]),
'epochs_trained': len(history.history['loss'])
}
self.is_trained = True
# Save model
if self.model_path:
print(f"\n 💾 Saving model to cache...")
self.model.save(self.model_path)
with open(self.config_path, 'w') as f:
json.dump(self.training_history, f, indent=2)
print(f" ✅ Model saved")
# Print results
print(f"\n ✅ TRAINING COMPLETED")
print(f" Final Loss: {self.training_history['final_loss']:.4f}")
print(f" Final Val Loss: {self.training_history['final_val_loss']:.4f}")
print(f" Final Accuracy: {self.training_history['final_accuracy']:.4f}")
print(f" Epochs: {self.training_history['epochs_trained']}")
return True
except Exception as e:
print(f"\n ❌ Training failed: {e}")
import traceback
traceback.print_exc()
return False
def predict(
self,
recent_df: pd.DataFrame,
recent_features: pd.DataFrame
) -> Dict[int, float]:
"""
Predicts probabilities for each number.
Args:
recent_df: Recent draws (at least sequence_length)
recent_features: Recent features
Returns:
{number: probability} for numbers 1-num_numbers
"""
if not self.is_trained or self.model is None:
print("⚠️ Model not trained, returning uniform distribution")
return {i: 0.5 for i in range(1, self.num_numbers + 1)}
# Prepare last sequence
if len(recent_df) < self.sequence_length:
print(f"⚠️ Not enough recent data (need {self.sequence_length}, got {len(recent_df)})")
return {i: 0.5 for i in range(1, self.num_numbers + 1)}
# Get last sequence
recent_features_normalized = recent_features.copy()
for col in recent_features_normalized.columns:
min_val = recent_features_normalized[col].min()
max_val = recent_features_normalized[col].max()
if max_val > min_val:
recent_features_normalized[col] = (recent_features_normalized[col] - min_val) / (max_val - min_val)
else:
recent_features_normalized[col] = 0.5
sequence = recent_features_normalized.iloc[-self.sequence_length:].values
X = np.array([sequence]) # Shape: (1, sequence_length, num_features)
# Predict
predictions = self.model.predict(X, verbose=0)[0] # Shape: (num_numbers,)
# Convert to dictionary
result = {i + 1: float(predictions[i]) for i in range(self.num_numbers)}
return result
def get_model_info(self) -> Dict:
"""Returns model information."""
return {
'is_trained': self.is_trained,
'tensorflow_available': TENSORFLOW_AVAILABLE,
'num_numbers': self.num_numbers,
'sequence_length': self.sequence_length,
'config': self.config,
'training_history': self.training_history,
'model_exists': self.model is not None
}
class HybridDeepLearningPredictor:
"""
Kombiniert RandomForest + LSTM für robustere Vorhersagen.
"""
def __init__(
self,
dl_engine: DeepLearningEngine,
rf_weight: float = 0.4,
dl_weight: float = 0.6
):
"""
Args:
dl_engine: Deep Learning Engine
rf_weight: Weight for RandomForest predictions
dl_weight: Weight for Deep Learning predictions
"""
self.dl_engine = dl_engine
self.rf_weight = rf_weight
self.dl_weight = dl_weight
print(f"🔀 Hybrid Predictor: RF={rf_weight:.1%} + DL={dl_weight:.1%}")
def predict(
self,
rf_predictions: Dict[int, float],
recent_df: pd.DataFrame,
recent_features: pd.DataFrame
) -> Dict[int, float]:
"""
Combines RandomForest and Deep Learning predictions.
Args:
rf_predictions: Predictions from RandomForest
recent_df: Recent draws for DL
recent_features: Recent features for DL
Returns:
Combined predictions
"""
# Get DL predictions
dl_predictions = self.dl_engine.predict(recent_df, recent_features)
# Combine
combined = {}
for num in range(1, self.dl_engine.num_numbers + 1):
rf_score = rf_predictions.get(num, 0.5)
dl_score = dl_predictions.get(num, 0.5)
combined[num] = (
self.rf_weight * rf_score +
self.dl_weight * dl_score
)
# Normalize to [0, 1]
min_score = min(combined.values())
max_score = max(combined.values())
if max_score > min_score:
combined = {
num: (score - min_score) / (max_score - min_score)
for num, score in combined.items()
}
return combined
@@ -0,0 +1,526 @@
#!/usr/bin/env python3
"""
Deep Learning Engine mit PyTorch LSTM für Lotto-Vorhersagen
============================================================
PyTorch-basierte Implementierung für Python 3.14+ Kompatibilität.
TensorFlow unterstützt Python 3.14 noch nicht, daher verwenden wir PyTorch.
Features:
- LSTM-Netzwerk für zeitliche Sequenzen
- Sequence-to-Probability Mapping
- Feature Engineering Integration
- Model Persistence & Caching
- Hybrid mit RandomForest
"""
import numpy as np
import pandas as pd
import os
import json
import pickle
from datetime import datetime
from typing import Dict, List, Tuple, Optional
import warnings
warnings.filterwarnings('ignore')
# PyTorch Imports
try:
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
PYTORCH_AVAILABLE = True
except ImportError:
PYTORCH_AVAILABLE = False
print("⚠️ PyTorch not available. Install with: pip install torch")
class LSTMModel(nn.Module):
"""PyTorch LSTM Model for Lotto prediction."""
def __init__(
self,
num_features: int,
num_numbers: int,
sequence_length: int,
lstm_units_1: int = 128,
lstm_units_2: int = 64,
dense_units: int = 128,
dropout_rate: float = 0.3
):
super(LSTMModel, self).__init__()
self.lstm1 = nn.LSTM(
input_size=num_features,
hidden_size=lstm_units_1,
batch_first=True,
bidirectional=True
)
self.dropout1 = nn.Dropout(dropout_rate)
self.batch_norm1 = nn.BatchNorm1d(lstm_units_1 * 2)
self.lstm2 = nn.LSTM(
input_size=lstm_units_1 * 2,
hidden_size=lstm_units_2,
batch_first=True,
bidirectional=True
)
self.dropout2 = nn.Dropout(dropout_rate)
self.batch_norm2 = nn.BatchNorm1d(lstm_units_2 * 2)
self.fc1 = nn.Linear(lstm_units_2 * 2, dense_units)
self.batch_norm3 = nn.BatchNorm1d(dense_units)
self.dropout3 = nn.Dropout(dropout_rate)
self.fc2 = nn.Linear(dense_units, num_numbers)
def forward(self, x):
# LSTM 1
x, _ = self.lstm1(x)
x = self.dropout1(x)
# Take last output
x = x[:, -1, :]
x = self.batch_norm1(x)
# LSTM 2 needs 3D input
x = x.unsqueeze(1)
x, _ = self.lstm2(x)
x = x[:, -1, :]
x = self.dropout2(x)
x = self.batch_norm2(x)
# Dense layers
x = torch.relu(self.fc1(x))
x = self.batch_norm3(x)
x = self.dropout3(x)
# Output
x = torch.sigmoid(self.fc2(x))
return x
class LottoDataset(Dataset):
"""PyTorch Dataset for Lotto sequences."""
def __init__(self, X, y):
self.X = torch.FloatTensor(X)
self.y = torch.FloatTensor(y)
def __len__(self):
return len(self.X)
def __getitem__(self, idx):
return self.X[idx], self.y[idx]
class DeepLearningEngine:
"""
PyTorch-based LSTM Deep Learning Engine for Lotto predictions.
"""
def __init__(
self,
num_numbers: int = 49,
sequence_length: int = 20,
cache_dir: str = None,
fast_mode: bool = True
):
"""
Args:
num_numbers: Maximum number (49 for Lotto, 50 for Eurojackpot)
sequence_length: How many past draws to consider
cache_dir: Directory for model persistence
fast_mode: Use faster training (fewer epochs)
"""
self.num_numbers = num_numbers
self.sequence_length = sequence_length
self.cache_dir = cache_dir
self.fast_mode = fast_mode
# Device
self.device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu')
# Model components
self.model = None
self.is_trained = False
self.training_history = {}
# Configuration
self.config = {
'lstm_units_1': 128,
'lstm_units_2': 64,
'dense_units': 128,
'dropout_rate': 0.3,
'learning_rate': 0.001,
'batch_size': 32,
'epochs': 30 if fast_mode else 100,
'validation_split': 0.2
}
if cache_dir:
os.makedirs(cache_dir, exist_ok=True)
self.model_path = os.path.join(cache_dir, f'lstm_model_{num_numbers}.pth')
self.config_path = os.path.join(cache_dir, f'lstm_config_{num_numbers}.json')
else:
self.model_path = None
self.config_path = None
print(f"🧠 Deep Learning Engine initialized (PyTorch)")
print(f" Numbers: 1-{num_numbers}")
print(f" Sequence Length: {sequence_length}")
print(f" Device: {self.device}")
print(f" Fast Mode: {fast_mode}")
def _build_model(self, num_features: int) -> LSTMModel:
"""Builds LSTM architecture."""
model = LSTMModel(
num_features=num_features,
num_numbers=self.num_numbers,
sequence_length=self.sequence_length,
lstm_units_1=self.config['lstm_units_1'],
lstm_units_2=self.config['lstm_units_2'],
dense_units=self.config['dense_units'],
dropout_rate=self.config['dropout_rate']
)
return model.to(self.device)
def _prepare_sequences(
self,
df: pd.DataFrame,
features_df: pd.DataFrame
) -> Tuple[np.ndarray, np.ndarray]:
"""Prepares sequences for LSTM training."""
print(f" Preparing sequences (length={self.sequence_length})...")
# Ensure data is sorted by date
if 'datum' in df.columns:
df = df.sort_values('datum').reset_index(drop=True)
# Extract number columns
num_cols = [col for col in df.columns if col.startswith('Z')]
# Normalize features to [0, 1]
features_normalized = features_df.copy()
for col in features_normalized.columns:
min_val = features_normalized[col].min()
max_val = features_normalized[col].max()
if max_val > min_val:
features_normalized[col] = (features_normalized[col] - min_val) / (max_val - min_val)
else:
features_normalized[col] = 0.5
X_sequences = []
y_targets = []
# Create sequences
for i in range(self.sequence_length, len(df)):
# Get sequence of features
sequence = features_normalized.iloc[i - self.sequence_length:i].values
X_sequences.append(sequence)
# Target: next draw as binary vector
target = np.zeros(self.num_numbers)
next_draw = df.iloc[i][num_cols].values
for num in next_draw:
if 1 <= num <= self.num_numbers:
target[int(num) - 1] = 1
y_targets.append(target)
X = np.array(X_sequences)
y = np.array(y_targets)
print(f" ✅ Created {len(X)} sequences")
print(f" Shape: X={X.shape}, y={y.shape}")
return X, y
def train(
self,
df: pd.DataFrame,
features_df: pd.DataFrame,
force_retrain: bool = False
) -> bool:
"""Trains LSTM model on historical data."""
if not PYTORCH_AVAILABLE:
print("❌ PyTorch not available")
return False
# Check for cached model
if not force_retrain and self.model_path and os.path.exists(self.model_path):
print(" 📦 Loading cached LSTM model...")
try:
checkpoint = torch.load(self.model_path, weights_only=False)
num_features = checkpoint['num_features']
self.model = self._build_model(num_features)
self.model.load_state_dict(checkpoint['model_state_dict'])
self.model.eval()
self.is_trained = True
# Load config
if os.path.exists(self.config_path):
with open(self.config_path, 'r') as f:
self.training_history = json.load(f)
print(f" ✅ Loaded cached model")
return True
except Exception as e:
print(f" ⚠️ Failed to load cached model: {e}")
print(" 🔄 Training new model...")
print(f"\n🧠 TRAINING DEEP LEARNING MODEL (PyTorch LSTM)")
print("=" * 70)
# Prepare data
X, y = self._prepare_sequences(df, features_df)
if len(X) < 100:
print(" ⚠️ Not enough data for training (need >100 sequences)")
return False
# Split data
split_idx = int(len(X) * (1 - self.config['validation_split']))
X_train, X_val = X[:split_idx], X[split_idx:]
y_train, y_val = y[:split_idx], y[split_idx:]
# Create datasets
train_dataset = LottoDataset(X_train, y_train)
val_dataset = LottoDataset(X_val, y_val)
train_loader = DataLoader(
train_dataset,
batch_size=self.config['batch_size'],
shuffle=True
)
val_loader = DataLoader(
val_dataset,
batch_size=self.config['batch_size'],
shuffle=False
)
# Build model
print(f" Building LSTM architecture...")
num_features = X.shape[2]
self.model = self._build_model(num_features)
# Show summary
total_params = sum(p.numel() for p in self.model.parameters())
print(f"\n 📊 Model Summary:")
print(f" Total parameters: {total_params:,}")
# Loss and optimizer
criterion = nn.BCELoss()
optimizer = optim.Adam(self.model.parameters(), lr=self.config['learning_rate'])
scheduler = optim.lr_scheduler.ReduceLROnPlateau(
optimizer, mode='min', factor=0.5, patience=5
)
# Train
print(f"\n 🚀 Training for {self.config['epochs']} epochs...")
print(f" Batch size: {self.config['batch_size']}")
print(f" Validation split: {self.config['validation_split']:.1%}")
best_val_loss = float('inf')
patience_counter = 0
patience = 10
try:
for epoch in range(self.config['epochs']):
# Training
self.model.train()
train_loss = 0.0
for batch_X, batch_y in train_loader:
batch_X = batch_X.to(self.device)
batch_y = batch_y.to(self.device)
optimizer.zero_grad()
outputs = self.model(batch_X)
loss = criterion(outputs, batch_y)
loss.backward()
optimizer.step()
train_loss += loss.item()
train_loss /= len(train_loader)
# Validation
self.model.eval()
val_loss = 0.0
with torch.no_grad():
for batch_X, batch_y in val_loader:
batch_X = batch_X.to(self.device)
batch_y = batch_y.to(self.device)
outputs = self.model(batch_X)
loss = criterion(outputs, batch_y)
val_loss += loss.item()
val_loss /= len(val_loader)
# Learning rate scheduling
scheduler.step(val_loss)
# Print progress every 5 epochs
if (epoch + 1) % 5 == 0:
print(f" Epoch {epoch+1}/{self.config['epochs']}: "
f"Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}")
# Early stopping
if val_loss < best_val_loss:
best_val_loss = val_loss
patience_counter = 0
# Save best model
if self.model_path:
torch.save({
'model_state_dict': self.model.state_dict(),
'num_features': num_features,
'config': self.config
}, self.model_path)
else:
patience_counter += 1
if patience_counter >= patience:
print(f" Early stopping at epoch {epoch+1}")
break
# Load best model
if self.model_path and os.path.exists(self.model_path):
checkpoint = torch.load(self.model_path, weights_only=False)
self.model.load_state_dict(checkpoint['model_state_dict'])
# Store training history
self.training_history = {
'trained_at': datetime.now().isoformat(),
'num_samples': len(X),
'num_features': num_features,
'final_loss': float(train_loss),
'final_val_loss': float(val_loss),
'best_val_loss': float(best_val_loss),
'epochs_trained': epoch + 1
}
self.is_trained = True
# Save config
if self.config_path:
with open(self.config_path, 'w') as f:
json.dump(self.training_history, f, indent=2)
# Print results
print(f"\n ✅ TRAINING COMPLETED")
print(f" Best Val Loss: {best_val_loss:.4f}")
print(f" Epochs: {self.training_history['epochs_trained']}")
return True
except Exception as e:
print(f"\n ❌ Training failed: {e}")
import traceback
traceback.print_exc()
return False
def predict(
self,
recent_df: pd.DataFrame,
recent_features: pd.DataFrame
) -> Dict[int, float]:
"""Predicts probabilities for each number."""
if not self.is_trained or self.model is None:
print("⚠️ Model not trained, returning uniform distribution")
return {i: 0.5 for i in range(1, self.num_numbers + 1)}
if len(recent_df) < self.sequence_length:
print(f"⚠️ Not enough recent data (need {self.sequence_length}, got {len(recent_df)})")
return {i: 0.5 for i in range(1, self.num_numbers + 1)}
# Prepare sequence
recent_features_normalized = recent_features.copy()
for col in recent_features_normalized.columns:
min_val = recent_features_normalized[col].min()
max_val = recent_features_normalized[col].max()
if max_val > min_val:
recent_features_normalized[col] = (recent_features_normalized[col] - min_val) / (max_val - min_val)
else:
recent_features_normalized[col] = 0.5
sequence = recent_features_normalized.iloc[-self.sequence_length:].values
X = torch.FloatTensor(sequence).unsqueeze(0).to(self.device)
# Predict
self.model.eval()
with torch.no_grad():
predictions = self.model(X)[0].cpu().numpy()
# Convert to dictionary
result = {i + 1: float(predictions[i]) for i in range(self.num_numbers)}
return result
def get_model_info(self) -> Dict:
"""Returns model information."""
return {
'is_trained': self.is_trained,
'pytorch_available': PYTORCH_AVAILABLE,
'num_numbers': self.num_numbers,
'sequence_length': self.sequence_length,
'device': str(self.device),
'config': self.config,
'training_history': self.training_history,
'model_exists': self.model is not None
}
class HybridDeepLearningPredictor:
"""Kombiniert RandomForest + LSTM für robustere Vorhersagen."""
def __init__(
self,
dl_engine: DeepLearningEngine,
rf_weight: float = 0.4,
dl_weight: float = 0.6
):
"""
Args:
dl_engine: Deep Learning Engine
rf_weight: Weight for RandomForest predictions
dl_weight: Weight for Deep Learning predictions
"""
self.dl_engine = dl_engine
self.rf_weight = rf_weight
self.dl_weight = dl_weight
print(f"🔀 Hybrid Predictor: RF={rf_weight:.1%} + DL={dl_weight:.1%}")
def predict(
self,
rf_predictions: Dict[int, float],
recent_df: pd.DataFrame,
recent_features: pd.DataFrame
) -> Dict[int, float]:
"""Combines RandomForest and Deep Learning predictions."""
# Get DL predictions
dl_predictions = self.dl_engine.predict(recent_df, recent_features)
# Combine
combined = {}
for num in range(1, self.dl_engine.num_numbers + 1):
rf_score = rf_predictions.get(num, 0.5)
dl_score = dl_predictions.get(num, 0.5)
combined[num] = (
self.rf_weight * rf_score +
self.dl_weight * dl_score
)
# Normalize to [0, 1]
min_score = min(combined.values())
max_score = max(combined.values())
if max_score > min_score:
combined = {
num: (score - min_score) / (max_score - min_score)
for num, score in combined.items()
}
return combined
+345
View File
@@ -0,0 +1,345 @@
#!/usr/bin/env python3
"""
Health-Check System mit Auto-Recovery
======================================
Prüft System-Gesundheit und behebt automatisch Probleme:
- CSV-Datei vorhanden und aktuell?
- Models trainiert und verfügbar?
- Learning State konsistent?
- Logs rotieren?
- Telegram-Bot erreichbar?
Bei Problemen:
- Auto-Retry
- Telegram-Alerts
- Logging
"""
import os
import sys
import json
import time
from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, List, Tuple
import pandas as pd
# Add parent dir to path
script_dir = os.path.dirname(os.path.abspath(__file__))
project_dir = os.path.dirname(os.path.dirname(script_dir))
sys.path.insert(0, project_dir)
from scripts.utils.notifier import LottoNotifier
class HealthCheck:
"""System Health-Check mit Auto-Recovery."""
def __init__(self, data_dir: str, lottery_name: str = "Lotto"):
self.data_dir = data_dir
self.lottery_name = lottery_name
self.notifier = LottoNotifier()
self.health_log = os.path.join(data_dir, "health_check.json")
self.issues = []
self.warnings = []
self.recovered = []
print(f"🏥 HEALTH-CHECK SYSTEM - {lottery_name}")
print("=" * 70)
def run_all_checks(self) -> bool:
"""Führt alle Health-Checks aus."""
all_ok = True
checks = [
("CSV Data", self.check_csv_data),
("ML Models", self.check_ml_models),
("Learning State", self.check_learning_state),
("Logs", self.check_logs),
("Telegram", self.check_telegram),
("Disk Space", self.check_disk_space)
]
for name, check_func in checks:
print(f"\n🔍 Checking: {name}...", end=" ", flush=True)
try:
status, message = check_func()
if status == "OK":
print(f"{message}")
elif status == "WARNING":
print(f"⚠️ {message}")
self.warnings.append(f"{name}: {message}")
elif status == "ERROR":
print(f"{message}")
self.issues.append(f"{name}: {message}")
all_ok = False
elif status == "RECOVERED":
print(f"🔧 {message}")
self.recovered.append(f"{name}: {message}")
except Exception as e:
print(f"❌ Exception: {e}")
self.issues.append(f"{name}: Exception - {e}")
all_ok = False
# Summary
print("\n" + "=" * 70)
self._print_summary()
# Save health log
self._save_health_log(all_ok)
# Send alert if issues
if self.issues:
self._send_alert()
return all_ok
def check_csv_data(self) -> Tuple[str, str]:
"""Prüft CSV-Datei."""
csv_file = os.path.join(self.data_dir, "AlleLottozahlen.csv")
if not os.path.exists(csv_file):
return ("ERROR", f"CSV file not found: {csv_file}")
# Check age
mtime = os.path.getmtime(csv_file)
age_days = (time.time() - mtime) / 86400
if age_days > 10:
return ("WARNING", f"CSV file is {age_days:.1f} days old")
# Check content
try:
df = pd.read_csv(csv_file, sep=';')
if len(df) < 100:
return ("ERROR", f"CSV has only {len(df)} rows")
return ("OK", f"{len(df):,} draws, {age_days:.1f} days old")
except Exception as e:
return ("ERROR", f"CSV parse error: {e}")
def check_ml_models(self) -> Tuple[str, str]:
"""Prüft ML Models."""
models_dir = os.path.join(self.data_dir, "ultimate_ml_models")
if not os.path.exists(models_dir):
return ("WARNING", "No models cache found (will train on next run)")
# Check RandomForest models
rf_model = os.path.join(models_dir, "trained_models.pkl")
if os.path.exists(rf_model):
age_days = (time.time() - os.path.getmtime(rf_model)) / 86400
status = "OK" if age_days < 10 else "WARNING"
msg = f"RandomForest models {age_days:.1f} days old"
else:
status = "WARNING"
msg = "RandomForest models not found"
# Check Deep Learning models
dl_model = os.path.join(models_dir, "deep_learning", "lstm_model_49.pth")
if os.path.exists(dl_model):
age_days = (time.time() - os.path.getmtime(dl_model)) / 86400
msg += f", LSTM {age_days:.1f} days old"
else:
msg += ", LSTM not found"
return (status, msg)
def check_learning_state(self) -> Tuple[str, str]:
"""Prüft Learning State."""
state_file = os.path.join(self.data_dir, "learning_state.json")
if not os.path.exists(state_file):
return ("WARNING", "No learning state found")
try:
with open(state_file, 'r') as f:
state = json.load(f)
cycles = state.get('learning_cycle', 0)
last_update = state.get('last_update', '')
if not last_update:
return ("WARNING", f"{cycles} cycles, no last_update timestamp")
last_dt = datetime.fromisoformat(last_update)
age_days = (datetime.now() - last_dt).days
if age_days > 10:
return ("WARNING", f"{cycles} cycles, last update {age_days} days ago")
return ("OK", f"{cycles} cycles, last update {age_days} days ago")
except Exception as e:
return ("ERROR", f"State parse error: {e}")
def check_logs(self) -> Tuple[str, str]:
"""Prüft und rotiert Logs."""
logs_dir = os.path.join(os.path.dirname(self.data_dir), "logs")
if not os.path.exists(logs_dir):
os.makedirs(logs_dir, exist_ok=True)
return ("RECOVERED", "Created logs directory")
# Check log sizes
total_size = 0
large_logs = []
for log_file in Path(logs_dir).glob("*.log"):
size_mb = log_file.stat().st_size / 1024 / 1024
total_size += size_mb
if size_mb > 50: # > 50 MB
large_logs.append(log_file.name)
# Rotate large logs
if large_logs:
for log_name in large_logs:
self._rotate_log(os.path.join(logs_dir, log_name))
return ("RECOVERED", f"Rotated {len(large_logs)} large logs, total {total_size:.1f} MB")
return ("OK", f"Total size {total_size:.1f} MB")
def check_telegram(self) -> Tuple[str, str]:
"""Prüft Telegram-Bot."""
config = self.notifier.config
if not config.get("telegram", {}).get("enabled"):
return ("WARNING", "Telegram disabled in config")
bot_token = config.get("telegram", {}).get("bot_token")
if not bot_token or bot_token == "YOUR_BOT_TOKEN":
return ("WARNING", "Telegram bot_token not configured")
# Simple check: Token format
if len(bot_token) < 20 or ':' not in bot_token:
return ("ERROR", "Invalid bot_token format")
return ("OK", "Telegram configured")
def check_disk_space(self) -> Tuple[str, str]:
"""Prüft Festplatten-Speicher."""
import shutil
usage = shutil.disk_usage(self.data_dir)
free_gb = usage.free / 1024 / 1024 / 1024
percent_free = (usage.free / usage.total) * 100
if percent_free < 10:
return ("ERROR", f"Only {free_gb:.1f} GB free ({percent_free:.1f}%)")
elif percent_free < 20:
return ("WARNING", f"{free_gb:.1f} GB free ({percent_free:.1f}%)")
return ("OK", f"{free_gb:.1f} GB free ({percent_free:.1f}%)")
def _rotate_log(self, log_path: str):
"""Rotiert ein Log-File."""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_path = f"{log_path}.{timestamp}"
os.rename(log_path, backup_path)
print(f" 📦 Rotated: {os.path.basename(log_path)}{os.path.basename(backup_path)}")
def _print_summary(self):
"""Druckt Zusammenfassung."""
print("\n📊 SUMMARY:")
if not self.issues and not self.warnings and not self.recovered:
print(" ✅ All checks passed - System healthy!")
return
if self.recovered:
print(f"\n 🔧 Auto-Recovered ({len(self.recovered)}):")
for item in self.recovered:
print(f"{item}")
if self.warnings:
print(f"\n ⚠️ Warnings ({len(self.warnings)}):")
for item in self.warnings:
print(f"{item}")
if self.issues:
print(f"\n ❌ Issues ({len(self.issues)}):")
for item in self.issues:
print(f"{item}")
def _save_health_log(self, all_ok: bool):
"""Speichert Health-Log."""
log_entry = {
"timestamp": datetime.now().isoformat(),
"status": "OK" if all_ok else "ISSUES",
"issues": self.issues,
"warnings": self.warnings,
"recovered": self.recovered
}
# Load existing log
if os.path.exists(self.health_log):
with open(self.health_log, 'r') as f:
log_data = json.load(f)
else:
log_data = {"checks": []}
# Append new entry
log_data["checks"].append(log_entry)
# Keep only last 100 entries
log_data["checks"] = log_data["checks"][-100:]
# Save
with open(self.health_log, 'w') as f:
json.dump(log_data, f, indent=2)
def _send_alert(self):
"""Sendet Telegram-Alert bei Problemen."""
message = f"🚨 *HEALTH-CHECK ALERT - {self.lottery_name}*\n\n"
message += f"❌ *{len(self.issues)} Issues detected:*\n"
for issue in self.issues:
message += f"{issue}\n"
if self.warnings:
message += f"\n⚠️ {len(self.warnings)} Warnings:\n"
for warning in self.warnings:
message += f"{warning}\n"
message += f"\n🕐 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
try:
self.notifier._send_telegram(message)
except Exception as e:
print(f" ⚠️ Could not send alert: {e}")
def main():
"""Main function."""
import argparse
parser = argparse.ArgumentParser(description="System Health-Check")
parser.add_argument(
'--data-dir',
type=str,
default="/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Lotto/data",
help='Data directory'
)
parser.add_argument(
'--lottery',
type=str,
default="Lotto",
help='Lottery name (Lotto or Eurojackpot)'
)
args = parser.parse_args()
# Run health check
checker = HealthCheck(args.data_dir, args.lottery)
success = checker.run_all_checks()
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()
+14 -15
View File
@@ -107,14 +107,6 @@ class LottoNotifier:
message += f"📅 *Generiert:* {timestamp}\n"
message += f"📊 *Anzahl Tipps:* {len(tips)}\n\n"
# Bester Tipp
message += "⭐ *BESTER TIPP:*\n"
numbers_str = ' - '.join([f"{n:02d}" for n in best_tip['numbers']])
message += f"🎯 Zahlen: `{numbers_str}`\n"
message += f"🌟 Superzahl: `{best_tip['superzahl']}`\n"
message += f"📈 Confidence: `{best_tip['confidence']:.4f}`\n"
message += f"🎨 Strategie: `{best_tip['strategy']}`\n\n"
# Statistiken
avg_conf = sum(t['confidence'] for t in tips) / len(tips)
avg_qual = sum(t['quality'] for t in tips) / len(tips)
@@ -123,16 +115,23 @@ class LottoNotifier:
message += f"🎯 Ø Confidence: `{avg_conf:.4f}`\n"
message += f"💎 Ø Quality: `{avg_qual:.4f}`\n\n"
# Top 3 Tipps
message += "🏆 *TOP 3 TIPPS:*\n"
sorted_tips = sorted(tips, key=lambda t: t['confidence'], reverse=True)[:3]
# Top 5 Tipps mit Details
message += "🏆 *TOP 5 EMPFEHLUNGEN:*\n"
message += "━━━━━━━━━━━━━━━━━━━━━━━\n"
sorted_tips = sorted(tips, key=lambda t: t['confidence'], reverse=True)[:5]
for i, tip in enumerate(sorted_tips, 1):
nums = ' - '.join([f"{n:02d}" for n in tip['numbers']])
message += f"{i}. `{nums}` + SZ `{tip['superzahl']}` "
message += f"({tip['confidence']:.3f})\n"
# Emoji basierend auf Rang
emoji = "🏆" if i == 1 else "🥈" if i == 2 else "🥉" if i == 3 else ""
message += "\n🍀 *Viel Glück!*"
nums = '-'.join([f"{n:02d}" for n in tip['numbers']])
message += f"\n{emoji} *#{i}* Conf: `{tip['confidence']:.2%}`\n"
message += f"🔢 `{nums}` + SZ `{tip['superzahl']}`\n"
message += f"📈 {tip['strategy']} | Q: `{tip['quality']:.3f}`\n"
message += "\n💡 Alle 10 Tipps in der CSV-Datei!\n"
message += "🍀 *Viel Glück!*"
return message