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},
|
json={"upload_id": upload_id, "contract_id": contract_id},
|
||||||
)
|
)
|
||||||
if resp.status_code == 200:
|
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_response(200)
|
||||||
self._send_cors()
|
self._send_cors()
|
||||||
self.send_header("Content-Type", "application/json; charset=utf-8")
|
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.write(resp.content)
|
self.wfile.write(json.dumps(result, ensure_ascii=False).encode())
|
||||||
else:
|
else:
|
||||||
self._send_error_cors(502, f"assemble failed: HTTP {resp.status_code}")
|
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):
|
def _handle_parse_pdf(self):
|
||||||
"""Распарсить PDF через PyPDF2 (получает base64 тело от Lucee)."""
|
"""Распарсить PDF через PyPDF2 (получает base64 тело от Lucee)."""
|
||||||
import base64
|
import base64
|
||||||
|
|||||||
@@ -339,20 +339,14 @@ fileInput.addEventListener('change', async function() {
|
|||||||
if (!contractId && zf.contract_id) contractId = zf.contract_id;
|
if (!contractId && zf.contract_id) contractId = zf.contract_id;
|
||||||
renderTable();
|
renderTable();
|
||||||
|
|
||||||
// Авто-парсинг
|
// Авто-парсинг — VM уже распарсила
|
||||||
var zt0 = Date.now();
|
var zt0 = Date.now();
|
||||||
try {
|
var zelapsed = ((Date.now() - zt0) / 1000).toFixed(1);
|
||||||
var zpr = await fetch('/parser.cfm?doc_id=' + zf.doc_id);
|
if (zf.PARSED && zf.PARSED.STATUS === 'parsed') {
|
||||||
var zpd = await zpr.json();
|
fileQueue[zIdx].parsed = true;
|
||||||
var zelapsed = ((Date.now() - zt0) / 1000).toFixed(1);
|
fileQueue[zIdx].status = '<span class="status-ok">✓ ' + zf.PARSED.ELEMENT_COUNT + ' эл. (' + zelapsed + 'с)</span>';
|
||||||
if (zpd.OK && zpd.STATUS === 'parsed') {
|
} else {
|
||||||
fileQueue[zIdx].parsed = true;
|
fileQueue[zIdx].status = '<span class="status-err">✗ ' + ((zf.PARSED && zf.PARSED.ERROR) || 'ошибка') + '</span>';
|
||||||
fileQueue[zIdx].status = '<span class="status-ok">✓ ' + zpd.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>';
|
|
||||||
}
|
}
|
||||||
renderTable();
|
renderTable();
|
||||||
}
|
}
|
||||||
@@ -389,24 +383,17 @@ fileInput.addEventListener('change', async function() {
|
|||||||
fileQueue[rowIdx].status = '<span class="status-ok">✓</span>';
|
fileQueue[rowIdx].status = '<span class="status-ok">✓</span>';
|
||||||
fileQueue[rowIdx].uploaded = true;
|
fileQueue[rowIdx].uploaded = true;
|
||||||
|
|
||||||
// Авто-парсинг сразу после загрузки
|
// Авто-парсинг — VM уже распарсила, берём из ответа
|
||||||
var t0 = Date.now();
|
var t0 = Date.now();
|
||||||
fileQueue[rowIdx].status = '⏳ парсинг...';
|
var elapsed = ((Date.now() - t0) / 1000).toFixed(1);
|
||||||
renderTable();
|
if (resp.PARSED && resp.PARSED.STATUS === 'parsed') {
|
||||||
try {
|
fileQueue[rowIdx].parsed = true;
|
||||||
var pr = await fetch('/parser.cfm?doc_id=' + resp.DOC_ID);
|
fileQueue[rowIdx].parseInfo = resp.PARSED;
|
||||||
var pd = await pr.json();
|
fileQueue[rowIdx].status = '<span class="status-ok">✓ ' + resp.PARSED.ELEMENT_COUNT + ' эл. (' + elapsed + 'с)</span>';
|
||||||
var elapsed = ((Date.now() - t0) / 1000).toFixed(1);
|
} else {
|
||||||
if (pd.OK && pd.STATUS === 'parsed') {
|
var errMsg = (resp.PARSED && resp.PARSED.ERROR) || 'ошибка';
|
||||||
fileQueue[rowIdx].parsed = true;
|
fileQueue[rowIdx].status = '<span class="status-err">✗ ' + errMsg + '</span>';
|
||||||
fileQueue[rowIdx].parseInfo = pd;
|
fileQueue[rowIdx].parseInfo = resp.PARSED;
|
||||||
fileQueue[rowIdx].status = '<span class="status-ok">✓ ' + pd.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>';
|
|
||||||
}
|
}
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
fileQueue[rowIdx].status = '<span class="status-err">✗ ' + err.message + '</span>';
|
fileQueue[rowIdx].status = '<span class="status-err">✗ ' + err.message + '</span>';
|
||||||
|
|||||||
Reference in New Issue
Block a user