- Add a special awards feature (off by default) to the judge scheduler. It:

- Creates a judging team for each special award
	- Assigns judges to special awards based on the number of students self
	  nominated.
	- Obeys the judges special award preferences (if enabled), and also
	  judges that specify if they are a judge for a specific special award
	  (if enabled).

- Add 2 new config variables.
	- Enable the special award scheduler
	- Specify the max. number of projects each special award judge can
	  handle (default: 20)

- Delete an extra blank line in register_participants_students.php
This commit is contained in:
dave 2007-03-28 06:16:41 +00:00
parent cc2e6b5bce
commit 1bdba54ed5
4 changed files with 252 additions and 14 deletions

View File

@ -24,6 +24,7 @@
<? <?
require("../common.inc.php"); require("../common.inc.php");
require("../questions.inc.php"); require("../questions.inc.php");
require("../projects.inc.php");
require("judges.inc.php"); require("judges.inc.php");
require("anneal.inc.php"); require("anneal.inc.php");
auth_required('admin'); auth_required('admin');
@ -78,13 +79,16 @@ function set_status($txt)
var='judge_scheduler_activity' AND year=0"); var='judge_scheduler_activity' AND year=0");
} }
$set_percent_last_percent = -1;
function set_percent($n) function set_percent($n)
{ {
global $set_percent_last_percent;
$p = floor($n); $p = floor($n);
if($p == $set_percent_last_percent) return;
TRACE("Progress: $p\%\n"); TRACE("Progress: $p\%\n");
mysql_query("UPDATE config SET val='$p' WHERE mysql_query("UPDATE config SET val='$p' WHERE
var='judge_scheduler_percent' AND year=0"); var='judge_scheduler_percent' AND year=0");
$set_percent_last_percent = $p;
} }
set_status("Initializing..."); set_status("Initializing...");
@ -376,7 +380,7 @@ $willing_chair_question_id = questions_find_question_id("judgereg", $config['FAI
printf("Judge Willing Chair = Question ID $willing_chair_question_id\n"); printf("Judge Willing Chair = Question ID $willing_chair_question_id\n");
/* Clean out the judging teams that were autocreated */ /* Clean out the judging teams that were autocreated */
TRACE("Deleting existing divisional judging teams:"); TRACE("Deleting autocreated divisional and special award judging teams:");
$q = mysql_query("SELECT * FROM judges_teams WHERE autocreate_type_id=1 AND year={$config['FAIRYEAR']}"); $q = mysql_query("SELECT * FROM judges_teams WHERE autocreate_type_id=1 AND year={$config['FAIRYEAR']}");
while($r = mysql_fetch_object($q)) { while($r = mysql_fetch_object($q)) {
$jteam_id = $r->id; $jteam_id = $r->id;
@ -394,7 +398,7 @@ while($r = mysql_fetch_object($q)) {
mysql_query("DELETE FROM judges_teams_timeslots_projects_link WHERE judges_teams_id='$jteam_id' AND year={$config['FAIRYEAR']}"); mysql_query("DELETE FROM judges_teams_timeslots_projects_link WHERE judges_teams_id='$jteam_id' AND year={$config['FAIRYEAR']}");
print mysql_error(); print mysql_error();
} }
/* Findally, delete all the autocreated judges teams */ /* Finally, delete all the autocreated judges teams */
mysql_query("DELETE FROM judges_teams WHERE autocreate_type_id=1 AND year={$config['FAIRYEAR']}"); mysql_query("DELETE FROM judges_teams WHERE autocreate_type_id=1 AND year={$config['FAIRYEAR']}");
print mysql_error(); print mysql_error();
TRACE(" Done.\n"); TRACE(" Done.\n");
@ -408,8 +412,7 @@ $q=mysql_query("SELECT judges.* FROM judges,judges_years WHERE ".
); );
$judges=array(); $judges=array();
$sa_judges = array();
while($r=mysql_fetch_object($q)) while($r=mysql_fetch_object($q))
{ {
@ -456,7 +459,36 @@ while($r=mysql_fetch_object($q))
if($r2->answer == 'yes') $willing_chair = 'yes'; if($r2->answer == 'yes') $willing_chair = 'yes';
} }
$judges[$r->id]=array( $sa_only = 'no';
if($r->typepref == 'speconly') $sa_only = 'yes';
$sa_sel = array();
if($sa_only == 'yes') {
TRACE("Judge [{$r->firstname} {$r->lastname}] is a special awards only.\n");
/* Find their special award id */
$qq = mysql_query("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.judges_id='{$r->id}'
AND judges_specialaward_sel.year='{$config['FAIRYEAR']}'
AND award_awards.year='{$config['FAIRYEAR']}'");
echo mysql_error();
if(mysql_num_rows($qq) == 0) {
TRACE(" - NO special award selected! (removing special award only request)\n");
$sa_only = 'no';
} else if(mysql_num_rows($qq) > 1) {
TRACE(" - More than ONE special award selected (removing special award only request):\n");
$sa_only = 'no';
}
while($rr = mysql_fetch_object($qq)) {
TRACE(" ".$rr->name."\n");
$sa_sel[] = $rr->id;
}
}
$j=array(
"judges_id"=>"$r->id", "judges_id"=>"$r->id",
"name"=>"$r->firstname $r->lastname", "name"=>"$r->firstname $r->lastname",
"years_school"=>$r->years_school, "years_school"=>$r->years_school,
@ -465,8 +497,20 @@ while($r=mysql_fetch_object($q))
"willing_chair"=>$willing_chair, "willing_chair"=>$willing_chair,
"divprefs"=>$divprefs, "divprefs"=>$divprefs,
"catprefs"=>$catprefs, "catprefs"=>$catprefs,
"languages"=>$langprefs "languages"=>$langprefs,
"sa_only"=>$sa_only,
"sa_sel"=>$sa_sel,
); );
/* If it's a special award only judge, keep them
* out of the judges list for the divisional annealer */
if($sa_only == 'yes') {
$sa_judges[$r->id] = $j;
} else {
$judges[$r->id] = $j;
}
} }
TRACE("Loaded ".count($judges)." judges.\n"); TRACE("Loaded ".count($judges)." judges.\n");
$jteam[0]['max_judges'] = count($judges); $jteam[0]['max_judges'] = count($judges);
@ -522,8 +566,8 @@ TRACE("Max Judging Team Number is currently $max_jteam_num\n");
for($x=1;$x<count($jteam); $x++) { for($x=1;$x<count($jteam); $x++) {
$t =& $jteam[$x]; $t =& $jteam[$x];
$num = $x + $max_jteam_num; $max_jteam_num++;
print("Judging Team $num: cost={$a->bucket_cost[$x]} "); print("Judging Team $max_jteam_num: cost={$a->bucket_cost[$x]} ");
print("langs=("); print("langs=(");
$langstr=""; $langstr="";
for($y=0; $y<count($t['langs']); $y++) { for($y=0; $y<count($t['langs']); $y++) {
@ -551,7 +595,7 @@ for($x=1;$x<count($jteam); $x++) {
/* Add this judging team to the database */ /* Add this judging team to the database */
$tn = $catstr." ".$divstr." (".$langstr.") ".($t['sub']+1); $tn = $catstr." ".$divstr." (".$langstr.") ".($t['sub']+1);
mysql_query("INSERT INTO judges_teams (num,name,autocreate_type_id,year) ". mysql_query("INSERT INTO judges_teams (num,name,autocreate_type_id,year) ".
" VALUES ('$num','$tn','1','{$config['FAIRYEAR']}')"); " VALUES ('$max_jteam_num','$tn','1','{$config['FAIRYEAR']}')");
$team_id=mysql_insert_id(); $team_id=mysql_insert_id();
$t['team_id'] = $team_id; $t['team_id'] = $team_id;
@ -598,8 +642,199 @@ for($x=1;$x<count($jteam); $x++) {
print("Unused Judges:\n"); print("Unused Judges:\n");
$ids = $a->bucket[0]; $ids = $a->bucket[0];
for($y=0; $y<count($ids); $y++) pr_judge($jteam[0], $ids[$y]); for($y=0; $y<count($ids); $y++) {
pr_judge($jteam[0], $ids[$y]);
$sa_judges[$ids[$y]] = $judges[$ids[$y]];
}
/* ====================================================================*/
/* Two functions for the Special Award Annealer, if special award
* scheduling is disabled, these will never get called */
function judges_sa_cost_function($annealer, $bucket_id, $ids)
{
global $sa_jteam;
global $sa_judges;
/* 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[$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 =& $sa_judges[$ids[$x]];
$apref = 0;
/* 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 */
foreach($t['award_ids'] as $aid) {
if(in_array($aid, $j['sa_sel'])) {
/* This judge wants to judge this award */
/* No cost */
} else {
if($j['sa_only'] == 'yes') {
/* 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;
}
function judges_sa_pick_move($a)
{
global $sa_judges;
/* Use the existing pick move, but we never want to
* select a judge that has sa_only=='yes', they are
* already in the correct place and are unmovable */
/* FIXME: this will spin forever if there aren't at least
* 2 judges to swap */
while(1) {
list($b1, $i1, $b2, $i2) = $a->pick_move();
/* See if $b1,$i1 is movable */
$id1 = $a->bucket[$b1][$i1];
$j1 =& $sa_judges[$id1];
// print("J1:");
// print_r($j1);
if($j1['sa_only'] == 'yes') continue;
if($i2 != -1) {
$id2 = $a->bucket[$b2][$i2];
$j2 =& $sa_judges[$id2];
// print("J2:");
// print_r($j2);
if($j2['sa_only'] == 'yes') continue;
}
return array($b1, $i1, $b2, $i2);
}
}
if($config['scheduler_enable_sa_scheduling'] == 'yes') {
set_status("Creating Special Award Judging Teams (one team per award)");
$q = "SELECT award_awards.name,award_awards.id FROM award_awards,award_types
WHERE
award_awards.year='{$config['FAIRYEAR']}'
AND award_types.id=award_awards.award_types_id
AND award_types.year='{$config['FAIRYEAR']}'
AND award_types.type='Special'
";
$r = mysql_query($q);
print(mysql_error());
/* 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;
while($i = mysql_fetch_object($r)) {
$projects = getProjectsNominatedForSpecialAward($i->id);
$max_jteam_num++; /* Pre-increment before using */
$pids = array_keys($projects);
$sa_jteam[$x]['id'] = $jteam_id;
$sa_jteam[$x]['projects'] = $pids;
$sa_jteam[$x]['sub'] = 0;
$sa_jteam[$x]['langs'] = array();
$min = floor(count($pids) / $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);
$tn = "{$i->name}";
/* Write this team to the DB */
mysql_query("INSERT INTO judges_teams (num,name,autocreate_type_id,year)
VALUES ('$max_jteam_num','$tn','1','{$config['FAIRYEAR']}')");
$sa_jteam[$x]['id'] = mysql_insert_id();
/* Link the award to this team */
mysql_query("INSERT INTO judges_teams_awards_link (award_awards_id,judges_teams_id,year)
VALUES ('{$i->id}','{$sa_jteam[$x]['id']}','{$config['FAIRYEAR']}')");
TRACE("Created Team: $tn\n");
$jteam_id++;
$x++;
}
/* ====================================================================*/
set_status("Assigning Judges to Special Award Teams\n");
$judge_ids = array_keys($sa_judges);
$e = $config['effort'];
$a = new annealer(count($sa_jteam), 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;
foreach($sa_jteam as $tid => $t) {
if($tid == 0) {
$x++;
continue;
}
print("Judging Team {$t['id']}: 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");
$ids = $a->bucket[$x];
for($y=0; $y<count($ids); $y++) {
// pr_judge($t, $ids[$y]);
$j =& $sa_judges[$ids[$y]];
print(" - {$j['name']}\n");
/* Link Judges to the judging team we just inserted */
mysql_query("INSERT INTO judges_teams_link
(judges_id,judges_teams_id,captain,year)
VALUES ('{$ids[$y]}','{$t['id']}',
'{$j['willing_chair']}',
'{$config['FAIRYEAR']}')");
}
$x++;
}
}
/* Resume normal flow now */
/* ====================================================================*/
set_status("Assigning Judging Teams and Projects to Timeslots"); set_status("Assigning Judging Teams and Projects to Timeslots");
TRACE("Loading Timeslot Data\n"); TRACE("Loading Timeslot Data\n");
@ -613,7 +848,7 @@ while($r=mysql_fetch_object($q)) {
"date"=>$r->date, "date"=>$r->date,
"starttime"=>substr($r->starttime,0,-3), "starttime"=>substr($r->starttime,0,-3),
"endtime"=>substr($r->endtime,0,-3)); "endtime"=>substr($r->endtime,0,-3));
print(" -".$available_timeslots[$x]['starttime']." -> ". print(" ".$available_timeslots[$x]['starttime']." -> ".
$available_timeslots[$x]['endtime']."\n"); $available_timeslots[$x]['endtime']."\n");
$x++; $x++;
} }

View File

@ -1 +1 @@
48 49

4
db/db.update.49.sql Normal file
View File

@ -0,0 +1,4 @@
INSERT INTO `config` ( `var` , `val` , `category` , `type` , `type_values` , `ord` , `description` , `year` ) VALUES ( 'scheduler_enable_sa_scheduling', 'no', 'Judge Scheduler', 'yesno', '', '900', 'Allow the scheduler to automatically create a judging team for each special award, and assigned unused divisional judges to special awards.', '-1');
INSERT INTO `config` ( `var` , `val` , `category` , `type` , `type_values` , `ord` , `description` , `year` ) VALUES ( 'projects_per_special_award_judge', '20', 'Judge Scheduler', 'number', '', '1000', 'The maximum number of projects that each special awards judge can judge.', '-1');

View File

@ -229,7 +229,6 @@ else if($newstatus=="complete")
else else
$numtoshow=$numfound; $numtoshow=$numfound;
echo "<form name=\"numstudentsform\" method=\"get\" action=\"register_participants_students.php\">"; echo "<form name=\"numstudentsform\" method=\"get\" action=\"register_participants_students.php\">";
echo i18n("Number of students that worked on the project: "); echo i18n("Number of students that worked on the project: ");
echo "<select name=\"numstudents\" onchange=\"document.forms.numstudentsform.submit()\">\n"; echo "<select name=\"numstudents\" onchange=\"document.forms.numstudentsform.submit()\">\n";