76 lines
3.1 KiB
Python
76 lines
3.1 KiB
Python
"""Upload-модуль: конвертация .doc→.docx + хранение/парсинг (sink для VM-буфера)."""
|
|
import base64
|
|
import hashlib
|
|
|
|
import httpx
|
|
from services.parse import parse_file
|
|
from db import documents
|
|
import config
|
|
|
|
|
|
def _convert(filename: str, data: bytes) -> bytes:
|
|
""".doc → .docx через внешний libreoffice-сервис. Возвращает docx-байты."""
|
|
try:
|
|
resp = httpx.post(
|
|
config.CONVERT_SERVICE_URL + "/convert",
|
|
files={"file": (filename, data, "application/msword")},
|
|
timeout=120,
|
|
)
|
|
except httpx.TimeoutException:
|
|
raise Exception("conversion timeout")
|
|
if resp.status_code != 200:
|
|
try:
|
|
err = resp.json().get("error", "conversion failed")
|
|
except Exception:
|
|
err = "conversion failed"
|
|
raise Exception(err)
|
|
return resp.content
|
|
|
|
|
|
def _store_and_parse(filename: str, data: bytes, batch_id, contract_id, zip_source=None, mime_type="application/octet-stream"):
|
|
"""Общая логика: дедуп → insert в БД → авто-парсинг. Возвращает dict-результат."""
|
|
content_hash = hashlib.sha256(data).hexdigest()[:16]
|
|
|
|
# Дедупликация по хешу
|
|
if batch_id:
|
|
existing = documents.get_by_hash(batch_id, content_hash)
|
|
if existing:
|
|
return {"ok": False, "error": "duplicate", "doc_id": existing["id"], "duplicate_of": True}
|
|
|
|
doc = documents.insert(
|
|
filename=filename,
|
|
mime_type=mime_type,
|
|
original_bytes=base64.b64encode(data).decode(),
|
|
batch_id=batch_id,
|
|
zip_source=zip_source,
|
|
content_hash=content_hash,
|
|
)
|
|
|
|
# Авто-парсинг
|
|
try:
|
|
result = parse_file(filename, data)
|
|
if result["status"] == "parsed":
|
|
documents.set_parsed(doc["id"], result["elements"])
|
|
parsed = {"status": "parsed", "element_count": result.get("element_count", 0)}
|
|
else:
|
|
documents.set_error(doc["id"], result.get("error", "parse failed"))
|
|
parsed = {"status": "error", "error": result.get("error", "parse failed")}
|
|
except Exception as e:
|
|
documents.set_error(doc["id"], str(e))
|
|
parsed = {"status": "error", "error": str(e)}
|
|
|
|
return {"ok": True, "doc_id": doc["id"], "contract_id": contract_id, "parsed": parsed}
|
|
|
|
|
|
def contracts_upload_sink(name, content, batch_id=None, contract_id=None, zip_source=None):
|
|
"""Sink для переиспользуемого модуля upload: вставить файл в documents + авто-парсинг.
|
|
|
|
Вызывается модулем create_upload_refs_blueprint(cfg, sink=...) на каждый
|
|
вытянутый с ВМ файл. .doc конвертируется в .docx через внешний LibreOffice
|
|
(парсер умеет только .docx). Возвращает dict ok/doc_id/contract_id/parsed.
|
|
"""
|
|
if name.lower().endswith(".doc"):
|
|
content = _convert(name, content)
|
|
name = name[:-4] + ".docx"
|
|
return _store_and_parse(name, content, batch_id, contract_id, zip_source)
|