Задания для групп хранятся по названию группы. Как обрабатывать переименование группы?

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

Мы создали отчёт, показывающий время (длительность) до повторного назначения.

Отчёт о времени до повторного назначения
-- цель: найти время от назначения группе
--    до закрытия темы / снятия назначения / повторного назначения подгруппе или исполнителю
--    Для постов, которые ещё не были переназначены, отсчёт продолжается,
--      поэтому используем текущую временную метку в разнице дат
--
-- ОБРАТИТЕ ВНИМАНИЕ: этот отчёт не включает ещё ожидающие назначения, сделанные до начала диапазона дат


-- [параметры]
-- date :start_date = 2023-01-01


WITH

-- найти посты назначения группе в диапазоне дат
group_assignment AS (
    SELECT p.topic_id
        , p.created_at as assign_date
        , pcf.value as group_name
        , post_number
    FROM posts p
    JOIN post_custom_fields pcf on pcf.post_id = p.id 
    WHERE p.action_code in ('assigned_group', 'reassigned_group')
        AND p.created_at >= :start_date AND p.created_at < :start_date::date + INTERVAL '90 day'
    ORDER BY topic_id
),

-- найти номер поста с наименьшим «изменением назначения» после назначения группе
assignment_change_post_number AS (
    SELECT min(p.post_number) as post_number
        , p.topic_id
        , ga.post_number as initial_assignment -- передаём дальше для использования в последующем соединении
    FROM posts p
    JOIN group_assignment ga ON ga.topic_id = p.topic_id AND ga.post_number < p.post_number
    WHERE p.action_code in ('reassigned_group', 'reassigned', 'unassigned', 'unassigned_group'
                            , 'assigned' -- вероятно, этот вариант здесь не появится, но на всякий случай...
                            , 'closed.enabled','autoclosed.enabled')  -- вероятно, избыточно с unassign*...
    GROUP BY p.topic_id, ga.post_number
),

-- найти дату изменения для номера поста
assignment_change_date AS (
    SELECT p.created_at as change_date
        , p.topic_id 
        , acpn.initial_assignment -- передаём дальше для использования в последующем соединении
    FROM posts p
    JOIN assignment_change_post_number acpn on p.topic_id=acpn.topic_id and p.post_number=acpn.post_number
),

-- выполнить расчёт даты между назначением и изменением назначения
date_math AS (
    SELECT ga.group_name
        -- для постов, всё ещё назначенных группе, используем текущую временную метку как «дату повторного назначения»
        , extract(epoch from (coalesce(acd.change_date, NOW()) - ga.assign_date)/86400) as days
    FROM group_assignment ga
    LEFT JOIN assignment_change_date acd on acd.topic_id = ga.topic_id 
        AND ga.post_number=acd.initial_assignment
)


SELECT group_name as "Команда"
    , count(*) as "Назначения"
    , round(avg(days)::numeric,2) as "Среднее кол-во дней до повторного назначения"
    , round(max(days)::numeric,2) as "Максимум"
    , round(min(days)::numeric,5) as "Минимум"
FROM date_math
GROUP BY group_name
ORDER BY "Среднее кол-во дней до повторного назначения" desc

Это работало хорошо, пока группы не начали переименовывать себя (по … причинам).

Поскольку записи о назначении потоков хранят названия групп — на момент назначения — а не идентификаторы групп, теперь в отчёте для некоторых групп появляется несколько строк (в зависимости от того, включает ли временной диапазон отчёта записи как до, так и после переименования).

Я хочу как объединить дублирующиеся записи, так и сделать этот отчёт устойчивым к будущим изменениям (простое жёсткое кодирование наборов синонимов здесь не подойдёт).

Существует ли где-то запись старых названий групп / переименований? Или … что-то ещё?

Так ли это? В исходной таблице assignment есть только идентификаторы, а не имена. Возможно, это побочный эффект вашего отчета DE, использующего CTE на основе имени?

@Falco, насколько я помню, таблица назначений хранит текущее состояние. Если мне нужны исторические данные (а они мне нужны), мне нужно обратиться к post_custom_fields, чтобы получить значение назначения для предыдущих состояний.

  SELECT p.topic_id
        , p.created_at as assign_date
        , pcf.value as group_name
        , post_number
    FROM posts p
    JOIN post_custom_fields pcf on pcf.post_id = p.id 
    WHERE p.action_code in ('assigned_group', 'reassigned_group')
        AND p.created_at >= :start_date AND p.created_at < :start_date::date + INTERVAL '90 day'
    ORDER BY topic_id

Или я смотрю не в то место?

О, теперь я понял: вам нужно отслеживать все исторические изменения статусов назначений! Это действительно сложная задача. Год назад один из клиентов заказал решение этой проблемы, поэтому я подготовил подробную спецификацию:

