266 lines
9.5 KiB
PHP
266 lines
9.5 KiB
PHP
<?php
|
|
|
|
namespace routes;
|
|
|
|
use classes\authentication;
|
|
use classes\response;
|
|
use classes\router;
|
|
use classes\weatherapi;
|
|
use DateInterval;
|
|
use DateTime;
|
|
use Exception;
|
|
use objects\department_time_bookings_opening_hours_o;
|
|
use objects\departments_o;
|
|
use objects\logs_o;
|
|
use objects\orders_o;
|
|
use traits\route_t;
|
|
|
|
class moduleWeatherAPIRoute
|
|
{
|
|
use route_t;
|
|
|
|
public function run(): void
|
|
{
|
|
global /** @var response $response */
|
|
/** @var router $router */
|
|
$router, $response;
|
|
|
|
$this->get('/modules/weatherapi/current', function () {
|
|
global $response;
|
|
self::requirePermission('modules_weatherapi_current');
|
|
$user = (new authentication())->get_user();
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
return;
|
|
}
|
|
|
|
self::requireParameters(['q']);
|
|
self::requireType('q', self::type_string());
|
|
$result = (new weatherapi())->current(self::getParameter('q'));
|
|
(new logs_o())->add('modules_weatherapi', 'global', 1, $user->id, 'MODULES_WEATHERAPI', 'Current weather request completed');
|
|
$response->success($result, 200);
|
|
}, [
|
|
'modules_weatherapi_current' => 'Get current weather data from WeatherAPI',
|
|
]);
|
|
|
|
$this->get('/modules/weatherapi/forecast', function () {
|
|
global $response;
|
|
self::requirePermission('modules_weatherapi_forecast');
|
|
$user = (new authentication())->get_user();
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
return;
|
|
}
|
|
|
|
self::requireParameters(['q']);
|
|
self::requireType('q', self::type_string());
|
|
$days = (int)(self::getParameter('days') ?? 1);
|
|
$result = (new weatherapi())->forecast(self::getParameter('q'), $days);
|
|
(new logs_o())->add('modules_weatherapi', 'global', 1, $user->id, 'MODULES_WEATHERAPI', 'Forecast weather request completed');
|
|
$response->success($result, 200);
|
|
}, [
|
|
'modules_weatherapi_forecast' => 'Get weather forecast data from WeatherAPI',
|
|
]);
|
|
|
|
$this->get('/modules/weatherapi/search', function () {
|
|
global $response;
|
|
self::requirePermission('modules_weatherapi_search');
|
|
$user = (new authentication())->get_user();
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
return;
|
|
}
|
|
|
|
self::requireParameters(['q']);
|
|
self::requireType('q', self::type_string());
|
|
$result = (new weatherapi())->search(self::getParameter('q'));
|
|
(new logs_o())->add('modules_weatherapi', 'global', 1, $user->id, 'MODULES_WEATHERAPI', 'Weather location search completed');
|
|
$response->success($result, 200);
|
|
}, [
|
|
'modules_weatherapi_search' => 'Search location data from WeatherAPI',
|
|
]);
|
|
|
|
$this->get('/departments/weather', function () {
|
|
global $response;
|
|
self::requirePermission('departments_weather_get');
|
|
$user = (new authentication())->get_user();
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
return;
|
|
}
|
|
|
|
self::requireParameters(['id']);
|
|
self::requireType((int)self::getParameter('id'), self::type_int());
|
|
self::requireMinValue((int)self::getParameter('id'), 1);
|
|
self::requireDepartmentAccess((int)self::getParameter('id'));
|
|
|
|
$department = (new departments_o())->select((int)self::getParameter('id'));
|
|
$lat = (float)$department->latitude->value();
|
|
$lon = (float)$department->longitude->value();
|
|
if ($lat === 0.0 && $lon === 0.0) {
|
|
$response->error('Department does not have GPS coordinates configured', 400);
|
|
return;
|
|
}
|
|
|
|
$opening_hours = new department_time_bookings_opening_hours_o();
|
|
$opening_hours->selectByDepartment((int)self::getParameter('id'));
|
|
|
|
$weather = (new weatherapi())->forecast($lat . ',' . $lon, 2);
|
|
$timeline = self::buildDepartmentWeatherTimeline((int)self::getParameter('id'), $weather, $opening_hours);
|
|
|
|
(new logs_o())->add('modules_weatherapi', (int)self::getParameter('id'), 1, $user->id, 'DEPARTMENTS_WEATHER_GET', 'Department weather timeline fetched');
|
|
$response->success($timeline, 200);
|
|
}, [
|
|
'departments_weather_get' => 'Get department weather timeline with washes and productivity status',
|
|
'department_access_:id' => 'Access weather timeline for a specific department',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
private function buildDepartmentWeatherTimeline(int $department_id, object $forecast, department_time_bookings_opening_hours_o $opening_hours): array
|
|
{
|
|
$hourly_weather = [];
|
|
foreach (($forecast->forecast->forecastday ?? []) as $day) {
|
|
foreach (($day->hour ?? []) as $hour) {
|
|
$key = (new DateTime((string)$hour->time))->format('Y-m-d H:00');
|
|
$hourly_weather[$key] = self::mapWeatherCondition((int)($hour->condition->code ?? 1000), (string)($hour->condition->text ?? ''));
|
|
}
|
|
}
|
|
|
|
$entries = [];
|
|
$slot = new DateTime(date('Y-m-d H:00:00'));
|
|
$slot->add(new DateInterval('PT1H'));
|
|
|
|
for ($i = 0; $i < 24; $i++) {
|
|
$slot_key = $slot->format('Y-m-d H:00');
|
|
$weather = $hourly_weather[$slot_key] ?? 'mostly_clear';
|
|
$is_open = self::isDepartmentOpenAtHour($opening_hours, $slot);
|
|
$hours = $is_open ? self::getDailyOpenHours($opening_hours, $slot) : 0;
|
|
$washes = self::countWashesForHour($department_id, $slot);
|
|
$entries[] = [
|
|
'time' => $slot->format('H:00'),
|
|
'weather' => $weather,
|
|
'washes' => $washes,
|
|
'hours' => $hours,
|
|
'status' => self::calculateStatus($washes, $hours),
|
|
];
|
|
$slot->add(new DateInterval('PT1H'));
|
|
}
|
|
|
|
return $entries;
|
|
}
|
|
|
|
private function calculateStatus(int $washes, int $hours): string
|
|
{
|
|
if ($hours <= 0) {
|
|
return 'unknown';
|
|
}
|
|
|
|
$ratio = $washes / $hours;
|
|
if ($ratio >= 0.8) {
|
|
return 'healthy';
|
|
}
|
|
if ($ratio >= 0.4) {
|
|
return 'degraded';
|
|
}
|
|
return 'unhealthy';
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
private function isDepartmentOpenAtHour(department_time_bookings_opening_hours_o $opening_hours, DateTime $date_time): bool
|
|
{
|
|
$weekday = strtolower($date_time->format('l'));
|
|
$start = $opening_hours->{"{$weekday}_start"}->value();
|
|
$end = $opening_hours->{"{$weekday}_end"}->value();
|
|
if ($start === null || $end === null) {
|
|
return false;
|
|
}
|
|
|
|
$current = $date_time->format('H:i');
|
|
$start_hour = (new DateTime((string)$start))->format('H:i');
|
|
$end_hour = (new DateTime((string)$end))->format('H:i');
|
|
|
|
return $current >= $start_hour && $current < $end_hour;
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
private function getDailyOpenHours(department_time_bookings_opening_hours_o $opening_hours, DateTime $date_time): int
|
|
{
|
|
$weekday = strtolower($date_time->format('l'));
|
|
$start = $opening_hours->{"{$weekday}_start"}->value();
|
|
$end = $opening_hours->{"{$weekday}_end"}->value();
|
|
if ($start === null || $end === null) {
|
|
return 0;
|
|
}
|
|
|
|
$start_dt = new DateTime((string)$start);
|
|
$end_dt = new DateTime((string)$end);
|
|
$hours = ((int)$end_dt->format('U') - (int)$start_dt->format('U')) / 3600;
|
|
|
|
return max(0, (int)round($hours));
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
private function countWashesForHour(int $department_id, DateTime $hour_start): int
|
|
{
|
|
$start = clone $hour_start;
|
|
$end = clone $hour_start;
|
|
$end->add(new DateInterval('PT59M59S'));
|
|
|
|
return (new orders_o())->countWashesInDateRange(
|
|
$start->format('Y-m-d H:i:s'),
|
|
$end->format('Y-m-d H:i:s'),
|
|
$department_id
|
|
);
|
|
}
|
|
|
|
private function mapWeatherCondition(int $code, string $text): string
|
|
{
|
|
$rain = [1063, 1150, 1153, 1180, 1183, 1186, 1189, 1192, 1195, 1240, 1243, 1246];
|
|
$showers = [1072, 1168, 1171, 1198, 1201, 1249, 1252];
|
|
$snow = [1066, 1069, 1114, 1117, 1204, 1207, 1210, 1213, 1216, 1219, 1222, 1225, 1237, 1255, 1258, 1261, 1264];
|
|
$thunder = [1087, 1273, 1276, 1279, 1282];
|
|
|
|
if ($code === 1000) {
|
|
return 'clear';
|
|
}
|
|
if ($code === 1003) {
|
|
return 'mostly_clear';
|
|
}
|
|
if ($code === 1006) {
|
|
return 'partly_cloudy';
|
|
}
|
|
if ($code === 1009) {
|
|
return 'mostly_cloudy';
|
|
}
|
|
if ($code === 1030 || str_contains(strtolower($text), 'overcast')) {
|
|
return 'overcast';
|
|
}
|
|
if ($code === 1135 || $code === 1147) {
|
|
return 'fog';
|
|
}
|
|
if (in_array($code, $thunder, true)) {
|
|
return 'thunderstorm';
|
|
}
|
|
if (in_array($code, $snow, true)) {
|
|
return 'snow';
|
|
}
|
|
if (in_array($code, $showers, true)) {
|
|
return 'showers';
|
|
}
|
|
if (in_array($code, $rain, true)) {
|
|
return 'rain';
|
|
}
|
|
|
|
return 'mostly_clear';
|
|
}
|
|
}
|