Средний возраст заявок: показатель, который никто не измеряет в GLPI

Средний возраст заявок показывает забытый бэклог, которого не видит среднее время решения. Узнайте, откуда берётся это число в GLPI, почему среднее вводит в заблуждение, как отделить возраст от застоя с помощью SQL и как автоматизировать еженедельный разбор бэклога.

В этой статьеРазделов: 8

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

Что измеряет средний возраст — и что он скрывает

Средний возраст заявок — это среднее число дней между созданием и сегодняшним днём, учитывающее только заявки, которые остаются открытыми. В отличие от среднего времени решения, которое видит только уже решённое, он фиксирует текущее состояние очереди. Проблема в том, что среднее само по себе — плохое обобщение реальной очереди: типичный бэклог бимодален — десятки новых заявок возрастом в несколько часов соседствуют с хвостом заявок, стоящих неделями. Масса новых заявок тянет среднее вниз и скрывает как раз старые, которые вам и нужно найти. Поэтому среднее никогда не используется в одиночку: его сопровождают распределение и медиана.

Откуда берётся это число в GLPI

Всё хранится в таблице glpi_tickets. Возраст использует столбец date (дата создания); выборка «открытых» использует status. Важные статусы:

  • 1 — Новая (ещё не отсортирована)
  • 2 — В работе (назначена)
  • 3 — В работе (запланирована)
  • 4 — В ожидании (ожидание третьей стороны)
  • 5 — Решена и 6 — Закрыта (вне бэклога)

Самая частая ошибка здесь — сваливать в одну кучу всё, что не 5 и не 6. Заявки в статусе В ожидании (статус 4) законно стоят в ожидании поставщика, клиента или запчасти — они раздувают средний возраст, хотя никто ничего не забыл. Сегментация по статусу — первый честный фильтр:

-- Average age by status (segment: "Pending" inflates the average)
SELECT
  status,
  ROUND(AVG(DATEDIFF(NOW(), date)), 1) AS avg_age_days,
  COUNT(*)                             AS total
FROM glpi_tickets
WHERE status NOT IN (5, 6)   -- exclude Solved and Closed
  AND is_deleted = 0
GROUP BY status
ORDER BY avg_age_days DESC;
-- status: 1 New, 2 assigned, 3 planned, 4 Pending

Возраст — не то же самое, что застой

Заявка, открытая 40 дней назад, но с задачей, зарегистрированной вчера, сильно отличается от заявки, открытой 40 дней назад, к которой никто не прикасался. Возраст (с момента создания) измеряет давность; застой (с момента последнего действия человека) измеряет заброшенность — и именно застой указывает на забытую заявку.

В сопровождении ошибкой, которая, как мы видели, обходится дорого, было использование date_mod в качестве «последнего действия». Столбец date_mod заявки обновляется чем угодно: отправленным уведомлением, добавленным наблюдателем, cron SLA, пересчитывающим срок, сработавшим бизнес-правилом. То есть он создаёт иллюзию движения в заявках, над которыми фактически никто не работал. Реальное последнее действие человека находится в комментариях и задачах — glpi_itilfollowups и glpi_tickettasks. Когда мы заменили date_mod на максимальную дату из этих двух таблиц, список застоявшихся заявок одного клиента вырос с 3 до 17: остальные 14 казались живыми только потому, что их каждый день задевала автоматизация.

-- Real staleness: days since the last human interaction
-- date_mod is unreliable (notifications, followers and cron bump it)
SELECT
  t.id,
  t.name,
  DATEDIFF(NOW(), t.date) AS age_days,
  DATEDIFF(NOW(), GREATEST(
      t.date,
      COALESCE(MAX(f.date), t.date),
      COALESCE(MAX(k.date), t.date)
  ))                       AS idle_days
FROM glpi_tickets t
LEFT JOIN glpi_itilfollowups f
       ON f.itemtype = 'Ticket' AND f.items_id = t.id
LEFT JOIN glpi_tickettasks k
       ON k.tickets_id = t.id
WHERE t.status NOT IN (4, 5, 6)   -- exclude Pending, Solved, Closed
  AND t.is_deleted = 0
GROUP BY t.id, t.name, t.date
HAVING idle_days >= 7
ORDER BY idle_days DESC;

Распределение, а не только среднее

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

-- Backlog distribution by age bracket
SELECT
  CASE
    WHEN DATEDIFF(NOW(), date) <= 3  THEN '0-3 days'
    WHEN DATEDIFF(NOW(), date) <= 7  THEN '3-7 days'
    WHEN DATEDIFF(NOW(), date) <= 15 THEN '7-15 days'
    WHEN DATEDIFF(NOW(), date) <= 30 THEN '15-30 days'
    ELSE '30+ days'
  END        AS bracket,
  COUNT(*)   AS tickets
