diff --git a/deploy/db/prompts.py b/deploy/db/prompts.py index 3dbfd5b..cfbb3ef 100644 --- a/deploy/db/prompts.py +++ b/deploy/db/prompts.py @@ -3,8 +3,9 @@ from .connection import query, execute, execute_returning def _serialize(row): - """Convert datetime fields to strings for JSON serialization.""" + """Convert datetime fields to strings for JSON serialization (non-mutating).""" if row and row.get("created_at"): + row = dict(row) row["created_at"] = str(row["created_at"]) return row @@ -24,57 +25,59 @@ def get(prompt_id): def seed_defaults(): """Auto-seed default prompts if table is empty.""" - count = query("SELECT COUNT(*) as cnt FROM prompts") - if count[0]["cnt"] > 0: - return - - extract_body = ( - "Ты — анализатор договоров облачного провайдера.\n\n" - "Ниже текст спецификации услуг из ПЕРВОГО документа (базовый договор).\n" - "Извлеки ВСЕ строки спецификации в JSON-массив.\n\n" - "Верни СТРОГО JSON без пояснений. Не используй markdown-блоки.\n\n" - "ФОРМАТ:\n" - '{\n "mode": "partial",\n "ops": [\n' - ' {"action": "ADD", "new_row": {"name": "полное наименование", "price": число, "qty": число, "sum": число, "date_start": "YYYY-MM-DD"}, "comment": ""}\n' - " ]\n}\n\n" - "ПРАВИЛА:\n" - "1. Извлеки КАЖДУЮ строку таблицы спецификации как отдельную ADD-операцию.\n" - '2. Пропускай итоговые строки ("Итого...") и строки с подписями.\n' - "3. Если ячейка пустая — ставь null (не пиши 0).\n" - "4. price, qty, sum — ЧИСЛА, не строки.\n\n" - "ТЕКСТ ДОКУМЕНТА:\n---\n{doc_text}\n---" - ) - execute( - "INSERT INTO prompts (role, name, body, is_active, notes) VALUES (%s, %s, %s, %s, %s)", - ("extract", "default-v1", extract_body, True, "Авто-создан из llm_prompt.py _build_initial"), - ) - diff_body = ( - "Ты — анализатор допсоглашений к договорам облачного провайдера.\n\n" - "У тебя есть текущая спецификация услуг и текст нового допсоглашения (ДС).\n" - "Твоя задача — определить, какие изменения вносит ДС в текущую спецификацию.\n\n" - "Верни СТРОГО JSON без пояснений. Не используй markdown-блоки.\n\n" - "ФОРМАТ:\n" - '{\n "mode": "partial" | "full_replace",\n "ops": [\n' - ' {"action": "ADD", "new_row": {"name": "...", "price": число, "qty": число, "sum": число, "date_start": "YYYY-MM-DD"}, "comment": "..."},\n' - ' {"action": "UPDATE", "target_id": "rN", "new_values": {"price": число}, "comment": "..."},\n' - ' {"action": "DELETE", "target_id": "rN", "comment": "..."},\n' - ' {"action": "UNRESOLVED", "new_values": {"name": "...", "price": число, ...}, "reason": "почему не смог сопоставить"}\n' - " ]\n}\n\n" - "ПРАВИЛА:\n" - '1. mode = "full_replace" — если в тексте есть фразы: «в следующей редакции», «заменить приложение», «излагается в следующей редакции». При full_replace — опиши ВСЕ новые строки как ADD.\n' - '2. mode = "partial" — если ДС меняет только отдельные строки.\n' - "3. Для UPDATE/DELETE — укажи target_id (r1, r2...) из списка текущей спецификации. НЕ придумывай новые id.\n" - "4. new_values в UPDATE — только ИЗМЕНЁННЫЕ поля (не все).\n" - "5. Если не можешь однозначно сопоставить строку — action: UNRESOLVED с reason.\n" - "6. Пропускай итоговые строки и подписи.\n" - "7. price, qty, sum — ЧИСЛА (не строки).\n\n" - "ТЕКУЩАЯ СПЕЦИФИКАЦИЯ:\n{spec_current}\n\n" - "ТЕКСТ ДОПСОГЛАШЕНИЯ:\n---\n{doc_text}\n---" - ) - execute( - "INSERT INTO prompts (role, name, body, is_active, notes) VALUES (%s, %s, %s, %s, %s)", - ("diff", "default-v1", diff_body, True, "Авто-создан из llm_prompt.py _build_diff"), - ) + # Check each role separately — don't skip if one is missing + existing_roles = set(r["role"] for r in query("SELECT DISTINCT role FROM prompts")) + + if "extract" not in existing_roles: + extract_body = ( + "Ты — анализатор договоров облачного провайдера.\n\n" + "Ниже текст спецификации услуг из ПЕРВОГО документа (базовый договор).\n" + "Извлеки ВСЕ строки спецификации в JSON-массив.\n\n" + "Верни СТРОГО JSON без пояснений. Не используй markdown-блоки.\n\n" + "ФОРМАТ:\n" + '{\n "mode": "partial",\n "ops": [\n' + ' {"action": "ADD", "new_row": {"name": "полное наименование", "price": число, "qty": число, "sum": число, "date_start": "YYYY-MM-DD"}, "comment": ""}\n' + " ]\n}\n\n" + "ПРАВИЛА:\n" + "1. Извлеки КАЖДУЮ строку таблицы спецификации как отдельную ADD-операцию.\n" + '2. Пропускай итоговые строки ("Итого...") и строки с подписями.\n' + "3. Если ячейка пустая — ставь null (не пиши 0).\n" + "4. price, qty, sum — ЧИСЛА, не строки.\n\n" + "ТЕКСТ ДОКУМЕНТА:\n---\n{doc_text}\n---" + ) + execute( + "INSERT INTO prompts (role, name, body, is_active, notes) VALUES (%s, %s, %s, %s, %s)", + ("extract", "default-v1", extract_body, True, "Авто-создан из llm_prompt.py _build_initial"), + ) + + if "diff" not in existing_roles: + diff_body = ( + "Ты — анализатор допсоглашений к договорам облачного провайдера.\n\n" + "У тебя есть текущая спецификация услуг и текст нового допсоглашения (ДС).\n" + "Твоя задача — определить, какие изменения вносит ДС в текущую спецификацию.\n\n" + "Верни СТРОГО JSON без пояснений. Не используй markdown-блоки.\n\n" + "ФОРМАТ:\n" + '{\n "mode": "partial" | "full_replace",\n "ops": [\n' + ' {"action": "ADD", "new_row": {"name": "...", "price": число, "qty": число, "sum": число, "date_start": "YYYY-MM-DD"}, "comment": "..."},\n' + ' {"action": "UPDATE", "target_id": "rN", "new_values": {"price": число}, "comment": "..."},\n' + ' {"action": "DELETE", "target_id": "rN", "comment": "..."},\n' + ' {"action": "UNRESOLVED", "new_values": {"name": "...", "price": число, ...}, "reason": "почему не смог сопоставить"}\n' + " ]\n}\n\n" + "ПРАВИЛА:\n" + '1. mode = "full_replace" — если в тексте есть фразы: «в следующей редакции», «заменить приложение», «излагается в следующей редакции». При full_replace — опиши ВСЕ новые строки как ADD.\n' + '2. mode = "partial" — если ДС меняет только отдельные строки.\n' + "3. Для UPDATE/DELETE — укажи target_id (r1, r2...) из списка текущей спецификации. НЕ придумывай новые id.\n" + "4. new_values в UPDATE — только ИЗМЕНЁННЫЕ поля (не все).\n" + "5. Если не можешь однозначно сопоставить строку — action: UNRESOLVED с reason.\n" + "6. Пропускай итоговые строки и подписи.\n" + "7. price, qty, sum — ЧИСЛА (не строки).\n\n" + "ТЕКУЩАЯ СПЕЦИФИКАЦИЯ:\n{spec_current}\n\n" + "ТЕКСТ ДОПСОГЛАШЕНИЯ:\n---\n{doc_text}\n---" + ) + execute( + "INSERT INTO prompts (role, name, body, is_active, notes) VALUES (%s, %s, %s, %s, %s)", + ("diff", "default-v1", diff_body, True, "Авто-создан из llm_prompt.py _build_diff"), + ) _ensure_classify_prompt()