Refactor schedule solver to solver per weekday and add charts and tables to template #409
This commit is contained in:
parent
d94e2353b0
commit
a2fdb346b3
3 changed files with 337 additions and 120 deletions
|
|
@ -24,6 +24,53 @@ use App\Entity\Hub;
|
||||||
|
|
||||||
class AnalyticsController extends Controller
|
class AnalyticsController extends Controller
|
||||||
{
|
{
|
||||||
|
protected $weekdays = [
|
||||||
|
'Monday',
|
||||||
|
'Tuesday',
|
||||||
|
'Wednesday',
|
||||||
|
'Thursday',
|
||||||
|
'Friday',
|
||||||
|
'Saturday',
|
||||||
|
'Sunday',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $day_shifts = [
|
||||||
|
['Mon - Sat', 0, 1, 2, 3, 4, 5], // Mon - Sat
|
||||||
|
['Tue - Sun', 1, 2, 3, 4, 5, 6], // Tue - Sun
|
||||||
|
['Wed - Mon', 2, 3, 4, 5, 6, 0], // Wed - Mon
|
||||||
|
['Thu - Tue', 3, 4, 5, 6, 0, 1], // Thu - Tue
|
||||||
|
['Fri - Wed', 4, 5, 6, 0, 1, 2], // Fri - Wed
|
||||||
|
['Sat - Thu', 5, 6, 0, 1, 2, 3], // Sat - Thu
|
||||||
|
['Sun - Fri', 6, 0, 1, 2, 3, 4], // Sun - Fri
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $hour_shifts = [
|
||||||
|
['00:00 - 09:00', 0, 1, 2, 3, 4, 5, 6, 7, 8],
|
||||||
|
['01:00 - 10:00', 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||||
|
['02:00 - 11:00', 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||||
|
['03:00 - 12:00', 3, 4, 5, 6, 7, 8, 9, 10, 11],
|
||||||
|
['04:00 - 13:00', 4, 5, 6, 7, 8, 9, 10, 11, 12],
|
||||||
|
['05:00 - 14:00', 5, 6, 7, 8, 9, 10, 11, 12, 13],
|
||||||
|
['06:00 - 15:00', 6, 7, 8, 9, 10, 11, 12, 13, 14],
|
||||||
|
['07:00 - 16:00', 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
||||||
|
['08:00 - 17:00', 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||||
|
['09:00 - 18:00', 9, 10, 11, 12, 13, 14, 15, 16, 17],
|
||||||
|
['10:00 - 19:00', 10, 11, 12, 13, 14, 15, 16, 17, 18],
|
||||||
|
['11:00 - 20:00', 11, 12, 13, 14, 15, 16, 17, 18, 19],
|
||||||
|
['12:00 - 21:00', 12, 13, 14, 15, 16, 17, 18, 19, 20],
|
||||||
|
['13:00 - 22:00', 13, 14, 15, 16, 17, 18, 19, 20, 21],
|
||||||
|
['14:00 - 23:00', 14, 15, 16, 17, 18, 19, 20, 21, 22],
|
||||||
|
['15:00 - 00:00', 15, 16, 17, 18, 19, 20, 21, 22, 23],
|
||||||
|
['16:00 - 01:00', 16, 17, 18, 19, 20, 21, 22, 23, 0],
|
||||||
|
['17:00 - 02:00', 17, 18, 19, 20, 21, 22, 23, 0, 1],
|
||||||
|
['18:00 - 03:00', 18, 19, 20, 21, 22, 23, 0, 1, 2],
|
||||||
|
['19:00 - 04:00', 19, 20, 21, 22, 23, 0, 1, 2, 3],
|
||||||
|
['20:00 - 05:00', 20, 21, 22, 23, 0, 1, 2, 3, 4],
|
||||||
|
['21:00 - 06:00', 21, 22, 23, 0, 1, 2, 3, 4, 5],
|
||||||
|
['22:00 - 07:00', 22, 23, 0, 1, 2, 3, 4, 5, 6],
|
||||||
|
['23:00 - 08:00', 23, 0, 1, 2, 3, 4, 5, 6, 7],
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Menu(selected="analytics_forecast")
|
* @Menu(selected="analytics_forecast")
|
||||||
* @IsGranted("analytics.forecast")
|
* @IsGranted("analytics.forecast")
|
||||||
|
|
@ -58,7 +105,8 @@ class AnalyticsController extends Controller
|
||||||
$hub_list = $req->request->get('hub_ids', []);
|
$hub_list = $req->request->get('hub_ids', []);
|
||||||
$distances = $req->request->get('distances', []);
|
$distances = $req->request->get('distances', []);
|
||||||
$today = DateTime::createFromFormat('d M Y', $req->request->get('date'));
|
$today = DateTime::createFromFormat('d M Y', $req->request->get('date'));
|
||||||
error_log(print_r($hub_list, true));
|
$month = $today->format('m');
|
||||||
|
// error_log(print_r($hub_list, true));
|
||||||
|
|
||||||
// $hub_list = [ 6, 4, 36, 7, 8, 126, 127, 18, 12, 9, 60, 10, 21, 135 ];
|
// $hub_list = [ 6, 4, 36, 7, 8, 126, 127, 18, 12, 9, 60, 10, 21, 135 ];
|
||||||
|
|
||||||
|
|
@ -88,17 +136,29 @@ class AnalyticsController extends Controller
|
||||||
$chart_all_weekdays = $this->generateAllWeekData($c_weekday, $today, $overlaps);
|
$chart_all_weekdays = $this->generateAllWeekData($c_weekday, $today, $overlaps);
|
||||||
|
|
||||||
// figure out the rider schedules based on the max hour values
|
// figure out the rider schedules based on the max hour values
|
||||||
$hour_max_values = $chart_all_weekdays['hour_max_values'];
|
$scheduler_data = $chart_all_weekdays['scheduler_data'];
|
||||||
ksort($hour_max_values);
|
// error_log(print_r($scheduler_data, true));
|
||||||
error_log(print_r($hour_max_values, true));
|
unset($chart_all_weekdays['scheduler_data']);
|
||||||
unset($chart_all_weekdays['hour_max_values']);
|
|
||||||
|
|
||||||
// run scheduler
|
// run scheduler
|
||||||
$shift = $this->runScheduler($hour_max_values);
|
// send 2018 + month data
|
||||||
|
$sched_res = $this->runScheduler($scheduler_data['2018'][$month]);
|
||||||
|
|
||||||
|
// tally total JOs for the month
|
||||||
|
$total_jos = 0;
|
||||||
|
foreach ($scheduler_data['2018'][$month] as $sday_data)
|
||||||
|
{
|
||||||
|
foreach ($sday_data as $shour_data)
|
||||||
|
{
|
||||||
|
$total_jos += $shour_data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$hub_data[$hub_id]['data_weekday'] = $chart_weekday;
|
$hub_data[$hub_id]['data_weekday'] = $chart_weekday;
|
||||||
$hub_data[$hub_id]['data_all_weekdays'] = $chart_all_weekdays;
|
$hub_data[$hub_id]['data_all_weekdays'] = $chart_all_weekdays;
|
||||||
$hub_data[$hub_id]['data_shift'] = $shift;
|
$hub_data[$hub_id]['data_shift'] = $sched_res['weekday_shifts'];
|
||||||
|
$hub_data[$hub_id]['total_jos'] = $total_jos;
|
||||||
|
$hub_data[$hub_id]['total_riders'] = $sched_res['total_riders'];
|
||||||
|
|
||||||
unset($hub_data[$hub_id]['c_weekday']);
|
unset($hub_data[$hub_id]['c_weekday']);
|
||||||
}
|
}
|
||||||
|
|
@ -118,18 +178,26 @@ class AnalyticsController extends Controller
|
||||||
return $this->render('analytics/forecast_submit.html.twig', $params);
|
return $this->render('analytics/forecast_submit.html.twig', $params);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function runScheduler($hour_data)
|
protected function runScheduler($scheduler_data)
|
||||||
{
|
{
|
||||||
// run python script to solve scheduling for riders
|
// run python script to solve scheduling for riders
|
||||||
|
|
||||||
$arg_string = implode('-', $hour_data);
|
|
||||||
|
|
||||||
$python_cmd = '/usr/bin/python3';
|
$python_cmd = '/usr/bin/python3';
|
||||||
$sched_script = __DIR__ . '/../../utils/schedule_solver/solver.py';
|
$sched_script = __DIR__ . '/../../utils/schedule_solver/solver.py';
|
||||||
|
|
||||||
error_log('running...' . $sched_script);
|
// go through the days
|
||||||
|
$args = [
|
||||||
|
$python_cmd,
|
||||||
|
$sched_script,
|
||||||
|
];
|
||||||
|
foreach ($scheduler_data as $weekday_data)
|
||||||
|
$args[] = implode('-', $weekday_data);
|
||||||
|
|
||||||
$proc = new Process([$python_cmd, $sched_script, $arg_string]);
|
error_log(print_r($args, true));
|
||||||
|
|
||||||
|
// error_log('running...' . $sched_script);
|
||||||
|
|
||||||
|
$proc = new Process($args);
|
||||||
$proc->run();
|
$proc->run();
|
||||||
|
|
||||||
if (!$proc->isSuccessful())
|
if (!$proc->isSuccessful())
|
||||||
|
|
@ -139,28 +207,74 @@ class AnalyticsController extends Controller
|
||||||
error_log($res);
|
error_log($res);
|
||||||
|
|
||||||
|
|
||||||
$shifts = [];
|
// segregate into weekdays
|
||||||
$res_lines = explode("\n", $res);
|
$day_data = [];
|
||||||
foreach ($res_lines as $line)
|
$i = 0;
|
||||||
|
foreach ($scheduler_data as $weekday_data)
|
||||||
{
|
{
|
||||||
$hour_data = explode('-', $line);
|
$total_jos = 0;
|
||||||
if (count($hour_data) != 2)
|
foreach ($weekday_data as $hourly_jo)
|
||||||
continue;
|
$total_jos += $hourly_jo;
|
||||||
|
|
||||||
$start_hour = $hour_data[0];
|
$day_data[$i] = [
|
||||||
$rider_count = $hour_data[1];
|
'weekday' => $this->weekdays[$i],
|
||||||
|
'total_jos' => $total_jos,
|
||||||
$display_shift = sprintf('%02d:00', $start_hour);
|
'total_riders' => 0,
|
||||||
|
'shifts' => [],
|
||||||
error_log('allocating ' . $rider_count . ' for ' . $display_shift);
|
|
||||||
|
|
||||||
$shifts[] = [
|
|
||||||
'label' => $display_shift,
|
|
||||||
'count' => $rider_count,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $shifts;
|
$shifts = [];
|
||||||
|
$res_lines = explode("\n", $res);
|
||||||
|
$total_riders = 0;
|
||||||
|
foreach ($res_lines as $line)
|
||||||
|
{
|
||||||
|
// format is day shift - hour shift - number of riders
|
||||||
|
$shift_data = explode('-', $line);
|
||||||
|
if (count($shift_data) != 3)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$day_shift_index = $shift_data[0];
|
||||||
|
$hour_shift_index = $shift_data[1];
|
||||||
|
$rider_count = $shift_data[2];
|
||||||
|
|
||||||
|
$total_riders += $rider_count;
|
||||||
|
|
||||||
|
$label = $this->day_shifts[$day_shift_index][0] . ' ' . $this->hour_shifts[$hour_shift_index][0];
|
||||||
|
|
||||||
|
// initialize hours
|
||||||
|
$rider_hours = [];
|
||||||
|
for ($i = 0; $i < 24; $i++)
|
||||||
|
$rider_hours[$i] = 0;
|
||||||
|
for ($i = 1; $i < count($this->hour_shifts[$hour_shift_index]); $i++)
|
||||||
|
$rider_hours[$this->hour_shifts[$hour_shift_index][$i]] = 1;
|
||||||
|
|
||||||
|
error_log('allocating ' . $rider_count . ' for ' . $label);
|
||||||
|
|
||||||
|
// add shifts to the weekday
|
||||||
|
for ($i = 1; $i < count($this->day_shifts[$day_shift_index]); $i++)
|
||||||
|
{
|
||||||
|
$day = $this->day_shifts[$day_shift_index][$i];
|
||||||
|
|
||||||
|
$day_data[$day]['shifts'][] = [
|
||||||
|
'label' => $label,
|
||||||
|
'count' => $rider_count,
|
||||||
|
'hours' => $rider_hours,
|
||||||
|
];
|
||||||
|
|
||||||
|
$day_data[$day]['total_riders'] += $rider_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'weekday_shifts' => $day_data,
|
||||||
|
'total_riders' => $total_riders,
|
||||||
|
];
|
||||||
|
|
||||||
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function generateHubData($em, $hub, $distance_limit, DateTime $today, &$overlaps)
|
protected function generateHubData($em, $hub, $distance_limit, DateTime $today, &$overlaps)
|
||||||
|
|
@ -305,21 +419,8 @@ class AnalyticsController extends Controller
|
||||||
|
|
||||||
protected function generateAllWeekData($all_weekday_data, $today, $overlaps)
|
protected function generateAllWeekData($all_weekday_data, $today, $overlaps)
|
||||||
{
|
{
|
||||||
// generate all weekdays, not just one weekday
|
|
||||||
|
|
||||||
$data = [];
|
$data = [];
|
||||||
|
|
||||||
// build wekdays
|
|
||||||
$weekdays = [
|
|
||||||
'Monday',
|
|
||||||
'Tuesday',
|
|
||||||
'Wednesday',
|
|
||||||
'Thursday',
|
|
||||||
'Friday',
|
|
||||||
'Saturday',
|
|
||||||
'Sunday',
|
|
||||||
];
|
|
||||||
|
|
||||||
// build hours
|
// build hours
|
||||||
$hours = [];
|
$hours = [];
|
||||||
for ($i = 0; $i < 24; $i++)
|
for ($i = 0; $i < 24; $i++)
|
||||||
|
|
@ -327,12 +428,10 @@ class AnalyticsController extends Controller
|
||||||
|
|
||||||
$month = $today->format('m');
|
$month = $today->format('m');
|
||||||
$year_data = [];
|
$year_data = [];
|
||||||
|
$scheduler_data = [];
|
||||||
|
|
||||||
// gather maximum for each hour
|
// gather maximum for each hour
|
||||||
// TODO: make it for all years, for now we only track 2018
|
foreach ($this->weekdays as $weekday)
|
||||||
$hour_max_values = [];
|
|
||||||
|
|
||||||
foreach ($weekdays as $weekday)
|
|
||||||
{
|
{
|
||||||
foreach ($all_weekday_data as $year => $year_data)
|
foreach ($all_weekday_data as $year => $year_data)
|
||||||
{
|
{
|
||||||
|
|
@ -346,11 +445,10 @@ class AnalyticsController extends Controller
|
||||||
];
|
];
|
||||||
|
|
||||||
// get hour data
|
// get hour data
|
||||||
|
$year_id = 'y' . $year;
|
||||||
|
$prefix = $year_id . '_' . $weekday;
|
||||||
if (isset($year_data[$month][$weekday][$hour]))
|
if (isset($year_data[$month][$weekday][$hour]))
|
||||||
{
|
{
|
||||||
$year_id = 'y' . $year;
|
|
||||||
$prefix = $year_id . '_' . $weekday;
|
|
||||||
|
|
||||||
// calculate the rider value for each JO and use that score as basis
|
// calculate the rider value for each JO and use that score as basis
|
||||||
$total_rv = $this->calculateTotalRiderValue($year_data[$month][$weekday][$hour]['jos'], $overlaps);
|
$total_rv = $this->calculateTotalRiderValue($year_data[$month][$weekday][$hour]['jos'], $overlaps);
|
||||||
$rv_average = ceil($total_rv / $year_data[$month][$weekday][$hour]['count']);
|
$rv_average = ceil($total_rv / $year_data[$month][$weekday][$hour]['count']);
|
||||||
|
|
@ -360,19 +458,24 @@ class AnalyticsController extends Controller
|
||||||
$data[$id][$prefix . '_average'] = ceil($year_data[$month][$weekday][$hour]['total'] / $year_data[$month][$weekday][$hour]['count']);
|
$data[$id][$prefix . '_average'] = ceil($year_data[$month][$weekday][$hour]['total'] / $year_data[$month][$weekday][$hour]['count']);
|
||||||
$data[$id][$prefix . '_rv_average'] = $rv_average;
|
$data[$id][$prefix . '_rv_average'] = $rv_average;
|
||||||
|
|
||||||
|
// assign scheduler data
|
||||||
// set maximum hour data
|
$scheduler_data[$year][$month][$weekday][$hour] = $rv_average;
|
||||||
if (!isset($hour_max_values[$hour]) || $rv_average > $hour_max_values[$hour])
|
}
|
||||||
$hour_max_values[$hour] = $rv_average;
|
else
|
||||||
|
{
|
||||||
|
if (!isset($scheduler_data[$year][$month][$weekday][$hour]))
|
||||||
|
{
|
||||||
|
$data[$id][$prefix . '_rv_average'] = 0;
|
||||||
|
$scheduler_data[$year][$month][$weekday][$hour] = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$data['scheduler_data'] = $scheduler_data;
|
||||||
|
|
||||||
// error_log(print_r($data, true));
|
// error_log(print_r($data, true));
|
||||||
|
|
||||||
$data['hour_max_values'] = $hour_max_values;
|
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,26 @@
|
||||||
|
|
||||||
{% block stylesheets %}
|
{% block stylesheets %}
|
||||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin=""/>
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin=""/>
|
||||||
|
<style>
|
||||||
|
.sched_col {
|
||||||
|
width: 3.5%;
|
||||||
|
}
|
||||||
|
.marked {
|
||||||
|
background-color: #ff0000;
|
||||||
|
}
|
||||||
|
.shift-table {
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-left: 20px;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
.shift-table th {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.shift-table td {
|
||||||
|
padding: 5px 10px 5px 10px;
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
@ -45,24 +65,83 @@
|
||||||
<div id="month-all-weekday-chart-{{ hub.id }}" style="height: 400px;">
|
<div id="month-all-weekday-chart-{{ hub.id }}" style="height: 400px;">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% for day_data in hub.data_shift %}
|
||||||
<div id="shift-table-{{ hub.id }}">
|
<div id="shift-table-{{ hub.id }}">
|
||||||
<table>
|
<table class="shift-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Shift</th>
|
<th>{{ day_data.weekday }}</th>
|
||||||
<th>Riders</th>
|
<th>00</th>
|
||||||
|
<th>01</th>
|
||||||
|
<th>02</th>
|
||||||
|
<th>03</th>
|
||||||
|
<th>04</th>
|
||||||
|
<th>05</th>
|
||||||
|
<th>06</th>
|
||||||
|
<th>07</th>
|
||||||
|
<th>08</th>
|
||||||
|
<th>09</th>
|
||||||
|
<th>10</th>
|
||||||
|
<th>11</th>
|
||||||
|
<th>12</th>
|
||||||
|
<th>13</th>
|
||||||
|
<th>14</th>
|
||||||
|
<th>15</th>
|
||||||
|
<th>16</th>
|
||||||
|
<th>17</th>
|
||||||
|
<th>18</th>
|
||||||
|
<th>19</th>
|
||||||
|
<th>20</th>
|
||||||
|
<th>21</th>
|
||||||
|
<th>22</th>
|
||||||
|
<th>23</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for shift_data in hub.data_shift %}
|
{% for shift_data in day_data.shifts %}
|
||||||
|
{% for i in 1..shift_data.count %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ shift_data.label }}</td>
|
<td style="width: 250px;">{{ shift_data.label }}</td>
|
||||||
<td>{{ shift_data.count }}</td>
|
{% for hour_coverage in shift_data.hours %}
|
||||||
|
{% if hour_coverage %}
|
||||||
|
<td class="sched_col marked"></td>
|
||||||
|
{% else %}
|
||||||
|
<td class="sched_col"></td>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<table class="shift-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 250px;">Day</th>
|
||||||
|
<th style="width: 100px;"># JO</th>
|
||||||
|
<th style="width: 100px;"># Rider</th>
|
||||||
|
<th style="width: 100px;">JO per Rider</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for i in 0..6 %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ hub.data_shift[i].weekday }}</td>
|
||||||
|
<td>{{ hub.data_shift[i].total_jos }}</td>
|
||||||
|
<td>{{ hub.data_shift[i].total_riders }}</td>
|
||||||
|
<td>{{ (hub.data_shift[i].total_jos / hub.data_shift[i].total_riders) | round(1, 'common') }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
<tr>
|
||||||
|
<td>Overall</td>
|
||||||
|
<td>{{ hub.total_jos }}</td>
|
||||||
|
<td>{{ hub.total_riders }}</td>
|
||||||
|
<td>{{ (hub.total_jos / hub.total_riders) | round(1, 'common') }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -144,6 +223,7 @@ s2020.dataFields.categoryX = "date";
|
||||||
|
|
||||||
var chart2 = am4core.create("month-weekday-chart-{{ hub.id }}", am4charts.XYChart);
|
var chart2 = am4core.create("month-weekday-chart-{{ hub.id }}", am4charts.XYChart);
|
||||||
chart2.data = {{ hub.data_weekday|json_encode|raw }};
|
chart2.data = {{ hub.data_weekday|json_encode|raw }};
|
||||||
|
chart2.legend = new am4charts.Legend();
|
||||||
|
|
||||||
var xAxis = chart2.xAxes.push(new am4charts.CategoryAxis());
|
var xAxis = chart2.xAxes.push(new am4charts.CategoryAxis());
|
||||||
xAxis.dataFields.category = "hour";
|
xAxis.dataFields.category = "hour";
|
||||||
|
|
@ -170,6 +250,7 @@ l2019.dataFields.categoryX = "hour";
|
||||||
|
|
||||||
var chart2 = am4core.create("month-weekday-chart-rv-{{ hub.id }}", am4charts.XYChart);
|
var chart2 = am4core.create("month-weekday-chart-rv-{{ hub.id }}", am4charts.XYChart);
|
||||||
chart2.data = {{ hub.data_weekday|json_encode|raw }};
|
chart2.data = {{ hub.data_weekday|json_encode|raw }};
|
||||||
|
chart2.legend = new am4charts.Legend();
|
||||||
|
|
||||||
var xAxis = chart2.xAxes.push(new am4charts.CategoryAxis());
|
var xAxis = chart2.xAxes.push(new am4charts.CategoryAxis());
|
||||||
xAxis.dataFields.category = "hour";
|
xAxis.dataFields.category = "hour";
|
||||||
|
|
|
||||||
|
|
@ -3,44 +3,50 @@ from ortools.linear_solver import pywraplp
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# get arguments
|
|
||||||
hours_string = sys.argv[1]
|
|
||||||
hours_data = hours_string.split('-')
|
|
||||||
|
|
||||||
# initialize hours
|
# days
|
||||||
|
days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
||||||
|
|
||||||
|
# hours
|
||||||
hours = [
|
hours = [
|
||||||
['00', 0],
|
'00',
|
||||||
['01', 0],
|
'01',
|
||||||
['02', 0],
|
'02',
|
||||||
['03', 0],
|
'03',
|
||||||
['04', 0],
|
'04',
|
||||||
['05', 0],
|
'05',
|
||||||
['06', 0],
|
'06',
|
||||||
['07', 0],
|
'07',
|
||||||
['08', 0],
|
'08',
|
||||||
['09', 0],
|
'09',
|
||||||
['10', 0],
|
'10',
|
||||||
['11', 0],
|
'11',
|
||||||
['12', 0],
|
'12',
|
||||||
['13', 0],
|
'13',
|
||||||
['14', 0],
|
'14',
|
||||||
['15', 0],
|
'15',
|
||||||
['16', 0],
|
'16',
|
||||||
['17', 0],
|
'17',
|
||||||
['18', 0],
|
'18',
|
||||||
['19', 0],
|
'19',
|
||||||
['20', 0],
|
'20',
|
||||||
['21', 0],
|
'21',
|
||||||
['22', 0],
|
'22',
|
||||||
['23', 0]]
|
'23']
|
||||||
|
|
||||||
# set hours from argument
|
# initialize required hours - req_hours[days][hours]
|
||||||
for i in range(0, len(hours_data)):
|
req_hours = [[0 for x in range(len(hours))] for y in range(len(days))]
|
||||||
hours[i][1] = int(hours_data[i])
|
|
||||||
|
|
||||||
|
# get arguments
|
||||||
|
# there will be 7 arguments, monday to sunday schedule
|
||||||
|
for day_index in range(0, len(days)):
|
||||||
|
hours_string = sys.argv[day_index + 1]
|
||||||
|
hours_data = hours_string.split('-')
|
||||||
|
for hour_index in range(0, len(hours)):
|
||||||
|
req_hours[day_index][hour_index] = int(hours_data[hour_index])
|
||||||
|
|
||||||
# all shifts available
|
# all hour shifts available
|
||||||
shifts = [
|
hour_shifts = [
|
||||||
['00 - 09', 0, 1, 2, 3, 4, 5, 6, 7, 8],
|
['00 - 09', 0, 1, 2, 3, 4, 5, 6, 7, 8],
|
||||||
['01 - 10', 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
['01 - 10', 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||||
['02 - 11', 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
['02 - 11', 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||||
|
|
@ -66,48 +72,75 @@ def main():
|
||||||
['22 - 07', 22, 23, 0, 1, 2, 3, 4, 5, 6],
|
['22 - 07', 22, 23, 0, 1, 2, 3, 4, 5, 6],
|
||||||
['23 - 08', 23, 0, 1, 2, 3, 4, 5, 6, 7]]
|
['23 - 08', 23, 0, 1, 2, 3, 4, 5, 6, 7]]
|
||||||
|
|
||||||
|
# all possible days riders come in
|
||||||
|
day_shifts = [
|
||||||
|
['Mon - Sat', 0, 1, 2, 3, 4, 5], # Mon - Sat
|
||||||
|
['Tue - Sun', 1, 2, 3, 4, 5, 6], # Tue - Sun
|
||||||
|
['Wed - Mon', 2, 3, 4, 5, 6, 0], # Wed - Mon
|
||||||
|
['Thu - Tue', 3, 4, 5, 6, 0, 1], # Thu - Tue
|
||||||
|
['Fri - Wed', 4, 5, 6, 0, 1, 2], # Fri - Wed
|
||||||
|
['Sat - Thu', 5, 6, 0, 1, 2, 3], # Sat - Thu
|
||||||
|
['Sun - Fri', 6, 0, 1, 2, 3, 4]] # Sun - Fri
|
||||||
|
|
||||||
|
# build shift lookup index
|
||||||
|
|
||||||
|
|
||||||
# instantiate glop solver
|
# instantiate glop solver
|
||||||
solver = pywraplp.Solver('SolveSchedule', pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)
|
solver = pywraplp.Solver('SolveSchedule', pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)
|
||||||
|
|
||||||
# solver variables
|
# solver variables
|
||||||
solv_shifts = [[]] * len(shifts)
|
solv_shifts = [[0 for x in range(len(hour_shifts))] for y in range(len(day_shifts))]
|
||||||
|
|
||||||
# objective
|
# objective
|
||||||
objective = solver.Objective()
|
objective = solver.Objective()
|
||||||
|
|
||||||
# variables for shifts
|
# variables for shifts
|
||||||
for i in range(0, len(shifts)):
|
for day_index in range(0, len(day_shifts)):
|
||||||
solv_shifts[i] = solver.IntVar(0, solver.infinity(), shifts[i][0])
|
for hour_index in range(0, len(hour_shifts)):
|
||||||
# objective is to minimize number of shifts
|
solv_shifts[day_index][hour_index] = solver.IntVar(0, solver.infinity(), day_shifts[day_index][0] + ' ' + hour_shifts[hour_index][0])
|
||||||
objective.SetCoefficient(solv_shifts[i], 1)
|
# objective is to minimize number of shifts
|
||||||
|
objective.SetCoefficient(solv_shifts[day_index][hour_index], 1)
|
||||||
objective.SetMinimization()
|
objective.SetMinimization()
|
||||||
|
|
||||||
# set constraints
|
# set constraints
|
||||||
constraints = [0] * len(hours)
|
constraints = [[0 for x in range(len(hours))] for y in range(len(days))]
|
||||||
for hour_index in range(0, len(hours)):
|
# go through all days
|
||||||
# hour personnel should be equal or less than shift personnel that covers those hours
|
for day_index in range(0, len(days)):
|
||||||
constraints[hour_index] = solver.Constraint(hours[hour_index][1], solver.infinity())
|
# go through all hours
|
||||||
for shift_index in range(0, len(shifts)):
|
for hour_index in range(0, len(hours)):
|
||||||
# get each shift's hour coverage
|
# hour personnel should be equal or less than shift personnel that covers those hours
|
||||||
for shift_hour_index in range(1, len(shifts[shift_index])):
|
# set the required manpower for that day + hour combo
|
||||||
# check if shift covers that hour
|
# print('setting constraint for', day_index, '-', hour_index, '=', req_hours[day_index][hour_index])
|
||||||
# NOTE: this can still be optimized later via indexing
|
constraints[day_index][hour_index] = solver.Constraint(req_hours[day_index][hour_index], solver.infinity())
|
||||||
if shifts[shift_index][shift_hour_index] == hour_index:
|
# go through all day shifts
|
||||||
# print('hour', hour_index, 'in shift index -', shift_index)
|
for shift_day in range(0, len(day_shifts)):
|
||||||
constraints[hour_index].SetCoefficient(solv_shifts[shift_index], 1)
|
# go through days inside day shift
|
||||||
|
for shift_day_index in range(1, len(day_shifts[shift_day])):
|
||||||
|
# is day shift part of that day?
|
||||||
|
if day_index == day_shifts[shift_day][shift_day_index]:
|
||||||
|
# go through all hour shifts
|
||||||
|
for shift_hour in range(0, len(hour_shifts)):
|
||||||
|
# go through all hours inside hour shift
|
||||||
|
for shift_hour_index in range(1, len(hour_shifts[shift_hour])):
|
||||||
|
# is hour shift part of the hour
|
||||||
|
if hour_index == hour_shifts[shift_hour][shift_hour_index]:
|
||||||
|
# print(day_index, ' - ', hour_index, ' vs ', day_shifts[shift_day][shift_day_index], ' - ', hour_shifts[shift_hour][shift_hour_index])
|
||||||
|
# add it to constraint
|
||||||
|
constraints[day_index][hour_index].SetCoefficient(solv_shifts[shift_day][shift_hour], 1)
|
||||||
|
|
||||||
# solve it!
|
# solve it!
|
||||||
status = solver.Solve()
|
status = solver.Solve()
|
||||||
|
#print('Number of variables =', solver.NumVariables())
|
||||||
|
#print('Number of constraints =', solver.NumConstraints())
|
||||||
|
|
||||||
if status == solver.OPTIMAL:
|
if status == solver.OPTIMAL:
|
||||||
#print('Optimal solution found!')
|
#print('Optimal solution found!')
|
||||||
#print('Number of variables =', solver.NumVariables())
|
for day_index in range(0, len(day_shifts)):
|
||||||
#print('Number of constraints =', solver.NumConstraints())
|
for hour_index in range(0, len(hour_shifts)):
|
||||||
for i in range(0, len(shifts)):
|
result = solv_shifts[day_index][hour_index].solution_value()
|
||||||
result = solv_shifts[i].solution_value()
|
if result > 0:
|
||||||
if result > 0:
|
print(day_index, hour_index, int(solv_shifts[day_index][hour_index].solution_value()), sep='-')
|
||||||
print(i, '-', int(solv_shifts[i].solution_value()), sep='')
|
#print(day_shifts[day_index][0], ' ', hour_shifts[hour_index][0], ' = ', int(solv_shifts[day_index][hour_index].solution_value()), sep='')
|
||||||
else:
|
else:
|
||||||
if status == solver.FEASIBLE:
|
if status == solver.FEASIBLE:
|
||||||
print('Feasible solution found!')
|
print('Feasible solution found!')
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue