From f3f51a97ef9d695e450677aec11f2017e07041ca Mon Sep 17 00:00:00 2001 From: Amirhossein Khalili Date: Fri, 21 Aug 2026 00:14:14 +0330 Subject: [PATCH] feat: add durable growth report schema --- migrations/000002_growth_analyst.sql | 57 ++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 migrations/000002_growth_analyst.sql diff --git a/migrations/000002_growth_analyst.sql b/migrations/000002_growth_analyst.sql new file mode 100644 index 0000000..f12b35f --- /dev/null +++ b/migrations/000002_growth_analyst.sql @@ -0,0 +1,57 @@ +CREATE TABLE IF NOT EXISTS growth_reports ( + id text PRIMARY KEY, + merchant_id bigint NOT NULL REFERENCES merchants(id), + terminal_id bigint REFERENCES terminals(id), + terminal_source_key text, + fingerprint text NOT NULL, + revision integer NOT NULL DEFAULT 1 CHECK (revision > 0), + status text NOT NULL CHECK (status IN ('QUEUED','ANALYZING','GENERATING','READY','PARTIAL','FAILED')), + horizon_days integer NOT NULL DEFAULT 90 CHECK (horizon_days = 90), + forecast_start_date date, + dataset_watermark text NOT NULL, + catalog_version text NOT NULL, + analytics_version text NOT NULL, + prompt_version text NOT NULL, + schema_version text NOT NULL, + model text NOT NULL, + deterministic_evidence jsonb NOT NULL DEFAULT '{}'::jsonb, + ai_response jsonb, + failure_code text, + input_tokens bigint NOT NULL DEFAULT 0 CHECK (input_tokens >= 0), + output_tokens bigint NOT NULL DEFAULT 0 CHECK (output_tokens >= 0), + latency_ms bigint NOT NULL DEFAULT 0 CHECK (latency_ms >= 0), + manual_refresh boolean NOT NULL DEFAULT false, + tehran_refresh_day date, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now(), + completed_at timestamptz +); +-- statement-break +CREATE UNIQUE INDEX IF NOT EXISTS growth_reports_current_fingerprint_idx + ON growth_reports (merchant_id, fingerprint) + WHERE manual_refresh = false AND status IN ('QUEUED','ANALYZING','GENERATING','READY','PARTIAL'); +-- statement-break +CREATE UNIQUE INDEX IF NOT EXISTS growth_reports_daily_merchant_refresh_idx + ON growth_reports (merchant_id, tehran_refresh_day) + WHERE manual_refresh = true; +-- statement-break +CREATE INDEX IF NOT EXISTS growth_reports_scope_latest_idx + ON growth_reports (merchant_id, terminal_id, created_at DESC); +-- statement-break +CREATE INDEX IF NOT EXISTS growth_reports_pending_idx + ON growth_reports (created_at) + WHERE status IN ('QUEUED','ANALYZING','GENERATING'); +-- statement-break +CREATE TABLE IF NOT EXISTS growth_report_idempotency ( + merchant_id bigint NOT NULL REFERENCES merchants(id), + idempotency_key text NOT NULL, + request_hash text NOT NULL, + report_id text NOT NULL REFERENCES growth_reports(id) ON DELETE CASCADE, + created_at timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY (merchant_id, idempotency_key) +); +-- statement-break +CREATE INDEX IF NOT EXISTS growth_report_idempotency_created_idx + ON growth_report_idempotency (created_at); +-- statement-break +INSERT INTO schema_migrations(version) VALUES (2) ON CONFLICT DO NOTHING;