|
|
@@ -0,0 +1,300 @@
|
|
|
+-- Creation Knowledge formal cloud schema.
|
|
|
+-- Applied to database: creation_knowledge_prod
|
|
|
+-- Schema: creation_knowledge
|
|
|
+
|
|
|
+CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|
|
+
|
|
|
+CREATE SCHEMA IF NOT EXISTS creation_knowledge;
|
|
|
+
|
|
|
+CREATE TABLE IF NOT EXISTS creation_knowledge.schema_migrations (
|
|
|
+ version text PRIMARY KEY,
|
|
|
+ description text NOT NULL,
|
|
|
+ applied_at timestamptz NOT NULL DEFAULT now()
|
|
|
+);
|
|
|
+
|
|
|
+CREATE OR REPLACE FUNCTION creation_knowledge.touch_updated_at()
|
|
|
+RETURNS trigger
|
|
|
+LANGUAGE plpgsql
|
|
|
+AS $$
|
|
|
+BEGIN
|
|
|
+ NEW.updated_at = now();
|
|
|
+ RETURN NEW;
|
|
|
+END;
|
|
|
+$$;
|
|
|
+
|
|
|
+CREATE TABLE IF NOT EXISTS creation_knowledge.query_batches (
|
|
|
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
+ name text NOT NULL,
|
|
|
+ source_type text NOT NULL DEFAULT 'manual',
|
|
|
+ generation_method text,
|
|
|
+ target_platforms text[] NOT NULL DEFAULT ARRAY[]::text[],
|
|
|
+ status text NOT NULL DEFAULT 'draft',
|
|
|
+ metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
|
+ error_message text,
|
|
|
+ created_at timestamptz NOT NULL DEFAULT now(),
|
|
|
+ updated_at timestamptz NOT NULL DEFAULT now()
|
|
|
+);
|
|
|
+
|
|
|
+CREATE TABLE IF NOT EXISTS creation_knowledge.queries (
|
|
|
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
+ batch_id uuid REFERENCES creation_knowledge.query_batches(id) ON DELETE CASCADE,
|
|
|
+ query_text text NOT NULL,
|
|
|
+ axes jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
|
+ keep boolean,
|
|
|
+ filter_reason text,
|
|
|
+ status text NOT NULL DEFAULT 'draft',
|
|
|
+ sort_order integer NOT NULL DEFAULT 0,
|
|
|
+ metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
|
+ error_message text,
|
|
|
+ created_at timestamptz NOT NULL DEFAULT now(),
|
|
|
+ updated_at timestamptz NOT NULL DEFAULT now()
|
|
|
+);
|
|
|
+
|
|
|
+CREATE TABLE IF NOT EXISTS creation_knowledge.acquisition_runs (
|
|
|
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
+ batch_id uuid REFERENCES creation_knowledge.query_batches(id),
|
|
|
+ run_key text UNIQUE,
|
|
|
+ status text NOT NULL DEFAULT 'pending',
|
|
|
+ note text,
|
|
|
+ metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
|
+ error_message text,
|
|
|
+ started_at timestamptz,
|
|
|
+ finished_at timestamptz,
|
|
|
+ created_at timestamptz NOT NULL DEFAULT now(),
|
|
|
+ updated_at timestamptz NOT NULL DEFAULT now()
|
|
|
+);
|
|
|
+
|
|
|
+CREATE TABLE IF NOT EXISTS creation_knowledge.acquisition_jobs (
|
|
|
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
+ run_id uuid NOT NULL REFERENCES creation_knowledge.acquisition_runs(id) ON DELETE CASCADE,
|
|
|
+ query_id uuid REFERENCES creation_knowledge.queries(id),
|
|
|
+ platform text NOT NULL,
|
|
|
+ status text NOT NULL DEFAULT 'pending',
|
|
|
+ search_limit integer,
|
|
|
+ display_limit integer,
|
|
|
+ attempt_count integer NOT NULL DEFAULT 0,
|
|
|
+ metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
|
+ error_message text,
|
|
|
+ started_at timestamptz,
|
|
|
+ finished_at timestamptz,
|
|
|
+ created_at timestamptz NOT NULL DEFAULT now(),
|
|
|
+ updated_at timestamptz NOT NULL DEFAULT now(),
|
|
|
+ UNIQUE (run_id, query_id, platform)
|
|
|
+);
|
|
|
+
|
|
|
+CREATE TABLE IF NOT EXISTS creation_knowledge.candidate_items (
|
|
|
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
+ job_id uuid REFERENCES creation_knowledge.acquisition_jobs(id) ON DELETE SET NULL,
|
|
|
+ query_id uuid REFERENCES creation_knowledge.queries(id) ON DELETE SET NULL,
|
|
|
+ platform text NOT NULL,
|
|
|
+ platform_item_id text,
|
|
|
+ canonical_url text,
|
|
|
+ content_type text,
|
|
|
+ title text,
|
|
|
+ author_name text,
|
|
|
+ published_at timestamptz,
|
|
|
+ raw_summary text,
|
|
|
+ status text NOT NULL DEFAULT 'candidate',
|
|
|
+ source_payload jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
|
+ metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
|
+ error_message text,
|
|
|
+ created_at timestamptz NOT NULL DEFAULT now(),
|
|
|
+ updated_at timestamptz NOT NULL DEFAULT now()
|
|
|
+);
|
|
|
+
|
|
|
+CREATE TABLE IF NOT EXISTS creation_knowledge.media_assets (
|
|
|
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
+ item_id uuid NOT NULL REFERENCES creation_knowledge.candidate_items(id) ON DELETE CASCADE,
|
|
|
+ media_type text NOT NULL,
|
|
|
+ source_url text,
|
|
|
+ oss_url text,
|
|
|
+ cdn_url text,
|
|
|
+ position integer NOT NULL DEFAULT 0,
|
|
|
+ status text NOT NULL DEFAULT 'pending',
|
|
|
+ source_payload jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
|
+ metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
|
+ error_message text,
|
|
|
+ created_at timestamptz NOT NULL DEFAULT now(),
|
|
|
+ updated_at timestamptz NOT NULL DEFAULT now()
|
|
|
+);
|
|
|
+
|
|
|
+CREATE TABLE IF NOT EXISTS creation_knowledge.item_classifications (
|
|
|
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
+ item_id uuid NOT NULL REFERENCES creation_knowledge.candidate_items(id) ON DELETE CASCADE,
|
|
|
+ is_creation_knowledge boolean,
|
|
|
+ label text,
|
|
|
+ confidence numeric(5,4),
|
|
|
+ reason text,
|
|
|
+ model_name text,
|
|
|
+ prompt_version text,
|
|
|
+ result_payload jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
|
+ status text NOT NULL DEFAULT 'pending',
|
|
|
+ error_message text,
|
|
|
+ created_at timestamptz NOT NULL DEFAULT now(),
|
|
|
+ updated_at timestamptz NOT NULL DEFAULT now()
|
|
|
+);
|
|
|
+
|
|
|
+CREATE TABLE IF NOT EXISTS creation_knowledge.decode_jobs (
|
|
|
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
+ item_id uuid NOT NULL REFERENCES creation_knowledge.candidate_items(id) ON DELETE CASCADE,
|
|
|
+ status text NOT NULL DEFAULT 'pending',
|
|
|
+ attempt_count integer NOT NULL DEFAULT 0,
|
|
|
+ metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
|
+ error_message text,
|
|
|
+ started_at timestamptz,
|
|
|
+ finished_at timestamptz,
|
|
|
+ created_at timestamptz NOT NULL DEFAULT now(),
|
|
|
+ updated_at timestamptz NOT NULL DEFAULT now()
|
|
|
+);
|
|
|
+
|
|
|
+CREATE TABLE IF NOT EXISTS creation_knowledge.decode_results (
|
|
|
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
+ decode_job_id uuid REFERENCES creation_knowledge.decode_jobs(id) ON DELETE SET NULL,
|
|
|
+ item_id uuid NOT NULL REFERENCES creation_knowledge.candidate_items(id) ON DELETE CASCADE,
|
|
|
+ read_result jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
|
+ gate_result jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
|
+ framing_result jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
|
+ status text NOT NULL DEFAULT 'draft',
|
|
|
+ error_message text,
|
|
|
+ created_at timestamptz NOT NULL DEFAULT now(),
|
|
|
+ updated_at timestamptz NOT NULL DEFAULT now()
|
|
|
+);
|
|
|
+
|
|
|
+CREATE TABLE IF NOT EXISTS creation_knowledge.knowledge_particles (
|
|
|
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
+ decode_result_id uuid REFERENCES creation_knowledge.decode_results(id) ON DELETE CASCADE,
|
|
|
+ item_id uuid REFERENCES creation_knowledge.candidate_items(id) ON DELETE CASCADE,
|
|
|
+ parent_particle_id uuid REFERENCES creation_knowledge.knowledge_particles(id) ON DELETE SET NULL,
|
|
|
+ particle_type text NOT NULL CHECK (particle_type IN ('what', 'how', 'why')),
|
|
|
+ title text NOT NULL,
|
|
|
+ business_stage text,
|
|
|
+ creation_stage text,
|
|
|
+ content jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
|
+ sort_order integer NOT NULL DEFAULT 0,
|
|
|
+ status text NOT NULL DEFAULT 'draft',
|
|
|
+ metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
|
+ error_message text,
|
|
|
+ created_at timestamptz NOT NULL DEFAULT now(),
|
|
|
+ updated_at timestamptz NOT NULL DEFAULT now()
|
|
|
+);
|
|
|
+
|
|
|
+CREATE TABLE IF NOT EXISTS creation_knowledge.scope_results (
|
|
|
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
+ particle_id uuid REFERENCES creation_knowledge.knowledge_particles(id) ON DELETE CASCADE,
|
|
|
+ item_id uuid REFERENCES creation_knowledge.candidate_items(id) ON DELETE CASCADE,
|
|
|
+ scope_type text NOT NULL,
|
|
|
+ scope_value text NOT NULL,
|
|
|
+ is_reused boolean,
|
|
|
+ matched_scope_id text,
|
|
|
+ confidence numeric(5,4),
|
|
|
+ evidence jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
|
+ status text NOT NULL DEFAULT 'draft',
|
|
|
+ error_message text,
|
|
|
+ created_at timestamptz NOT NULL DEFAULT now(),
|
|
|
+ updated_at timestamptz NOT NULL DEFAULT now()
|
|
|
+);
|
|
|
+
|
|
|
+CREATE TABLE IF NOT EXISTS creation_knowledge.payload_drafts (
|
|
|
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
+ particle_id uuid REFERENCES creation_knowledge.knowledge_particles(id) ON DELETE CASCADE,
|
|
|
+ item_id uuid REFERENCES creation_knowledge.candidate_items(id) ON DELETE CASCADE,
|
|
|
+ payload jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
|
+ review_status text NOT NULL DEFAULT 'pending',
|
|
|
+ ingest_ready boolean NOT NULL DEFAULT false,
|
|
|
+ status text NOT NULL DEFAULT 'draft',
|
|
|
+ error_message text,
|
|
|
+ created_at timestamptz NOT NULL DEFAULT now(),
|
|
|
+ updated_at timestamptz NOT NULL DEFAULT now()
|
|
|
+);
|
|
|
+
|
|
|
+CREATE TABLE IF NOT EXISTS creation_knowledge.ingest_records (
|
|
|
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
+ payload_draft_id uuid REFERENCES creation_knowledge.payload_drafts(id) ON DELETE SET NULL,
|
|
|
+ target_system text NOT NULL,
|
|
|
+ target_id text,
|
|
|
+ status text NOT NULL DEFAULT 'pending',
|
|
|
+ attempt_count integer NOT NULL DEFAULT 0,
|
|
|
+ response_payload jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
|
+ error_message text,
|
|
|
+ created_at timestamptz NOT NULL DEFAULT now(),
|
|
|
+ updated_at timestamptz NOT NULL DEFAULT now()
|
|
|
+);
|
|
|
+
|
|
|
+CREATE TABLE IF NOT EXISTS creation_knowledge.contract_snapshots (
|
|
|
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
+ contract_name text NOT NULL,
|
|
|
+ contract_type text NOT NULL,
|
|
|
+ version_label text,
|
|
|
+ content_hash text,
|
|
|
+ source_path text,
|
|
|
+ snapshot jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
|
+ created_at timestamptz NOT NULL DEFAULT now()
|
|
|
+);
|
|
|
+
|
|
|
+CREATE INDEX IF NOT EXISTS idx_queries_batch ON creation_knowledge.queries(batch_id);
|
|
|
+CREATE INDEX IF NOT EXISTS idx_acquisition_runs_batch ON creation_knowledge.acquisition_runs(batch_id);
|
|
|
+CREATE INDEX IF NOT EXISTS idx_acquisition_jobs_run ON creation_knowledge.acquisition_jobs(run_id);
|
|
|
+CREATE INDEX IF NOT EXISTS idx_candidate_items_job ON creation_knowledge.candidate_items(job_id);
|
|
|
+CREATE INDEX IF NOT EXISTS idx_candidate_items_platform_item ON creation_knowledge.candidate_items(platform, platform_item_id);
|
|
|
+CREATE INDEX IF NOT EXISTS idx_media_assets_item ON creation_knowledge.media_assets(item_id);
|
|
|
+CREATE INDEX IF NOT EXISTS idx_item_classifications_item ON creation_knowledge.item_classifications(item_id);
|
|
|
+CREATE INDEX IF NOT EXISTS idx_decode_jobs_item ON creation_knowledge.decode_jobs(item_id);
|
|
|
+CREATE INDEX IF NOT EXISTS idx_decode_results_item ON creation_knowledge.decode_results(item_id);
|
|
|
+CREATE INDEX IF NOT EXISTS idx_knowledge_particles_item ON creation_knowledge.knowledge_particles(item_id);
|
|
|
+CREATE INDEX IF NOT EXISTS idx_scope_results_particle ON creation_knowledge.scope_results(particle_id);
|
|
|
+CREATE INDEX IF NOT EXISTS idx_payload_drafts_particle ON creation_knowledge.payload_drafts(particle_id);
|
|
|
+CREATE INDEX IF NOT EXISTS idx_ingest_records_payload ON creation_knowledge.ingest_records(payload_draft_id);
|
|
|
+CREATE INDEX IF NOT EXISTS idx_contract_snapshots_name ON creation_knowledge.contract_snapshots(contract_name, contract_type);
|
|
|
+
|
|
|
+DO $$
|
|
|
+DECLARE
|
|
|
+ table_name text;
|
|
|
+BEGIN
|
|
|
+ FOREACH table_name IN ARRAY ARRAY[
|
|
|
+ 'query_batches',
|
|
|
+ 'queries',
|
|
|
+ 'acquisition_runs',
|
|
|
+ 'acquisition_jobs',
|
|
|
+ 'candidate_items',
|
|
|
+ 'media_assets',
|
|
|
+ 'item_classifications',
|
|
|
+ 'decode_jobs',
|
|
|
+ 'decode_results',
|
|
|
+ 'knowledge_particles',
|
|
|
+ 'scope_results',
|
|
|
+ 'payload_drafts',
|
|
|
+ 'ingest_records'
|
|
|
+ ]
|
|
|
+ LOOP
|
|
|
+ EXECUTE format('DROP TRIGGER IF EXISTS trg_%I_touch_updated_at ON creation_knowledge.%I', table_name, table_name);
|
|
|
+ EXECUTE format(
|
|
|
+ 'CREATE TRIGGER trg_%I_touch_updated_at BEFORE UPDATE ON creation_knowledge.%I FOR EACH ROW EXECUTE FUNCTION creation_knowledge.touch_updated_at()',
|
|
|
+ table_name,
|
|
|
+ table_name
|
|
|
+ );
|
|
|
+ END LOOP;
|
|
|
+END;
|
|
|
+$$;
|
|
|
+
|
|
|
+DO $$
|
|
|
+BEGIN
|
|
|
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'ck_app') THEN
|
|
|
+ GRANT CONNECT ON DATABASE creation_knowledge_prod TO ck_app;
|
|
|
+ GRANT USAGE ON SCHEMA creation_knowledge TO ck_app;
|
|
|
+ GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA creation_knowledge TO ck_app;
|
|
|
+ GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA creation_knowledge TO ck_app;
|
|
|
+ ALTER DEFAULT PRIVILEGES IN SCHEMA creation_knowledge
|
|
|
+ GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO ck_app;
|
|
|
+ ALTER DEFAULT PRIVILEGES IN SCHEMA creation_knowledge
|
|
|
+ GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO ck_app;
|
|
|
+ ALTER ROLE ck_app IN DATABASE creation_knowledge_prod
|
|
|
+ SET search_path = creation_knowledge, public;
|
|
|
+ END IF;
|
|
|
+END;
|
|
|
+$$;
|
|
|
+
|
|
|
+INSERT INTO creation_knowledge.schema_migrations(version, description)
|
|
|
+VALUES ('001_creation_knowledge_schema', 'formal creation knowledge cloud schema')
|
|
|
+ON CONFLICT (version) DO UPDATE
|
|
|
+SET description = EXCLUDED.description,
|
|
|
+ applied_at = now();
|