# Logistik

{% hint style="warning" %}
Aktifkan **IoT Query** sebelum memanfaatkan data untuk membangun analitik komprehensif. Jika Anda belum memilikinya, hubungi kami untuk detail aktivasi - <iotquery@navixy.com>
{% endhint %}

Logistik adalah ekosistem kompleks yang melibatkan koordinasi transportasi, operasi gudang, inventaris, dan pelaksanaan pengiriman. Mengintegrasikan telematika ke dalam proses logistik memungkinkan perusahaan mengumpulkan data real-time tentang kendaraan, pengemudi, rute, dan kondisi kargo, yang secara signifikan meningkatkan pengambilan keputusan dan efisiensi operasional.

Navixy **IoT Query**, dengan kemampuan ingestion data dan analitik deret waktu yang andal, mendukung transformasi digital operasi logistik dengan memungkinkan visibilitas mendalam ke setiap tahap siklus hidup. Kemampuan ingestion telematika yang andal menyediakan visibilitas komprehensif ke dalam operasi ini. Data GPS real-time, diagnostik data sensor, geofencing, dan analitik sensor memungkinkan operator logistik mendigitalkan alur kerja, mengotomatiskan kontrol, dan membuat keputusan yang tepat.

| Tahap Siklus Hidup              | Tujuan                                                                                              | Kasus Penggunaan / Resep yang Dicakup                                                                                                                   |
| ------------------------------- | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Manajemen rute**              | Mengoptimalkan perutean kendaraan, memastikan pengiriman yang efisien, dan mengurangi keterlambatan | Jumlah perjalanan per hari Jumlah jarak tempuh per kendaraan per hari (7 hari terakhir)                                                                 |
| **Pemantauan Kargo**            | Memastikan kondisi transportasi yang tepat untuk barang sensitif                                    | Peristiwa Pelanggaran Suhu (dan Kelembapan) dalam 7 Hari Terakhir                                                                                       |
| **Operasi Kendaraan**           | Melacak pemanfaatan armada, memastikan perawatan, dan mengurangi waktu henti                        | Ringkasan Jam Mesin per Kendaraan / Pengemudi / Hari (7 Hari Terakhir) Analisis Waktu Henti Kendaraan Pelacakan Aset Tanpa Pergerakan                   |
| **Keamanan & Keselamatan Rute** | Mendeteksi penyalahgunaan, aktivitas tidak sah, dan pelanggaran keselamatan                         | Deteksi Penyimpangan Rute- Berhenti Tidak Sah (24 Jam Terakhir) Deteksi Penggunaan di Luar Jam Kerja                                                    |
| **Manajemen Kepatuhan**         | Memantau perilaku pengemudi, menegakkan kebijakan, dan kepatuhan operasional                        | Ringkasan Jam Mesin per Kendaraan / Pengemudi / Hari (7 Hari Terakhir) Deteksi Penggunaan di Luar Jam Kerja                                             |
| **Analisis Pasca-Pengiriman**   | Mengevaluasi efisiensi operasional dan kinerja historis                                             | Laporan Log Peristiwa Kendaraan Jumlah jarak tempuh per kendaraan per hari (7 Hari Terakhir) Jumlah perjalanan per hari Pelacakan Aset Tanpa Pergerakan |

## **Pelacakan Aset Tanpa Pergerakan** <a href="#asset-tracking-without-movement" id="asset-tracking-without-movement"></a>

Kasus ini mengidentifikasi aset (misalnya, kendaraan atau trailer) yang tidak mengubah GPS-nya dibandingkan **koordinat minimum dan maksimum** selama periode tersebut. Jika kedua nilai berada dalam rentang yang sangat sempit (ambang toleransi, misalnya ±0,01 derajat), kami menandai aset sebagai tidak bergerak. Kueri ini juga menggabungkan tabel objects dan vehicles di raw\_business\_data untuk mengambil label aset yang bermakna untuk keluaran hasil.

{% code expandable="true" %}

```sql
WITH gps_bounds AS (
    SELECT
        td.device_id,
        MIN(td.latitude) AS min_lat,
        MAX(td.latitude) AS max_lat,
        MIN(td.longitude) AS min_lon,
        MAX(td.longitude) AS max_lon,
        COUNT(*) AS location_records
    FROM raw_telematics_data.tracking_data_core td
    WHERE td.device_time >= now() - interval '48 hours'
    GROUP BY td.device_id
),
stationary_devices AS (
    SELECT
        device_id
    FROM gps_bounds
    WHERE location_records > 10 -- exclude devices with very sparse data
	AND((max_lat - min_lat) <= 2000 -- ~10 meters
		OR
      	(max_lon - min_lon) <= 1000)  -- ~10 meters
    		)
SELECT
    v.vehicle_id,
    v.vehicle_label,
    o.object_id,
    o.object_label,
    sd.device_id,
    gb.min_lat / 1e7 AS latitude,
    gb.min_lon / 1e7 AS longitude,
    gb.location_records
FROM stationary_devices sd
JOIN gps_bounds gb ON sd.device_id = gb.device_id
JOIN raw_business_data.objects o ON o.device_id = sd.device_id
LEFT JOIN raw_business_data.vehicles v ON v.object_id = o.object_id
ORDER BY gb.location_records DESC;
```

