chore: import coordenador (subtree)

git-subtree-dir: coordenador
git-subtree-mainline: facbe2e340
git-subtree-split: ce7af94d55
This commit is contained in:
Gustavo Santos
2026-10-01 14:25:55 -03:00
72 changed files with 8913 additions and 0 deletions
+82
View File
@@ -0,0 +1,82 @@
-- Candidatos V2: mesma base/exclusao do V1, sem corte em blocos.
-- Parametros substituidos pelo node: {DIAS_SEM_INTERACAO_ALERTA} {HORAS_SLA_RISCO} {GLPI_BASE_URL} {CANDIDATE_POOL_SIZE}
WITH ultima_interacao AS (
SELECT tickets_id, MAX(data_interacao) AS ultima_interacao_em
FROM (
SELECT tickets_id, MAX(data_interacao) AS data_interacao
FROM (
SELECT items_id AS tickets_id, date AS data_interacao FROM glpi_itilfollowups WHERE itemtype = 'Ticket' AND is_private = 0
UNION ALL
SELECT items_id AS tickets_id, date_mod AS data_interacao FROM glpi_itilfollowups WHERE itemtype = 'Ticket' AND is_private = 0
UNION ALL
SELECT items_id AS tickets_id, date_creation AS data_interacao FROM glpi_itilfollowups WHERE itemtype = 'Ticket' AND is_private = 0
) followup_datas
WHERE data_interacao IS NOT NULL
GROUP BY tickets_id
UNION ALL
SELECT tickets_id, MAX(data_interacao) AS data_interacao
FROM (
SELECT tickets_id, date AS data_interacao FROM glpi_tickettasks
UNION ALL
SELECT tickets_id, date_mod AS data_interacao FROM glpi_tickettasks
UNION ALL
SELECT tickets_id, date_creation AS data_interacao FROM glpi_tickettasks
) task_datas
WHERE data_interacao IS NOT NULL
GROUP BY tickets_id
) interacoes
GROUP BY tickets_id
), tecnicos AS (
SELECT tu.tickets_id,
GROUP_CONCAT(TRIM(CONCAT(COALESCE(u.firstname, ''), ' ', COALESCE(u.realname, u.name))) SEPARATOR ', ') AS tecnicos
FROM glpi_tickets_users tu
LEFT JOIN glpi_users u ON u.id = tu.users_id
WHERE tu.type = 2
GROUP BY tu.tickets_id
), chamados_base AS (
SELECT
t.id, t.name AS titulo, t.content AS descricao, t.status,
CASE t.status WHEN 1 THEN 'Novo' WHEN 2 THEN 'Em atendimento' WHEN 3 THEN 'Planejado' WHEN 4 THEN 'Pendente' ELSE CONCAT('Status ', t.status) END AS status_nome,
t.date AS aberto_em, t.date_mod AS modificado_em, t.time_to_resolve AS sla_resolver_ate,
t.priority, t.urgency, t.impact,
COALESCE(e.completename, e.name, 'Nao informado') AS entidade,
COALESCE(c.completename, c.name, 'Sem categoria') AS categoria,
COALESCE(tecnicos.tecnicos, 'Sem tecnico atribuido') AS tecnico_atribuido,
GREATEST(COALESCE(ultima_interacao.ultima_interacao_em, '1970-01-01 00:00:00'), COALESCE(t.date_mod, '1970-01-01 00:00:00'), COALESCE(t.date, '1970-01-01 00:00:00')) AS ultima_interacao_em,
TIMESTAMPDIFF(DAY, t.date, NOW()) AS dias_aberto,
GREATEST(TIMESTAMPDIFF(DAY, GREATEST(COALESCE(ultima_interacao.ultima_interacao_em, '1970-01-01 00:00:00'), COALESCE(t.date_mod, '1970-01-01 00:00:00'), COALESCE(t.date, '1970-01-01 00:00:00')), NOW()), 0) AS dias_sem_interacao,
TIMESTAMPDIFF(HOUR, NOW(), t.time_to_resolve) AS horas_ate_sla,
CASE WHEN t.time_to_resolve IS NOT NULL AND t.time_to_resolve < NOW() THEN 1 ELSE 0 END AS sla_vencido,
CASE WHEN t.time_to_resolve IS NOT NULL AND t.time_to_resolve >= NOW() AND TIMESTAMPDIFF(HOUR, NOW(), t.time_to_resolve) <= {HORAS_SLA_RISCO} THEN 1 ELSE 0 END AS sla_em_risco,
CASE WHEN tecnicos.tecnicos IS NULL THEN 1 ELSE 0 END AS sem_tecnico,
CONCAT('{GLPI_BASE_URL}', '/front/ticket.form.php?id=', t.id) AS link_glpi
FROM glpi_tickets t
LEFT JOIN glpi_entities e ON e.id = t.entities_id
LEFT JOIN glpi_itilcategories c ON c.id = t.itilcategories_id
LEFT JOIN ultima_interacao ON ultima_interacao.tickets_id = t.id
LEFT JOIN tecnicos ON tecnicos.tickets_id = t.id
WHERE t.is_deleted = false
AND t.status NOT IN (5, 6)
AND COALESCE(c.completename, c.name, '') NOT LIKE 'ADMINISTRATIVO%'
AND COALESCE(c.completename, c.name, '') NOT LIKE 'DESENVOLVIMENTO%'
AND COALESCE(c.completename, c.name, '') NOT LIKE 'INFRA%'
), chamados_ranked AS (
SELECT
id, titulo, descricao, status, status_nome, aberto_em, modificado_em, ultima_interacao_em,
dias_aberto, dias_sem_interacao, sla_resolver_ate, horas_ate_sla, sla_vencido, sla_em_risco,
priority AS prioridade, urgency AS urgencia, impact AS impacto,
entidade, categoria, tecnico_atribuido, sem_tecnico, link_glpi,
(
CASE WHEN sla_vencido = 1 THEN 100 ELSE 0 END
+ CASE WHEN sla_em_risco = 1 THEN 45 ELSE 0 END
+ CASE WHEN dias_sem_interacao >= 7 THEN 35 WHEN dias_sem_interacao >= 3 THEN 25 WHEN dias_sem_interacao >= {DIAS_SEM_INTERACAO_ALERTA} THEN 10 ELSE 0 END
+ LEAST(dias_aberto, 30)
+ (priority * 8) + (urgency * 5) + (impact * 5)
+ CASE WHEN sem_tecnico = 1 THEN 25 ELSE 0 END
) AS score_criticidade
FROM chamados_base
)
SELECT * FROM chamados_ranked
ORDER BY score_criticidade DESC, sla_vencido DESC, dias_sem_interacao DESC, dias_aberto DESC
LIMIT {CANDIDATE_POOL_SIZE};
+17
View File
@@ -0,0 +1,17 @@
-- Contexto (followups publicos + tarefas) dos candidatos, numa unica query
-- para garantir que o node de merge so roda depois que os dois tipos ja chegaram.
-- IDs interpolados pelo node como lista de inteiros (nunca string crua do GLPI).
-- Coluna de status de glpi_tickettasks (state) a confirmar via DESCRIBE quando a
-- credencial MySQL estiver disponivel (Task 14) -- ver nota no plano.
SELECT 'followup' AS tipo, items_id AS tickets_id, id, date, content AS texto, NULL AS status
FROM glpi_itilfollowups
WHERE itemtype = 'Ticket' AND is_private = 0 AND items_id IN ({IDS})
UNION ALL
SELECT 'task' AS tipo, tickets_id, id, date, content AS texto, state AS status
FROM glpi_tickettasks
WHERE tickets_id IN ({IDS})
ORDER BY tickets_id, date ASC;
@@ -0,0 +1,213 @@
-- Top 10 chamados abertos por blocos operacionais.
-- Blocos:
-- - 3 chamados com SLA vencido mais criticos
-- - 3 chamados com maior tempo sem interacao
-- - 2 chamados sem tecnico atribuido
-- - 2 chamados de prioridade alta mais recentes
-- Se faltar item em algum bloco, o complemento vem do ranking geral.
--
-- Ajuste os parametros abaixo no workflow n8n:
-- {DIAS_SEM_INTERACAO_ALERTA}
-- {HORAS_SLA_RISCO}
-- {GLPI_BASE_URL}
WITH ultima_interacao AS (
SELECT
tickets_id,
MAX(data_interacao) AS ultima_interacao_em
FROM (
SELECT tickets_id, MAX(data_interacao) AS data_interacao
FROM (
SELECT items_id AS tickets_id, date AS data_interacao
FROM glpi_itilfollowups
WHERE itemtype = 'Ticket'
AND is_private = 0
UNION ALL
SELECT items_id AS tickets_id, date_mod AS data_interacao
FROM glpi_itilfollowups
WHERE itemtype = 'Ticket'
AND is_private = 0
UNION ALL
SELECT items_id AS tickets_id, date_creation AS data_interacao
FROM glpi_itilfollowups
WHERE itemtype = 'Ticket'
AND is_private = 0
) followup_datas
WHERE data_interacao IS NOT NULL
GROUP BY tickets_id
UNION ALL
SELECT tickets_id, MAX(data_interacao) AS data_interacao
FROM (
SELECT tickets_id, date AS data_interacao
FROM glpi_tickettasks
UNION ALL
SELECT tickets_id, date_mod AS data_interacao
FROM glpi_tickettasks
UNION ALL
SELECT tickets_id, date_creation AS data_interacao
FROM glpi_tickettasks
) task_datas
WHERE data_interacao IS NOT NULL
GROUP BY tickets_id
) interacoes
GROUP BY tickets_id
), tecnicos AS (
SELECT
tu.tickets_id,
GROUP_CONCAT(TRIM(CONCAT(COALESCE(u.firstname, ''), ' ', COALESCE(u.realname, u.name))) SEPARATOR ', ') AS tecnicos
FROM glpi_tickets_users tu
LEFT JOIN glpi_users u ON u.id = tu.users_id
WHERE tu.type = 2
GROUP BY tu.tickets_id
), chamados_base AS (
SELECT
t.id,
t.name AS titulo,
t.status,
CASE t.status
WHEN 1 THEN 'Novo'
WHEN 2 THEN 'Em atendimento'
WHEN 3 THEN 'Planejado'
WHEN 4 THEN 'Pendente'
ELSE CONCAT('Status ', t.status)
END AS status_nome,
t.date AS aberto_em,
t.date_mod AS modificado_em,
t.time_to_resolve AS sla_resolver_ate,
t.priority,
t.urgency,
t.impact,
COALESCE(e.completename, e.name, 'Nao informado') AS entidade,
COALESCE(c.completename, c.name, 'Sem categoria') AS categoria,
COALESCE(tecnicos.tecnicos, 'Sem tecnico atribuido') AS tecnico_atribuido,
GREATEST(
COALESCE(ultima_interacao.ultima_interacao_em, '1970-01-01 00:00:00'),
COALESCE(t.date_mod, '1970-01-01 00:00:00'),
COALESCE(t.date, '1970-01-01 00:00:00')
) AS ultima_interacao_em,
TIMESTAMPDIFF(DAY, t.date, NOW()) AS dias_aberto,
GREATEST(TIMESTAMPDIFF(DAY, GREATEST(
COALESCE(ultima_interacao.ultima_interacao_em, '1970-01-01 00:00:00'),
COALESCE(t.date_mod, '1970-01-01 00:00:00'),
COALESCE(t.date, '1970-01-01 00:00:00')
), NOW()), 0) AS dias_sem_interacao,
TIMESTAMPDIFF(HOUR, NOW(), t.time_to_resolve) AS horas_ate_sla,
CASE WHEN t.time_to_resolve IS NOT NULL AND t.time_to_resolve < NOW() THEN 1 ELSE 0 END AS sla_vencido,
CASE
WHEN t.time_to_resolve IS NOT NULL
AND t.time_to_resolve >= NOW()
AND TIMESTAMPDIFF(HOUR, NOW(), t.time_to_resolve) <= {HORAS_SLA_RISCO} THEN 1
ELSE 0
END AS sla_em_risco,
CASE WHEN tecnicos.tecnicos IS NULL THEN 1 ELSE 0 END AS sem_tecnico,
CONCAT('{GLPI_BASE_URL}', '/front/ticket.form.php?id=', t.id) AS link_glpi
FROM glpi_tickets t
LEFT JOIN glpi_entities e ON e.id = t.entities_id
LEFT JOIN glpi_itilcategories c ON c.id = t.itilcategories_id
LEFT JOIN ultima_interacao ON ultima_interacao.tickets_id = t.id
LEFT JOIN tecnicos ON tecnicos.tickets_id = t.id
WHERE t.is_deleted = false
AND t.status NOT IN (5, 6)
AND COALESCE(c.completename, c.name, '') NOT LIKE 'ADMINISTRATIVO%'
AND COALESCE(c.completename, c.name, '') NOT LIKE 'DESENVOLVIMENTO%'
AND COALESCE(c.completename, c.name, '') NOT LIKE 'INFRA%'
), chamados_ranked AS (
SELECT
id,
titulo,
status_nome,
aberto_em,
modificado_em,
ultima_interacao_em,
dias_aberto,
dias_sem_interacao,
sla_resolver_ate,
horas_ate_sla,
sla_vencido,
sla_em_risco,
priority AS prioridade,
urgency AS urgencia,
impact AS impacto,
entidade,
categoria,
tecnico_atribuido,
sem_tecnico,
link_glpi,
(
CASE WHEN sla_vencido = 1 THEN 100 ELSE 0 END
+ CASE WHEN sla_em_risco = 1 THEN 45 ELSE 0 END
+ CASE
WHEN dias_sem_interacao >= 7 THEN 35
WHEN dias_sem_interacao >= 3 THEN 25
WHEN dias_sem_interacao >= {DIAS_SEM_INTERACAO_ALERTA} THEN 10
ELSE 0
END
+ LEAST(dias_aberto, 30)
+ (priority * 8)
+ (urgency * 5)
+ (impact * 5)
+ CASE WHEN sem_tecnico = 1 THEN 25 ELSE 0 END
) AS score_criticidade
FROM chamados_base
), sla_top AS (
SELECT *, 'SLA vencido' AS bloco_origem, 1 AS bloco_ordem
FROM chamados_ranked
WHERE sla_vencido = 1
ORDER BY score_criticidade DESC, dias_sem_interacao DESC, dias_aberto DESC
LIMIT 3
), sem_interacao_top AS (
SELECT *, 'Sem interacao' AS bloco_origem, 2 AS bloco_ordem
FROM chamados_ranked
WHERE id NOT IN (SELECT id FROM sla_top)
ORDER BY dias_sem_interacao DESC, score_criticidade DESC, dias_aberto DESC
LIMIT 3
), sem_tecnico_top AS (
SELECT *, 'Sem tecnico' AS bloco_origem, 3 AS bloco_ordem
FROM chamados_ranked
WHERE sem_tecnico = 1
AND id NOT IN (SELECT id FROM sla_top)
AND id NOT IN (SELECT id FROM sem_interacao_top)
ORDER BY score_criticidade DESC, dias_sem_interacao DESC, dias_aberto DESC
LIMIT 2
), prioridade_alta_recente_top AS (
SELECT *, 'Prioridade alta recente' AS bloco_origem, 4 AS bloco_ordem
FROM chamados_ranked
WHERE prioridade >= 4
AND id NOT IN (SELECT id FROM sla_top)
AND id NOT IN (SELECT id FROM sem_interacao_top)
AND id NOT IN (SELECT id FROM sem_tecnico_top)
ORDER BY aberto_em DESC, score_criticidade DESC
LIMIT 2
), selecionados AS (
SELECT * FROM sla_top
UNION ALL
SELECT * FROM sem_interacao_top
UNION ALL
SELECT * FROM sem_tecnico_top
UNION ALL
SELECT * FROM prioridade_alta_recente_top
), complemento AS (
SELECT *, 'Ranking geral' AS bloco_origem, 5 AS bloco_ordem
FROM chamados_ranked
WHERE id NOT IN (SELECT id FROM selecionados)
ORDER BY score_criticidade DESC, sla_vencido DESC, dias_sem_interacao DESC, dias_aberto DESC
LIMIT 10
)
SELECT *
FROM (
SELECT * FROM selecionados
UNION ALL
SELECT * FROM complemento
) ranking_final
ORDER BY bloco_ordem ASC, score_criticidade DESC, dias_sem_interacao DESC, dias_aberto DESC
LIMIT 10;