Add exports panel: versioned drafts, download, upload revisions, mark final
Export DOCX now saves to data/exports/{case_number}/ with auto-versioning
(טיוטה-v1, v2...). The case view UI shows all drafts with download buttons,
allows uploading revised versions (עריכה-v1...), and marking a version as
final (copies to training corpus for style learning).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
125
web/app.py
125
web/app.py
@@ -554,6 +554,131 @@ async def api_learn(case_number: str):
|
||||
return {"status": "final", "message": "לולאת למידה הופעלה — גרסה סופית נקלטה"}
|
||||
|
||||
|
||||
# ── Exports API — drafts, versions, download, upload, mark-final ──
|
||||
|
||||
|
||||
@app.get("/api/cases/{case_number}/exports")
|
||||
async def api_list_exports(case_number: str):
|
||||
"""List all exported drafts and versions for a case."""
|
||||
export_dir = config.EXPORTS_DIR / case_number
|
||||
if not export_dir.exists():
|
||||
return []
|
||||
files = []
|
||||
for f in sorted(export_dir.iterdir(), key=lambda p: p.stat().st_mtime, reverse=True):
|
||||
if f.is_file() and f.suffix.lower() == ".docx":
|
||||
stat = f.stat()
|
||||
files.append({
|
||||
"filename": f.name,
|
||||
"size": stat.st_size,
|
||||
"created_at": stat.st_mtime,
|
||||
"is_final": f.name.startswith("סופי-"),
|
||||
})
|
||||
return files
|
||||
|
||||
|
||||
@app.get("/api/cases/{case_number}/exports/{filename}/download")
|
||||
async def api_download_export(case_number: str, filename: str):
|
||||
"""Download an exported file."""
|
||||
export_dir = config.EXPORTS_DIR / case_number
|
||||
path = export_dir / filename
|
||||
if not path.exists() or not path.parent.samefile(export_dir):
|
||||
raise HTTPException(404, "קובץ לא נמצא")
|
||||
return FileResponse(
|
||||
path,
|
||||
media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
filename=filename,
|
||||
)
|
||||
|
||||
|
||||
@app.post("/api/cases/{case_number}/exports/upload")
|
||||
async def api_upload_export(case_number: str, file: UploadFile = File(...)):
|
||||
"""Upload a revised version of a draft."""
|
||||
case = await db.get_case_by_number(case_number)
|
||||
if not case:
|
||||
raise HTTPException(404, f"תיק {case_number} לא נמצא")
|
||||
|
||||
if not file.filename:
|
||||
raise HTTPException(400, "No filename provided")
|
||||
|
||||
ext = Path(file.filename).suffix.lower()
|
||||
if ext != ".docx":
|
||||
raise HTTPException(400, "רק קבצי DOCX נתמכים")
|
||||
|
||||
content = await file.read()
|
||||
if len(content) > MAX_FILE_SIZE:
|
||||
raise HTTPException(400, f"קובץ גדול מדי. מקסימום: {MAX_FILE_SIZE // (1024*1024)}MB")
|
||||
|
||||
export_dir = config.EXPORTS_DIR / case_number
|
||||
export_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Version numbering for uploads
|
||||
existing = sorted(export_dir.glob("עריכה-v*.docx"))
|
||||
next_ver = 1
|
||||
for p in existing:
|
||||
try:
|
||||
ver = int(p.stem.split("-v")[1])
|
||||
next_ver = max(next_ver, ver + 1)
|
||||
except (IndexError, ValueError):
|
||||
pass
|
||||
|
||||
dest = export_dir / f"עריכה-v{next_ver}.docx"
|
||||
dest.write_bytes(content)
|
||||
|
||||
return {
|
||||
"filename": dest.name,
|
||||
"size": len(content),
|
||||
"version": next_ver,
|
||||
}
|
||||
|
||||
|
||||
@app.post("/api/cases/{case_number}/exports/{filename}/mark-final")
|
||||
async def api_mark_final(case_number: str, filename: str):
|
||||
"""Mark an export as the final version — copies to training corpus."""
|
||||
case = await db.get_case_by_number(case_number)
|
||||
if not case:
|
||||
raise HTTPException(404, f"תיק {case_number} לא נמצא")
|
||||
|
||||
export_dir = config.EXPORTS_DIR / case_number
|
||||
source = export_dir / filename
|
||||
if not source.exists() or not source.parent.samefile(export_dir):
|
||||
raise HTTPException(404, "קובץ לא נמצא")
|
||||
|
||||
# Rename/copy to final
|
||||
final_name = f"סופי-{case_number}.docx"
|
||||
final_path = export_dir / final_name
|
||||
shutil.copy2(str(source), str(final_path))
|
||||
|
||||
# Also copy to training directory for future style learning
|
||||
config.TRAINING_DIR.mkdir(parents=True, exist_ok=True)
|
||||
training_dest = config.TRAINING_DIR / f"החלטה-{case_number}.docx"
|
||||
shutil.copy2(str(source), str(training_dest))
|
||||
|
||||
# Update case status to final
|
||||
pool = await db.get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
await conn.execute(
|
||||
"UPDATE cases SET status = 'final', updated_at = now() WHERE id = $1",
|
||||
UUID(case["id"]),
|
||||
)
|
||||
|
||||
return {
|
||||
"final_filename": final_name,
|
||||
"training_copy": str(training_dest),
|
||||
"status": "final",
|
||||
}
|
||||
|
||||
|
||||
@app.post("/api/cases/{case_number}/export-docx")
|
||||
async def api_export_docx(case_number: str):
|
||||
"""Trigger DOCX export for a case."""
|
||||
result = await drafting_tools.export_docx(case_number)
|
||||
try:
|
||||
data = json.loads(result)
|
||||
return data
|
||||
except json.JSONDecodeError:
|
||||
raise HTTPException(500, result)
|
||||
|
||||
|
||||
@app.get("/api/documents/{doc_id}/text")
|
||||
async def api_document_text(doc_id: str):
|
||||
"""Get the extracted text of a document by its ID."""
|
||||
|
||||
Reference in New Issue
Block a user