{% endcode %}

## **Analisis Waktu Henti Kendaraan** <a href="#vehicle-downtime-analysis" id="vehicle-downtime-analysis"></a>

Kasus ini berfokus pada analisis berapa lama kendaraan tidak beroperasi karena perawatan, kerusakan, atau ketidakaktifan. Metrik waktu henti sangat penting bagi operasi logistik untuk memantau kesehatan armada, mengurangi waktu menganggur, dan meningkatkan pemanfaatan serta efisiensi penjadwalan secara keseluruhan.

Inti analisis waktu henti terletak pada pemanfaatan tabel vehicle\_service\_tasks dari raw\_business\_data, yang mencatat baik **peristiwa perawatan terencana maupun tidak terencana**. Setiap tugas berisi start\_date dan end\_date, yang merepresentasikan periode waktu henti. Dengan memfilter **tugas servis yang selesai**, kita dapat menghitung durasi pasti setiap kendaraan tidak beroperasi.

Kueri menghitung total waktu henti per kendaraan dengan menjumlahkan durasi semua tugas servisnya (dalam jam). Kueri ini juga memungkinkan pemisahan antara perawatan terencana dan tidak terencana dengan menggunakan flag is\_unplanned. Agar hasil lebih dapat ditindaklanjuti, kueri ini menggabungkan tabel vehicles untuk menyertakan label kendaraan, nomor registrasi, dan informasi model.

{% code expandable="true" %}

```sql
WITH downtime_durations AS (
    SELECT
        vst.vehicle_id,
        vst.is_unplanned,
        vst.start_date,
        vst.end_date,
        EXTRACT(EPOCH FROM (vst.end_date - vst.start_date))/3600 AS downtime_hours
    FROM raw_business_data.vehicle_service_tasks vst
    WHERE vst.status = 'done'
      AND vst.start_date IS NOT NULL
      AND vst.end_date IS NOT NULL
)
SELECT
    v.vehicle_id,
    v.vehicle_label,
    v.registration_number,
    v.model,
    COUNT(dd.*) AS total_service_events,
    SUM(dd.downtime_hours) AS total_downtime_hours,
    SUM(dd.downtime_hours) FILTER (WHERE dd.is_unplanned = TRUE) AS unplanned_downtime_hours,
    SUM(dd.downtime_hours) FILTER (WHERE dd.is_unplanned = FALSE) AS planned_downtime_hours
FROM downtime_durations dd
JOIN raw_business_data.vehicles v ON v.vehicle_id = dd.vehicle_id
GROUP BY v.vehicle_id, v.vehicle_label, v.registration_number, v.model
ORDER BY total_downtime_hours DESC;
```

{% endcode %}

## **Deteksi Penyimpangan Rute** <a href="#route-deviation-detection" id="route-deviation-detection"></a>

Kasus ini mengidentifikasi kejadian ketika kendaraan menyimpang dari rute yang ditetapkan atau diharapkan — khususnya zona geofenced atau koridor pengiriman. Melacak penyimpangan seperti ini membantu memastikan kepatuhan rute, mengurangi keterlambatan, mendeteksi perilaku mengemudi yang berisiko, dan menjaga SLA pengiriman.

Logika ini membandingkan posisi GPS aktual kendaraan dari tracking\_data\_core (dalam skema raw\_telematics\_data) dengan zona geografis yang telah ditentukan dari tabel zones di raw\_business\_data. Zona ini merepresentasikan rute atau segmen rute yang ditetapkan. Dengan menggunakan perbandingan geometris melalui ST\_DWithin, kami menentukan apakah suatu titik berada di dalam atau di luar area rute yang dibuffer.

Kueri ini menggabungkan setiap posisi GPS dengan setiap zona rute yang diketahui menggunakan **CROSS JOIN**spasial, lalu menerapkan ST\_DWithin() untuk memeriksa apakah kendaraan berada di dalam koridor yang diizinkan. Kami mengisolasi baris ketika kendaraan **berada di luar semua rute geofenced** dan menandainya sebagai penyimpangan. Output akhir mencantumkan penyimpangan ini, termasuk perangkat, stempel waktu, label kendaraan, dan seberapa jauh titik tersebut dari pusat zona terdekat.

{% code expandable="true" %}

