SSE streaming: прогресс по каждому файлу во время LLM (v1.0.53)
This commit is contained in:
+255
-240
@@ -1,40 +1,50 @@
|
||||
<cfsetting showdebugoutput="no" enablecfoutputonly="true" requesttimeout="600">
|
||||
<cfheader name="Content-Type" value="application/json; charset=utf-8">
|
||||
<cfheader name="Content-Type" value="text/event-stream; charset=utf-8">
|
||||
<cfheader name="Cache-Control" value="no-cache">
|
||||
<cfheader name="Connection" value="keep-alive">
|
||||
<cfheader name="X-Accel-Buffering" value="no">
|
||||
|
||||
<cfparam name="url.contract_id" default="">
|
||||
<cfset result = {OK: false, ERROR: ""}>
|
||||
|
||||
<cfoutput>: ok
|
||||
|
||||
</cfoutput><cfflush>
|
||||
|
||||
<cftry>
|
||||
<cfif NOT len(url.contract_id)>
|
||||
<cfset result.ERROR = "contract_id required">
|
||||
<cfelse>
|
||||
<!--- Все допники договора с parsed_text --->
|
||||
<cfquery name="supps" datasource="baza">
|
||||
SELECT s.id as supp_id, s.type, d.id as doc_id, d.filename, d.elements_json, d.parsed_text
|
||||
FROM supplements s
|
||||
JOIN documents d ON s.document_id = d.id
|
||||
WHERE s.contract_id = <cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar">
|
||||
AND d.elements_json IS NOT NULL
|
||||
ORDER BY s.created_at
|
||||
</cfquery>
|
||||
<cfoutput>data: #serializeJSON({type:"error",message:"contract_id required"})#
|
||||
|
||||
<cfif supps.recordCount EQ 0>
|
||||
<cfset result.ERROR = "Нет распарсенных файлов. Сначала нажмите Парсинг.">
|
||||
<cfelse>
|
||||
<cfset extractedInfo = []>
|
||||
<cfset extractedRows = {}> <!--- supp_id → [rows] --->
|
||||
</cfoutput><cfflush>
|
||||
<cfabort>
|
||||
</cfif>
|
||||
|
||||
<!--- Промпт из БД --->
|
||||
<cfset promptText = "">
|
||||
<cfquery name="pq" datasource="baza">
|
||||
SELECT prompt_text FROM prompts WHERE contract_id = <cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar">
|
||||
</cfquery>
|
||||
<cfif pq.recordCount GT 0 AND len(pq.prompt_text)>
|
||||
<cfset promptText = pq.prompt_text>
|
||||
</cfif>
|
||||
<cfquery name="supps" datasource="baza">
|
||||
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 = <cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar">
|
||||
AND d.elements_json IS NOT NULL
|
||||
ORDER BY s.created_at
|
||||
</cfquery>
|
||||
|
||||
<!--- DEFAULT_PROMPT --->
|
||||
<cfset defaultPrompt = 'Ты — анализатор договоров. Ниже текст спецификации услуг из договора облачного провайдера.
|
||||
<cfif supps.recordCount EQ 0>
|
||||
<cfoutput>data: #serializeJSON({type:"error",message:"Нет распарсенных файлов. Сначала нажмите Парсинг."})#
|
||||
|
||||
</cfoutput><cfflush>
|
||||
<cfabort>
|
||||
</cfif>
|
||||
|
||||
<cfset extractedRows = {}>
|
||||
|
||||
<cfset promptText = "">
|
||||
<cfquery name="pq" datasource="baza">
|
||||
SELECT prompt_text FROM prompts WHERE contract_id = <cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar">
|
||||
</cfquery>
|
||||
<cfif pq.recordCount GT 0 AND len(pq.prompt_text)>
|
||||
<cfset promptText = pq.prompt_text>
|
||||
</cfif>
|
||||
|
||||
<cfset defaultPrompt = 'Ты — анализатор договоров. Ниже текст спецификации услуг из договора облачного провайдера.
|
||||
|
||||
Найди ВСЕ таблицы со списком услуг. Извлеки каждую строку в JSON-массив.
|
||||
|
||||
@@ -60,230 +70,235 @@
|
||||
{parsed_text}
|
||||
---'>
|
||||
|
||||
<!--- LLM API --->
|
||||
<cfset apiKey = "sk-ucI5YvOticoOQ9Kuj5K9mQ">
|
||||
<cfset apiKey = "sk-ucI5YvOticoOQ9Kuj5K9mQ">
|
||||
<cfset llmStart = getTickCount()>
|
||||
|
||||
<!--- Шаг 1: LLM-извлечение для каждого допника --->
|
||||
<cfloop query="supps">
|
||||
<cfset sid = supps.supp_id>
|
||||
<cfloop query="supps">
|
||||
<cfset sid = supps.supp_id>
|
||||
|
||||
<!--- Проверить: уже извлечено? --->
|
||||
<cfquery name="ex" datasource="baza">
|
||||
SELECT row_num, name, price, qty, sum, date_start
|
||||
FROM spec_rows WHERE supplement_id = <cfqueryparam value="#sid#" cfsqltype="cf_sql_varchar">
|
||||
ORDER BY row_num
|
||||
</cfquery>
|
||||
<cfquery name="ex" datasource="baza">
|
||||
SELECT row_num, name, price, qty, sum, date_start
|
||||
FROM spec_rows WHERE supplement_id = <cfqueryparam value="#sid#" cfsqltype="cf_sql_varchar">
|
||||
ORDER BY row_num
|
||||
</cfquery>
|
||||
|
||||
<cfif ex.recordCount GT 0>
|
||||
<!--- Уже извлечено — загрузить существующие --->
|
||||
<cfset rows = []>
|
||||
<cfloop query="ex">
|
||||
<cfset arrayAppend(rows, {ROW_NUM: ex.row_num, NAME: ex.name, PRICE: ex.price, QTY: ex.qty, SUM: ex.sum, DATE_START: ex.date_start})>
|
||||
</cfloop>
|
||||
<cfset extractedRows[sid] = rows>
|
||||
<cfset arrayAppend(extractedInfo, {SUPPLEMENT_ID: sid, FILENAME: supps.filename, ROWS_SAVED: ex.recordCount, SKIPPED: true})>
|
||||
<cfcontinue>
|
||||
</cfif>
|
||||
<cfif ex.recordCount GT 0>
|
||||
<cfset rows = []>
|
||||
<cfloop query="ex">
|
||||
<cfset arrayAppend(rows, {ROW_NUM: ex.row_num, NAME: ex.name, PRICE: ex.price, QTY: ex.qty, SUM: ex.sum, DATE_START: ex.date_start})>
|
||||
</cfloop>
|
||||
<cfset extractedRows[sid] = rows>
|
||||
<cfoutput>data: #serializeJSON({type:"extract_skip",supplement_id:sid,filename:supps.filename,count:ex.recordCount})#
|
||||
|
||||
<!--- textify: elements → текст --->
|
||||
<cfset elements = deserializeJSON(supps.elements_json)>
|
||||
<cfset textLines = []>
|
||||
<cfloop array="#elements#" index="el">
|
||||
<cfif el.type EQ "paragraph">
|
||||
<cfset prefix = len(el.style) ? "[#el.style#] " : "">
|
||||
<cfset arrayAppend(textLines, prefix & el.text)>
|
||||
<cfelseif el.type EQ "table">
|
||||
<cfset rows = el.rows>
|
||||
<cfif arrayLen(rows) GT 0>
|
||||
<cfset ncols = arrayLen(rows[1])>
|
||||
<cfset arrayAppend(textLines, "--- Таблица (" & arrayLen(rows) & "×" & ncols & ") ---")>
|
||||
<cfloop array="#rows#" index="row">
|
||||
<cfset cells = []>
|
||||
<cfloop from="1" to="#ncols#" index="ci">
|
||||
<cfset val = "">
|
||||
<cfif ci LTE arrayLen(row)>
|
||||
<cfset val = replace(replace(toString(row[ci]), chr(10), " ", "ALL"), "|", "\|", "ALL")>
|
||||
</cfif>
|
||||
<cfset arrayAppend(cells, val)>
|
||||
</cfloop>
|
||||
<cfset arrayAppend(textLines, "| " & arrayToList(cells, " | ") & " |")>
|
||||
</cfloop>
|
||||
<cfset arrayAppend(textLines, "")>
|
||||
</cfif>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
<cfset parsedText = arrayToList(textLines, chr(10))>
|
||||
</cfoutput><cfflush>
|
||||
<cfcontinue>
|
||||
</cfif>
|
||||
|
||||
<!--- Промпт --->
|
||||
<cfset prompt = replace(len(promptText) ? promptText : defaultPrompt, "{parsed_text}", parsedText, "ALL")>
|
||||
|
||||
<!--- LLM --->
|
||||
<cfset llmPayload = {
|
||||
"model": "gpt-oss-120b",
|
||||
"messages": [{"role": "user", "content": prompt}],
|
||||
"max_tokens": 8000,
|
||||
"temperature": 0.1
|
||||
}>
|
||||
|
||||
<cfhttp url="https://api.aillm.ru/v1/chat/completions" method="POST" timeout="120">
|
||||
<cfhttpparam type="header" name="Authorization" value="Bearer #apiKey#">
|
||||
<cfhttpparam type="header" name="Content-Type" value="application/json">
|
||||
<cfhttpparam type="body" value="#serializeJSON(llmPayload)#">
|
||||
</cfhttp>
|
||||
|
||||
<cfif NOT (cfhttp.statusCode EQ 200 OR left(cfhttp.statusCode, 3) EQ "200")>
|
||||
<cfset arrayAppend(extractedInfo, {SUPPLEMENT_ID: sid, FILENAME: supps.filename, ERROR: "LLM HTTP #cfhttp.statusCode#"})>
|
||||
<cfcontinue>
|
||||
</cfif>
|
||||
|
||||
<cfset llmResp = deserializeJSON(cfhttp.fileContent)>
|
||||
<cfset llmText = llmResp.choices[1].message.content>
|
||||
|
||||
<!--- Вытащить JSON из ответа --->
|
||||
<cfset jsonText = llmText>
|
||||
<cfset p1 = find("```json", jsonText)>
|
||||
<cfif p1 GT 0>
|
||||
<cfset jsonText = mid(jsonText, p1 + 7, len(jsonText))>
|
||||
<cfset p2 = find("```", jsonText)>
|
||||
<cfif p2 GT 0>
|
||||
<cfset jsonText = left(jsonText, p2 - 1)>
|
||||
</cfif>
|
||||
<cfelse>
|
||||
<cfset p1 = find("{", jsonText)>
|
||||
<cfset p2 = find("}", reverse(jsonText))>
|
||||
<cfif p1 GT 0 AND p2 GT 0>
|
||||
<cfset jsonText = mid(jsonText, p1, len(jsonText) - p2 - p1 + 2)>
|
||||
</cfif>
|
||||
</cfif>
|
||||
<cfset jsonText = trim(jsonText)>
|
||||
|
||||
<cftry>
|
||||
<cfset extracted = deserializeJSON(jsonText)>
|
||||
<cfset rows = []>
|
||||
<cfif structKeyExists(extracted, "rows") AND isArray(extracted.rows)>
|
||||
<cfset rows = extracted.rows>
|
||||
</cfif>
|
||||
|
||||
<!--- Сохранить spec_rows (DELETE + INSERT) --->
|
||||
<cftransaction>
|
||||
<cfquery datasource="baza">
|
||||
DELETE FROM spec_rows WHERE supplement_id = <cfqueryparam value="#sid#" cfsqltype="cf_sql_varchar">
|
||||
</cfquery>
|
||||
<cfloop array="#rows#" index="row">
|
||||
<cfset rn = structKeyExists(row, 'row_num') ? row.row_num : 0>
|
||||
<cfset nm = structKeyExists(row, 'name') ? row.name : ''>
|
||||
<cfset pr = (structKeyExists(row, 'price') AND len(row.price)) ? row.price : ''>
|
||||
<cfset qt = (structKeyExists(row, 'qty') AND len(row.qty)) ? row.qty : ''>
|
||||
<cfset sm = (structKeyExists(row, 'sum') AND len(row.sum)) ? row.sum : ''>
|
||||
<cfset ds = (structKeyExists(row, 'date_start') AND len(row.date_start)) ? row.date_start : ''>
|
||||
|
||||
<cfquery datasource="baza">
|
||||
INSERT INTO spec_rows (supplement_id, row_num, name, price, qty, sum, date_start)
|
||||
VALUES (
|
||||
<cfqueryparam value="#sid#" cfsqltype="cf_sql_varchar">,
|
||||
<cfqueryparam value="#rn#" cfsqltype="cf_sql_integer">,
|
||||
<cfqueryparam value="#nm#" cfsqltype="cf_sql_varchar">,
|
||||
<cfqueryparam value="#pr#" cfsqltype="cf_sql_float" null="#len(pr) EQ 0#">,
|
||||
<cfqueryparam value="#qt#" cfsqltype="cf_sql_float" null="#len(qt) EQ 0#">,
|
||||
<cfqueryparam value="#sm#" cfsqltype="cf_sql_float" null="#len(sm) EQ 0#">,
|
||||
<cfqueryparam value="#ds#" cfsqltype="cf_sql_varchar" null="#len(ds) EQ 0#">
|
||||
)
|
||||
</cfquery>
|
||||
<cfset elements = deserializeJSON(supps.elements_json)>
|
||||
<cfset textLines = []>
|
||||
<cfloop array="#elements#" index="el">
|
||||
<cfif el.type EQ "paragraph">
|
||||
<cfset prefix = len(el.style) ? "[#el.style#] " : "">
|
||||
<cfset arrayAppend(textLines, prefix & el.text)>
|
||||
<cfelseif el.type EQ "table">
|
||||
<cfset rows = el.rows>
|
||||
<cfif arrayLen(rows) GT 0>
|
||||
<cfset ncols = arrayLen(rows[1])>
|
||||
<cfset arrayAppend(textLines, "--- Таблица (" & arrayLen(rows) & "×" & ncols & ") ---")>
|
||||
<cfloop array="#rows#" index="row">
|
||||
<cfset cells = []>
|
||||
<cfloop from="1" to="#ncols#" index="ci">
|
||||
<cfset val = "">
|
||||
<cfif ci LTE arrayLen(row)>
|
||||
<cfset val = replace(replace(toString(row[ci]), chr(10), " ", "ALL"), "|", "\|", "ALL")>
|
||||
</cfif>
|
||||
<cfset arrayAppend(cells, val)>
|
||||
</cfloop>
|
||||
</cftransaction>
|
||||
|
||||
<cfset savedRows = []>
|
||||
<cfloop array="#rows#" index="r">
|
||||
<cfset arrayAppend(savedRows, {ROW_NUM: structKeyExists(r,'row_num')?r.row_num:0, NAME: structKeyExists(r,'name')?r.name:'', PRICE: structKeyExists(r,'price')?r.price:'', QTY: structKeyExists(r,'qty')?r.qty:'', SUM: structKeyExists(r,'sum')?r.sum:'', DATE_START: structKeyExists(r,'date_start')?r.date_start:''})>
|
||||
<cfset arrayAppend(textLines, "| " & arrayToList(cells, " | ") & " |")>
|
||||
</cfloop>
|
||||
<cfset extractedRows[sid] = savedRows>
|
||||
<cfset arrayAppend(extractedInfo, {SUPPLEMENT_ID: sid, FILENAME: supps.filename, ROWS_SAVED: arrayLen(savedRows), SKIPPED: false})>
|
||||
<cfset arrayAppend(textLines, "")>
|
||||
</cfif>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
<cfset parsedText = arrayToList(textLines, chr(10))>
|
||||
|
||||
<cfcatch>
|
||||
<cfset arrayAppend(extractedInfo, {SUPPLEMENT_ID: sid, FILENAME: supps.filename, ERROR: "JSON parse: " & cfcatch.message})>
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
<cfset prompt = replace(len(promptText) ? promptText : defaultPrompt, "{parsed_text}", parsedText, "ALL")>
|
||||
|
||||
<cfoutput>data: #serializeJSON({type:"extract_start",supplement_id:sid,filename:supps.filename})#
|
||||
|
||||
</cfoutput><cfflush>
|
||||
|
||||
<cfset t0 = getTickCount()>
|
||||
<cfset llmPayload = {
|
||||
"model": "gpt-oss-120b",
|
||||
"messages": [{"role": "user", "content": prompt}],
|
||||
"max_tokens": 8000,
|
||||
"temperature": 0.1
|
||||
}>
|
||||
|
||||
<cfhttp url="https://api.aillm.ru/v1/chat/completions" method="POST" timeout="120">
|
||||
<cfhttpparam type="header" name="Authorization" value="Bearer #apiKey#">
|
||||
<cfhttpparam type="header" name="Content-Type" value="application/json">
|
||||
<cfhttpparam type="body" value="#serializeJSON(llmPayload)#">
|
||||
</cfhttp>
|
||||
|
||||
<cfset elapsed = int((getTickCount() - t0) / 1000 * 10 + 0.5) / 10>
|
||||
|
||||
<cfif NOT (cfhttp.statusCode EQ 200 OR left(cfhttp.statusCode, 3) EQ "200")>
|
||||
<cfoutput>data: #serializeJSON({type:"extract_error",supplement_id:sid,filename:supps.filename,error:"LLM HTTP #cfhttp.statusCode#",time_s:elapsed})#
|
||||
|
||||
</cfoutput><cfflush>
|
||||
<cfcontinue>
|
||||
</cfif>
|
||||
|
||||
<cfset llmResp = deserializeJSON(cfhttp.fileContent)>
|
||||
<cfset llmText = llmResp.choices[1].message.content>
|
||||
|
||||
<cfset jsonText = llmText>
|
||||
<cfset p1 = find("```json", jsonText)>
|
||||
<cfif p1 GT 0>
|
||||
<cfset jsonText = mid(jsonText, p1 + 7, len(jsonText))>
|
||||
<cfset p2 = find("```", jsonText)>
|
||||
<cfif p2 GT 0><cfset jsonText = left(jsonText, p2 - 1)></cfif>
|
||||
<cfelse>
|
||||
<cfset p1 = find("{", jsonText)>
|
||||
<cfset p2 = find("}", reverse(jsonText))>
|
||||
<cfif p1 GT 0 AND p2 GT 0>
|
||||
<cfset jsonText = mid(jsonText, p1, len(jsonText) - p2 - p1 + 2)>
|
||||
</cfif>
|
||||
</cfif>
|
||||
<cfset jsonText = trim(jsonText)>
|
||||
|
||||
<cftry>
|
||||
<cfset extracted = deserializeJSON(jsonText)>
|
||||
<cfset rows = []>
|
||||
<cfif structKeyExists(extracted, "rows") AND isArray(extracted.rows)>
|
||||
<cfset rows = extracted.rows>
|
||||
</cfif>
|
||||
|
||||
<cftransaction>
|
||||
<cfquery datasource="baza">
|
||||
DELETE FROM spec_rows WHERE supplement_id = <cfqueryparam value="#sid#" cfsqltype="cf_sql_varchar">
|
||||
</cfquery>
|
||||
<cfloop array="#rows#" index="row">
|
||||
<cfset rn = structKeyExists(row, 'row_num') ? row.row_num : 0>
|
||||
<cfset nm = structKeyExists(row, 'name') ? row.name : ''>
|
||||
<cfset pr = (structKeyExists(row, 'price') AND len(row.price)) ? row.price : ''>
|
||||
<cfset qt = (structKeyExists(row, 'qty') AND len(row.qty)) ? row.qty : ''>
|
||||
<cfset sm = (structKeyExists(row, 'sum') AND len(row.sum)) ? row.sum : ''>
|
||||
<cfset ds = (structKeyExists(row, 'date_start') AND len(row.date_start)) ? row.date_start : ''>
|
||||
|
||||
<cfquery datasource="baza">
|
||||
INSERT INTO spec_rows (supplement_id, row_num, name, price, qty, sum, date_start)
|
||||
VALUES (
|
||||
<cfqueryparam value="#sid#" cfsqltype="cf_sql_varchar">,
|
||||
<cfqueryparam value="#rn#" cfsqltype="cf_sql_integer">,
|
||||
<cfqueryparam value="#nm#" cfsqltype="cf_sql_varchar">,
|
||||
<cfqueryparam value="#pr#" cfsqltype="cf_sql_float" null="#len(pr) EQ 0#">,
|
||||
<cfqueryparam value="#qt#" cfsqltype="cf_sql_float" null="#len(qt) EQ 0#">,
|
||||
<cfqueryparam value="#sm#" cfsqltype="cf_sql_float" null="#len(sm) EQ 0#">,
|
||||
<cfqueryparam value="#ds#" cfsqltype="cf_sql_varchar" null="#len(ds) EQ 0#">
|
||||
)
|
||||
</cfquery>
|
||||
</cfloop>
|
||||
</cftransaction>
|
||||
|
||||
<cfset savedRows = []>
|
||||
<cfloop array="#rows#" index="r">
|
||||
<cfset arrayAppend(savedRows, {ROW_NUM: structKeyExists(r,'row_num')?r.row_num:0, NAME: structKeyExists(r,'name')?r.name:'', PRICE: structKeyExists(r,'price')?r.price:'', QTY: structKeyExists(r,'qty')?r.qty:'', SUM: structKeyExists(r,'sum')?r.sum:'', DATE_START: structKeyExists(r,'date_start')?r.date_start:''})>
|
||||
</cfloop>
|
||||
<cfset extractedRows[sid] = savedRows>
|
||||
|
||||
<cfoutput>data: #serializeJSON({type:"extract_done",supplement_id:sid,filename:supps.filename,count:arrayLen(savedRows),time_s:elapsed})#
|
||||
|
||||
</cfoutput><cfflush>
|
||||
|
||||
<cfcatch>
|
||||
<cfoutput>data: #serializeJSON({type:"extract_error",supplement_id:sid,filename:supps.filename,error:"JSON parse: " & cfcatch.message,time_s:elapsed})#
|
||||
|
||||
</cfoutput><cfflush>
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
</cfloop>
|
||||
|
||||
<!--- Differ --->
|
||||
<cfset allChanges = []>
|
||||
|
||||
<cfquery name="initSup" datasource="baza">
|
||||
SELECT id FROM supplements WHERE contract_id=<cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar"> AND type='initial' ORDER BY created_at LIMIT 1
|
||||
</cfquery>
|
||||
<cfif initSup.recordCount EQ 0>
|
||||
<cfquery name="initSup" datasource="baza">
|
||||
SELECT id FROM supplements WHERE contract_id=<cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar"> ORDER BY created_at LIMIT 1
|
||||
</cfquery>
|
||||
</cfif>
|
||||
|
||||
<cfif initSup.recordCount GT 0 AND structKeyExists(extractedRows, initSup.id)>
|
||||
<cfset initByNum = {}>
|
||||
<cfloop array="#extractedRows[initSup.id]#" index="r">
|
||||
<cfset initByNum[r.ROW_NUM] = r>
|
||||
</cfloop>
|
||||
|
||||
<cfquery name="amendments" datasource="baza">
|
||||
SELECT id, type FROM supplements WHERE contract_id=<cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar"> AND id!=<cfqueryparam value="#initSup.id#" cfsqltype="cf_sql_varchar"> ORDER BY created_at
|
||||
</cfquery>
|
||||
|
||||
<cfloop query="amendments">
|
||||
<cfif NOT structKeyExists(extractedRows, amendments.id)>
|
||||
<cfcontinue>
|
||||
</cfif>
|
||||
<cfset amdRows = extractedRows[amendments.id]>
|
||||
<cfset amdByNum = {}>
|
||||
<cfloop array="#amdRows#" index="r">
|
||||
<cfset amdByNum[r.ROW_NUM] = r>
|
||||
</cfloop>
|
||||
|
||||
<!--- Шаг 2: Differ (логика из differ.cfm, но на свежих данных) --->
|
||||
<cfset allChanges = []>
|
||||
<cfset allNums = structKeyArray(initByNum)>
|
||||
<cfloop array="#structKeyArray(amdByNum)#" index="n">
|
||||
<cfif NOT structKeyExists(initByNum, n)><cfset arrayAppend(allNums, n)></cfif>
|
||||
</cfloop>
|
||||
<cfset arraySort(allNums, "numeric")>
|
||||
|
||||
<!--- Найти initial --->
|
||||
<cfquery name="initSup" datasource="baza">
|
||||
SELECT id FROM supplements WHERE contract_id=<cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar"> AND type='initial' ORDER BY created_at LIMIT 1
|
||||
</cfquery>
|
||||
<cfif initSup.recordCount EQ 0>
|
||||
<cfquery name="initSup" datasource="baza">
|
||||
SELECT id FROM supplements WHERE contract_id=<cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar"> ORDER BY created_at LIMIT 1
|
||||
</cfquery>
|
||||
</cfif>
|
||||
|
||||
<cfif initSup.recordCount GT 0 AND structKeyExists(extractedRows, initSup.id)>
|
||||
<cfset initByNum = {}>
|
||||
<cfloop array="#extractedRows[initSup.id]#" index="r">
|
||||
<cfset initByNum[r.ROW_NUM] = r>
|
||||
</cfloop>
|
||||
|
||||
<cfquery name="amendments" datasource="baza">
|
||||
SELECT id, type FROM supplements WHERE contract_id=<cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar"> AND id!=<cfqueryparam value="#initSup.id#" cfsqltype="cf_sql_varchar"> ORDER BY created_at
|
||||
</cfquery>
|
||||
|
||||
<cfloop query="amendments">
|
||||
<cfif NOT structKeyExists(extractedRows, amendments.id)>
|
||||
<cfcontinue>
|
||||
</cfif>
|
||||
<cfset amdRows = extractedRows[amendments.id]>
|
||||
<cfset amdByNum = {}>
|
||||
<cfloop array="#amdRows#" index="r">
|
||||
<cfset amdByNum[r.ROW_NUM] = r>
|
||||
</cfloop>
|
||||
|
||||
<cfset allNums = structKeyArray(initByNum)>
|
||||
<cfloop array="#structKeyArray(amdByNum)#" index="n">
|
||||
<cfif NOT structKeyExists(initByNum, n)><cfset arrayAppend(allNums, n)></cfif>
|
||||
</cfloop>
|
||||
<cfset arraySort(allNums, "numeric")>
|
||||
|
||||
<cfset changes = []>
|
||||
<cfset fields = ["NAME","PRICE","QTY","SUM","DATE_START"]>
|
||||
<cfloop array="#allNums#" index="num">
|
||||
<cfset inOld = structKeyExists(initByNum, num)>
|
||||
<cfset inNew = structKeyExists(amdByNum, num)>
|
||||
<cfif inOld AND NOT inNew>
|
||||
<cfset arrayAppend(changes, {ROW_NUM:num, CHANGE_TYPE:"deleted", OLD_VALUES:initByNum[num], NEW_VALUES:{}})>
|
||||
<cfelseif NOT inOld AND inNew>
|
||||
<cfset arrayAppend(changes, {ROW_NUM:num, CHANGE_TYPE:"added", OLD_VALUES:{}, NEW_VALUES:amdByNum[num]})>
|
||||
<cfelse>
|
||||
<cfset ov = initByNum[num]>
|
||||
<cfset nv = amdByNum[num]>
|
||||
<cfset same = true>
|
||||
<cfset cf = {}>
|
||||
<cfloop array="#fields#" index="f">
|
||||
<cfif structKeyExists(ov,f) AND structKeyExists(nv,f)>
|
||||
<cfif toString(ov[f]) NEQ toString(nv[f])>
|
||||
<cfset same = false><cfset cf[f] = {OLD:ov[f], NEW:nv[f]}>
|
||||
</cfif>
|
||||
<cfelseif structKeyExists(ov,f) OR structKeyExists(nv,f)>
|
||||
<cfset same = false><cfset cf[f] = {OLD: structKeyExists(ov,f)?ov[f]:"", NEW: structKeyExists(nv,f)?nv[f]:""}>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
<cfset arrayAppend(changes, {ROW_NUM:num, CHANGE_TYPE:same?"unchanged":"changed", OLD_VALUES:ov, NEW_VALUES:nv, CHANGED_FIELDS:cf})>
|
||||
<cfset changes = []>
|
||||
<cfset fields = ["NAME","PRICE","QTY","SUM","DATE_START"]>
|
||||
<cfloop array="#allNums#" index="num">
|
||||
<cfset inOld = structKeyExists(initByNum, num)>
|
||||
<cfset inNew = structKeyExists(amdByNum, num)>
|
||||
<cfif inOld AND NOT inNew>
|
||||
<cfset arrayAppend(changes, {ROW_NUM:num, CHANGE_TYPE:"deleted", OLD_VALUES:initByNum[num], NEW_VALUES:{}})>
|
||||
<cfelseif NOT inOld AND inNew>
|
||||
<cfset arrayAppend(changes, {ROW_NUM:num, CHANGE_TYPE:"added", OLD_VALUES:{}, NEW_VALUES:amdByNum[num]})>
|
||||
<cfelse>
|
||||
<cfset ov = initByNum[num]>
|
||||
<cfset nv = amdByNum[num]>
|
||||
<cfset same = true>
|
||||
<cfset cf = {}>
|
||||
<cfloop array="#fields#" index="f">
|
||||
<cfif structKeyExists(ov,f) AND structKeyExists(nv,f)>
|
||||
<cfif toString(ov[f]) NEQ toString(nv[f])>
|
||||
<cfset same = false><cfset cf[f] = {OLD:ov[f], NEW:nv[f]}>
|
||||
</cfif>
|
||||
<cfelseif structKeyExists(ov,f) OR structKeyExists(nv,f)>
|
||||
<cfset same = false><cfset cf[f] = {OLD: structKeyExists(ov,f)?ov[f]:"", NEW: structKeyExists(nv,f)?nv[f]:""}>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
<cfset sm = {ADDED:0, DELETED:0, CHANGED:0, UNCHANGED:0}>
|
||||
<cfloop array="#changes#" index="ch"><cfset sm[ch.CHANGE_TYPE] = sm[ch.CHANGE_TYPE] + 1></cfloop>
|
||||
<cfset arrayAppend(allChanges, {SUPPLEMENT_ID:amendments.id, TYPE:amendments.type, CHANGES:changes, SUMMARY:sm})>
|
||||
</cfloop>
|
||||
</cfif>
|
||||
|
||||
<cfset result = {
|
||||
OK: true,
|
||||
CONTRACT_ID: url.contract_id,
|
||||
EXTRACTED: extractedInfo,
|
||||
ALL_CHANGES: allChanges
|
||||
}>
|
||||
</cfif>
|
||||
<cfset arrayAppend(changes, {ROW_NUM:num, CHANGE_TYPE:same?"unchanged":"changed", OLD_VALUES:ov, NEW_VALUES:nv, CHANGED_FIELDS:cf})>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
<cfset sm = {ADDED:0, DELETED:0, CHANGED:0, UNCHANGED:0}>
|
||||
<cfloop array="#changes#" index="ch"><cfset sm[ch.CHANGE_TYPE] = sm[ch.CHANGE_TYPE] + 1></cfloop>
|
||||
<cfset arrayAppend(allChanges, {SUPPLEMENT_ID:amendments.id, TYPE:amendments.type, CHANGES:changes, SUMMARY:sm})>
|
||||
</cfloop>
|
||||
</cfif>
|
||||
<cfcatch><cfset result = {OK: false, ERROR: cfcatch.message, DETAIL: cfcatch.detail}></cfcatch>
|
||||
|
||||
<cfset totalTime = int((getTickCount() - llmStart) / 1000 * 10 + 0.5) / 10>
|
||||
<cfoutput>data: #serializeJSON({type:"done",total_time_s:totalTime,all_changes:allChanges})#
|
||||
|
||||
</cfoutput><cfflush>
|
||||
|
||||
<cfcatch>
|
||||
<cfoutput>data: #serializeJSON({type:"error",message:cfcatch.message,detail:cfcatch.detail})#
|
||||
|
||||
</cfoutput><cfflush>
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
<cfoutput>#serializeJSON(result)#</cfoutput>
|
||||
|
||||
Reference in New Issue
Block a user