Improve generator quality and fix data gaps (Apr-May 2026)

Data:
- Add 12 missing draws (2026-01-09, 2026-04-03 to 2026-05-15)
- Retrain LSTM and RF models on complete 952-draw dataset
- Run retroactive learning for all 12 skipped draws

Automation:
- Add eurojackpot-zahlen.eu web scraper as fallback when APIs fail
- Fixes recurring gap problem caused by Sazka detail-endpoint change

Generator improvements:
- Add hard structural filters (sum 95-165, min 1 even + 1 odd per tip)
- Add long-term frequency prior as learner dampener (alpha=0.2)
- Fix ENSEMBLE strategy generating duplicate tips (greedy → weighted random)
- Fix non-vectorized frequency calculation (stack().value_counts())
- Fix Division-by-Zero edge case in set_frequency_prior
- Fix potential KeyError on euro_ai_score in structural filter loop

.gitignore:
- Exclude data/backups/, weekly tip CSVs, performance reports

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-19 10:35:57 +02:00
co-authored by Claude Sonnet 4.6
parent 98a74ff2a7
commit 4fc488c668
13 changed files with 1022 additions and 112 deletions
@@ -164,6 +164,12 @@ class UltimateAIMLEurojackpotGenerator:
# 6. Initialize Real-Time Learning
print("📚 Activating Real-Time Learning...", end=" ", flush=True)
self.real_time_learner.initialize(self.features_df)
# Langzeit-Frequenz als Dämpfer übergeben
_main_counts = self.df[['Z1','Z2','Z3','Z4','Z5']].stack().value_counts()
_euro_counts = self.df[['SZ1','SZ2']].stack().value_counts()
freq_main = {n: int(_main_counts.get(n, 0)) for n in range(1, 51)}
freq_euro = {n: int(_euro_counts.get(n, 0)) for n in range(1, 13)}
self.real_time_learner.set_frequency_prior(freq_main, freq_euro)
print("")
self.is_trained = self.ai_ml_engine.is_trained
@@ -251,6 +257,21 @@ class UltimateAIMLEurojackpotGenerator:
tip_counter += len(tips)
print(f"✅ ({count} tips)")
# Strukturfilter: Summe + Parität korrigieren
fixed_count = 0
for tip in all_tips:
if not self._passes_structural_constraints(tip['main_numbers']):
tip['main_numbers'] = self._apply_structural_fix(tip['main_numbers'], main_predictions)
tip['main_ai_score'] = np.mean([main_predictions.get(n, 0.1) for n in tip['main_numbers']])
tip['pattern_weight'] = self.pattern_engine.calculate_pattern_weight(tip['main_numbers'])
tip['confidence'] = tip['main_ai_score'] * 0.5 + tip.get('euro_ai_score', 0.0) * 0.3 + tip['pattern_weight'] * 0.2
tip['quality'] = self._calculate_quality_score(
tip['main_numbers'], tip['euro_numbers'], main_predictions, euro_predictions, tip['pattern_weight']
)
fixed_count += 1
if fixed_count:
print(f" 🔧 {fixed_count} Tip(s) via Strukturfilter korrigiert (Summe/Parität)")
# Output all tips
print(f"\n📋 RESULTS:")
print("=" * 100)
@@ -459,27 +480,20 @@ class UltimateAIMLEurojackpotGenerator:
selected_main = []
for position in range(5):
best_candidate = None
best_score = -1
for candidate in all_candidates:
if candidate not in selected_main:
ai_s = main_preds.get(candidate, 0.1)
pattern_s = self.pattern_engine.get_number_pattern_score(candidate)
diversity_s = self._calculate_diversity_score(candidate, selected_main) if selected_main else 0.5
ensemble_score = (ai_s * 0.4 + pattern_s * 0.3 + diversity_s * 0.3)
if ensemble_score > best_score:
best_score = ensemble_score
best_candidate = candidate
if best_candidate:
selected_main.append(best_candidate)
else:
available = [n for n in range(1, 51) if n not in selected_main]
if available:
selected_main.append(random.choice(available))
candidates = [c for c in all_candidates if c not in selected_main]
if not candidates:
candidates = [n for n in range(1, 51) if n not in selected_main]
scores = []
for c in candidates:
ai_s = main_preds.get(c, 0.1)
pattern_s = self.pattern_engine.get_number_pattern_score(c)
diversity_s = self._calculate_diversity_score(c, selected_main) if selected_main else 0.5
scores.append(ai_s * 0.4 + pattern_s * 0.3 + diversity_s * 0.3)
# Gewichtete Zufallsauswahl: Qualität bleibt hoch, Duplikate werden vermieden
choice = random.choices(candidates, weights=scores)[0]
selected_main.append(choice)
selected_main = sorted(selected_main[:5])
@@ -505,6 +519,69 @@ class UltimateAIMLEurojackpotGenerator:
'quality': quality
}
def _passes_structural_constraints(self, main_numbers):
"""Prüft Summenbereich und Parität."""
s = sum(main_numbers)
if s < 95 or s > 165:
return False
even = sum(1 for n in main_numbers if n % 2 == 0)
if even == 0 or even == 5:
return False
return True
def _apply_structural_fix(self, main_numbers, main_preds):
"""Korrigiert Summe/Parität durch minimalen Tausch."""
numbers = list(main_numbers)
# Parität: mind. 1 gerade und 1 ungerade
even = [n for n in numbers if n % 2 == 0]
odd = [n for n in numbers if n % 2 != 0]
if len(even) == 0:
# alle ungerade → tausche den am schlechtesten bewerteten gegen bestes gerades
worst = min(odd, key=lambda n: main_preds.get(n, 0))
candidates = sorted(
[n for n in range(2, 51, 2) if n not in numbers],
key=lambda n: -main_preds.get(n, 0)
)
if candidates:
numbers.remove(worst)
numbers.append(candidates[0])
elif len(odd) == 0:
worst = min(even, key=lambda n: main_preds.get(n, 0))
candidates = sorted(
[n for n in range(1, 51, 2) if n not in numbers],
key=lambda n: -main_preds.get(n, 0)
)
if candidates:
numbers.remove(worst)
numbers.append(candidates[0])
# Summe korrigieren
for _ in range(10):
s = sum(numbers)
if 95 <= s <= 165:
break
if s > 165:
highest = max(numbers)
candidates = sorted(
[n for n in range(1, highest) if n not in numbers],
key=lambda n: -main_preds.get(n, 0)
)
if candidates:
numbers.remove(highest)
numbers.append(candidates[0])
else:
lowest = min(numbers)
candidates = sorted(
[n for n in range(lowest + 1, 51) if n not in numbers],
key=lambda n: -main_preds.get(n, 0)
)
if candidates:
numbers.remove(lowest)
numbers.append(candidates[0])
return sorted(numbers)
def _calculate_diversity_score(self, candidate, selected):
"""Berechnet Diversität."""
if not selected:
@@ -1473,6 +1550,9 @@ class EurojackpotRealTimeLearner:
self.stats = defaultdict(int)
self.cache_path = cache_path
self.learning_state_file = os.path.join(cache_path, 'learning_state.json') if cache_path else None
self.freq_prior_main = {} # Langzeit-Frequenz Dämpfer
self.freq_prior_euro = {}
self.freq_alpha = 0.2 # 20% Langzeit-Frequenz, 80% Learner
# Lade gespeicherten State
if self.learning_state_file and os.path.exists(self.learning_state_file):
@@ -1482,24 +1562,37 @@ class EurojackpotRealTimeLearner:
"""Initialisiert Learning."""
self.features_df = features_df
def adjust_main_predictions(self, predictions):
"""Passt Main Predictions an."""
adjusted = {}
def set_frequency_prior(self, freq_main, freq_euro):
"""Setzt Langzeit-Frequenz als Dämpfer (normalisiert auf [0,1])."""
max_main = max(freq_main.values(), default=0) or 1
max_euro = max(freq_euro.values(), default=0) or 1
self.freq_prior_main = {n: v / max_main for n, v in freq_main.items()}
self.freq_prior_euro = {n: v / max_euro for n, v in freq_euro.items()}
def adjust_main_predictions(self, predictions):
"""Passt Main Predictions an, gedämpft durch Langzeit-Frequenz."""
adjusted = {}
for number, pred in predictions.items():
adjustment = self.adjustments_main.get(number, 0)
adjusted[number] = np.clip(pred + adjustment * self.learning_rate, 0, 1)
learner_val = np.clip(pred + adjustment * self.learning_rate, 0, 1)
if self.freq_prior_main:
freq_val = self.freq_prior_main.get(number, 0.5)
adjusted[number] = (1 - self.freq_alpha) * learner_val + self.freq_alpha * freq_val
else:
adjusted[number] = learner_val
return adjusted
def adjust_euro_predictions(self, predictions):
"""Passt Euro Predictions an."""
"""Passt Euro Predictions an, gedämpft durch Langzeit-Frequenz."""
adjusted = {}
for number, pred in predictions.items():
adjustment = self.adjustments_euro.get(number, 0)
adjusted[number] = np.clip(pred + adjustment * self.learning_rate, 0, 1)
learner_val = np.clip(pred + adjustment * self.learning_rate, 0, 1)
if self.freq_prior_euro:
freq_val = self.freq_prior_euro.get(number, 0.5)
adjusted[number] = (1 - self.freq_alpha) * learner_val + self.freq_alpha * freq_val
else:
adjusted[number] = learner_val
return adjusted
def learn_from_result(self, drawing):