219 lines
6.3 KiB
Python
219 lines
6.3 KiB
Python
|
|
#!/usr/bin/env python3
|
|
import json, subprocess, threading, uuid, shutil
|
|
from pathlib import Path
|
|
from flask import Flask, request, jsonify, send_from_directory
|
|
|
|
BASE=Path('/home/nvme/epub-gec-experiment')
|
|
INPUT=Path('/home/orangepi/epub-corrector/input')
|
|
RUNS=BASE/'runs'/'ui'
|
|
MEMORY_DIR=BASE/'review_memory'
|
|
MEMORY_FILE=MEMORY_DIR/'auto_memory.json'
|
|
|
|
RUNS.mkdir(parents=True, exist_ok=True)
|
|
MEMORY_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
def empty_memory():
|
|
return {
|
|
'approved': [],
|
|
'sentence_rewrites': [],
|
|
'preserved': [],
|
|
'rejected': [],
|
|
}
|
|
|
|
|
|
def load_memory():
|
|
if not MEMORY_FILE.exists():
|
|
return empty_memory()
|
|
|
|
data = json.loads(
|
|
MEMORY_FILE.read_text(encoding='utf-8')
|
|
)
|
|
|
|
memory = empty_memory()
|
|
|
|
for section in memory:
|
|
memory[section] = data.get(section, [])
|
|
|
|
return memory
|
|
|
|
|
|
def merge_memory(new_decisions):
|
|
memory = load_memory()
|
|
|
|
key_functions = {
|
|
'approved': lambda item: (
|
|
item.get('original', '').casefold(),
|
|
item.get('replacement', '').casefold(),
|
|
),
|
|
'sentence_rewrites': lambda item: (
|
|
item.get('original_context', ''),
|
|
item.get('rewritten_context', ''),
|
|
),
|
|
'preserved': lambda item: (
|
|
item.get('value', '').casefold()
|
|
),
|
|
'rejected': lambda item: (
|
|
item.get('original', '').casefold(),
|
|
item.get('replacement', '').casefold(),
|
|
),
|
|
}
|
|
|
|
for section, key_function in key_functions.items():
|
|
merged = {}
|
|
|
|
for item in memory[section]:
|
|
key = key_function(item)
|
|
|
|
if key:
|
|
merged[key] = item
|
|
|
|
for item in new_decisions.get(section, []):
|
|
key = key_function(item)
|
|
|
|
if key:
|
|
merged[key] = item
|
|
|
|
memory[section] = list(merged.values())
|
|
|
|
# Una successiva approvazione prevale
|
|
# sull'eventuale rifiuto della stessa coppia.
|
|
approved_pairs = {
|
|
key_functions['approved'](item)
|
|
for item in memory['approved']
|
|
}
|
|
|
|
memory['rejected'] = [
|
|
item
|
|
for item in memory['rejected']
|
|
if key_functions['rejected'](item)
|
|
not in approved_pairs
|
|
]
|
|
|
|
temporary = MEMORY_FILE.with_suffix('.tmp')
|
|
|
|
temporary.write_text(
|
|
json.dumps(
|
|
memory,
|
|
ensure_ascii=False,
|
|
indent=2,
|
|
),
|
|
encoding='utf-8',
|
|
)
|
|
|
|
temporary.replace(MEMORY_FILE)
|
|
|
|
return memory
|
|
app=Flask(__name__, static_folder='static')
|
|
jobs={}
|
|
|
|
def run_job(job_id, epub):
|
|
job=jobs[job_id]
|
|
report=RUNS/f'{job_id}.json'; log=RUNS/f'{job_id}.log'
|
|
lt_url = "".join([
|
|
"http:",
|
|
"//",
|
|
"127.0.0.1",
|
|
":8010",
|
|
"/v2/check",
|
|
])
|
|
|
|
cmd = [
|
|
str(BASE / ".venv-rknn/bin/python"),
|
|
str(BASE / "epub_npu_dry_run_grouped_v2.py"),
|
|
str(epub),
|
|
"--memory",
|
|
str(MEMORY_FILE),
|
|
"--threshold",
|
|
"0.999",
|
|
"--min-margin",
|
|
"0.002",
|
|
"--languagetool-url",
|
|
lt_url,
|
|
"--output",
|
|
str(report),
|
|
]
|
|
job.update(status='running',report=str(report),log=str(log),cmd=cmd)
|
|
with log.open('w') as f:
|
|
p=subprocess.Popen(cmd,stdout=f,stderr=subprocess.STDOUT,text=True)
|
|
job['pid']=p.pid; rc=p.wait()
|
|
job['status']='done' if rc==0 else 'failed'; job['returncode']=rc
|
|
|
|
@app.get('/')
|
|
def index(): return send_from_directory('static','index.html')
|
|
|
|
@app.get('/api/books')
|
|
def books(): return jsonify([{'name':p.name,'path':str(p)} for p in sorted(INPUT.glob('*.epub'))])
|
|
|
|
@app.post('/api/start')
|
|
def start():
|
|
data=request.get_json(); epub=Path(data['path'])
|
|
if epub.parent.resolve()!=INPUT.resolve() or not epub.exists(): return jsonify(error='EPUB non valido'),400
|
|
jid=uuid.uuid4().hex[:10]; jobs[jid]={'id':jid,'book':str(epub),'status':'queued'}
|
|
threading.Thread(target=run_job,args=(jid,epub),daemon=True).start(); return jsonify(jobs[jid])
|
|
|
|
@app.get('/api/job/<jid>')
|
|
def job(jid):
|
|
j=jobs.get(jid)
|
|
if not j:return jsonify(error='Job non trovato'),404
|
|
if j.get('log') and Path(j['log']).exists(): j['tail']='\n'.join(Path(j['log']).read_text(errors='replace').splitlines()[-25:])
|
|
return jsonify(j)
|
|
|
|
@app.get('/api/report/<jid>')
|
|
def report(jid):
|
|
j=jobs.get(jid); p=Path(j.get('report','')) if j else None
|
|
if not p or not p.exists(): return jsonify(error='Rapporto non pronto'),404
|
|
data=json.loads(p.read_text()); items=[]
|
|
for i,x in enumerate(data.get('groups',[])):
|
|
if x.get('decision') in {'AUTO_APPLY','REVIEW_HIGH','REVIEW_AMBIGUOUS'} and x.get('best_proposal'):
|
|
items.append({'id':i,'decision':x['decision'],'file':x['file'],'paragraph':x['paragraph'],'offset':x['offset'],'original':x['original'],'proposal':x['best_proposal'],'probability':x.get('best_probability'),'margin':x.get('margin'),'context':x['context']})
|
|
return jsonify(items=items,summary=data.get('summary',{}))
|
|
|
|
@app.post('/api/apply/<jid>')
|
|
def apply(jid):
|
|
j=jobs.get(jid)
|
|
if not j:return jsonify(error='Job non trovato'),404
|
|
decisions = request.get_json()
|
|
|
|
manifest = RUNS / f'{jid}_approved.json'
|
|
|
|
manifest.write_text(
|
|
json.dumps(
|
|
decisions,
|
|
ensure_ascii=False,
|
|
indent=2,
|
|
),
|
|
encoding='utf-8',
|
|
)
|
|
|
|
memory = merge_memory(decisions)
|
|
|
|
out = (
|
|
BASE
|
|
/ 'output'
|
|
/ f"{Path(j['book']).stem}_ui_reviewed.epub"
|
|
)
|
|
|
|
rep = RUNS / f'{jid}_applied.json'
|
|
cmd=[str(BASE/'.venv-rknn/bin/python'),str(BASE/'apply_approved_epub_changes.py'),j['book'],'--changes',str(manifest),'--output',str(out),'--report',str(rep)]
|
|
r=subprocess.run(cmd,capture_output=True,text=True)
|
|
if r.returncode:return jsonify(error=r.stdout+r.stderr),400
|
|
return jsonify(
|
|
output=str(out),
|
|
report=str(rep),
|
|
memory=str(MEMORY_FILE),
|
|
memory_counts={
|
|
key: len(value)
|
|
for key, value in memory.items()
|
|
},
|
|
message=r.stdout,
|
|
)
|
|
|
|
@app.post('/api/languagetool/<action>')
|
|
def lt(action):
|
|
if action not in {'start','stop','restart'}:return jsonify(error='Azione non valida'),400
|
|
r=subprocess.run(['sudo','docker',action,'languagetool-epub'],capture_output=True,text=True)
|
|
return jsonify(ok=r.returncode==0,output=r.stdout+r.stderr)
|
|
|
|
app.run(host='0.0.0.0',port=8090,threaded=True)
|