fix: deliver validated AI insight responses
This commit is contained in:
192
migrations/000005_growth_coach.sql
Normal file
192
migrations/000005_growth_coach.sql
Normal file
@@ -0,0 +1,192 @@
|
||||
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;
|
||||
19
migrations/000006_growth_coach_completion_guard.sql
Normal file
19
migrations/000006_growth_coach_completion_guard.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
BEGIN;
|
||||
|
||||
UPDATE golden_steps
|
||||
SET status = 'IN_PROGRESS',
|
||||
completed_at = NULL,
|
||||
updated_at = now()
|
||||
WHERE action_type = 'METRIC'
|
||||
AND status = 'COMPLETED'
|
||||
AND (
|
||||
current_value IS NULL
|
||||
OR target IS NULL
|
||||
OR metric_direction IS NULL
|
||||
OR (metric_direction = 'INCREASE' AND current_value < target)
|
||||
OR (metric_direction = 'DECREASE' AND current_value > target)
|
||||
);
|
||||
|
||||
INSERT INTO schema_migrations(version) VALUES (6);
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,20 @@
|
||||
BEGIN;
|
||||
|
||||
UPDATE golden_steps AS action
|
||||
SET status = 'IN_PROGRESS',
|
||||
completed_at = NULL,
|
||||
updated_at = now()
|
||||
WHERE action.action_type = 'MANUAL'
|
||||
AND action.status = 'COMPLETED'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM growth_coach_events AS event
|
||||
WHERE event.entity_type = 'ACTION'
|
||||
AND event.entity_id = action.id
|
||||
AND event.event_type = 'action.transitioned'
|
||||
AND event.payload ->> 'status' = 'COMPLETED'
|
||||
);
|
||||
|
||||
INSERT INTO schema_migrations(version) VALUES (7);
|
||||
|
||||
COMMIT;
|
||||
154
migrations/000008_section_refresh_monthly_goals.sql
Normal file
154
migrations/000008_section_refresh_monthly_goals.sql
Normal file
@@ -0,0 +1,154 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE ai_insight_snapshots
|
||||
ADD COLUMN IF NOT EXISTS section_id text NOT NULL DEFAULT 'full';
|
||||
ALTER TABLE ai_insight_jobs
|
||||
ADD COLUMN IF NOT EXISTS section_id text NOT NULL DEFAULT 'full';
|
||||
ALTER TABLE growth_coach_profiles
|
||||
ADD COLUMN IF NOT EXISTS changed_fields jsonb NOT NULL DEFAULT '[]'::jsonb;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ai_generation_holds (
|
||||
terminal_id bigint PRIMARY KEY REFERENCES terminals(id) ON DELETE CASCADE,
|
||||
merchant_id bigint NOT NULL REFERENCES merchants(id),
|
||||
reason text NOT NULL CHECK (reason IN ('ONBOARDING_REQUIRED')),
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE ai_insight_jobs DROP CONSTRAINT IF EXISTS ai_insight_jobs_trigger_reason_check;
|
||||
ALTER TABLE ai_insight_jobs ADD CONSTRAINT ai_insight_jobs_trigger_reason_check
|
||||
CHECK (trigger_reason IN ('BACKFILL','NIGHTLY','IMPORT','CALENDAR','ANOMALY','MANUAL','PROFILE'));
|
||||
|
||||
ALTER TABLE ai_insight_snapshots DROP CONSTRAINT IF EXISTS ai_insight_snapshots_section_id_check;
|
||||
ALTER TABLE ai_insight_snapshots ADD CONSTRAINT ai_insight_snapshots_section_id_check
|
||||
CHECK (section_id IN ('full','past','latest-observed','future-7-days','future-30-days','future-90-days','findings','options'));
|
||||
ALTER TABLE ai_insight_jobs DROP CONSTRAINT IF EXISTS ai_insight_jobs_section_id_check;
|
||||
ALTER TABLE ai_insight_jobs ADD CONSTRAINT ai_insight_jobs_section_id_check
|
||||
CHECK (section_id IN ('full','past','latest-observed','future-7-days','future-30-days','future-90-days','findings','options'));
|
||||
|
||||
DROP INDEX IF EXISTS ai_insight_manual_refresh_idx;
|
||||
CREATE TABLE IF NOT EXISTS ai_insight_refresh_attempts (
|
||||
id bigserial PRIMARY KEY,
|
||||
job_id text NOT NULL REFERENCES ai_insight_jobs(id) ON DELETE CASCADE,
|
||||
merchant_id bigint NOT NULL REFERENCES merchants(id),
|
||||
terminal_source_key text,
|
||||
module text NOT NULL,
|
||||
section_id text NOT NULL CHECK (section_id IN ('past','latest-observed','future-7-days','future-30-days','future-90-days','findings','options')),
|
||||
tehran_refresh_day date NOT NULL,
|
||||
accepted_at timestamptz NOT NULL DEFAULT now(),
|
||||
UNIQUE(job_id,section_id)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS ai_insight_refresh_scope_idx
|
||||
ON ai_insight_refresh_attempts(merchant_id,coalesce(terminal_source_key,''),module,section_id,tehran_refresh_day,accepted_at DESC);
|
||||
|
||||
ALTER TABLE growth_coach_recommendations
|
||||
ADD COLUMN IF NOT EXISTS subtasks jsonb NOT NULL DEFAULT '[]'::jsonb;
|
||||
ALTER TABLE growth_coach_recommendations
|
||||
ADD COLUMN IF NOT EXISTS event_ids jsonb NOT NULL DEFAULT '[]'::jsonb;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS monthly_goals (
|
||||
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,
|
||||
profile_id text NOT NULL REFERENCES growth_coach_profiles(id),
|
||||
title text NOT NULL,
|
||||
expected_impact text NOT NULL,
|
||||
status text NOT NULL CHECK (status IN ('ACTIVE','COMPLETED','MISSED','ABANDONED')),
|
||||
metric_id text NOT NULL,
|
||||
metric_direction text NOT NULL CHECK (metric_direction IN ('INCREASE','DECREASE')),
|
||||
baseline numeric(30,6) NOT NULL,
|
||||
target numeric(30,6) NOT NULL,
|
||||
current_value numeric(30,6) NOT NULL,
|
||||
sample_size bigint NOT NULL,
|
||||
baseline_watermark text,
|
||||
latest_watermark text,
|
||||
progress_bps integer NOT NULL DEFAULT 0 CHECK (progress_bps BETWEEN 0 AND 10000),
|
||||
event_ids jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||||
deadline date NOT NULL,
|
||||
actor_user_id bigint NOT NULL REFERENCES merchant_users(id),
|
||||
accepted_at timestamptz NOT NULL DEFAULT now(),
|
||||
completed_at timestamptz,
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
CHECK (target <> baseline)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS monthly_goals_scope_idx
|
||||
ON monthly_goals(merchant_id,terminal_id,status,deadline);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS goal_subtasks (
|
||||
id text PRIMARY KEY,
|
||||
goal_id text NOT NULL REFERENCES monthly_goals(id) ON DELETE CASCADE,
|
||||
merchant_id bigint NOT NULL REFERENCES merchants(id),
|
||||
position smallint NOT NULL CHECK (position BETWEEN 1 AND 7),
|
||||
title text NOT NULL,
|
||||
status text NOT NULL CHECK (status IN ('TODO','DONE')),
|
||||
deadline date NOT NULL,
|
||||
event_id text,
|
||||
completed_by bigint REFERENCES merchant_users(id),
|
||||
completed_at timestamptz,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
UNIQUE(goal_id,position)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS goal_subtasks_goal_idx ON goal_subtasks(goal_id,position);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS goal_deadline_proposals (
|
||||
id text PRIMARY KEY,
|
||||
goal_id text NOT NULL REFERENCES monthly_goals(id) ON DELETE CASCADE,
|
||||
subtask_id text NOT NULL REFERENCES goal_subtasks(id) ON DELETE CASCADE,
|
||||
merchant_id bigint NOT NULL REFERENCES merchants(id),
|
||||
event_id text NOT NULL,
|
||||
current_deadline date NOT NULL,
|
||||
proposed_deadline date NOT NULL,
|
||||
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()
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS goal_deadline_proposals_open_idx
|
||||
ON goal_deadline_proposals(subtask_id,proposed_deadline) WHERE status='OFFERED';
|
||||
|
||||
-- Preserve non-reset merchants' accepted work as legacy goals without inventing subtasks.
|
||||
INSERT INTO monthly_goals(id,recommendation_id,merchant_id,terminal_id,terminal_source_key,profile_id,title,expected_impact,status,metric_id,metric_direction,baseline,target,current_value,sample_size,baseline_watermark,deadline,actor_user_id,accepted_at,completed_at,updated_at)
|
||||
SELECT a.id,a.recommendation_id,a.merchant_id,a.terminal_id,a.terminal_source_key,p.profile_id,a.title,r.expected_impact,
|
||||
CASE a.status WHEN 'COMPLETED' THEN 'COMPLETED' WHEN 'MISSED' THEN 'MISSED' WHEN 'ABANDONED' THEN 'ABANDONED' ELSE 'ACTIVE' END,
|
||||
a.metric_id,a.metric_direction,a.baseline,a.target,coalesce(a.current_value,a.baseline),coalesce(a.sample_size,0),
|
||||
coalesce((SELECT source_sha256 FROM import_runs WHERE status='COMPLETED' ORDER BY completed_at DESC LIMIT 1),'no-data'),
|
||||
a.deadline,a.actor_user_id,a.created_at,a.completed_at,a.updated_at
|
||||
FROM golden_steps a
|
||||
JOIN growth_coach_recommendations r ON r.id=a.recommendation_id
|
||||
JOIN growth_coach_plans p ON p.id=r.plan_id
|
||||
WHERE a.action_type='METRIC' AND a.metric_id IS NOT NULL AND a.metric_direction IS NOT NULL
|
||||
AND a.baseline IS NOT NULL AND a.target IS NOT NULL AND a.target<>a.baseline
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Repair impossible completions in both the compatibility table and the new goal table.
|
||||
UPDATE golden_steps
|
||||
SET status='IN_PROGRESS',completed_at=NULL,updated_at=now()
|
||||
WHERE status='COMPLETED' AND (
|
||||
baseline IS NULL OR target IS NULL OR target=baseline OR current_value IS NULL OR
|
||||
NOT EXISTS(SELECT 1 FROM growth_coach_progress_snapshots p WHERE p.action_id=golden_steps.id) OR
|
||||
(metric_direction='INCREASE' AND current_value<target) OR
|
||||
(metric_direction='DECREASE' AND current_value>target)
|
||||
);
|
||||
|
||||
-- Treat the currently imported dataset as the baseline evidence for repaired or
|
||||
-- pre-existing compatibility actions. A later watermark is required to evaluate.
|
||||
INSERT INTO growth_coach_progress_snapshots(action_id,watermark,value,sample_size)
|
||||
SELECT a.id,
|
||||
coalesce((SELECT source_sha256 FROM import_runs WHERE status='COMPLETED' ORDER BY completed_at DESC LIMIT 1),'no-data'),
|
||||
coalesce(a.current_value,a.baseline),coalesce(a.sample_size,0)
|
||||
FROM golden_steps a
|
||||
WHERE a.action_type='METRIC' AND a.status='IN_PROGRESS' AND a.baseline IS NOT NULL AND a.target IS NOT NULL AND a.target<>a.baseline
|
||||
ON CONFLICT DO NOTHING;
|
||||
UPDATE monthly_goals
|
||||
SET status='ACTIVE',completed_at=NULL,progress_bps=0,updated_at=now()
|
||||
WHERE status='COMPLETED' AND (
|
||||
baseline_watermark IS NULL OR latest_watermark IS NULL OR latest_watermark=baseline_watermark OR
|
||||
(metric_direction='INCREASE' AND current_value<target) OR
|
||||
(metric_direction='DECREASE' AND current_value>target)
|
||||
);
|
||||
|
||||
INSERT INTO schema_migrations(version) VALUES (8) ON CONFLICT DO NOTHING;
|
||||
COMMIT;
|
||||
27
migrations/000009_ranked_campaign_calendar.sql
Normal file
27
migrations/000009_ranked_campaign_calendar.sql
Normal file
@@ -0,0 +1,27 @@
|
||||
ALTER TABLE calendar_events
|
||||
ADD COLUMN IF NOT EXISTS occasion_key text,
|
||||
ADD COLUMN IF NOT EXISTS event_variant text NOT NULL DEFAULT 'PRIMARY',
|
||||
ADD COLUMN IF NOT EXISTS aliases jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS themes jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS campaign_tags jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS importance_bps integer;
|
||||
-- statement-break
|
||||
UPDATE calendar_events
|
||||
SET occasion_key = coalesce(occasion_key,event_key),
|
||||
importance_bps = coalesce(importance_bps,0)
|
||||
WHERE occasion_key IS NULL OR importance_bps IS NULL;
|
||||
-- statement-break
|
||||
ALTER TABLE calendar_events
|
||||
ALTER COLUMN occasion_key SET NOT NULL,
|
||||
ALTER COLUMN importance_bps SET DEFAULT 0,
|
||||
ALTER COLUMN importance_bps SET NOT NULL,
|
||||
ADD CONSTRAINT calendar_events_importance_bps_check CHECK (importance_bps BETWEEN 0 AND 10000),
|
||||
ADD CONSTRAINT calendar_events_variant_check CHECK (event_variant IN ('PRIMARY','CAMPAIGN_WINDOW','OPERATIONAL_WINDOW'));
|
||||
-- statement-break
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS calendar_events_canonical_occurrence_idx
|
||||
ON calendar_events (catalog_version_id, occasion_key, event_variant, start_date, end_date);
|
||||
-- statement-break
|
||||
CREATE INDEX IF NOT EXISTS calendar_events_ranked_context_idx
|
||||
ON calendar_events (catalog_version_id, importance_bps DESC, relevance_bps DESC, start_date);
|
||||
-- statement-break
|
||||
INSERT INTO schema_migrations(version) VALUES (9) ON CONFLICT DO NOTHING;
|
||||
23
migrations/000010_transaction_insight_period_scope.sql
Normal file
23
migrations/000010_transaction_insight_period_scope.sql
Normal file
@@ -0,0 +1,23 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE ai_insight_snapshots ADD COLUMN IF NOT EXISTS period_id text;
|
||||
ALTER TABLE ai_insight_snapshots ADD COLUMN IF NOT EXISTS period_type text;
|
||||
ALTER TABLE ai_insight_snapshots ADD COLUMN IF NOT EXISTS period_start_at timestamptz;
|
||||
ALTER TABLE ai_insight_snapshots ADD COLUMN IF NOT EXISTS period_end_at timestamptz;
|
||||
ALTER TABLE ai_insight_snapshots ADD COLUMN IF NOT EXISTS period_label_fa text;
|
||||
ALTER TABLE ai_insight_refresh_attempts ADD COLUMN IF NOT EXISTS period_id text;
|
||||
|
||||
ALTER TABLE ai_insight_snapshots DROP CONSTRAINT IF EXISTS ai_insight_snapshots_period_scope_check;
|
||||
ALTER TABLE ai_insight_snapshots ADD CONSTRAINT ai_insight_snapshots_period_scope_check CHECK (
|
||||
(period_id IS NULL AND period_type IS NULL AND period_start_at IS NULL AND period_end_at IS NULL AND period_label_fa IS NULL)
|
||||
OR
|
||||
(period_id IS NOT NULL AND period_type IN ('DAY','WEEK','JALALI_MONTH','PERSIAN_SEASON') AND period_start_at IS NOT NULL AND period_end_at IS NOT NULL AND period_label_fa IS NOT NULL AND period_end_at > period_start_at)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ai_insight_latest_period_scope_idx
|
||||
ON ai_insight_snapshots (merchant_id,terminal_source_key,module,period_id,revision DESC,created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS ai_insight_refresh_period_scope_idx
|
||||
ON ai_insight_refresh_attempts (merchant_id,coalesce(terminal_source_key,''),module,coalesce(period_id,''),section_id,tehran_refresh_day,accepted_at DESC);
|
||||
|
||||
INSERT INTO schema_migrations(version) VALUES (10) ON CONFLICT DO NOTHING;
|
||||
COMMIT;
|
||||
Reference in New Issue
Block a user