Merge pull request 'feat(learning): מסלול נקי להעלאת החלטה סופית + פאנל-סגנון דו-סוכני (DeepSeek+Gemini)' (#158) from worktree-final-upload-pipeline into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 42s
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 42s
This commit was merged in pull request #158.
This commit is contained in:
122
web/app.py
122
web/app.py
@@ -3312,6 +3312,128 @@ async def api_mark_final(case_number: str, filename: str):
|
||||
}
|
||||
|
||||
|
||||
@app.post("/api/cases/{case_number}/final/upload")
|
||||
async def api_upload_final_decision(case_number: str, file: UploadFile = File(...)):
|
||||
"""Clean path: upload the CHAIR's signed final decision (Dafna's version).
|
||||
|
||||
Distinct from the two pre-existing flows:
|
||||
• exports/upload → uploads a *revised version of OUR draft* (retrofits bookmarks,
|
||||
becomes active_draft). NOT for the chair's final.
|
||||
• exports/{f}/mark-final → marks one of *our* exports as final.
|
||||
|
||||
This endpoint takes the EXTERNAL signed final, stores it canonically, enrolls it in
|
||||
the style corpus, and opens the draft↔final reconciliation pair (INV-LRN4) so the
|
||||
staged learning step can persist its analysis. It does NOT touch active_draft and
|
||||
does NOT run the LLM pipeline (run-learning / run-halacha do, on the local worker).
|
||||
"""
|
||||
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, "לא צוין שם קובץ")
|
||||
if Path(file.filename).suffix.lower() != ".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.find_case_dir(case_number) / "exports"
|
||||
export_dir.mkdir(parents=True, exist_ok=True)
|
||||
final_name = f"סופי-{case_number}.docx"
|
||||
final_path = export_dir / final_name
|
||||
final_path.write_bytes(content)
|
||||
|
||||
# Enroll in the style corpus. Use the FULL case_number as decision_number so a
|
||||
# בל"מ never collides with a same-numbered ערר already in the corpus (e.g. ARAR-25-8126).
|
||||
config.TRAINING_DIR.mkdir(parents=True, exist_ok=True)
|
||||
training_dest = config.TRAINING_DIR / f"החלטה-{case_number}.docx"
|
||||
shutil.copy2(str(final_path), str(training_dest))
|
||||
|
||||
# Extract the final text (word count for the UI; full text snapshotted into the pair).
|
||||
final_text = ""
|
||||
try:
|
||||
final_text, _pages, _ = await extractor.extract_text(str(final_path))
|
||||
except Exception as e:
|
||||
logger.warning("final text extraction failed for %s: %s", case_number, e)
|
||||
|
||||
# Case → 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"]),
|
||||
)
|
||||
|
||||
# INV-LRN4 — snapshot OUR draft now and open the pair (status=final_received).
|
||||
pair_id: str | None = None
|
||||
draft_words = 0
|
||||
try:
|
||||
decision = await db.get_decision_by_case(UUID(case["id"]))
|
||||
draft_text = ""
|
||||
if decision:
|
||||
async with pool.acquire() as conn:
|
||||
brows = await conn.fetch(
|
||||
"SELECT content FROM decision_blocks "
|
||||
"WHERE decision_id = $1 AND word_count > 0 ORDER BY block_index",
|
||||
UUID(decision["id"]),
|
||||
)
|
||||
draft_text = "\n\n".join(b["content"] for b in brows if b["content"])
|
||||
draft_words = len(draft_text.split())
|
||||
pair_id = await db.create_draft_final_pair(
|
||||
UUID(case["id"]), draft_text, str(final_path),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning("draft_final_pair snapshot failed for %s: %s", case_number, e)
|
||||
|
||||
case_dir = config.find_case_dir(case_number)
|
||||
if case_dir.exists():
|
||||
commit_and_push(case_dir, f"החלטה סופית של היו\"ר: {final_name}")
|
||||
|
||||
return {
|
||||
"final_filename": final_name,
|
||||
"training_copy": str(training_dest),
|
||||
"pair_id": pair_id,
|
||||
"draft_words": draft_words,
|
||||
"final_words": len(final_text.split()),
|
||||
"status": "final",
|
||||
}
|
||||
|
||||
|
||||
async def _wake_final_task(case_number: str, task: str) -> dict:
|
||||
"""Shared trigger for the staged learning / halacha steps — wakes the local curator."""
|
||||
case = await db.get_case_by_number(case_number)
|
||||
if not case:
|
||||
raise HTTPException(404, f"תיק {case_number} לא נמצא")
|
||||
final_name = f"סופי-{case_number}.docx"
|
||||
prefix = case_number[:1]
|
||||
company_id = (
|
||||
PAPERCLIP_COMPANIES["licensing"] if prefix == "1"
|
||||
else PAPERCLIP_COMPANIES["betterment"] if prefix in ("8", "9")
|
||||
else ""
|
||||
)
|
||||
try:
|
||||
return await pc_wake_curator_for_final(
|
||||
case_number, final_name, company_id=company_id, task=task,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning("final %s wakeup failed for %s: %s", task, case_number, e)
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
@app.post("/api/cases/{case_number}/final/run-learning")
|
||||
async def api_final_run_learning(case_number: str):
|
||||
"""Staged step 1 — voice learning: wake the local worker to run ingest_final_version
|
||||
(Opus distillation) + the 2-judge style panel (DeepSeek+Gemini)."""
|
||||
return await _wake_final_task(case_number, "learning")
|
||||
|
||||
|
||||
@app.post("/api/cases/{case_number}/final/run-halacha")
|
||||
async def api_final_run_halacha(case_number: str):
|
||||
"""Staged step 2 — halacha validation: wake the local worker to extract the cited
|
||||
halachot, build corroboration, and run the 3-judge halacha panel (--apply)."""
|
||||
return await _wake_final_task(case_number, "halacha")
|
||||
|
||||
|
||||
@app.post("/api/cases/{case_number}/export-docx")
|
||||
async def api_export_docx(case_number: str, background_tasks: BackgroundTasks):
|
||||
"""Trigger DOCX export for a case.
|
||||
|
||||
Reference in New Issue
Block a user