v1.0.108: advisory lock + NFKC + ID-суррогаты + обогащение ops именами
This commit is contained in:
+5
-1
@@ -25,6 +25,10 @@
|
|||||||
<cfset result = {ok: true, skipped: true, message: "supplement already processed"}>
|
<cfset result = {ok: true, skipped: true, message: "supplement already processed"}>
|
||||||
<cfelse>
|
<cfelse>
|
||||||
<cftransaction>
|
<cftransaction>
|
||||||
|
<!--- Advisory lock: атомарный seq без гонки --->
|
||||||
|
<cfquery datasource="baza">
|
||||||
|
SELECT pg_advisory_xact_lock(hashtext(<cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar">))
|
||||||
|
</cfquery>
|
||||||
<cfset seq = 0>
|
<cfset seq = 0>
|
||||||
<cfquery name="sq" datasource="baza">
|
<cfquery name="sq" datasource="baza">
|
||||||
SELECT COALESCE(MAX(seq), 0) as m FROM spec_events
|
SELECT COALESCE(MAX(seq), 0) as m FROM spec_events
|
||||||
@@ -114,7 +118,7 @@
|
|||||||
INSERT INTO spec_current (contract_id, name_hash, name, price, qty, sum, date_start, last_event_id)
|
INSERT INTO spec_current (contract_id, name_hash, name, price, qty, sum, date_start, last_event_id)
|
||||||
VALUES (
|
VALUES (
|
||||||
<cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar">,
|
<cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar">,
|
||||||
md5(lower(trim(<cfqueryparam value="#nm#" cfsqltype="cf_sql_varchar">)) || coalesce(<cfqueryparam value="#ds#" cfsqltype="cf_sql_varchar">, '')),
|
md5(lower(trim(regexp_replace(translate(normalize(<cfqueryparam value="#nm#" cfsqltype="cf_sql_varchar">, NFKC), '–—‑−«»""', '------""'), '\s+', ' ', 'g'))) || coalesce(<cfqueryparam value="#ds#" cfsqltype="cf_sql_varchar">, '')),
|
||||||
<cfqueryparam value="#nm#" cfsqltype="cf_sql_varchar">,
|
<cfqueryparam value="#nm#" cfsqltype="cf_sql_varchar">,
|
||||||
<cfqueryparam value="#pr#" cfsqltype="cf_sql_float" null="#len(pr) EQ 0#">,
|
<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="#qt#" cfsqltype="cf_sql_float" null="#len(qt) EQ 0#">,
|
||||||
|
|||||||
@@ -212,6 +212,19 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
llm_result = result[0]
|
llm_result = result[0]
|
||||||
ops = llm_result.get("ops", [])
|
ops = llm_result.get("ops", [])
|
||||||
mode = llm_result.get("mode", "partial")
|
mode = llm_result.get("mode", "partial")
|
||||||
|
|
||||||
|
# Маппинг target_id (r1, r2...) → target_hash из current_spec
|
||||||
|
id_map = {f"r{i+1}": r["hash"] for i, r in enumerate(current_spec)}
|
||||||
|
# Обогащение ops: target_id → target_hash + имя из current_spec
|
||||||
|
spec_names = {r["hash"]: r["name"] for r in current_spec}
|
||||||
|
for op in ops:
|
||||||
|
tid = op.get("target_id")
|
||||||
|
if tid and tid in id_map:
|
||||||
|
op["target_hash"] = id_map[tid]
|
||||||
|
# Добавить имя для UPDATE/DELETE из spec_current
|
||||||
|
th = op.get("target_hash")
|
||||||
|
if th and th in spec_names and not op.get("new_row", {}).get("name"):
|
||||||
|
op.setdefault("new_row", {})["name"] = spec_names[th]
|
||||||
self._sse({"type": "llm_done", "supplement_id": sid, "filename": s["filename"],
|
self._sse({"type": "llm_done", "supplement_id": sid, "filename": s["filename"],
|
||||||
"ops_count": len(ops), "mode": mode, "time_s": elapsed})
|
"ops_count": len(ops), "mode": mode, "time_s": elapsed})
|
||||||
|
|
||||||
|
|||||||
@@ -39,9 +39,9 @@ def _build_initial(doc_text: str) -> str:
|
|||||||
|
|
||||||
def _build_diff(current_spec: list, doc_text: str) -> str:
|
def _build_diff(current_spec: list, doc_text: str) -> str:
|
||||||
lines = []
|
lines = []
|
||||||
for r in current_spec:
|
for i, r in enumerate(current_spec):
|
||||||
lines.append(
|
lines.append(
|
||||||
f"[hash: {r.get('hash', '?')}] {r.get('name', '?')} | "
|
f"[id: r{i+1}] {r.get('name', '?')} | "
|
||||||
f"цена={r.get('price', '')} | объём={r.get('qty', '')} | "
|
f"цена={r.get('price', '')} | объём={r.get('qty', '')} | "
|
||||||
f"сумма={r.get('sum', '')} | начало={r.get('date_start', '')}"
|
f"сумма={r.get('sum', '')} | начало={r.get('date_start', '')}"
|
||||||
)
|
)
|
||||||
@@ -59,8 +59,8 @@ def _build_diff(current_spec: list, doc_text: str) -> str:
|
|||||||
"mode": "partial" | "full_replace",
|
"mode": "partial" | "full_replace",
|
||||||
"ops": [
|
"ops": [
|
||||||
{{"action": "ADD", "new_row": {{"name": "...", "price": число, "qty": число, "sum": число, "date_start": "YYYY-MM-DD"}}, "comment": "..."}},
|
{{"action": "ADD", "new_row": {{"name": "...", "price": число, "qty": число, "sum": число, "date_start": "YYYY-MM-DD"}}, "comment": "..."}},
|
||||||
{{"action": "UPDATE", "target_hash": "hash_строки", "new_values": {{"price": число}}, "comment": "..."}},
|
{{"action": "UPDATE", "target_id": "rN", "new_values": {{"price": число}}, "comment": "..."}},
|
||||||
{{"action": "DELETE", "target_hash": "hash_строки", "comment": "..."}},
|
{{"action": "DELETE", "target_id": "rN", "comment": "..."}},
|
||||||
{{"action": "UNRESOLVED", "new_values": {{"name": "...", "price": число, ...}}, "reason": "почему не смог сопоставить"}}
|
{{"action": "UNRESOLVED", "new_values": {{"name": "...", "price": число, ...}}, "reason": "почему не смог сопоставить"}}
|
||||||
]
|
]
|
||||||
}}
|
}}
|
||||||
@@ -68,7 +68,7 @@ def _build_diff(current_spec: list, doc_text: str) -> str:
|
|||||||
ПРАВИЛА:
|
ПРАВИЛА:
|
||||||
1. mode = "full_replace" — если в тексте есть фразы: «в следующей редакции», «заменить приложение», «излагается в следующей редакции». При full_replace — опиши ВСЕ новые строки как ADD.
|
1. mode = "full_replace" — если в тексте есть фразы: «в следующей редакции», «заменить приложение», «излагается в следующей редакции». При full_replace — опиши ВСЕ новые строки как ADD.
|
||||||
2. mode = "partial" — если ДС меняет только отдельные строки.
|
2. mode = "partial" — если ДС меняет только отдельные строки.
|
||||||
3. Для UPDATE/DELETE — находи соответствующую строку по target_hash.
|
3. Для UPDATE/DELETE — укажи target_id (r1, r2...) из списка текущей спецификации. НЕ придумывай новые id.
|
||||||
4. new_values в UPDATE — только ИЗМЕНЁННЫЕ поля (не все).
|
4. new_values в UPDATE — только ИЗМЕНЁННЫЕ поля (не все).
|
||||||
5. Если не можешь однозначно сопоставить строку — action: UNRESOLVED с reason.
|
5. Если не можешь однозначно сопоставить строку — action: UNRESOLVED с reason.
|
||||||
6. Пропускай итоговые строки и подписи.
|
6. Пропускай итоговые строки и подписи.
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="topbar">
|
<div class="topbar">
|
||||||
<img src="/nubes-logo.svg" alt="Nubes">
|
<img src="/nubes-logo.svg" alt="Nubes">
|
||||||
<span class="title">Сверка договоров — LLM AI-driven Event Sourcing <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.0.107 — Lucee</span></span>
|
<span class="title">Сверка договоров — LLM AI-driven Event Sourcing <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.0.108 — Lucee</span></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
|||||||
Reference in New Issue
Block a user