ALTER TABLE ai_qualification_batches ADD COLUMN IF NOT EXISTS prompt_tokens INTEGER, ADD COLUMN IF NOT EXISTS completion_tokens INTEGER, ADD COLUMN IF NOT EXISTS total_tokens INTEGER, ADD COLUMN IF NOT EXISTS estimated_cost_usd NUMERIC(12, 6); ALTER TABLE raw_posts ADD COLUMN IF NOT EXISTS rewrite_status TEXT, ADD COLUMN IF NOT EXISTS rewritten_text TEXT, ADD COLUMN IF NOT EXISTS rewrite_notes TEXT, ADD COLUMN IF NOT EXISTS rewrite_model TEXT, ADD COLUMN IF NOT EXISTS rewrite_prompt_hash TEXT, ADD COLUMN IF NOT EXISTS rewrite_batch_id BIGINT, ADD COLUMN IF NOT EXISTS rewritten_at TIMESTAMPTZ; CREATE INDEX IF NOT EXISTS idx_raw_posts_writer_claim ON raw_posts(qualification_status, rewrite_status, qualified_at, id) WHERE qualification_status='accepted'; CREATE TABLE IF NOT EXISTS ai_writer_batches ( id BIGSERIAL PRIMARY KEY, status TEXT NOT NULL DEFAULT 'processing', provider TEXT NOT NULL, model TEXT NOT NULL, prompt_hash TEXT NOT NULL, posts_count INTEGER NOT NULL DEFAULT 0, ready_count INTEGER NOT NULL DEFAULT 0, failed_count INTEGER NOT NULL DEFAULT 0, request_json JSONB NOT NULL DEFAULT '{}'::jsonb, response_json JSONB, error TEXT, prompt_tokens INTEGER, completion_tokens INTEGER, total_tokens INTEGER, estimated_cost_usd NUMERIC(12, 6), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), completed_at TIMESTAMPTZ ); INSERT INTO worker_controls(name, enabled, settings_json) VALUES ('ai-writer', FALSE, '{}'::jsonb) ON CONFLICT (name) DO NOTHING; INSERT INTO app_settings(key, value_json, value_type, title, description, category) VALUES ('ai_writer_enabled', 'false'::jsonb, 'bool', 'AI-райтер включён', 'Разрешает отправлять прошедшие квалификацию посты на рерайт.', 'AI Writer'), ('ai_writer_provider', '"anthropic"'::jsonb, 'str', 'Провайдер LLM', 'Через какого провайдера LiteLLM отправлять запросы райтера.', 'AI Writer'), ('ai_writer_model', '"claude-haiku-4-5-20251001"'::jsonb, 'str', 'Модель', 'Модель для рерайта. Для отладки используем дешёвую Haiku.', 'AI Writer'), ('ai_writer_api_key', '""'::jsonb, 'secret', 'API-ключ', 'Ключ провайдера для AI-райтера. Можно использовать тот же Anthropic-ключ, что и у квалификатора.', 'AI Writer'), ('ai_writer_api_base', '""'::jsonb, 'str', 'API base URL', 'Необязательный base URL для OpenAI-compatible провайдеров.', 'AI Writer'), ('ai_writer_prompt', '"Ты редактор Telegram/VK-канала про милитари, тактическую и airsoft-снарягу. Перепиши исходный пост производителя в готовый к публикации пост на русском языке.\\n\\nСтиль: живой, понятный, без канцелярита, без чрезмерной рекламы, без выдуманных фактов. Сохраняй конкретику: модель, материал, цвета, размеры, назначение, цену и условия, если они есть в исходнике. Не добавляй то, чего нет в исходном посте.\\n\\nФормат результата: 1 короткий заголовок первой строкой, затем 2-5 абзацев или аккуратный список, если исходник товарный. В конце не добавляй ссылки и хэштеги.\\n\\nВерни только JSON-объект по схеме: {\\\"rewrites\\\":[{\\\"id\\\":123,\\\"text\\\":\\\"готовый текст\\\",\\\"notes\\\":\\\"короткая заметка для редактора\\\"}]}. Без markdown вне JSON."'::jsonb, 'text', 'Промпт райтера', 'Главный промпт рерайта. Поддерживает обычные переносы строк.', 'AI Writer'), ('ai_writer_batch_size', '1'::jsonb, 'int', 'Размер batch', 'Сколько постов переписывать за один запрос. Для отладки лучше 1.', 'AI Writer'), ('ai_writer_max_text_chars', '3500'::jsonb, 'int', 'Максимум символов исходника', 'Обрезает исходный raw-текст перед отправкой в LLM.', 'AI Writer'), ('ai_writer_temperature', '0.4'::jsonb, 'float', 'Температура генерации', 'Ниже — стабильнее и суше, выше — свободнее и разнообразнее.', 'AI Writer'), ('ai_writer_timeout_sec', '180'::jsonb, 'int', 'Таймаут, сек', 'Сколько ждать ответ модели.', 'AI Writer'), ('ai_writer_interval_sec', '60'::jsonb, 'int', 'Интервал ожидания, сек', 'Пауза между проверками очереди writer, когда работы нет.', 'AI Writer') ON CONFLICT (key) DO NOTHING;