-- Migration 054: Activity session metrics (EAV) + attribute profiles -- Date: 2026-04-14 -- Additive only: safe for production (no data deletion). -- Agent guide: .claude/docs/technical/ACTIVITY_SESSION_METRICS_EAV_AGENT_GUIDE.md -- Session interval (nullable; optional backfill later) ALTER TABLE activity_log ADD COLUMN IF NOT EXISTS started_at TIMESTAMPTZ, ADD COLUMN IF NOT EXISTS ended_at TIMESTAMPTZ; CREATE INDEX IF NOT EXISTS idx_activity_log_profile_started ON activity_log (profile_id, started_at DESC) WHERE started_at IS NOT NULL; COMMENT ON COLUMN activity_log.started_at IS 'Training start (wall clock, TZ-aware); optional; for dedupe/analysis'; COMMENT ON COLUMN activity_log.ended_at IS 'Training end (wall clock, TZ-aware); optional'; -- Which parameters apply to which training category (training_types.category) CREATE TABLE IF NOT EXISTS training_category_parameter ( id SERIAL PRIMARY KEY, training_category VARCHAR(50) NOT NULL, training_parameter_id INT NOT NULL REFERENCES training_parameters(id) ON DELETE CASCADE, sort_order INT NOT NULL DEFAULT 0, required BOOLEAN NOT NULL DEFAULT false, ui_group VARCHAR(50), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uq_training_category_parameter UNIQUE (training_category, training_parameter_id) ); CREATE INDEX IF NOT EXISTS idx_tcp_category ON training_category_parameter (training_category); COMMENT ON TABLE training_category_parameter IS 'EAV schema: parameters enabled per training category'; -- Per training type: extra parameters or overrides (NULL sort/required/ui = inherit from category row if present) CREATE TABLE IF NOT EXISTS training_type_parameter ( id SERIAL PRIMARY KEY, training_type_id INT NOT NULL REFERENCES training_types(id) ON DELETE CASCADE, training_parameter_id INT NOT NULL REFERENCES training_parameters(id) ON DELETE CASCADE, sort_order INT, required BOOLEAN, ui_group VARCHAR(50), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uq_training_type_parameter UNIQUE (training_type_id, training_parameter_id) ); CREATE INDEX IF NOT EXISTS idx_ttp_type ON training_type_parameter (training_type_id); COMMENT ON TABLE training_type_parameter IS 'EAV schema: add/override parameters for a concrete training_types row'; -- EAV values per activity session CREATE TABLE IF NOT EXISTS activity_session_metrics ( id BIGSERIAL PRIMARY KEY, activity_log_id UUID NOT NULL REFERENCES activity_log(id) ON DELETE CASCADE, training_parameter_id INT NOT NULL REFERENCES training_parameters(id) ON DELETE RESTRICT, value_num DOUBLE PRECISION, value_int BIGINT, value_text TEXT, value_bool BOOLEAN, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uq_activity_session_metric UNIQUE (activity_log_id, training_parameter_id), CONSTRAINT chk_activity_session_metric_one_value CHECK ( ( (value_num IS NOT NULL)::int + (value_int IS NOT NULL)::int + (value_text IS NOT NULL)::int + (value_bool IS NOT NULL)::int ) = 1 ) ); CREATE INDEX IF NOT EXISTS idx_asm_activity ON activity_session_metrics (activity_log_id); CREATE INDEX IF NOT EXISTS idx_asm_parameter ON activity_session_metrics (training_parameter_id); COMMENT ON TABLE activity_session_metrics IS 'EAV: one row per (session, training_parameter); exactly one value_* set'; DO $$ BEGIN RAISE NOTICE 'Migration 054: activity_session_metrics EAV + attribute profile tables + activity_log timestamps'; END $$;