v1.0.175: виртуальные группы + classify_raw + stepper

This commit is contained in:
2026-06-24 17:06:25 +04:00
parent b5d35f57fc
commit 475d0b8dc7
7 changed files with 114 additions and 25 deletions
+6 -6
View File
@@ -31,15 +31,14 @@ LLM_MODEL = "gpt-oss-120b"
def _call_llm_classify(header_text):
"""
Прямой вызов LLM для классификации ОДНОГО документа.
Не использует общий call_llm() из services/llm.py — здесь свой промпт и формат ответа.
Возвращает распарсенный JSON dict с полями: doc_type, own_number, parent_number, doc_date, counterparty, confidence.
Возвращает (parsed_dict, raw_text).
"""
prompt, _ = build_classify_prompt(header_text)
payload = {
"model": LLM_MODEL,
"messages": [{"role": "user", "content": prompt}],
"max_tokens": 1000, # достаточно для JSON с длинными названиями
"temperature": 0.1, # минимальная температура для детерминированности
"max_tokens": 1000,
"temperature": 0.1,
}
with httpx.Client(http2=True, timeout=60, verify=False) as client:
resp = client.post(
@@ -50,7 +49,7 @@ def _call_llm_classify(header_text):
data = resp.json()
raw = data.get("choices", [{}])[0].get("message", {}).get("content", "")
return _safe_json_parse(raw)
return _safe_json_parse(raw), raw
def classify_batch(batch_id):
@@ -74,7 +73,7 @@ def classify_batch(batch_id):
"""Классифицировать один документ: выжимка → LLM → сохранить результат."""
try:
text = _smart_extract(doc["elements_json"])
result = _call_llm_classify(text)
result, raw = _call_llm_classify(text)
db_docs.set_classification(
doc["id"],
result.get("doc_type", "other"),
@@ -82,6 +81,7 @@ def classify_batch(batch_id):
result.get("parent_number"),
result.get("doc_date"),
result.get("counterparty"),
classify_raw=raw,
)
return True
except Exception as e:
+38 -12
View File
@@ -33,8 +33,9 @@ def group_documents(batch_id):
1. Отделить contract от supplement/specification
2. Каждый contract → якорь группы
3. Для каждого supplement: найти contract по parent_number (нормализованный)
4. Несматченные группа "__unresolved__"
5. Внутри группы сортировка по doc_date
4. Оставшиеся supplement/spec → виртуальные группы по parent_number/own_number
5. Совсем без номеров → группа "__unresolved__"
6. Внутри группы сортировка по doc_date
Возвращает {ok, groups: [{contract_number, counterparty, documents: [...]}], total_docs}.
"""
@@ -79,18 +80,43 @@ def group_documents(batch_id):
groups.append(group)
# Unmatched documents → "unresolved" group (including failed/pending)
unmatched = [s for s in supplements_list if s.get("doc_type") != "contract"]
# ── Virtual groups: unmatched supplements grouped by number ──────────
# Group remaining supplements/specs by normalized parent_number (priority) or own_number
virtual = {}
for s in supplements_list:
num = normalize_number(s.get("parent_number") or s.get("own_number") or "")
if not num:
continue # no number → stays in supplements_list for unresolved
if num not in virtual:
# Use counterparty from first doc in group
cp = s.get("counterparty") or ""
virtual[num] = {
"contract_number": s.get("parent_number") or s.get("own_number") or "?",
"counterparty": cp,
"documents": [],
}
virtual[num]["documents"].append(s)
# Update counterparty if current doc has a better one
if not virtual[num]["counterparty"] and s.get("counterparty"):
virtual[num]["counterparty"] = s.get("counterparty")
for vnum, vgroup in virtual.items():
vgroup["documents"].sort(key=lambda x: x.get("doc_date") or "")
groups.append(vgroup)
# Remove grouped docs from supplements_list
for s in vgroup["documents"]:
supplements_list.remove(s)
# ── Unresolved: everything left (no number, failed, pending, other) ──
unmatched = [s for s in supplements_list] # remaining after virtual grouping
unmatched += [d for d in docs if d.get("classify_status") != "classified"]
if unmatched or len(contracts_list) == 0:
# Put unmatched into a separate group
if unmatched:
groups.append({
"contract_number": "__unresolved__",
"counterparty": "",
"documents": unmatched,
})
if unmatched:
groups.append({
"contract_number": "__unresolved__",
"counterparty": "",
"documents": unmatched,
})
return {"ok": True, "groups": groups, "total_docs": len(docs)}