264 lines
10 KiB
PL/PgSQL
264 lines
10 KiB
PL/PgSQL
BEGIN;
|
|
|
|
CREATE SCHEMA IF NOT EXISTS mv_loss_intelligence;
|
|
|
|
COMMENT ON SCHEMA mv_loss_intelligence IS
|
|
'Capa común MESAVAULT LossIntelligence. Conserva configuración, catálogo de acciones, Loss Ledger, pruebas y decisiones. No sustituye las tablas raw de captura.';
|
|
|
|
CREATE TABLE IF NOT EXISTS mv_loss_intelligence.deployment_config (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
tenant TEXT NOT NULL,
|
|
site TEXT NOT NULL,
|
|
scope_code TEXT NOT NULL,
|
|
scope_name TEXT NOT NULL,
|
|
mode TEXT NOT NULL DEFAULT 'SHADOW'
|
|
CHECK (mode IN ('SHADOW', 'ACTIVE', 'PAUSED', 'CLOSED')),
|
|
planned_go_live_date DATE,
|
|
actual_go_live_at TIMESTAMPTZ,
|
|
timezone TEXT NOT NULL DEFAULT 'Europe/Madrid',
|
|
review_owner_primary TEXT,
|
|
review_owner_secondary TEXT,
|
|
review_cadence TEXT NOT NULL DEFAULT 'DAILY',
|
|
daily_review_limit INTEGER NOT NULL DEFAULT 5
|
|
CHECK (daily_review_limit BETWEEN 1 AND 20),
|
|
notes TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE (tenant, site, scope_code),
|
|
CHECK (actual_go_live_at IS NULL OR planned_go_live_date IS NULL OR actual_go_live_at::date >= planned_go_live_date - 31)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS mv_loss_intelligence.economic_parameters (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
tenant TEXT NOT NULL,
|
|
site TEXT NOT NULL,
|
|
scope_code TEXT NOT NULL DEFAULT 'GLOBAL',
|
|
machine_id TEXT,
|
|
product_id BIGINT,
|
|
parameter_code TEXT NOT NULL,
|
|
parameter_value NUMERIC(18,6) NOT NULL,
|
|
unit TEXT NOT NULL,
|
|
confidence TEXT NOT NULL DEFAULT 'LOW'
|
|
CHECK (confidence IN ('HIGH', 'MEDIUM', 'LOW', 'NOT_ACCEPTED')),
|
|
includes_energy BOOLEAN,
|
|
valid_from TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
valid_to TIMESTAMPTZ,
|
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
source_ref TEXT,
|
|
approved_by TEXT,
|
|
notes TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
CHECK (valid_to IS NULL OR valid_to > valid_from)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_li_economic_parameters_lookup
|
|
ON mv_loss_intelligence.economic_parameters
|
|
(tenant, site, parameter_code, is_active, valid_from DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS mv_loss_intelligence.product_cycle_parameters (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
tenant TEXT NOT NULL,
|
|
site TEXT NOT NULL,
|
|
machine_id TEXT,
|
|
product_id BIGINT,
|
|
product_default_code TEXT,
|
|
product_name TEXT,
|
|
units_per_cycle NUMERIC(10,4) NOT NULL
|
|
CHECK (units_per_cycle > 0),
|
|
target_cycle_rate_ppm NUMERIC(12,4)
|
|
CHECK (target_cycle_rate_ppm IS NULL OR target_cycle_rate_ppm > 0),
|
|
target_units_per_min NUMERIC(14,4)
|
|
GENERATED ALWAYS AS (
|
|
CASE
|
|
WHEN target_cycle_rate_ppm IS NULL THEN NULL
|
|
ELSE target_cycle_rate_ppm * units_per_cycle
|
|
END
|
|
) STORED,
|
|
confidence TEXT NOT NULL DEFAULT 'LOW'
|
|
CHECK (confidence IN ('HIGH', 'MEDIUM', 'LOW')),
|
|
source_ref TEXT,
|
|
approved_by TEXT,
|
|
valid_from TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
valid_to TIMESTAMPTZ,
|
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
notes TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
CHECK (product_id IS NOT NULL OR product_default_code IS NOT NULL),
|
|
CHECK (valid_to IS NULL OR valid_to > valid_from)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_li_product_cycle_parameters_lookup
|
|
ON mv_loss_intelligence.product_cycle_parameters
|
|
(tenant, site, product_id, product_default_code, machine_id, is_active, valid_from DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS mv_loss_intelligence.loss_policies (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
tenant TEXT NOT NULL,
|
|
site TEXT NOT NULL,
|
|
policy_code TEXT NOT NULL,
|
|
source_type TEXT NOT NULL DEFAULT 'MACHINE_STOP',
|
|
noise_below_s NUMERIC(12,2) NOT NULL DEFAULT 5
|
|
CHECK (noise_below_s >= 0),
|
|
aggregate_below_s NUMERIC(12,2) NOT NULL DEFAULT 300
|
|
CHECK (aggregate_below_s >= noise_below_s),
|
|
operator_individual_from_s NUMERIC(12,2) NOT NULL DEFAULT 300
|
|
CHECK (operator_individual_from_s >= noise_below_s),
|
|
aggregation_window_min INTEGER NOT NULL DEFAULT 15
|
|
CHECK (aggregation_window_min BETWEEN 1 AND 1440),
|
|
require_valid_order BOOLEAN NOT NULL DEFAULT true,
|
|
require_closed_event BOOLEAN NOT NULL DEFAULT true,
|
|
exclude_planned_by_default BOOLEAN NOT NULL DEFAULT true,
|
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
notes TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE (tenant, site, policy_code)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS mv_loss_intelligence.action_catalog (
|
|
action_id TEXT PRIMARY KEY,
|
|
catalog_code TEXT NOT NULL,
|
|
catalog_version TEXT NOT NULL,
|
|
tenant TEXT,
|
|
site TEXT,
|
|
loss_type TEXT NOT NULL,
|
|
source_cause_code TEXT,
|
|
action_type TEXT NOT NULL
|
|
CHECK (action_type IN ('TECHNICAL', 'METHOD', 'ORGANIZATIONAL', 'PROCESS_DESIGN', 'DATA_QUALITY', 'INVESTMENT_DECISION')),
|
|
action_candidate TEXT NOT NULL,
|
|
applies_when JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
does_not_apply_when JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
suggested_owner TEXT,
|
|
test_window TEXT NOT NULL,
|
|
metric_before TEXT NOT NULL,
|
|
metric_after TEXT NOT NULL,
|
|
success_criteria TEXT NOT NULL,
|
|
risk_level TEXT NOT NULL DEFAULT 'LOW'
|
|
CHECK (risk_level IN ('LOW', 'MEDIUM', 'HIGH')),
|
|
cost_to_test TEXT NOT NULL DEFAULT 'LOW'
|
|
CHECK (cost_to_test IN ('LOW', 'MEDIUM', 'HIGH')),
|
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
source_ref TEXT,
|
|
notes TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_li_action_catalog_lookup
|
|
ON mv_loss_intelligence.action_catalog
|
|
(loss_type, source_cause_code, is_active);
|
|
|
|
CREATE TABLE IF NOT EXISTS mv_loss_intelligence.loss_ledger (
|
|
loss_id BIGSERIAL PRIMARY KEY,
|
|
tenant TEXT NOT NULL,
|
|
site TEXT NOT NULL,
|
|
deployment_id BIGINT REFERENCES mv_loss_intelligence.deployment_config(id),
|
|
ledger_phase TEXT NOT NULL DEFAULT 'SHADOW'
|
|
CHECK (ledger_phase IN ('SHADOW', 'ACTIVE')),
|
|
source_type TEXT NOT NULL,
|
|
source_key TEXT NOT NULL,
|
|
machine_id TEXT,
|
|
asset TEXT,
|
|
order_ref TEXT,
|
|
odoo_production_id BIGINT,
|
|
odoo_workorder_id BIGINT,
|
|
product_id BIGINT,
|
|
product_default_code TEXT,
|
|
product_name TEXT,
|
|
started_at TIMESTAMPTZ,
|
|
ended_at TIMESTAMPTZ,
|
|
event_count INTEGER NOT NULL DEFAULT 1 CHECK (event_count > 0),
|
|
duration_s NUMERIC(18,2) NOT NULL DEFAULT 0 CHECK (duration_s >= 0),
|
|
cycle_count NUMERIC(18,4),
|
|
units_per_cycle NUMERIC(10,4),
|
|
estimated_units NUMERIC(18,4),
|
|
loss_type TEXT NOT NULL,
|
|
cause_code TEXT,
|
|
classification_status TEXT NOT NULL DEFAULT 'PENDING'
|
|
CHECK (classification_status IN ('PENDING', 'CLASSIFIED', 'EXCLUDED', 'NOT_REQUIRED')),
|
|
capacity_loss_eur NUMERIC(18,2),
|
|
energy_waste_kwh NUMERIC(18,6),
|
|
energy_waste_eur NUMERIC(18,2),
|
|
technical_confidence TEXT NOT NULL DEFAULT 'MEDIUM'
|
|
CHECK (technical_confidence IN ('HIGH', 'MEDIUM', 'LOW')),
|
|
economic_confidence TEXT NOT NULL DEFAULT 'LOW'
|
|
CHECK (economic_confidence IN ('HIGH', 'MEDIUM', 'LOW', 'NOT_ACCEPTED')),
|
|
priority_score NUMERIC(12,2),
|
|
priority_level TEXT
|
|
CHECK (priority_level IS NULL OR priority_level IN ('LOW', 'MEDIUM', 'HIGH', 'CRITICAL')),
|
|
action_catalog_id TEXT REFERENCES mv_loss_intelligence.action_catalog(action_id),
|
|
action_candidate TEXT,
|
|
action_final TEXT,
|
|
review_owner TEXT,
|
|
review_due_at TIMESTAMPTZ,
|
|
status TEXT NOT NULL DEFAULT 'DETECTED'
|
|
CHECK (status IN ('DETECTED', 'CANDIDATE', 'IN_REVIEW', 'IN_TRIAL', 'VALIDATED', 'ADAPTED', 'DISCARDED', 'ESCALATED', 'CLOSED')),
|
|
learning_reusable TEXT,
|
|
evidence_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
detected_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
promoted_at TIMESTAMPTZ,
|
|
closed_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE (tenant, site, source_type, source_key)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_li_loss_ledger_review
|
|
ON mv_loss_intelligence.loss_ledger
|
|
(tenant, site, status, review_due_at, priority_score DESC);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_li_loss_ledger_order_machine
|
|
ON mv_loss_intelligence.loss_ledger
|
|
(tenant, site, machine_id, order_ref, started_at DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS mv_loss_intelligence.action_trials (
|
|
trial_id BIGSERIAL PRIMARY KEY,
|
|
loss_id BIGINT NOT NULL REFERENCES mv_loss_intelligence.loss_ledger(loss_id) ON DELETE CASCADE,
|
|
trial_code TEXT NOT NULL,
|
|
action_final TEXT NOT NULL,
|
|
owner TEXT NOT NULL,
|
|
baseline_from TIMESTAMPTZ,
|
|
baseline_to TIMESTAMPTZ,
|
|
test_from TIMESTAMPTZ,
|
|
test_to TIMESTAMPTZ,
|
|
metric_code TEXT NOT NULL,
|
|
metric_unit TEXT,
|
|
metric_before NUMERIC(18,6),
|
|
metric_after NUMERIC(18,6),
|
|
change_pct NUMERIC(12,4),
|
|
success_criteria TEXT NOT NULL,
|
|
result TEXT
|
|
CHECK (result IS NULL OR result IN ('PENDING', 'SUCCESS', 'PARTIAL', 'NO_CHANGE', 'WORSE', 'INVALID_TEST')),
|
|
decision TEXT
|
|
CHECK (decision IS NULL OR decision IN ('KEEP', 'ADAPT', 'REPEAT', 'DISCARD', 'SCALE')),
|
|
learning TEXT,
|
|
evidence_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE (loss_id, trial_code)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS mv_loss_intelligence.review_decisions (
|
|
decision_id BIGSERIAL PRIMARY KEY,
|
|
loss_id BIGINT REFERENCES mv_loss_intelligence.loss_ledger(loss_id) ON DELETE CASCADE,
|
|
trial_id BIGINT REFERENCES mv_loss_intelligence.action_trials(trial_id) ON DELETE CASCADE,
|
|
decision_type TEXT NOT NULL
|
|
CHECK (decision_type IN ('PROMOTE', 'CLASSIFY', 'ASSIGN_ACTION', 'ADAPT_ACTION', 'DEFER', 'EXCLUDE', 'CLOSE', 'ESCALATE')),
|
|
decision_text TEXT NOT NULL,
|
|
decided_by TEXT NOT NULL,
|
|
decided_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
next_review_at TIMESTAMPTZ,
|
|
evidence_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
CHECK (loss_id IS NOT NULL OR trial_id IS NOT NULL)
|
|
);
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'grafana_ucepsa_ro') THEN
|
|
GRANT USAGE ON SCHEMA mv_loss_intelligence TO grafana_ucepsa_ro;
|
|
GRANT SELECT ON ALL TABLES IN SCHEMA mv_loss_intelligence TO grafana_ucepsa_ro;
|
|
END IF;
|
|
END $$;
|
|
|
|
COMMIT;
|