Files

164 lines
6.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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)