docs: архитектурный анализ + History + gitignore (2026-06-27)
This commit is contained in:
+163
@@ -0,0 +1,163 @@
|
||||
"""
|
||||
actions.py — API-обёртки для симулятора.
|
||||
|
||||
Все запросы к contracts.kube5s.ru с таймаутами.
|
||||
Эмулирует действия пользователя через UI (загрузка, classify, groups, compare).
|
||||
"""
|
||||
|
||||
import requests, json, time, sys, os, uuid
|
||||
|
||||
BASE = "https://contracts.kube5s.ru"
|
||||
TIMEOUT = 120
|
||||
|
||||
# Убрать warnings
|
||||
import urllib3
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
def _post(url, **kw):
|
||||
return requests.post(url, timeout=TIMEOUT, verify=False, **kw)
|
||||
|
||||
def _get(url, **kw):
|
||||
return requests.get(url, timeout=TIMEOUT, verify=False, **kw)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Действия
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class Session:
|
||||
"""Состояние сессии: batch_id, contract_id, files."""
|
||||
def __init__(self):
|
||||
self.batch_id = str(uuid.uuid4())
|
||||
self.contract_id = None
|
||||
self.files = [] # {name, doc_id, status}
|
||||
|
||||
def upload_file(self, path):
|
||||
"""Загрузить один файл. Возвращает ответ API."""
|
||||
fname = os.path.basename(path)
|
||||
with open(path, 'rb') as f:
|
||||
data = {'batch_id': self.batch_id}
|
||||
if self.contract_id:
|
||||
data['contract_id'] = self.contract_id
|
||||
r = _post(f"{BASE}/upload", files={'files': (fname, f)}, data=data)
|
||||
result = r.json()
|
||||
if result.get('ok'):
|
||||
doc_id = result.get('doc_id')
|
||||
parsed = result.get('parsed', {})
|
||||
self.files.append({
|
||||
'name': fname, 'doc_id': doc_id,
|
||||
'status': parsed.get('status', 'uploaded'),
|
||||
'element_count': parsed.get('element_count', 0)
|
||||
})
|
||||
if result.get('contract_id'):
|
||||
self.contract_id = result['contract_id']
|
||||
return result
|
||||
|
||||
def upload_files(self, paths, delay=0.2):
|
||||
"""Загрузить несколько файлов последовательно."""
|
||||
for p in paths:
|
||||
r = self.upload_file(p)
|
||||
status = r.get('parsed', {}).get('status', r.get('ok', '?'))
|
||||
print(f" ↑ {os.path.basename(p)}: {status}")
|
||||
time.sleep(delay)
|
||||
|
||||
def classify(self):
|
||||
"""Запустить классификацию, ждать завершения."""
|
||||
r = _post(f"{BASE}/api/classify-batch", json={'batch_id': self.batch_id})
|
||||
result = r.json()
|
||||
if not result.get('ok'):
|
||||
print(f" ✗ classify failed: {result}")
|
||||
return result
|
||||
|
||||
# Поллить прогресс
|
||||
total = result.get('total', 0)
|
||||
print(f" ⏳ classify {total} файлов...")
|
||||
for _ in range(300): # макс 10 минут (300 × 2с)
|
||||
time.sleep(2)
|
||||
pr = _get(f"{BASE}/api/batch-progress?batch={self.batch_id}").json()
|
||||
done = pr.get('counts', {}).get('classified', 0) + pr.get('counts', {}).get('failed', 0)
|
||||
if done >= total and total > 0:
|
||||
print(f" ✓ classify done: {pr.get('counts')}")
|
||||
return pr
|
||||
return {"ok": False, "error": "timeout"}
|
||||
|
||||
def get_groups(self):
|
||||
"""Получить группы после классификации."""
|
||||
r = _get(f"{BASE}/api/groups?batch={self.batch_id}")
|
||||
data = r.json()
|
||||
groups = data.get('groups', [])
|
||||
real = [g for g in groups if g.get('contract_number') != '__unresolved__']
|
||||
unres = [g for g in groups if g.get('contract_number') == '__unresolved__']
|
||||
print(f" 📋 группы: {len(real)} (+ {len(unres)} нераспознано)")
|
||||
for g in real:
|
||||
print(f" №{g.get('contract_number')} — {g.get('counterparty','?')} ({len(g.get('documents',[]))} док.)")
|
||||
return data
|
||||
|
||||
def apply_and_compare(self, group_index=0):
|
||||
"""Применить группу и запустить сравнение через SSE."""
|
||||
r = _get(f"{BASE}/api/groups?batch={self.batch_id}")
|
||||
groups = r.json().get('groups', [])
|
||||
real = [g for g in groups if g.get('contract_number') != '__unresolved__']
|
||||
if not real:
|
||||
print(" ✗ нет групп для сравнения")
|
||||
return None
|
||||
|
||||
group = real[group_index]
|
||||
print(f" ⚖ compare группы №{group.get('contract_number')}...")
|
||||
|
||||
# Применить группу
|
||||
ar = _post(f"{BASE}/api/apply-groups", json={'batch_id': self.batch_id, 'groups': [group]})
|
||||
ad = ar.json()
|
||||
if not ad.get('ok') or not ad.get('contract_ids'):
|
||||
print(f" ✗ apply failed: {ad}")
|
||||
return None
|
||||
|
||||
cid = ad['contract_ids'][0]
|
||||
|
||||
# SSE compare
|
||||
import sseclient # pip install sseclient-py
|
||||
url = f"{BASE}/process-v2?contract_id={cid}"
|
||||
response = requests.get(url, stream=True, timeout=300, verify=False)
|
||||
client = sseclient.SSEClient(response)
|
||||
|
||||
sections = {}
|
||||
total_ops = 0
|
||||
for event in client.events():
|
||||
d = json.loads(event.data)
|
||||
t = d.get('type')
|
||||
|
||||
if t == 'extract_start':
|
||||
sections[d['supplement_id']] = d['filename']
|
||||
elif t == 'llm_done':
|
||||
print(f" ✓ {sections.get(d['supplement_id'],'?')}: {d.get('ops_count',0)} оп. {d.get('mode','?')} ({d.get('time_s',0)}с)")
|
||||
elif t == 'applied':
|
||||
ops = d.get('ops', [])
|
||||
total_ops += len(ops)
|
||||
s = d.get('summary', {})
|
||||
print(f" 📊 +{s.get('added',0)} ~{s.get('updated',0)} -{s.get('deleted',0)}")
|
||||
elif t == 'done':
|
||||
print(f" ✓ compare done: {d.get('total_time_s',0)}с, всего {total_ops} оп.")
|
||||
return d
|
||||
elif t == 'error':
|
||||
print(f" ✗ compare error: {d.get('message','?')}")
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
def cleanup(self):
|
||||
"""Очистить БД после теста."""
|
||||
try:
|
||||
_post(f"{BASE}/api/cleanup")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def upload_files_from_dir(session, dir_path, pattern="*", delay=0.2):
|
||||
"""Загрузить все файлы из папки."""
|
||||
import glob
|
||||
files = sorted(glob.glob(os.path.join(dir_path, pattern)))
|
||||
files = [f for f in files if f.endswith(('.docx', '.doc', '.pdf', '.zip'))]
|
||||
if not files:
|
||||
print(f" (нет файлов в {dir_path})")
|
||||
return
|
||||
print(f" Загрузка {len(files)} файлов из {dir_path}...")
|
||||
session.upload_files(files, delay)
|
||||
Reference in New Issue
Block a user