```sql
WITH positions AS (
    SELECT
        td.device_id,
        td.device_time,
        o.object_id,
        o.object_label,
        z.zone_id,
        z.zone_label,
        ST_SetSRID(ST_MakePoint(td.longitude / 1e7, td.latitude / 1e7), 4326)::geography AS gps_point,
        ST_Buffer(
            ST_SetSRID(ST_MakePoint(z.circle_center_longitude, z.circle_center_latitude), 4326)::geography,
            z.radius
        ) AS route_buffer
    FROM raw_telematics_data.tracking_data_core td
    JOIN raw_business_data.objects o ON td.device_id = o.device_id
    CROSS JOIN raw_business_data.zones z
    WHERE td.device_time >= now() - interval '2 days'
),
evaluated AS (
    SELECT
        device_id,
        device_time,
        object_id,
        object_label,
        zone_id,
        zone_label,
        NOT ST_DWithin(gps_point, route_buffer, 0) AS is_deviation,
        ST_Distance(gps_point, route_buffer) AS deviation_distance_meters
    FROM positions
),
deviations_only AS (
    SELECT *
    FROM evaluated
    WHERE is_deviation = true
)
SELECT
    device_id,
    object_label,
    zone_label,
    device_time,
    deviation_distance_meters
FROM deviations_only
ORDER BY device_time DESC;
```

{% endcode %}

## **Ringkasan Jam Mesin per Kendaraan / Pengemudi / Hari (7 Hari Terakhir)** <a href="#engine-hours-summary-per-vehicle-driver-day-last-7-days" id="engine-hours-summary-per-vehicle-driver-day-last-7-days"></a>

Kasus ini mengukur berapa lama mesin aktif untuk setiap kendaraan secara harian, memungkinkan manajer armada melacak **pemanfaatan**, mengidentifikasi **penggunaan berlebihan atau kurang dimanfaatkan**, dan mengaitkan aktivitas dengan penugasan pengemudi. Jika dikaitkan dengan pengemudi, kasus ini juga mendukung **validasi jam kerja** dan **analisis kinerja**.

Tabel states di raw\_telematics\_data merekam **indikator status mesin deret waktu**, biasanya dengan state\_name seperti 'ignition' dan nilai 1 (menyala) atau 0 (mati). Untuk menghitung jam mesin, kami menemukan semua transisi berstempel waktu untuk setiap perangkat dan menghitung durasi saat mesin menyala (1).

Untuk mengaitkan aktivitas mesin dengan **kendaraan dan pengemudi**, kami menggunakan tabel objects, vehicles, dan driver\_history dari raw\_business\_data. Kami menghubungkan setiap rekaman status ke pengemudi saat ini pada objek tersebut (melalui riwayat penugasan pengemudi) dan ke kendaraan yang соответствing. Kemudian kami mengelompokkan data berdasarkan hari, kendaraan, dan pengemudi, menjumlahkan total waktu mesin aktif (dalam jam).

{% code expandable="true" %}

```sql
WITH inputs_core AS (
   SELECT
       i.device_id,
       i.device_time,
       i.event_id,
       i.record_added_at,
       i.sensor_name,
       i.value
   FROM raw_telematics_data.inputs i
   WHERE i.device_time >= now() - interval '7 days'
),
clear_inputs AS (
   SELECT
       i.device_id,
       i.device_time,
       i.event_id,
       i.record_added_at,
       i.sensor_name,
       i.value,
       LAG(i.device_time) OVER (PARTITION BY i.device_id ORDER BY i.device_time) AS prev_time,
       LAG(i.value::int) OVER (PARTITION BY i.device_id ORDER BY i.device_time) AS prev_status
   FROM inputs_core i
   LEFT JOIN raw_business_data.sensor_description sd
       ON sd.input_label = i.sensor_name
      AND sd.device_id = i.device_id
   WHERE sd.sensor_type = 'engine'
     AND i.value = '1'
),
engine_on_periods AS (
   SELECT
       device_id,
       prev_time AS engine_on_time,
       device_time AS engine_off_time,
       device_time::date AS activity_day,
       EXTRACT(EPOCH FROM (device_time - prev_time)) / 3600 AS engine_hours
   FROM clear_inputs
   WHERE value::int = 0 AND prev_status = 1  -- transitions from ON to OFF
),
enriched_with_objects AS (
   SELECT
       eop.*,
       o.object_id,
       o.object_label,
       v.vehicle_id,
       v.vehicle_label,
       v.registration_number
   FROM engine_on_periods eop
   JOIN raw_business_data.objects o ON o.device_id = eop.device_id
   LEFT JOIN raw_business_data.vehicles v ON v.object_id = o.object_id
),
assigned_drivers AS (
   SELECT
       ewo.*,
       dh.new_employee_id as employee_id
   FROM enriched_with_objects ewo
   LEFT JOIN LATERAL (
       SELECT d.*
       FROM raw_business_data.driver_history d
       WHERE d.object_id = ewo.object_id
         AND d.changed_datetime <= ewo.engine_on_time
       ORDER BY d.changed_datetime DESC
       LIMIT 1
   ) dh ON true
)
SELECT
   activity_day,
   vehicle_label,
   registration_number,
   object_label,
   e.first_name || ' ' || e.last_name AS driver_name,
   SUM(engine_hours) AS total_engine_hours
FROM assigned_drivers ad
LEFT JOIN raw_business_data.employees e ON ad.employee_id = e.employee_id
GROUP BY activity_day, vehicle_label, registration_number, object_label, driver_name
ORDER BY activity_day DESC, vehicle_label;

```

