feat(case-ui): חלוקת רשימת-המסמכים בטאב-הסקירה ל-4 קבוצות
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 36s
Lint — undefined names / undefined-names (pull_request) Successful in 10s

חלוקה פנימית (לנוחות בלבד, לא משנה doc_type) של רשימת מסמכי-התיק:
1. עיקריים (appeal/response/appraisal) · 2. נלווים · 3. פרוטוקול ועדת הערר ·
4. לאחר הדיון. הסיווג נגזר בצד-הלקוח מ-doc_type + שני דגלי-metadata.

שני פקדים חדשים בעורך-התיוג:
- מתג "התקבל אחרי הדיון" → metadata.is_post_hearing (כבר נצרך ע"י בלוק-ח);
  גובר על הסיווג-לפי-סוג ומעביר לקבוצה 4.
- בורר "פרוטוקול של: ועדת הערר / ועדה מקומית-מחוזית" → metadata.protocol_scope
  (נראה רק כשהסוג protocol); ברירת-מחדל appeal→קבוצה 3, lower→קבוצה 2.

Backend: PATCH /documents/{id} + MCP document_update מקבלים is_post_hearing
ו-protocol_scope, נשמרים ב-metadata JSONB (אותו מסלול כמו appraiser_side).

עיצוב אושר ב-Claude Design (X17): 18k-case-documents-grouped.html.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-05 06:52:17 +00:00
parent bc0a4bb895
commit cc8af903b7
6 changed files with 304 additions and 27 deletions

View File

@@ -5722,13 +5722,21 @@ async def api_reprocess_document(case_number: str, doc_id: str):
_ALLOWED_APPRAISER_SIDES = {"committee", "appellant", "deciding"}
# metadata.protocol_scope — only meaningful when doc_type == "protocol".
# "appeal" = ועדת הערר (default when absent), "lower" = ועדה מקומית/מחוזית.
# Used by the overview panel to sort a protocol into group 3 vs group 2.
_ALLOWED_PROTOCOL_SCOPES = {"appeal", "lower"}
class DocumentPatchRequest(BaseModel):
"""Patch payload for a single document. Both fields are optional."""
"""Patch payload for a single document. All fields are optional; only the
ones present are applied. The metadata.* flags are display/processing hints
that never change the stored doc_type."""
doc_type: str | None = None
appraiser_side: str | None = None # committee | appellant | deciding | "" to clear
is_post_hearing: bool | None = None # material submitted after the hearing (→ group 4, block-chet)
protocol_scope: str | None = None # appeal | lower | "" to clear (protocol docs only)
@app.patch("/api/cases/{case_number}/documents/{doc_id}")
@@ -5753,6 +5761,13 @@ async def api_patch_document(case_number: str, doc_id: str, req: DocumentPatchRe
updates: dict = {}
# Load metadata once — appraiser_side, is_post_hearing and protocol_scope
# all live in the same JSONB blob, so a single patch may touch several.
metadata = doc.get("metadata") or {}
if isinstance(metadata, str):
metadata = json.loads(metadata)
metadata_dirty = False
if req.doc_type is not None:
if req.doc_type not in DOC_TYPE_NAMES:
raise HTTPException(
@@ -5769,13 +5784,34 @@ async def api_patch_document(case_number: str, doc_id: str, req: DocumentPatchRe
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)
metadata_dirty = True
if req.is_post_hearing is not None:
# Store True; drop the key entirely when False so absence == "before".
if req.is_post_hearing:
metadata["is_post_hearing"] = True
else:
metadata.pop("is_post_hearing", None)
metadata_dirty = True
if req.protocol_scope is not None:
if req.protocol_scope and req.protocol_scope not in _ALLOWED_PROTOCOL_SCOPES:
raise HTTPException(
422,
f"protocol_scope לא תקין: {req.protocol_scope}. ערכים מותרים: "
f"{', '.join(sorted(_ALLOWED_PROTOCOL_SCOPES))}",
)
if req.protocol_scope:
metadata["protocol_scope"] = req.protocol_scope
else:
metadata.pop("protocol_scope", None)
metadata_dirty = True
if metadata_dirty:
updates["metadata"] = metadata
if not updates: