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:
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user