117 lines
5.8 KiB
MySQL
117 lines
5.8 KiB
MySQL
|
|
-- 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.';
|