v1.0.149: VM parses PDF + stores elements_json in Lucee
This commit is contained in:
@@ -346,14 +346,64 @@ class Handler(BaseHTTPRequestHandler):
|
||||
json={"upload_id": upload_id, "contract_id": contract_id},
|
||||
)
|
||||
if resp.status_code == 200:
|
||||
result = resp.json()
|
||||
doc_id = result.get("DOC_ID", "")
|
||||
|
||||
# Парсинг на VM + сохранить в Lucee
|
||||
parsed = self._parse_file(filename, file_data)
|
||||
if parsed and parsed.get("status") == "parsed" and doc_id:
|
||||
try:
|
||||
escaped = json.dumps(parsed["elements"], ensure_ascii=False).replace("'", "''")
|
||||
with httpx.Client(http2=True, timeout=15, verify=False) as c:
|
||||
c.post(
|
||||
f"{LUCEE_URL}/api.cfm",
|
||||
data={
|
||||
"action": "execute",
|
||||
"sql": f"UPDATE documents SET elements_json='{escaped}'::jsonb, status='parsed' WHERE id='{doc_id}'"
|
||||
}
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
result["parsed"] = parsed
|
||||
self.send_response(200)
|
||||
self._send_cors()
|
||||
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||||
self.end_headers()
|
||||
self.wfile.write(resp.content)
|
||||
self.wfile.write(json.dumps(result, ensure_ascii=False).encode())
|
||||
else:
|
||||
self._send_error_cors(502, f"assemble failed: HTTP {resp.status_code}")
|
||||
|
||||
def _parse_file(self, filename, data):
|
||||
"""Распарсить файл на VM: PDF→PyPDF2, DOCX/DOC→python-docx, иначе plain text."""
|
||||
import base64
|
||||
ext = filename.rsplit('.', 1)[-1].lower() if '.' in filename else ''
|
||||
try:
|
||||
if ext == 'pdf':
|
||||
from PyPDF2 import PdfReader
|
||||
import io as io_mod
|
||||
reader = PdfReader(io_mod.BytesIO(data))
|
||||
elements = []
|
||||
for page in reader.pages:
|
||||
text = page.extract_text()
|
||||
if text:
|
||||
for line in text.split('\n'):
|
||||
line = line.strip()
|
||||
if line:
|
||||
elements.append({"type": "paragraph", "text": line, "style": ""})
|
||||
return {"status": "parsed", "element_count": len(elements), "elements": elements}
|
||||
elif ext in ('docx', 'doc'):
|
||||
# Для DOCX/DOC — оставляем Lucee (Apache POI работает)
|
||||
return None
|
||||
else:
|
||||
# Plain text
|
||||
text = data.decode('utf-8', errors='ignore')[:5000]
|
||||
lines = [l.strip() for l in text.split('\n') if l.strip()]
|
||||
return {"status": "parsed", "element_count": len(lines),
|
||||
"elements": [{"type": "paragraph", "text": l, "style": ""} for l in lines]}
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e), "element_count": 0}
|
||||
|
||||
def _handle_parse_pdf(self):
|
||||
"""Распарсить PDF через PyPDF2 (получает base64 тело от Lucee)."""
|
||||
import base64
|
||||
|
||||
@@ -339,20 +339,14 @@ fileInput.addEventListener('change', async function() {
|
||||
if (!contractId && zf.contract_id) contractId = zf.contract_id;
|
||||
renderTable();
|
||||
|
||||
// Авто-парсинг
|
||||
// Авто-парсинг — VM уже распарсила
|
||||
var zt0 = Date.now();
|
||||
try {
|
||||
var zpr = await fetch('/parser.cfm?doc_id=' + zf.doc_id);
|
||||
var zpd = await zpr.json();
|
||||
var zelapsed = ((Date.now() - zt0) / 1000).toFixed(1);
|
||||
if (zpd.OK && zpd.STATUS === 'parsed') {
|
||||
if (zf.PARSED && zf.PARSED.STATUS === 'parsed') {
|
||||
fileQueue[zIdx].parsed = true;
|
||||
fileQueue[zIdx].status = '<span class="status-ok">✓ ' + zpd.ELEMENT_COUNT + ' эл. (' + zelapsed + 'с)</span>';
|
||||
fileQueue[zIdx].status = '<span class="status-ok">✓ ' + zf.PARSED.ELEMENT_COUNT + ' эл. (' + zelapsed + 'с)</span>';
|
||||
} else {
|
||||
fileQueue[zIdx].status = '<span class="status-err">✗ ' + (zpd.ERROR_COUNT > 0 ? (zpd.ERRORS && zpd.ERRORS[0] || 'ошибка') : 'ошибка') + '</span>';
|
||||
}
|
||||
} catch(zpe) {
|
||||
fileQueue[zIdx].status = '<span class="status-err">✗ парсинг: сеть</span>';
|
||||
fileQueue[zIdx].status = '<span class="status-err">✗ ' + ((zf.PARSED && zf.PARSED.ERROR) || 'ошибка') + '</span>';
|
||||
}
|
||||
renderTable();
|
||||
}
|
||||
@@ -389,24 +383,17 @@ fileInput.addEventListener('change', async function() {
|
||||
fileQueue[rowIdx].status = '<span class="status-ok">✓</span>';
|
||||
fileQueue[rowIdx].uploaded = true;
|
||||
|
||||
// Авто-парсинг сразу после загрузки
|
||||
// Авто-парсинг — VM уже распарсила, берём из ответа
|
||||
var t0 = Date.now();
|
||||
fileQueue[rowIdx].status = '⏳ парсинг...';
|
||||
renderTable();
|
||||
try {
|
||||
var pr = await fetch('/parser.cfm?doc_id=' + resp.DOC_ID);
|
||||
var pd = await pr.json();
|
||||
var elapsed = ((Date.now() - t0) / 1000).toFixed(1);
|
||||
if (pd.OK && pd.STATUS === 'parsed') {
|
||||
if (resp.PARSED && resp.PARSED.STATUS === 'parsed') {
|
||||
fileQueue[rowIdx].parsed = true;
|
||||
fileQueue[rowIdx].parseInfo = pd;
|
||||
fileQueue[rowIdx].status = '<span class="status-ok">✓ ' + pd.ELEMENT_COUNT + ' эл. (' + elapsed + 'с)</span>';
|
||||
fileQueue[rowIdx].parseInfo = resp.PARSED;
|
||||
fileQueue[rowIdx].status = '<span class="status-ok">✓ ' + resp.PARSED.ELEMENT_COUNT + ' эл. (' + elapsed + 'с)</span>';
|
||||
} else {
|
||||
fileQueue[rowIdx].status = '<span class="status-err">✗ ' + (pd.ERROR_COUNT > 0 ? (pd.ERRORS && pd.ERRORS[0] || 'ошибка') : 'ошибка') + '</span>';
|
||||
fileQueue[rowIdx].parseInfo = pd;
|
||||
}
|
||||
} catch(pe) {
|
||||
fileQueue[rowIdx].status = '<span class="status-err">✗ парсинг: сеть</span>';
|
||||
var errMsg = (resp.PARSED && resp.PARSED.ERROR) || 'ошибка';
|
||||
fileQueue[rowIdx].status = '<span class="status-err">✗ ' + errMsg + '</span>';
|
||||
fileQueue[rowIdx].parseInfo = resp.PARSED;
|
||||
}
|
||||
} catch(err) {
|
||||
fileQueue[rowIdx].status = '<span class="status-err">✗ ' + err.message + '</span>';
|
||||
|
||||
Reference in New Issue
Block a user