Edit document doc_type and appraiser side from the case UI
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m26s
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m26s
Until now changing a document's doc_type required a manual SQL update.
Adds an inline editor on the document badge so the chair can retag
without leaving the case page, and threads an appraiser_side tag
(committee / appellant / deciding) through the appraisal pipeline so
betterment-levy cases — which usually have 2-3 appraisers — render
conflicts with the deciding appraiser's view marked as governing.
Backend
- New appraiser_facts.appraiser_side column (V5.1) populated from
documents.metadata.appraiser_side at extraction time.
- extract_appraiser_facts now returns status='sides_missing' with the
list of untagged appraisals instead of running with empty side
labels — chair must tag every appraisal first via the UI.
- Conflict detection orders entries committee → appellant → deciding so
the deciding appraiser appears last; block-tet's prompt instructs the
writer to phrase the deciding appraiser's view as the governing
factual finding ("ואולם, השמאי המכריע קבע...").
- New PATCH /api/cases/{n}/documents/{doc_id} (Pydantic model with
whitelist validation) and matching document_update MCP tool. Both
merge appraiser_side into metadata JSONB instead of touching the
schema.
UI
- New shared doc-types module exports the canonical 11 doc_type
options plus the 3 appraiser-side options; both upload-sheet and
the document badge now read from it instead of duplicating Hebrew
labels.
- New DocumentTypeEditor renders a Popover off the doc-type Badge
with two Selects. The save button stays disabled while doc_type is
appraisal but no side has been picked, mirroring the backend
enforcement so the user finds out before triggering extraction.
- usePatchDocument React-Query mutation invalidates the case detail
on success so the badge updates without a manual refresh.
This commit is contained in:
65
web/app.py
65
web/app.py
@@ -2999,6 +2999,71 @@ async def api_reprocess_document(case_number: str, doc_id: str):
|
||||
return {"status": "reprocessing"}
|
||||
|
||||
|
||||
_ALLOWED_APPRAISER_SIDES = {"committee", "appellant", "deciding"}
|
||||
|
||||
|
||||
class DocumentPatchRequest(BaseModel):
|
||||
"""Patch payload for a single document. Both fields are optional."""
|
||||
|
||||
doc_type: str | None = None
|
||||
appraiser_side: str | None = None # committee | appellant | deciding | "" to clear
|
||||
|
||||
|
||||
@app.patch("/api/cases/{case_number}/documents/{doc_id}")
|
||||
async def api_patch_document(case_number: str, doc_id: str, req: DocumentPatchRequest):
|
||||
"""Update a document's tags. Currently supports doc_type and the
|
||||
metadata.appraiser_side flag (used by extract_appraiser_facts).
|
||||
|
||||
Returns the refreshed document row.
|
||||
"""
|
||||
case = await db.get_case_by_number(case_number)
|
||||
if not case:
|
||||
raise HTTPException(404, f"תיק {case_number} לא נמצא")
|
||||
|
||||
try:
|
||||
document_id = UUID(doc_id)
|
||||
except ValueError:
|
||||
raise HTTPException(400, f"doc_id לא תקין: {doc_id}")
|
||||
|
||||
doc = await db.get_document(document_id)
|
||||
if not doc or UUID(doc["case_id"]) != UUID(case["id"]):
|
||||
raise HTTPException(404, "מסמך לא נמצא בתיק")
|
||||
|
||||
updates: dict = {}
|
||||
|
||||
if req.doc_type is not None:
|
||||
if req.doc_type not in DOC_TYPE_NAMES:
|
||||
raise HTTPException(
|
||||
422,
|
||||
f"doc_type לא תקין: {req.doc_type}. ערכים מותרים: "
|
||||
f"{', '.join(sorted(DOC_TYPE_NAMES.keys()))}",
|
||||
)
|
||||
updates["doc_type"] = req.doc_type
|
||||
|
||||
if req.appraiser_side is not None:
|
||||
if req.appraiser_side and req.appraiser_side not in _ALLOWED_APPRAISER_SIDES:
|
||||
raise HTTPException(
|
||||
422,
|
||||
f"appraiser_side לא תקין: {req.appraiser_side}. ערכים מותרים: "
|
||||
f"{', '.join(sorted(_ALLOWED_APPRAISER_SIDES))}",
|
||||
)
|
||||
metadata = doc.get("metadata") or {}
|
||||
if isinstance(metadata, str):
|
||||
metadata = json.loads(metadata)
|
||||
if req.appraiser_side:
|
||||
metadata["appraiser_side"] = req.appraiser_side
|
||||
else:
|
||||
metadata.pop("appraiser_side", None)
|
||||
updates["metadata"] = metadata
|
||||
|
||||
if not updates:
|
||||
return {"status": "noop", "document": doc}
|
||||
|
||||
await db.update_document(document_id, **updates)
|
||||
fresh = await db.get_document(document_id)
|
||||
return {"status": "completed", "document": fresh}
|
||||
|
||||
|
||||
@app.delete("/api/cases/{case_number}/documents/{doc_id}")
|
||||
async def api_delete_document(case_number: str, doc_id: str):
|
||||
"""Delete a single document from a case (including its chunks and file)."""
|
||||
|
||||
Reference in New Issue
Block a user