FROM glpi_tickets
WHERE status NOT IN (4, 5, 6)
  AND is_deleted = 0
GROUP BY bracket
ORDER BY MIN(DATEDIFF(NOW(), date));

В качестве честного ориентира по диапазонам (календарные дни, не рабочие): меньше 3 дней — здорово, от 3 до 7 дней — приемлемо, от 7 до 15 дней — тревожный сигнал, а свыше 15 дней требует разбора каждой заявки по отдельности. Это не универсальные цели — справедливое значение зависит от договора и типа работы.

От числа к действию: матрица решений

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

Технический сигналКлассификацияДействие
status 1 (Новая) и возраст > 3 днейНе отсортированаНазначить ответственного/группу прямо сейчас
idle_days высокий, без внешнего наблюдателяЗабытаПереназначить и потребовать выполнения
status 4 (В ожидании) с просроченным ответомИстёкшая блокировкаНапомнить третьей стороне или переоткрыть
высокий возраст и низкий idle_daysОбоснованно (сложная)Пересмотреть SLA / объём работ
высокий возраст и повторяющаяся категорияКандидат в проблемуСоздать Проблему / статью базы знаний

Тихий случай, который стоит выделить: заявки, застрявшие в статусе Новая, потому что бизнес-правило назначения не совпало с категорией и так и не задало группу. Они стареют незаметно — пропадают из панелей по группам и появляются, только когда вы смотрите на средний возраст без фильтра по группе.

Автоматизация еженедельного разбора

Выполнять запрос вручную не масштабируется. Мы сохраняем запрос по застою в файл .sql и планируем его в системном cron, читая учётные данные из файла .cnf (chmod 600), чтобы никогда не оставлять пароль в командной строке. Результат — еженедельный CSV, который становится повесткой разбора бэклога:

# /etc/cron.d/glpi-idade  ->  every Monday 07:30, as www-data
# Builds the list of stale tickets (7+ days) for the backlog review
30 7 * * 1 www-data /usr/bin/mysql --defaults-file=/etc/glpi/kpi-ro.cnf glpi \
  < /opt/glpi-kpi/estagnados.sql \
  > /var/log/glpi-kpi/estagnados-$(date +\%Y-\%m-\%d).csv 2>&1

Типичные ошибки на практике

  • Смотреть только на среднее: оно сглаживает хвост. Всегда отслеживайте распределение и, по возможности, медиану.
  • Смешивать В ожидании с остальными: статус 4 — это законное ожидание; сам по себе он поднимает среднее и вызывает ложную тревогу.
  • Использовать date_mod как последнее действие: его обновляют автоматизации. Используйте даты комментариев и задач.
  • Сравнивать организации с разными календарями: DATEDIFF считает календарные дни; организации с разными графиками работы нельзя сравнивать напрямую.
  • Забывать is_deleted = 0: корзина GLPI не исчезает из таблицы и искажает любое среднее.

Следующий шаг

Объедините средний возраст с другими KPI службы поддержки (Service Desk) и соберите всё на панели в Metabase или Grafana с фильтром по организации и группе. Так возраст перестаёт быть числом в конце месяца и становится оповещением, которое срабатывает, пока ещё можно действовать.

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


Проверено командой NexTool Solutions.

Часто задаваемые вопросы

Это среднее число дней между датой создания (столбец date) и сегодняшним днём с учётом только открытых заявок (статус, отличный от 5 Решена и 6 Закрыта). Он фиксирует текущее состояние очереди, в отличие от среднего времени решения, которое измеряет только уже завершённые заявки.

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

Возраст — это время с момента создания; застой — время с последнего действия человека (комментария или задачи). Старая заявка, с которой работали вчера, не забыта; старая заявка без взаимодействия — забыта. Именно застой указывает на заброшенность.

Потому что date_mod обновляется уведомлениями, добавлением наблюдателей, cron SLA и бизнес-правилами — автоматическими действиями, которые не являются работой человека. Используйте максимальную дату в glpi_itilfollowups и glpi_tickettasks, чтобы узнать, когда к заявке действительно кто-то прикасался.

В качестве ориентира в календарных днях: меньше 3 дней — здорово, от 3 до 7 — приемлемо, от 7 до 15 — тревожный сигнал, а свыше 15 требует разбора каждой заявки по отдельности. Это не универсальная цель — всё зависит от договора, рабочего календаря и типа работы.

Не напрямую. Модуль Статистики помогает в разовых запросах, но не хранит исторические ряды и не отделяет возраст от застоя. Для этого используют SQL с пользователем только для чтения или панель в Metabase/Grafana, наполняемую такими запросами.

Нужна помощь?