tracksolid_timescale_grafan.../02_tracksolid_full_schema_rev.sql

322 lines
13 KiB
MySQL
Raw Permalink Normal View History

2026-04-07 18:34:40 +00:00
-- =============================================================================
-- Fireside Communications — Tracksolid Pro Fleet Telemetry
-- PostgreSQL 16 + PostGIS 3 + TimescaleDB 2.15
-- Complete Database Bootstrap Script (Revised & Unified)
-- =============================================================================
-- Author: DevOps / Telematics Specialist
-- Version: 2.0 (DWH + TimescaleDB Integrated)
-- =============================================================================
-- =============================================================================
-- STEP 1 — DATABASE, EXTENSIONS, ROLES
-- =============================================================================
-- Database creation (Run manually if needed, or logical script starts here)
-- CREATE DATABASE tracksolid_db ENCODING = 'UTF8';
-- \connect tracksolid_db
-- ── Extensions ────────────────────────────────────────────────────────────────
CREATE EXTENSION IF NOT EXISTS postgis CASCADE;
CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS btree_gist;
-- ── Roles ─────────────────────────────────────────────────────────────────────
DO $$
BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'tracksolid_owner') THEN
CREATE ROLE tracksolid_owner WITH LOGIN PASSWORD 'SET_PASSWORD_IN_ENV';
END IF;
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'grafana_ro') THEN
CREATE ROLE grafana_ro WITH LOGIN PASSWORD 'SET_PASSWORD_IN_ENV';
END IF;
END$$;
GRANT CONNECT ON DATABASE tracksolid_db TO tracksolid_owner;
GRANT CONNECT ON DATABASE tracksolid_db TO grafana_ro;
-- =============================================================================
-- STEP 2 — SCHEMAS
-- =============================================================================
CREATE SCHEMA IF NOT EXISTS tracksolid AUTHORIZATION tracksolid_owner;
CREATE SCHEMA IF NOT EXISTS infrastructure AUTHORIZATION tracksolid_owner;
CREATE SCHEMA IF NOT EXISTS dwh_gold AUTHORIZATION tracksolid_owner;
ALTER DATABASE tracksolid_db SET search_path TO tracksolid, infrastructure, dwh_gold, public;
-- =============================================================================
-- STEP 3 — OPERATIONAL TABLES (tracksolid)
-- =============================================================================
-- 3.01 Master Device Registry
CREATE TABLE IF NOT EXISTS tracksolid.devices (
imei TEXT PRIMARY KEY,
device_name TEXT,
mc_type TEXT,
mc_type_use_scope TEXT,
vehicle_name TEXT,
vehicle_number TEXT,
vehicle_models TEXT,
vehicle_icon TEXT,
vin TEXT,
engine_number TEXT,
vehicle_brand TEXT,
fuel_100km NUMERIC(6,2),
driver_name TEXT,
driver_phone TEXT,
sim TEXT,
iccid TEXT,
imsi TEXT,
account TEXT,
customer_name TEXT,
device_group_id TEXT,
device_group TEXT,
activation_time TIMESTAMPTZ,
expiration TIMESTAMPTZ,
enabled_flag SMALLINT NOT NULL DEFAULT 1,
status TEXT DEFAULT 'active',
city TEXT,
current_mileage_km NUMERIC(12,2),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_synced_at TIMESTAMPTZ
);
-- 3.02 Token Cache
CREATE TABLE IF NOT EXISTS tracksolid.api_token_cache (
id BIGSERIAL PRIMARY KEY,
account TEXT NOT NULL UNIQUE,
access_token TEXT NOT NULL,
refresh_token TEXT,
app_key TEXT,
expires_at TIMESTAMPTZ NOT NULL,
obtained_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- 3.03 Ingestion Health Log
CREATE TABLE IF NOT EXISTS tracksolid.ingestion_log (
id BIGSERIAL PRIMARY KEY,
run_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
endpoint TEXT NOT NULL,
imei_count INTEGER NOT NULL DEFAULT 0,
rows_upserted INTEGER NOT NULL DEFAULT 0,
rows_inserted INTEGER NOT NULL DEFAULT 0,
duration_ms INTEGER NOT NULL DEFAULT 0,
success BOOLEAN NOT NULL DEFAULT TRUE,
error_code TEXT,
error_message TEXT
);
-- 3.04 Live Positions (Hot Snapshots)
CREATE TABLE IF NOT EXISTS tracksolid.live_positions (
imei TEXT PRIMARY KEY REFERENCES tracksolid.devices(imei),
geom geometry(Point, 4326),
lat DOUBLE PRECISION,
lng DOUBLE PRECISION,
pos_type TEXT,
confidence SMALLINT,
gps_time TIMESTAMPTZ,
hb_time TIMESTAMPTZ,
speed NUMERIC(7,2),
direction NUMERIC(6,2),
acc_status TEXT,
gps_signal SMALLINT,
gps_num SMALLINT,
elec_quantity NUMERIC(5,2),
power_value NUMERIC(5,2),
battery_power_val NUMERIC(5,2),
tracker_oil TEXT,
temperature NUMERIC(8,2),
current_mileage NUMERIC(12,2),
device_status TEXT,
expire_flag TEXT,
activation_flag TEXT,
loc_desc TEXT,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- 3.05 Position History (Hypertable Source)
CREATE TABLE IF NOT EXISTS tracksolid.position_history (
imei TEXT NOT NULL REFERENCES tracksolid.devices(imei),
gps_time TIMESTAMPTZ NOT NULL,
geom geometry(Point, 4326),
lat DOUBLE PRECISION,
lng DOUBLE PRECISION,
speed NUMERIC(7,2),
direction NUMERIC(6,2),
acc_status TEXT,
satellite SMALLINT,
current_mileage NUMERIC(12,2),
recorded_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (imei, gps_time)
);
-- 3.06 Trip Summaries
CREATE TABLE IF NOT EXISTS tracksolid.trips (
id BIGSERIAL PRIMARY KEY,
imei TEXT NOT NULL REFERENCES tracksolid.devices(imei),
start_time TIMESTAMPTZ NOT NULL,
end_time TIMESTAMPTZ,
start_geom geometry(Point, 4326),
end_geom geometry(Point, 4326),
distance_m NUMERIC(12,2), -- QA-02: Stored in Meters
avg_speed_kmh NUMERIC(7,2),
max_speed_kmh NUMERIC(7,2),
updated_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT trips_imei_start_unique UNIQUE (imei, start_time)
);
-- 3.07 Parking & Idling
CREATE TABLE IF NOT EXISTS tracksolid.parking_events (
id BIGSERIAL PRIMARY KEY,
imei TEXT NOT NULL REFERENCES tracksolid.devices(imei),
event_type TEXT CHECK (event_type IN ('parking', 'idling')),
start_time TIMESTAMPTZ NOT NULL,
end_time TIMESTAMPTZ,
duration_seconds INTEGER,
geom geometry(Point, 4326),
address TEXT,
updated_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT parking_dedup UNIQUE (imei, start_time, event_type)
);
-- 3.08 Alarms, OBD, Fault Codes
CREATE TABLE IF NOT EXISTS tracksolid.alarms (
id BIGSERIAL PRIMARY KEY,
imei TEXT REFERENCES tracksolid.devices(imei),
alarm_type TEXT,
alarm_time TIMESTAMPTZ,
geom geometry(Point, 4326),
lat DOUBLE PRECISION,
lng DOUBLE PRECISION,
speed NUMERIC(7,2),
acc_status TEXT,
updated_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT alarms_dedup UNIQUE (imei, alarm_type, alarm_time)
2026-04-07 18:34:40 +00:00
);
CREATE TABLE IF NOT EXISTS tracksolid.obd_readings (
id BIGSERIAL PRIMARY KEY,
imei TEXT REFERENCES tracksolid.devices(imei),
reading_time TIMESTAMPTZ,
engine_rpm INTEGER,
fuel_level_pct NUMERIC(5,2),
updated_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT obd_readings_dedup UNIQUE (imei, reading_time)
2026-04-07 18:34:40 +00:00
);
-- =============================================================================
-- STEP 4 — TIMESCALEDB CONFIGURATION
-- =============================================================================
-- Convert to Hypertable
SELECT create_hypertable('tracksolid.position_history', 'gps_time', chunk_time_interval => INTERVAL '7 days', if_not_exists => TRUE);
-- Enable Compression
ALTER TABLE tracksolid.position_history SET (timescaledb.compress, timescaledb.compress_segmentby = 'imei');
SELECT add_compression_policy('tracksolid.position_history', INTERVAL '14 days');
-- Retention: 90 Days for Hot History
SELECT add_retention_policy('tracksolid.position_history', INTERVAL '90 days');
-- =============================================================================
-- STEP 5 — DATA WAREHOUSE GOLD LAYER (dwh_gold)
-- =============================================================================
CREATE TABLE dwh_gold.dim_vehicles (
vehicle_key SERIAL PRIMARY KEY,
imei TEXT UNIQUE,
vehicle_number TEXT,
is_active BOOLEAN DEFAULT TRUE
);
CREATE TABLE dwh_gold.fact_daily_fleet_metrics (
day DATE NOT NULL,
vehicle_key INTEGER REFERENCES dwh_gold.dim_vehicles(vehicle_key),
total_distance_km NUMERIC(12,2),
max_speed_kmh NUMERIC(7,2),
idle_hours NUMERIC(5,2),
PRIMARY KEY (day, vehicle_key)
);
-- =============================================================================
-- STEP 6 — TRIGGERS & VIEWS
-- =============================================================================
-- Updated_at Trigger Function
CREATE OR REPLACE FUNCTION tracksolid.set_updated_at() RETURNS TRIGGER AS $$
BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql;
-- Apply trigger to tables with updated_at column
2026-04-07 18:34:40 +00:00
DO $$
DECLARE t TEXT;
BEGIN
FOR t IN
SELECT pt.tablename
FROM pg_tables pt
JOIN information_schema.columns c
ON c.table_schema = pt.schemaname AND c.table_name = pt.tablename
WHERE pt.schemaname = 'tracksolid' AND c.column_name = 'updated_at'
2026-04-07 18:34:40 +00:00
LOOP
EXECUTE format('CREATE TRIGGER trg_upd_%I BEFORE UPDATE ON tracksolid.%I FOR EACH ROW EXECUTE FUNCTION tracksolid.set_updated_at()', t, t);
END LOOP;
END $$;
-- Enriched Live View
CREATE OR REPLACE VIEW tracksolid.v_fleet_status AS
SELECT
d.imei, d.vehicle_number, d.driver_name,
lp.lat, lp.lng, lp.geom, lp.speed, lp.acc_status, lp.gps_time,
CASE
WHEN lp.gps_time >= NOW() - INTERVAL '5 minutes' THEN 'online'
WHEN lp.gps_time >= NOW() - INTERVAL '30 minutes' THEN 'recent'
ELSE 'offline'
END AS connectivity_status,
EXTRACT(EPOCH FROM (NOW() - lp.gps_time))::int AS seconds_since_fix
FROM tracksolid.devices d
LEFT JOIN tracksolid.live_positions lp USING (imei)
WHERE d.enabled_flag = 1;
-- Ingestion Health View
CREATE OR REPLACE VIEW tracksolid.v_ingestion_health AS
SELECT DISTINCT ON (endpoint) endpoint, run_at, success, error_message, EXTRACT(EPOCH FROM (NOW() - run_at))::int AS seconds_ago
FROM tracksolid.ingestion_log ORDER BY endpoint, run_at DESC;
-- =============================================================================
-- STEP 7 — CONTINUOUS AGGREGATES (Performance)
-- =============================================================================
CREATE MATERIALIZED VIEW tracksolid.v_mileage_daily_cagg
WITH (timescaledb.continuous) AS
SELECT
time_bucket('1 day', gps_time) AS bucket,
imei,
(MAX(current_mileage) - MIN(current_mileage)) AS dist_km,
AVG(speed) AS avg_speed
FROM tracksolid.position_history
GROUP BY bucket, imei;
SELECT add_continuous_aggregate_policy('tracksolid.v_mileage_daily_cagg',
start_offset => INTERVAL '3 days', end_offset => INTERVAL '1 hour', schedule_interval => INTERVAL '1 hour');
-- =============================================================================
-- STEP 8 — PERMISSIONS
-- =============================================================================
GRANT USAGE ON SCHEMA tracksolid, infrastructure, dwh_gold TO tracksolid_owner, grafana_ro;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA tracksolid, infrastructure, dwh_gold TO tracksolid_owner;
GRANT SELECT ON ALL TABLES IN SCHEMA tracksolid, infrastructure, dwh_gold TO grafana_ro;
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO grafana_ro;
ALTER DEFAULT PRIVILEGES IN SCHEMA tracksolid GRANT ALL ON TABLES TO tracksolid_owner;
ALTER DEFAULT PRIVILEGES IN SCHEMA tracksolid GRANT SELECT ON TABLES TO grafana_ro;
-- =============================================================================
-- STEP 9 — VERIFICATION
-- =============================================================================
SELECT PostGIS_Full_Version();
SELECT * FROM timescaledb_information.hypertables;