{% endcode %}

## **Peristiwa Pelanggaran Suhu (dan Kelembapan) dalam 7 Hari Terakhir** <a href="#temperature-and-humidity-violation-events-in-the-last-7-days" id="temperature-and-humidity-violation-events-in-the-last-7-days"></a>

Kasus ini mengidentifikasi pembacaan sensor — seperti **suhu atau kelembapan** — yang melebihi ambang kritis selama transportasi. Pemantauan pelanggaran seperti ini sangat penting bagi industri yang mengangkut barang mudah rusak (misalnya makanan, farmasi) untuk memastikan kepatuhan terhadap persyaratan rantai dingin dan mencegah pembusukan.

Kueri ini mengekstrak **data input sensor** dari tabel inputs dalam skema raw\_telematics\_data. Setiap baris merepresentasikan pembacaan sensor (misalnya suhu, kelembapan) yang dicatat pada stempel waktu tertentu oleh perangkat. Kami memfilter catatan ini agar hanya menyertakan data dari **7 hari terakhir**.

Logika pemfilteran utama didasarkan pada **pola nama sensor** dan perbandingan **nilai numeriknya terhadap ambang batas** (misalnya, >25°C untuk suhu, >80% untuk kelembapan). Karena value disimpan sebagai teks, kami mengubahnya menjadi numerik sebelum menerapkan kondisi ambang. Untuk memperkaya hasil, kami menggabungkan dengan tabel objects untuk mengambil label kendaraan atau aset, yang meningkatkan interpretasi bagi manajer armada.

{% code expandable="true" %}

```sql
WITH recent_sensor_data AS (
   SELECT
       i.device_id,
       i.device_time,
       i.sensor_name,
       i.value::float AS value
   FROM raw_telematics_data.inputs i
   WHERE i.device_time >= now() - interval '1 hour'
),
sensor_meta AS (
   SELECT
       sd.device_id,
       sd.input_label,
       sd.sensor_id,
       sd.sensor_type,
       sd.calibration_data
   FROM raw_business_data.sensor_description sd
),
joined_data AS (
   SELECT
       rsd.*,
       sm.sensor_id,
       sm.sensor_type,
       sm.calibration_data
   FROM recent_sensor_data rsd
   LEFT JOIN sensor_meta sm
       ON rsd.device_id = sm.device_id
      AND rsd.sensor_name = sm.input_label
),
calibrated_data AS (
   SELECT
       jd.device_id,
       jd.device_time,
       jd.sensor_name,
       jd.value,
       jd.sensor_id,
       jd.sensor_type,
       CASE
           WHEN cd_low.cal_value IS NOT NULL AND cd_high.cal_value IS NOT NULL THEN
               CASE
                   WHEN cd_high.cal_value = cd_low.cal_value THEN cd_low.cal_volume
                   ELSE cd_low.cal_volume +
                       ((jd.value - cd_low.cal_value) / NULLIF(cd_high.cal_value - cd_low.cal_value, 0))
                       * (cd_high.cal_volume - cd_low.cal_volume)
               END
           ELSE jd.value
       END AS calibrated_value
   FROM joined_data jd
   LEFT JOIN LATERAL (
       SELECT
           (p->>'in')::float  AS cal_value,
           (p->>'out')::float AS cal_volume
       FROM jsonb_array_elements(jd.calibration_data) AS p
       WHERE (p->>'in')::float <= jd.value
       ORDER BY (p->>'in')::float DESC
       LIMIT 1
   ) cd_low ON TRUE
   LEFT JOIN LATERAL (
       SELECT
           (p->>'in')::float  AS cal_value,
           (p->>'out')::float AS cal_volume
       FROM jsonb_array_elements(jd.calibration_data) AS p
       WHERE (p->>'in')::float >= jd.value
       ORDER BY (p->>'in')::float ASC
       LIMIT 1
   ) cd_high ON TRUE
),
violations AS (
   SELECT
       cd.device_id,
       cd.device_time,
       cd.sensor_name,
       cd.calibrated_value,
       CASE
           WHEN cd.sensor_type = 'temperature' AND cd.calibrated_value > 25 THEN 'Suhu Tinggi'
           WHEN cd.sensor_type = 'temperature' AND cd.calibrated_value < 0 THEN 'Suhu Rendah'
           WHEN cd.sensor_type = 'humidity' AND cd.calibrated_value > 80 THEN 'Kelembapan Tinggi'
           ELSE NULL
       END AS violation_type
   FROM calibrated_data cd
   WHERE cd.sensor_type IN ('temperature', 'humidity')
)
SELECT
   v.vehicle_label,
   o.object_label,
   v.registration_number,
   v.model,
   vio.device_time,
   vio.sensor_name,
   vio.calibrated_value,
   vio.violation_type
FROM violations vio
JOIN raw_business_data.objects o
  ON o.device_id = vio.device_id
LEFT JOIN raw_business_data.vehicles v
  ON v.object_id = o.object_id
ORDER BY vio.device_time DESC;

```

