# Leasing

{% hint style="warning" %}
Activer **IoT Query** avant d'utiliser les données pour construire des analyses complètes. Si vous ne l'avez pas encore, contactez‑nous pour les détails d'activation - <iotquery@navixy.com>
{% endhint %}

Les sociétés de leasing (en particulier les banques et les fournisseurs de location longue durée) conservent la propriété du véhicule ou de l'équipement tandis que le client n'en loue que l'utilisation, elles supportent donc le risque lié à l'actif pendant toute la durée du contrat. 

Pour protéger la valeur résiduelle, faire respecter les limites contractuelles (kilométrage, géographie, maintenance) et rationaliser les obligations de service complet, elles s'appuient sur Navixy. Les données GPS en temps réel, le diagnostic basé sur les capteurs et l'analyse comportementale leur permettent de vérifier les conditions d'utilisation, d'automatiser la planification des services, de détecter tôt les problèmes mécaniques, de calculer les pénalités ou frais d'excès de kilomètres et, si nécessaire, d'immobiliser ou de récupérer l'actif — ce qui sécurise leur investissement, réduit les coûts opérationnels et améliore la transparence pour le client tout au long du cycle de leasing.

Navixy **IoT Query** participera à l'organisation de tout type d'analytique à chaque étape du contrat de leasing. Un contrat de leasing traverse plusieurs phases prévisibles : Intégration & Configuration de l'actif → Phase opérationnelle → Supervision du risque et de la conformité

Les recettes SQL suivantes dans votre livre surveillent collectivement chaque jalon critique tout au long de ce cycle de vie :

| Phase du cycle de vie                                 | Objectifs & jalons                                                                                                                                          | Cas d'utilisation couverts / Recettes                                                                                                                                                                                                                |
| ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Intégration & configuration de l'actif                | • Enregistrer le véhicule, activer l'assurance et les identifiants du conducteur. • Importer les actifs dans le portail client avec la visibilité correcte. | Alertes d'expiration d'immatriculation/assurance – dates de référence capturées. Expiration du permis de conduire – valide les conducteurs avant mise en service.                                                                                    |
| Planification de la maintenance préventive            | • Établir des calendriers de service récurrents basés sur le kilométrage et le temps. • Garantir les changements saisonniers de pneus.                      | Inspections de routine par intervalle – tâches pilotées par calendrier. Service par seuil de kilométrage – règles de maintenance mineure/majeure basées sur les km. Surveillance des heures moteur – service basé sur les heures pour la machinerie. |
| Limites d'utilisation liées au contrat                | • Faire respecter les allocations de kilométrage et les plafonds financiers. • Détecter le sur‑usage tôt pour éviter les surprises en fin de contrat.       | Plafond de kilométrage & pénalités – contrôle annuel / total du contrat des kilomètres.                                                                                                                                                              |
| Comportement en temps réel du conducteur & de l'actif | • Protéger la valeur de l'actif ; former les conducteurs. • Repérer les mauvais usages qui annulent la couverture « full‑service ».                         | Freinages brusques. Accélérations soudaines. Virages/prise de courbe brusques.                                                                                                                                                                       |
| Supervision du risque & de la conformité              | • Maintenir les actifs à l'intérieur des limites géographiques et contractuelles. • Conserver le droit de désactiver ou de récupérer.                       | Sortie de géofence (frontière nationale) – alerte instantanée en cas de violation de territoire. Détection d'allumage & d'inactivité – suivi du gaspillage de carburant / mauvais usage.                                                             |

### Modèle de tableau de bord

Bien que les recettes SQL ci‑dessous fournissent un contrôle complet sur l'analytique du leasing, vous pouvez démarrer plus rapidement avec un tableau de bord préconstruit qui visualise les métriques critiques tout au long du cycle de leasing. Le modèle évite de construire des requêtes et des visualisations depuis zéro. Importez‑le, ajustez les paramètres et commencez à surveiller la conformité, le risque et la protection des actifs immédiatement.

Le modèle couvre les principaux flux de travail de leasing : suivi des expirations d'immatriculation et d'assurance, surveillance des permis de conduire, détection des freinages et accélérations brusques avec classifications de gravité, analyse du temps d'inactivité et surveillance de l'activité des appareils.

<figure><img src="https://504457471-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoFNFEIINiGFbhi3Px3dE%2Fuploads%2FqszX88nbhkjswe0Q3JJx%2Fimage.png?alt=media&#x26;token=1163380a-e669-4e4e-9666-1b13bc802aa5" alt=""><figcaption></figcaption></figure>

