From 4e20b7760ea516eb44a036062b6aacd0ab0f31a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Sat, 20 Jun 2026 10:31:46 +0400 Subject: [PATCH] =?UTF-8?q?Phase=203:=20apply=5Fevents.cfm=20+=20process.c?= =?UTF-8?q?fm=20v2=20+=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA=D0=B0=20v2=20(v1.0?= =?UTF-8?q?.74)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apply_events.cfm | 188 +++++++++++++++++++++++++++++++++++++++++++++++ index.cfm | 78 +++++++++++++++++++- process.cfm | 6 ++ process_v2.cfm | 158 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 429 insertions(+), 1 deletion(-) create mode 100644 apply_events.cfm create mode 100644 process_v2.cfm diff --git a/apply_events.cfm b/apply_events.cfm new file mode 100644 index 0000000..fff3ab2 --- /dev/null +++ b/apply_events.cfm @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + + + + + + SELECT COALESCE(MAX(seq), 0) as m FROM spec_events + WHERE contract_id = + + + + + + + SELECT name_hash, name, price, qty, sum, date_start + 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' + ) + + + + DELETE FROM spec_current WHERE contract_id = + + + + + + + + + + + + + + + + + + + + INSERT INTO spec_events (contract_id, supplement_id, seq, action, new_values, comment, status) + VALUES ( + , + , + , + 'ADD', + ::jsonb, + , + 'applied' + ) + RETURNING id + + + + INSERT INTO spec_current (contract_id, name_hash, name, price, qty, sum, date_start, last_event_id) + VALUES ( + , + md5(lower(trim()) || coalesce(, '')), + , + , + , + , + , + + ) + + + + + + + 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, target_hash, new_values, comment, status) + VALUES ( + , + , + , + 'UPDATE', + , + ::jsonb, + , + 'applied' + ) + RETURNING id + + + + UPDATE spec_current SET + price = , + qty = , + sum = , + date_start = , + last_event_id = , + updated_at = now() + WHERE contract_id = + AND name_hash = + + + + + + + INSERT INTO spec_events (contract_id, supplement_id, seq, action, target_hash, comment, status) + VALUES ( + , + , + , + 'DELETE', + , + , + 'applied' + ) + + + DELETE 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' + ) + + + + + + + + + + + + + +#serializeJSON(result)# diff --git a/index.cfm b/index.cfm index cdf099a..398a50a 100644 --- a/index.cfm +++ b/index.cfm @@ -61,7 +61,7 @@
Nubes - Сверка договоров — LLM v1.0.72 — Lucee + Сверка договоров — LLM v1.0.74 — Lucee
@@ -90,6 +90,10 @@ Сравнить (LLM) + +
⚙ Промпт LLM @@ -319,9 +323,81 @@ parseBtn.addEventListener('click', async function() { var llmBtn = document.getElementById('llmBtn'); llmBtn.style.display = 'flex'; llmBtn.disabled = false; + var llmBtnV2 = document.getElementById('llmBtnV2'); + llmBtnV2.style.display = 'flex'; + llmBtnV2.disabled = false; lucide.createIcons(); }); +// ── LLM v2: Event Sourcing ───────────────────────────────── +document.getElementById('llmBtnV2').addEventListener('click', function() { + if (!contractId) return; + var btn = this; + btn.disabled = true; + btn.innerHTML = ' LLM v2...'; + + var diffCard = document.getElementById('diffCard'); + var diffBody = document.getElementById('diffBody'); + var diffStatus = document.getElementById('diffStatus'); + diffCard.style.display = 'block'; + diffBody.innerHTML = ''; + diffStatus.textContent = '⏳ 0.0с'; + + var llmStart = Date.now(); + var timerInterval = setInterval(function() { + diffStatus.textContent = '⏳ ' + ((Date.now() - llmStart) / 1000).toFixed(1) + 'с'; + }, 200); + + var es = new EventSource('/process.cfm?contract_id=' + contractId + '&v=2'); + + es.onmessage = function(e) { + var d = JSON.parse(e.data); + + if (d.type === 'extract_start') { + diffBody.innerHTML += '
⏳ ' + d.filename + ' → LLM...
'; + } + else if (d.type === 'llm_done') { + diffBody.innerHTML += '
✓ ' + d.filename + ': ' + d.ops_count + ' операций, ' + d.mode + ' (' + d.time_s + 'с)
'; + } + else if (d.type === 'applied') { + var s = d.summary || {}; + diffBody.innerHTML += '
+' + (s.added||0) + ' ~' + (s.updated||0) + ' -' + (s.deleted||0) + ' ?' + (s.unresolved||0) + '
'; + } + else if (d.type === 'extract_error' || d.type === 'apply_error') { + diffBody.innerHTML += '
✗ ' + d.filename + ': ' + d.error + '
'; + } + else if (d.type === 'done') { + clearInterval(timerInterval); + es.close(); + diffStatus.textContent = '✓ ' + d.total_time_s + 'с'; + btn.innerHTML = ' ✓ Готово'; + if (d.total_unresolved > 0) { + diffBody.innerHTML += '
⚠ ' + d.total_unresolved + ' неразрешённых строк — разобрать
'; + } + document.getElementById('chatCard').style.display = 'block'; + lucide.createIcons(); + } + else if (d.type === 'error') { + clearInterval(timerInterval); + es.close(); + diffBody.innerHTML += '
✗ ' + d.message + '
'; + btn.innerHTML = ' Сравнить v2'; + btn.disabled = false; + } + }; + + es.onerror = function() { + clearInterval(timerInterval); + es.close(); + if (diffBody.innerHTML === '') { + diffBody.innerHTML = '
✗ Ошибка соединения
'; + } + btn.innerHTML = ' Сравнить v2'; + btn.disabled = false; + lucide.createIcons(); + }; +}); + // ── Обновить supplement_id/type для radio ────────────────── async function refreshSupps() { if (!contractId) { console.log('refreshSupps: no contractId'); return; } diff --git a/process.cfm b/process.cfm index 0e5553d..796d797 100644 --- a/process.cfm +++ b/process.cfm @@ -6,11 +6,17 @@ + : ok + + + + + data: #serializeJSON({type:"error",message:"contract_id required"})# diff --git a/process_v2.cfm b/process_v2.cfm new file mode 100644 index 0000000..493adaa --- /dev/null +++ b/process_v2.cfm @@ -0,0 +1,158 @@ + + + + SELECT s.id as supp_id, s.type, d.id as doc_id, d.filename, d.elements_json + FROM supplements s + JOIN documents d ON s.document_id = d.id + WHERE s.contract_id = + AND d.elements_json IS NOT NULL + ORDER BY s.created_at + + + + data: #serializeJSON({type:"error",message:"Нет распарсенных файлов"})# + + + + + + + + + + + + + + SELECT name_hash, name, price, qty, sum, date_start + FROM spec_current + WHERE contract_id = + ORDER BY name + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + data: #serializeJSON({type:"extract_start",supplement_id:sid,filename:supps.filename})# + + + + + + + + + + + + + + + + data: #serializeJSON({type:"extract_error",supplement_id:sid,filename:supps.filename,error:"LLM HTTP #cfhttp.statusCode#",time_s:elapsed})# + + + + + + + + + + + data: #serializeJSON({type:"llm_done",supplement_id:sid,filename:supps.filename,ops_count:arrayLen(ops),mode:mode,time_s:elapsed})# + + + + + + + + + + + + + + + + data: #serializeJSON({type:"applied",supplement_id:sid,summary:applyResult.summary})# + + + + data: #serializeJSON({type:"apply_error",supplement_id:sid,error:structKeyExists(applyResult,'error')?applyResult.error:'unknown'})# + + + + + data: #serializeJSON({type:"apply_error",supplement_id:sid,error:"apply HTTP #cfhttp.statusCode#"})# + + + + + + data: #serializeJSON({type:"extract_error",supplement_id:sid,filename:supps.filename,error:"JSON parse: " & cfcatch.message,time_s:elapsed})# + + + + + + + + + data: #serializeJSON({type:"done",total_time_s:totalTime,total_unresolved:totalUnresolved})# + + + + + data: #serializeJSON({type:"error",message:cfcatch.message,detail:cfcatch.detail})# + + + +