diff --git a/apply_events.cfm b/apply_events.cfm index fff3ab2..23e4051 100644 --- a/apply_events.cfm +++ b/apply_events.cfm @@ -16,6 +16,14 @@ + + + SELECT COUNT(*) as cnt FROM spec_events + WHERE supplement_id = + + + + @@ -24,28 +32,31 @@ - + + + + + + - - SELECT name_hash, name, price, qty, sum, date_start + + INSERT INTO spec_events (contract_id, supplement_id, seq, action, target_hash, comment, status) + SELECT + , + , + + ROW_NUMBER() OVER (), + 'DELETE', + name_hash, + 'full_replace', + 'applied' FROM spec_current WHERE contract_id = - - - - INSERT INTO spec_events (contract_id, supplement_id, seq, action, target_hash, comment, status) - VALUES ( - , - , - , - 'DELETE', - , - 'full_replace', - 'applied' - ) - - + + SELECT COUNT(*) as cnt FROM spec_current + WHERE contract_id = + + DELETE FROM spec_current WHERE contract_id = @@ -54,6 +65,25 @@ + + + + + + + + + + + SELECT name_hash, price, qty, sum, date_start FROM spec_current + WHERE contract_id = + AND name_hash IN (,) + + + + + + @@ -61,9 +91,9 @@ - - - + + + @@ -90,25 +120,34 @@ , , , - + , ) - - SELECT price, qty, sum, date_start FROM spec_current - WHERE contract_id = - AND name_hash = - - - - + + + + INSERT INTO spec_events (contract_id, supplement_id, seq, action, new_values, comment, status) + VALUES ( + , + , + , + 'UNRESOLVED', + ::jsonb, + , + 'pending' + ) + + + + - - - + + + @@ -138,6 +177,7 @@ AND name_hash = + @@ -179,6 +219,7 @@ + diff --git a/deploy/convert_server.py b/deploy/convert_server.py index 04fabf4..0e71e4e 100755 --- a/deploy/convert_server.py +++ b/deploy/convert_server.py @@ -7,13 +7,13 @@ class ThreadingHTTPServer(ThreadingMixIn, HTTPServer): """Многопоточный HTTP-сервер.""" daemon_threads = True from urllib.parse import urlparse, parse_qs -import subprocess, tempfile, os, sys, json, time +import subprocess, tempfile, os, sys, json, time, re import httpx from llm_prompt import build_prompt LLM_URL = "https://api.aillm.ru/v1/chat/completions" -LLM_KEY = "sk-ucI5YvOticoOQ9Kuj5K9mQ" +LLM_KEY = os.environ.get("LLM_KEY", "") LLM_MODEL = "gpt-oss-120b" LUCEE_URL = "https://contractor.luceek8s.dev.nubes.ru" @@ -91,6 +91,10 @@ class Handler(BaseHTTPRequestHandler): if not cid: self.send_error(400, "contract_id required") return + # Validate UUID to prevent SQL injection + if not re.fullmatch(r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}', cid, re.I): + self.send_error(400, "invalid contract_id format") + return self.send_response(200) self.send_header("Content-Type", "text/event-stream; charset=utf-8") @@ -103,6 +107,12 @@ class Handler(BaseHTTPRequestHandler): t0 = time.time() try: + # 0. Сброс предыдущих результатов сравнения + r = httpx.get(f"{LUCEE_URL}/reset_contract.cfm?contract_id={cid}", timeout=10) + if r.status_code != 200: + self._sse({"type": "error", "message": f"reset failed: HTTP {r.status_code}"}) + return + # 1. Список supplements supps = self._lucee_query( f"SELECT s.id, s.type, d.filename FROM supplements s " @@ -140,9 +150,15 @@ class Handler(BaseHTTPRequestHandler): if not docs: continue ej = docs[0]["elements_json"] + # Lucee JSONB может вернуть: строку, dict-обёртку {Value,Type}, или уже список if isinstance(ej, dict) and "Value" in ej: ej = ej["Value"] - elements = json.loads(ej) + if isinstance(ej, str): + elements = json.loads(ej) + elif isinstance(ej, list): + elements = ej + else: + elements = [] # Lucee serializeJSON → UPPERCASE keys, normalize to lowercase elements = [{k.lower(): v for k, v in el.items()} for el in elements] lines = [] diff --git a/index.cfm b/index.cfm index 45675c5..b2ee0d0 100644 --- a/index.cfm +++ b/index.cfm @@ -71,14 +71,14 @@
Nubes - Сверка договоров — LLM v1.0.91 — Lucee + Сверка договоров — LLM AI-driven Event Sourcing v1.0.104 — Lucee
- Загрузка договоров приложений спецификаций + Загрузка договоров/приложений/спецификаций
@@ -557,9 +557,7 @@ async function showText(i) { } rightHtml += '
Все элементы:
'; rightHtml += '
' + textify + '
'; - rightHtml += '
doc_id' + (f.doc_id || '—') + '
'; if (f.supp_id) { - rightHtml += '
supp_id' + f.supp_id + '
'; rightHtml += '
Тип ДС' + (f.supp_type || '—') + '
'; } } catch(e) { diff --git a/reset_contract.cfm b/reset_contract.cfm new file mode 100644 index 0000000..cd09040 --- /dev/null +++ b/reset_contract.cfm @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + DELETE FROM spec_current + WHERE contract_id = + + + DELETE FROM spec_events + WHERE contract_id = + + + + + + + + + +#serializeJSON(result)#