diff --git a/composer.json b/composer.json index 7219d070..d2f81e89 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,7 @@ "symfony/framework-bundle": "^4.0", "symfony/maker-bundle": "^1.0", "symfony/orm-pack": "^1.0", + "symfony/process": "^4.0", "symfony/profiler-pack": "^1.0", "symfony/security-bundle": "^4.0", "symfony/translation": "^4.0", diff --git a/composer.lock b/composer.lock index ca8a10a1..1e76e914 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "b101ecfbc1f6f2270f0e8ad326035b7e", + "content-hash": "f03b92d48946e8b2ee19466f931c826f", "packages": [ { "name": "catalyst/auth-bundle", @@ -4143,6 +4143,55 @@ ], "time": "2019-11-27T16:25:15+00:00" }, + { + "name": "symfony/process", + "version": "v4.4.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "c714958428a85c86ab97e3a0c96db4c4f381b7f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/c714958428a85c86ab97e3a0c96db4c4f381b7f5", + "reference": "c714958428a85c86ab97e3a0c96db4c4f381b7f5", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2020-05-30T20:06:45+00:00" + }, { "name": "symfony/profiler-pack", "version": "v1.0.4", diff --git a/src/Controller/AnalyticsController.php b/src/Controller/AnalyticsController.php index 140405cf..6c81bea9 100644 --- a/src/Controller/AnalyticsController.php +++ b/src/Controller/AnalyticsController.php @@ -8,6 +8,8 @@ use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Process\Process; +use Symfony\Component\Process\Exception\ProcessFailedException; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted; @@ -83,7 +85,20 @@ class AnalyticsController extends Controller { $c_weekday = $one_hub['c_weekday']; $chart_weekday = $this->generateWeekdayData($c_weekday, $today, $overlaps); + $chart_all_weekdays = $this->generateAllWeekData($c_weekday, $today, $overlaps); + + // figure out the rider schedules based on the max hour values + $hour_max_values = $chart_all_weekdays['hour_max_values']; + ksort($hour_max_values); + error_log(print_r($hour_max_values, true)); + unset($chart_all_weekdays['hour_max_values']); + + // run scheduler + $shift = $this->runScheduler($hour_max_values); + $hub_data[$hub_id]['data_weekday'] = $chart_weekday; + $hub_data[$hub_id]['data_all_weekdays'] = $chart_all_weekdays; + $hub_data[$hub_id]['data_shift'] = $shift; unset($hub_data[$hub_id]['c_weekday']); } @@ -103,6 +118,51 @@ class AnalyticsController extends Controller return $this->render('analytics/forecast_submit.html.twig', $params); } + protected function runScheduler($hour_data) + { + // run python script to solve scheduling for riders + + $arg_string = implode('-', $hour_data); + + $python_cmd = '/usr/bin/python3'; + $sched_script = __DIR__ . '/../../utils/schedule_solver/solver.py'; + + error_log('running...' . $sched_script); + + $proc = new Process([$python_cmd, $sched_script, $arg_string]); + $proc->run(); + + if (!$proc->isSuccessful()) + error_log('SCHEDULER DID NOT RUN PROPERLY'); + + $res = $proc->getOutput(); + error_log($res); + + + $shifts = []; + $res_lines = explode("\n", $res); + foreach ($res_lines as $line) + { + $hour_data = explode('-', $line); + if (count($hour_data) != 2) + continue; + + $start_hour = $hour_data[0]; + $rider_count = $hour_data[1]; + + $display_shift = sprintf('%02d:00', $start_hour); + + error_log('allocating ' . $rider_count . ' for ' . $display_shift); + + $shifts[] = [ + 'label' => $display_shift, + 'count' => $rider_count, + ]; + } + + return $shifts; + } + protected function generateHubData($em, $hub, $distance_limit, DateTime $today, &$overlaps) { $date_start = DateTime::createFromFormat('Y-m-d H:i:s', '2018-01-01 00:00:00'); @@ -243,6 +303,79 @@ class AnalyticsController extends Controller return $chart_year; } + protected function generateAllWeekData($all_weekday_data, $today, $overlaps) + { + // generate all weekdays, not just one weekday + + $data = []; + + // build wekdays + $weekdays = [ + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + 'Sunday', + ]; + + // build hours + $hours = []; + for ($i = 0; $i < 24; $i++) + $hours[] = sprintf('%02d', $i); + + $month = $today->format('m'); + $year_data = []; + + // gather maximum for each hour + // TODO: make it for all years, for now we only track 2018 + $hour_max_values = []; + + foreach ($weekdays as $weekday) + { + foreach ($all_weekday_data as $year => $year_data) + { + // go through the hours + foreach ($hours as $hour) + { + $id = $hour + 0; + if (!isset($data[$id])) + $data[$id] = [ + 'hour' => $hour, + ]; + + // get hour data + 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 + $total_rv = $this->calculateTotalRiderValue($year_data[$month][$weekday][$hour]['jos'], $overlaps); + $rv_average = ceil($total_rv / $year_data[$month][$weekday][$hour]['count']); + + $data[$id][$prefix] = $year_data[$month][$weekday][$hour]['total']; + $data[$id][$prefix . '_count'] = $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; + + + // set maximum hour data + if (!isset($hour_max_values[$hour]) || $rv_average > $hour_max_values[$hour]) + $hour_max_values[$hour] = $rv_average; + } + } + } + } + + // error_log(print_r($data, true)); + + $data['hour_max_values'] = $hour_max_values; + + return $data; + } + protected function generateWeekdayData($all_weekday_data, $today, $overlaps) { $data = []; @@ -329,8 +462,16 @@ class AnalyticsController extends Controller $jos = $stmt->fetchAll(); // error_log($sql); - error_log(print_r($jos, true)); + // error_log(print_r($jos, true)); return $jos; } + + protected function generateRiderSchedule() + { + } + + protected function solveRiderSchedule() + { + } } diff --git a/symfony.lock b/symfony.lock index 116ed675..900434cc 100644 --- a/symfony.lock +++ b/symfony.lock @@ -260,6 +260,9 @@ "symfony/polyfill-php73": { "version": "v1.11.0" }, + "symfony/process": { + "version": "v4.4.9" + }, "symfony/profiler-pack": { "version": "v1.0.3" }, diff --git a/templates/analytics/forecast_submit.html.twig b/templates/analytics/forecast_submit.html.twig index c8c3d28a..d4aedb2d 100644 --- a/templates/analytics/forecast_submit.html.twig +++ b/templates/analytics/forecast_submit.html.twig @@ -41,6 +41,28 @@
| Shift | +Riders | +
|---|---|
| {{ shift_data.label }} | +{{ shift_data.count }} | +