wulf-pulse/migrations/069_create_analyzer_tables.sql

117 lines
5.8 KiB
MySQL
Raw Normal View History

-- AI Ticket Analyzer feature
-- Stores versioned analyses of Autotask tickets, share log, and an on-demand job queue.
-- See docs/wulf-pulse-ticket-analyzer-prompt.md for the feature spec.
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- =============================================================================
-- analyzer_analyses
-- One row per completed (or failed) analysis; versioned per ticket_number.
-- =============================================================================
CREATE TABLE IF NOT EXISTS analyzer_analyses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
ticket_number TEXT NOT NULL,
autotask_ticket_id BIGINT NOT NULL,
analysis_version INT NOT NULL,
content_hash_at_analysis TEXT NOT NULL,
triggered_by_user_id TEXT REFERENCES "user"(id) ON DELETE SET NULL,
triggered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
status TEXT NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending','running','complete','failed')),
completed_at TIMESTAMPTZ,
-- Model usage
haiku_used BOOLEAN NOT NULL DEFAULT false,
sonnet_used BOOLEAN NOT NULL DEFAULT false,
opus_used BOOLEAN NOT NULL DEFAULT false,
total_input_tokens INT NOT NULL DEFAULT 0,
total_output_tokens INT NOT NULL DEFAULT 0,
estimated_cost_usd NUMERIC(10,4) NOT NULL DEFAULT 0,
-- Structured output (each LLM stage's parsed JSON)
summary TEXT,
timeline JSONB,
what_was_done JSONB,
what_should_have_been_done JSONB,
gaps JSONB,
next_step TEXT,
next_step_rationale TEXT,
post_resolution_analysis TEXT,
confidence_score NUMERIC(3,2),
needs_human_review BOOLEAN NOT NULL DEFAULT false,
human_review_reasons JSONB,
itglue_docs_referenced JSONB NOT NULL DEFAULT '[]'::jsonb,
-- Debugging / observability
model_traces JSONB,
filtered_noise_count INT NOT NULL DEFAULT 0,
error_message TEXT,
CONSTRAINT analyzer_analyses_version_unique UNIQUE (ticket_number, analysis_version)
);
CREATE INDEX IF NOT EXISTS idx_analyzer_analyses_ticket_version
ON analyzer_analyses (ticket_number, analysis_version DESC);
CREATE INDEX IF NOT EXISTS idx_analyzer_analyses_triggered_at
ON analyzer_analyses (triggered_at DESC);
CREATE INDEX IF NOT EXISTS idx_analyzer_analyses_needs_review
ON analyzer_analyses (needs_human_review)
WHERE needs_human_review = true;
CREATE INDEX IF NOT EXISTS idx_analyzer_analyses_autotask_ticket_id
ON analyzer_analyses (autotask_ticket_id);
COMMENT ON TABLE analyzer_analyses IS 'Versioned AI analyses of Autotask tickets; one row per completed analysis run.';
COMMENT ON COLUMN analyzer_analyses.content_hash_at_analysis IS 'sha256 of canonical-JSON of (tagged_events, ticket_status, ticket_priority, queue) at analysis time. Used for idempotency.';
COMMENT ON COLUMN analyzer_analyses.filtered_noise_count IS 'Count of workflow-rule and notification-email notes stripped during pre-processing.';
COMMENT ON COLUMN analyzer_analyses.itglue_docs_referenced IS 'IDs/names of IT Glue docs cited; doc bodies are NEVER stored here (security).';
-- =============================================================================
-- analyzer_shares
-- Audit log of share-by-email events for any analysis.
-- =============================================================================
CREATE TABLE IF NOT EXISTS analyzer_shares (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
analysis_id UUID NOT NULL REFERENCES analyzer_analyses(id) ON DELETE CASCADE,
shared_by_user_id TEXT NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
shared_with_email TEXT NOT NULL,
note TEXT,
shared_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
viewed_at TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_analyzer_shares_analysis_id ON analyzer_shares (analysis_id);
CREATE INDEX IF NOT EXISTS idx_analyzer_shares_shared_by ON analyzer_shares (shared_by_user_id);
CREATE INDEX IF NOT EXISTS idx_analyzer_shares_shared_at ON analyzer_shares (shared_at DESC);
COMMENT ON TABLE analyzer_shares IS 'Audit log of analyses shared by email. Recipient domains validated against ALLOWED_SHARE_DOMAINS at write time.';
-- =============================================================================
-- analyzer_jobs
-- On-demand pipeline queue. A worker initialised at server start polls this table.
-- =============================================================================
CREATE TABLE IF NOT EXISTS analyzer_jobs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
ticket_number TEXT NOT NULL,
queued_by_user_id TEXT REFERENCES "user"(id) ON DELETE SET NULL,
status TEXT NOT NULL DEFAULT 'queued'
CHECK (status IN ('queued','fetching','triaging','itglue','analyzing','deep_review','complete','failed')),
result_analysis_id UUID REFERENCES analyzer_analyses(id) ON DELETE SET NULL,
queued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
started_at TIMESTAMPTZ,
finished_at TIMESTAMPTZ,
error_message TEXT
);
CREATE INDEX IF NOT EXISTS idx_analyzer_jobs_status_queued
ON analyzer_jobs (status, queued_at)
WHERE status IN ('queued','fetching','triaging','itglue','analyzing','deep_review');
CREATE INDEX IF NOT EXISTS idx_analyzer_jobs_ticket_number ON analyzer_jobs (ticket_number);
CREATE INDEX IF NOT EXISTS idx_analyzer_jobs_queued_at ON analyzer_jobs (queued_at DESC);
COMMENT ON TABLE analyzer_jobs IS 'On-demand analyzer pipeline jobs. Polled by a single worker that initialises at server start.';