{% endcode %}

## **Berhenti Tidak Sah (24 Jam Terakhir)** <a href="#unauthorized-stops-last-24-hours" id="unauthorized-stops-last-24-hours"></a>

Kasus ini mengidentifikasi **berhenti tidak sah atau tidak terencana** yang dilakukan oleh kendaraan dalam 24 jam terakhir. Ini membantu mendeteksi potensi pelanggaran rute pengiriman, istirahat tanpa izin, atau waktu menganggur yang dapat memengaruhi efisiensi bahan bakar dan kinerja SLA.

Kueri ini menganalisis **titik lokasi dengan kecepatan rendah atau nol** menggunakan Kueri ini menggunakan tabel tracking\_data\_core dari raw\_telematics\_data untuk mengekstrak data lokasi deret waktu dan kecepatan. Berhenti terdeteksi ketika **kecepatan turun di bawah 3 km/jam** selama durasi **lebih dari 2 menit**. Dengan menggunakan fungsi LAG dan LEAD, kueri ini membagi periode kecepatan rendah tersebut untuk menentukan stempel waktu mulai dan akhir berhenti.

Untuk mendeteksi **berhenti tidak sah**, kueri ini memfilter lokasi yang berada dalam **zona geofenced** yang diketahui (tabel zones) menggunakan ST\_DWithin dari PostGIS. Hanya berhenti **di luar buffer zona mana pun** yang dilaporkan. Hasilnya mencakup ID kendaraan, label objek, registrasi, stempel waktu, durasi, dan koordinat untuk setiap berhenti.

{% code expandable="true" %}

```sql
WITH speed_data AS (
    SELECT
        td.device_id,
        td.device_time,
        td.speed / 100.0 AS speed_kph,
        td.latitude / 1e7 AS lat,
        td.longitude / 1e7 AS lon
    FROM raw_telematics_data.tracking_data_core td
    WHERE td.device_time >= now() - interval '1 day'
),
low_speed_points AS (
    SELECT
        *,
        LAG(device_time) OVER (PARTITION BY device_id ORDER BY device_time) AS prev_time,
        LAG(speed_kph) OVER (PARTITION BY device_id ORDER BY device_time) AS prev_speed
    FROM speed_data
),
stops_marked AS (
    SELECT *,
        CASE 
            WHEN speed_kph < 3 AND (prev_speed >= 3 OR prev_speed IS NULL) THEN 1 
            ELSE 0 
        END AS stop_start,
        CASE 
            WHEN speed_kph >= 3 AND prev_speed < 3 THEN 1 
            ELSE 0 
        END AS stop_end
    FROM low_speed_points
),
stop_segments AS (
    SELECT
        device_id,
        device_time AS stop_start_time,
        LEAD(device_time) OVER (PARTITION BY device_id ORDER BY device_time) AS stop_end_time,
        lat,
        lon
    FROM stops_marked
    WHERE stop_start = 1
),
unauthorized_stops AS (
    SELECT
        ss.*,
        EXTRACT(EPOCH FROM (ss.stop_end_time - ss.stop_start_time))/60 AS stop_duration_min
    FROM stop_segments ss
    LEFT JOIN raw_business_data.zones z ON
        ST_DWithin(
            ST_SetSRID(ST_MakePoint(ss.lon, ss.lat), 4326)::geography,
            ST_SetSRID(ST_MakePoint(z.circle_center_longitude, z.circle_center_latitude), 4326)::geography,
            z.radius
        )
    WHERE z.zone_id IS NULL  -- not inside any known zone
      AND EXTRACT(EPOCH FROM (ss.stop_end_time - ss.stop_start_time)) > 120  -- minimum 2 minutes
),
with_metadata AS (
    SELECT
        us.*,
        o.object_label,
        v.vehicle_label,
        v.registration_number
    FROM unauthorized_stops us
    JOIN raw_business_data.objects o ON o.device_id = us.device_id
    LEFT JOIN raw_business_data.vehicles v ON v.object_id = o.object_id
)
SELECT
    vehicle_label,
    registration_number,
    object_label,
    stop_start_time,
    stop_end_time,
    ROUND(stop_duration_min, 1) AS stop_duration_minutes,
    ROUND(lat, 6) AS latitude,
    ROUND(lon, 6) AS longitude
FROM with_metadata
ORDER BY stop_start_time DESC;
```

