Условие
В службе доставки еды есть смены курьеров. Бизнес жалуется: «слишком высокий ФОТ при низкой производительности». Какие метрики утилизации курьеров вы бы предложили? Как декомпозировать «непродуктивное время»? Как искать неэффективные зоны / тайм-слоты?
Решение
Метрики утилизации
Hours-based (макроуровень):
utilization = active_minutes / on_shift_minutes— доля активного времени.orders_per_hour— доставок в час смены.idle_rate— доля простоя в зоне.revenue_per_courier_hour— выручка / час оплаченного времени.
Order-lifecycle (микроуровень):
dispatch_time— от размещения заказа до назначения курьеру.to_restaurant_time— путь курьера до ресторана.wait_at_restaurant— ожидание готовности заказа.to_client_time— путь к клиенту.delivery_time— общее время доставки.
Декомпозиция непродуктивного времени
on_shift_time = active_time + idle_time
active_time = travel_to_restaurant + wait_at_restaurant + travel_to_client + handover
idle_time = waiting_for_dispatch + break + tech_issues
«Тёплые точки» неэффективности:
- wait_at_restaurant — рестораны медленно готовят. Решение: SLA для ресторанов, отображение «слот готовности» при приёме заказа.
- waiting_for_dispatch — мало заказов в зоне в это время. Решение: перебрасывать курьеров в соседнюю зону или сокращать смену.
- dispatch_time — алгоритм назначения медленный или зоны нарезаны слишком крупно.
- travel_to_restaurant — рестораны далеко от dispatch-точки → нужно перераспределить зоны.
SQL для расчёта
WITH shift_minutes AS (
SELECT courier_id,
shift_id,
SUM(EXTRACT(EPOCH FROM (end_ts - start_ts)) / 60) AS shift_min
FROM courier_shifts
GROUP BY 1, 2
),
order_minutes AS (
SELECT courier_id, shift_id,
SUM(EXTRACT(EPOCH FROM (delivered_at - accepted_at)) / 60) AS active_min,
COUNT(*) AS n_orders,
AVG(EXTRACT(EPOCH FROM (picked_up_at - arrived_at_resto)) / 60) AS avg_wait_resto
FROM orders
GROUP BY 1, 2
)
SELECT
s.courier_id, s.shift_id, s.shift_min,
COALESCE(o.active_min, 0) AS active_min,
COALESCE(o.active_min, 0) / s.shift_min AS utilization,
COALESCE(o.n_orders, 0) AS n_orders,
s.shift_min - COALESCE(o.active_min, 0) AS idle_min,
o.avg_wait_resto
FROM shift_minutes s
LEFT JOIN order_minutes o USING (courier_id, shift_id);Heatmap «зона × час» для неэффективности
SELECT zone_id,
EXTRACT(HOUR FROM accepted_at) AS hr,
AVG(EXTRACT(EPOCH FROM (delivered_at - placed_at)) / 60) AS avg_delivery_min,
COUNT(*) AS n_orders,
SUM(CASE WHEN wait_at_resto_min > 10 THEN 1 ELSE 0 END) * 1.0 / COUNT(*) AS pct_long_wait
FROM orders
WHERE accepted_at >= CURRENT_DATE - 30
GROUP BY 1, 2;→ Видно, в каких зонах и часах рестораны тормозят и где «дыра» с курьерами.
Что НЕ делать
- Не оптимизировать только utilization — высокая утилизация может означать пере-нагрузку и выгорание / уход курьеров.
- Не сводить к одной метрике — нужен баланс utilization × CSAT × SLA delivery time.
Прогноз спроса для shift planning
# простой baseline: avg orders / (zone, hour, dow) с поправкой на weather/holidays
import pandas as pd
hist = pd.read_sql(...)
baseline = hist.groupby(['zone_id','hour','dow'])['orders'].mean().reset_index()
# +ridge / xgboost с фичами weather, events для уточненияПодводные камни
- «Utilization 90%» — почти всегда плохо. Маленькая буферность → каждый incident ломает SLA.
active_timeбез правильного определения: «работа с заказом» начинается с accept или с pickup? Согласовать.avg_delivery_timeобманчив: одна 60-минутная доставка ломает среднее. Использовать p90 / median.- Зоны нарезаны вручную — часто устарели; A/B-тесты на нарезке.
- «Низкая утилизация всегда плохо» — не всегда: low utilization + быстрый SLA = высокая CSAT. Зависит от стратегии.
- Привязка к расценкам: курьер на час vs на заказ — оптимизация разная.
- Smoothing нулей: курьер без заказов в смене может быть не «бесполезным», а сидеть в пустой зоне. Винить алгоритм dispatch, не курьера.
Эталонный ответ
Метрики: utilization, orders/hour, idle_rate, time-of-cycle декомпозиция (travel_to_resto, wait_at_resto, travel_to_client). Декомпозируем simple total cost = on_shift_time × hourly_rate; неэффективность — в waiting_at_resto (SLA рестораны) и idle (плохой dispatch / нарезка зон). Решения: SLA для ресторанов, перенарезка зон, динамическое управление сменами на основе прогноза спроса.
Главный принцип: utilization 90%+ часто = плохой UX и выгорание. Баланс utilization × SLA × CSAT, не максимизация одной.