Implement iterative minimum participant checks for workshop assignments
All checks were successful
Deploy Workshop-Wahlen (DEV / PROD) / deploy (push) Successful in 12s

This commit is contained in:
ProgrammGamer
2026-03-02 13:08:02 +01:00
parent 8a7b0d71ee
commit 0cabca874d

View File

@@ -144,50 +144,47 @@ function kc_run_zuteilung($wahl_id) {
$assign($tn, $ws_id, 99); $assign($tn, $ws_id, 99);
} }
// 6.5 NEUE LOGIK: NACH Zufallsverteilung Minimalanzahlen überprüfen // 6.5 ITERATIVE MINIMALANZAHL-SICHERUNG: Bis alle erfüllt sind
// Unterbesetzte Workshops auflösen und TN nur auf "sichere" Workshops verteilen
$ws_ids_in_wahl = array_keys($workshops); $ws_ids_in_wahl = array_keys($workshops);
$iteration = 0;
$max_iterations = 10;
if (!empty($ws_ids_in_wahl)) { while($iteration < $max_iterations) {
// Aktuelle Belegung zählen $iteration++;
$assigned_counts_raw = $wpdb->get_results($wpdb->prepare( error_log("Phase $phase: Minimalanzahl-Check Iteration $iteration");
"SELECT workshop_id, COUNT(*) AS cnt
FROM {$prefix}kc_zuteilung if (!empty($ws_ids_in_wahl)) {
WHERE wahl_id=%d AND phase=%d AND workshop_id IS NOT NULL // Aktuelle Belegung zählen
GROUP BY workshop_id", $assigned_counts_raw = $wpdb->get_results($wpdb->prepare(
$wahl_id, $phase "SELECT workshop_id, COUNT(*) AS cnt
), 'ARRAY_A'); FROM {$prefix}kc_zuteilung
WHERE wahl_id=%d AND phase=%d AND workshop_id IS NOT NULL
GROUP BY workshop_id",
$wahl_id, $phase
), 'ARRAY_A');
$assigned_counts = []; $assigned_counts = [];
foreach($assigned_counts_raw as $ar) { foreach($assigned_counts_raw as $ar) {
$assigned_counts[intval($ar['workshop_id'])] = intval($ar['cnt']); $assigned_counts[intval($ar['workshop_id'])] = intval($ar['cnt']);
}
// ─────────────── DIAGNOSE VOR UMVERTEILUNG ───────────────
error_log("=== DIAGNOSE Mindestanzahl Phase $phase (VOR Umverteilung) ===");
foreach ($ws_ids_in_wahl as $wsid) {
$name = $workshops[$wsid]->name ?? 'ID '.$wsid;
$min = $min_map[$wsid] ?? 0;
$cnt = $assigned_counts[$wsid] ?? 0;
$status = ($min > 0 && $cnt > 0 && $cnt < $min) ? 'UNTER MIN → wird umverteilt' :
(($min > 0 && $cnt >= $min) ? 'OK ✓' : ($cnt == 0 ? 'leer' : 'OK (kein Min)'));
error_log("WS $wsid ($name) | min=$min | TN=$cnt | $status");
}
error_log("============================");
// Unterbesetzte Workshops finden
$failing = [];
foreach($ws_ids_in_wahl as $wsid) {
$min_req = $min_map[$wsid] ?? 0;
$cnt = $assigned_counts[$wsid] ?? 0;
// Workshop ist unterbesetzt wenn: er hat Teilnehmer, aber weniger als Minimum
if ($min_req > 0 && $cnt > 0 && $cnt < $min_req) {
$failing[] = $wsid;
} }
}
if (!empty($failing)) { // Unterbesetzte Workshops finden (NUR die mit Teilnehmern, aber < min)
error_log("Phase $phase: Workshops UNTER Mindestanzahl: " . implode(', ', $failing) . " → Umverteilung startet"); $failing = [];
foreach($ws_ids_in_wahl as $wsid) {
$min_req = $min_map[$wsid] ?? 0;
$cnt = $assigned_counts[$wsid] ?? 0;
if ($min_req > 0 && $cnt > 0 && $cnt < $min_req) {
$failing[] = $wsid;
}
}
if (empty($failing)) {
// Alle erfüllt!
error_log("Phase $phase: ✓ Alle Workshops erfüllen ihre Minimalanzahl (Iteration $iteration)");
break;
}
error_log("Phase $phase [Iter $iteration]: $" . count($failing) . " Workshops UNTER Mindestanzahl → umverteilen");
// Alle TN aus unterbesetzten Workshops sammeln // Alle TN aus unterbesetzten Workshops sammeln
$to_reassign = []; $to_reassign = [];
@@ -202,98 +199,121 @@ function kc_run_zuteilung($wahl_id) {
} }
} }
if (!empty($to_reassign)) { if (empty($to_reassign)) {
error_log("Phase $phase: " . count($to_reassign) . " Teilnehmer werden umverteilt"); // Keine TN zu umverteilen
error_log("Phase $phase [Iter $iteration]: Keine Teilnehmer in unterbesetzten Workshops gefunden");
break;
}
error_log("Phase $phase [Iter $iteration]: " . count($to_reassign) . " Teilnehmer werden entfernt und neu verteilt");
// Alle Zuweisungen aus unterbesetzten Workshops löschen
$fw_list = implode(',', array_map('intval', $failing));
$wpdb->query("DELETE FROM {$prefix}kc_zuteilung
WHERE wahl_id = " . intval($wahl_id) . "
AND phase = " . intval($phase) . "
AND workshop_id IN ($fw_list)");
// Kapazitäten wieder freigeben
foreach($failing as $fw) {
$freed = $assigned_counts[$fw] ?? 0;
$caps[$fw] = $workshop_caps[$fw]; // Auf max zurücksetzen
}
// "Sichere" Workshops für Umverteilung: Kein Min ODER Min erfüllt (OHNE underbesetzte)
$safe_workshops = [];
foreach($ws_ids_in_wahl as $wsid) {
if (in_array($wsid, $failing)) continue; // Unterbesetzte raus
$min_req = $min_map[$wsid] ?? 0;
$cnt = $assigned_counts[$wsid] ?? 0;
// Alte Zuweisungen löschen // Sicher wenn:
$fw_list = implode(',', array_map('intval', $failing)); // A) Kein Minimum UND noch Platz, ODER
$wpdb->query("DELETE FROM {$prefix}kc_zuteilung // B) Min > 0 UND Min schon erreicht UND noch Platz, ODER
WHERE wahl_id = " . intval($wahl_id) . " // C) Min > 0 UND alles folgenden TN passen rein (reicht für Min)
AND phase = " . intval($phase) . " $remaining_tns_to_reassign = count($to_reassign);
AND workshop_id IN ($fw_list)"); $cap_left = $caps[$wsid] ?? 0;
// Kapazitäten wieder freigeben $can_fit = false;
foreach($failing as $fw) { if ($min_req == 0) {
$freed = $assigned_counts[$fw] ?? 0; $can_fit = $cap_left > 0;
$caps[$fw] = ($caps[$fw] ?? 0) + $freed; } else {
} // Wenn Min schon erfüllt, kann man noch hinzufügen
$current_cnt = $assigned_counts[$wsid] ?? 0;
// "Sichere" Workshops: Nur die, die KEIN Minimum haben ODER ihr Minimum BEREITS erfüllt haben if ($current_cnt >= $min_req && $cap_left > 0) {
$safe_workshops = []; $can_fit = true;
foreach($ws_ids_in_wahl as $wsid) { }
if (in_array($wsid, $failing)) continue; // Unterbesetzte raus // ODER: Wenn noch Platz für den Rest + Min
$min_req = $min_map[$wsid] ?? 0; elseif ($cap_left >= $min_req) {
$cnt = $assigned_counts[$wsid] ?? 0; $can_fit = true;
// Sicher wenn: kein Min UND Kapazität übrig OR Min erfüllt und noch Platz
if (($min_req == 0 && isset($caps[$wsid]) && $caps[$wsid] > 0) ||
($min_req > 0 && $cnt >= $min_req && isset($caps[$wsid]) && $caps[$wsid] > 0)) {
$safe_workshops[] = $wsid;
} }
} }
error_log("Phase $phase: " . count($safe_workshops) . " 'sichere' Workshops für Umverteilung verfügbar"); if ($can_fit) {
$safe_workshops[] = $wsid;
// Teilnehmer auf sichere Workshops verteilen }
foreach($to_reassign as $tid) { }
$tn = $all_teilnehmer_by_id[$tid] ?? $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$prefix}kc_teilnehmer WHERE id = %d", $tid error_log("Phase $phase [Iter $iteration]: " . count($safe_workshops) . " sichere Workshops verfügbar");
));
if (!$tn) continue; // Teilnehmer auf sichere Workshops neu verteilen
foreach($to_reassign as $tid) {
$reassigned = false; $tn = $all_teilnehmer_by_id[$tid] ?? $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$prefix}kc_teilnehmer WHERE id = %d", $tid
// Zuerst Wünsche versuchen (nur auf sichere Workshops) ));
for($w = 1; $w <= 3; $w++) { if (!$tn) continue;
$choice = intval($tn->{"wunsch$w"});
if ($choice > 0 && in_array($choice, $safe_workshops) && isset($caps[$choice]) && $caps[$choice] > 0) { $reassigned = false;
if ($assign($tn, $choice, $w)) {
$reassigned = true; // Zuerst Wünsche auf sichere Workshops
break; for($w = 1; $w <= 3; $w++) {
} $choice = intval($tn->{"wunsch$w"});
} if ($choice > 0 && in_array($choice, $safe_workshops) && isset($caps[$choice]) && $caps[$choice] > 0) {
} if ($assign($tn, $choice, $w)) {
$reassigned = true;
if ($reassigned) continue; break;
}
// Falls kein Wunsch auf sicherer Liste frei → zufällig auf sichere verteilen }
if (!empty($safe_workshops)) { }
$available = [];
foreach($safe_workshops as $wsid) { if ($reassigned) continue;
if (isset($caps[$wsid]) && $caps[$wsid] > 0) {
$available[] = $wsid; // Zufällig auf sichere Workshops
} if (!empty($safe_workshops)) {
} $available = [];
if (!empty($available)) { foreach($safe_workshops as $wsid) {
$target = $available[array_rand($available)]; if (isset($caps[$wsid]) && $caps[$wsid] > 0) {
if ($assign($tn, $target, 99)) { $available[] = $wsid;
$reassigned = true; }
} }
} if (!empty($available)) {
} $target = $available[array_rand($available)];
if ($assign($tn, $target, 99)) {
// Wenn immer noch nichts frei → unassigned $reassigned = true;
if (!$reassigned) { }
$wpdb->insert("{$prefix}kc_zuteilung", [ }
'wahl_id' => $wahl_id, }
'teilnehmer_id' => $tn->id,
'vorname' => $tn->vorname, // Falls immer noch nichts frei → unzugewiesen
'nachname' => $tn->nachname, if (!$reassigned) {
'phase' => $phase, $wpdb->insert("{$prefix}kc_zuteilung", [
'workshop_id' => null, 'wahl_id' => $wahl_id,
'wunsch_rang' => -1 'teilnehmer_id' => $tn->id,
]); 'vorname' => $tn->vorname,
} 'nachname' => $tn->nachname,
} 'phase' => $phase,
'workshop_id' => null,
// Unterbesetzte Workshops sind nun aus dem Pool raus 'wunsch_rang' => -1
foreach($failing as $fw) { ]);
error_log("Workshop $fw wird aus dem Pool entfernt (konnte nicht mit Minimum befüllt werden)");
} }
} }
} else {
error_log("Phase $phase: Alle Workshops erfüllen ihre Minimalanzahl ✓");
} }
} }
if ($iteration >= $max_iterations) {
error_log("Phase $phase: WARNUNG - Max Iterationen ($max_iterations) erreicht. Es könnten noch unterbesetzte Workshops existieren.");
}
// 7) Kapazitätsprüfung (Debug / Log) // 7) Kapazitätsprüfung (Debug / Log)