diff --git a/extractor.cfm b/extractor.cfm index f269019..891a16c 100644 --- a/extractor.cfm +++ b/extractor.cfm @@ -20,6 +20,18 @@ + + + SELECT 1 FROM spec_rows WHERE supplement_id = LIMIT 1 + + + + SELECT COUNT(*) as n FROM spec_rows WHERE supplement_id = + + + + + @@ -136,11 +148,15 @@ - + - - - + + + DELETE FROM spec_rows WHERE supplement_id = + + + + @@ -165,6 +181,7 @@ +
Nubes - Сверка договоров — LLM v1.0.55 — Lucee + Сверка договоров — LLM v1.0.56 — Lucee
@@ -142,7 +142,6 @@ lucide.createIcons(); var UPLOAD_URL = 'https://contracts.kube5s.ru/lucee/upload.cfm'; var CONVERT_URL = 'https://contracts.kube5s.ru/convert-doc'; var SITE_URL = ''; // same origin for api calls -var LUCEE_PROXY = 'https://contracts.kube5s.ru/lucee'; // SSE через ВМ (без буферизации) var fileInput = document.getElementById('fileInput'); var parseBtn = document.getElementById('parseBtn'); @@ -333,7 +332,7 @@ document.getElementById('llmBtn').addEventListener('click', async function() { var diffBody = document.getElementById('diffBody'); var diffStatus = document.getElementById('diffStatus'); diffCard.style.display = 'block'; - diffBody.innerHTML = ''; + diffBody.innerHTML = '⏳ LLM-извлечение...'; diffStatus.textContent = '⏳ 0.0с'; var llmStart = Date.now(); @@ -341,98 +340,65 @@ document.getElementById('llmBtn').addEventListener('click', async function() { diffStatus.textContent = '⏳ ' + ((Date.now() - llmStart) / 1000).toFixed(1) + 'с'; }, 200); - // SSE — прогресс по каждому файлу - var es = new EventSource(LUCEE_PROXY + '/process.cfm?contract_id=' + contractId); - - es.onmessage = function(e) { - var d = JSON.parse(e.data); - - if (d.type === 'extract_start') { - diffBody.innerHTML += '
⏳ ' + d.filename + ' → LLM... 0.0с
'; - } - else if (d.type === 'extract_done') { - var el = document.getElementById('ext-' + d.supplement_id); - if (el) el.innerHTML = '✓ ' + d.filename + ': ' + d.count + ' строк (' + d.time_s + 'с)'; - } - else if (d.type === 'extract_skip') { - diffBody.innerHTML += '
⏭ ' + d.filename + ' (уже извлечено, ' + d.count + ' строк)
'; - } - else if (d.type === 'extract_error') { - var el = document.getElementById('ext-' + d.supplement_id); - if (el) el.innerHTML = '✗ ' + d.filename + ': ' + d.error + ''; - else diffBody.innerHTML += '
✗ ' + d.filename + ': ' + d.error + '
'; - } - else if (d.type === 'done') { - clearInterval(timerInterval); - es.close(); - diffStatus.textContent = '✓ ' + d.total_time_s + 'с'; - btn.innerHTML = ' ✓ Готово'; - document.getElementById('chatCard').style.display = 'block'; - - // Сравнение - diffBody.innerHTML += '
📊 Сравнение:
'; - var all = d.all_changes || []; - if (all.length === 0) { - diffBody.innerHTML += '
Нет допников для сравнения.
'; - } else { - all.forEach(function(ch) { - var s = ch.SUMMARY || {}; - diffBody.innerHTML += '
📄 Допник (' + ch.TYPE + ')
'; - diffBody.innerHTML += '
+' + (s.ADDED||0) + ' ~' + (s.CHANGED||0) + ' -' + (s.DELETED||0) + '
'; - - var changes = ch.CHANGES || []; - if (changes.length === 0) { - diffBody.innerHTML += '
Нет изменений
'; + // 1. Extract for each supplement + try { + var resp = await fetch('/api.cfm?action=query&sql=' + encodeURIComponent("SELECT id, type FROM supplements WHERE contract_id='" + contractId + "' ORDER BY created_at")); + var data = await resp.json(); + if (data.OK && data.ROWS) { + for (var i = 0; i < data.ROWS.length; i++) { + try { + var er = await fetch('/extractor.cfm?supplement_id=' + data.ROWS[i].id); + var ed = await er.json(); + if (ed.OK) { + var info = (ed.SKIPPED ? '⏭ ' : '✓ ') + (data.ROWS[i].type || '') + ': ' + ed.ROWS_SAVED + ' строк'; + diffBody.innerHTML += '
' + info + '
'; } else { - var tbl = '
'; - changes.forEach(function(c) { - var cls = c.CHANGE_TYPE === 'added' ? 'diff-added' : c.CHANGE_TYPE === 'deleted' ? 'diff-deleted' : c.CHANGE_TYPE === 'changed' ? 'diff-changed' : ''; - var vals = c.NEW_VALUES && Object.keys(c.NEW_VALUES).length > 0 ? c.NEW_VALUES : (c.OLD_VALUES || {}); - tbl += ''; - }); - tbl += '
УслугаЦенаОбъёмСуммаНачало
' + (c.ROW_NUM||'') + '' + (vals.NAME||'') + '' + _dc(c,'PRICE') + '' + _dc(c,'QTY') + '' + _dc(c,'SUM') + '' + (vals.DATE_START||'') + '
'; - diffBody.innerHTML += tbl; + diffBody.innerHTML += '
✗ ' + (ed.ERROR||'') + '
'; } - }); + } catch(e) {} } - lucide.createIcons(); } - else if (d.type === 'error') { - clearInterval(timerInterval); - es.close(); - diffBody.innerHTML += '
✗ ' + d.message + '
'; - btn.innerHTML = ' Сравнить (LLM)'; - btn.disabled = false; + } catch(e) {} + + // 2. Differ + diffBody.innerHTML += '
📊 Сравнение:
'; + try { + var resp = await fetch('/differ.cfm?contract_id=' + contractId); + var data = await resp.json(); + if (data.OK && data.ALL_CHANGES) { + var all = data.ALL_CHANGES; + diffBody.innerHTML = ''; + all.forEach(function(ch) { + var s = ch.SUMMARY || {}; + diffBody.innerHTML += '
📄 Допник (' + ch.TYPE + ')
'; + diffBody.innerHTML += '
+' + (s.ADDED||0) + ' ~' + (s.CHANGED||0) + ' -' + (s.DELETED||0) + '
'; + diffStatus.textContent = 'изм:' + (s.CHANGED||0) + ' +' + (s.ADDED||0) + ' -' + (s.DELETED||0); + + var changes = ch.CHANGES || []; + if (changes.length === 0) { + diffBody.innerHTML += '
Нет изменений
'; + } else { + var tbl = '
'; + changes.forEach(function(c) { + var cls = c.CHANGE_TYPE === 'added' ? 'diff-added' : c.CHANGE_TYPE === 'deleted' ? 'diff-deleted' : c.CHANGE_TYPE === 'changed' ? 'diff-changed' : ''; + var vals = c.NEW_VALUES && Object.keys(c.NEW_VALUES).length > 0 ? c.NEW_VALUES : (c.OLD_VALUES || {}); + tbl += ''; + }); + tbl += '
УслугаЦенаОбъёмСуммаНачало
' + (c.ROW_NUM||'') + '' + (vals.NAME||'') + '' + _dc(c,'PRICE') + '' + _dc(c,'QTY') + '' + _dc(c,'SUM') + '' + (vals.DATE_START||'') + '
'; + diffBody.innerHTML += tbl; + } + }); + } else { + diffBody.innerHTML += '
✗ ' + (data.ERROR||'') + '
'; } - }; + } catch(e) { diffBody.innerHTML += '
✗ ' + e.message + '
'; } - es.onerror = function() { - clearInterval(timerInterval); - es.close(); - if (diffBody.innerHTML === '') { - diffBody.innerHTML = '
✗ Ошибка соединения
'; - } - btn.innerHTML = ' Сравнить (LLM)'; - btn.disabled = false; - lucide.createIcons(); - }; + clearInterval(timerInterval); + diffStatus.textContent = '✓ ' + ((Date.now() - llmStart) / 1000).toFixed(1) + 'с'; - // Таймер для файловых строк - var fileTimerInterval = setInterval(function() { - var now = Date.now(); - document.querySelectorAll('.file-timer').forEach(function(el) { - var start = parseInt(el.dataset.start); - if (start) el.textContent = ((now - start) / 1000).toFixed(1) + 'с'; - }); - }, 200); - - // Сохранить интервалы для очистки - es._fileTimer = fileTimerInterval; - var origClose = es.close; - es.close = function() { - clearInterval(fileTimerInterval); - origClose.call(es); - }; + btn.innerHTML = ' ✓ Готово'; + document.getElementById('chatCard').style.display = 'block'; + lucide.createIcons(); }); function _dc(c, field) {