<? /* * This file is part of the 'Science Fair In A Box' project * SFIAB Website: http://www.sfiab.ca * * Copyright (C) 2005-2008 Sci-Tech Ontario Inc <info@scitechontario.org> * Copyright (C) 2008-2012 Youth Science Ontario <info@youthscienceontario.ca> * Copyright (C) 2005-2012 James Grant <james@lightbox.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation, version 2. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ ?> <? require_once ('../common.inc.php'); require_once ('../user.inc.php'); require_once ('../questions.inc.php'); require_once ('../projects.inc.php'); require_once ('judges.inc.php'); require_once ('anneal.inc.php'); // INFO ONLY: Re Windows OS. I have not found a test that works for both methods of starting this // SERVER_ADDR is Always null in Windows OS IIS server // when I launch using judges_sa_launcher_apache.php I could test using SERVER_NAME // However when I Launch using $WshShell->run($bat_filename,0,false ); for Windows IIS it seems: // All the $_SERVER variables are set as if were a website page so any variable I have tried will cause a bailout // THUS.. There is no test I have found to verify this was run from the command line (or in background) for Windows if (get_value_from_array($_SERVER, 'SERVER_ADDR')) { echo 'This script must be run from the command line'; exit; } // function TRACE() { } // function TRACE_R() { } function TRACE($str) { print ($str); } function TRACE_R($array) { print_r($array); } TRACE('<pre>'); $round_divisional1 = NULL; $round_divisional2 = NULL; function set_status($txt) { global $pdo; TRACE("Status: $txt\n"); $stmt = $pdo->prepare("UPDATE config SET val=? WHERE var='judge_scheduler_activity' AND year=0"); $stmt->execute([$txt]); } $set_percent_last_percent = -1; function set_percent($n) { global $pdo; global $set_percent_last_percent; $p = floor($n); if ($p == $set_percent_last_percent) return; TRACE("Progress: $p\%\n"); $stmt = $pdo->prepare("UPDATE config SET val=? WHERE var='judge_scheduler_percent' AND year=0"); $stmt->execute([$p]); $set_percent_last_percent = $p; } set_status('Initializing...'); set_percent(0); /* * The cost function is: * + 50 * each judge below the min for each team * + 10 * each judge above the max for each team * + 2 * each level of preference away from the * max level for each judge * + 40 if the team doesn't have a chair. * + 25 for each memember on the team that can't speak the language * of the judging team * * ( ex: if a judge has selected LS->2, PS->0, CS->-1 * then matching that judge with a: * LS = -4, * PS = 0, * CS = -2, * else = 0 * ) */ /* Compute the cost of adding a judge to a team */ function judges_cost_function($annealer, $bucket_id, $ids) { global $config; global $jteam; global $judges, $round_divisional2; /* Bucket ID is the team number */ /* ids are the judge ids currently in the bucket */ // TRACE("Bucket id=$bucket_id, ids="); // TRACE_R($ids); $cost = 0; $have_chair = false; $have_div2 = false; $years_experience = 0; if ($bucket_id == 0) { /* * This is the placeholder for all judges, there's a slight * cost for not using a judge */ $cost = count($ids) * 5; // TRACE("Extra judge team cost=$cost\n"); return $cost; } $t = &$jteam[$bucket_id]; /* Compute the over max / under min costs */ $c = count($ids); $min = ($c < $t['min_judges']) ? $t['min_judges'] - $c : 0; $max = ($c > $t['max_judges']) ? $c - $t['max_judges'] : 0; $cost += $min * 50; $cost += $max * 10; // add an additional large cost above the minimum requirement cost if the team is completely empty if ($c == 0) $cost += 50; // TRACE("Under min=$min, over max=$max\n"); /* For each judge on the team, score their preferences */ for ($x = 0; $x < count($ids); $x++) { $j = &$judges[$ids[$x]]; /* * Get the division, and see where it fits with this * judges preferences */ $cpref = 0; for ($y = 0; $y < count($t['cats']); $y++) { $l = $t['cats'][$y]; /* Lookup the judge cat pref for this category */ $pref = -$j['cat_prefs'][$l] + 2; /* $pref = 0 (best match) --- 4 (worst match) */ // but wait, if they're "indifferent" then we really dont care, so the cost for it shoudl be 0. if ($pref == 2) $pref = 0; $cpref += $pref; } $dpref = 0; for ($y = 0; $y < count($t['divs']); $y++) { $l = $t['divs'][$y]; /* Lookup the judge cat pref for this category */ $pref = -$j['div_prefs'][$l] + 5; /* $pref = 0 (best match) --- 4 (worst match) */ $dpref += $pref; } // TRACE("Judge {$ids[$x]}({$j['name']}) cp=$cpref, dp=$dpref\n"); $cost += 2 * $cpref; // division matching is more important than category matching $cost += 3 * $dpref; /* See if the judge is willing to chair a team */ if ($j['willing_chair'] == 'yes') $have_chair = true; /* * For each lang the team needs that the judge doesn't have, * increase the cost */ for ($y = 0; $y < count($t['langs']); $y++) { $l = $t['langs'][$y]; if (!in_array($l, $j['languages'])) $cost += 45; } /* * For each additional language that the judge knows that they dont need * increase the cost, this should hopefully stop the condition where * it uses up all the bilingual judges for english only teams * leaving no french/bilingual judges for the french teams */ $tlangs_count = count($t['langs']); $jlangs_count = count($j['languages']); if ($jlangs_count > $tlangs_count) $cost += ($jlangs_count - $tlangs_count) * 15; /* * If divisional round2 is enabled, make sure there is a judge * on the team for round2 */ if ($j['available_for_divisional2'] == true) $have_div2 = true; /* Add up the years experience */ $years_experience += $j['years_school'] + $j['years_regional'] + $j['years_national']; $years_experience_weighted += $j['years_school'] + $j['years_regional'] * 2 + $j['years_national'] * 4; } /* Huge penalty for a team without a willing chair, but only if the min judges per team >1 */ if (!$have_chair && $config['min_judges_per_team'] > 1) $cost += 40; /* Huge penalty for not having a round2 person on the team */ if ($round_divisional2 != NULL) { if ($have_div2 == false) $cost += 40; } /* * Small penalty for a jteam with very little experience, * but only if there's more than 1 person on the team */ if ($years_experience_weighted < 5 && count($ids) > 1) { $cost += (5 - $years_experience_weighted) * 2; } // TRACE("Team $bucket_id, cost is $cost\n"); return $cost; } $current_jdiv = array(); function jdiv_compute_cost($annealer, $bucket_id, $ids) { /* IDS is a list of project ids for a judging team */ global $current_jdiv; $cost = 0; $t_div = array(); $t_cat = array(); $t_lang = array(); /* * Foreach project this jteam is judging, record the * div/cat/lang */ for ($x = 0; $x < count($ids); $x++) { $proj = &$current_jdiv['projects'][$ids[$x]]; if (!in_array($proj['div'], $t_div)) $t_div[] = $proj['div']; if (!in_array($proj['cat'], $t_cat)) $t_cat[] = $proj['cat']; if (!in_array($proj['lang'], $t_lang)) $t_lang[] = $proj['lang']; } /* Square the project count for highter penalties for more projects */ $cost += floor(abs(count($ids) - $annealer->items_per_bucket)) * 100; /* Score 100 pts for multiple languages */ $cost += (count($t_lang) - 1) * 75; /* Score 25pts for multiple divs/cats */ $cost += (count($t_div) - 1) * 25; $cost += (count($t_cat) - 1) * 25; /* * Score +200 pts for each duplicate project this team is judging, we * really don't want a jteam judging the same project twice */ for ($x = 0; $x < count($ids) - 1; $x++) { for ($y = $x + 1; $y < count($ids); $y++) { if ($ids[$x] == $ids[$y]) $cost += 200; } } return $cost; } /* * Returns true if a judge time preference indicates they are available for the * specified round. Always returns true if judge time availablility selection * is off */ function judge_available_for_round($j, $r) { global $config; if ($config['judges_availability_enable'] == 'no') return true; foreach ($j['availability'] as $a) { if ($a['start'] <= $r['starttime'] && $a['end'] >= $r['endtime'] && $a['date'] == $r['date']) { return true; } } return false; } function judge_mark_for_round($j, $r) { /* * The judge has been assigned to round $r, modify their available to * exclude any time that falls within this time * TODO: modify the DB to store date/times in timestamps, so we don't * have to deal with dates separately. */ global $config; global $judges; if ($config['judges_availability_enable'] == 'no') return true; /* * Grab a pointer to the real judge, because we want to * modify it, not a copy of it */ $ju = &$judges[$j['id']]; $availability = &$ju['availability']; foreach ($availability as $key => &$a) { if ($r['starttime'] >= $a['start'] && $r['starttime'] <= $a['end']) { /* * Round starts in the middle of this availablity slot * modify this availabilty so it doesn't overlap */ /* This may cause $a['start'] == $a['end'], that's ok */ $a['end'] = $r['starttime']; // TRACE("adjust starttime\n"); } if ($r['endtime'] >= $a['start'] && $r['endtime'] <= $a['end']) { /* * Round ends in the middle of this availablity slot * modify this availabilty so it doesn't overlap */ /* This may cause $a['start'] == $a['end'], that's ok */ $a['start'] = $r['endtime']; // TRACE("adjust endtime\n"); } if ($a['start'] >= $a['end']) { /* Delete the whole round */ unset($ju['availability'][$key]); } } // print_r($ju['availability']); } /* * UNUSED: should be moved to the timeslot manager to ensure rounds * don't overlap. */ function rounds_overlap($r1, $r2) { $s1 = strtotime("{$r1['date']} {$r1['starttime']}"); $e1 = strtotime("{$r1['date']} {$r1['endtime']}"); $s2 = strtotime("{$r1['date']} {$r2['starttime']}"); $e2 = strtotime("{$r1['date']} {$r2['endtime']}"); if ($s1 <= $s2 && $e1 > $s1) return true; if ($s1 > $s2 && $s1 < $e2) return true; return false; } /* Print a judge */ function pr_judge(&$jt, $jid) { global $judges; $j = &$judges[$jid]; print (" - {$j['name']} (" . join(' ', $j['languages']) . ')'); print ('('); foreach ($jt['cats'] as $c) print ("c{$c}={$j['cat_prefs'][$c]} "); echo ' / '; foreach ($j['cat_prefs'] AS $k => $v) { print ("c{$k}=$v "); } echo ') ('; foreach ($jt['divs'] as $d) print ("d{$d}={$j['div_prefs'][$d]} "); echo ' / '; foreach ($j['div_prefs'] AS $k => $v) { print ("d{$k}=$v "); } print (')'); if ($j['willing_chair'] == 'yes') print (' chair '); print ("\n"); } set_status('Loading Data From Database...'); TRACE("\n\n"); $div = array(); TRACE("Loading Project Divisions...\n"); $q = $pdo->prepare("SELECT * FROM projectdivisions WHERE year=? ORDER BY id"); $q->execute([$config['FAIRYEAR']]); while ($r = $q->fetch(PDO::FETCH_OBJ)) { $divshort[$r->id] = $r->division_shortform; $div[$r->id] = $r->division; TRACE(" {$r->id} - {$div[$r->id]}\n"); } TRACE("Loading Project Age Categories...\n"); $cat = array(); $q = $pdo->prepare("SELECT * FROM projectcategories WHERE year=? ORDER BY id"); $q->execute([$config['FAIRYEAR']]); while ($r = $q->fetch(PDO::FETCH_OBJ)) { $cat[$r->id] = $r->category; TRACE(" {$r->id} - {$r->category}\n"); } TRACE("Loading Languages...\n"); $langr = array(); $q = $pdo->prepare("SELECT * FROM languages WHERE active='Y'"); $q->execute(); while ($r = $q->fetch(PDO::FETCH_OBJ)) { $langr[$r->lang] = $r->langname; TRACE(" {$r->lang} - {$r->langname}\n"); } TRACE("Loading Judging Round time data...\n"); $round_special_awards = array(); $round = array(); $q = $pdo->prepare("SELECT * FROM judges_timeslots WHERE round_id='0' AND `year`=?"); $q->execute([$config['FAIRYEAR']]); /* Loads judges_timeslots.id, .starttime, .endtime, .date, .name */ while ($r = $q->fetch(PDO::FETCH_ASSOC)) { TRACE(" id:{$r['id']} type:{$r['type']} name:{$r['name']}\n"); $qq = $pdo->prepare("SELECT * FROM judges_timeslots WHERE round_id=?"); $qq->execute([$r['id']]); if ($qq->rowCount() == 0) { echo "ERROR: Round type:{$r['type']} name:{$r['name']} has no judging timeslots! Abort.\n"; exit; } while ($rr = $qq->fetch(PDO::FETCH_ASSOC)) { TRACE(" Timeslot: {$rr['starttime']}-{$rr['endtime']}\n"); $r['timeslots'][] = $rr; } $round[] = $r; if ($r['type'] == 'divisional1') $round_divisional1 = $r; if ($r['type'] == 'divisional2') $round_divisional2 = $r; if ($r['type'] == 'special') $round_special_awards[] = $r; } if ($round_divisional1 == NULL) { echo "No divisional1 round defined! Aborting!\n"; exit; } $jdiv = array(); TRACE("Loading Judging Division Configuration and Projects...\n"); $q = $pdo->prepare('SELECT * FROM judges_jdiv'); $q->execute(); while ($r = $q->fetch(PDO::FETCH_OBJ)) { /* Ignore jdiv 0 (all unassigned div/cats) */ if ($r->jdiv_id == 0) continue; $jdiv[$r->jdiv_id]['config'][] = array('div' => $r->projectdivisions_id, 'cat' => $r->projectcategories_id, 'lang' => $r->lang); } $keys = array_keys($jdiv); foreach ($keys as $jdiv_id) { TRACE(" $jdiv_id - "); $jdiv[$jdiv_id]['projects'] = array(); for ($x = 0; $x < count($jdiv[$jdiv_id]['config']); $x++) { $d = $jdiv[$jdiv_id]['config'][$x]; if ($x > 0) TRACE("\t- "); TRACE($cat[$d['cat']] . ' ' . $div[$d['div']] . ' - ' . $langr[$d['lang']]); $qp = $pdo->prepare('SELECT projects.* FROM projects, registrations WHERE ' . " projects.year=? AND " . " projectdivisions_id=? AND " . " projectcategories_id=? AND " . " language=? AND " . ' registrations.id = projects.registrations_id ' . getJudgingEligibilityCode()); $qp->execute([$config['FAIRYEAR'],$d['div'],$d['cat'],$d['lang']]); $count = 0; while ($rp = $qp->fetch(PDO::FETCH_OBJ)) { $jdiv[$jdiv_id]['projects'][$rp->id] = array( 'div' => $d['div'], 'cat' => $d['cat'], 'lang' => $d['lang'] ); $jdiv[$jdiv_id]['award_ids'] = array(); $count++; } TRACE(" ($count projects)\n"); } if (count($jdiv[$jdiv_id]['projects']) == 0) { TRACE("\t- This div has no projects, removing.\n"); unset($jdiv[$jdiv_id]); } } /* Clean out the judging teams that were autocreated in a previous run */ TRACE('Deleting autocreated divisional and special award judging teams:'); $q = $pdo->prepare("SELECT * FROM judges_teams WHERE autocreate_type_id=1 AND year=?"); $q->execute([$config['FAIRYEAR']]); while ($r = $q->fetch(PDO::FETCH_OBJ)) { $id = $r->id; print (" $id"); /* Clean out the judges_teams_link */ $stmt = $pdo->prepare("DELETE FROM judges_teams_link WHERE judges_teams_id=? AND year=?"); $stmt->execute([$id,$config['FAIRYEAR']]); show_pdo_errors_if_any($pdo); /* Awards */ $stmt = $pdo->prepare("DELETE FROM judges_teams_awards_link WHERE judges_teams_id=? AND year=?"); $stmt->execute([$id,$config['FAIRYEAR']]); show_pdo_errors_if_any($pdo); /* Timeslots */ $stmt = $pdo->prepare("DELETE FROM judges_teams_timeslots_link WHERE judges_teams_id=? AND year=?"); $stmt->execute([$id,$config['FAIRYEAR']]); show_pdo_errors_if_any($pdo); /* Timeslots projects */ $stmt = $pdo->prepare("DELETE FROM judges_teams_timeslots_projects_link WHERE judges_teams_id=? AND year=?"); $stmt->execute([$id,$config['FAIRYEAR']]); show_pdo_errors_if_any($pdo); } echo "\n"; /* Finally, delete all the autocreated judges teams */ $stmt = $pdo->prepare("DELETE FROM judges_teams WHERE autocreate_type_id=1 AND year=?"); $stmt->execute([$config['FAIRYEAR']]); show_pdo_errors_if_any($pdo); /* * Also delete any judges_teams_link that link to teams that dont exist, just * in case */ $q = $pdo->prepare("SELECT judges_teams_link.id, judges_teams.id AS judges_teams_id FROM judges_teams_link LEFT JOIN judges_teams ON judges_teams_link.judges_teams_id=judges_teams.id WHERE judges_teams_link.year=?"); $q->execute([$config['FAIRYEAR']]); $n = 0; while ($r = $q->fetch(PDO::FETCH_OBJ)) { if (!$r->judges_teams_id) { $stmt = $pdo->prepare("DELETE FROM judges_teams_link WHERE id=?"); $stmt->execute([$r->id]); $n++; } } print ("Deleted $n orphaned team linkings\n"); TRACE(" Done.\n"); set_status('Loading Judges'); $judges = judges_load_all(); foreach ($judges as &$j) { if ($j['judge_active'] == 'no') { TRACE(" {$j['name']} has their judge profile deactivated, skipping.\n"); unset($judges[$j['id']]); continue; } if ($j['judge_complete'] == 'no') { TRACE(" {$j['name']} hasn't completed their judge profile, skipping.\n"); unset($judges[$j['id']]); continue; } $q = $pdo->prepare("SELECT users_id FROM judges_teams_link WHERE users_id=? AND year=?"); $q->execute([$j['id'],$config['FAIRYEAR']]); if ($q->rowCount() != 0) { TRACE(" {$j['name']} is already on a judging team, skipping.\n"); unset($judges[$j['id']]); continue; } if ($config['judges_availability_enable'] == 'yes') { /* Load the judge time availability */ $q = $pdo->prepare("SELECT * FROM judges_availability WHERE users_id=? ORDER BY `start`"); $q->execute([$j['id']]); if ($q->rowCount() == 0) { TRACE(" {$j['name']} hasn't selected any time availability, POTENTIAL BUG (they shouldn't be marked as complete).\n"); TRACE(" Ignoring this judge.\n"); unset($judges[$j['id']]); continue; } while ($r = $q->fetch(PDO::FETCH_ASSOC)) { $j['availability'][] = $r; } } /* Load special award preferences */ $q = $pdo->prepare("SELECT award_awards.id,award_awards.name FROM judges_specialaward_sel,award_awards WHERE award_awards.id=judges_specialaward_sel.award_awards_id AND judges_specialaward_sel.users_id=? AND award_awards.year=?"); $q->execute([$j['id'],$config['FAIRYEAR']]); show_pdo_errors_if_any($pdo); if ($j['special_award_only'] == 'yes') { TRACE(" {$j['name']} is a special awards only.\n"); /* Find their special award id */ if ($q->rowCount() == 0) { TRACE(" NO special award selected! (removing special award only request)\n"); $j['special_award_only'] = 'no'; // } else if($q->rowCount()> 1) { // TRACE(" More than ONE special award selected (removing special award only request):\n"); // $j['special_award_only'] = 'no'; } } $j['special_awards'] = array(); while ($r = $q->fetch(PDO::FETCH_OBJ)) { if ($j['special_award_only'] == 'yes') { TRACE(" {$r->name}\n"); } /* * Add them to the SA judge list (modify the actual list, not * $j, which is a copy */ $j['special_awards'][] = $r->id; } /* * optimization, so the div1 cost function can try to find one * round2 judge per team */ $j['available_for_divisional2'] = judge_available_for_round($j, $round_divisional2); } unset($j); TRACE('Loaded ' . count($judges) . " judges\n"); $jteam[0]['max_judges'] = count($judges); if (count($judges) == 0) { echo "No judges available. Aborting!\n"; set_status('Error - no judges available...'); set_percent(0); exit; } /* * Load the numbers for any user-defined judge teams that already exist, * these numbers will be off-limits for auto-assigning numbers */ $q = $pdo->prepare("SELECT * FROM judges_teams WHERE year=?"); $q->execute([$config['FAIRYEAR']]); $used_judges_teams_numbers = array(); while ($i = $q->fetch(PDO::FETCH_ASSOC)) { $used_judges_teams_numbers[] = $i['num']; } echo "The following judge team numbers are already used: \n"; print_r($used_judges_teams_numbers); $next_judges_teams_number_try = 1; /* A function to get the next available number */ function next_judges_teams_number() { global $used_judges_teams_numbers; global $next_judges_teams_number_try; while (1) { if (!in_array($next_judges_teams_number_try, $used_judges_teams_numbers)) break; $next_judges_teams_number_try++; } $r = $next_judges_teams_number_try; $next_judges_teams_number_try++; return $r; } function judge_team_create($num, $name) { global $config, $pdo; $name = $pdo->quote($name); $stmt = $pdo->prepare(' INSERT INTO judges_teams (num, name, autocreate_type_id, year) VALUES (:num, :name, :autocreate_type_id, :year) '); $stmt->bindValue(':num', $num, PDO::PARAM_INT); $stmt->bindValue(':name', $name, PDO::PARAM_STR); $stmt->bindValue(':autocreate_type_id', 1, PDO::PARAM_INT); $stmt->bindValue(':year', $config['FAIRYEAR'], PDO::PARAM_INT); $stmt->execute(); $id = $pdo->lastInsertId(); return $id; } function judge_team_add_judge($team_id, $users_id) { global $config, $judges; $stmt = $pdo->prepare("INSERT INTO judges_teams_link (users_id, judges_teams_id, captain, year) VALUES (?, ?, ?, ?)"); $stmt->execute([$users_id, $team_id, $judges[$users_id]['willing_chair'], $config['FAIRYEAR']]); show_pdo_errors_if_any($pdo); } /* * Round 1 Divisional Scheduling * - Compute required divisional judge teams * - Delete existing ones * - Anneal Projects to Teams * - Anneal Judtes to Projects */ set_status('Computing required judging teams'); TRACE(" Each judging team may judge {$config['max_projects_per_team']} projects\n"); TRACE(" Each project must be judged {$config['times_judged']} times\n"); $keys = array_keys($jdiv); foreach ($keys as $jdiv_id) { $c = count($jdiv[$jdiv_id]['projects']); $t = ceil($c / $config['max_projects_per_team'] * $config['times_judged']); if ($t < $config['times_judged']) $t = $config['times_judged']; TRACE(" $jdiv_id has $c projects, requires $t judging teams\n"); $jdiv[$jdiv_id]['num_jteams'] = $t; } $jteam = array(); $jteam_id = 0; /* Create one more jteam, for anyone the annealer doesn't want to place */ $jteam[$jteam_id]['id'] = $jteam_id; $jteam[$jteam_id]['projects'] = array(); $jteam[$jteam_id]['divs'] = array(); $jteam[$jteam_id]['cats'] = array(); $jteam[$jteam_id]['langs'] = array(); $jteam[$jteam_id]['min_judges'] = 0; $jteam[$jteam_id]['max_judges'] = 0; $jteam_id++; set_status('Assigning projects to judging teams'); $keys = array_keys($jdiv); for ($k = 0; $k < count($keys); $k++) { $jdiv_id = $keys[$k]; TRACE("Judging Division $jdiv_id ({$jdiv[$jdiv_id]['num_jteams']} teams): \n"); $project_ids = array(); for ($x = 0; $x < $config['times_judged']; $x++) { $project_ids = array_merge($project_ids, array_keys($jdiv[$jdiv_id]['projects'])); } $current_jdiv = $jdiv[$jdiv_id]; $e = 100 + 10 * ($config['effort'] / 1000); $a = new annealer($jdiv[$jdiv_id]['num_jteams'], 125, $e, 0.9, 'jdiv_compute_cost', $project_ids); $a->anneal(); $jdiv[$jdiv_id]['jteams'] = array(); for ($x = 0; $x < $a->num_buckets; $x++) { $bkt = $a->bucket[$x]; TRACE(" SubTeam $x: (jteam $jteam_id)\n"); $jdiv[$jdiv_id]['jteams'][] = $jteam_id; $jteam[$jteam_id]['id'] = $jteam_id; $jteam[$jteam_id]['num'] = next_judges_teams_number(); $jteam[$jteam_id]['projects'] = $a->bucket[$x]; $jteam[$jteam_id]['sub'] = $x; $jteam[$jteam_id]['jdiv_id'] = $jdiv_id; $jteam[$jteam_id]['divs'] = array(); $jteam[$jteam_id]['cats'] = array(); $jteam[$jteam_id]['langs'] = array(); $jteam[$jteam_id]['min_judges'] = $config['min_judges_per_team']; $jteam[$jteam_id]['max_judges'] = $config['max_judges_per_team']; foreach ($bkt as $projid) { $p = $jdiv[$jdiv_id]['projects'][$projid]; TRACE(" $projid - " . $cat[$p['cat']] . ' ' . $div[$p['div']] . ' - ' . $langr[$p['lang']] . "\n"); if (!in_array($p['cat'], $jteam[$jteam_id]['cats'])) { $jteam[$jteam_id]['cats'][] = $p['cat']; } if (!in_array($p['div'], $jteam[$jteam_id]['divs'])) { $jteam[$jteam_id]['divs'][] = $p['div']; } if (!in_array($p['lang'], $jteam[$jteam_id]['langs'])) { $jteam[$jteam_id]['langs'][] = $p['lang']; } } $jteam_id++; } } TRACE('There are ' . (count($jteam) - 1) . " judging teams\n"); TRACE("Finding judges available for round1 divisional\n"); $div1_judge_ids = array(); foreach ($judges as $j) { if (judge_available_for_round($j, $round_divisional1) == false) continue; if ($j['special_award_only'] == 'yes') continue; /* If we get here, the judge is ok for div1 */ $div1_judge_ids[] = $j['id']; } TRACE(count($div1_judge_ids) . " judges available for round1 divisional\n"); function judges_to_teams_update($progress, $total) { set_percent(($progress * 50) / $total); } set_status('Assigning Judges to Teams'); $e = $config['effort']; $a = new annealer(count($jteam), 25, $e, 0.98, 'judges_cost_function', $div1_judge_ids); $a->set_update_callback('judges_to_teams_update'); $a->anneal(); for ($x = 1; $x < count($jteam); $x++) { $t = &$jteam[$x]; print ("Judging Team {$t['num']}: cost={$a->bucket_cost[$x]} "); $lang_array = $t['langs']; asort($lang_array); $langstr = implode(' ', $lang_array); // sort the cats and divs too, so we dont end up with "int/sen" <--> "sen/int" asort($t['cats']); asort($t['divs']); print ("langs=($langstr) "); print ('cats=('); $catstr = ''; if (count($t['cats'])) { $first = true; foreach ($t['cats'] AS $cid) { print ('c' . $cid . ' '); if (!$first) $catstr .= '+'; $catstr .= $cat[$cid]; $first = false; } } print (') divs=('); $divstr = ''; if (count($t['divs'])) { $first = true; foreach ($t['divs'] AS $did) { print ('d' . $did . ' '); if (!$first) $divstr .= '/'; $divstr .= $div[$did]; $first = false; } } print (")\n"); /* Add this judging team to the database */ $tn = "$catstr $divstr ($langstr) " . ($t['sub'] + 1); $team_id = judge_team_create($t['num'], $tn); $t['team_id'] = $team_id; $ids = $a->bucket[$x]; for ($y = 0; $y < count($ids); $y++) { pr_judge($t, $ids[$y]); $j = &$judges[$ids[$y]]; /* Mark this judge as used in round1 divisional */ judge_mark_for_round($j, $round_divisional1); /* Add the judge to our internal team list */ $t['judge_ids'][] = $j['id']; /* Write the SQL to do it */ judge_team_add_judge($team_id, $j['id']); } /* * Get the original jdiv that this team was created from. The exact * breakdown of each and every div/cat/lang that this team is judging * is in the jdiv['config'] array */ $jd = $jdiv[$t['jdiv_id']]; for ($y = 0; $y < count($jd['config']); $y++) { $cfg = $jd['config'][$y]; $q = $pdo->prepare("SELECT award_awards.id FROM award_awards, award_awards_projectcategories, award_awards_projectdivisions WHERE award_awards.year=? AND award_awards.id=award_awards_projectcategories.award_awards_id AND award_awards.id=award_awards_projectdivisions.award_awards_id AND award_awards_projectcategories.projectcategories_id=? AND award_awards_projectdivisions.projectdivisions_id=? AND award_awards.award_types_id='1' "); $q->execute([$config['FAIRYEAR'],$cfg['cat'],$cfg['div']]); if ($q->rowCount() != 1) { echo error(i18n('Cannot find award for %1 - %2', array($cat[$cfg['cat']], $div[$cfg['div']]))); } else { $r = $q->fetch(PDO::FETCH_OBJ); $stmt = $pdo->prepare("INSERT INTO judges_teams_awards_link (award_awards_id,judges_teams_id,year) VALUES (?,?,?)"); $stmt->execute([$r->id,$team_id,$config['FAIRYEAR']]); /* Add the award ID to the jdiv, if it's not already there */ if (!in_array($r->id, $jdiv[$t['jdiv_id']]['award_ids'])) { $jdiv[$t['jdiv_id']]['award_ids'][] = $r->id; } } } } print ("Unused Judges:\n"); $ids = $a->bucket[0]; for ($y = 0; $y < count(get_value_or_default($ids, [])); $y++) { pr_judge($jteam[0], $ids[$y]); } /* * Round 2 Divisional Scheduling * - Find a judge on each team that is available for both rounds * - Mark them as used * - No annealing required */ if ($round_divisional2 == NULL) { echo "No Round 2 Divisional defined, skipping.\n"; } else { echo "Finding round2 carry-over judges:\n"; foreach ($jdiv as $jdiv_id => $jd) { $num = next_judges_teams_number(); $team_id = judge_team_create($num, 'Round 2 Divisional ' . $jdiv_id); TRACE("Created Round2 team id $team_id\n"); /* Find all the jteams in this jdiv */ for ($x = 1; $x < count($jteam); $x++) { $t = &$jteam[$x]; if ($t['jdiv_id'] != $jdiv_id) continue; TRACE(" Round1 team #{$t['num']} ({$t['id']})\n"); $rep_id = NULL; $chair_rep = false; /* * We would like the willing_chair to be the person that sticks around * for round2, but if that's not possible, prefer anyone on the jteam be * around for round2 */ foreach ($t['judge_ids'] as $judge_id) { $j = &$judges[$judge_id]; if (judge_available_for_round($j, $round_divisional2)) { if ($j['willing_chair'] == true) { $rep_id = $judge_id; $chair_rep = true; break; } else if ($chair_rep == false) { $rep_id = $judge_id; } } } if ($rep_id != NULL) { pr_judge($t, $rep_id); /* Mark this judge as used in this round */ judge_mark_for_round($judges[$rep_id], $round_divisional2); /* Write it to the DB */ judge_team_add_judge($team_id, $rep_id); } else { echo "WARNING: Team $x has no carryover judge.\n"; } } /* Assign all the awards in this jdiv */ foreach ($jd['award_ids'] as $aid) { $stmt = $pdo->prepare("INSERT INTO judges_teams_awards_link (award_awards_id,judges_teams_id,year) VALUES (?,?,?)"); $stmt->execute([$aid,$team_id,$config['FAIRYEAR']]); } } } /* * Special Awards * - Find ALL special award rounds * - special case: for awards with judges who are special awards * only, that award should match their timeslot * - Assign special awards to rounds based on needed judges * - Assign judges to teams */ /* ==================================================================== */ /* * Two functions for the Special Award Annealer, if special award * scheduling is disabled, these will never get called */ $current_jteam_ids = array(); function judges_sa_cost_function($annealer, $bucket_id, $ids) { global $sa_jteam; global $judges, $current_jteam_ids; /* Bucket ID is the team number */ /* ids are the judge ids currently in the bucket */ $cost = 0; if ($bucket_id == 0) { /* This is the placeholder */ $cost = count($ids) * 50; return $cost; } $t = &$sa_jteam[$current_jteam_ids[$bucket_id]]; /* Compute the over max / under min costs */ $c = count($ids); $min = ($c < $t['min_judges']) ? $t['min_judges'] - $c : 0; $max = ($c > $t['max_judges']) ? $c - $t['max_judges'] : 0; $cost += $min * 50; $cost += $max * 10; /* For each judge on the team, score their preferences */ for ($x = 0; $x < count($ids); $x++) { $j = &$judges[$ids[$x]]; $apref = 0; /* * Make sure this judge isn't on the team more than * once. For S-A only judges, we duplicate the judge IDs * so the judge can be scheduled on multiple teams */ if ($j['special_award_only'] == 'yes') { for ($i = 0; $i < count($ids); $i++) { if ($i == $x) continue; if ($ids[$i] == $ids[$x]) $cost += 1000; } } /* * See if the sa_jteam award id (what the team is judging) * is in the judges selection list */ /* Run through all awards this team is judging */ // TRACE(" - {$j['name']}\n"); foreach ($t['award_ids'] as $aid) { if (in_array($aid, $j['special_awards'])) { // / TRACE(" - award match\n"); /* This judge wants to judge this award */ /* No cost */ } else { if ($j['special_award_only'] == 'yes') { // TRACE(" - sa only mismatch\n"); /* * This judge is for an award, but * NOT assigned to the proper one, * HUGE cost */ $cost += 500; } $apref++; } } $cost += 5 * $apref; } // TRACE("Team $bucket_id, cost is $cost\n"); return $cost; } if ($config['scheduler_enable_sa_scheduling'] == 'yes') { TRACE("Finding judges for special award round(s)\n"); foreach ($round_special_awards as &$r) { $r['available_judge_ids'] = array(); } $total_judges = 0; foreach ($judges as &$j) { TRACE(" {$j['firstname']} {$j['lastname']}\n"); foreach ($round_special_awards as &$r) { if (judge_available_for_round($j, $r) == true) { TRACE(" {$r['name']} yes\n"); $r['available_judge_ids'][] = $j['id']; $total_judges++; } else { TRACE(" {$r['name']} no\n"); } } } unset($j); unset($r); set_status('Creating Special Award Judging Teams (one team per award)'); /* Load special awards */ $q = "SELECT award_awards.name,award_awards.id FROM award_awards,award_types WHERE award_awards.year=? AND award_types.id=award_awards.award_types_id AND award_awards.schedule_judges='yes' AND award_types.year=? AND award_types.type='Special' "; $r = $pdo->prepare($q); $r->execute([$config['FAIRYEAR'],$config['FAIRYEAR']]); print ($pdo->errorInfo()); /* sa_jteam for leftover judges, if any */ $sa_jteam = array(); $sa_jteam[0]['id'] = 0; $sa_jteam[0]['projects'] = array(); $sa_jteam[0]['langs'] = array(); $sa_jteam[0]['min_judges'] = 0; $sa_jteam[0]['max_judges'] = 0; $sa_jteam[0]['award_ids'] = array(); $x = 1; $required_judges = 0; while ($i = $r->fetch(PDO::FETCH_OBJ)) { $projects = getProjectsNominatedForSpecialAward($i->id); $languages = getLanguagesOfProjectsNominatedForSpecialAward($i->id); /* * Construct an internal team for annealing, and create * a DB team too */ $sa_jteam[$x]['num'] = next_judges_teams_number(); $sa_jteam[$x]['id'] = judge_team_create($sa_jteam[$x]['num'], $i->name . ' (' . implode(' ', $languages) . ')'); /* Note, we use $x instead of the ID, because the DB id could be zero. */ $sa_jteam[$x]['projects'] = $projects; $sa_jteam[$x]['round'] = NULL; $sa_jteam[$x]['sub'] = 0; $sa_jteam[$x]['langs'] = array(); $min = floor(count($projects) / $config['projects_per_special_award_judge']) + 1; $sa_jteam[$x]['min_judges'] = $min; $sa_jteam[$x]['max_judges'] = $min; $sa_jteam[$x]['award_ids'] = array($i->id); $sa_jteam[$x]['name'] = $i->name; $required_judges += $min; /* Link the award to this team */ $stmt = $pdo->prepare("INSERT INTO judges_teams_awards_link (award_awards_id,judges_teams_id,year) VALUES (?,?,?)"); $stmt->execute([$i->id,$sa_jteam[$x]['id'],$config['FAIRYEAR']]); TRACE("Created Team: {$i->name}, " . count($projects) . " projects => $min judges needed (db id:{$sa_jteam[$x]['id']}) \n"); $x++; } TRACE("Total Judges: $total_judges, Required: $required_judges\n"); /* ==================================================================== */ set_status("Assigning Special Award Teams to Special Award Round(s)\n"); /* * Compute how many judges each round needs based on the total number * of needed judges, e.g. if SAround1 has 10 judges available and SAround2 * has 20 judges available, and we total need 90 judges, then we * want to assign jteams so that SAround1 has 30 slots, and SAround2 has * 60 to balance the deficit */ foreach ($round_special_awards as &$r) { $x = count($r['available_judge_ids']); $target = ($x * $required_judges) / $total_judges; $r['target_judges'] = $target; TRACE("Round {$r['name']} should be assigned $target judge timeslots\n"); /* * Setup for the next step, always add special award * judge team 0 to ALL rounds */ $r['jteam_ids'] = array(0); $r['assigned_judges'] = 0; } unset($r); /* ==================================================================== */ /* * Scan the list of special awards, check each special award to see if * it has special award only judges, we want those special awards pre-assigned * to rounds where ALL SA-only judges are available, or, as best we can. */ foreach ($sa_jteam as $x => &$jt) { if ($x == 0) continue; $sa_judges = array(); foreach ($round_special_awards as $i => $r) { $sa_round_count[$i] = 0; } foreach ($jt['award_ids'] as $aid) { foreach ($judges as $jid => $j) { if ($j['special_award_only'] == 'no') continue; if (in_array($aid, $j['special_awards'])) { $sa_judges[] = $jid; foreach ($round_special_awards as $i => $r) { // TRACE("Checking {$j['name']} in round {$r['name']}\n"); if (judge_available_for_round($j, $r)) { // TRACE(" yes, round $i ++\n"); $sa_round_count[$i]++; } } } } } /* If there are no SA-only judges, skip the pre-assignment */ if (count($sa_judges) == 0) continue; /* * There are count($sa_judges), find the round * with the highest count */ $highest_count = 0; $highest_offset = -1; foreach ($round_special_awards as $i => $r) { if ($sa_round_count[$i] > $highest_count || $highest_offset == -1) { $highest_count = $sa_round_count[$i]; $highest_offset = $i; } } /* Assign this jteam to that round */ $round_special_awards[$highest_offset]['jteam_ids'][] = $x; $round_special_awards[$highest_offset]['assigned_judges'] += $jt['min_judges']; TRACE("Pre-assigning Team {$jt['name']} to Round {$round_special_awards[$highest_offset]['name']}\n"); $jt['assigned'] = true; /* * If the max judges for the jteam is less than the max, update the max, * this prevents the scheduler from trying to remove sa-only judges * from the jteam because of the over-max cost penalty */ if ($jt['max_judges'] < count($sa_judges)) { TRACE(' Changing max_judges to ' . count($sa_judges) . " to accomodate all SA-only judge requests.\n"); $jt['max_judges'] = count($sa_judges); } } unset($jt); /* * Use a greedy algorithm to assign the remaining jteams. First sort * the teams by the number of judges needed so those can be assigned * first */ function sa_cmp($a, $b) { return $b['min_judges'] - $a['min_judges']; } uasort($sa_jteam, 'sa_cmp'); foreach ($sa_jteam as $x => $jt) { if ($x == 0) continue; if ($jt['assigned'] == true) continue; $highest = 0; $highest_offset = -1; /* * Find the round with the highest missing judges, this works * even if the $p computation is negative */ foreach ($round_special_awards as $o => $r) { $p = $r['target_judges'] - $r['assigned_judges']; // TRACE(" Round {$r['name']} p=$p\n"); if ($highest_offset == -1 || $p > $highest) { $highest = $p; $highest_offset = $o; } } /* Assign this jteam id to the special award round */ $round_special_awards[$highest_offset]['jteam_ids'][] = $x; $round_special_awards[$highest_offset]['assigned_judges'] += $jt['min_judges']; TRACE("Assigned Team {$jt['name']} to Round {$round_special_awards[$highest_offset]['name']}\n"); } unset($jt); /* * Now that teams have been assigned to rounds, search for all the * SA only judges again, and duplicate the available judge id if they are signed * up to judge more than one award in the round */ foreach ($judges as &$j) { if ($j['special_award_only'] == 'no') continue; foreach ($round_special_awards as &$r) { $count = 0; if (judge_available_for_round($j, $r) == false) continue; /* Find out how many of their special awards are in this round. */ foreach ($sa_jteam as $jt_id => &$jt) { /* Is the team in this round? */ if (!in_array($jt_id, $r['jteam_ids'])) continue; /* Is this SA judge requsing an award judged by this team? */ foreach ($jt['award_ids'] as $aid) { if (in_array($aid, $j['special_awards'])) $count++; } } unset($jt); while ($count > 1) { $r['available_judge_ids'][] = $j['id']; $count--; TRACE(" Duplicate {$j['firstname']} {$j['lastname']} for multiple SA-only request in round {$r['name']}\n"); } } unset($r); } unset($j); /* Now, anneal in each special award round */ foreach ($round_special_awards as $r) { set_status("Assigning Judges in round {$r['name']}\n"); $current_jteam_ids = $r['jteam_ids']; $judge_ids = $r['available_judge_ids']; $e = $config['effort']; $a = new annealer(count($r['jteam_ids']), 25, $e, 0.98, 'judges_sa_cost_function', $judge_ids); // $a->set_update_callback(judges_to_teams_update); // $a->set_pick_move(judges_sa_pick_move); $a->anneal(); $x = 0; unset($t); unset($tid); foreach ($r['jteam_ids'] as $tid) { if ($tid == 0) { $x++; continue; } $t = &$sa_jteam[$tid]; print ("Judging Team {$t['id']} \"{$t['name']}\": cost={$a->bucket_cost[$x]} #=({$t['min_judges']},{$t['max_judges']}) "); // print("langs=("); /* $langstr=""; for($y=0; $y<count($t['langs']); $y++) { if($y != 0) $langstr .= " "; $langstr .= $t['langs'][$y]; } print("$langstr)");*/ print ("\n"); /* Do timeslot and project timeslot assignment */ $stmt = $pdo->prepare("INSERT INTO judges_teams_timeslots_link (judges_teams_id,judges_timeslots_id,year) VALUES (?,?,?)"); $stmt->execute([$t['id'],$r['timeslots'][0]['id'],$config['FAIRYEAR']]); show_pdo_errors_if_any($pdo); foreach ($t['projects'] as $proj) { $pid = $proj['id']; $stmt = $pdo->prepare("INSERT INTO judges_teams_timeslots_projects_link (judges_teams_id,judges_timeslots_id,projects_id,year) VALUES (?,?,?,?)"); $stmt->execute([$t['id'],$r['timeslots'][0]['id'],$pid,$config['FAIRYEAR']]); show_pdo_errors_if_any($pdo); } $ids = $a->bucket[$x]; foreach ($a->bucket[$x] as $jid) { // pr_judge($t, $ids[$y]); $j = &$judges[$jid]; print (" - {$j['name']}\n"); /* Link Judges to the judging team we just inserted */ judge_team_add_judge($t['id'], $jid); } $x++; } } } /* Resume normal flow now */ /* * Timeslot Scheduling */ /* ==================================================================== */ set_status('Assigning Judging Teams and Projects to Timeslots'); TRACE("Loading Divisional1 Timeslot Data\n"); $available_timeslots = array(); $q = $pdo->prepare("SELECT * FROM judges_timeslots WHERE round_id=? AND year=? AND type='timeslot' ORDER BY date,starttime"); $q->execute([$round_divisional1['id'],$config['FAIRYEAR']]); $x = 0; while ($r = $q->fetch(PDO::FETCH_OBJ)) { $available_timeslots[] = array('id' => $r->id, 'date' => $r->date, 'starttime' => substr($r->starttime, 0, -3), 'endtime' => substr($r->endtime, 0, -3)); print (' ' . $available_timeslots[$x]['starttime'] . ' -> ' . $available_timeslots[$x]['endtime'] . "\n"); $x++; } $n_timeslots = count($available_timeslots); /* First, check to see if the project is being judged 3 or * more times in a row, OR, if it has large gaps that aren't at the end of the judging */ /* * I'm going to leave this here, for now, we shoudl do something like * this at some point in evaluating projects, but right now * the randomness is pretty good. */ /* for($x=0; $x<count($project_index); $x++) { $i_start = $x * $n_timeslots; $i_end = ($x+1) * $n_timeslots; $z_count = 0; $r_count = 0; for($y=$i_start; $y<$i_end; $y++) { $jteam_id = $ids[$y]; if($jteam_id == 0) { $z_count++; $r_count=0; } else {*/ /* * Do the z_count cost here so we don;t * count any zcount cost for the end * of the timetable */ /* * if($z_count > 2) $cost += $z_count; * $r_count++; * $z_count=0; * if($r_count > 2) $cost += $r_count; * } * } * } */ function timeslot_pick_move($a) { /* * Use the existing pick move, but we want the item numbers * in each bucket to always be the same */ list($b1, $i1, $b2, $i2) = $a->pick_move(); $i2 = $i1; return array($b1, $i1, $b2, $i2); } function timeslot_cost_function($annealer, $bucket_id, $ids) { $cost = 0; /* * Check to make sure a judge isn't judging two projects * at the same time */ $n_pids = count($ids); for ($x = 0; $x < $n_pids - 1; $x++) { $jteam_id1 = $ids[$x]; if ($jteam_id1 == 0) continue; for ($y = $x + 1; $y < $n_pids; $y++) { $jteam_id2 = $ids[$y]; if ($jteam_id1 == $jteam_id2) $cost += 50; } } return $cost; } $keys = array_keys($jdiv); $keys_count = count($keys); for ($k = 0; $k < $keys_count; $k++) { $jdiv_id = $keys[$k]; $pids = array_keys($jdiv[$jdiv_id]['projects']); $n_projects = count($pids); if ($n_projects == 0) continue; unset($project_rlookup); $project_rlookup = array(); for ($x = 0; $x < count($pids); $x++) { $project_rlookup[$pids[$x]] = $x; } // $current_jdiv = $jdiv_id; printf("jdiv $jdiv_id, $n_projects projects in this jdiv\n"); unset($jteams_ids); $jteams_ids = array(); /* Pad to the correct length */ for ($x = 0; $x < ($n_timeslots * $n_projects); $x++) $jteams_ids[] = 0; printf('total of ' . count($jteams_ids) . " slots (should be $n_timeslots * $n_projects)\n"); /* * Fill out the jteam array with a jteam_id for each time the * jteam_id is supposed to judge a project */ $jteams = $jdiv[$jdiv_id]['jteams']; foreach ($jteams as $jteam_id) { for ($y = 0; $y < count($jteam[$jteam_id]['projects']); $y++) { $pid = $jteam[$jteam_id]['projects'][$y]; $idx = $project_rlookup[$pid]; for ($o = $idx;; $o += $n_projects) { if ($jteams_ids[$o] != 0) continue; $jteams_ids[$o] = $jteam_id; break; } } } /* * $y = 0; * foreach($jteams as $jteam_id) { * $o = 0; * print("setting up jteam $jteam_id\n"); * print_r($jteam[$jteam_id]); * foreach($jteam[$jteam_id]['projects'] as $pid) { * $jteams_ids[$y * $n_timeslots + $o] = $jteam_id; * $o++; * } * $y++; * } * printf("jteams_ids=\n"); * print_r($jteams_ids); */ print ('Jteams ids len=' . count($jteams_ids)); print ("n_timeslots=$n_timeslots\n"); set_percent(50 + ($k / $keys_count) * 50); $e = 500 + 50 * ($config['effort'] / 1000); $a = new annealer($n_timeslots, 100, $e, 0.98, 'timeslot_cost_function', $jteams_ids); $a->set_pick_move('timeslot_pick_move'); $a->anneal(); printf(' '); for ($y = 0; $y < $n_timeslots; $y++) { printf('%4d ', $y + 1); } printf("\n"); for ($x = 0; $x < count($pids); $x++) { $pid = $pids[$x]; printf('Project %4d: ', $pid); for ($y = 0; $y < $n_timeslots; $y++) { $jteam_id = $a->bucket[$y][$x]; printf('%4d ', $jteam[$jteam_id]['id']); if ($jteam_id == 0) continue; /* if jteam_id isn't 0, instert it into the db */ $stmt = $pdo->prepare('INSERT INTO judges_teams_timeslots_link (judges_teams_id,judges_timeslots_id,year) VALUES (?,?,?)'); $stmt->execute([$jteam[$jteam_id]['team_id'],$available_timeslots[$y]['id'],$config['FAIRYEAR']]); $stmt = $pdo->prepare('INSERT INTO judges_teams_timeslots_projects_link (judges_teams_id,judges_timeslots_id,projects_id,year) VALUES (?,?,?,?)'); $stmt->execute([$jteam[$jteam_id]['team_id'],$available_timeslots[$y]['id'],$pid,$config['FAIRYEAR']]); } printf("\n"); } } TRACE("All Done.\n"); echo '</pre>'; set_percent(-1); set_status('Done'); // echo happy("Scheduler completed successfully"); // send_footer(); ?>