{% endcode %}

## **Deteksi Penggunaan di Luar Jam Kerja** <a href="#off-hour-usage-detection" id="off-hour-usage-detection"></a>

Kasus ini mengidentifikasi kejadian ketika kendaraan dioperasikan **di luar jam kerja normal** — yang didefinisikan di sini sebagai **Senin hingga Jumat, 09:00–18:00**. Deteksi seperti ini sangat penting untuk menandai **penggunaan tanpa izin**, mengidentifikasi potensi **penyalahgunaan kendaraan**, dan meningkatkan **keamanan aset**.

Logika ini dibangun berdasarkan tabel tracking\_data\_core dari raw\_telematics\_data, yang mencatat peristiwa GPS berstempel waktu per perangkat. Kami menurunkan **hari dalam seminggu** dan **jam penggunaan** dari setiap entri device\_time dan memfilter catatan **di luar jendela bisnis yang ditentukan** (yaitu, sebelum pukul 9 pagi, setelah pukul 6 sore, atau kapan saja pada akhir pekan).

Untuk memberikan kejelasan, kami memperkaya data GPS dengan metadata objek dan kendaraan dari raw\_business\_data (misalnya, label kendaraan, registrasi, ID objek). Untuk ringkasan yang lebih bermakna, kami secara opsional mengagregasi penggunaan untuk menghitung **berapa banyak peristiwa di luar jam kerja** yang terjadi per kendaraan dan kapan peristiwa itu terjadi. Ini dapat membantu mengidentifikasi pola atau pelanggar berulang.

{% code expandable="true" %}

```sql
WITH gps_events AS (
    SELECT
        td.device_id,
        td.device_time,
        EXTRACT(DOW FROM td.device_time) AS day_of_week, -- 0=Sunday, 6=Saturday
        EXTRACT(HOUR FROM td.device_time) AS hour_of_day,
        td.latitude / 1e7 AS latitude,
        td.longitude / 1e7 AS longitude
    FROM raw_telematics_data.tracking_data_core td
    WHERE td.device_time >= now() - interval '7 days'
),
off_hour_events AS (
    SELECT
        ge.*
    FROM gps_events ge
    WHERE 
        day_of_week IN (0, 6)  -- Saturday or Sunday
        OR hour_of_day < 9 
        OR hour_of_day >= 18
),
with_metadata AS (
    SELECT
        o.object_label,
        v.vehicle_label,
        v.registration_number,
        e.first_name || ' ' || e.last_name AS driver_name,
        o.device_id,
        o.object_id,
        o.create_datetime,
        ge.device_time,
        ge.latitude,
        ge.longitude,
        ge.day_of_week,
        ge.hour_of_day
    FROM off_hour_events ge
    JOIN raw_business_data.objects o ON ge.device_id = o.device_id
    LEFT JOIN raw_business_data.vehicles v ON v.object_id = o.object_id
    LEFT JOIN raw_business_data.driver_history dh ON dh.object_id = o.object_id 
        AND dh.changed_datetime <= ge.device_time
    LEFT JOIN raw_business_data.employees e ON e.employee_id = dh.new_employee_id
)
SELECT
    object_label,
    vehicle_label,
    registration_number,
    driver_name,
    device_time,
    TO_CHAR(device_time, 'Day') AS weekday,
    TO_CHAR(device_time, 'HH24:MI') AS time_of_event,
    ROUND(latitude::numeric, 6) AS lat,
    ROUND(longitude::numeric, 6) AS lon
FROM with_metadata
ORDER BY device_time DESC;
```

{% endcode %}

## **Jumlah perjalanan per hari** <a href="#trip-counts-per-day" id="trip-counts-per-day"></a>

Kasus ini mengukur **berapa banyak perjalanan** yang diselesaikan setiap kendaraan setiap hari dan seberapa jauh mereka menempuh perjalanan, membantu tim logistik menilai **penggunaan kendaraan**, mengoptimalkan rute, dan mendeteksi anomali seperti perjalanan yang tidak selesai atau penggunaan yang tidak dilaporkan.