Importer la configuration dans [Dashboard Studio](https://marketplace.navixy.com/shop/dashboard-studio/), ajustez les seuils pour vos contrats (plafonds de kilométrage, niveaux de gravité des comportements, paramètres de détection d'inactivité) et déployez un espace de surveillance complet. Ceci est utile lorsque les équipes ont besoin de tableaux de bord opérationnels pour la conformité et la supervision des risques au quotidien sans écrire de SQL.

**Prérequis :**

* IoT Query activé dans votre environnement
* Dashboard Studio installé et accessible
* Au moins 72 heures de données de suivi
* Tables du schéma standard remplies : `tracking_data_core`, `states`, `objects`, `vehicles`, `employees`

**Configuration après importation :**

Après l'importation du modèle, adaptez‑le à vos contrats de leasing spécifiques et à vos seuils opérationnels :

1. Vérifiez la plage de temps par défaut de 72 heures et ajustez si votre disponibilité de données diffère.
2. Définissez les seuils de gravité pour les événements de conduite dans les paramètres de requête (par défaut : 60+ km/h/s pour avertissement, 80+ km/h/s pour alertes critiques).
3. Configurez les paramètres de détection d'inactivité (par défaut : vitesse inférieure à 5 km/h, durée minimale 5 minutes).
4. Mettez à jour les étiquettes de zone de géofence dans la requête de franchissement de frontière si vous surveillez des restrictions territoriales.
5. Utilisez le sélecteur de temps global pour analyser des périodes historiques ou vous concentrer sur l'activité récente.

**JSON du modèle :**

{% file src="<https://504457471-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoFNFEIINiGFbhi3Px3dE%2Fuploads%2F22FioPPyhxRlqYT1rCFm%2FLeasing%20Dashboard-schema.json?alt=media&token=0adb5715-ed43-4882-b82b-8d09b979164b>" %}

{% code lineNumbers="true" expandable="true" %}

```json
{
  "id": null,
  "uid": "hello-world",
  "tags": [
    "example",
    "getting-started"
  ],
  "time": {
    "to": "now",
    "from": "now-72h"
  },
  "links": [],
  "style": "dark",
  "title": "Tableau de bord Leasing",
  "panels": [
    {
      "id": 10,
      "type": "text",
      "title": "Détection d'allumage & d'inactivité",
      "gridPos": {
        "h": 4,
        "w": 24,
        "x": 0,
        "y": 158
      },
      "options": {
        "mode": "markdown",
        "content": " "
      },
      "x-navixy": {
        "sql": {
          "statement": ""
        },
        "dataset": {
          "shape": "table",
          "columns": {}
        }
      }
    },
    {
      "id": 11,
      "type": "piechart",
      "title": "Conducteurs avec dates d'expiration prochaines",
      "gridPos": {
        "h": 12,
        "w": 12,
        "x": 12,
        "y": 0
      },
      "options": {
        "pieType": "donut"
      },
      "x-navixy": {
        "sql": {
          "params": {},
          "statement": "SELECT \r\n    CASE \r\n        WHEN (DATE(e.driver_license_valid_till) - CURRENT_DATE)::INTEGER < 0 THEN 'Expired'\r\n        ELSE 'Others'\r\n    END AS category,\r\n    COUNT(*) AS value\r\nFROM raw_business_data.employees e\r\nWHERE e.driver_license_valid_till IS NOT NULL\r\nGROUP BY category\r\nORDER BY category"
        },
        "verify": {
          "max_rows": 1000
        },
        "dataset": {
          "shape": "pie",
          "columns": {
            "value": {
              "type": "integer"
            },
            "category": {
              "type": "string"
            }
          }
        }
      }
    },
    {
      "id": 12,
      "type": "piechart",
      "title": "Véhicules avec dates d'expiration prochaines",
      "gridPos": {
        "h": 12,
        "w": 12,
        "x": 0,
        "y": 0
      },
      "options": {
        "pieType": "donut"
      },
      "x-navixy": {
        "sql": {
          "params": {},
          "statement": "WITH expiry_data AS (\r\n    SELECT \r\n        CASE \r\n            WHEN v.free_insurance_valid_till_date IS NOT NULL\r\n                AND (v.liability_insurance_valid_till IS NULL \r\n                      OR v.free_insurance_valid_till_date <= v.liability_insurance_valid_till)\r\n                THEN v.free_insurance_valid_till_date\r\n            WHEN v.liability_insurance_valid_till IS NOT NULL\r\n                THEN v.liability_insurance_valid_till\r\n            ELSE NULL\r\n        END AS nearest_expiry_date\r\n    FROM raw_business_data.vehicles v\r\n),\r\ncategorized_data AS (\r\n    SELECT \r\n        CASE \r\n            WHEN (DATE(nearest_expiry_date) - CURRENT_DATE)::INTEGER < 0 THEN 'Expired'\r\n            WHEN (DATE(nearest_expiry_date) - CURRENT_DATE)::INTEGER >= 0 \r\n                  AND (DATE(nearest_expiry_date) - CURRENT_DATE)::INTEGER < 30 THEN 'Expires within 30 days'\r\n            ELSE 'Others'\r\n        END AS category\r\n    FROM expiry_data\r\n    WHERE nearest_expiry_date IS NOT NULL\r\n)\r\nSELECT \r\n    category,\r\n    COUNT(*) AS value\r\nFROM categorized_data\r\nGROUP BY category\r\nORDER BY \r\n    CASE category\r\n        WHEN 'Expired' THEN 1\r\n        WHEN 'Expires within 30 days' THEN 2\r\n        WHEN 'Others' THEN 3\r\n    END"
        },
        "verify": {
          "max_rows": 1000
        },
        "dataset": {
          "shape": "pie",
          "columns": {
            "value": {
              "type": "integer"
            },
            "category": {
              "type": "string"
            }
          }
        }
      }
    },
    {
      "id": 13,
      "type": "barchart",
      "title": "Événements de freinage brusque",
      "gridPos": {
        "h": 11,
        "w": 24,
        "x": 0,
        "y": 12
      },
      "options": {
        "textMode": "auto"
      },
      "x-navixy": {
        "sql": {
          "params": {},
          "statement": "WITH spd AS (\r\n          SELECT\r\n            device_id,\r\n            device_time,\r\n            speed/100.0 AS kmh,\r\n            LAG(speed/100.0) OVER (PARTITION BY device_id ORDER BY device_time) AS prev_kmh,\r\n            EXTRACT(EPOCH FROM (device_time - LAG(device_time) OVER (PARTITION BY device_id ORDER BY device_time))) AS dt_sec\r\n          FROM\r\n            raw_telematics_data.tracking_data_core\r\n          WHERE 1=1\r\n        ),\r\n        decels AS (\r\n          SELECT\r\n            device_id,\r\n            device_time,\r\n            (prev_kmh - kmh) / NULLIF(dt_sec, 0) AS decel_kmh_per_sec\r\n          FROM\r\n            spd\r\n          WHERE\r\n            prev_kmh IS NOT NULL\r\n        ),\r\n        events_with_severity AS (\r\n          SELECT \r\n            DATE(d.device_time) AS category,\r\n            CASE \r\n              WHEN d.decel_kmh_per_sec >= 80 THEN 'Critical'\r\n              WHEN d.decel_kmh_per_sec >= 60 THEN 'Warning'\r\n              ELSE 'Normal'\r\n            END AS series\r\n          FROM decels d\r\n          WHERE d.decel_kmh_per_sec >= 60\r\n        )\r\n        SELECT \r\n          category,\r\n          COUNT(*) AS value,\r\n          series\r\n        FROM events_with_severity\r\n        GROUP BY category, series\r\n        ORDER BY series"
        },
        "verify": {
          "max_rows": 1000
        },
        "dataset": {
          "shape": "category_value",
          "columns": {
            "value": {
              "type": "integer"
            },
            "series": {
              "type": "string"
            },
            "category": {
              "type": "date"
            }
          }
        },
        "visualization": {
          "sortOrder": "none",
          "colorPalette": "vibrant"
        }
      }
    },
    {
      "id": 14,
      "type": "barchart",
      "title": "Événements d'accélération brusque",
      "gridPos": {
        "h": 17,
        "w": 24,
        "x": 0,
        "y": 41
      },
      "options": {
        "orientation": "vertical"
      },
      "x-navixy": {
        "sql": {
          "params": {},
          "statement": "WITH spd AS (\r\n          SELECT\r\n            device_id,\r\n            device_time,\r\n            speed/100.0 AS kmh,\r\n            LAG(speed/100.0) OVER (PARTITION BY device_id ORDER BY device_time) AS prev_kmh,\r\n            EXTRACT(EPOCH FROM (device_time - LAG(device_time) OVER (PARTITION BY device_id ORDER BY device_time))) AS dt_sec\r\n          FROM\r\n            raw_telematics_data.tracking_data_core\r\n        ),\r\n        accels AS (\r\n          SELECT\r\n            device_id,\r\n            device_time,\r\n            (kmh - prev_kmh) / NULLIF(dt_sec, 0) AS accel_kmh_per_sec\r\n          FROM\r\n            spd\r\n          WHERE\r\n            prev_kmh IS NOT NULL\r\n        ),\r\n        events_with_severity AS (\r\n          SELECT \r\n            DATE(a.device_time) AS category,\r\n            CASE \r\n              WHEN a.accel_kmh_per_sec >= 80 THEN 'Critical'\r\n              WHEN a.accel_kmh_per_sec >= 60 THEN 'Warning'\r\n              ELSE 'Normal'\r\n            END AS series\r\n          FROM accels a\r\n          WHERE a.accel_kmh_per_sec >= 60\r\n        )\r\n        SELECT \r\n          category,\r\n          COUNT(*) AS value,\r\n          series\r\n        FROM events_with_severity\r\n        GROUP BY category, series\r\n        ORDER BY series"
        },
        "verify": {
          "max_rows": 1000
        },
        "dataset": {
          "shape": "category_value",
          "columns": {
            "value": {
              "type": "integer"
            },
            "series": {
              "type": "string"
            },
            "category": {
              "type": "date"
            }
          }
        },
        "visualization": {
          "colorPalette": "vibrant"
        }
      }
    },
    {
      "id": 15,
      "type": "barchart",
      "title": "Virages / Prise de courbe soudains",
      "gridPos": {
        "h": 13,
        "w": 24,
        "x": 0,
        "y": 23
      },
      "options": {
        "orientation": "vertical"
      },
      "x-navixy": {
        "sql": {
          "params": {},
          "statement": "WITH pts AS (\r\n          SELECT\r\n            device_id,\r\n            device_time,\r\n            latitude/1e7::numeric AS lat,\r\n            longitude/1e7::numeric AS lon,\r\n            LAG(latitude/1e7::numeric) OVER (PARTITION BY device_id ORDER BY device_time) AS prev_lat,\r\n            LAG(longitude/1e7::numeric) OVER (PARTITION BY device_id ORDER BY device_time) AS prev_lon,\r\n            speed/100.0 AS kmh,\r\n            LAG(speed/100.0) OVER (PARTITION BY device_id ORDER BY device_time) AS prev_kmh\r\n          FROM\r\n            raw_telematics_data.tracking_data_core\r\n        ),\r\n        bearing AS (\r\n          SELECT *,\r\n                 atan2(\r\n                   sin(radians(lon-prev_lon))*cos(radians(lat)),\r\n                   cos(radians(prev_lat))*sin(radians(lat)) -\r\n                   sin(radians(prev_lat))*cos(radians(lat))*cos(radians(lon-prev_lon))\r\n                 ) * 180/pi() AS heading_change\r\n          FROM pts\r\n          WHERE prev_lat IS NOT NULL AND prev_lon IS NOT NULL\r\n        ),\r\n        events_with_severity AS (\r\n          SELECT \r\n            DATE(b.device_time) AS category,\r\n            CASE \r\n              WHEN ABS(b.heading_change) >= 50 AND b.kmh >= 30 THEN 'Critical'\r\n              WHEN ABS(b.heading_change) >= 30 AND b.kmh >= 30 THEN 'Warning'\r\n              ELSE 'Normal'\r\n            END AS series\r\n          FROM bearing b\r\n        )\r\n        SELECT \r\n          category,\r\n          COUNT(*) AS value,\r\n          series\r\n        FROM events_with_severity\r\n        GROUP BY category, series\r\n        ORDER BY category, series"
        },
        "verify": {
          "max_rows": 1000
        },
        "dataset": {
          "shape": "category_value",
          "columns": {
            "value": {
              "type": "integer"
            },
            "series": {
              "type": "string"
            },
            "category": {
              "type": "date"
            }
          }
        },
        "visualization": {
          "stacking": "percent",
          "colorPalette": "classic"
        }
      }
    },
    {
      "id": 16,
      "type": "stat",
      "title": "Nombre total d'événements d'inactivité",
      "gridPos": {
        "h": 5,
        "w": 8,
        "x": 0,
        "y": 36
      },
      "options": {
        "textMode": "auto"
      },
      "x-navixy": {
        "sql": {
          "params": {},
          "statement": "WITH ign AS (\r\n          SELECT device_id,\r\n                 device_time,\r\n                 value::int AS ign_on\r\n          FROM raw_telematics_data.states\r\n          WHERE state_name = 'ignition'\r\n        ),\r\n        spd AS (\r\n          SELECT device_id, device_time, speed/100.0 AS kmh\r\n          FROM raw_telematics_data.tracking_data_core\r\n        ),\r\n        merged AS (\r\n          SELECT i.device_id,\r\n                 i.device_time,\r\n                 i.ign_on,\r\n                 s.kmh,\r\n                 LEAD(i.device_time) OVER (PARTITION BY i.device_id ORDER BY i.device_time) AS next_time\r\n          FROM ign i\r\n          LEFT JOIN spd s ON s.device_id = i.device_id AND s.device_time = i.device_time\r\n        )\r\n        SELECT COUNT(*) AS value\r\n        FROM merged m\r\n        WHERE m.ign_on = 1 \r\n          AND (m.kmh IS NULL OR m.kmh < 5) \r\n          AND m.next_time IS NOT NULL\r\n          AND EXTRACT(EPOCH FROM (m.next_time - m.device_time))/60 >= 5"
        },
        "verify": {
          "max_rows": 1000
        },
        "dataset": {
          "shape": "kpi",
          "columns": {}
        }
      }
    },
    {
      "id": 9,
      "type": "timeseries",
      "title": "Messages au fil du temps",
      "gridPos": {
        "h": 13,
        "w": 24,
        "x": 0,
        "y": 83
      },
      "options": {
        "legend": {
          "calcs": [],
          "placement": "bottom",
          "showLegend": true,
          "displayMode": "list"
        },
        "tooltip": {
          "mode": "single",
          "sort": "none"
        }
      },
      "targets": [],
      "x-navixy": {
        "sql": {
          "params": {},
          "statement": "WITH bounds AS (\nSELECT date_trunc('hour', NOW() - INTERVAL '24 hours') AS start_ts, date_trunc('hour', NOW()) AS end_ts ), hours AS (\nSELECT generate_series( (\nSELECT start_ts\nFROM bounds), (\nSELECT end_ts\nFROM bounds), INTERVAL '1 hour' ) AS bucket ), counts AS (\nSELECT date_trunc('hour', t.device_time) AS bucket, COUNT(*) AS messages, COUNT(DISTINCT t.device_id) AS unique_devices\nFROM raw_telematics_data.tracking_data_core t --\n    JOIN raw_business_data.objects o\n  ON o.device_id = t.device_id --\n  AND o.client_id = 398286 -- uncomment & set if you want a specific client\nWHERE t.device_time >= (\nSELECT start_ts\nFROM bounds)\n  AND t.device_time < (\nSELECT end_ts\nFROM bounds) + INTERVAL '1 hour'\nGROUP BY 1 )\nSELECT h.bucket AS time, COALESCE(c.messages, 0) AS messages, COALESCE(c.unique_devices, 0) AS unique_devices\nFROM hours h LEFT\n    JOIN counts c USING (bucket)\nORDER BY time;"
        },
        "verify": {
          "max_rows": 1000
        },
        "dataset": {
          "shape": "time_value",
          "columns": {}
        },
        "visualization": {
          "lineStyle": "solid",
          "colorPalette": "modern",
          "interpolation": "smooth",
          "legendPosition": "top"
        }
      },
      "datasource": null,
      "fieldConfig": {
        "defaults": {
          "unit": "short",
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "stacking": {
              "mode": "none",
              "group": "A"
            },
            "drawStyle": "line",
            "lineWidth": 1,
            "spanNulls": false,
            "showPoints": "auto",
            "fillOpacity": 10,
            "gradientMode": "none",
            "axisPlacement": "auto",
            "lineInterpolation": "linear"
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              }
            ]
          }
        },
        "overrides": []
      }
    },
    {
      "id": 1,
      "type": "kpi",
      "title": "Nombre d'échantillons",
      "gridPos": {
        "h": 5,
        "w": 6,
        "x": 18,
        "y": 128
      },
      "options": {
        "textMode": "auto",
        "colorMode": "value",
        "graphMode": "none",
        "justifyMode": "auto",
        "orientation": "auto"
      },
      "targets": [],
      "x-navixy": {
        "sql": {
          "params": {
            "tenant_id": {
              "type": "uuid"
            }
          },
          "statement": "SELECT *\nFROM raw_telematics_data.tracking_data_core LIMIT 10;"
        },
        "verify": {
          "max_rows": 1
        },
        "dataset": {
          "shape": "kpi",
          "columns": {
            "value": {
              "type": "number"
            }
          }
        }
      },
      "datasource": null,
      "description": "",
      "fieldConfig": {
        "defaults": {
          "unit": "short",
          "color": {
            "mode": "thresholds"
          },
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              }
            ]
          }
        },
        "overrides": []
      }
    },
    {
      "id": 2,
      "type": "barchart",
      "title": "Données d'exemple par catégorie",
      "gridPos": {
        "h": 15,
        "w": 18,
        "x": 0,
        "y": 130
      },
      "options": {
        "valueMode": "color",
        "displayMode": "gradient",
        "orientation": "horizontal",
        "showUnfilled": true
      },
      "targets": [],
      "x-navixy": {
        "sql": {
          "params": {},
          "statement": "SELECT o.object_label, COUNT(*) AS msgs_24h\nFROM raw_telematics_data.tracking_data_core AS t\n    JOIN raw_business_data.objects AS o\n  ON o.device_id = t.device_id\nWHERE t.device_time >= NOW() - INTERVAL '24 hours'\nGROUP BY o.client_id, o.object_label, t.device_id\nORDER BY msgs_24h DESC LIMIT 20;"
        },
        "verify": {
          "max_rows": 10
        },
        "dataset": {
          "shape": "category_value",
          "columns": {}
        },
        "visualization": {
          "orientation": "vertical"
        }
      },
      "datasource": null,
      "fieldConfig": {
        "defaults": {
          "unit": "short",
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "hideFrom": {
              "viz": false,
              "legend": false,
              "tooltip": false
            }
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              }
            ]
          }
        },
        "overrides": []
      }
    },
    {
      "id": 3,
      "type": "table",
      "title": "Messages par jour",
      "gridPos": {
        "h": 9,
        "w": 12,
        "x": 4,
        "y": 148
      },
      "options": {
        "sortBy": [],
        "showHeader": true
      },
      "targets": [],
      "x-navixy": {
        "sql": {
          "params": {
            "__to": null,
            "__from": null
          },
          "statement": "SELECT TO_CHAR(date_trunc('day', t.device_time), 'Mon DD, YYYY') AS \"Date\", \n       COUNT(*) AS \"Messages\"\nFROM raw_telematics_data.tracking_data_core AS t\nWHERE t.device_time >= ${__from}\n  AND t.device_time < ${__to}\nGROUP BY 1\nORDER BY 1;"
        },
        "verify": {
          "max_rows": 10
        },
        "dataset": {
          "shape": "table",
          "columns": {}
        },
        "visualization": {
          "pageSize": 5
        }
      },
      "datasource": null,
      "fieldConfig": {
        "defaults": {
          "custom": {
            "align": "auto",
            "displayMode": "auto"
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              }
            ]
          }
        },
        "overrides": []
      }
    },
    {
      "id": 6,
      "type": "kpi",
      "title": "Temps total d'inactivité",
      "gridPos": {
        "h": 5,
        "w": 8,
        "x": 8,
        "y": 36
      },
      "options": {
        "textMode": "auto"
      },
      "x-navixy": {
        "sql": {
          "params": {},
          "statement": "WITH ign AS (\n    SELECT device_id,\n            device_time,\n            value::int AS ign_on\n    FROM raw_telematics_data.states\n    WHERE state_name = 'ignition'\n  ),\n  spd AS (\n    SELECT device_id, device_time, speed/100 AS kmh\n    FROM raw_telematics_data.tracking_data_core\n  ),\n  merged AS (\n    SELECT i.device_id,\n            i.device_time,\n            i.ign_on,\n            s.kmh,\n            LEAD(i.device_time) OVER (PARTITION BY i.device_id ORDER BY i.device_time) AS next_time\n    FROM ign i\n    LEFT JOIN spd s ON s.device_id = i.device_id AND s.device_time = i.device_time\n  )\n  SELECT round(COALESCE(SUM(EXTRACT(EPOCH FROM (m.next_time - m.device_time))/60), 0), 0) AS value\n  FROM merged m\n  WHERE m.ign_on = 1 \n    AND (m.kmh IS NULL OR m.kmh < 5) \n    AND m.next_time IS NOT NULL\n    AND EXTRACT(EPOCH FROM (m.next_time - m.device_time))/60 >= 5"
        },
        "verify": {
          "max_rows": 1000
        },
        "dataset": {
          "shape": "kpi",
          "columns": {
            "value": {
              "type": "number"
            }
          }
        }
      }
    },
    {
      "id": 7,
      "type": "piechart",
      "title": "Appareils les plus actifs au cours des dernières 24h",
      "gridPos": {
        "h": 9,
        "w": 12,
        "x": 0,
        "y": 121
      },
      "options": {
        "pieType": "donut"
      },
      "x-navixy": {
        "sql": {
          "params": {},
          "statement": "SELECT o.object_label, COUNT(*) AS msgs_24h\nFROM raw_telematics_data.tracking_data_core AS t\n    JOIN raw_business_data.objects AS o\n  ON o.device_id = t.device_id\nWHERE t.device_time >= NOW() - INTERVAL '24 hours'\nGROUP BY o.client_id, o.object_label, t.device_id\nORDER BY msgs_24h DESC LIMIT 20;"
        },
        "verify": {
          "max_rows": 1000
        },
        "dataset": {
          "shape": "pie",
          "columns": {}
        }
      }
    },
    {
      "id": 8,
      "type": "table",
      "title": "Mouvements récents des véhicules",
      "gridPos": {
        "h": 25,
        "w": 24,
        "x": 0,
        "y": 96
      },
      "options": {
        "showHeader": true
      },
      "x-navixy": {
        "sql": {
          "params": {},
          "statement": "SELECT\n  to_char(t.device_time, 'YYYY-MM-DD HH24:MI:SS TZ') AS \"Timestamp\",\n  t.device_id                                       AS \"Device ID\",\n  t.speed                                           AS \"Speed\",\n  round(t.latitude::numeric  / 10000000, 6)         AS \"Latitude (°)\",\n  round(t.longitude::numeric / 10000000, 6)         AS \"Longitude (°)\"\nFROM raw_telematics_data.tracking_data_core t\nWHERE t.device_time >= NOW() - INTERVAL '3 days'\n  AND t.speed > 0\nORDER BY t.device_time DESC;"
        },
        "verify": {
          "max_rows": 3
        },
        "dataset": {
          "shape": "table",
          "columns": {
            "Speed": {
              "type": "integer"
            },
            "Device ID": {
              "type": "integer"
            },
            "Timestamp": {
              "type": "string"
            },
            "Latitude (°)": {
              "type": "number"
            },
            "Longitude (°)": {
              "type": "number"
            }
          }
        },
        "visualization": {
          "pageSize": 25,
          "sortable": true,
          "showHeader": true,
          "showTotals": false,
          "rowHighlighting": "hover"
        }
      }
    },
    {
      "id": 19,
      "type": "stat",
      "title": "Durée moyenne d'inactivité",
      "gridPos": {
        "h": 5,
        "w": 8,
        "x": 16,
        "y": 36
      },
      "options": {
        "textMode": "auto"
      },
      "x-navixy": {
        "sql": {
          "params": {},
          "statement": "WITH ign AS (\r\n          SELECT device_id,\r\n                 device_time,\r\n                 value::int AS ign_on\r\n          FROM raw_telematics_data.states\r\n          WHERE state_name = 'ignition'\r\n        ),\r\n        spd AS (\r\n          SELECT device_id, device_time, speed/100.0 AS kmh\r\n          FROM raw_telematics_data.tracking_data_core\r\n        ),\r\n        merged AS (\r\n          SELECT i.device_id,\r\n                 i.device_time,\r\n                 i.ign_on,\r\n                 s.kmh,\r\n                 LEAD(i.device_time) OVER (PARTITION BY i.device_id ORDER BY i.device_time) AS next_time\r\n          FROM ign i\r\n          LEFT JOIN spd s ON s.device_id = i.device_id AND s.device_time = i.device_time\r\n        )\r\n        SELECT round(COALESCE(AVG(EXTRACT(EPOCH FROM (m.next_time - m.device_time))/60), 0),0) AS value\r\n        FROM merged m\r\n        WHERE m.ign_on = 1 \r\n          AND (m.kmh IS NULL OR m.kmh < 5) \r\n          AND m.next_time IS NOT NULL\r\n          AND EXTRACT(EPOCH FROM (m.next_time - m.device_time))/60 >= 5"
        },
        "verify": {
          "max_rows": 1000
        },
        "dataset": {
          "shape": "kpi",
          "columns": {}
        }
      }
    }
  ],
  "refresh": "30s",
  "version": 1,
  "editable": true,
  "timezone": "browser",
  "x-navixy": {
    "execution": {
      "dialect": "postgresql",
      "endpoint": "/api/v1/sql/run",
      "max_rows": 1000,
      "read_only": true,
      "timeout_ms": 5000,
      "allowed_schemas": [
        "demo_data"
      ]
    },
    "parameters": {
      "bindings": {
        "to": "${__to}",
        "from": "${__from}",
        "tenant_id": "${var_tenant}"
      }
    },
    "schemaVersion": "1.0.0"
  },
  "templating": {
    "list": [
      {
        "name": "var_tenant",
        "type": "constant",
        "label": "Tenant",
        "query": "demo-tenant-id",
        "current": {
          "text": "Demo Tenant",
          "value": "demo-tenant-id"
        },
        "options": [
          {
            "text": "Demo Tenant",
            "value": "demo-tenant-id",
            "selected": true
          }
        ]
      }
    ],
    "enable": true
  },
  "timepicker": {
    "now": true,
    "enable": true,
    "hidden": false,
    "collapse": false,
    "time_options": [
      "5m",
      "15m",
      "1h",
      "6h",
      "12h",
      "24h"
    ],
    "refresh_intervals": [
      "5s",
      "10s",
      "30s",
      "1m",
      "5m",
      "15m",
      "30m",
      "1h"
    ]
  },
  "annotations": {
    "list": [
      {
        "hide": true,
        "name": "Annotations & Alerts",
        "type": "dashboard",
        "enable": true,
        "target": {
          "tags": [],
          "type": "dashboard",
          "limit": 100,
          "matchAny": false
        },
        "builtIn": 1,
        "iconColor": "rgba(0, 211, 255, 1)",
        "datasource": {
          "uid": "-- Dashboard --",
          "type": "dashboard"
        }
      }
    ]
  },
  "description": "Exemple simple de tableau de bord pour démarrer",
  "graphTooltip": 1,
  "schemaVersion": 38
}
```

{% endcode %}

Pour en savoir plus sur l'application de tableau de bord d'IoT Query, voir [Dashboard Studio](https://www.navixy.com/docs/analytics/fr/studio-de-tableaux-de-bord).

Pour l'assistance à la configuration, contactez <iotquery@navixy.com>.

## **Alertes d'expiration d'immatriculation / d'assurance**

Les banques doivent suivre les prochaines expirations d'immatriculation et d'assurance car elles sont responsables des inspections techniques, de l'immatriculation et de l'assurance. Des alertes en temps utile évitent les amendes et les immobilisations de véhicules.

{% code expandable="true" %}

```sql
SELECT 
    v.vehicle_id,
    v.vehicle_label,
    v.registration_number,
    v.free_insurance_valid_till_date,
    v.liability_insurance_valid_till
FROM raw_business_data.vehicles v
WHERE v.free_insurance_valid_till_date BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL '30 days'
    OR v.liability_insurance_valid_till BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL '30 days';
```

{% endcode %}

## **Expiration du permis de conduire**

Bien que ce ne soit pas toujours obligatoire, proposer des alertes proactives d'expiration de permis est un service à valeur ajoutée. Les avertissements précoces permettent aux clients de renouveler leurs permis avant leur expiration. Veuillez noter que vous

{% code expandable="true" %}

```sql
SELECT e.employee_id,
       e.first_name || ' ' || e.last_name AS driver_name,
       e.driver_license_number,
       e.driver_license_valid_till
FROM raw_business_data.employees e
WHERE e.driver_license_valid_till BETWEEN CURRENT_DATE AND CURRENT_DATE + (30 * INTERVAL '1 day');
```

{% endcode %}

## Sortie de géofence (frontière nationale) <a href="#geofence-exit-country-border" id="geofence-exit-country-border"></a>

Les contrats peuvent restreindre les déplacements du véhicule à un territoire spécifique (par ex. : Serbie). La sortie de cette zone doit alerter instantanément la banque afin qu'elle puisse agir (par ex. : contacter le client, immobiliser l'actif).

Cette requête SQL est conçue pour surveiller et identifier lorsqu'un appareil quitte une zone géographique prédéfinie étiquetée "Tallaght Depot Geofences." Le processus commence par la collecte et l'ordonnancement des points géographiques qui définissent la frontière de la zone. Pour s'assurer que la frontière forme un polygone valide, le premier point est ajouté à la fin de la liste, fermant ainsi la forme. Cet ensemble fermé de points est ensuite utilisé pour créer un polygone représentant la zone géographique, qui est converti en un objet geography pour l'analyse spatiale.

La requête récupère ensuite les données de suivi des appareils dans une plage temporelle spécifiée, convertissant les valeurs brutes de latitude et de longitude en points géographiques. Elle calcule si chaque point d'appareil est à l'intérieur ou à l'extérieur de la zone prédéfinie en utilisant la fonction ST\_Contains, qui vérifie l'inclusion spatiale. Le paramètre calculé pos indique « inside » si le point est à l'intérieur de la zone et « outside » sinon. Enfin, la requête filtre ces résultats pour détecter les transitions où un appareil passe de l'intérieur de la zone à l'extérieur, en utilisant une fonction de fenêtre pour comparer la position actuelle avec la précédente. Cette logique aide à surveiller les mouvements des appareils et à détecter les événements de sortie de zones géographiques spécifiques. Assurez‑vous d'ajouter la valeur correcte pour le paramètre : `z.zone_label = 'your_zone_label'.`

{% code expandable="true" %}

```sql
WITH zone AS (
  SELECT z.zone_id,
         ST_MakePolygon(ST_MakeLine(ARRAY_AGG(ST_MakePoint(g.longitude, g.latitude) ORDER BY g.number)))::geography AS geog
  FROM raw_business_data.zones z
  JOIN raw_business_data.geofence_points g ON g.zone_id = z.zone_id
  WHERE z.zone_label = 'your_zone_label'
  GROUP BY z.zone_id
),
pts AS (
  SELECT device_id,
         device_time,
         ST_SetSRID(ST_MakePoint(longitude/1e7::numeric, latitude/1e7::numeric), 4326)::geography AS geog
  FROM raw_telematics_data.tracking_data_core
  WHERE device_time BETWEEN '2025-07-27 00:00:00' AND '2025-07-28 23:59:59'
),
states AS (
  SELECT p.*,
         CASE WHEN ST_Contains(z.geog::geometry, p.geog::geometry) THEN 'inside' ELSE 'outside' END AS pos
  FROM pts p CROSS JOIN zone z
),
filtered_states AS (
  SELECT
    device_id,
    device_time,
    pos,
    LAG(pos) OVER (PARTITION BY device_id ORDER BY device_time) AS prev_pos
  FROM states
)
SELECT device_id, device_time, pos
FROM filtered_states
WHERE prev_pos = 'inside' AND pos = 'outside';

```

{% endcode %}

## **Inspections de routine selon un intervalle de temps**

Certaines tâches de maintenance se répètent selon des calendriers fixes. Le système doit signaler les véhicules dont la prochaine inspection / vérification est due dans un intervalle défini.

{% code expandable="true" %}

```sql
WITH t AS (
    SELECT
        vehicle_id,
        description,
        start_date,
        date_repeat_interval,
        make_interval(days => date_repeat_interval) AS repeat_interval
    FROM raw_business_data.vehicle_service_tasks
    WHERE date_repeat_interval IS NOT NULL
)
SELECT
    vehicle_id,
    description,
    start_date,
    date_repeat_interval,
    start_date
        + repeat_interval
            * floor(
                extract(epoch from (current_date::timestamp - start_date))
                / extract(epoch from repeat_interval)
              ) AS last_due,
    start_date
        + repeat_interval
            * (
                floor(
                    extract(epoch from (current_date::timestamp - start_date))
                    / extract(epoch from repeat_interval)
                ) + 1
              ) AS next_due
FROM t
WHERE (
        start_date
            + repeat_interval
                * (
                    floor(
                        extract(epoch from (current_date::timestamp - start_date))
                        / extract(epoch from repeat_interval)
                    ) + 1
                  )
      ) BETWEEN current_date
          AND (current_date + interval '30 days');
```

{% endcode %}

## **Service selon un seuil de kilométrage (Mineur/Majeur)**

Les entretiens mineurs et majeurs sont déclenchés par le kilométrage depuis le dernier événement de service. Lorsque les kilomètres accumulés dépassent le seuil, le service approprié doit être programmé.

Veuillez noter que le champ `vst.description doit contenir des commentaires / descriptions pertinents pour l'utiliser dans les filtres du code SQL ci‑dessous.`

{% code expandable="true" %}

```sql
SELECT
  v.vehicle_id,
  v.vehicle_label,
  km.km_since_service,
  vst.mileage_limit
FROM
  raw_business_data.vehicles v
  JOIN LATERAL (
    SELECT MAX(vst.completion_date) AS last_service_date
    FROM raw_business_data.vehicle_service_tasks vst
    WHERE vst.vehicle_id = v.vehicle_id
      AND (vst.description ILIKE '%minor%' OR vst.description ILIKE '%major%')
      AND vst.completion_date IS NOT NULL
  ) ls ON TRUE
  JOIN raw_business_data.objects o ON o.object_id = v.vehicle_id
  JOIN LATERAL (
    SELECT SUM(t.track_distance_meters) / 1000.0 AS km_since_service
    FROM business_data.tracks t
    WHERE t.device_id = o.device_id
      AND t.track_start_time > ls.last_service_date
  ) km ON TRUE
  JOIN raw_business_data.vehicle_service_tasks vst
    ON vst.vehicle_id = v.vehicle_id
    AND vst.completion_date = ls.last_service_date
    AND (vst.description ILIKE '%minor%' OR vst.description ILIKE '%major%')

```

{% endcode %}

## **Plafond de kilométrage et pénalités**

Les contrats de leasing limitent souvent le kilométrage (par ex. 25 000 km/an). Si la limite est dépassée, des clauses pénales s'appliquent. Le système doit comparer le kilométrage réel sur la période du contrat avec la limite convenue et calculer les frais.

{% code expandable="true" %}

```sql
WITH driven AS (
  SELECT
    o.object_id,
    DATE_TRUNC('year', t.track_start_time) AS year,
    SUM(t.track_distance_meters) / 1000.0 AS km_year
  FROM
    business_data.tracks t
    JOIN raw_business_data.objects o ON o.device_id = t.device_id
  WHERE
    t.track_start_time >= '2023-01-01'::date
    AND t.track_start_time < '2024-01-01'::date
  GROUP BY
    o.object_id, DATE_TRUNC('year', t.track_start_time)
),
limits AS (
  SELECT
    object_id,
    10000 AS km_limit,
    0.5 AS penalty_rate
  FROM
    raw_business_data.objects
)
SELECT
  d.object_id,
  d.year,
  d.km_year,
  l.km_limit,
  GREATEST(d.km_year - l.km_limit, 0) AS km_over,
  GREATEST(d.km_year - l.km_limit, 0) * l.penalty_rate AS penalty_amount
FROM
  driven d
  JOIN limits l ON d.object_id = l.object_id;
```

{% endcode %}

## **Surveillance des heures moteur**

Pour les machines et équipements agricoles, ce sont les heures de fonctionnement — et non le kilométrage — qui régissent la maintenance et la facturation. Les données d'heures moteur (par ex. depuis le CAN-Bus) doivent être surveillées et résumées.

{% code expandable="true" %}

```sql
WITH last_service AS (
  SELECT
    vst.vehicle_id,
    MAX(vst.completion_date) AS last_service_date,
    MAX(vst.completion_engine_hours) AS last_service_engine_hours
  FROM
    raw_business_data.vehicle_service_tasks vst
  GROUP BY
    vst.vehicle_id
),
engine_hours_since_service AS (
  SELECT
    v.vehicle_id,
    SUM(t.track_duration_seconds) / 3600.0 AS engine_hours_since_service
  FROM
    raw_business_data.vehicles v
    JOIN raw_business_data.objects o ON o.object_id = v.object_id
    JOIN business_data.tracks t ON t.device_id = o.device_id
    JOIN last_service ls ON ls.vehicle_id = v.vehicle_id
  WHERE
    t.track_start_time > ls.last_service_date
  GROUP BY
    v.vehicle_id
)
SELECT
  v.vehicle_id,
  v.vehicle_label,
  ls.last_service_engine_hours,
  ehs.engine_hours_since_service,
  (COALESCE(ehs.engine_hours_since_service,0) + COALESCE(ls.last_service_engine_hours,0)) AS current_engine_hours,
  vst.engine_hours_limit
FROM
  raw_business_data.vehicles v
  JOIN last_service ls ON ls.vehicle_id = v.vehicle_id
  LEFT JOIN engine_hours_since_service ehs ON ehs.vehicle_id = v.vehicle_id
  JOIN raw_business_data.vehicle_service_tasks vst
    ON vst.vehicle_id = v.vehicle_id
    AND vst.completion_date = ls.last_service_date
```

{% endcode %}

## **Événements de freinage brusque**

Le comportement de conduite affecte l'usure et la conformité au contrat. Détecter les freinages brusques aide la banque à attribuer l'usure prématurée des freins/pneus à un mauvais usage du conducteur et, si nécessaire, à transférer les coûts.

La requête SQL ci‑dessous calcule d'abord la vitesse en kilomètres par heure et la différence de temps entre points de données consécutifs pour chaque appareil. En utilisant ces informations, elle calcule ensuite le taux de décélération en kilomètres par heure par seconde. Enfin, elle filtre et renvoie les enregistrements où le taux de décélération est de 20 km/h par seconde ou plus, indiquant des événements de décélération significatifs.

{% code expandable="true" %}

```sql
WITH spd AS (
  SELECT
    device_id,
    device_time,
    speed/100.0 AS kmh,
    LAG(speed/100.0) OVER (PARTITION BY device_id ORDER BY device_time) AS prev_kmh,
    EXTRACT(EPOCH FROM (device_time - LAG(device_time) OVER (PARTITION BY device_id ORDER BY device_time))) AS dt_sec
  FROM
    raw_telematics_data.tracking_data_core
  WHERE
    device_time BETWEEN '2025-07-24 00:00:00' AND '2025-07-24 23:59:59'
),
decels AS (
  SELECT
    device_id,
    device_time,
    (prev_kmh - kmh) / NULLIF(dt_sec, 0) AS decel_kmh_per_sec
  FROM
    spd
  WHERE
    prev_kmh IS NOT NULL
)
SELECT *
FROM decels
WHERE decel_kmh_per_sec >= 20;
```

{% endcode %}

## **Événements d'accélération brutale**

L'accélération agressive augmente l'usure des pneus, transmissions, trains motrices et supports moteur. Identifier ces événements facilite le coaching et la récupération éventuelle des coûts.

La requête SQL ci‑dessous est conçue pour identifier les événements d'accélération significatifs à partir d'un jeu de données de suivi. Elle calcule d'abord la vitesse en kilomètres par heure et la différence de temps entre points de données consécutifs pour chaque appareil. En utilisant ces informations, elle calcule ensuite le taux d'accélération en kilomètres par heure par seconde. Enfin, elle filtre et renvoie les enregistrements où le taux d'accélération atteint ou dépasse un seuil spécifié, indiquant des événements d'accélération significatifs.

{% code expandable="true" %}

```sql
WITH spd AS (
  SELECT
    device_id,
    device_time,
    speed/100.0 AS kmh,
    LAG(speed/100.0) OVER (PARTITION BY device_id ORDER BY device_time) AS prev_kmh,
    EXTRACT(EPOCH FROM (device_time - LAG(device_time) OVER (PARTITION BY device_id ORDER BY device_time))) AS dt_sec
  FROM
    raw_telematics_data.tracking_data_core
  WHERE
    device_time BETWEEN '2025-07-28 00:00:00' AND '2025-07-28 23:59:59'
)
SELECT
  device_id,
  device_time,
  (kmh - prev_kmh) / NULLIF(dt_sec, 0) AS accel_kmh_per_sec
FROM
  spd
WHERE
  prev_kmh IS NOT NULL
  AND (kmh - prev_kmh) / NULLIF(dt_sec, 0) >= 20;
```

{% endcode %}

## **Virages brusques / Prise de courbe**

Les virages serrés combinés à des changements de vitesse abrupts indiquent une conduite à risque. Surveiller ce type de comportement aide à détecter une utilisation inappropriée du véhicule.

Cette requête SQL est conçue pour identifier des changements significatifs de direction et de vitesse à partir de données de suivi sur une période spécifiée. Elle convertit d'abord les valeurs brutes de latitude et longitude en degrés décimaux et calcule la vitesse en kilomètres par heure. En utilisant la fonction LAG, elle récupère les données de position et de vitesse précédentes pour chaque appareil, ce qui permet de calculer les variations dans le temps. La requête calcule ensuite le changement d'azimut en degrés à l'aide de fonctions trigonométriques pour déterminer le cap entre points consécutifs. Elle calcule également la variation de vitesse entre ces points. Enfin, la requête filtre les résultats pour n'inclure que les enregistrements où le changement absolu d'azimut est de 10 degrés ou plus et où la variation absolue de vitesse est de 5 km/h ou plus, identifiant des manœuvres ou événements significatifs dans les données de suivi.

{% code expandable="true" %}

```sql
WITH pts AS (
  SELECT
    device_id,
    device_time,
    latitude/1e7::numeric AS lat,
    longitude/1e7::numeric AS lon,
    LAG(latitude/1e7::numeric) OVER (PARTITION BY device_id ORDER BY device_time) AS prev_lat,
    LAG(longitude/1e7::numeric) OVER (PARTITION BY device_id ORDER BY device_time) AS prev_lon,
    speed/100.0 AS kmh,
    LAG(speed/100.0) OVER (PARTITION BY device_id ORDER BY device_time) AS prev_kmh
  FROM
    raw_telematics_data.tracking_data_core
  WHERE
    device_time BETWEEN '2025-07-28 00:00:00' AND '2025-07-28 23:59:59'
),
bearing AS (
  SELECT *,
         atan2(
           sin(radians(lon-prev_lon))*cos(radians(lat)),
           cos(radians(prev_lat))*sin(radians(lat)) -
           sin(radians(prev_lat))*cos(radians(lat))*cos(radians(lon-prev_lon))
         ) * 180/pi() AS heading_change,
         (kmh - prev_kmh) AS delta_speed
  FROM pts
)
SELECT *
FROM bearing
WHERE abs(heading_change) >= 10
  AND abs(delta_speed) >= 5;

```

{% endcode %}

## **Détection d'allumage et d'inactivité**

Mesurer le temps d'inactivité (allumage, faible/aucune vitesse) permet de réduire le gaspillage de carburant et d'identifier les mauvais usages. Les longues périodes au ralenti doivent être signalées et gérées.

{% hint style="info" %}
[Dashboard Studio](https://www.navixy.com/docs/analytics/fr/studio-de-tableaux-de-bord) et des outils similaires ne prennent pas en charge la substitution de variables (`:variable` syntaxe). Remplacez tous les paramètres par des valeurs littérales avant d'exécuter cette requête. Voir l'exemple ci‑dessous pour le format correct.
{% endhint %}

{% code expandable="true" %}

```sql
WITH ign AS (
    SELECT
        device_id,
        device_time,
        value::int AS ign_on
    FROM raw_telematics_data.states
    WHERE state_name = 'ignition'
      AND device_time BETWEEN :from_ts::timestamptz AND :to_ts::timestamptz
      -- Pour Dashboard Studio, remplacez :from_ts et :to_ts par des horodatages littéraux :
      -- TIMESTAMPTZ '2024-01-01 00:00:00+00' AND TIMESTAMPTZ '2024-01-07 00:00:00+00'
),
spd AS (
    SELECT
        device_id,
        device_time,
        speed / 100.0 AS kmh
    FROM raw_telematics_data.tracking_data_core
    WHERE device_time BETWEEN :from_ts AND :to_ts
    -- Pour Dashboard Studio, remplacez :from_ts et :to_ts par des horodatages littéraux :
    -- TIMESTAMPTZ '2024-01-01 00:00:00+00' AND TIMESTAMPTZ '2024-01-07 00:00:00+00'
),
merged AS (
    SELECT
        i.device_id,
        i.device_time,
        i.ign_on,
        s.kmh,
        LEAD(i.device_time) OVER (
            PARTITION BY i.device_id
            ORDER BY i.device_time
        ) AS next_time
    FROM ign i
    LEFT JOIN spd s USING (device_id, device_time)
)
SELECT
    device_id,
    device_time AS idle_start,
    next_time   AS idle_end,
    EXTRACT(EPOCH FROM (next_time - device_time)) / 60.0 AS idle_minutes
FROM merged
WHERE ign_on = 1
  AND kmh < :idle_speed
  -- Pour Dashboard Studio, remplacez :idle_speed par une valeur numérique, p.ex., 5
  AND next_time IS NOT NULL
  AND (next_time - device_time) >= (:idle_min::int * INTERVAL '1 minute');
  -- Pour Dashboard Studio, remplacez :idle_min par une valeur numérique, p.ex., (5 * INTERVAL '1 minute')
```

{% endcode %}
