ALTER TABLE ai_insight_snapshots ADD COLUMN IF NOT EXISTS deterministic_copy jsonb; -- statement-break ALTER TABLE ai_insight_snapshots ADD COLUMN IF NOT EXISTS provider_request_id text; -- statement-break UPDATE ai_insight_snapshots SET deterministic_copy=ai_response WHERE deterministic_copy IS NULL; -- statement-break CREATE TABLE IF NOT EXISTS growth_coach_profiles ( id text PRIMARY KEY, merchant_id bigint NOT NULL REFERENCES merchants(id), terminal_id bigint NOT NULL REFERENCES terminals(id), terminal_source_key text NOT NULL, version integer NOT NULL CHECK (version > 0), is_current boolean NOT NULL DEFAULT true, business_description text NOT NULL CHECK (char_length(business_description) BETWEEN 20 AND 2000), niches jsonb NOT NULL, sales_channels jsonb NOT NULL, primary_goal text NOT NULL CHECK (char_length(primary_goal) BETWEEN 3 AND 500), constraints_text text NOT NULL DEFAULT '' CHECK (char_length(constraints_text) <= 1000), difficulty smallint NOT NULL DEFAULT 2 CHECK (difficulty BETWEEN 1 AND 5), actor_user_id bigint NOT NULL REFERENCES merchant_users(id), created_at timestamptz NOT NULL DEFAULT now(), UNIQUE (merchant_id,terminal_id,version) ); -- statement-break CREATE UNIQUE INDEX IF NOT EXISTS growth_coach_profile_current_idx ON growth_coach_profiles(merchant_id,terminal_id) WHERE is_current; -- statement-break CREATE TABLE IF NOT EXISTS growth_coach_plans ( id text PRIMARY KEY, merchant_id bigint NOT NULL REFERENCES merchants(id), terminal_id bigint NOT NULL REFERENCES terminals(id), terminal_source_key text NOT NULL, profile_id text NOT NULL REFERENCES growth_coach_profiles(id), parent_id text REFERENCES growth_coach_plans(id), cadence text NOT NULL CHECK (cadence IN ('DAILY','WEEKLY','MONTHLY')), status text NOT NULL CHECK (status IN ('GENERATING','READY','PARTIAL','FAILED','CLOSED')), period_start date NOT NULL, period_end date NOT NULL, daily_brief text, difficulty smallint NOT NULL CHECK (difficulty BETWEEN 1 AND 5), prompt_version text NOT NULL, schema_version text NOT NULL, model text NOT NULL, provider_request_id text, input_tokens bigint NOT NULL DEFAULT 0, output_tokens bigint NOT NULL DEFAULT 0, latency_ms bigint NOT NULL DEFAULT 0, provider_requests bigint NOT NULL DEFAULT 0, estimated_cost text, failure_code text, created_at timestamptz NOT NULL DEFAULT now(), completed_at timestamptz, CHECK (period_end >= period_start) ); -- statement-break CREATE INDEX IF NOT EXISTS growth_coach_plans_scope_idx ON growth_coach_plans(merchant_id,terminal_id,cadence,period_start DESC); -- statement-break CREATE TABLE IF NOT EXISTS growth_coach_recommendations ( id text PRIMARY KEY, plan_id text NOT NULL REFERENCES growth_coach_plans(id) ON DELETE CASCADE, merchant_id bigint NOT NULL REFERENCES merchants(id), terminal_id bigint NOT NULL REFERENCES terminals(id), rank smallint NOT NULL CHECK (rank BETWEEN 1 AND 5), status text NOT NULL CHECK (status IN ('OFFERED','ACCEPTED','REJECTED','EXPIRED')), action_type text NOT NULL CHECK (action_type IN ('MANUAL','METRIC')), title text NOT NULL, rationale text NOT NULL, expected_impact text NOT NULL, effort text NOT NULL CHECK (effort IN ('LOW','MEDIUM','HIGH')), evidence_ids jsonb NOT NULL, metric_id text, metric_direction text CHECK (metric_direction IN ('INCREASE','DECREASE')), action_level text CHECK (action_level IN ('SMALL','MEDIUM','STRETCH')), baseline numeric(30,6), target numeric(30,6), sample_size bigint, deadline date NOT NULL, decision_reason text, decision_note text, decided_by bigint REFERENCES merchant_users(id), decided_at timestamptz, created_at timestamptz NOT NULL DEFAULT now(), UNIQUE(plan_id,rank) ); -- statement-break CREATE TABLE IF NOT EXISTS golden_steps ( id text PRIMARY KEY, recommendation_id text NOT NULL UNIQUE REFERENCES growth_coach_recommendations(id), merchant_id bigint NOT NULL REFERENCES merchants(id), terminal_id bigint NOT NULL REFERENCES terminals(id), terminal_source_key text NOT NULL, action_type text NOT NULL CHECK (action_type IN ('MANUAL','METRIC')), title text NOT NULL, status text NOT NULL CHECK (status IN ('NOT_STARTED','IN_PROGRESS','COMPLETED','ABANDONED','MISSED')), metric_id text, metric_direction text, baseline numeric(30,6), target numeric(30,6), current_value numeric(30,6), sample_size bigint, deadline date NOT NULL, started_at timestamptz, completed_at timestamptz, actor_user_id bigint NOT NULL REFERENCES merchant_users(id), created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); -- statement-break CREATE INDEX IF NOT EXISTS golden_steps_scope_idx ON golden_steps(merchant_id,terminal_id,status,deadline); -- statement-break CREATE TABLE IF NOT EXISTS growth_coach_jobs ( id text PRIMARY KEY, merchant_id bigint NOT NULL REFERENCES merchants(id), terminal_id bigint NOT NULL REFERENCES terminals(id), terminal_source_key text NOT NULL, profile_id text NOT NULL REFERENCES growth_coach_profiles(id), cadence text NOT NULL CHECK (cadence IN ('DAILY','WEEKLY','MONTHLY')), trigger_reason text NOT NULL, period_start date NOT NULL, period_end date NOT NULL, fingerprint text NOT NULL UNIQUE, idempotency_key text, request_hash text, status text NOT NULL CHECK (status IN ('QUEUED','RUNNING','COMPLETED','FAILED')), attempts integer NOT NULL DEFAULT 0, scheduled_at timestamptz NOT NULL DEFAULT now(), lease_token text, lease_expires_at timestamptz, failure_code text, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), completed_at timestamptz, UNIQUE(merchant_id,idempotency_key) ); -- statement-break CREATE INDEX IF NOT EXISTS growth_coach_jobs_pending_idx ON growth_coach_jobs(scheduled_at,created_at) WHERE status IN ('QUEUED','RUNNING'); -- statement-break CREATE TABLE IF NOT EXISTS growth_coach_events ( id bigserial PRIMARY KEY, merchant_id bigint NOT NULL REFERENCES merchants(id), terminal_id bigint NOT NULL REFERENCES terminals(id), actor_user_id bigint REFERENCES merchant_users(id), entity_type text NOT NULL, entity_id text NOT NULL, event_type text NOT NULL, payload jsonb NOT NULL DEFAULT '{}'::jsonb, occurred_at timestamptz NOT NULL DEFAULT now() ); -- statement-break CREATE TABLE IF NOT EXISTS growth_coach_progress_snapshots ( id bigserial PRIMARY KEY, action_id text NOT NULL REFERENCES golden_steps(id) ON DELETE CASCADE, watermark text NOT NULL, value numeric(30,6) NOT NULL, sample_size bigint NOT NULL, evaluated_at timestamptz NOT NULL DEFAULT now(), UNIQUE(action_id,watermark) ); -- statement-break CREATE TABLE IF NOT EXISTS growth_coach_difficulty_proposals ( id text PRIMARY KEY, merchant_id bigint NOT NULL REFERENCES merchants(id), terminal_id bigint NOT NULL REFERENCES terminals(id), from_level smallint NOT NULL CHECK (from_level BETWEEN 1 AND 5), to_level smallint NOT NULL CHECK (to_level BETWEEN 1 AND 5), reason text NOT NULL, status text NOT NULL CHECK (status IN ('OFFERED','ACCEPTED','REJECTED','EXPIRED')), decided_by bigint REFERENCES merchant_users(id), decided_at timestamptz, created_at timestamptz NOT NULL DEFAULT now() ); -- statement-break CREATE TABLE IF NOT EXISTS growth_coach_outcome_reviews ( id text PRIMARY KEY, plan_id text NOT NULL UNIQUE REFERENCES growth_coach_plans(id), merchant_id bigint NOT NULL REFERENCES merchants(id), terminal_id bigint NOT NULL REFERENCES terminals(id), summary text NOT NULL, completed_on_time integer NOT NULL DEFAULT 0, missed integer NOT NULL DEFAULT 0, created_at timestamptz NOT NULL DEFAULT now() ); -- statement-break CREATE TABLE IF NOT EXISTS growth_coach_idempotency ( merchant_id bigint NOT NULL REFERENCES merchants(id), idempotency_key text NOT NULL, request_hash text NOT NULL, response jsonb NOT NULL, created_at timestamptz NOT NULL DEFAULT now(), PRIMARY KEY(merchant_id,idempotency_key) ); -- statement-break INSERT INTO schema_migrations(version) VALUES (5) ON CONFLICT DO NOTHING;