-- content-atom: schema для MVP stage 1 -- Postgres (atom-db) + pgvector (расширение "vector") CREATE EXTENSION IF NOT EXISTS vector; CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE EXTENSION IF NOT EXISTS btree_gin; -- Проекты (домены) CREATE TABLE projects ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), slug TEXT UNIQUE NOT NULL, name TEXT NOT NULL, kind TEXT NOT NULL, -- site | service | social | podcast niche TEXT, role TEXT NOT NULL DEFAULT 'satellite', -- flagship | satellite | experiment | service locale TEXT DEFAULT 'ru', settings JSONB DEFAULT '{}', status TEXT DEFAULT 'active', -- active | paused | archived created_at TIMESTAMPTZ DEFAULT now() ); CREATE INDEX ON projects (role, niche); -- Атомы CREATE TABLE atoms ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), project_id UUID REFERENCES projects(id) ON DELETE CASCADE, type TEXT NOT NULL, -- claim | fact | stat | quote | definition | instruction | case | comparison | qa | opinion | hook | transition | cta | media_ref status TEXT NOT NULL DEFAULT 'draft', title TEXT, summary TEXT, body JSONB NOT NULL, plain_text TEXT, language TEXT DEFAULT 'ru', entities JSONB DEFAULT '[]', topics TEXT[] DEFAULT '{}', intent TEXT, audience TEXT, confidence NUMERIC(3,2), quality_score NUMERIC(4,2), word_count INT, source_refs JSONB DEFAULT '[]', embedding VECTOR(1024), generator JSONB, cost JSONB, pipeline_stage TEXT, parent_atom_id UUID, lineage_root_id UUID, created_at TIMESTAMPTZ DEFAULT now(), updated_at TIMESTAMPTZ DEFAULT now() ); CREATE INDEX ON atoms (project_id, type, status); CREATE INDEX ON atoms USING gin (plain_text gin_trgm_ops); CREATE INDEX ON atoms USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100); -- Версии под каналы CREATE TABLE atom_versions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), atom_id UUID REFERENCES atoms(id) ON DELETE CASCADE, channel TEXT NOT NULL, -- web_article | telegram | youtube_script | podcast | newsletter | service_card format TEXT NOT NULL, -- markdown | html | plain | srt | ssml | json content TEXT NOT NULL, meta JSONB DEFAULT '{}', quality_score NUMERIC(4,2), created_at TIMESTAMPTZ DEFAULT now(), UNIQUE (atom_id, channel, format) ); CREATE INDEX ON atom_versions (channel, created_at DESC); -- Задачи пайплайна + стоимость CREATE TABLE jobs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), kind TEXT NOT NULL, -- generate | edit | adapt | publish | index atom_id UUID REFERENCES atoms(id), status TEXT DEFAULT 'queued', attempts INT DEFAULT 0, max_attempts INT DEFAULT 3, payload JSONB DEFAULT '{}', error TEXT, scheduled_at TIMESTAMPTZ DEFAULT now(), started_at TIMESTAMPTZ, finished_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT now() ); CREATE TABLE runs ( id BIGSERIAL PRIMARY KEY, job_id UUID REFERENCES jobs(id) ON DELETE CASCADE, stage TEXT NOT NULL, model TEXT, tokens_in INT, tokens_out INT, seconds NUMERIC, cost_usd NUMERIC DEFAULT 0, quality NUMERIC(4,2), created_at TIMESTAMPTZ DEFAULT now() ); -- Индексы CREATE INDEX ON jobs (status, scheduled_at); CREATE INDEX ON runs (stage, created_at DESC); -- Связи между атомами (дедup + lineage) CREATE TABLE atom_relations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), atom_id_a UUID REFERENCES atoms(id) ON DELETE CASCADE, atom_id_b UUID REFERENCES atoms(id) ON DELETE CASCADE, relation_type TEXT NOT NULL, -- duplicate | related | superset | contradiction | parent_of similarity NUMERIC(4,3), -- cosine similarity (0-1) shared_topics TEXT[] DEFAULT '{}', confidence NUMERIC(3,2), source TEXT, -- semantic | lexical | manual created_at TIMESTAMPTZ DEFAULT now(), UNIQUE (atom_id_a, atom_id_b, relation_type) ); CREATE INDEX ON atom_relations (atom_id_a, relation_type); CREATE INDEX ON atom_relations (atom_id_b, relation_type); CREATE INDEX ON atom_relations (similarity DESC); -- Медиа-активы (stage 3) CREATE TABLE assets ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), project_id UUID REFERENCES projects(id) ON DELETE CASCADE, asset_type TEXT NOT NULL, -- image | audio | video | subtitle format TEXT NOT NULL, -- mp4 | mp3 | wav | jpg | png | srt uri TEXT, -- путь или URL title TEXT, duration_sec NUMERIC, transcript TEXT, meta JSONB DEFAULT '{}', size_bytes BIGINT, created_at TIMESTAMPTZ DEFAULT now() ); CREATE INDEX ON assets (project_id, asset_type); -- Привязка атомов к медиа (stage 3) CREATE TABLE atom_assets ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), atom_id UUID REFERENCES atoms(id) ON DELETE CASCADE, asset_id UUID REFERENCES assets(id) ON DELETE CASCADE, start_time_sec NUMERIC, -- таймкод начала end_time_sec NUMERIC, -- таймкод конца role TEXT, -- narration | b-roll | caption created_at TIMESTAMPTZ DEFAULT now(), UNIQUE (atom_id, asset_id, start_time_sec) ); CREATE INDEX ON atom_assets (asset_id); CREATE INDEX ON atom_assets (atom_id); -- Композиции (stage 3) CREATE TABLE compositions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), project_id UUID REFERENCES projects(id) ON DELETE CASCADE, channel TEXT NOT NULL, -- youtube | podcast | newsletter | web | telegram slug TEXT, title TEXT, status TEXT DEFAULT 'draft', -- draft | building | ready | scheduled | published recipe JSONB NOT NULL, -- правила сборки: типы, темы, порядок atom_ids UUID[] NOT NULL, -- порядок атомов в композиции content TEXT, meta JSONB DEFAULT '{}', scheduled_at TIMESTAMPTZ, published_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT now(), UNIQUE (project_id, channel, slug) ); CREATE INDEX ON compositions (project_id, channel, status); -- Публикации (stage 3) CREATE TABLE publications ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), composition_id UUID REFERENCES compositions(id) ON DELETE CASCADE, target TEXT NOT NULL, -- URL, channel_id, video_id, list_id external_id TEXT, published_at TIMESTAMPTZ DEFAULT now(), metrics JSONB DEFAULT '{}' -- views, clicks, conversions, revenue ); CREATE INDEX ON publications (composition_id); -- Промпты как данные (stage 3) CREATE TABLE prompts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), slug TEXT NOT NULL, version TEXT NOT NULL DEFAULT 'v1', description TEXT, system_prompt TEXT, user_prompt TEXT, params JSONB DEFAULT '{}', -- model, temperature, max_tokens created_at TIMESTAMPTZ DEFAULT now(), updated_at TIMESTAMPTZ DEFAULT now(), UNIQUE (slug, version) );