Modelo Físico — DDL PostgreSQL¶
Script base do banco KotaJá com constraints e índices essenciais.
-- =========================
-- EXTENSÕES
-- =========================
CREATE EXTENSION IF NOT EXISTS "pgcrypto"; -- gen_random_uuid()
-- =========================
-- ENUMS
-- =========================
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'user_role') THEN
CREATE TYPE user_role AS ENUM ('admin','manager','coordinator','store_owner','researcher');
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'submission_status') THEN
CREATE TYPE submission_status AS ENUM ('PENDING','APPROVED','REJECTED');
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'review_decision') THEN
CREATE TYPE review_decision AS ENUM ('APPROVED','REJECTED');
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'plan_status') THEN
CREATE TYPE plan_status AS ENUM ('DRAFT','PUBLISHED','CLOSED');
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'batch_status') THEN
CREATE TYPE batch_status AS ENUM ('RUNNING','SUCCESS','FAILED');
END IF;
END $$;
-- =========================
-- USERS
-- =========================
CREATE TABLE IF NOT EXISTS users (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
email text NOT NULL UNIQUE,
role user_role NOT NULL,
active boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL DEFAULT now()
);
-- =========================
-- CATÁLOGO
-- =========================
CREATE TABLE IF NOT EXISTS brands (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL UNIQUE,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS vehicle_models (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
brand_id uuid NOT NULL REFERENCES brands(id),
name text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (brand_id, name)
);
CREATE TABLE IF NOT EXISTS vehicle_variants (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
model_id uuid NOT NULL REFERENCES vehicle_models(id),
year smallint NOT NULL,
fuel_type text NOT NULL,
trim text NOT NULL,
transmission text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (model_id, year, fuel_type, trim, transmission)
);
-- =========================
-- REGIÕES E LOJAS
-- =========================
CREATE TABLE IF NOT EXISTS regions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL UNIQUE,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS stores (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
legal_name text NOT NULL,
trade_name text,
cnpj text,
city text NOT NULL,
state text NOT NULL,
active boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS store_submissions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
submitted_by_user_id uuid NOT NULL REFERENCES users(id),
store_id uuid REFERENCES stores(id),
status submission_status NOT NULL,
payload_json jsonb NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS store_documents (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
submission_id uuid NOT NULL REFERENCES store_submissions(id),
file_name text NOT NULL,
file_url text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS store_reviews (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
submission_id uuid NOT NULL REFERENCES store_submissions(id),
reviewer_user_id uuid NOT NULL REFERENCES users(id),
decision review_decision NOT NULL,
reason text,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS store_region_history (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
store_id uuid NOT NULL REFERENCES stores(id),
region_id uuid NOT NULL REFERENCES regions(id),
valid_from date NOT NULL,
valid_to date,
created_at timestamptz NOT NULL DEFAULT now(),
CHECK (valid_to IS NULL OR valid_to >= valid_from)
);
-- =========================
-- OPERAÇÃO SEMANAL E COLETA
-- =========================
CREATE TABLE IF NOT EXISTS weekly_plans (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
region_id uuid NOT NULL REFERENCES regions(id),
week_start date NOT NULL,
status plan_status NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS weekly_assignments (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
weekly_plan_id uuid NOT NULL REFERENCES weekly_plans(id),
researcher_user_id uuid NOT NULL REFERENCES users(id),
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS weekly_assignment_stores (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
weekly_assignment_id uuid NOT NULL REFERENCES weekly_assignments(id),
store_id uuid NOT NULL REFERENCES stores(id),
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (weekly_assignment_id, store_id)
);
CREATE TABLE IF NOT EXISTS price_observations (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
researcher_user_id uuid NOT NULL REFERENCES users(id),
store_id uuid NOT NULL REFERENCES stores(id),
vehicle_variant_id uuid NOT NULL REFERENCES vehicle_variants(id),
price_value numeric(12,2) NOT NULL CHECK (price_value > 0),
observed_at timestamptz NOT NULL,
notes text,
created_at timestamptz NOT NULL DEFAULT now()
);
-- =========================
-- BATCH E AGREGAÇÃO
-- =========================
CREATE TABLE IF NOT EXISTS batch_runs (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
month_ref date NOT NULL,
status batch_status NOT NULL,
started_at timestamptz NOT NULL DEFAULT now(),
finished_at timestamptz
);
CREATE TABLE IF NOT EXISTS monthly_price_averages (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
month_ref date NOT NULL,
region_id uuid NOT NULL REFERENCES regions(id),
vehicle_variant_id uuid NOT NULL REFERENCES vehicle_variants(id),
avg_price numeric(12,2) NOT NULL,
sample_size integer NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (month_ref, region_id, vehicle_variant_id)
);
CREATE TABLE IF NOT EXISTS public_quote_queries (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
month_ref date,
region_id uuid REFERENCES regions(id),
brand_id uuid REFERENCES brands(id),
model_id uuid REFERENCES vehicle_models(id),
vehicle_variant_id uuid REFERENCES vehicle_variants(id),
user_agent text,
ip_hash text,
created_at timestamptz NOT NULL DEFAULT now()
);
-- =========================
-- ÍNDICES ESSENCIAIS
-- =========================
CREATE INDEX IF NOT EXISTS idx_obs_batch
ON price_observations (observed_at, store_id, vehicle_variant_id);
CREATE INDEX IF NOT EXISTS idx_obs_store_time
ON price_observations (store_id, observed_at);
CREATE INDEX IF NOT EXISTS idx_avg_lookup
ON monthly_price_averages (month_ref, region_id, vehicle_variant_id);
CREATE INDEX IF NOT EXISTS idx_region_current
ON store_region_history (store_id, valid_to);
CREATE INDEX IF NOT EXISTS idx_submissions_status_time
ON store_submissions (status, created_at);
CREATE INDEX IF NOT EXISTS idx_reviews_submission_time
ON store_reviews (submission_id, created_at);