Это практически задача в два этапа.

Сначала нужно сохранять переходы состояний назначений в таблицу (новое назначение, изменение назначения — смена исполнителя или статуса, удаление назначения).

Затем использовать эти данные для создания нового представления — панели управления с графиками и таблицами.

Новые настройки

  • enable assign reports
    • тип: bool
    • значение по умолчанию: false

Новый интерфейс

Это будет отдельная страница, либо по адресу /admin/dashboard/assignments (если этот путь расширяемый), либо по адресу /admin/plugins/assign.

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

Сохранение переходов состояний назначений

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

Оценка трудозатрат

2,5–3 недели на полную реализацию с интерфейсом
1 неделя только на создание новой таблицы

К сожалению, клиент отложил эту работу в сторону до того, как мы успели приступить к ней, но это решение полностью закроет ваши потребности.

:joy:

Если не брать в расчет оплату месяца кастомной разработки, есть ли способ посмотреть предыдущие названия групп? :sweat_smile:

Думаю, они находятся в таблице group_histories, если это поможет?

Спасибо @JammyDodger! Я смотрел эту таблицу, но сделал вывод, что она касается членства на основе кодов действий.

Вы заставили меня посмотреть снова, и я обнаружил, что action=1 and subject='name' — это именно то, что мне нужно. Спасибо!

Обновленный отчет для всех заинтересованных лиц. Изменение заключается в добавлении запроса group_aliases (и его использования):

-- цель: найти время от назначения группы 
--    до закрытия темы / отмены назначения / повторного назначения подгруппе или отдельному лицу
--    Для постов, которые не были переназначены, отсчет времени продолжается, 
--      поэтому используйте текущую метку времени в разнице дат
--
-- ОБРАТИТЕ ВНИМАНИЕ, что в этом отчете опущены все еще ожидающие назначения, сделанные до начала диапазона дат


-- [параметры]
-- date :start_date = 2023-01-01


WITH

group_aliases AS (
    SELECT group_id,
            -- преобразуем столбцы в строки
            UNNEST (array[prev_value, new_value]) AS "alias"
        FROM group_histories 
        WHERE action = 1 AND subject='name'
    UNION
    SELECT id as group_id
            , name
        FROM groups
    
),
-- найти посты назначения группы в диапазоне дат
group_assignment AS (
    SELECT p.topic_id
        , p.created_at as assign_date
        , ga.group_id as group_id
        , post_number
    FROM posts p
    JOIN post_custom_fields pcf on pcf.post_id = p.id 
    JOIN group_aliases ga on pcf.value = ga.alias
    WHERE p.action_code in ('assigned_group', 'reassigned_group')
        AND p.created_at >= :start_date AND p.created_at < :start_date::date + INTERVAL '90 day'
    ORDER BY topic_id
),

-- найти номер поста с наименьшим изменением "назначения" после назначения группы
assignment_change_post_number AS (
    SELECT min(p.post_number) as post_number
        , p.topic_id
        , ga.post_number as initial_assignment -- передаем дальше для использования в последующем соединении
    FROM posts p
    JOIN group_assignment ga ON ga.topic_id = p.topic_id AND ga.post_number < p.post_number
    WHERE p.action_code in ('reassigned_group', 'reassigned', 'unassigned', 'unassigned_group'
                            , 'assigned' -- вероятно, этот вариант здесь не появится, но на всякий случай...
                            , 'closed.enabled','autoclosed.enabled')  -- вероятно, избыточно с unassign*...
    GROUP BY p.topic_id, ga.post_number
),

-- найти дату изменения для номера поста
assignment_change_date AS (
    SELECT p.created_at as change_date
        , p.topic_id 
        , acpn.initial_assignment -- передаем дальше для использования в последующем соединении
    FROM posts p
    JOIN assignment_change_post_number acpn on p.topic_id=acpn.topic_id and p.post_number=acpn.post_number
),

-- выполнить вычисления с датами между назначением и изменением назначения
date_math AS (
    SELECT ga.group_id as group_id
        -- для постов, все еще назначенных группе, используем текущую метку времени как "дату переназначения" 
        , extract(epoch from (coalesce(acd.change_date, NOW()) - ga.assign_date)/86400) as days
    FROM group_assignment ga
    LEFT JOIN assignment_change_date acd on acd.topic_id = ga.topic_id 
        AND ga.post_number=acd.initial_assignment
)

SELECT date_math.group_id
    , count(*) as "Assignments"
    , round(avg(days)::numeric,2) as "Avg days to reassignment"
    , round(max(days)::numeric,2) as "Max"
    , round(min(days)::numeric,5) as "Min"
FROM date_math
GROUP BY group_id
ORDER BY "Avg days to reassignment" desc