Untuk mendefinisikan **perjalanan**, kami menggunakan perubahan pada **status pergerakan kendaraan** — yaitu, transisi dari berhenti ke bergerak dan kembali ke berhenti. Dengan menggunakan nilai kecepatan dari tabel tracking\_data\_core, kueri ini membagi data berdasarkan transisi tersebut. Suatu perjalanan diidentifikasi sebagai **periode pergerakan berkelanjutan** di mana kecepatan tetap di atas ambang batas (misalnya, >5 km/jam).

Setiap perjalanan mencakup:

* Sebuah **tanda waktu dan lokasi awal** (titik bergerak pertama)
* Sebuah **tanda waktu dan lokasi akhir** (titik bergerak terakhir sebelum berhenti)
* The **Jarak Haversine** antara lokasi awal dan akhir

Kami menghitung jumlah perjalanan dan total jarak per hari per kendaraan, secara opsional diperkaya dengan label kendaraan dari tabel vehicles.

{% code expandable="true" %}

```sql
WITH base_points AS (
    SELECT
        td.device_id,
        td.device_time,
        td.latitude / 1e7 AS lat,
        td.longitude / 1e7 AS lon,
        td.speed / 100.0 AS speed_kph,
        LEAD(td.speed / 100.0) OVER (PARTITION BY td.device_id ORDER BY td.device_time) AS next_speed,
        LAG(td.speed / 100.0) OVER (PARTITION BY td.device_id ORDER BY td.device_time) AS prev_speed
    FROM raw_telematics_data.tracking_data_core td
    WHERE td.device_time >= now() - interval '7 days'
),
trip_segments AS (
    SELECT
        *,
        CASE 
            WHEN speed_kph >= 5 AND (prev_speed < 5 OR prev_speed IS NULL) THEN 'start'
            WHEN speed_kph < 5 AND prev_speed >= 5 THEN 'end'
        END AS trip_marker
    FROM base_points
),
trip_points AS (
    SELECT
        device_id,
        device_time AS trip_start_time,
        lat AS start_lat,
        lon AS start_lon,
        LEAD(device_time) OVER (PARTITION BY device_id ORDER BY device_time) AS trip_end_time,
        LEAD(lat) OVER (PARTITION BY device_id ORDER BY device_time) AS end_lat,
        LEAD(lon) OVER (PARTITION BY device_id ORDER BY device_time) AS end_lon
    FROM trip_segments
    WHERE trip_marker = 'start'
),
trip_metrics AS (
    SELECT
        tp.device_id,
        tp.trip_start_time,
        tp.trip_end_time,
        tp.start_lat,
        tp.start_lon,
        tp.end_lat,
        tp.end_lon,
        tp.trip_start_time::date AS trip_day,
        -- Perkiraan jarak menggunakan rumus Haversine (dalam km)
        111 * SQRT(POWER(tp.end_lat - tp.start_lat, 2) + POWER((tp.end_lon - tp.start_lon) * COS(RADIANS(tp.start_lat)), 2))::numeric AS distance_km
    FROM trip_points tp
    WHERE tp.trip_end_time IS NOT NULL
)
SELECT
    v.vehicle_label,
    v.registration_number,
    tm.device_id,
    tm.trip_day,
    COUNT(*) AS trip_count,
    ROUND(SUM(tm.distance_km), 2) AS total_distance_km
FROM trip_metrics tm
JOIN raw_business_data.objects o ON o.device_id = tm.device_id
LEFT JOIN raw_business_data.vehicles v ON v.object_id = o.object_id
GROUP BY v.vehicle_label, v.registration_number, tm.device_id, tm.trip_day
ORDER BY tm.trip_day DESC, v.vehicle_label;
```

{% endcode %}

## **Jumlah Mileage per Kendaraan per Hari (7 Hari Terakhir)** <a href="#mileage-count-per-vehicle-per-day-last-7-days" id="mileage-count-per-vehicle-per-day-last-7-days"></a>

Kasus ini menghitung **jarak tempuh harian** (dalam kilometer) untuk setiap kendaraan selama 7 hari terakhir. Ini sangat penting untuk melacak **pemanfaatan kendaraan**, memantau **efisiensi bahan bakar**, merencanakan **pemeliharaan**, dan mendeteksi penggunaan yang kurang atau berlebihan.

Kami mengekstrak semua rekaman GPS dari tracking\_data\_core untuk 7 hari terakhir. Setiap titik GPS memiliki tanda waktu, lintang, dan bujur. Untuk setiap kendaraan dan setiap hari, kami:

1. **Mengurutkan titik GPS secara kronologis** per perangkat.
2. **Menghitung jarak antara titik-titik berurutan** menggunakan rumus Haversine.
3. **Menjumlahkan jarak per hari per perangkat** untuk mendapatkan total jarak tempuh.

