734 lines
22 KiB
Markdown
734 lines
22 KiB
Markdown
|
|
# Tracksolid — Docker & Remote Database Command Reference
|
||
|
|
|
||
|
|
**Server:** `rahamafresh.com` · **User:** `kianiadee`
|
||
|
|
**DB:** `tracksolid_db` · **DB user:** `postgres`
|
||
|
|
**Container:** TimescaleDB — name includes a Coolify-generated suffix that changes on every redeploy
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Table of Contents
|
||
|
|
|
||
|
|
1. [How It Works](#1-how-it-works)
|
||
|
|
2. [Setup — Shell Function on the Server](#2-setup--shell-function-on-the-server)
|
||
|
|
3. [Connecting From Your Mac](#3-connecting-from-your-mac)
|
||
|
|
4. [Fleet & Live Positions](#4-fleet--live-positions)
|
||
|
|
5. [Trips & Movement](#5-trips--movement)
|
||
|
|
6. [Alarms](#6-alarms)
|
||
|
|
7. [Parking & Idle Time](#7-parking--idle-time)
|
||
|
|
8. [Position History & Route Replay](#8-position-history--route-replay)
|
||
|
|
9. [Ingestion Pipeline Health](#9-ingestion-pipeline-health)
|
||
|
|
10. [Device & Fleet Registry](#10-device--fleet-registry)
|
||
|
|
11. [Schema & Migrations](#11-schema--migrations)
|
||
|
|
12. [Container & Service Operations](#12-container--service-operations)
|
||
|
|
13. [Quick Reference — Flag Meanings](#13-quick-reference--flag-meanings)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 1. How It Works
|
||
|
|
|
||
|
|
All remote database access uses two layers:
|
||
|
|
|
||
|
|
```
|
||
|
|
Your Mac → SSH to rahamafresh.com → docker exec into timescale_db → psql
|
||
|
|
```
|
||
|
|
|
||
|
|
**Layer 1 — find the container.** Coolify appends a random suffix to container names on every redeploy, so the container name is never hardcoded. Instead, resolve it dynamically:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
TS_DB=$(docker ps --filter "name=timescale_db" --format "{{.Names}}" | head -1)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Layer 2 — run psql inside it.**
|
||
|
|
|
||
|
|
```bash
|
||
|
|
docker exec -i "$TS_DB" psql -U postgres -d tracksolid_db -c "SELECT ..."
|
||
|
|
```
|
||
|
|
|
||
|
|
Use `-i` (not `-it`) when running non-interactively or piping input — the `-t` TTY flag conflicts with stdin redirection and causes errors.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 2. Setup — Shell Function on the Server
|
||
|
|
|
||
|
|
Add this once to `~/.zshrc` on the server. It resolves the container automatically and passes all arguments through to `psql`.
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Auto-resolves current TimescaleDB container — survives Coolify redeployments
|
||
|
|
tsdb() {
|
||
|
|
local container
|
||
|
|
container=$(docker ps --filter "name=timescale_db" --format "{{.Names}}" | head -1)
|
||
|
|
if [[ -z "$container" ]]; then
|
||
|
|
echo "ERROR: no running timescale_db container found" >&2
|
||
|
|
return 1
|
||
|
|
fi
|
||
|
|
docker exec -it "$container" psql -U postgres -d tracksolid_db "$@"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Activate:**
|
||
|
|
```bash
|
||
|
|
source ~/.zshrc
|
||
|
|
```
|
||
|
|
|
||
|
|
**Usage on the server:**
|
||
|
|
```bash
|
||
|
|
tsdb # open interactive psql prompt
|
||
|
|
tsdb -c "SELECT COUNT(*) FROM tracksolid.trips;"
|
||
|
|
tsdb -c "\dt tracksolid.*" # list all tracksolid tables
|
||
|
|
tsdb -c "\d tracksolid.trips" # describe a table
|
||
|
|
tsdb -f /app/my_query.sql # run a SQL file
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 3. Connecting From Your Mac
|
||
|
|
|
||
|
|
### Open an interactive psql session
|
||
|
|
```bash
|
||
|
|
ssh kianiadee@rahamafresh.com -t \
|
||
|
|
'TS=$(docker ps --filter "name=timescale_db" --format "{{.Names}}" | head -1) &&
|
||
|
|
docker exec -it "$TS" psql -U postgres -d tracksolid_db'
|
||
|
|
```
|
||
|
|
|
||
|
|
### Run a single query and return output to your terminal
|
||
|
|
```bash
|
||
|
|
ssh kianiadee@rahamafresh.com \
|
||
|
|
'TS=$(docker ps --filter "name=timescale_db" --format "{{.Names}}" | head -1) &&
|
||
|
|
docker exec -i "$TS" psql -U postgres -d tracksolid_db -c "SELECT now();"'
|
||
|
|
```
|
||
|
|
|
||
|
|
### Pipe a local SQL file into the remote database
|
||
|
|
```bash
|
||
|
|
ssh kianiadee@rahamafresh.com \
|
||
|
|
'TS=$(docker ps --filter "name=timescale_db" --format "{{.Names}}" | head -1) &&
|
||
|
|
docker exec -i "$TS" psql -U postgres -d tracksolid_db' \
|
||
|
|
< /path/to/local_query.sql
|
||
|
|
```
|
||
|
|
|
||
|
|
### Run a migration file from your Mac
|
||
|
|
```bash
|
||
|
|
ssh kianiadee@rahamafresh.com \
|
||
|
|
'TS=$(docker ps --filter "name=timescale_db" --format "{{.Names}}" | head -1) &&
|
||
|
|
docker exec -i "$TS" psql -U postgres -d tracksolid_db' \
|
||
|
|
< 05_enhancement_migration.sql
|
||
|
|
```
|
||
|
|
|
||
|
|
> **Note on quoting:** When embedding SQL in a shell one-liner, single-quote literals inside the SQL need to be escaped as `'"'"'`. The `tsdb()` function avoids this entirely — use it for complex queries.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 4. Fleet & Live Positions
|
||
|
|
|
||
|
|
### All devices with a position in the last hour
|
||
|
|
```bash
|
||
|
|
ssh kianiadee@rahamafresh.com \
|
||
|
|
'TS=$(docker ps --filter "name=timescale_db" --format "{{.Names}}" | head -1) &&
|
||
|
|
docker exec -i "$TS" psql -U postgres -d tracksolid_db -c "
|
||
|
|
SELECT
|
||
|
|
d.device_name,
|
||
|
|
d.mc_type,
|
||
|
|
ROUND(lp.lat::numeric, 5) AS lat,
|
||
|
|
ROUND(lp.lng::numeric, 5) AS lng,
|
||
|
|
lp.speed,
|
||
|
|
lp.acc_status,
|
||
|
|
lp.gps_signal,
|
||
|
|
lp.gps_num AS satellites,
|
||
|
|
lp.gps_time AT TIME ZONE '"'"'Africa/Nairobi'"'"' AS last_fix_eat,
|
||
|
|
ROUND(EXTRACT(EPOCH FROM (now() - lp.gps_time)) / 60.0, 0) AS mins_ago
|
||
|
|
FROM tracksolid.live_positions lp
|
||
|
|
JOIN tracksolid.devices d ON d.imei = lp.imei
|
||
|
|
WHERE lp.gps_time > now() - interval '"'"'1 hour'"'"'
|
||
|
|
ORDER BY lp.gps_time DESC;"'
|
||
|
|
```
|
||
|
|
|
||
|
|
### All 19 live positions — current snapshot
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
d.device_name,
|
||
|
|
d.mc_type,
|
||
|
|
ROUND(lp.lat::numeric, 5) AS lat,
|
||
|
|
ROUND(lp.lng::numeric, 5) AS lng,
|
||
|
|
lp.speed,
|
||
|
|
lp.acc_status,
|
||
|
|
lp.gps_time AT TIME ZONE 'Africa/Nairobi' AS last_fix_eat,
|
||
|
|
ROUND(EXTRACT(EPOCH FROM (now() - lp.gps_time)) / 3600.0, 1) AS hours_ago,
|
||
|
|
lp.current_mileage AS odometer_km
|
||
|
|
FROM tracksolid.live_positions lp
|
||
|
|
JOIN tracksolid.devices d ON d.imei = lp.imei
|
||
|
|
ORDER BY lp.gps_time DESC;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Devices with no position (silent fleet)
|
||
|
|
```sql
|
||
|
|
SELECT d.imei, d.device_name, d.mc_type, d.sim, d.expiration::date
|
||
|
|
FROM tracksolid.devices d
|
||
|
|
LEFT JOIN tracksolid.live_positions lp ON lp.imei = d.imei
|
||
|
|
WHERE lp.imei IS NULL
|
||
|
|
ORDER BY d.mc_type, d.device_name;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Vehicles currently moving (ACC on OR speed > 0)
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
d.device_name,
|
||
|
|
lp.speed,
|
||
|
|
lp.acc_status,
|
||
|
|
lp.gps_time AT TIME ZONE 'Africa/Nairobi' AS last_fix_eat
|
||
|
|
FROM tracksolid.live_positions lp
|
||
|
|
JOIN tracksolid.devices d ON d.imei = lp.imei
|
||
|
|
WHERE lp.speed > 0 OR lp.acc_status = '1'
|
||
|
|
ORDER BY lp.speed DESC;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Geographic anomaly check — vehicles outside Kenya
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
d.device_name,
|
||
|
|
ROUND(lp.lat::numeric, 4) AS lat,
|
||
|
|
ROUND(lp.lng::numeric, 4) AS lng,
|
||
|
|
lp.gps_time AT TIME ZONE 'Africa/Nairobi' AS last_fix_eat
|
||
|
|
FROM tracksolid.live_positions lp
|
||
|
|
JOIN tracksolid.devices d ON d.imei = lp.imei
|
||
|
|
WHERE lp.lat NOT BETWEEN -5.0 AND 5.0
|
||
|
|
OR lp.lng NOT BETWEEN 33.9 AND 42.0;
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 5. Trips & Movement
|
||
|
|
|
||
|
|
### Trips today — summary per vehicle
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
d.device_name,
|
||
|
|
d.vehicle_number,
|
||
|
|
d.driver_name,
|
||
|
|
COUNT(*) AS trips,
|
||
|
|
ROUND(SUM(t.distance_km)::numeric, 2) AS total_km,
|
||
|
|
ROUND(AVG(t.avg_speed_kmh)::numeric, 1) AS avg_speed_kmh,
|
||
|
|
MAX(t.max_speed_kmh) AS top_speed_kmh,
|
||
|
|
ROUND(SUM(t.driving_time_s) / 3600.0, 2) AS drive_hours,
|
||
|
|
ROUND(SUM(t.idle_time_s) / 3600.0, 2) AS idle_hours,
|
||
|
|
MIN(t.start_time AT TIME ZONE 'Africa/Nairobi') AS day_start,
|
||
|
|
MAX(t.end_time AT TIME ZONE 'Africa/Nairobi') AS day_end
|
||
|
|
FROM tracksolid.trips t
|
||
|
|
JOIN tracksolid.devices d ON d.imei = t.imei
|
||
|
|
WHERE t.start_time >= CURRENT_DATE AT TIME ZONE 'Africa/Nairobi'
|
||
|
|
GROUP BY d.device_name, d.vehicle_number, d.driver_name
|
||
|
|
ORDER BY total_km DESC;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Trips in last 24 hours — full detail
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
d.device_name,
|
||
|
|
t.start_time AT TIME ZONE 'Africa/Nairobi' AS start_eat,
|
||
|
|
t.end_time AT TIME ZONE 'Africa/Nairobi' AS end_eat,
|
||
|
|
ROUND(t.distance_km::numeric, 2) AS distance_km,
|
||
|
|
t.avg_speed_kmh,
|
||
|
|
t.max_speed_kmh,
|
||
|
|
ROUND(t.driving_time_s / 60.0, 0) AS drive_mins,
|
||
|
|
ROUND(t.idle_time_s / 60.0, 0) AS idle_mins,
|
||
|
|
t.source
|
||
|
|
FROM tracksolid.trips t
|
||
|
|
JOIN tracksolid.devices d ON d.imei = t.imei
|
||
|
|
WHERE t.start_time > now() - interval '24 hours'
|
||
|
|
ORDER BY t.start_time DESC;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Speeding incidents — trips where max speed exceeded threshold
|
||
|
|
```sql
|
||
|
|
-- Change 80 to your speed limit in km/h
|
||
|
|
SELECT
|
||
|
|
d.device_name,
|
||
|
|
d.driver_name,
|
||
|
|
t.start_time AT TIME ZONE 'Africa/Nairobi' AS trip_start,
|
||
|
|
t.max_speed_kmh,
|
||
|
|
ROUND(t.distance_km::numeric, 2) AS distance_km
|
||
|
|
FROM tracksolid.trips t
|
||
|
|
JOIN tracksolid.devices d ON d.imei = t.imei
|
||
|
|
WHERE t.max_speed_kmh > 80
|
||
|
|
ORDER BY t.max_speed_kmh DESC;
|
||
|
|
```
|
||
|
|
|
||
|
|
### After-hours trips (before 06:00 or after 20:00 EAT)
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
d.device_name,
|
||
|
|
d.driver_name,
|
||
|
|
t.start_time AT TIME ZONE 'Africa/Nairobi' AS start_eat,
|
||
|
|
ROUND(t.distance_km::numeric, 2) AS distance_km
|
||
|
|
FROM tracksolid.trips t
|
||
|
|
JOIN tracksolid.devices d ON d.imei = t.imei
|
||
|
|
WHERE EXTRACT(HOUR FROM t.start_time AT TIME ZONE 'Africa/Nairobi') >= 20
|
||
|
|
OR EXTRACT(HOUR FROM t.start_time AT TIME ZONE 'Africa/Nairobi') < 6
|
||
|
|
ORDER BY t.start_time DESC;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Fleet utilisation rate per vehicle (today)
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
d.device_name,
|
||
|
|
d.driver_name,
|
||
|
|
ROUND(SUM(t.driving_time_s) / 3600.0, 2) AS drive_hours,
|
||
|
|
ROUND(SUM(t.idle_time_s) / 3600.0, 2) AS idle_hours,
|
||
|
|
ROUND(SUM(t.driving_time_s) / (10.0 * 3600) * 100, 1) AS utilisation_pct
|
||
|
|
FROM tracksolid.trips t
|
||
|
|
JOIN tracksolid.devices d ON d.imei = t.imei
|
||
|
|
WHERE t.start_time >= CURRENT_DATE AT TIME ZONE 'Africa/Nairobi'
|
||
|
|
GROUP BY d.device_name, d.driver_name
|
||
|
|
ORDER BY utilisation_pct DESC;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Vehicles that did not move today
|
||
|
|
```sql
|
||
|
|
SELECT d.device_name, d.mc_type, d.driver_name
|
||
|
|
FROM tracksolid.devices d
|
||
|
|
LEFT JOIN tracksolid.trips t
|
||
|
|
ON t.imei = d.imei
|
||
|
|
AND t.start_time >= CURRENT_DATE AT TIME ZONE 'Africa/Nairobi'
|
||
|
|
WHERE t.imei IS NULL
|
||
|
|
ORDER BY d.device_name;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Distance per driver — last 30 days
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
d.driver_name,
|
||
|
|
COUNT(DISTINCT DATE(t.start_time AT TIME ZONE 'Africa/Nairobi')) AS active_days,
|
||
|
|
COUNT(*) AS total_trips,
|
||
|
|
ROUND(SUM(t.distance_km)::numeric, 0) AS total_km,
|
||
|
|
ROUND(AVG(t.distance_km)::numeric, 1) AS avg_km_per_trip,
|
||
|
|
MAX(t.max_speed_kmh) AS top_speed_ever
|
||
|
|
FROM tracksolid.trips t
|
||
|
|
JOIN tracksolid.devices d ON d.imei = t.imei
|
||
|
|
WHERE t.start_time > now() - interval '30 days'
|
||
|
|
AND d.driver_name IS NOT NULL AND d.driver_name != ''
|
||
|
|
GROUP BY d.driver_name
|
||
|
|
ORDER BY total_km DESC;
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 6. Alarms
|
||
|
|
|
||
|
|
### All alarms — last 24 hours
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
d.device_name,
|
||
|
|
a.alarm_type,
|
||
|
|
a.alarm_name,
|
||
|
|
a.alarm_time AT TIME ZONE 'Africa/Nairobi' AS alarm_time_eat,
|
||
|
|
ROUND(a.lat::numeric, 5) AS lat,
|
||
|
|
ROUND(a.lng::numeric, 5) AS lng,
|
||
|
|
a.speed,
|
||
|
|
a.severity,
|
||
|
|
a.acknowledged_at
|
||
|
|
FROM tracksolid.alarms a
|
||
|
|
JOIN tracksolid.devices d ON d.imei = a.imei
|
||
|
|
WHERE a.alarm_time > now() - interval '24 hours'
|
||
|
|
ORDER BY a.alarm_time DESC;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Alarm summary by type — last 7 days
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
a.alarm_name,
|
||
|
|
a.alarm_type,
|
||
|
|
COUNT(*) AS occurrences,
|
||
|
|
COUNT(DISTINCT a.imei) AS devices_affected,
|
||
|
|
MAX(a.alarm_time AT TIME ZONE 'Africa/Nairobi') AS last_seen_eat
|
||
|
|
FROM tracksolid.alarms a
|
||
|
|
WHERE a.alarm_time > now() - interval '7 days'
|
||
|
|
GROUP BY a.alarm_name, a.alarm_type
|
||
|
|
ORDER BY occurrences DESC;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Unacknowledged alarms
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
d.device_name,
|
||
|
|
a.alarm_name,
|
||
|
|
a.alarm_time AT TIME ZONE 'Africa/Nairobi' AS alarm_time_eat,
|
||
|
|
ROUND(EXTRACT(EPOCH FROM (now() - a.alarm_time)) / 3600.0, 1) AS hours_open
|
||
|
|
FROM tracksolid.alarms a
|
||
|
|
JOIN tracksolid.devices d ON d.imei = a.imei
|
||
|
|
WHERE a.acknowledged_at IS NULL
|
||
|
|
ORDER BY a.alarm_time DESC;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Acknowledge an alarm
|
||
|
|
```sql
|
||
|
|
UPDATE tracksolid.alarms
|
||
|
|
SET acknowledged_at = now(),
|
||
|
|
acknowledged_by = 'operator_name'
|
||
|
|
WHERE id = <alarm_id>;
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 7. Parking & Idle Time
|
||
|
|
|
||
|
|
### Parking events today
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
d.device_name,
|
||
|
|
pe.event_type,
|
||
|
|
pe.start_time AT TIME ZONE 'Africa/Nairobi' AS start_eat,
|
||
|
|
pe.end_time AT TIME ZONE 'Africa/Nairobi' AS end_eat,
|
||
|
|
ROUND(pe.duration_seconds / 60.0, 0) AS duration_mins,
|
||
|
|
pe.address
|
||
|
|
FROM tracksolid.parking_events pe
|
||
|
|
JOIN tracksolid.devices d ON d.imei = pe.imei
|
||
|
|
WHERE pe.start_time >= CURRENT_DATE AT TIME ZONE 'Africa/Nairobi'
|
||
|
|
ORDER BY pe.start_time DESC;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Longest idle periods — last 7 days
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
d.device_name,
|
||
|
|
d.driver_name,
|
||
|
|
pe.start_time AT TIME ZONE 'Africa/Nairobi' AS idle_start,
|
||
|
|
ROUND(pe.duration_seconds / 3600.0, 2) AS idle_hours,
|
||
|
|
pe.address
|
||
|
|
FROM tracksolid.parking_events pe
|
||
|
|
JOIN tracksolid.devices d ON d.imei = pe.imei
|
||
|
|
WHERE pe.start_time > now() - interval '7 days'
|
||
|
|
ORDER BY pe.duration_seconds DESC
|
||
|
|
LIMIT 20;
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 8. Position History & Route Replay
|
||
|
|
|
||
|
|
### Position history by source — counts
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
source,
|
||
|
|
COUNT(*) AS fixes,
|
||
|
|
MIN(gps_time AT TIME ZONE 'Africa/Nairobi') AS earliest,
|
||
|
|
MAX(gps_time AT TIME ZONE 'Africa/Nairobi') AS latest
|
||
|
|
FROM tracksolid.position_history
|
||
|
|
GROUP BY source;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Route replay for a specific vehicle — last 24 hours
|
||
|
|
```sql
|
||
|
|
-- Replace 'FRED KMGW 538W HULETI' with the device_name you want
|
||
|
|
SELECT
|
||
|
|
ph.gps_time AT TIME ZONE 'Africa/Nairobi' AS gps_time_eat,
|
||
|
|
ROUND(ph.lat::numeric, 5) AS lat,
|
||
|
|
ROUND(ph.lng::numeric, 5) AS lng,
|
||
|
|
ph.speed,
|
||
|
|
ph.direction,
|
||
|
|
ph.acc_status,
|
||
|
|
ph.source
|
||
|
|
FROM tracksolid.position_history ph
|
||
|
|
JOIN tracksolid.devices d ON d.imei = ph.imei
|
||
|
|
WHERE d.device_name = 'FRED KMGW 538W HULETI'
|
||
|
|
AND ph.gps_time > now() - interval '24 hours'
|
||
|
|
ORDER BY ph.gps_time ASC;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Fix density per device — last 24 hours
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
d.device_name,
|
||
|
|
COUNT(*) AS total_fixes,
|
||
|
|
COUNT(*) FILTER (WHERE ph.source = 'poll') AS poll_fixes,
|
||
|
|
COUNT(*) FILTER (WHERE ph.source = 'track_list') AS track_list_fixes,
|
||
|
|
MIN(ph.gps_time AT TIME ZONE 'Africa/Nairobi') AS first_fix,
|
||
|
|
MAX(ph.gps_time AT TIME ZONE 'Africa/Nairobi') AS last_fix
|
||
|
|
FROM tracksolid.position_history ph
|
||
|
|
JOIN tracksolid.devices d ON d.imei = ph.imei
|
||
|
|
WHERE ph.gps_time > now() - interval '24 hours'
|
||
|
|
GROUP BY d.device_name
|
||
|
|
ORDER BY total_fixes DESC;
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 9. Ingestion Pipeline Health
|
||
|
|
|
||
|
|
### Pipeline health — last hour (key check)
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
endpoint,
|
||
|
|
COUNT(*) AS calls,
|
||
|
|
SUM(rows_upserted) AS upserted,
|
||
|
|
SUM(rows_inserted) AS inserted,
|
||
|
|
ROUND(AVG(duration_ms)::numeric, 0) AS avg_ms,
|
||
|
|
COUNT(*) FILTER (WHERE success = false) AS failures,
|
||
|
|
MAX(run_at AT TIME ZONE 'Africa/Nairobi') AS last_call_eat
|
||
|
|
FROM tracksolid.ingestion_log
|
||
|
|
WHERE run_at > now() - interval '1 hour'
|
||
|
|
GROUP BY endpoint
|
||
|
|
ORDER BY calls DESC;
|
||
|
|
```
|
||
|
|
|
||
|
|
### All-time ingestion summary
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
endpoint,
|
||
|
|
COUNT(*) AS total_calls,
|
||
|
|
SUM(rows_upserted) AS total_upserted,
|
||
|
|
SUM(rows_inserted) AS total_inserted,
|
||
|
|
ROUND(AVG(duration_ms)::numeric, 0) AS avg_ms,
|
||
|
|
COUNT(*) FILTER (WHERE success = false) AS failures,
|
||
|
|
MIN(run_at AT TIME ZONE 'Africa/Nairobi') AS first_call,
|
||
|
|
MAX(run_at AT TIME ZONE 'Africa/Nairobi') AS last_call
|
||
|
|
FROM tracksolid.ingestion_log
|
||
|
|
GROUP BY endpoint
|
||
|
|
ORDER BY total_calls DESC;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Recent calls — last 20 entries
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
run_at AT TIME ZONE 'Africa/Nairobi' AS run_eat,
|
||
|
|
endpoint,
|
||
|
|
imei_count,
|
||
|
|
rows_upserted,
|
||
|
|
rows_inserted,
|
||
|
|
duration_ms,
|
||
|
|
success,
|
||
|
|
error_message
|
||
|
|
FROM tracksolid.ingestion_log
|
||
|
|
ORDER BY run_at DESC
|
||
|
|
LIMIT 20;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Failed calls — all time
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
run_at AT TIME ZONE 'Africa/Nairobi' AS run_eat,
|
||
|
|
endpoint,
|
||
|
|
error_code,
|
||
|
|
error_message
|
||
|
|
FROM tracksolid.ingestion_log
|
||
|
|
WHERE success = false
|
||
|
|
ORDER BY run_at DESC;
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 10. Device & Fleet Registry
|
||
|
|
|
||
|
|
### Full fleet — all 63 devices
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
device_name,
|
||
|
|
mc_type,
|
||
|
|
vehicle_number,
|
||
|
|
driver_name,
|
||
|
|
sim,
|
||
|
|
ROUND(current_mileage_km::numeric, 0) AS odometer_km,
|
||
|
|
expiration::date AS expires,
|
||
|
|
enabled_flag
|
||
|
|
FROM tracksolid.devices
|
||
|
|
ORDER BY mc_type, device_name;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Fleet by model
|
||
|
|
```sql
|
||
|
|
SELECT mc_type, COUNT(*) AS devices
|
||
|
|
FROM tracksolid.devices
|
||
|
|
GROUP BY mc_type ORDER BY devices DESC;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Odometer leaders — top 15
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
device_name,
|
||
|
|
mc_type,
|
||
|
|
sim,
|
||
|
|
ROUND(current_mileage_km::numeric, 0) AS odometer_km,
|
||
|
|
expiration::date AS expires
|
||
|
|
FROM tracksolid.devices
|
||
|
|
WHERE current_mileage_km IS NOT NULL AND current_mileage_km > 0
|
||
|
|
ORDER BY current_mileage_km DESC
|
||
|
|
LIMIT 15;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Devices needing metadata (blank vehicle_number or driver_name)
|
||
|
|
```sql
|
||
|
|
SELECT device_name, mc_type, sim
|
||
|
|
FROM tracksolid.devices
|
||
|
|
WHERE vehicle_number IS NULL OR vehicle_number = ''
|
||
|
|
OR driver_name IS NULL OR driver_name = ''
|
||
|
|
ORDER BY mc_type, device_name;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Subscription status breakdown
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
COUNT(*) FILTER (WHERE expiration IS NULL) AS no_expiry_set,
|
||
|
|
COUNT(*) FILTER (WHERE expiration < now()) AS already_expired,
|
||
|
|
COUNT(*) FILTER (WHERE expiration BETWEEN now() AND now() + interval '90 days') AS expiring_90days,
|
||
|
|
COUNT(*) FILTER (WHERE expiration > now() + interval '90 days') AS valid_long_term
|
||
|
|
FROM tracksolid.devices;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Update vehicle metadata for a device
|
||
|
|
```sql
|
||
|
|
UPDATE tracksolid.devices
|
||
|
|
SET vehicle_number = 'KDA 123B',
|
||
|
|
driver_name = 'John Kamau',
|
||
|
|
vehicle_category = 'van'
|
||
|
|
WHERE device_name = 'FRED KMGW 538W HULETI';
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 11. Schema & Migrations
|
||
|
|
|
||
|
|
### Check applied migrations
|
||
|
|
```sql
|
||
|
|
SELECT filename, applied_at AT TIME ZONE 'Africa/Nairobi' AS applied_eat
|
||
|
|
FROM tracksolid.schema_migrations
|
||
|
|
ORDER BY applied_at;
|
||
|
|
```
|
||
|
|
|
||
|
|
### List all tables in tracksolid schema
|
||
|
|
```sql
|
||
|
|
SELECT table_name FROM information_schema.tables
|
||
|
|
WHERE table_schema = 'tracksolid'
|
||
|
|
ORDER BY table_name;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Row counts across all key tables
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
(SELECT COUNT(*) FROM tracksolid.devices) AS devices,
|
||
|
|
(SELECT COUNT(*) FROM tracksolid.live_positions) AS live_positions,
|
||
|
|
(SELECT COUNT(*) FROM tracksolid.position_history) AS position_history,
|
||
|
|
(SELECT COUNT(*) FROM tracksolid.trips) AS trips,
|
||
|
|
(SELECT COUNT(*) FROM tracksolid.alarms) AS alarms,
|
||
|
|
(SELECT COUNT(*) FROM tracksolid.parking_events) AS parking_events,
|
||
|
|
(SELECT COUNT(*) FROM tracksolid.obd_readings) AS obd_readings,
|
||
|
|
(SELECT COUNT(*) FROM tracksolid.device_events) AS device_events,
|
||
|
|
(SELECT COUNT(*) FROM tracksolid.fuel_readings) AS fuel_readings,
|
||
|
|
(SELECT COUNT(*) FROM tracksolid.temperature_readings) AS temperature_readings,
|
||
|
|
(SELECT COUNT(*) FROM tracksolid.lbs_readings) AS lbs_readings,
|
||
|
|
(SELECT COUNT(*) FROM tracksolid.ingestion_log) AS ingestion_log;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Describe a table
|
||
|
|
```bash
|
||
|
|
# On the server
|
||
|
|
tsdb -c "\d tracksolid.trips"
|
||
|
|
tsdb -c "\d tracksolid.live_positions"
|
||
|
|
```
|
||
|
|
|
||
|
|
### Run DWH gold ETL for yesterday
|
||
|
|
```sql
|
||
|
|
SELECT dwh_gold.refresh_daily_metrics(CURRENT_DATE - 1);
|
||
|
|
```
|
||
|
|
|
||
|
|
### Apply a migration manually (from your Mac)
|
||
|
|
```bash
|
||
|
|
ssh kianiadee@rahamafresh.com \
|
||
|
|
'TS=$(docker ps --filter "name=timescale_db" --format "{{.Names}}" | head -1) &&
|
||
|
|
docker exec -i "$TS" psql -U postgres -d tracksolid_db' \
|
||
|
|
< 05_enhancement_migration.sql
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 12. Container & Service Operations
|
||
|
|
|
||
|
|
### Find the current TimescaleDB container name
|
||
|
|
```bash
|
||
|
|
ssh kianiadee@rahamafresh.com \
|
||
|
|
'docker ps --filter "name=timescale_db" --format "{{.Names}}"'
|
||
|
|
```
|
||
|
|
|
||
|
|
### List all running containers
|
||
|
|
```bash
|
||
|
|
ssh kianiadee@rahamafresh.com 'docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"'
|
||
|
|
```
|
||
|
|
|
||
|
|
### Check container logs (last 100 lines)
|
||
|
|
```bash
|
||
|
|
# TimescaleDB
|
||
|
|
ssh kianiadee@rahamafresh.com \
|
||
|
|
'TS=$(docker ps --filter "name=timescale_db" --format "{{.Names}}" | head -1) &&
|
||
|
|
docker logs "$TS" --tail 100'
|
||
|
|
|
||
|
|
# Ingest movement service
|
||
|
|
ssh kianiadee@rahamafresh.com \
|
||
|
|
'SVC=$(docker ps --filter "name=ingest_movement" --format "{{.Names}}" | head -1) &&
|
||
|
|
docker logs "$SVC" --tail 100'
|
||
|
|
|
||
|
|
# Ingest events service
|
||
|
|
ssh kianiadee@rahamafresh.com \
|
||
|
|
'SVC=$(docker ps --filter "name=ingest_events" --format "{{.Names}}" | head -1) &&
|
||
|
|
docker logs "$SVC" --tail 100'
|
||
|
|
|
||
|
|
# Webhook receiver
|
||
|
|
ssh kianiadee@rahamafresh.com \
|
||
|
|
'SVC=$(docker ps --filter "name=webhook_receiver" --format "{{.Names}}" | head -1) &&
|
||
|
|
docker logs "$SVC" --tail 100'
|
||
|
|
```
|
||
|
|
|
||
|
|
### Follow logs live (stream)
|
||
|
|
```bash
|
||
|
|
ssh kianiadee@rahamafresh.com \
|
||
|
|
'SVC=$(docker ps --filter "name=ingest_movement" --format "{{.Names}}" | head -1) &&
|
||
|
|
docker logs "$SVC" --follow --tail 50'
|
||
|
|
```
|
||
|
|
|
||
|
|
### Restart a service
|
||
|
|
```bash
|
||
|
|
# Replace <service_name_prefix> with: ingest_movement, ingest_events, webhook_receiver
|
||
|
|
ssh kianiadee@rahamafresh.com \
|
||
|
|
'SVC=$(docker ps --filter "name=ingest_movement" --format "{{.Names}}" | head -1) &&
|
||
|
|
docker restart "$SVC"'
|
||
|
|
```
|
||
|
|
|
||
|
|
### Check disk space on the server
|
||
|
|
```bash
|
||
|
|
ssh kianiadee@rahamafresh.com 'df -h /'
|
||
|
|
```
|
||
|
|
|
||
|
|
### Check TimescaleDB chunk sizes (storage usage)
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
hypertable_name,
|
||
|
|
pg_size_pretty(SUM(total_bytes)) AS total_size
|
||
|
|
FROM timescaledb_information.chunks
|
||
|
|
GROUP BY hypertable_name
|
||
|
|
ORDER BY SUM(total_bytes) DESC;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Vacuum / analyse a table (maintenance)
|
||
|
|
```bash
|
||
|
|
ssh kianiadee@rahamafresh.com \
|
||
|
|
'TS=$(docker ps --filter "name=timescale_db" --format "{{.Names}}" | head -1) &&
|
||
|
|
docker exec -i "$TS" psql -U postgres -d tracksolid_db -c "
|
||
|
|
VACUUM ANALYSE tracksolid.position_history;"'
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 13. Quick Reference — Flag Meanings
|
||
|
|
|
||
|
|
| Flag | Context | Meaning |
|
||
|
|
|---|---|---|
|
||
|
|
| `--filter "name=X"` | `docker ps` | Match containers whose name contains `X` |
|
||
|
|
| `--format "{{.Names}}"` | `docker ps` | Output only the container name column |
|
||
|
|
| `head -1` | shell | Take only the first result (guard against duplicates) |
|
||
|
|
| `exec -i` | `docker exec` | Keep stdin open — required for piping SQL or running non-interactively |
|
||
|
|
| `exec -it` | `docker exec` | Add a TTY — use only for interactive sessions, not when piping |
|
||
|
|
| `-U postgres` | `psql` | Connect as the `postgres` superuser |
|
||
|
|
| `-d tracksolid_db` | `psql` | Target this database |
|
||
|
|
| `-c "..."` | `psql` | Run a single SQL statement and exit |
|
||
|
|
| `-f file.sql` | `psql` | Execute all SQL in a file |
|
||
|
|
| `AT TIME ZONE 'Africa/Nairobi'` | SQL | Convert UTC timestamp to EAT (UTC+3) for display |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
*Last updated: 2026-04-11*
|