"""Centralized SQL for mail_summary.""" class Schema: ENSURE_ANALYSIS_JOBS = """ CREATE TABLE IF NOT EXISTS analysis_jobs ( id BIGSERIAL PRIMARY KEY, status TEXT NOT NULL DEFAULT 'pending', mailbox_scope TEXT NOT NULL DEFAULT 'both', date_from DATE NOT NULL, date_to DATE NOT NULL, model TEXT NOT NULL DEFAULT '', inbox_zip_name TEXT, sent_zip_name TEXT, inbox_count INTEGER NOT NULL DEFAULT 0, sent_count INTEGER NOT NULL DEFAULT 0, filtered_inbox_count INTEGER NOT NULL DEFAULT 0, filtered_sent_count INTEGER NOT NULL DEFAULT 0, progress_pct INTEGER NOT NULL DEFAULT 0, progress_label TEXT NOT NULL DEFAULT '', progress_detail TEXT NOT NULL DEFAULT '', error_message TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), started_at TIMESTAMPTZ, completed_at TIMESTAMPTZ, CONSTRAINT analysis_jobs_status_chk CHECK ( status IN ('pending', 'extracting', 'filtering', 'analyzing', 'completed', 'failed') ), CONSTRAINT analysis_jobs_scope_chk CHECK ( mailbox_scope IN ('inbox', 'sent', 'both') ), CONSTRAINT analysis_jobs_date_chk CHECK (date_from <= date_to) ) """ ADD_PROGRESS_PCT = """ ALTER TABLE analysis_jobs ADD COLUMN IF NOT EXISTS progress_pct INTEGER NOT NULL DEFAULT 0 """ ADD_PROGRESS_LABEL = """ ALTER TABLE analysis_jobs ADD COLUMN IF NOT EXISTS progress_label TEXT NOT NULL DEFAULT '' """ ADD_PROGRESS_DETAIL = """ ALTER TABLE analysis_jobs ADD COLUMN IF NOT EXISTS progress_detail TEXT NOT NULL DEFAULT '' """ IDX_ANALYSIS_JOBS_CREATED_AT = """ CREATE INDEX IF NOT EXISTS idx_analysis_jobs_created_at ON analysis_jobs (created_at DESC) """ IDX_ANALYSIS_JOBS_STATUS = """ CREATE INDEX IF NOT EXISTS idx_analysis_jobs_status ON analysis_jobs (status) """ ENSURE_MAILS = """ CREATE TABLE IF NOT EXISTS mails ( id BIGSERIAL PRIMARY KEY, job_id BIGINT NOT NULL REFERENCES analysis_jobs(id) ON DELETE CASCADE, mailbox TEXT NOT NULL, message_id TEXT, thread_key TEXT, subject TEXT NOT NULL DEFAULT '', from_addr TEXT NOT NULL DEFAULT '', to_addrs TEXT NOT NULL DEFAULT '', cc_addrs TEXT NOT NULL DEFAULT '', mailed_at TIMESTAMPTZ, attachment_names JSONB NOT NULL DEFAULT '[]'::jsonb, body_text TEXT, body_purged BOOLEAN NOT NULL DEFAULT FALSE, summary JSONB, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT mails_mailbox_chk CHECK (mailbox IN ('inbox', 'sent')) ) """ IDX_MAILS_JOB_ID = "CREATE INDEX IF NOT EXISTS idx_mails_job_id ON mails (job_id)" IDX_MAILS_JOB_MAILED_AT = ( "CREATE INDEX IF NOT EXISTS idx_mails_job_mailed_at ON mails (job_id, mailed_at)" ) IDX_MAILS_THREAD_KEY = ( "CREATE INDEX IF NOT EXISTS idx_mails_thread_key ON mails (job_id, thread_key)" ) IDX_MAILS_MESSAGE_ID = ( "CREATE INDEX IF NOT EXISTS idx_mails_message_id ON mails (job_id, message_id)" ) ENSURE_WEEKLY_REPORTS = """ CREATE TABLE IF NOT EXISTS weekly_reports ( id BIGSERIAL PRIMARY KEY, job_id BIGINT NOT NULL UNIQUE REFERENCES analysis_jobs(id) ON DELETE CASCADE, model TEXT NOT NULL DEFAULT '', scope_note TEXT NOT NULL DEFAULT '', report JSONB NOT NULL DEFAULT '{}'::jsonb, report_markdown TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ) """ IDX_WEEKLY_REPORTS_CREATED_AT = """ CREATE INDEX IF NOT EXISTS idx_weekly_reports_created_at ON weekly_reports (created_at DESC) """ class Jobs: INSERT = """ INSERT INTO analysis_jobs ( status, mailbox_scope, date_from, date_to, model, inbox_zip_name, sent_zip_name ) VALUES ( 'pending', %(mailbox_scope)s, %(date_from)s, %(date_to)s, %(model)s, %(inbox_zip_name)s, %(sent_zip_name)s ) RETURNING id, status, mailbox_scope, date_from, date_to, model, inbox_zip_name, sent_zip_name, inbox_count, sent_count, filtered_inbox_count, filtered_sent_count, progress_pct, progress_label, progress_detail, error_message, created_at, started_at, completed_at """ GET_BY_ID = """ SELECT id, status, mailbox_scope, date_from, date_to, model, inbox_zip_name, sent_zip_name, inbox_count, sent_count, filtered_inbox_count, filtered_sent_count, progress_pct, progress_label, progress_detail, error_message, created_at, started_at, completed_at FROM analysis_jobs WHERE id = %(job_id)s """ LIST_RECENT = """ SELECT id, status, mailbox_scope, date_from, date_to, model, inbox_zip_name, sent_zip_name, inbox_count, sent_count, filtered_inbox_count, filtered_sent_count, progress_pct, progress_label, progress_detail, error_message, created_at, started_at, completed_at FROM analysis_jobs ORDER BY created_at DESC LIMIT %(limit)s """ UPDATE = """ UPDATE analysis_jobs SET status = COALESCE(%(status)s, status), inbox_count = COALESCE(%(inbox_count)s, inbox_count), sent_count = COALESCE(%(sent_count)s, sent_count), filtered_inbox_count = COALESCE(%(filtered_inbox_count)s, filtered_inbox_count), filtered_sent_count = COALESCE(%(filtered_sent_count)s, filtered_sent_count), progress_pct = COALESCE(%(progress_pct)s, progress_pct), progress_label = COALESCE(%(progress_label)s, progress_label), progress_detail = COALESCE(%(progress_detail)s, progress_detail), error_message = COALESCE(%(error_message)s, error_message), started_at = CASE WHEN %(set_started)s THEN COALESCE(started_at, NOW()) ELSE started_at END, completed_at = CASE WHEN %(set_completed)s THEN NOW() ELSE completed_at END WHERE id = %(job_id)s RETURNING id, status, mailbox_scope, date_from, date_to, model, inbox_zip_name, sent_zip_name, inbox_count, sent_count, filtered_inbox_count, filtered_sent_count, progress_pct, progress_label, progress_detail, error_message, created_at, started_at, completed_at """ class Mails: INSERT = """ INSERT INTO mails ( job_id, mailbox, message_id, thread_key, subject, from_addr, to_addrs, cc_addrs, mailed_at, attachment_names, body_text ) VALUES ( %(job_id)s, %(mailbox)s, %(message_id)s, %(thread_key)s, %(subject)s, %(from_addr)s, %(to_addrs)s, %(cc_addrs)s, %(mailed_at)s, %(attachment_names)s, %(body_text)s ) RETURNING id """ LIST_BY_JOB = """ SELECT id, job_id, mailbox, message_id, thread_key, subject, from_addr, to_addrs, cc_addrs, mailed_at, attachment_names, body_text, body_purged, summary, created_at FROM mails WHERE job_id = %(job_id)s ORDER BY mailed_at NULLS LAST, id """ UPDATE_SUMMARY = """ UPDATE mails SET summary = %(summary)s WHERE id = %(mail_id)s """ PURGE_BODIES = """ UPDATE mails SET body_text = NULL, body_purged = TRUE WHERE job_id = %(job_id)s """ class Reports: GET_BY_JOB = """ SELECT id, job_id, model, scope_note, report, report_markdown, created_at, updated_at FROM weekly_reports WHERE job_id = %(job_id)s """ UPSERT = """ INSERT INTO weekly_reports (job_id, model, scope_note, report, report_markdown) VALUES ( %(job_id)s, %(model)s, %(scope_note)s, %(report)s, %(report_markdown)s ) ON CONFLICT (job_id) DO UPDATE SET model = EXCLUDED.model, scope_note = EXCLUDED.scope_note, report = EXCLUDED.report, report_markdown = EXCLUDED.report_markdown, updated_at = NOW() RETURNING id, job_id, model, scope_note, report, report_markdown, created_at, updated_at """