v2.0.2: fix SQLite — UUID in all INSERTs, now()→datetime, ::jsonb cleanup
Deploy contracts-flask / validate (push) Successful in 0s

This commit is contained in:
2026-07-16 07:29:51 +04:00
parent 9e22182f9a
commit b8340a0637
7 changed files with 56 additions and 44 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
"""Конфигурация приложения — все настройки в одном месте.""" """Конфигурация приложения — все настройки в одном месте."""
import os import os
VERSION = "2.0.1" VERSION = "2.0.2"
LLM_URL = os.getenv("LLM_API_URL", "https://api.aillm.ru/v1/chat/completions") LLM_URL = os.getenv("LLM_API_URL", "https://api.aillm.ru/v1/chat/completions")
LLM_KEY = os.getenv("LLM_API_KEY", "") LLM_KEY = os.getenv("LLM_API_KEY", "")
+7 -4
View File
@@ -1,12 +1,15 @@
"""Contracts CRUD.""" """Contracts CRUD."""
from db.connection import query, execute, execute_returning import uuid
from db.connection import query, execute
def insert(number, client=""): def insert(number, client=""):
return execute_returning( cid = str(uuid.uuid4())
"INSERT INTO contracts (number, client) VALUES (%s, %s) RETURNING *", execute(
(number, client), "INSERT INTO contracts (id, number, client) VALUES (%s, %s, %s)",
(cid, number, client),
) )
return get(cid)
def get(contract_id): def get(contract_id):
+7 -4
View File
@@ -1,14 +1,17 @@
"""Documents CRUD.""" """Documents CRUD."""
import uuid
from db.connection import query, execute, execute_returning from db.connection import query, execute, execute_returning
def insert(filename, mime_type, original_bytes, status="uploaded", batch_id=None, zip_source=None, content_hash=None): def insert(filename, mime_type, original_bytes, status="uploaded", batch_id=None, zip_source=None, content_hash=None):
"""Insert document, return row dict.""" """Insert document, return row dict."""
return execute_returning( doc_id = str(uuid.uuid4())
"""INSERT INTO documents (filename, mime_type, original_bytes, status, batch_id, zip_source, content_hash) execute(
VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING *""", """INSERT INTO documents (id, filename, mime_type, original_bytes, status, batch_id, zip_source, content_hash)
(filename, mime_type, original_bytes, status, batch_id, zip_source, content_hash), VALUES (%s, %s, %s, %s, %s, %s, %s, %s)""",
(doc_id, filename, mime_type, original_bytes, status, batch_id, zip_source, content_hash),
) )
return get(doc_id)
def get(doc_id): def get(doc_id):
+12 -9
View File
@@ -1,5 +1,6 @@
"""Prompts CRUD.""" """Prompts CRUD."""
from db.connection import query, execute, execute_returning import uuid
from db.connection import query, execute
def _serialize(row): def _serialize(row):
@@ -46,8 +47,8 @@ def seed_defaults():
"ТЕКСТ ДОКУМЕНТА:\n---\n{doc_text}\n---" "ТЕКСТ ДОКУМЕНТА:\n---\n{doc_text}\n---"
) )
execute( execute(
"INSERT INTO prompts (role, name, body, is_active, notes) VALUES (%s, %s, %s, %s, %s)", "INSERT INTO prompts (id, role, name, body, is_active, notes) VALUES (%s, %s, %s, %s, %s, %s)",
("extract", "default-v1", extract_body, True, "Авто-создан из llm_prompt.py _build_initial"), (str(uuid.uuid4()), "extract", "default-v1", extract_body, True, "Авто-создан из llm_prompt.py _build_initial"),
) )
if "diff" not in existing_roles: if "diff" not in existing_roles:
@@ -75,8 +76,8 @@ def seed_defaults():
"ТЕКСТ ДОПСОГЛАШЕНИЯ:\n---\n{doc_text}\n---" "ТЕКСТ ДОПСОГЛАШЕНИЯ:\n---\n{doc_text}\n---"
) )
execute( execute(
"INSERT INTO prompts (role, name, body, is_active, notes) VALUES (%s, %s, %s, %s, %s)", "INSERT INTO prompts (id, role, name, body, is_active, notes) VALUES (%s, %s, %s, %s, %s, %s)",
("diff", "default-v1", diff_body, True, "Авто-создан из llm_prompt.py _build_diff"), (str(uuid.uuid4()), "diff", "default-v1", diff_body, True, "Авто-создан из llm_prompt.py _build_diff"),
) )
_ensure_classify_prompt() _ensure_classify_prompt()
@@ -97,11 +98,13 @@ def save_new_version(role, name, body, notes="", is_active=True):
"""Save new prompt version. Deactivates all others for this role, inserts new one.""" """Save new prompt version. Deactivates all others for this role, inserts new one."""
if is_active: if is_active:
execute("UPDATE prompts SET is_active=false WHERE role=%s", (role,)) execute("UPDATE prompts SET is_active=false WHERE role=%s", (role,))
return execute_returning( pid = str(uuid.uuid4())
"""INSERT INTO prompts (role, name, body, is_active, notes) execute(
VALUES (%s, %s, %s, %s, %s) RETURNING *""", """INSERT INTO prompts (id, role, name, body, is_active, notes)
(role, name, body, is_active, notes), VALUES (%s, %s, %s, %s, %s, %s)""",
(pid, role, name, body, is_active, notes),
) )
return get(pid)
def activate(prompt_id): def activate(prompt_id):
+20 -20
View File
@@ -40,11 +40,11 @@ def apply_ops(contract_id, supplement_id, document_id, ops, prompt_id, raw_llm_r
continue continue
name_hash = _hash(name, nr.get("date_start")) name_hash = _hash(name, nr.get("date_start"))
execute( execute(
"""INSERT INTO spec_events (contract_id, supplement_id, seq, action, target_hash, """INSERT INTO spec_events (id, contract_id, supplement_id, seq, action, target_hash,
new_values, comment, status, prompt_version, source_document_id, raw_llm_response) new_values, comment, status, prompt_version, source_document_id, raw_llm_response)
VALUES (%s, %s, %s, 'ADD', %s, %s, %s, 'applied', %s, %s, %s)""", VALUES (%s, %s, %s, %s, 'ADD', %s, %s, %s, 'applied', %s, %s, %s)""",
( (
contract_id, supplement_id, seq, name_hash, str(uuid.uuid4()), contract_id, supplement_id, seq, name_hash,
json.dumps(nr, ensure_ascii=False), json.dumps(nr, ensure_ascii=False),
op.get("comment", ""), prompt_id, document_id, op.get("comment", ""), prompt_id, document_id,
json.dumps(raw_llm_response, ensure_ascii=False), json.dumps(raw_llm_response, ensure_ascii=False),
@@ -62,11 +62,11 @@ def apply_ops(contract_id, supplement_id, document_id, ops, prompt_id, raw_llm_r
seq += 1 seq += 1
continue continue
execute( execute(
"""INSERT INTO spec_events (contract_id, supplement_id, seq, action, target_hash, """INSERT INTO spec_events (id, contract_id, supplement_id, seq, action, target_hash,
new_values, comment, status, prompt_version, source_document_id, raw_llm_response) new_values, comment, status, prompt_version, source_document_id, raw_llm_response)
VALUES (%s, %s, %s, 'UPDATE', %s, %s, %s, 'applied', %s, %s, %s)""", VALUES (%s, %s, %s, %s, 'UPDATE', %s, %s, %s, 'applied', %s, %s, %s)""",
( (
contract_id, supplement_id, seq, th, str(uuid.uuid4()), contract_id, supplement_id, seq, th,
json.dumps(nv, ensure_ascii=False), json.dumps(nv, ensure_ascii=False),
op.get("comment", ""), prompt_id, document_id, op.get("comment", ""), prompt_id, document_id,
json.dumps(raw_llm_response, ensure_ascii=False), json.dumps(raw_llm_response, ensure_ascii=False),
@@ -83,11 +83,11 @@ def apply_ops(contract_id, supplement_id, document_id, ops, prompt_id, raw_llm_r
seq += 1 seq += 1
continue continue
execute( execute(
"""INSERT INTO spec_events (contract_id, supplement_id, seq, action, target_hash, """INSERT INTO spec_events (id, contract_id, supplement_id, seq, action, target_hash,
new_values, comment, status, prompt_version, source_document_id, raw_llm_response) new_values, comment, status, prompt_version, source_document_id, raw_llm_response)
VALUES (%s, %s, %s, 'DELETE', %s, %s, %s, 'applied', %s, %s, %s)""", VALUES (%s, %s, %s, %s, 'DELETE', %s, %s, %s, 'applied', %s, %s, %s)""",
( (
contract_id, supplement_id, seq, th, str(uuid.uuid4()), contract_id, supplement_id, seq, th,
json.dumps({}), op.get("comment", ""), json.dumps({}), op.get("comment", ""),
prompt_id, document_id, prompt_id, document_id,
json.dumps(raw_llm_response, ensure_ascii=False), json.dumps(raw_llm_response, ensure_ascii=False),
@@ -100,11 +100,11 @@ def apply_ops(contract_id, supplement_id, document_id, ops, prompt_id, raw_llm_r
elif action == "UNRESOLVED": elif action == "UNRESOLVED":
# Log but don't apply # Log but don't apply
execute( execute(
"""INSERT INTO spec_events (contract_id, supplement_id, seq, action, target_hash, """INSERT INTO spec_events (id, contract_id, supplement_id, seq, action, target_hash,
new_values, comment, status, prompt_version, source_document_id, raw_llm_response) new_values, comment, status, prompt_version, source_document_id, raw_llm_response)
VALUES (%s, %s, %s, 'UNRESOLVED', %s, %s, %s, 'unresolved', %s, %s, %s)""", VALUES (%s, %s, %s, %s, 'UNRESOLVED', %s, %s, %s, 'unresolved', %s, %s, %s)""",
( (
contract_id, supplement_id, seq, str(uuid.uuid4()), contract_id, supplement_id, seq,
op.get("target_hash", ""), op.get("target_hash", ""),
json.dumps(op.get("new_values", {}), ensure_ascii=False), json.dumps(op.get("new_values", {}), ensure_ascii=False),
op.get("reason", op.get("comment", "")), op.get("reason", op.get("comment", "")),
@@ -125,11 +125,11 @@ def apply_ops(contract_id, supplement_id, document_id, ops, prompt_id, raw_llm_r
def _log_unresolved(contract_id, supplement_id, seq, op, prompt_id, document_id, raw_llm_response, reason): def _log_unresolved(contract_id, supplement_id, seq, op, prompt_id, document_id, raw_llm_response, reason):
"""Log an op as UNRESOLVED instead of silently ignoring it.""" """Log an op as UNRESOLVED instead of silently ignoring it."""
execute( execute(
"""INSERT INTO spec_events (contract_id, supplement_id, seq, action, target_hash, """INSERT INTO spec_events (id, contract_id, supplement_id, seq, action, target_hash,
new_values, comment, status, prompt_version, source_document_id, raw_llm_response) new_values, comment, status, prompt_version, source_document_id, raw_llm_response)
VALUES (%s, %s, %s, 'UNRESOLVED', %s, %s, %s, 'unresolved', %s, %s, %s)""", VALUES (%s, %s, %s, %s, 'UNRESOLVED', %s, %s, %s, 'unresolved', %s, %s, %s)""",
( (
contract_id, supplement_id, seq, str(uuid.uuid4()), contract_id, supplement_id, seq,
op.get("target_hash", ""), op.get("target_hash", ""),
json.dumps(op.get("new_values", op.get("new_row", {})) or {}, ensure_ascii=False), json.dumps(op.get("new_values", op.get("new_row", {})) or {}, ensure_ascii=False),
reason, reason,
@@ -159,16 +159,16 @@ def _upsert_spec_current(contract_id, name_hash, row):
) )
if existing: if existing:
execute( execute(
"""UPDATE spec_current SET name=%s, price=%s, qty=%s, sum=%s, date_start=%s, updated_at=now() """UPDATE spec_current SET name=%s, price=%s, qty=%s, sum=%s, date_start=%s, updated_at=datetime('now')
WHERE contract_id=%s AND name_hash=%s""", WHERE contract_id=%s AND name_hash=%s""",
(row.get("name"), row.get("price"), row.get("qty"), row.get("sum"), (row.get("name"), row.get("price"), row.get("qty"), row.get("sum"),
row.get("date_start"), contract_id, name_hash), row.get("date_start"), contract_id, name_hash),
) )
else: else:
execute( execute(
"""INSERT INTO spec_current (contract_id, name_hash, name, price, qty, sum, date_start) """INSERT INTO spec_current (id, contract_id, name_hash, name, price, qty, sum, date_start)
VALUES (%s, %s, %s, %s, %s, %s, %s)""", VALUES (%s, %s, %s, %s, %s, %s, %s, %s)""",
(contract_id, name_hash, row.get("name"), row.get("price"), (str(uuid.uuid4()), contract_id, name_hash, row.get("name"), row.get("price"),
row.get("qty"), row.get("sum"), row.get("date_start")), row.get("qty"), row.get("sum"), row.get("date_start")),
) )
@@ -182,7 +182,7 @@ def _update_spec_current(contract_id, name_hash, new_values):
sets.append(f"{field} = %s") sets.append(f"{field} = %s")
params.append(new_values[field]) params.append(new_values[field])
if sets: if sets:
sets.append("updated_at = now()") sets.append("updated_at = datetime('now')")
params.extend([contract_id, name_hash]) params.extend([contract_id, name_hash])
execute( execute(
f"UPDATE spec_current SET {', '.join(sets)} WHERE contract_id = %s AND name_hash = %s", f"UPDATE spec_current SET {', '.join(sets)} WHERE contract_id = %s AND name_hash = %s",
+8 -5
View File
@@ -1,13 +1,16 @@
"""Supplements CRUD.""" """Supplements CRUD."""
from db.connection import query, execute, execute_returning import uuid
from db.connection import query, execute
def insert(contract_id, document_id, supp_type="additional"): def insert(contract_id, document_id, supp_type="additional"):
return execute_returning( sid = str(uuid.uuid4())
"""INSERT INTO supplements (contract_id, document_id, type) execute(
VALUES (%s, %s, %s) RETURNING *""", """INSERT INTO supplements (id, contract_id, document_id, type)
(contract_id, document_id, supp_type), VALUES (%s, %s, %s, %s)""",
(sid, contract_id, document_id, supp_type),
) )
return get(sid)
def list_by_contract(contract_id): def list_by_contract(contract_id):
+1 -1
View File
@@ -73,7 +73,7 @@
<body> <body>
<div class="topbar"> <div class="topbar">
<img src="/static/logo.svg" alt="Nubes"> <img src="/static/logo.svg" alt="Nubes">
<span class="title">Сверка договоров — LLM AI-driven Event Sourcing <span style="font-weight:400;color:var(--muted);font-size:12px;">v2.0.1</span></span> <span class="title">Сверка договоров — LLM AI-driven Event Sourcing <span style="font-weight:400;color:var(--muted);font-size:12px;">v2.0.2</span></span>
<div id="pipelineStepper" style="display:flex;gap:8px;font-size:11px;align-items:center;color:var(--muted);"> <div id="pipelineStepper" style="display:flex;gap:8px;font-size:11px;align-items:center;color:var(--muted);">
<span id="stepUpload">○ Загрузка</span><span></span> <span id="stepUpload">○ Загрузка</span><span></span>
<span id="stepClassify">○ Классификация</span><span></span> <span id="stepClassify">○ Классификация</span><span></span>