# Техническая документация

# Таблицы по окнам возможностей



# staffRequests

В базе данных Ally информация об окнах возможностей хранится в таблице staffRequests.

[![Снимок экрана 2025-05-13 162043.png](https://bookstack.ally.software/uploads/images/gallery/2025-05/scaled-1680-/snimok-ekrana-2025-05-13-162043.png)](https://bookstack.ally.software/uploads/images/gallery/2025-05/snimok-ekrana-2025-05-13-162043.png)

Описание полей таблицы staffRequests:

<div align="left" dir="ltr" id="bkmrk-%D0%9F%D0%BE%D0%BB%D0%B5%C2%A0-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-id-%D0%A3%D0%BD"><table border="1" style="border-collapse: collapse; width: 124.321%; border-style: solid;"><colgroup><col style="width: 17.6761%;" width="106"></col><col style="width: 82.3239%;" width="494"></col></colgroup><tbody><tr><td class="align-center">Поле

</td><td class="align-center">Описание

</td></tr><tr><td>id

</td><td>Уникальный идентификатор заявки

</td></tr><tr><td>createdAt

</td><td>Дата и время создания заявки

</td></tr><tr><td>updatedAt

</td><td>Дата и время последнего изменения заявки

</td></tr><tr><td>details

</td><td>Подробная информация об окне возможностей в которую входит:

- staffType - Тип сотрудников, для которых предназначена заявка (аутсорс, штат или все вместе)
- extra - Указывает, является ли заявка экстренной (значение true или false)
- regionId - Идентификатор региона
- placeId - Идентификатор торговой точки (значение id из таблицы restaurants в базе данных Ally)
- positionType - Определяет доступность окна для ролей исполнителей (значения: all — доступно всем разрешённым ролям, selected — доступно только определённым ролям). Это поле не используется в форме
- comment - Комментарий к заявке
- partner - Название контрагента для заявки
- endAt - Дата и время окончания выполнения заявки
- beginAt - Дата и время начала выполнения заявки
- position - Роль исполнителя
- notifyDelay - Задержка для оповещений в секундах. Это поле используется для настройки задержки при выборе ТД и аутсорсинга
- staffPositions - Список ролей, для которых доступно окно. Это поле не используется в форме
- closeAt - Дата и время закрытия заявки
- meta - Дополнительная информация о том, является ли окно периодом или одноразовой заявкой, а также данные о времени
    
    
    - time - Время смены в указанном периоде
    - type - Тип заявки: периодическая или одноразовая (на один день)
    - dates - Даты периода, если заявка является периодической
- fromType - Источник набора исполнителей (значения: all — из любых мест, selected — только из указанного места)
- staffCount - Количество откликов на заявку
- userIdFIO - ФИО и userId пользователя, назначенного на заявку

</td></tr><tr><td>state

</td><td>Текущий статус заявки

</td></tr><tr><td>createdBy

</td><td>Идентификатор пользователя, создавшего заявку (указывается userId пользователя)

</td></tr><tr><td>closeReason

</td><td>Причина закрытия или удаления окна

</td></tr><tr><td>reservedAt

</td><td>Дата и время резервирования окна. Устаревшее поле, вместо него используется таблица staffRequestsReserves

</td></tr><tr><td>reservedBy

</td><td>Идентификатор пользователя, зарезервировавшего окно (указывается userId пользователя). Устаревшее поле, вместо него используется таблица staffRequestsReserves

</td></tr><tr><td>updatedBy

</td><td>Идентификатор пользователя, последним изменившего заявку

</td></tr></tbody></table>

</div>

# staffRequestsReserves

В базе данных Ally резервы окон возможностей хранятся в таблице staffRequestsReserves.

[![Снимок экрана 2025-05-13 161528.png](https://bookstack.ally.software/uploads/images/gallery/2025-05/scaled-1680-/snimok-ekrana-2025-05-13-161528.png)](https://bookstack.ally.software/uploads/images/gallery/2025-05/snimok-ekrana-2025-05-13-161528.png)

Описание полей таблицы staffRequestsReserves:

<div align="left" dir="ltr" id="bkmrk-%D0%9F%D0%BE%D0%BB%D0%B5-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-id-%D0%A3%D0%BD%D0%B8"><table border="1" style="border-collapse: collapse; width: 124.568%; border-style: solid;"><colgroup><col style="width: 17.3333%;" width="104"></col><col style="width: 82.6667%;" width="496"></col></colgroup><tbody><tr><td class="align-center">Поле

</td><td class="align-center">Описание

</td></tr><tr><td>id

</td><td>Уникальный идентификатор

</td></tr><tr><td>createdAt

</td><td>Дата и время создания записи

</td></tr><tr><td>updatedAt

</td><td>Дата и время последнего обновления записи

</td></tr><tr><td>active

</td><td>Статус резерва (активен или нет)

</td></tr><tr><td>requestId

</td><td>Идентификатор окна (ссылается на таблицу staffRequests)

</td></tr><tr><td>userId

</td><td>Идентификатор пользователя

</td></tr><tr><td>details

</td><td>Дополнительные данные

</td></tr><tr><td>partner

</td><td>Название контрагента для резерва

</td></tr><tr><td>updatedBy

</td><td>Идентификатор пользователя, который последний раз отредактировал запись

</td></tr></tbody></table>

</div>

# staffRequestsDeclines

В базе данных Ally отклоненные окна возможностей сохраняются в таблице staffRequestsDeclines.

[![Снимок экрана 2025-05-13 161720.png](https://bookstack.ally.software/uploads/images/gallery/2025-05/scaled-1680-/snimok-ekrana-2025-05-13-161720.png)](https://bookstack.ally.software/uploads/images/gallery/2025-05/snimok-ekrana-2025-05-13-161720.png)

Описание полей таблицы staffRequestsReserves:

<div align="left" dir="ltr" id="bkmrk-%D0%9F%D0%BE%D0%BB%D0%B5-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-id-%D0%A3%D0%BD%D0%B8"><table border="1" style="border-collapse: collapse; width: 117.654%; border-style: solid;"><colgroup><col style="width: 17.5525%;" width="105"></col><col style="width: 82.4475%;" width="495"></col></colgroup><tbody><tr><td class="align-center">Поле

</td><td class="align-center">Описание

</td></tr><tr><td>id

</td><td>Уникальный идентификатор

</td></tr><tr><td>createdAt

</td><td>Дата и время создания записи

</td></tr><tr><td>updatedAt

</td><td>Дата и время последнего обновления записи

</td></tr><tr><td>requestId

</td><td>Идентификатор окна (ссылается на таблицу staffRequests)

</td></tr><tr><td>userId

</td><td>Идентификатор пользователя

</td></tr><tr><td>details

</td><td>Дополнительные данные

</td></tr><tr><td>updatedBy

</td><td>Идентификатор пользователя, который последний раз отредактировал запись

</td></tr></tbody></table>

</div>

# staffRequestsEvents

В базе данных Ally связь между окном возможностей и выходом хранится в таблице staffRequestsEvents.

[![Снимок экрана 2025-05-13 161629.png](https://bookstack.ally.software/uploads/images/gallery/2025-05/scaled-1680-/snimok-ekrana-2025-05-13-161629.png)](https://bookstack.ally.software/uploads/images/gallery/2025-05/snimok-ekrana-2025-05-13-161629.png)

Описание полей таблицы staffRequestsEvents:

<div align="left" dir="ltr" id="bkmrk-%D0%9F%D0%BE%D0%BB%D0%B5-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-id-%D0%A3%D0%BD%D0%B8"><table border="1" style="border-collapse: collapse; width: 121.975%; height: 262.734px; border-style: solid;"><colgroup><col style="width: 17.3333%;" width="104"></col><col style="width: 82.6667%;" width="496"></col></colgroup><tbody><tr style="height: 50.3906px;"><td class="align-center" style="height: 50.3906px;">Поле

</td><td class="align-center" style="height: 50.3906px;">Описание

</td></tr><tr style="height: 35.3906px;"><td style="height: 35.3906px;">id

</td><td style="height: 35.3906px;">Уникальный идентификатор

</td></tr><tr style="height: 35.3906px;"><td style="height: 35.3906px;">createdAt

</td><td style="height: 35.3906px;">Дата и время создания записи

</td></tr><tr style="height: 35.3906px;"><td style="height: 35.3906px;">updatedAt

</td><td style="height: 35.3906px;">Дата и время последнего обновления записи

</td></tr><tr style="height: 35.3906px;"><td style="height: 35.3906px;">requestId

</td><td style="height: 35.3906px;">Идентификатор окна (ссылается на таблицу staffRequests)

</td></tr><tr style="height: 35.3906px;"><td style="height: 35.3906px;">eventId

</td><td style="height: 35.3906px;">Идентификатор выхода (ссылается на таблицу events)

</td></tr><tr style="height: 35.3906px;"><td style="height: 35.3906px;">updatedBy

</td><td style="height: 35.3906px;">Идентификатор пользователя, который последний раз отредактировал запись

</td></tr></tbody></table>

</div>

# staffRequestsSubscriptions

В базе данных Ally информация о подписках на окна возможностей в мобильном приложении хранится в таблице staffRequestsSubscriptions.

[![Снимок экрана 2025-05-13 161858.png](https://bookstack.ally.software/uploads/images/gallery/2025-05/scaled-1680-/snimok-ekrana-2025-05-13-161858.png)](https://bookstack.ally.software/uploads/images/gallery/2025-05/snimok-ekrana-2025-05-13-161858.png)

Описание полей таблицы staffRequestsSubscriptions:

<div align="left" dir="ltr" id="bkmrk-%D0%9F%D0%BE%D0%BB%D0%B5-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-id-%D0%A3%D0%BD%D0%B8"><table border="1" style="border-collapse: collapse; width: 122.346%; border-style: solid;"><colgroup><col style="width: 17.3333%;" width="104"></col><col style="width: 82.6667%;" width="496"></col></colgroup><tbody><tr><td class="align-center">Поле

</td><td class="align-center">Описание

</td></tr><tr><td>id

</td><td>Уникальный идентификатор

</td></tr><tr><td>createdAt

</td><td>Дата и время создания записи

</td></tr><tr><td>updatedAt

</td><td>Дата и время последнего обновления записи

</td></tr><tr><td>details

</td><td>Дополнительные данные

</td></tr><tr><td>userId

</td><td>Идентификатор пользователя

</td></tr><tr><td>updatedBy

</td><td>Идентификатор пользователя, который последний раз отредактировал запись

</td></tr></tbody></table>

</div>

# staffRequestsResponses

В базе данных Ally отклики на окна возможностей хранятся в таблице staffRequestsResponses.

[![Снимок экрана 2025-05-13 161935.png](https://bookstack.ally.software/uploads/images/gallery/2025-05/scaled-1680-/snimok-ekrana-2025-05-13-161935.png)](https://bookstack.ally.software/uploads/images/gallery/2025-05/snimok-ekrana-2025-05-13-161935.png)

Описание полей таблицы staffRequestsResponses:

<div align="left" dir="ltr" id="bkmrk-%D0%9F%D0%BE%D0%BB%D0%B5-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-id-%D0%A3%D0%BD%D0%B8"><table border="1" style="border-collapse: collapse; width: 126.049%; border-style: solid;"><colgroup><col style="width: 16.6667%;" width="100"></col><col style="width: 83.3333%;" width="500"></col></colgroup><tbody><tr><td class="align-center">Поле

</td><td class="align-center">Описание

</td></tr><tr><td>id

</td><td>Уникальный идентификатор

</td></tr><tr><td>createdAt

</td><td>Дата и время создания записи

</td></tr><tr><td>updatedAt

</td><td>Дата и время последнего обновления записи

</td></tr><tr><td>details

</td><td>Дополнительные данные, комментарии

</td></tr><tr><td>requestId

</td><td>Идентификатор окна (ссылается на таблицу staffRequests)

</td></tr><tr><td>userId

</td><td>Идентификатор пользователя

</td></tr><tr><td>offerId

</td><td>Идентификатор оффера (ссылается на таблицу staffRequestsOffers). Устаревшее поле, не используется

</td></tr><tr><td>updatedBy

</td><td>Идентификатор пользователя, который последний раз отредактировал запись

</td></tr></tbody></table>

</div>

# staffRequestsOffers

staffRequestsOffers - устаревшая таблица, которая уже не используется.

# Предиктивная аналитика



# Описание прогнозной модели

Прогнозная модель используется для оценки заказов, трудоемкости и распределения ресурсов. Модели и скрипты размещены на сервере izb-ally-nodered02 (IP: 10.1.241.244). Скрипты написаны на Python с использованием библиотеки ENTA.

---

#### Скрипты и команды для работы с моделью

```shell
python3.10 main.py --help
Usage: main.py [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  copy      copy time_series from one DB to another
  fit       Fits model with collected lines from DB
  forecast  Makes forecast for the next 7 days and saves it to DB
  test      Evaluates forecast metrics
  view      Prints one segment used for forecast

```

---

#### Модель и обучение

Наилучшие результаты показывает модель CatBoost. Перед использованием модель необходимо обучить. После обучения она применяется для прогнозов. Данные для обучения поступают из различных источников, результаты сохраняются в гипертаблицу time\_series.

---

#### Источники данных

- **Погода**: 
    - Исторические данные (с 2005 года) и ежедневные обновления загружаются с rp5.ru в таблицы weather и weather\_stations с помощью скрипта rp5\_weather (настраивается через файл static/cities.txt с списком метеостанций).
    - Актуальный прогноз погоды загружается ежедневно с api.met.no через Node-RED в таблицу weather.
- **Календарь**: Учитываются данные производственного календаря из таблицы calendar и православных праздников. Включение мусульманских праздников не улучшило точность.
- **База данных ВкусВилл**

---

#### Параметры прогноза

Прогноз строится на заданное количество дней вперед с различной дискретизацией (шагом по времени).

##### Прогноз для последней мили (сборка и доставка)

Строится с шагом 1 час на 14 дней вперед.

- **Обычный прогноз**: Основан на данных о сборке и доставке, где заказы привязаны к торговой точке (ТТ). Данные загружаются из MS SQL в БД Ally через Node-RED.
- **Зональный прогноз**: Учитывает данные о сборке/доставке и статистику распределения адресов по геозонам ТТ. На основе геозон формируются сводные буферы с вероятностями выполнения заказов (на основе статистики за 7 дней). История заказов сопоставляется с текущими геозонами для обучения модели, что позволяет прогнозировать для новых ТТ с ограниченной историей.

##### Прогноз для розницы

Строится с шагом 1 день на 21 день вперед. Основан на оценке трудоемкости, загружаемой во временной ряд 51 через Node-RED.

# Работа с данными для прогнозов

Исходные данные извлекаются из базы данных ВкусВилл (таблицы: Report.\[report\_zakaz\_tbl\], \[GeoReports\].\[Analytics\].\[EffectiveZonesOnlineServices\] и \[Geo\].\[geo\].\[tt\]), а также с внешних источников: API met.no и rp5.ru.  
Собранные данные сохраняются в следующих таблицах:

- vv\_orders\_ts — временные ряды заказов;
- test\_vv\_points — тестовые точки;
- weather — данные о погоде;
- weather\_stations — информация о метеостанциях;
- calendar — календарные данные.

Прогнозная модель CatBoost извлекает данные из указанных таблиц и выполняет их обработку.

Результаты обработки, включая прогнозы модели CatBoost, сохраняются в таблице time\_series. На основе этих результатов производятся дальнейшие расчеты для аналитики.

[![Описание прогноза (2).png](https://bookstack.ally.software/uploads/images/gallery/2025-09/scaled-1680-/opisanie-prognoza-2.png)](https://bookstack.ally.software/uploads/images/gallery/2025-09/opisanie-prognoza-2.png)

---

### Получение данных о заказах

Для получения данных о заказах составляется отчет по заказам с фильтрацией по дате заказа и наличию даты поставки.

Источником получения данных служит таблица Report.\[report\_zakaz\_tbl\]

Запрос извлекает данные о заказах из таблицы отчетов за указанный период времени. Запрос фокусируется на заказах, где дата заказа находится в заданном диапазоне, и дата поставки не является NULL. Дополнительно вычисляется поле с датой поставки, увеличенной на 15 минут.

<details id="bkmrk-%D0%A2%D0%B5%D0%BA%D1%81%D1%82-%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%B0-%D0%9D%D0%B8%D0%B6%D0%B5-%D0%BF"><summary>Текст запроса</summary>

Ниже представлен запрос для получения информации о заказах:

```
SELECT 
    id_order as id, 
    ShopNo as shopno, 
    order_type, 
    gettype, 
    date_order, 
    date_supply, 
    date_supply_untill, 
    date_posted, 
    date_collect_start, 
    date_collected, 
    date_delivery_start, 
    date_delivered, 
    sum_paid, 
    sum_paid_coupon, 
    count_pos, 
    latitude, 
    longitude, 
    client_type, 
    order_weight, 
    delivery_hottime, 
    collecting_await_dur, 
    collecting_dur, 
    delivery_await_dur, 
    delivery_dur, 
    completed_agg, 
    distance, 
    DATEADD(Minute, 15, date_supply) as date_supply_vv 
FROM Report.[report_zakaz_tbl] rzt (NOLOCK) 
WHERE date_order BETWEEN '{{payload.from}}' AND '{{payload.to}}' 
AND date_supply IS NOT NULL
```

</details><details id="bkmrk-%D0%9F%D0%BE%D0%BB%D1%8F-%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%B0-%D0%97%D0%B0%D0%BF%D1%80%D0%BE%D1%81-"><summary>Поля запроса</summary>

Запрос возвращает 27 полей. Ниже приведена таблица с описаниями:

<table border="1" style="border-collapse: collapse; width: 100%; height: 833.11px;"><colgroup><col style="width: 33.3745%;"></col><col style="width: 27.6935%;"></col><col style="width: 39.0555%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td class="align-center" style="height: 29.7969px; background-color: rgb(206, 212, 217);">Поле SELECT</td><td class="align-center" style="height: 29.7969px; background-color: rgb(206, 212, 217);">Исходное поле</td><td class="align-center" style="height: 29.7969px; background-color: rgb(206, 212, 217);">Тип данных</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">id</td><td style="height: 29.7969px;">id\_order</td><td style="height: 29.7969px;">INT</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">shopno</td><td style="height: 29.7969px;">ShopNo</td><td style="height: 29.7969px;">INT</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">order\_type</td><td style="height: 29.7969px;">order\_type</td><td style="height: 29.7969px;">TinyInt</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">gettype</td><td style="height: 29.7969px;">gettype</td><td style="height: 29.7969px;">TinyInt</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">date\_order</td><td style="height: 29.7969px;">date\_order</td><td style="height: 29.7969px;">DateTime</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">date\_supply</td><td style="height: 29.7969px;">date\_supply</td><td style="height: 29.7969px;">DateTime</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">date\_supply\_untill</td><td style="height: 29.7969px;">date\_supply\_untill</td><td style="height: 29.7969px;">DateTime</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">date\_posted</td><td style="height: 29.7969px;">date\_posted</td><td style="height: 29.7969px;">DateTime</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">date\_collect\_start</td><td style="height: 29.7969px;">date\_collect\_start</td><td style="height: 29.7969px;">DateTime</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">date\_collected</td><td style="height: 29.7969px;">date\_collected</td><td style="height: 29.7969px;">DateTime</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">date\_delivery\_start</td><td style="height: 29.7969px;">date\_delivery\_start</td><td style="height: 29.7969px;">DateTime</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">date\_delivered</td><td style="height: 29.7969px;">date\_delivered</td><td style="height: 29.7969px;">DateTime</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">sum\_paid</td><td style="height: 29.7969px;">sum\_paid</td><td style="height: 29.7969px;">FLOAT</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">sum\_paid\_coupon</td><td style="height: 29.7969px;">sum\_paid\_coupon</td><td style="height: 29.7969px;">FLOAT</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">count\_pos</td><td style="height: 29.7969px;">count\_pos</td><td style="height: 29.7969px;">SmallInt</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">latitude</td><td style="height: 29.7969px;">latitude</td><td style="height: 29.7969px;">Decimal(19,16)</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">longitude</td><td style="height: 29.7969px;">longitude</td><td style="height: 29.7969px;">Decimal(19,16)</td></tr><tr style="height: 28.5938px;"><td style="height: 28.5938px;">client\_type</td><td style="height: 28.5938px;">client\_type</td><td style="height: 28.5938px;">NVarChar(100) COLLATE Cyrillic\_General\_CI\_AS</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">order\_weight</td><td style="height: 29.7969px;">order\_weight</td><td style="height: 29.7969px;">Decimal(15,3)</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">delivery\_hottime</td><td style="height: 29.7969px;">delivery\_hottime</td><td style="height: 29.7969px;">DateTime</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">collecting\_await\_dur</td><td style="height: 29.7969px;">collecting\_await\_dur</td><td style="height: 29.7969px;">INT</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">collecting\_dur</td><td style="height: 29.7969px;">collecting\_dur</td><td style="height: 29.7969px;">INT</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">delivery\_await\_dur</td><td style="height: 29.7969px;">delivery\_await\_dur</td><td style="height: 29.7969px;">INT</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">delivery\_dur</td><td style="height: 29.7969px;">delivery\_dur</td><td style="height: 29.7969px;">INT</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">completed\_agg</td><td style="height: 29.7969px;">completed\_agg</td><td style="height: 29.7969px;">INT</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">distance</td><td style="height: 29.7969px;">distance</td><td style="height: 29.7969px;">Real</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">date\_supply\_vv</td><td style="height: 29.7969px;">date\_supply</td><td style="height: 29.7969px;">(вычисляемое)</td></tr></tbody></table>

</details><details id="bkmrk-%D0%A4%D0%B8%D0%BB%D1%8C%D1%82%D1%80%D1%8B-%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%B0-%D0%92-%D0%B7%D0%B0"><summary>Фильтры запроса</summary>

В запросе присутствуют 2 фильтра:

- date\_order BETWEEN '{{payload.from}}' AND '{{payload.to}}' — Фильтр по дате заказа. Включает заказы, где дата заказа &gt;= '{{payload.from}}' и &lt;= '{{payload.to}}'.
- date\_supply IS NOT NULL — Исключает заказы без указанной даты поставки.

</details><details id="bkmrk-%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D1%8F%D0%B5%D0%BC%D1%8B%D0%B5-%D0%BF%D0%BE%D0%BB%D1%8F-%D0%92-%D0%B7"><summary>Вычисляемые поля</summary>

В запросе есть вычисляемое поле:

- DATEADD(Minute, 15, date\_supply) as date\_supply\_vv — Функция SQL Server для добавления 15 минут к полю date\_supply.

</details>---

### Получение данных о геозонах

Для получения данных о геозонах составляется отчет по зонам с фильтрацией по магазинам.

Источником получения данных служит таблицы \[GeoReports\].\[Analytics\].\[EffectiveZonesOnlineServices\] и \[Geo\].\[geo\].\[tt\]

Запрос объединяет данные из двух таблиц для получения информации о зонах, услугах и временных интервалах.

<details id="bkmrk-%D0%A2%D0%B5%D0%BA%D1%81%D1%82-%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%B0-%D0%9D%D0%B8%D0%B6%D0%B5-%D0%BF-1"><summary>Текст запроса</summary>

Ниже представлен запрос для получения информации о заказах:

```
SELECT 
    tt.N AS id_tt, 
    id_online_service, 
    id_poly, 
    date_add, 
    geo, 
    payway, 
    time_start, 
    time_end 
FROM [GeoReports].[Analytics].[EffectiveZonesOnlineServices] (NOLOCK) z
JOIN [Geo].[geo].[tt] tt ON tt.id_TT = z.id_tt
WHERE tt.name_TT LIKE '%ДС[_]%
```

</details><details id="bkmrk-%D0%9F%D0%BE%D0%BB%D1%8F-%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%B0-%D0%97%D0%B0%D0%BF%D1%80%D0%BE%D1%81--1"><summary>Поля запроса</summary>

Запрос возвращает 8 полей. Ниже приведена таблица с описаниями:

<table border="1" style="border-collapse: collapse; width: 100%; height: 297.969px;"><colgroup><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td class="align-center" style="height: 29.7969px; background-color: rgb(206, 212, 217);">Поле SELECT</td><td class="align-center" style="height: 29.7969px; background-color: rgb(206, 212, 217);">Исходное поле</td><td class="align-center" style="height: 29.7969px; background-color: rgb(206, 212, 217);">Тип данных</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">id\_tt</td><td style="height: 29.7969px;">tt.N</td><td style="height: 29.7969px;">  
</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">id\_online\_service

</td><td style="height: 29.7969px;">id\_online\_service

</td><td style="height: 29.7969px;">  
</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">id\_poly

</td><td style="height: 29.7969px;">id\_poly

</td><td style="height: 29.7969px;">  
</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">date\_add

</td><td style="height: 29.7969px;">date\_add

</td><td style="height: 29.7969px;">  
</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">geo

</td><td style="height: 29.7969px;">geo

</td><td style="height: 29.7969px;">  
</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">payway

</td><td style="height: 29.7969px;">payway

</td><td style="height: 29.7969px;">  
</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">time\_start

</td><td style="height: 29.7969px;">time\_start

</td><td style="height: 29.7969px;">  
</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">time\_end

</td><td style="height: 29.7969px;">time\_end

</td><td style="height: 29.7969px;"> </td></tr></tbody></table>

</details><details id="bkmrk-%D0%A4%D0%B8%D0%BB%D1%8C%D1%82%D1%80%D1%8B-%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%B0-%D0%92-%D0%B7%D0%B0-1"><summary>Фильтры запроса</summary>

В запросе присутствуют 2 фильтра:

- INNER JOIN на tt.id\_TT = z.id\_tt — Это объединяет записи только если есть совпадение по идентификатору точки (id\_tt). Если в z нет соответствующей записи в tt, она не попадет в результат.
- WHERE tt.name\_TT like '%ДС\[\_\]%' — Фильтр по имени точки (магазина) в таблице tt.

</details>---

### Таблицы хранения полученных данных

Исходные данные, используемые для построения прогноза, размещены в следующих таблицах:

- vv\_orders\_ts — гипертаблица с информацией о заказах. Включает поля, заполняемые на основе исходной таблицы Report.\[report\_zakaz\_tbl\], а также ряд дополнительных полей.
- test\_vv\_points — географические зоны, связанные с торговыми точками (ТТ).
- weather — данные о погоде, загруженные из сервиса [https://api.met.no](https://api.met.no).
- weather\_stations — погодные станции с сервиса rp5.ru. Применялись для загрузки исторических метеоданных.
- calendar — табель-календарь, заполняемый посредством системы репликации.

Кроме того, на базе этих таблиц создаются материализованные представления:

- vv\_orders\_ts\_hash\_hourly — заказы в географической зоне за определенный час, исключая заказы с типом gettype = 6 (доставка через Яндекс).
- vv\_lines\_ts\_hash\_hourly — количество собранных строк для географической зоны за конкретный час.

Ниже приведено описание этих таблиц и представлений, в которым перечислены поля использующиеся для составления прогноза.

<details id="bkmrk-vv_orders_ts-%D0%9F%D0%BE%D0%BB%D0%B5%C2%A0-%D0%A2"><summary>vv\_orders\_ts</summary>

<table border="1" style="border-collapse: collapse; width: 100%; height: 923.704px;"><colgroup><col style="width: 50.0618%;"></col><col style="width: 50.0618%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td class="align-center" style="background-color: rgb(206, 212, 217); height: 29.7969px;">Поле </td><td class="align-center" style="background-color: rgb(206, 212, 217); height: 29.7969px;">Тип данных</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">id</td><td style="height: 29.7969px;">int4</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">shopno</td><td style="height: 29.7969px;">int4</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">order\_type</td><td style="height: 29.7969px;">int4</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">gettype</td><td style="height: 29.7969px;">int4</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">date\_order</td><td style="height: 29.7969px;">timestamp</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">date\_supply</td><td style="height: 29.7969px;">timestamp</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">date\_supply\_untill</td><td style="height: 29.7969px;">timestamp</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">date\_posted</td><td style="height: 29.7969px;">timestamp</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">date\_collect\_start</td><td style="height: 29.7969px;">timestamp</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">date\_collected</td><td style="height: 29.7969px;">timestamp</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">date\_delivery\_start</td><td style="height: 29.7969px;">timestamp</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">date\_delivered</td><td style="height: 29.7969px;">timestamp</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">sum\_paid</td><td style="height: 29.7969px;">float8</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">sum\_paid\_coupon</td><td style="height: 29.7969px;">float8</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">count\_pos</td><td style="height: 29.7969px;">int2</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">latitude</td><td style="height: 29.7969px;">numeric(19, 16)</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">longitude</td><td style="height: 29.7969px;">numeric(19, 16)</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">client\_type</td><td style="height: 29.7969px;">varchar(100)</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">order\_weight</td><td style="height: 29.7969px;">numeric(15, 3)</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">delivery\_hottime</td><td style="height: 29.7969px;">timestamp</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">collecting\_await\_dur</td><td style="height: 29.7969px;">int4</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">collecting\_dur</td><td style="height: 29.7969px;">int4</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">delivery\_await\_dur</td><td style="height: 29.7969px;">int4</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">delivery\_dur</td><td style="height: 29.7969px;">int4</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">completed\_agg</td><td style="height: 29.7969px;">int4</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">distance</td><td style="height: 29.7969px;">float4</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">date\_supply\_vv</td><td style="height: 29.7969px;">timestamp</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">details</td><td style="height: 29.7969px;">jsonb</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">tt\_id</td><td style="height: 29.7969px;">int4</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">geohash</td><td style="height: 29.7969px;">varchar(20)</td></tr></tbody></table>

</details><details id="bkmrk-test_vv_points-%D0%9F%D0%BE%D0%BB%D0%B5%C2%A0"><summary>test\_vv\_points</summary>

<table border="1" style="border-collapse: collapse; width: 100%; height: 115.188px;"><colgroup><col style="width: 24.0051%;"></col><col style="width: 25.303%;"></col><col style="width: 50.6919%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td class="align-center" style="background-color: rgb(206, 212, 217); height: 29.7969px;">Поле </td><td class="align-center" style="background-color: rgb(206, 212, 217); height: 29.7969px;">Тип данных</td><td class="align-center" style="background-color: rgb(206, 212, 217);">Описание</td></tr><tr style="height: 25.7969px;"><td style="height: 25.7969px;">geohash</td><td style="height: 25.7969px;">varchar(20)</td><td>Геохэш области</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">tt\_id</td><td style="height: 29.7969px;">numeric</td><td>ИД иорговой точки</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">coeff</td><td style="height: 29.7969px;">int4</td><td>Вероятность того, что заказ в данной области попадет в данную ТТ. Определяется на основе статистики заказов за предыдущие 3 дня

</td></tr></tbody></table>

</details><details id="bkmrk-weather-%D0%9F%D0%BE%D0%BB%D0%B5%C2%A0-%D0%A2%D0%B8%D0%BF-%D0%B4%D0%B0"><summary>weather</summary>

<table border="1" style="border-collapse: collapse; width: 100%; height: 115.188px;"><colgroup><col style="width: 24.0051%;"></col><col style="width: 25.303%;"></col><col style="width: 50.6919%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td class="align-center" style="background-color: rgb(206, 212, 217); height: 29.7969px;">Поле </td><td class="align-center" style="background-color: rgb(206, 212, 217); height: 29.7969px;">Тип данных</td><td class="align-center" style="background-color: rgb(206, 212, 217);">Описание</td></tr><tr style="height: 25.7969px;"><td style="height: 25.7969px;">weather\_station\_id</td><td style="height: 25.7969px;">int4</td><td>ИД погодной станции</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">date</td><td style="height: 29.7969px;">timestamp</td><td>Время</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">temperature</td><td style="height: 29.7969px;">numeric(3, 1)</td><td>Температура

</td></tr><tr><td>humidity</td><td>int4</td><td>Влажность

</td></tr><tr><td>wind\_speed</td><td>int4</td><td>Скорость ветра

</td></tr></tbody></table>

</details><details id="bkmrk-weather_stations-%D0%9F%D0%BE%D0%BB"><summary>weather\_stations</summary>

<table border="1" style="border-collapse: collapse; width: 100%; height: 115.188px;"><colgroup><col style="width: 24.0051%;"></col><col style="width: 25.303%;"></col><col style="width: 50.6919%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td class="align-center" style="background-color: rgb(206, 212, 217); height: 29.7969px;">Поле </td><td class="align-center" style="background-color: rgb(206, 212, 217); height: 29.7969px;">Тип данных</td><td class="align-center" style="background-color: rgb(206, 212, 217);">Описание</td></tr><tr style="height: 25.7969px;"><td style="height: 25.7969px;">id</td><td style="height: 25.7969px;">serial4</td><td>ИД погодной станции</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">active</td><td style="height: 29.7969px;">bool</td><td>Признак активности</td></tr></tbody></table>

</details><details id="bkmrk-calendar-%D0%9F%D0%BE%D0%BB%D0%B5%C2%A0-%D0%A2%D0%B8%D0%BF-%D0%B4"><summary>calendar</summary>

<table border="1" style="border-collapse: collapse; width: 100%; height: 119.188px;"><colgroup><col style="width: 24.0051%;"></col><col style="width: 25.303%;"></col><col style="width: 50.6919%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td class="align-center" style="background-color: rgb(206, 212, 217); height: 29.7969px;">Поле </td><td class="align-center" style="background-color: rgb(206, 212, 217); height: 29.7969px;">Тип данных</td><td class="align-center" style="background-color: rgb(206, 212, 217); height: 29.7969px;">Описание</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">id</td><td style="height: 29.7969px;">serial4</td><td style="height: 29.7969px;">ИД</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">date</td><td style="height: 29.7969px;">date</td><td style="height: 29.7969px;">Дата</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">type</td><td style="height: 29.7969px;">int4</td><td style="height: 29.7969px;">Тип (2 - суббота, 3 - воскресенье, 4 - предпраздничный день, 5 - праздник)</td></tr></tbody></table>

</details><details id="bkmrk-vv_orders_ts_hash_ho-1"><summary>vv\_orders\_ts\_hash\_hourly</summary>

Для составления представления формируется запрос который формирует представление для хранения агрегированных данных о количестве заказов, сгруппированных по геохешу и часовым интервалам. Исключаются заказы с типом получения gettype = 6. Представление обновляется автоматически.

Источником получения данных служит таблица vv\_orders\_ts.

Текст запроса:

```
CREATE MATERIALIZED VIEW vv_orders_ts_hash_hourly 
WITH (timescaledb.continuous) AS 
SELECT 
    geohash, 
    time_bucket('1 hour', ts.date_supply_vv) AS bucket, 
    COUNT(*) AS cnt 
FROM vv_orders_ts ts 
WHERE ts.gettype != 6 
GROUP BY geohash, bucket;
```

Поля запроса:

<table border="1" style="border-collapse: collapse; width: 100%; height: 119.188px;"><colgroup><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td class="align-center" style="background-color: rgb(206, 212, 217); height: 29.7969px;">Поле</td><td class="align-center" style="background-color: rgb(206, 212, 217); height: 29.7969px;">Тип данных</td><td class="align-center" style="background-color: rgb(206, 212, 217); height: 29.7969px;">Описание</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">geohash</td><td style="height: 29.7969px;">varchar(20)</td><td style="height: 29.7969px;">  
</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">bucket</td><td style="height: 29.7969px;">timestamp</td><td style="height: 29.7969px;"> </td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">cnt</td><td style="height: 29.7969px;">int8</td><td style="height: 29.7969px;">  
</td></tr></tbody></table>

</details><details id="bkmrk-vv_lines_ts_hash_hou"><summary>vv\_lines\_ts\_hash\_hourly</summary>

Для составления представления формируется запрос который формирует представление для хранения агрегированных данных о суммарном количестве позиций (строк) в заказах, сгруппированных по геохешу и часовым интервалам.

Источником получения данных служит таблица vv\_orders\_ts.

Текст запроса:

```
CREATE MATERIALIZED VIEW vv_lines_ts_hash_hourly 
WITH (timescaledb.continuous) AS 
SELECT 
    geohash, 
    time_bucket('1 hour', ts.date_supply_vv) AS bucket, 
    SUM(ts.count_pos) AS cnt 
FROM vv_orders_ts ts 
GROUP BY geohash, bucket;
```

Поля запроса:

<table border="1" style="border-collapse: collapse; width: 100%; height: 119.188px;"><colgroup><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td class="align-center" style="background-color: rgb(206, 212, 217); height: 29.7969px;">Поле</td><td class="align-center" style="background-color: rgb(206, 212, 217); height: 29.7969px;">Тип данных</td><td class="align-center" style="background-color: rgb(206, 212, 217); height: 29.7969px;">Описание</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">geohash</td><td style="height: 29.7969px;">varchar(20)</td><td style="height: 29.7969px;">  
</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">bucket</td><td style="height: 29.7969px;">timestamp</td><td style="height: 29.7969px;"> </td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">cnt</td><td style="height: 29.7969px;">int8</td><td style="height: 29.7969px;"> </td></tr></tbody></table>

</details>---

### Хранение результатов обработки исходных данных

Данные для прогнозов, сами прогнозы и результаты их анализа хранятся во временных рядах в таблице time\_series:

<table border="1" id="bkmrk-%D0%9F%D0%BE%D0%BB%D0%B5-%D0%A2%D0%B8%D0%BF-%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85-%D0%9E%D0%BF%D0%B8%D1%81" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col><col style="width: 33.3333%;"></col></colgroup><tbody><tr><td class="align-center" style="background-color: rgb(206, 212, 217);">Поле</td><td class="align-center" style="background-color: rgb(206, 212, 217);">Тип данных</td><td class="align-center" style="background-color: rgb(206, 212, 217);">Описание</td></tr><tr><td>id</td><td>bigserial</td><td>  
</td></tr><tr><td>tstamp</td><td>timestamptz</td><td> </td></tr><tr><td>type</td><td>int4</td><td>Описывает тип данных</td></tr><tr><td>restaurant\_id</td><td>int4</td><td>  
</td></tr><tr><td>user\_id</td><td>int4</td><td>  
</td></tr><tr><td>value</td><td>float8</td><td>Значение прогноза</td></tr><tr><td>details</td><td>jsonb</td><td> </td></tr></tbody></table>

Тип прогноза, для которого сформирован результат обработки, определяется значением поля type. Возможные числовые значения поля и их интерпретация приведены ниже. Сформированный прогноз по часам помещается в таблицу под типами 4-7

<table border="1" id="bkmrk-id-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-%D0%9F%D0%BE%D0%B4%D1%80%D0%B0%D0%B7%D0%B4%D0%B5" style="border-collapse: collapse; width: 100%; height: 515.532px;"><colgroup><col style="width: 5.93523%;"></col><col style="width: 34.1142%;"></col><col style="width: 20.0247%;"></col><col style="width: 20.0247%;"></col><col style="width: 20.0247%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td class="align-center" style="height: 29.7969px; background-color: rgb(206, 212, 217);"><span style="color: rgb(0, 0, 0);">type</span></td><td class="align-center" style="height: 29.7969px; background-color: rgb(206, 212, 217);"><span style="color: rgb(0, 0, 0);">Описание</span></td><td class="align-center" style="height: 29.7969px; background-color: rgb(206, 212, 217);"><span style="color: rgb(0, 0, 0);">Подразделение</span></td><td class="align-center" style="height: 29.7969px; background-color: rgb(206, 212, 217);"><span style="color: rgb(0, 0, 0);">Тип</span></td><td class="align-center" style="height: 29.7969px; background-color: rgb(206, 212, 217);"><span style="color: rgb(0, 0, 0);">Источник</span></td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">1</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Данные по доставке</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Последняя миля</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Исходные данные</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Репликатор</span></td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">2</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Данные по сборке</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Последняя миля</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Исходные данные</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Репликатор</span></td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">4</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Прогноз доставка</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Последняя миля</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Прогноз</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Python</span></td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">5</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Прогноз сборка</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Последняя миля</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Прогноз</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Python</span></td></tr><tr style="height: 30.5938px;"><td style="height: 30.5938px;"><span style="color: rgb(0, 0, 0);">6</span></td><td style="height: 30.5938px;"><span style="color: rgb(0, 0, 0);">Прогноз по геозонам доставка</span></td><td style="height: 30.5938px;"><span style="color: rgb(0, 0, 0);">Последняя миля</span></td><td style="height: 30.5938px;"><span style="color: rgb(0, 0, 0);">Прогноз</span></td><td style="height: 30.5938px;"><span style="color: rgb(0, 0, 0);">Python</span></td></tr><tr style="height: 31.5938px;"><td style="height: 31.5938px;"><span style="color: rgb(0, 0, 0);">7</span></td><td style="height: 31.5938px;"><span style="color: rgb(0, 0, 0);">Прогноз по геозонам сборка</span></td><td style="height: 31.5938px;"><span style="color: rgb(0, 0, 0);">Последняя миля</span></td><td style="height: 31.5938px;"><span style="color: rgb(0, 0, 0);">Прогноз</span></td><td style="height: 31.5938px;"><span style="color: rgb(0, 0, 0);">Python</span></td></tr><tr style="height: 31.5938px;"><td style="height: 31.5938px;"><span style="color: rgb(0, 0, 0);">51</span></td><td style="height: 31.5938px;"><span style="color: rgb(0, 0, 0);">Оценка трудоемкости розница</span></td><td style="height: 31.5938px;"><span style="color: rgb(0, 0, 0);">Розница</span></td><td style="height: 31.5938px;"><span style="color: rgb(0, 0, 0);">Исходные данные</span></td><td style="height: 31.5938px;"><span style="color: rgb(0, 0, 0);">Node-RED</span></td></tr><tr style="height: 46.5938px;"><td style="height: 46.5938px;"><span style="color: rgb(0, 0, 0);">52</span></td><td style="height: 46.5938px;"><span style="color: rgb(0, 0, 0);">Прогноз суммарной трудоемкости Розница</span></td><td style="height: 46.5938px;"><span style="color: rgb(0, 0, 0);">Розница</span></td><td style="height: 46.5938px;"><span style="color: rgb(0, 0, 0);">Прогноз</span></td><td style="height: 46.5938px;"><span style="color: rgb(0, 0, 0);">Python</span></td></tr><tr style="height: 46.5938px;"><td style="height: 46.5938px;"><span style="color: rgb(0, 0, 0);">53</span></td><td style="height: 46.5938px;"><span style="color: rgb(0, 0, 0);">Прогноз трудоемкости кассиров Розница</span></td><td style="height: 46.5938px;"><span style="color: rgb(0, 0, 0);">Розница</span></td><td style="height: 46.5938px;"><span style="color: rgb(0, 0, 0);">Прогноз</span></td><td style="height: 46.5938px;"><span style="color: rgb(0, 0, 0);">Python</span></td></tr><tr style="height: 30.5938px;"><td style="height: 30.5938px;"><span style="color: rgb(0, 0, 0);">55</span></td><td style="height: 30.5938px;"><span style="color: rgb(0, 0, 0);">Факт суммарной выработки Розница</span></td><td style="height: 30.5938px;"><span style="color: rgb(0, 0, 0);">Розница</span></td><td style="height: 30.5938px;"><span style="color: rgb(0, 0, 0);">Аналитика</span></td><td style="height: 30.5938px;"><span style="color: rgb(0, 0, 0);">pgAgent</span></td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">56</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Факт выработки кассиров Розница</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Розница</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Аналитика</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">pgAgent</span></td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">154</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Курьеры факт</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Последняя миля</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Аналитика</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Node-RED</span></td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">155</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Курьеры план</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Последняя миля</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Аналитика</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Node-RED</span></td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">156</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Курьеры прогноз</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Последняя миля</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Аналитика</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Node-RED</span></td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">157</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Заказы план</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Последняя миля</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Аналитика</span></td><td style="height: 29.7969px;"><span style="color: rgb(0, 0, 0);">Node-RED</span></td></tr></tbody></table>

# Запросы для составления прогнозов

Данные о доставке и сборке используются в качестве основного прогнозируемого ряда, в то время как остальные (погода, праздники) в качестве регрессионных данных.

---

### Зональный прогноз доставки

Для зонального прогноза доставки извлекаются и агрегируются данные рассчитываемые как сумма количества заказов (cnt), умноженного на коэффициент из геоточки (coeff), сгруппированных по идентификатору торговой точки (tt\_id) и часовому интервалу (bucket).

Ниже представлен запрос который делает все это и объединяет данные о заказах с географическими точками для анализа по сегментам (торговым точкам или зонам) в заданном диапазоне дат:

```
SELECT 
    h.bucket AS timestamp, 
    vp.tt_id AS segment, 
    SUM(cnt * coeff) AS target 
FROM vv_orders_ts_hash_hourly h 
JOIN test_vv_points vp ON vp.geohash = h.geohash 
WHERE bucket BETWEEN '{from_date}' AND '{to_date}' 
GROUP BY vp.tt_id, h.bucket;
```

Запрос берет значения из:

- vv\_orders\_ts\_hash\_hourly — представление которое содержит агрегированные данные о количестве заказов по геохешу и часам.
- test\_vv\_points — таблица, содержащая географические зоны, связанные с торговыми точками.

Запрос формирует следующие поля:

<table border="1" id="bkmrk-%D0%9F%D0%BE%D0%BB%D0%B5-%D0%B2-select-%D0%98%D1%81%D1%85%D0%BE%D0%B4%D0%BD" style="border-collapse: collapse; width: 100%; height: 208.578px;"><colgroup><col style="width: 14.8331%;"></col><col style="width: 16.2368%;"></col><col style="width: 68.9301%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td class="align-center" style="height: 29.7969px; background-color: rgb(206, 212, 217);">Поле в SELECT</td><td class="align-center" style="height: 29.7969px; background-color: rgb(206, 212, 217);">Исходное поле</td><td class="align-center" style="height: 29.7969px; background-color: rgb(206, 212, 217);">Описание</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">timestamp</td><td style="height: 29.7969px;">h.bucket</td><td style="height: 29.7969px;">Временной интервал, начало часа для агрегации данных о заказах.</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">segment</td><td style="height: 29.7969px;">vp.tt\_id</td><td style="height: 29.7969px;">Идентификатор торговой точки, к которой привязан geohash.</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">target</td><td style="height: 29.7969px;">(Вычисляемое)</td><td style="height: 29.7969px;">Сумма (cnt \* coeff), где cnt — количество заказов, coeff — коэффициент из геоточки.</td></tr></tbody></table>

---

### Зональный прогноз сборки

Для зонального прогноза сборки извлекаются и агрегируются данные рассчитываемые как сумма количества строк в заказах (cnt), умноженного на коэффициент (coeff), сгруппированных по идентификатору торговой точки (tt\_id) и часовому интервалу (bucket).

Ниже представлен запрос который который делает все это и объединяет данные о строках заказов с географическими точками для зонального анализа в заданном диапазоне дат.

```
SELECT 
    h.bucket AS timestamp, 
    vp.tt_id AS segment, 
    SUM(cnt * coeff) AS target 
FROM vv_lines_ts_hash_hourly h 
JOIN test_vv_points vp ON vp.geohash = h.geohash 
WHERE bucket BETWEEN '{from_date}' AND '{to_date}' 
GROUP BY vp.tt_id, h.bucket;
```

Запрос берет значения из:

- vv\_lines\_ts\_hash\_hourly — представление которое содержит агрегированные данные о количестве заказов по геохешу и часам.
- test\_vv\_points — таблица, содержащая географические зоны, связанные с торговыми точками.

Запрос формирует следующие поля:

<table border="1" id="bkmrk-%D0%9F%D0%BE%D0%BB%D0%B5-%D0%B2-select-%D0%98%D1%81%D1%85%D0%BE%D0%B4%D0%BD-1" style="border-collapse: collapse; width: 100%; height: 208.578px;"><colgroup><col style="width: 14.8331%;"></col><col style="width: 16.2368%;"></col><col style="width: 68.9301%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td class="align-center" style="height: 29.7969px; background-color: rgb(206, 212, 217);">Поле в SELECT</td><td class="align-center" style="height: 29.7969px; background-color: rgb(206, 212, 217);">Исходное поле</td><td class="align-center" style="height: 29.7969px; background-color: rgb(206, 212, 217);">Описание</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">timestamp</td><td style="height: 29.7969px;">h.bucket</td><td style="height: 29.7969px;">Временной интервал, начало часа для агрегации данных о заказах.</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">segment</td><td style="height: 29.7969px;">vp.tt\_id</td><td style="height: 29.7969px;">Идентификатор торговой точки, к которой привязан geohash.</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">target</td><td style="height: 29.7969px;">(Вычисляемое)</td><td style="height: 29.7969px;">Сумма (cnt \* coeff), где cnt — количество строк в заказах, coeff — коэффициент из геоточки.</td></tr></tbody></table>

---

### Данные о погоде

В прогнозах используются данные о погоде, такие как температура, влажность и скорость ветра из активных погодных станций в заданном диапазоне дат.

Ниже представлен запрос данных о погоде с дополнением последними значениями на конечную дату.

```
WITH t AS (
    SELECT 
        w.weather_station_id AS station_id, 
        w.date, 
        w.temperature, 
        w.humidity, 
        w.wind_speed, 
        ROW_NUMBER() OVER (PARTITION BY w.weather_station_id ORDER BY w.date DESC) AS rn 
    FROM weather w 
    JOIN weather_stations st ON st.id = w.weather_station_id 
    WHERE st.active AND date BETWEEN '{from_date}' AND '{to_date}'
)
SELECT 
    station_id, 
    date, 
    temperature, 
    humidity, 
    wind_speed 
FROM t 
UNION 
SELECT 
    station_id, 
    '{to_date}', 
    temperature, 
    humidity, 
    wind_speed 
FROM t 
WHERE t.rn = 1 
ORDER BY station_id, date;
```

Запрос берет значения из:

- weather — содержит исторические данные о погоде по станциям и датам.
- weather\_stations — содержит информацию о погодных станциях.

Запрос формирует следующие поля:

<table border="1" id="bkmrk-%D0%9F%D0%BE%D0%BB%D0%B5-%D0%B2-select-%D0%98%D1%81%D1%85%D0%BE%D0%B4%D0%BD-2" style="border-collapse: collapse; width: 100%; height: 255.172px;"><colgroup><col style="width: 13.9679%;"></col><col style="width: 18.0458%;"></col><col style="width: 67.9864%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td class="align-center" style="height: 29.7969px; background-color: rgb(206, 212, 217);">Поле в SELECT</td><td class="align-center" style="height: 29.7969px; background-color: rgb(206, 212, 217);">Исходное поле</td><td class="align-center" style="height: 29.7969px; background-color: rgb(206, 212, 217);">Описание</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">station\_id</td><td style="height: 29.7969px;">w.weather\_station\_id</td><td style="height: 29.7969px;">Идентификатор погодной станции.</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">date</td><td style="height: 29.7969px;">w.date</td><td style="height: 29.7969px;">Дата измерения погоды.</td></tr><tr style="height: 46.5938px;"><td style="height: 46.5938px;">temperature</td><td style="height: 46.5938px;">w.temperature</td><td style="height: 46.5938px;">Температура</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">humidity</td><td style="height: 29.7969px;">w.humidity</td><td style="height: 29.7969px;">Влажность воздуха</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">wind\_speed</td><td style="height: 29.7969px;">w.wind\_speed</td><td style="height: 29.7969px;">Скорость ветра</td></tr></tbody></table>

На основе метеорологических данных рассчитывается эквивалентная температура, которая используется в качестве входных данных для регрессионного анализа.

```
37 - (37 - temperature) / (0.68 - 0.0014 * humidity] + 1 / (1.76 + 1.4 * pow(wind_speed, 0.75))) - 0.29 * temperature * (1 - humidity / 100)
```

В этой формуле:

- Вычисляется разница между 37°C и фактической температурой: (37 - temperature). Это базовый "дефицит тепла".
- Вычисляется фактор сопротивления: (0.68 - 0.0014 \* humidity + 1 / (1.76 + 1.4 \* pow(wind\_speed, 0.75))). Он увеличивается при высокой влажности (меньше охлаждения) и уменьшается при сильном ветре (больше охлаждения).
- Делится разница на фактор сопротивления и вычитается из 37: это даёт основную ощущаемую температуру с учетом конвекции.
- Вычитается корректировка на испарение: 0.29 \* temperature \* (1 - humidity / 100), которая дополнительно охлаждает в сухих условиях.

---

### Календарные данные

В прогнозах используются данные производственного календаря из таблицы и календарь Православных Христианских праздников.

Ниже представлен запрос календарных данных:

```
SELECT 
    date, 
    type AS holiday 
FROM calendar 
WHERE date BETWEEN '{from_date}' AND '{to_date}' 
ORDER BY date;
```

Запрос берет значения из:

- calendar — табель-календарь, заполняемый через систему репликации

Запрос формирует следующие поля:

<table border="1" id="bkmrk-%D0%9F%D0%BE%D0%BB%D0%B5-%D0%B2-select-%D0%98%D1%81%D1%85%D0%BE%D0%B4%D0%BD-3" style="border-collapse: collapse; width: 100%; height: 255.172px;"><colgroup><col style="width: 13.9679%;"></col><col style="width: 18.0458%;"></col><col style="width: 67.9864%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td class="align-center" style="height: 29.7969px; background-color: rgb(206, 212, 217);">Поле в SELECT</td><td class="align-center" style="height: 29.7969px; background-color: rgb(206, 212, 217);">Исходное поле</td><td class="align-center" style="height: 29.7969px; background-color: rgb(206, 212, 217);">Описание</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">date</td><td style="height: 29.7969px;">date</td><td style="height: 29.7969px;">Дата календарного события

</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">holiday

</td><td style="height: 29.7969px;">type

</td><td style="height: 29.7969px;">Тип события</td></tr></tbody></table>

Данные о православных праздниках хранятся в файле: [calendar.csv](https://bookstack.ally.software/attachments/40)

# Расчёт требуемого количества сборщиков и курьеров

Логика расчета одинакова для сборщиков и курьеров. Прогноз трудоемкости автоматически преобразуется в требуемое количество сотрудников. Алгоритм включает следующие шаги:

#### 1. Оценка скорости сборки сотрудников

Для каждого сотрудника рассчитывается средняя скорость сборки на основе фактических данных за последний месяц. Скорость измеряется в количестве строк заказов, собираемых за час. Расчет выполняется в преобразовании scheduleBindings, код которого хранится в БД приложения. Из представления time\_series\_col\_hourly извлекается сумма собранных строк, деленная на общее количество часов работы. Учитываются только часы с продолжительностью сборки от 1000 до 3600 секунд, что исключает неполные или чрезмерно длинные интервалы и повышает надежность оценки.

```javascript
$userStatAvgRaw := $query($tqueryQuery, {
    'table': 'time_series_col_hourly',
    'from': $userStatFrom,
    'restaurantIds': $restaurantIds,
    'query': [
    {
        'select': {
          'user_id': 'user_id'
        },
        'sum': {
            'sum_qty': 'qty'
        },
        'count': {
            'cnt': '*'
        },
        'where': {
          'len >=': 1000000,
          'len <=': 3600000
        }
    }
    ]
  });
  $userStatAvg := $userStatAvgRaw.tquery{
    $string(user_id): {
      'speed': $number(sum_qty) / $number(cnt)
    }
  };
```

#### 2. Расчет плановой производительности

Далее для каждого часа прогнозируемого периода рассчитывается ожидаемое количество собранных строк на основе списка запланированных сборщиков и их индивидуальной скорости. Расчет выполняется в настройке dayPlaceTransform. Если дата относится к будущему, сотрудники фильтруются по позиции "Сборщик". Для каждого сотрудника извлекается индивидуальная скорость (userSpeed). Интервал события разбивается на часы; для каждого часа вклад рассчитывается как длительность работы × скорость / ~3,6 млн миллисекунд (для перевода в часы). Вклады суммируются в sum\_qty для часа. В массиве $hours (0–23 часа) значение plan устанавливается как sum\_qty (или 0 при отсутствии данных). Таким образом формируется плановая производительность — ожидаемое количество собранных строк заказов на основе текущего плана.

```javascript
$eventsStat := $inPast
    ? $lookup($lookup($ttStat, $string(place.id)),$dateStr)
    : $statEvents[position~>/Сборщик/].(
    $userSpeed := [$lookup($users,$string(userId)).userSpeedAvg, $speed][$!=null][0];
    $user := $lookup($users, $string(userId));
    $event := $user.isActive ? $range(beginAt, endAt).intersect($day);
    $event ? $array($event.snapTo('hour').by('hour')).(
        $r := $.range('hour').intersect($event);
        $r ? {'h':$.format('H'), 't': $r.diff()*$userSpeed/3599999}
    );
){
    h: { 'sum_qty': $round($sum(t)) }
};


$hours := [0..23].(
    ...
    $plan := $number([$lookup($eventsStat, $string($)).sum_qty, 0][0]);
    ...
);
```

#### 3. Сравнение с прогнозом

Далее рассчитывается разность между прогнозируемым количеством строк на сборку и плановой производительностью. Этот шаг выполняется в настройке dayPlaceTransform. В массиве $hours для каждого часа извлекается прогнозное значение ($stat) и план ($plan). Если дата в будущем, разность ($delta) вычисляется как (прогноз - план) / средняя скорость сборки. Положительное значение указывает на нехватку сотрудников, отрицательное — на избыток. Если прогноз отсутствует, $delta устанавливается в 0, чтобы избежать ошибок в рекомендациях.

```javascript
$hours := [0..23].(
    $statObj := $lookup($hourStat, $string($));
    $stat := $number($inPast ? $statObj.cnt : $statObj.cnt) * $extraNorm;
    $plan := $number([$lookup($eventsStat, $string($)).sum_qty, 0][0]);
    $delta := $stat
        ? $inPast
            ? $round($plan - $stat, 1)
            : $round(($stat - $plan) / $speed)
        : 0;
    ...
);
```

#### 4. Корректировка численности

Затем разность преобразуется в дополнительное или избыточное количество сотрудников с использованием средней скорости сборки по всей торговой точке. Эта скорость рассчитывается в scheduleBindings аналогично индивидуальной, но для всех сборщиков вместе. Из представления time\_series\_col\_hourly извлекается сумма собранных строк с группировкой по торговой точке и времени, а также агрегированные метрики: суммы, средние и количество записей. Ограничения по секундам сборки здесь не применяются.

```javascript
$ttStatRaw := $query($tqueryQuery, {
    'table': 'time_series_col_hourly',
    'from': $statFrom,
    'to': $to,
    'restaurantIds': $restaurantIds,
    'query': [
    {
        'select': {
            'restaurant_id': 'restaurant_id',
            'tstamp': 'tstamp'
        },
        'avg': {
            'avg_cnt': 'cnt',
            'avg_qty': 'qty'
        },
        'sum': {
            'sum_qty': 'qty',
            'sum_cnt': 'cnt'
        },
        'count': {
            'cnt': '*'
        }
    }
    ]
  });


  $ttStat := $ttStatRaw.tquery{
   $string(restaurant_id): $.{
   'time': $moment(tstamp),
        'sum_cnt': sum_cnt,
        'sum_qty': sum_qty,
        'avg_cnt': avg_cnt,
        'avg_qty': avg_qty,
        'cnt': cnt
   }{
        time.format('YYYY-MM-DD'): ${time.format('H'): {
        'sum_cnt': sum_cnt,
        'sum_qty': sum_qty,
        'avg_cnt': avg_cnt,
        'avg_qty': avg_qty,
        'cnt': cnt
        }}
    }
  };
```

#### 5. Вывод итогового количества

Наконец, полученное число используется для формирования рекомендаций о том, сколько сотрудников добавить или убрать в конкретный час. Пороговые значения определяются в настройке dayPlaceTransform. В функции $formatRowPlan (применяемой для будущих дней) извлекается объект часа с разностью ($delta). На основе этого значения формируется текст рекомендации: если разность равна нулю, отображается "Норма" в зеленом цвете без изменений; если разность положительная, показывается "Нехватка X человек" в красном (X — значение разности, что означает необходимость добавить сотрудников); если разность отрицательная, отображается "Избыток X человек" в оранжевом (X — абсолютное значение разности, что предполагает возможность убрать сотрудников). Если данных нет, выводится сообщение "Нет данных".

```javascript
$formatRowPlan := function($t) {(
    $h := $hours[$t];
    $info := $h.stat ? $h.plan & ' из ' & $h.stat : '-';
    $span := $h.delta = 0
        ? ''
        : $h.delta > 0
            ? $redSpan
            : $amberSpan;
    $spanEnd := $h.delta = 0
        ? ''
        : '';


    $span & $formatNumber($t, '00') & ':00 - ' & $formatNumber($t+1, '00') & ':00' & $spanEnd & ' | 

' & $span & $info & $spanEnd & '
' &
($h.stat
? $h.delta = 0
? $greenBox & 'Норма'
: $h.delta > 0
? $redBox & 'Нехватка ' & $h.delta & ' чел.'
: $amberBox & 'Избыток ' & -$h.delta & ' чел.'
: 'Нет данных') & '
';
)};
```

---

### Формула расчета требуемого количества сборщиков или курьеров

Все вышеперечисленные шаги можно описать формулой, которая рассчитывает рекомендуемое общее количество работников, необходимое для обработки заказов в конкретный час.

<span style="font-size: 12.0pt; line-height: 115%; font-family: 'Calibri',sans-serif; mso-ascii-theme-font: minor-latin; mso-fareast-font-family: Calibri; mso-fareast-theme-font: minor-latin; mso-hansi-theme-font: minor-latin; mso-bidi-font-family: 'Times New Roman'; mso-bidi-theme-font: minor-bidi; mso-ansi-language: RU; mso-fareast-language: EN-US; mso-bidi-language: AR-SA;">![](https://bookstack.ally.software/uploads/images/gallery/2025-08/embedded-image-0kqglfmf.png)</span>

Где:

<span style="font-size: 12.0pt; line-height: 115%; font-family: 'Aptos',sans-serif; mso-ascii-theme-font: minor-latin; mso-fareast-font-family: Aptos; mso-fareast-theme-font: minor-latin; mso-hansi-theme-font: minor-latin; mso-bidi-font-family: 'Times New Roman'; mso-bidi-theme-font: minor-bidi; mso-ansi-language: RU; mso-fareast-language: EN-US; mso-bidi-language: AR-SA;">t — Конкретный час, о котором идёт речь</span>

<span style="font-size: 12.0pt; line-height: 115%; font-family: 'Aptos',sans-serif; mso-ascii-theme-font: minor-latin; mso-fareast-font-family: Aptos; mso-fareast-theme-font: minor-latin; mso-hansi-theme-font: minor-latin; mso-bidi-font-family: 'Times New Roman'; mso-bidi-theme-font: minor-bidi; mso-ansi-language: RU; mso-fareast-language: EN-US; mso-bidi-language: AR-SA;">Dt — Сколько заказов на доставку ожидается в этот час</span>

<span style="font-size: 12.0pt; line-height: 115%; font-family: 'Aptos',sans-serif; mso-ascii-theme-font: minor-latin; mso-fareast-font-family: Aptos; mso-fareast-theme-font: minor-latin; mso-hansi-theme-font: minor-latin; mso-bidi-font-family: 'Times New Roman'; mso-bidi-theme-font: minor-bidi; mso-ansi-language: RU; mso-fareast-language: EN-US; mso-bidi-language: AR-SA;">Nt — Сколько людей уже поставили смену на этот час</span>

**![](https://bookstack.ally.software/uploads/images/gallery/2025-08/embedded-image-im2x2hgz.png)** — <span style="font-size: 12.0pt; line-height: 115%; font-family: 'Aptos',sans-serif; mso-ascii-theme-font: minor-latin; mso-fareast-font-family: Aptos; mso-fareast-theme-font: minor-latin; mso-hansi-theme-font: minor-latin; mso-bidi-font-family: 'Times New Roman'; mso-bidi-theme-font: minor-bidi; mso-ansi-language: RU; mso-fareast-language: EN-US; mso-bidi-language: AR-SA;">Средняя скорость работы i-го человека, записавшегося в смену</span>

![](https://bookstack.ally.software/uploads/images/gallery/2025-08/embedded-image-qbkb5ame.png)— <span style="font-size: 12.0pt; line-height: 115%; font-family: 'Aptos',sans-serif; mso-ascii-theme-font: minor-latin; mso-fareast-font-family: Aptos; mso-fareast-theme-font: minor-latin; mso-hansi-theme-font: minor-latin; mso-bidi-font-family: 'Times New Roman'; mso-bidi-theme-font: minor-bidi; mso-ansi-language: RU; mso-fareast-language: EN-US; mso-bidi-language: AR-SA;">Средняя скорость по всем курьерам на точке</span>

<span style="font-size: 12.0pt; line-height: 115%; font-family: 'Aptos',sans-serif; mso-ascii-theme-font: minor-latin; mso-fareast-font-family: Aptos; mso-fareast-theme-font: minor-latin; mso-hansi-theme-font: minor-latin; mso-bidi-font-family: 'Times New Roman'; mso-bidi-theme-font: minor-bidi; mso-ansi-language: RU; mso-fareast-language: EN-US; mso-bidi-language: AR-SA;">Rt — Сколько работников нужно вывести в итоге в этот час</span>

# Аналитика сборки по прошедшим дням

Пользователю отображаются следующие элементы:

1. Временной интервал, за который представлены данные;
2. Прогнозное количество заказов для указанного интервала (ожидаемое значение, сформированное [прогнозной моделью](https://bookstack.ally.software/books/texniceskaia-dokumentaciia/page/opisanie-prognoznoi-modeli) на основе данных, описанных в [статье](https://bookstack.ally.software/books/texniceskaia-dokumentaciia/page/rabota-s-dannymi-dlia-prognozov));
3. Фактическое количество заказов за интервал (реальное значение, извлеченное из [базы данных ВкусВилл](https://bookstack.ally.software/books/texniceskaia-dokumentaciia/page/rabota-s-dannymi-dlia-prognozov));
4. Оценка точности прогноза, рассчитываемая как симметричная средняя абсолютная процентная ошибка. ```
    (2 * (факт - прогноз) / (факт + прогноз))
    ```

Возможные значения:

- "Попадание" (прогноз был в пределах нормы если разница между фактом и прогнозом меньше либо равна 3 или оценка точности &lt; 0,15),
- "Недопрогноз" (прогноз был занижен, фактическое значение выше если оценка точности &gt; 0),
- "Перепрогноз" (прогноз был завышен, фактическое значение ниже если оценка точности &lt; 0)

[![WDsimage.png](https://bookstack.ally.software/uploads/images/gallery/2025-09/scaled-1680-/wdsimage.png)](https://bookstack.ally.software/uploads/images/gallery/2025-09/wdsimage.png)

---

Прогнозное значение извлекается из таблицы [time\_series](https://bookstack.ally.software/link/733#bkmrk-%D0%A5%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5-%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%D0%BE%D0%B2), из записей с типом 7 (обозначающим прогноз по геозонам для сборки заказов). Значение берется из поля value.

[![x8mimage.png](https://bookstack.ally.software/uploads/images/gallery/2025-09/scaled-1680-/CzBx8mimage.png)](https://bookstack.ally.software/uploads/images/gallery/2025-09/CzBx8mimage.png)

---

Фактическое значение формируется из представления time\_series\_col\_hourly, которое создается на основе информации из таблицы time\_series. Количество заказов рассчитывается путем суммирования значений поля qty всех записей за выбранный интервал. Пример расчета: для значений qty = 90 + 118 + 68 общее количество заказов составляет 276.

[![4pRimage.png](https://bookstack.ally.software/uploads/images/gallery/2025-09/scaled-1680-/4primage.png)](https://bookstack.ally.software/uploads/images/gallery/2025-09/4primage.png)

---

Для отображения всех этих данных в интерфейсе используется настройка dayPlaceTransform, которая формирует массив $hours. Массив содержит объекты для каждого часа суток (0–23) с соответствующими полями:

- hour — текущий час;
- stat — прогнозное количество заказов на этот час;
- plan — фактическое количество заказов на этот час;
- delta — отклонение между прогнозом и фактическим значением.
- smape — относительная ошибка прогноза, чтобы оценить его качество независимо от масштаба данных (симметричная средняя абсолютная процентная ошибка).
- err — ошибка прогноза: 0 — в пределах нормы (попадание), 1 — недопрогноз (прогноз занижен), -1 — перепрогноз (прогноз завышен).

Код, формирующий массив представлен ниже.

```javascript
$hours := [0..23].(
    $statObj := $lookup($hourStat, $string($));
    $stat := $number($inPast ? $statObj.cnt : $statObj.cnt) * $extraNorm;
    $plan := $number([$lookup($eventsStat, $string($)).sum_qty, 0][0]);
    $delta := $stat
        ? $inPast
            ? $round($plan - $stat, 1)
            : $round(($stat - $plan) / $speed)
        : 0;
    $smape := 2 * ($plan - $stat) / ($plan + $stat);
    $err := ($abs($delta) <= 3 or $abs($smape) < 0.15)
        ? 0
        : $smape > 0
            ? 1
            : -1;
    {
        "hour": $,
        "stat": $round($stat,1),
        "plan": $round($plan,1),
        "delta": $delta,
        "smape": $smape,
        "err": $err
    };
);
```

# Аналитика сборки по будущим дням

Пользователю предоставляется следующая информация:

1. Временной интервал, за который представлены данные;
2. Покрываемое количество заказов для указанного интервала (основывается на списке запланированных сборщиков и их индивидуальной скорости работы; механизм расчета описан в [статье](https://bookstack.ally.software/books/texniceskaia-dokumentaciia/page/rascet-trebuemogo-kolicestva-sborshhikov-i-kurerov), пункты 1 и 2);
3. Прогнозное количество заказов за интервал (ожидаемое значение, сформированное [прогнозной моделью](https://bookstack.ally.software/books/texniceskaia-dokumentaciia/page/opisanie-prognoznoi-modeli) на основе данных, описанных в [статье](https://bookstack.ally.software/books/texniceskaia-dokumentaciia/page/rabota-s-dannymi-dlia-prognozov));
4. Оценка требуемого количества сборщиков (механизм расчета описан в [статье](https://bookstack.ally.software/books/texniceskaia-dokumentaciia/page/rascet-trebuemogo-kolicestva-sborshhikov-i-kurerov)); возможные значения: 
    - "Норма" (сотрудников достаточно для обработки ожидаемого количества заказов)
    - "Избыток" (сотрудников запланировано больше необходимого, возможен простой персонала)
    - "Нехватка" (сотрудников недостаточно для успешной обработки заказов).

[![Irmimage.png](https://bookstack.ally.software/uploads/images/gallery/2025-09/scaled-1680-/irmimage.png)](https://bookstack.ally.software/uploads/images/gallery/2025-09/irmimage.png)

---

Прогнозное значение извлекается из таблицы [time\_series](https://bookstack.ally.software/link/733#bkmrk-%D0%A5%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5-%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%D0%BE%D0%B2), из записей с типом 7 (обозначающим прогноз по геозонам для сборки заказов). Значение берется из поля value.

[![BBYimage.png](https://bookstack.ally.software/uploads/images/gallery/2025-09/scaled-1680-/bbyimage.png)](https://bookstack.ally.software/uploads/images/gallery/2025-09/bbyimage.png)

---

Покрываемое количество заказов рассчитывается в приложении через настройку dayPlaceTransform на основе данных о запланированных сотрудниках на час и их скорости сборки. При изменении числа сотрудников данные обновляются автоматически.  
После расчета, эта настройка формирует массив $hours, содержащий данные для отображения в интерфейсе. Массив состоит из объектов для каждого часа суток (0–23), включающих поля:

- hour — текущий час;
- stat — прогнозное количество заказов;
- plan — покрываемое количество заказов, рассчитанное на основе сотрудников и скорости сборки;

Код, формирующий массив представлен ниже.

```javascript
$hours := [0..23].(
    $statObj := $lookup($hourStat, $string($));
    $stat := $number($inPast ? $statObj.cnt : $statObj.cnt) * $extraNorm;
    $plan := $number([$lookup($eventsStat, $string($)).sum_qty, 0][0]);
    $delta := $stat
        ? $inPast
            ? $round($plan - $stat, 1)
            : $round(($stat - $plan) / $speed)
        : 0;
    $smape := 2 * ($plan - $stat) / ($plan + $stat);
    $err := ($abs($delta) <= 3 or $abs($smape) < 0.15)
        ? 0
        : $smape > 0
            ? 1
            : -1;
    {
        "hour": $,
        "stat": $round($stat,1),
        "plan": $round($plan,1),
        "delta": $delta,
        "smape": $smape,
        "err": $err
    };
);
```

---

Так же, в таблице [time\_series](https://bookstack.ally.software/link/733#bkmrk-%D0%A5%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5-%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%D0%BE%D0%B2) в записях с типом 163 сохраняются данные, вычисляемые в базе данных по аналогичной схеме один раз в сутки и используемые для выгрузки.

[![sdGimage.png](https://bookstack.ally.software/uploads/images/gallery/2025-09/scaled-1680-/sdgimage.png)](https://bookstack.ally.software/uploads/images/gallery/2025-09/sdgimage.png)

# Аналитика курьеров по прошедшим дням

Пользователю предоставляется следующая информация:

1. Временной интервал, за который представлены данные;
2. Прогнозное количество заказов для указанного интервала (ожидаемое значение, сформированное [прогнозной моделью](https://bookstack.ally.software/books/texniceskaia-dokumentaciia/page/opisanie-prognoznoi-modeli) на основе данных, описанных в [статье](https://bookstack.ally.software/books/texniceskaia-dokumentaciia/page/rabota-s-dannymi-dlia-prognozov));
3. Фактическое количество заказов за интервал (реальное значение, извлеченное из [базы данных ВкусВилл](https://bookstack.ally.software/books/texniceskaia-dokumentaciia/page/rabota-s-dannymi-dlia-prognozov));
4. Оценка точности прогноза, рассчитываемая как симметричная средняя абсолютная процентная ошибка. ```
    (2 * (факт - прогноз) / (факт + прогноз))
    ```

Возможные значения:

- "Попадание" (прогноз был в пределах нормы если разница между фактом и прогнозом меньше либо равна 3 или оценка точности &lt; 0,15),
- "Недопрогноз" (прогноз был занижен, фактическое значение выше если оценка точности &gt; 0),
- "Перепрогноз" (прогноз был завышен, фактическое значение ниже если оценка точности &lt; 0)

[![04iimage.png](https://bookstack.ally.software/uploads/images/gallery/2025-09/scaled-1680-/XRO04iimage.png)](https://bookstack.ally.software/uploads/images/gallery/2025-09/XRO04iimage.png)

---

Прогнозное значение извлекается из таблицы [time\_series](https://bookstack.ally.software/link/733#bkmrk-%D0%A5%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5-%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%D0%BE%D0%B2), из записей с типом 6 (обозначающим прогноз по геозонам для доставки заказов). Значение берется из поля value.

[![hLIimage.png](https://bookstack.ally.software/uploads/images/gallery/2025-09/scaled-1680-/hliimage.png)](https://bookstack.ally.software/uploads/images/gallery/2025-09/hliimage.png)

---

Фактическое значение рассчитывается из представления time\_series\_deliv\_hourly, созданного на основе таблицы time\_series и содержащего данные о заказах курьеров. Количество заказов определяется суммированием значений поля cnt (количество доставленных заказов) всех записей за выбранный интервал. Пример: для интервала 00:00–01:00 2 августа сумма значений cnt (8 + 1 + 4 + 1 + 1 + 1 + 3 + 2 + 1 + 2 + 1 + 1 + 1 + 4 + 1 + 2 + 2 + 2 + 3 + 4 + 5 + 1 + 1 + 1 + 1 + 1) равна 55.

[![D0Kimage.png](https://bookstack.ally.software/uploads/images/gallery/2025-09/scaled-1680-/d0kimage.png)](https://bookstack.ally.software/uploads/images/gallery/2025-09/d0kimage.png)

---

Для отображения всех этих данных в интерфейсе используется настройка dayPlaceTransform, которая формирует массив $hours. Массив содержит объекты для каждого часа суток (0–23) с соответствующими полями:

- hour — текущий час;
- stat — прогнозное количество заказов на этот час;
- plan — фактическое количество заказов на этот час;
- delta — отклонение между прогнозом и фактическим значением.
- smape — относительная ошибка прогноза, чтобы оценить его качество независимо от масштаба данных (симметричная средняя абсолютная процентная ошибка).
- err — ошибка прогноза: 0 — в пределах нормы (попадание), 1 — недопрогноз (прогноз занижен), -1 — перепрогноз (прогноз завышен).

Код, формирующий массив представлен ниже.

```javascript
$hours := [0..23].(
    $statObj := $lookup($hourStat, $string($));
    $stat := $number($inPast ? $statObj.cnt : $statObj.cnt) * $extraNorm;
    $plan := $number([$lookup($eventsStat, $string($)).sum_qty, 0][0]);
    $delta := $stat
        ? $inPast
            ? $round($plan - $stat, 1)
            : $round(($stat - $plan) / $speed)
        : 0;
    $smape := 2 * ($plan - $stat) / ($plan + $stat);
    $err := ($abs($delta) <= 3 or $abs($smape) < 0.15)
        ? 0
        : $smape > 0
            ? 1
            : -1;
    {
        "hour": $,
        "stat": $round($stat,1),
        "plan": $round($plan,1),
        "delta": $delta,
        "smape": $smape,
        "err": $err
    };
)
```

# Аналитика курьеров по будущим дням

Пользователю предоставляется следующая информация:

1. Временной интервал, за который представлены данные;
2. Покрываемое количество заказов для указанного интервала (основывается на списке запланированных сборщиков и их индивидуальной скорости работы; механизм расчета описан в [статье](https://bookstack.ally.software/books/texniceskaia-dokumentaciia/page/rascet-trebuemogo-kolicestva-sborshhikov-i-kurerov), пункты 1 и 2);
3. Прогнозное количество заказов за интервал (ожидаемое значение, сформированное [прогнозной моделью](https://bookstack.ally.software/books/texniceskaia-dokumentaciia/page/opisanie-prognoznoi-modeli) на основе данных, описанных в [статье](https://bookstack.ally.software/books/texniceskaia-dokumentaciia/page/rabota-s-dannymi-dlia-prognozov));
4. Оценка требуемого количества сборщиков (механизм расчета описан в [статье](https://bookstack.ally.software/books/texniceskaia-dokumentaciia/page/rascet-trebuemogo-kolicestva-sborshhikov-i-kurerov)); возможные значения: 
    - "Норма" (сотрудников достаточно для обработки ожидаемого количества заказов)
    - "Избыток" (сотрудников запланировано больше необходимого, возможен простой персонала)
    - "Нехватка" (сотрудников недостаточно для успешной обработки заказов).

[![EErimage.png](https://bookstack.ally.software/uploads/images/gallery/2025-09/scaled-1680-/eerimage.png)](https://bookstack.ally.software/uploads/images/gallery/2025-09/eerimage.png)

---

Прогнозное значение извлекается из таблицы [time\_series](https://bookstack.ally.software/link/733#bkmrk-%D0%A5%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5-%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%D0%BE%D0%B2), из записей с типом 6 (обозначающим прогноз по геозонам для доставки заказов). Значение берется из поля value.

[![u9iimage.png](https://bookstack.ally.software/uploads/images/gallery/2025-09/scaled-1680-/v0hu9iimage.png)](https://bookstack.ally.software/uploads/images/gallery/2025-09/v0hu9iimage.png)

---

Покрываемое количество заказов рассчитывается в приложении через настройку dayPlaceTransform на основе данных о запланированных сотрудниках на час и их скорости сборки. При изменении числа сотрудников данные обновляются автоматически.  
После расчета, эта настройка формирует массив $hours, содержащий данные для отображения в интерфейсе. Массив состоит из объектов для каждого часа суток (0–23), включающих поля:

- hour — текущий час;
- stat — прогнозное количество заказов;
- plan — покрываемое количество заказов, рассчитанное на основе сотрудников и скорости сборки;

Код, формирующий массив представлен ниже.

```javascript
$hours := [0..23].(
    $statObj := $lookup($hourStat, $string($));
    $stat := $number($inPast ? $statObj.cnt : $statObj.cnt) * $extraNorm;
    $plan := $number([$lookup($eventsStat, $string($)).sum_qty, 0][0]);
    $delta := $stat
        ? $inPast
            ? $round($plan - $stat, 1)
            : $round(($stat - $plan) / $speed)
        : 0;
    $smape := 2 * ($plan - $stat) / ($plan + $stat);
    $err := ($abs($delta) <= 3 or $abs($smape) < 0.15)
        ? 0
        : $smape > 0
            ? 1
            : -1;
    {
        "hour": $,
        "stat": $round($stat,1),
        "plan": $round($plan,1),
        "delta": $delta,
        "smape": $smape,
        "err": $err
    };
);
```

# Таблица плановых и фактических курьеров

В интерфейсе отображаются следующие данные за выбранный день:

- Количество запланированных курьеров: Учитываются курьеры, чьи выходы были назначены заранее (до наступления дня). Данные извлекаются из списка запланированных сотрудников, фильтруются по курьерам и разбиваются по типам (Авто, Вело, Вело Свой, Ночной Вело, Ночной Авто, Мобильный Авто и т.д.).
- Количество фактически отработавших на смене: Учитываются курьеры, которые реально работали в этот день и у которых проставлен соответствующий выход в приложении. Это определяется по информации которая приходит извне из приложения ПМ Курьер. Для будущих дней значение всегда равно нулю, поскольку день еще не наступил.
- Процент вакцинированных: Рассчитывается на основе списка запланированных или фактически вышедших курьеров. Определяется доля сотрудников с активным статусом вакцинации среди общего количества в списке.

[![image.png](https://bookstack.ally.software/uploads/images/gallery/2025-09/scaled-1680-/VCAimage.png)](https://bookstack.ally.software/uploads/images/gallery/2025-09/VCAimage.png)

Значения рассчитываются динамически исходя из текущего графика. При изменении графика данные обновляются в реальном времени.