Pendekatan ini memberikan akurasi tinggi tanpa bergantung pada sensor odometer eksternal. Secara opsional, kueri menggabungkan objects dan vehicles untuk memperkaya hasil dengan metadata aset.

{% code expandable="true" %}

```sql
WITH gps_points AS (
    SELECT
        td.device_id,
        td.device_time,
        td.device_time::date AS trip_day,
        td.latitude / 1e7 AS lat,
        td.longitude / 1e7 AS lon,
        LAG(td.latitude / 1e7) OVER (PARTITION BY td.device_id, td.device_time::date ORDER BY td.device_time) AS prev_lat,
        LAG(td.longitude / 1e7) OVER (PARTITION BY td.device_id, td.device_time::date ORDER BY td.device_time) AS prev_lon
    FROM raw_telematics_data.tracking_data_core td
    WHERE td.device_time >= now() - interval '7 days'
),
distances AS (
    SELECT
        device_id,
        trip_day,
        -- Aproksimasi rumus Haversine dalam km
        111 * SQRT(POWER(lat - prev_lat, 2) + POWER((lon - prev_lon) * COS(RADIANS(lat)), 2)) AS segment_distance_km
    FROM gps_points
    WHERE prev_lat IS NOT NULL AND prev_lon IS NOT NULL
      AND ABS(lat - prev_lat) < 1 AND ABS(lon - prev_lon) < 1 -- kecualikan outlier
)
SELECT
    v.vehicle_label,
    v.registration_number,
    o.object_label,
    d.device_id,
    d.trip_day,
    ROUND(SUM(d.segment_distance_km)::numeric, 2) AS mileage_km
FROM distances d
JOIN raw_business_data.objects o ON o.device_id = d.device_id
LEFT JOIN raw_business_data.vehicles v ON v.object_id = o.object_id
GROUP BY v.vehicle_label, v.registration_number, o.object_label, d.device_id, d.trip_day
ORDER BY d.trip_day DESC, v.vehicle_label;
```

{% endcode %}

## **Laporan Log Peristiwa Kendaraan** <a href="#vehicle-event-log-report" id="vehicle-event-log-report"></a>

Kasus ini menyediakan laporan komprehensif atas semua **peristiwa terkait kendaraan** (misalnya, ignition, door open, harsh braking, dll.) di seluruh armada. Laporan ini mencakup **jenis peristiwa**, **tanda waktu**, dan **konteks kendaraan**, sehingga memungkinkan tim operasi untuk mengaudit perilaku, melacak aktivitas abnormal, atau mendorong peringatan dan analitik.

Sumber utama adalah tabel states dari skema raw\_telematics\_data. Setiap baris mencakup: device\_id (sumber peristiwa), device\_time (tanda waktu), state\_name (label peristiwa), dan value (status atau pengukuran).

Untuk membuat laporan yang dapat digunakan:

1. Kami mengekstrak semua rekaman dari 7 hari terakhir.
2. Kelompokkan berdasarkan **jenis peristiwa**, **kendaraan**, dan **tanggal** untuk menyediakan sebuah **jumlah seberapa sering setiap peristiwa terjadi** dan **kapan peristiwa tersebut terjadi**.
3. Perkaya hasil dengan metadata kendaraan (vehicle\_label, registration\_number, object\_label) melalui objects dan vehicles.

Ini menghasilkan **linimasa peristiwa harian** di seluruh armada - penting untuk diagnostik, analisis perilaku, dan pemeliharaan proaktif.

{% code expandable="true" %}

```sql
WITH raw_events AS (
    SELECT
        s.device_id,
        s.device_time,
        s.device_time::date AS event_day,
        s.state_name,
        s.value
    FROM raw_telematics_data.states s
    WHERE s.device_time >= now() - interval '7 days'
),
with_vehicle_info AS (
    SELECT
        re.device_id,
        re.event_day,
        re.device_time,
        re.state_name,
        re.value,
        v.vehicle_label,
        v.registration_number,
        o.object_label
    FROM raw_events re
    JOIN raw_business_data.objects o ON o.device_id = re.device_id
    LEFT JOIN raw_business_data.vehicles v ON v.object_id = o.object_id
),
event_summary AS (
    SELECT
        event_day,
        state_name,
        vehicle_label,
        registration_number,
        object_label,
        COUNT(*) AS event_count,
        MIN(device_time) AS first_occurred,
        MAX(device_time) AS last_occurred
    FROM with_vehicle_info
    GROUP BY event_day, state_name, vehicle_label, registration_number, object_label
)
SELECT
    event_day,
    vehicle_label,
    registration_number,
    object_label,
    state_name AS event_type,
    event_count,
    first_occurred,
    last_occurred
FROM event_summary
ORDER BY event_day DESC, vehicle_label, state_name;
```

{% endcode %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://navixy.com/docs/analytics/id/example-queries/logistics.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
