232 lines
10 KiB
SQL
232 lines
10 KiB
SQL
-- ============================================================
|
|
-- Materialized view : reporting.mv_interventions
|
|
-- Scope : interventions des 12 derniers mois
|
|
-- Refresh : via management command refresh_reporting_views (daily)
|
|
--
|
|
-- Pour recréer manuellement :
|
|
-- DROP MATERIALIZED VIEW IF EXISTS reporting.mv_interventions;
|
|
-- \i mv_interventions.sql
|
|
-- ============================================================
|
|
|
|
DROP MATERIALIZED VIEW IF EXISTS reporting.mv_interventions;
|
|
|
|
CREATE MATERIALIZED VIEW reporting.mv_interventions AS
|
|
WITH
|
|
-- ── Moment de passage à 'à traiter' ──────────────────────────────────────
|
|
tbe AS (
|
|
SELECT intervention_id, MIN(event_time) AS dt
|
|
FROM django.interventions_interventiontimeline
|
|
WHERE to_status = 'to_be_processed'
|
|
GROUP BY intervention_id
|
|
),
|
|
|
|
-- ── Premier passage à 'pris en charge' ───────────────────────────────────
|
|
first_assigned AS (
|
|
SELECT intervention_id, MIN(event_time) AS dt
|
|
FROM django.interventions_interventiontimeline
|
|
WHERE to_status = 'assigned'
|
|
GROUP BY intervention_id
|
|
),
|
|
|
|
-- ── Premier passage à 'démarré' ──────────────────────────────────────────
|
|
first_ip AS (
|
|
SELECT intervention_id, MIN(event_time) AS dt
|
|
FROM django.interventions_interventiontimeline
|
|
WHERE to_status = 'in_progress'
|
|
GROUP BY intervention_id
|
|
),
|
|
|
|
-- ── Premier passage à 'terminé' ──────────────────────────────────────────
|
|
first_finished AS (
|
|
SELECT intervention_id, MIN(event_time) AS dt
|
|
FROM django.interventions_interventiontimeline
|
|
WHERE to_status = 'finished'
|
|
GROUP BY intervention_id
|
|
),
|
|
|
|
-- ── Premier passage à 'en pause' ou 'terminé' ───────────────────────────
|
|
first_paused_or_finished AS (
|
|
SELECT intervention_id, MIN(event_time) AS dt
|
|
FROM django.interventions_interventiontimeline
|
|
WHERE to_status IN ('on_pause', 'finished')
|
|
GROUP BY intervention_id
|
|
),
|
|
|
|
-- ── Dernier passage à 'terminé' ──────────────────────────────────────────
|
|
last_finished AS (
|
|
SELECT intervention_id, MAX(event_time) AS dt
|
|
FROM django.interventions_interventiontimeline
|
|
WHERE to_status = 'finished'
|
|
GROUP BY intervention_id
|
|
),
|
|
|
|
-- ── Temps d'exécution : somme des segments actifs en 'démarré' ───────────
|
|
-- La n-ième entrée en in_progress est appariée à la n-ième sortie de in_progress
|
|
-- grâce au ROW_NUMBER, ce qui gère correctement les pauses successives.
|
|
ip_starts AS (
|
|
SELECT
|
|
intervention_id,
|
|
event_time,
|
|
ROW_NUMBER() OVER (PARTITION BY intervention_id ORDER BY event_time) AS rn
|
|
FROM django.interventions_interventiontimeline
|
|
WHERE to_status = 'in_progress'
|
|
),
|
|
ip_ends AS (
|
|
SELECT
|
|
intervention_id,
|
|
event_time,
|
|
ROW_NUMBER() OVER (PARTITION BY intervention_id ORDER BY event_time) AS rn
|
|
FROM django.interventions_interventiontimeline
|
|
WHERE from_status = 'in_progress'
|
|
),
|
|
exec_time AS (
|
|
SELECT
|
|
s.intervention_id,
|
|
ROUND(
|
|
SUM(EXTRACT(EPOCH FROM (e.event_time - s.event_time)) / 60.0)
|
|
)::INTEGER AS total_minutes
|
|
FROM ip_starts s
|
|
JOIN ip_ends e
|
|
ON e.intervention_id = s.intervention_id
|
|
AND e.rn = s.rn
|
|
AND e.event_time >= s.event_time
|
|
GROUP BY s.intervention_id
|
|
),
|
|
|
|
-- ── Première localisation associée à l'intervention ──────────────────────
|
|
-- On prend la première par id (ordre d'insertion).
|
|
first_loc AS (
|
|
SELECT DISTINCT ON (il.intervention_id)
|
|
il.intervention_id,
|
|
il.content_type_id AS loc_content_type_id,
|
|
il.object_id AS loc_object_id,
|
|
ct.app_label AS loc_app_label,
|
|
ct.model AS loc_model
|
|
FROM django.interventions_interventionlocation il
|
|
JOIN django.django_content_type ct ON ct.id = il.content_type_id
|
|
ORDER BY il.intervention_id, il.id
|
|
)
|
|
|
|
SELECT
|
|
-- ── Identité ─────────────────────────────────────────────────────────────
|
|
i.id,
|
|
i.code,
|
|
i.title,
|
|
i.lon,
|
|
i.lat,
|
|
|
|
-- ── Thématique ───────────────────────────────────────────────────────────
|
|
i.thematic_id,
|
|
t.name_fr AS thematic_name_fr,
|
|
t.name_nl AS thematic_name_nl,
|
|
|
|
-- ── Catégorie d'asset ────────────────────────────────────────────────────
|
|
i.asset_category_id,
|
|
ac.name_fr AS asset_category_name_fr,
|
|
ac.name_nl AS asset_category_name_nl,
|
|
|
|
-- ── Statut & priorité ────────────────────────────────────────────────────
|
|
i.status,
|
|
i.priority,
|
|
|
|
-- ── Catégorie source ─────────────────────────────────────────────────────
|
|
i.source_category_id,
|
|
sc.name_fr AS source_category_name_fr,
|
|
|
|
-- ── Contrat & ordre ──────────────────────────────────────────────────────
|
|
i.contract_id,
|
|
c.contract_number,
|
|
i.order_id,
|
|
i.created_by_provider,
|
|
|
|
-- ── Dimensions maintenance ───────────────────────────────────────────────
|
|
i.maintain_type AS maintenance_type,
|
|
i.origin_type,
|
|
i.type AS intervention_type,
|
|
|
|
-- ── Date de création ─────────────────────────────────────────────────────
|
|
i.creation_time,
|
|
|
|
-- ── Horodatages bruts des jalons (utiles pour calculs complémentaires) ───
|
|
tbe.dt AS dt_to_be_processed,
|
|
fa.dt AS dt_first_assigned,
|
|
fip.dt AS dt_first_inprogress,
|
|
ffin.dt AS dt_first_finished,
|
|
lfin.dt AS dt_last_finished,
|
|
|
|
-- ── Métriques de temps en minutes entières (NULL si statut non atteint) ──
|
|
|
|
-- Temps de prise en charge : 'à traiter' → 'pris en charge'
|
|
CASE
|
|
WHEN fa.dt IS NOT NULL AND tbe.dt IS NOT NULL
|
|
THEN ROUND(EXTRACT(EPOCH FROM (fa.dt - tbe.dt)) / 60.0)::INTEGER
|
|
END AS response_time_min,
|
|
|
|
-- Temps d'intervention : 'à traiter' → premier 'démarré'
|
|
CASE
|
|
WHEN fip.dt IS NOT NULL AND tbe.dt IS NOT NULL
|
|
THEN ROUND(EXTRACT(EPOCH FROM (fip.dt - tbe.dt)) / 60.0)::INTEGER
|
|
END AS time_to_inprogress_min,
|
|
|
|
-- Premier temps de réalisation : 'à traiter' → premier 'en pause' ou 'terminé'
|
|
CASE
|
|
WHEN fpof.dt IS NOT NULL AND tbe.dt IS NOT NULL
|
|
THEN ROUND(EXTRACT(EPOCH FROM (fpof.dt - tbe.dt)) / 60.0)::INTEGER
|
|
END AS first_resolution_min,
|
|
|
|
-- Temps de réalisation final : 'à traiter' → dernier 'terminé'
|
|
CASE
|
|
WHEN lfin.dt IS NOT NULL AND tbe.dt IS NOT NULL
|
|
THEN ROUND(EXTRACT(EPOCH FROM (lfin.dt - tbe.dt)) / 60.0)::INTEGER
|
|
END AS final_resolution_min,
|
|
|
|
-- Temps d'exécution : somme des durées actives en 'démarré'
|
|
exec_t.total_minutes AS execution_time_min,
|
|
|
|
-- ── Localisation (GenericFK décodée) ─────────────────────────────────────
|
|
fl.loc_content_type_id,
|
|
fl.loc_object_id,
|
|
fl.loc_app_label,
|
|
fl.loc_model
|
|
|
|
FROM django.interventions_intervention i
|
|
LEFT JOIN django.common_thematic t ON t.id = i.thematic_id
|
|
LEFT JOIN django.assets_assetcategory ac ON ac.id = i.asset_category_id
|
|
LEFT JOIN django.interventions_sourcecategory sc ON sc.id = i.source_category_id
|
|
LEFT JOIN django.contracts_contract c ON c.id = i.contract_id
|
|
LEFT JOIN tbe ON tbe.intervention_id = i.id
|
|
LEFT JOIN first_assigned fa ON fa.intervention_id = i.id
|
|
LEFT JOIN first_ip fip ON fip.intervention_id = i.id
|
|
LEFT JOIN first_finished ffin ON ffin.intervention_id = i.id
|
|
LEFT JOIN first_paused_or_finished fpof ON fpof.intervention_id = i.id
|
|
LEFT JOIN last_finished lfin ON lfin.intervention_id = i.id
|
|
LEFT JOIN exec_time exec_t ON exec_t.intervention_id = i.id
|
|
LEFT JOIN first_loc fl ON fl.intervention_id = i.id
|
|
|
|
WHERE (
|
|
SELECT MIN(tl.event_time)
|
|
FROM django.interventions_interventiontimeline tl
|
|
WHERE tl.intervention_id = i.id
|
|
) > NOW() - INTERVAL '12 months'
|
|
|
|
WITH DATA;
|
|
|
|
-- ── Index unique requis pour REFRESH MATERIALIZED VIEW CONCURRENTLY ─────────
|
|
CREATE UNIQUE INDEX reporting_mv_interventions_pk
|
|
ON reporting.mv_interventions (id);
|
|
|
|
-- ── Index opérationnels ──────────────────────────────────────────────────────
|
|
CREATE INDEX reporting_mv_interventions_creation
|
|
ON reporting.mv_interventions (creation_time);
|
|
|
|
CREATE INDEX reporting_mv_interventions_thematic
|
|
ON reporting.mv_interventions (thematic_id);
|
|
|
|
CREATE INDEX reporting_mv_interventions_status
|
|
ON reporting.mv_interventions (status);
|
|
|
|
CREATE INDEX reporting_mv_interventions_contract
|
|
ON reporting.mv_interventions (contract_id);
|
|
|
|
CREATE INDEX reporting_mv_interventions_loc
|
|
ON reporting.mv_interventions (loc_content_type_id, loc_object_id);
|