The database schema is designed around PostgreSQL 15 with the pgvector extension for semantic search capabilities. The schema enforces strict referential integrity and leverages Row Level Security (RLS) for multi-tenant data isolation.
Stores user account information and subscription details.
CREATE TABLE public.users (
user_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
firebase_uid VARCHAR(128) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
display_name VARCHAR(255),
avatar_url TEXT,
tier VARCHAR(20) CHECK (tier IN ('free', 'pro', 'mechanic', 'dealer')) DEFAULT 'free',
stripe_customer_id VARCHAR(255),
subscription_status VARCHAR(50) CHECK (subscription_status IN ('active', 'canceled', 'past_due', 'trialing', 'none')) DEFAULT 'none',
subscription_period_end TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
last_login_at TIMESTAMP,
preferences JSONB DEFAULT '{}'::jsonb
);
-- Indexes
CREATE INDEX idx_users_firebase_uid ON public.users(firebase_uid);
CREATE INDEX idx_users_email ON public.users(email);
CREATE INDEX idx_users_tier ON public.users(tier);
-- RLS Policies
ALTER TABLE public.users ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view their own profile"
ON public.users FOR SELECT
USING (auth.uid() = firebase_uid);
CREATE POLICY "Users can update their own profile"
ON public.users FOR UPDATE
USING (auth.uid() = firebase_uid);
Central table for vehicle information decoded from VIN.
CREATE TABLE public.vehicles (
vehicle_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES public.users(user_id) ON DELETE CASCADE NOT NULL,
vin VARCHAR(17) NOT NULL,
year INT NOT NULL CHECK (year >= 1900 AND year <= 2100),
make VARCHAR(100) NOT NULL,
model VARCHAR(100) NOT NULL,
trim VARCHAR(100),
engine_displacement_l DECIMAL(4, 2),
engine_cylinders INT,
fuel_type VARCHAR(50),
transmission VARCHAR(100),
drivetrain VARCHAR(50),
body_type VARCHAR(50),
plant_country VARCHAR(100),
plant_city VARCHAR(100),
in_service_date DATE,
purchase_date DATE,
purchase_price DECIMAL(10, 2),
current_market_value DECIMAL(10, 2),
current_mileage INT,
profile_image TEXT,
color VARCHAR(50),
license_plate VARCHAR(20),
manual_id UUID REFERENCES public.manuals(manual_id),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
is_active BOOLEAN DEFAULT true,
metadata JSONB DEFAULT '{}'::jsonb
);
-- Indexes
CREATE INDEX idx_vehicles_user_id ON public.vehicles(user_id);
CREATE INDEX idx_vehicles_vin ON public.vehicles(vin);
CREATE INDEX idx_vehicles_make_model ON public.vehicles(make, model);
CREATE INDEX idx_vehicles_year ON public.vehicles(year);
-- RLS Policies
ALTER TABLE public.vehicles ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view their own vehicles"
ON public.vehicles FOR SELECT
USING (auth.uid() = (SELECT firebase_uid FROM public.users WHERE user_id = vehicles.user_id));
CREATE POLICY "Users can insert their own vehicles"
ON public.vehicles FOR INSERT
WITH CHECK (auth.uid() = (SELECT firebase_uid FROM public.users WHERE user_id = vehicles.user_id));
CREATE POLICY "Users can update their own vehicles"
ON public.vehicles FOR UPDATE
USING (auth.uid() = (SELECT firebase_uid FROM public.users WHERE user_id = vehicles.user_id));
CREATE POLICY "Users can delete their own vehicles"
ON public.vehicles FOR DELETE
USING (auth.uid() = (SELECT firebase_uid FROM public.users WHERE user_id = vehicles.user_id));
Stores owner’s manual documents and metadata.
CREATE TABLE public.manuals (
manual_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
make VARCHAR(100) NOT NULL,
model VARCHAR(100) NOT NULL,
year INT NOT NULL,
trim VARCHAR(100),
file_url TEXT,
file_size_bytes BIGINT,
page_count INT,
language VARCHAR(10) DEFAULT 'en',
version VARCHAR(50),
upload_date TIMESTAMP DEFAULT NOW(),
processed BOOLEAN DEFAULT false,
processing_status VARCHAR(50) CHECK (processing_status IN ('pending', 'processing', 'completed', 'failed')) DEFAULT 'pending',
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
metadata JSONB DEFAULT '{}'::jsonb
);
-- Indexes
CREATE INDEX idx_manuals_make_model_year ON public.manuals(make, model, year);
CREATE INDEX idx_manuals_processing_status ON public.manuals(processing_status);
-- Note: Manuals are read-only for users, no RLS policies for modification
Stores chunked manual content with vector embeddings for RAG.
-- Enable pgvector extension
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE public.vector_embeddings (
embedding_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
manual_id UUID REFERENCES public.manuals(manual_id) ON DELETE CASCADE NOT NULL,
chunk_text TEXT NOT NULL,
chunk_index INT NOT NULL,
parent_chunk_id UUID REFERENCES public.vector_embeddings(embedding_id),
page_number INT,
section_title VARCHAR(255),
embedding vector(1536), -- text-embedding-3-small dimension
token_count INT,
chunk_type VARCHAR(50) CHECK (chunk_type IN ('parent', 'child')) DEFAULT 'child',
created_at TIMESTAMP DEFAULT NOW(),
metadata JSONB DEFAULT '{}'::jsonb
);
-- Indexes
CREATE INDEX idx_embeddings_manual_id ON public.vector_embeddings(manual_id);
CREATE INDEX idx_embeddings_chunk_index ON public.vector_embeddings(chunk_index);
CREATE INDEX idx_embeddings_parent_chunk ON public.vector_embeddings(parent_chunk_id);
-- Vector similarity index (ivfflat)
CREATE INDEX idx_embeddings_vector ON public.vector_embeddings
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
-- Function for similarity search
CREATE OR REPLACE FUNCTION search_manual_chunks(
query_embedding vector(768),
match_threshold float,
match_count int,
filter_manual_id uuid DEFAULT NULL
)
RETURNS TABLE (
embedding_id uuid,
chunk_text text,
similarity float
)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY
SELECT
ve.embedding_id,
ve.chunk_text,
1 - (ve.embedding <=> query_embedding) as similarity
FROM public.vector_embeddings ve
WHERE
(filter_manual_id IS NULL OR ve.manual_id = filter_manual_id)
AND 1 - (ve.embedding <=> query_embedding) > match_threshold
ORDER BY ve.embedding <=> query_embedding
LIMIT match_count;
END;
$$;
Tracks all service and repair activities.
CREATE TABLE public.maintenance_records (
record_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
vehicle_id UUID REFERENCES public.vehicles(vehicle_id) ON DELETE CASCADE NOT NULL,
type VARCHAR(50) CHECK (type IN ('routine', 'repair', 'modification', 'diagnostic', 'inspection')) NOT NULL,
date DATE NOT NULL,
mileage INT,
title VARCHAR(255) NOT NULL,
description TEXT,
cost DECIMAL(10, 2),
labor_cost DECIMAL(10, 2),
parts_cost DECIMAL(10, 2),
shop_name VARCHAR(255),
shop_location VARCHAR(255),
technician_name VARCHAR(255),
dtc_codes TEXT[], -- Array of diagnostic trouble codes
parts_replaced TEXT[],
attachment_urls TEXT[],
next_service_date DATE,
next_service_mileage INT,
warranty_covered BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
metadata JSONB DEFAULT '{}'::jsonb
);
-- Indexes
CREATE INDEX idx_maintenance_vehicle_id ON public.maintenance_records(vehicle_id);
CREATE INDEX idx_maintenance_date ON public.maintenance_records(date DESC);
CREATE INDEX idx_maintenance_type ON public.maintenance_records(type);
CREATE INDEX idx_maintenance_dtc_codes ON public.maintenance_records USING GIN(dtc_codes);
-- RLS Policies
ALTER TABLE public.maintenance_records ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view their vehicle maintenance"
ON public.maintenance_records FOR SELECT
USING (
vehicle_id IN (
SELECT vehicle_id FROM public.vehicles
WHERE user_id = (SELECT user_id FROM public.users WHERE firebase_uid = auth.uid())
)
);
CREATE POLICY "Users can insert maintenance for their vehicles"
ON public.maintenance_records FOR INSERT
WITH CHECK (
vehicle_id IN (
SELECT vehicle_id FROM public.vehicles
WHERE user_id = (SELECT user_id FROM public.users WHERE firebase_uid = auth.uid())
)
);
Manages loans, leases, and cash purchase tracking.
CREATE TABLE public.financial_accounts (
account_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
vehicle_id UUID REFERENCES public.vehicles(vehicle_id) ON DELETE CASCADE NOT NULL,
type VARCHAR(20) CHECK (type IN ('loan', 'lease', 'cash')) NOT NULL,
lender_name VARCHAR(100),
account_number BYTEA,
start_date DATE NOT NULL,
end_date DATE,
term_months INT,
interest_rate DECIMAL(5, 4), -- APR as decimal (e.g., 0.0549 for 5.49%)
money_factor DECIMAL(7, 6), -- For leases
residual_value DECIMAL(10, 2), -- For leases
monthly_payment DECIMAL(10, 2),
down_payment DECIMAL(10, 2),
principal_amount DECIMAL(10, 2),
current_balance DECIMAL(10, 2),
total_paid DECIMAL(10, 2) DEFAULT 0,
payoff_date DATE,
status VARCHAR(50) CHECK (status IN ('active', 'paid_off', 'refinanced', 'defaulted')) DEFAULT 'active',
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
metadata JSONB DEFAULT '{}'::jsonb
);
-- Indexes
CREATE INDEX idx_financial_vehicle_id ON public.financial_accounts(vehicle_id);
CREATE INDEX idx_financial_type ON public.financial_accounts(type);
CREATE INDEX idx_financial_status ON public.financial_accounts(status);
-- RLS Policies
ALTER TABLE public.financial_accounts ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view their vehicle finances"
ON public.financial_accounts FOR SELECT
USING (
vehicle_id IN (
SELECT vehicle_id FROM public.vehicles
WHERE user_id = (SELECT user_id FROM public.users WHERE firebase_uid = auth.uid())
)
);
Stores OBD-II diagnostic trouble codes with AI analysis.
CREATE TABLE public.diagnostic_codes (
diagnostic_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
vehicle_id UUID REFERENCES public.vehicles(vehicle_id) ON DELETE CASCADE NOT NULL,
code VARCHAR(10) NOT NULL, -- e.g., P0420, B1342
code_type VARCHAR(10) CHECK (code_type IN ('P', 'C', 'B', 'U')), -- Powertrain, Chassis, Body, Network
description TEXT NOT NULL,
severity VARCHAR(20) CHECK (severity IN ('low', 'medium', 'high', 'critical')) NOT NULL,
detected_at TIMESTAMP DEFAULT NOW(),
cleared_at TIMESTAMP,
status VARCHAR(50) CHECK (status IN ('active', 'pending', 'resolved', 'false_positive')) DEFAULT 'active',
mileage_at_detection INT,
freeze_frame_data JSONB,
ai_analysis TEXT,
recommended_actions TEXT[],
estimated_repair_cost_min DECIMAL(10, 2),
estimated_repair_cost_max DECIMAL(10, 2),
related_maintenance_id UUID REFERENCES public.maintenance_records(record_id),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
metadata JSONB DEFAULT '{}'::jsonb
);
-- Indexes
CREATE INDEX idx_diagnostic_vehicle_id ON public.diagnostic_codes(vehicle_id);
CREATE INDEX idx_diagnostic_code ON public.diagnostic_codes(code);
CREATE INDEX idx_diagnostic_status ON public.diagnostic_codes(status);
CREATE INDEX idx_diagnostic_severity ON public.diagnostic_codes(severity);
CREATE INDEX idx_diagnostic_detected ON public.diagnostic_codes(detected_at DESC);
-- RLS Policies
ALTER TABLE public.diagnostic_codes ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view their vehicle diagnostics"
ON public.diagnostic_codes FOR SELECT
USING (
vehicle_id IN (
SELECT vehicle_id FROM public.vehicles
WHERE user_id = (SELECT user_id FROM public.users WHERE firebase_uid = auth.uid())
)
);
Stores conversational AI interactions.
CREATE TABLE public.chat_sessions (
session_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES public.users(user_id) ON DELETE CASCADE NOT NULL,
vehicle_id UUID REFERENCES public.vehicles(vehicle_id) ON DELETE SET NULL,
title VARCHAR(255),
started_at TIMESTAMP DEFAULT NOW(),
last_message_at TIMESTAMP DEFAULT NOW(),
message_count INT DEFAULT 0,
is_active BOOLEAN DEFAULT true,
context_type VARCHAR(50) CHECK (context_type IN ('general', 'manual', 'diagnostic', 'maintenance', 'valuation')) DEFAULT 'general',
metadata JSONB DEFAULT '{}'::jsonb
);
-- Indexes
CREATE INDEX idx_chat_user_id ON public.chat_sessions(user_id);
CREATE INDEX idx_chat_vehicle_id ON public.chat_sessions(vehicle_id);
CREATE INDEX idx_chat_started_at ON public.chat_sessions(started_at DESC);
-- RLS Policies
ALTER TABLE public.chat_sessions ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view their own chat sessions"
ON public.chat_sessions FOR SELECT
USING (user_id = (SELECT user_id FROM public.users WHERE firebase_uid = auth.uid()));
Stores individual messages within chat sessions.
CREATE TABLE public.chat_messages (
message_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
session_id UUID REFERENCES public.chat_sessions(session_id) ON DELETE CASCADE NOT NULL,
role VARCHAR(20) CHECK (role IN ('user', 'assistant', 'system')) NOT NULL,
content TEXT NOT NULL,
tokens_used INT,
model_version VARCHAR(50),
retrieval_context JSONB, -- RAG sources used
created_at TIMESTAMP DEFAULT NOW(),
metadata JSONB DEFAULT '{}'::jsonb
);
-- Indexes
CREATE INDEX idx_messages_session_id ON public.chat_messages(session_id);
CREATE INDEX idx_messages_created_at ON public.chat_messages(created_at);
-- RLS Policies
ALTER TABLE public.chat_messages ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view messages in their sessions"
ON public.chat_messages FOR SELECT
USING (
session_id IN (
SELECT session_id FROM public.chat_sessions
WHERE user_id = (SELECT user_id FROM public.users WHERE firebase_uid = auth.uid())
)
);
Automated maintenance reminders based on mileage and time.
CREATE TABLE public.service_reminders (
reminder_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
vehicle_id UUID REFERENCES public.vehicles(vehicle_id) ON DELETE CASCADE NOT NULL,
service_type VARCHAR(100) NOT NULL, -- e.g., 'oil_change', 'tire_rotation'
title VARCHAR(255) NOT NULL,
description TEXT,
due_mileage INT,
due_date DATE,
interval_miles INT,
interval_months INT,
priority VARCHAR(20) CHECK (priority IN ('low', 'medium', 'high')) DEFAULT 'medium',
status VARCHAR(50) CHECK (status IN ('upcoming', 'due', 'overdue', 'completed', 'dismissed')) DEFAULT 'upcoming',
notification_sent BOOLEAN DEFAULT false,
completed_at TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
metadata JSONB DEFAULT '{}'::jsonb
);
-- Indexes
CREATE INDEX idx_reminders_vehicle_id ON public.service_reminders(vehicle_id);
CREATE INDEX idx_reminders_due_date ON public.service_reminders(due_date);
CREATE INDEX idx_reminders_status ON public.service_reminders(status);
-- RLS Policies
ALTER TABLE public.service_reminders ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view their vehicle reminders"
ON public.service_reminders FOR SELECT
USING (
vehicle_id IN (
SELECT vehicle_id FROM public.vehicles
WHERE user_id = (SELECT user_id FROM public.users WHERE firebase_uid = auth.uid())
)
);
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Apply to all tables with updated_at
CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON public.users
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_vehicles_updated_at BEFORE UPDATE ON public.vehicles
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_maintenance_updated_at BEFORE UPDATE ON public.maintenance_records
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Add similar triggers for other tables
CREATE OR REPLACE FUNCTION calculate_vehicle_value(
p_vehicle_id UUID,
p_base_market_value DECIMAL,
p_current_mileage INT,
p_avg_mileage INT,
p_mileage_adjustment DECIMAL DEFAULT 0.10,
p_condition_penalty DECIMAL DEFAULT 0
)
RETURNS DECIMAL AS $$
DECLARE
calculated_value DECIMAL;
BEGIN
calculated_value := p_base_market_value
- (p_mileage_adjustment * (p_current_mileage - p_avg_mileage))
- (p_condition_penalty);
-- Ensure value doesn't go below a minimum threshold
IF calculated_value < 0 THEN
calculated_value := 0;
END IF;
RETURN calculated_value;
END;
$$ LANGUAGE plpgsql;
Database migrations are stored in /supabase/migrations/ and are version-controlled. Each migration file follows the naming convention:
YYYYMMDDHHMMSS_description.sql
Example migration files:
20250101000000_initial_schema.sql - Core tables and extensions20250101000001_add_rls_policies.sql - Row Level Security policies20250101000002_create_functions.sql - Database functions and triggers20250101000003_add_indexes.sql - Performance indexesusers (1) ──< (N) vehicles
│ │
│ ├──< (N) maintenance_records
│ ├──< (N) diagnostic_codes
│ ├──< (N) service_reminders
│ └──< (1) financial_accounts
│
└──< (N) chat_sessions ──< (N) chat_messages
manuals (1) ──< (N) vector_embeddings
│
└──< (N) vehicles (reference)
This schema provides a robust foundation for the Gear AI CoPilot application, supporting all core features while maintaining security, performance, and scalability. The use of pgvector enables advanced RAG capabilities, while RLS policies ensure strict data isolation between users.