forked from science-ation/science-ation
998fe35d67
number, and an option to create numbers automatically - Completely remove all references (hopefully) to the Tour ID - Add a Tour type report - Rework the student report query so it actually makes sense. It really should be a whole bunch of left joins based on the students. Using comma (cross) joins creates situations where if the right side of the join doesn't have information, it wipes out the left side. (Happened before with emergency contact info). - Remove the (two) old custom tour reports - Add 4 new tour reports to the database: - ALL tours for the committee, tour list for students, student emergency contact info for tour coordinators, and a list of tour assignments for students
246 lines
6.7 KiB
PHP
246 lines
6.7 KiB
PHP
<?
|
|
/*
|
|
This file is part of the 'Science Fair In A Box' project
|
|
SFIAB Website: http://www.sfiab.ca
|
|
|
|
Copyright (C) 2005 Sci-Tech Ontario Inc <info@scitechontario.org>
|
|
Copyright (C) 2005 James Grant <james@lightbox.org>
|
|
Copyright (C) 2008 Daivd Grant <dave@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("anneal.inc.php");
|
|
|
|
if($_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>");
|
|
|
|
|
|
function set_status($txt)
|
|
{
|
|
TRACE("Status: $txt\n");
|
|
mysql_query("UPDATE config SET val='$txt' WHERE
|
|
var='tours_assigner_activity' AND year=0");
|
|
}
|
|
|
|
$set_percent_last_percent = -1;
|
|
function set_percent($n)
|
|
{
|
|
global $set_percent_last_percent;
|
|
$p = floor($n);
|
|
if($p == $set_percent_last_percent) return;
|
|
TRACE("Progress: $p\%\n");
|
|
$set_percent_last_percent = $p;
|
|
mysql_query("UPDATE config SET val='$p' WHERE
|
|
var='tours_assigner_percent' AND year=0");
|
|
}
|
|
|
|
set_status("Initializing...");
|
|
set_percent(0);
|
|
|
|
/* The cost function is:
|
|
- Foreach student in a tour
|
|
+15 - Above the grade level
|
|
+25 - Below the grade level
|
|
+5 - Noone from the same school
|
|
If ranked (rank=1,2,3,4,...):
|
|
+(rank*rank*5 - 5) = +0, +15, +40, +75
|
|
If not ranked:
|
|
+(max_choices*max_choices*5) (always greater than ranked)
|
|
- Foreach tour
|
|
+100 for each student above the capacity
|
|
+200 for each student below 1/4 the capacity,but
|
|
zero if the tour is empty
|
|
*/
|
|
function tour_cost_function($annealer, $bucket_id, $ids)
|
|
{
|
|
global $config;
|
|
global $tid;
|
|
global $tours;
|
|
global $students;
|
|
/* Bucket ID is the tour number */
|
|
/* ids are the student ids currently in the bucket */
|
|
|
|
// TRACE("Bucket id=$bucket_id, ids=");
|
|
// TRACE_R($ids);
|
|
|
|
$cost = 0;
|
|
|
|
$t =& $tours[$bucket_id];
|
|
$tid = $t['id'];
|
|
|
|
/* Compute the over max / under min costs */
|
|
$c = count($ids);
|
|
$over = ($c > $t['capacity']) ? $c - $t['capacity'] : 0;
|
|
if($c > 0)
|
|
$under = ($c < ($t['capacity']/4)) ? ($t['capacity']/4) - $c : 0;
|
|
else
|
|
$under = 0;
|
|
|
|
$cost += $over * 100;
|
|
$cost += $under * 200;
|
|
|
|
// TRACE("Under min=$min, over max=$max\n");
|
|
|
|
$schools = array();
|
|
/* For each student on the tour */
|
|
foreach($ids as $x=>$sid) {
|
|
$s =& $students[$sid];
|
|
|
|
/* Score the rank */
|
|
$rank_cost = -1;
|
|
foreach($s['rank'] as $rank=>$rank_tid) {
|
|
if($rank_tid != $tid) continue;
|
|
$rank_cost = ($rank * $rank * 5) - 5;
|
|
}
|
|
if($rank_cost == -1) {
|
|
$rank_cost = $config['tours_choices_max'] * $config['tours_choices_max'] * 5;
|
|
}
|
|
$cost += $rank_cost;
|
|
|
|
/* Check for student below/above grade range */
|
|
if($s['grade'] < $t['grade_min']) $cost += 15;
|
|
if($s['grade'] > $t['grade_min']) $cost += 25;
|
|
|
|
/* Record the school */
|
|
$schools[$s['schools_id']]++;
|
|
}
|
|
|
|
/* Search the schools array for insteances of '1' */
|
|
foreach($schools as $sid=>$cnt) {
|
|
if($cnt == 1) $cost += 5;
|
|
}
|
|
|
|
// TRACE("Team $bucket_id, cost is $cost\n");
|
|
|
|
return $cost;
|
|
}
|
|
|
|
set_status("Cleaning existing tour assignments...");
|
|
TRACE("\n\n");
|
|
$q=mysql_query("DELETE FROM tours_choice
|
|
WHERE year='{$config['FAIRYEAR']}'
|
|
AND rank='0'");
|
|
|
|
set_status("Loading Data From Database...");
|
|
TRACE("\n\n");
|
|
TRACE("Tours...\n");
|
|
$tours = array();
|
|
$q=mysql_query("SELECT * FROM tours WHERE year='{$config['FAIRYEAR']}'");
|
|
$x=0;
|
|
/* Index with $x here, because these need to match up with the bucket ids of
|
|
* the annealer */
|
|
while($r=mysql_fetch_object($q)) {
|
|
$tours[$x]['capacity'] = $r->capacity;
|
|
$tours[$x]['grade_min'] = $r->grade_min;
|
|
$tours[$x]['grade_max'] = $r->grade_max;
|
|
$tours[$x]['id'] = $r->id;
|
|
$tours[$x]['name'] = $r->name;
|
|
TRACE(" ($x) ${$r->id}: #{$r->num} {$r->name} (c:{$r->capacity} g:{$r->grade_min}-{$r->grade_max})\n");
|
|
$x++;
|
|
}
|
|
|
|
$students = array();
|
|
TRACE("Loading Students...\n");
|
|
$q=mysql_query("SELECT students.id,students.grade,
|
|
students.registrations_id,
|
|
students.schools_id,
|
|
students.firstname, students.lastname,
|
|
tours_choice.rank,tours_choice.tour_id
|
|
FROM students
|
|
LEFT JOIN tours_choice ON (tours_choice.students_id=students.id)
|
|
LEFT JOIN registrations ON (registrations.id=students.registrations_id)
|
|
WHERE
|
|
students.year='{$config['FAIRYEAR']}'
|
|
AND tours_choice.year='{$config['FAIRYEAR']}'
|
|
AND registrations.status='complete'
|
|
ORDER BY
|
|
students.id, tours_choice.rank
|
|
");
|
|
$last_sid = -1;
|
|
TRACE(mysql_error());
|
|
while($r=mysql_fetch_object($q)) {
|
|
$sid = $r->id;
|
|
if($last_sid != $sid) {
|
|
$students[$sid]['name'] = $r->firstname.' '.$r->lastname;
|
|
$students[$sid]['grade'] = $r->grade;
|
|
$students[$sid]['registrations_id'] = $r->registrations_id;
|
|
$students[$sid]['rank'] = array();
|
|
$students[$sid]['schools_id'] = $r->schools_id;
|
|
$last_sid = $sid;
|
|
}
|
|
$students[$sid]['rank'][$r->rank] = $r->tour_id;
|
|
}
|
|
$student_ids = array_keys($students);
|
|
TRACE(" ".(count($student_ids))." students loaded\n");
|
|
|
|
function tours_assignment_update($progress, $total)
|
|
{
|
|
set_percent(($progress * 50) / $total);
|
|
}
|
|
|
|
TRACE("Effort: {$config['tours_assigner_effort']}\n");
|
|
set_status("Assigning students to tours");
|
|
$e = 100 + 10 * ($config['tours_assigner_effort'] / 100);
|
|
$a = new annealer(count($tours), 50, $e, 0.98,
|
|
tour_cost_function, $student_ids);
|
|
$a->set_update_callback(tours_assignment_update);
|
|
$a->anneal();
|
|
|
|
/* Record the assignments */
|
|
foreach($tours as $x=>$t) {
|
|
TRACE("($x) {$t['id']} #{$t['num']} {$t['name']} (c:{$t['capacity']} g:{$t['grade_min']}-{$t['grade_max']})\n");
|
|
|
|
$sids = $a->bucket[$x];
|
|
|
|
TRACE(" - Cost:{$a->bucket_cost[$x]} Students: ".(count($sids))."\n");
|
|
foreach($sids as $sid) {
|
|
$s = $students[$sid];
|
|
$tids = implode(' ', $s['rank']);
|
|
TRACE(" - {$s['name']} ($tids) (g:{$s['grade']} s:{$s['schools_id']})\n");
|
|
mysql_query("INSERT INTO tours_choice
|
|
(`students_id`,`registrations_id`,
|
|
`tour_id`,`year`,`rank`)
|
|
VALUES (
|
|
'$sid', '{$s['registrations_id']}',
|
|
'{$t['id']}', '{$config['FAIRYEAR']}',
|
|
'0')");
|
|
}
|
|
}
|
|
|
|
TRACE("All Done.\n");
|
|
echo "</pre>";
|
|
|
|
set_percent(-1);
|
|
set_status("Done");
|
|
|
|
//echo happy("Scheduler completed successfully");
|
|
|
|
//send_footer();
|
|
?>
|