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:
@@ -491,8 +491,17 @@ CREATE TABLE IF NOT EXISTS appraiser_facts (
|
||||
CREATE INDEX IF NOT EXISTS idx_appraiser_facts_case ON appraiser_facts(case_id, fact_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_appraiser_facts_identifier ON appraiser_facts(case_id, identifier);
|
||||
|
||||
-- V5.1: appraiser_side — which party this appraiser represents.
|
||||
-- Values: 'committee' (הוועדה), 'appellant' (העורר), 'deciding' (מכריע).
|
||||
-- Required by extract_appraiser_facts; the chair tags it via the UI before extraction.
|
||||
-- Set via documents.metadata.appraiser_side at upload/edit time, then propagated here
|
||||
-- so that conflict rendering in block-tet can label each entry with its side.
|
||||
ALTER TABLE appraiser_facts ADD COLUMN IF NOT EXISTS appraiser_side TEXT DEFAULT '';
|
||||
CREATE INDEX IF NOT EXISTS idx_appraiser_facts_side ON appraiser_facts(case_id, appraiser_side);
|
||||
|
||||
-- documents.metadata.is_post_hearing: flag for materials submitted after the hearing
|
||||
-- (השלמות טיעון, הצעות פשרה). Used by block-chet to include them in the proceedings narrative.
|
||||
-- documents.metadata.appraiser_side: which side the appraiser represents (see above).
|
||||
-- No schema change needed — uses existing JSONB metadata column.
|
||||
"""
|
||||
|
||||
@@ -1333,7 +1342,7 @@ async def replace_appraiser_facts(
|
||||
) -> int:
|
||||
"""Replace all appraiser_facts for a given document.
|
||||
|
||||
Each fact dict: appraiser_name, fact_type ('plan'|'permit'),
|
||||
Each fact dict: appraiser_name, appraiser_side, fact_type ('plan'|'permit'),
|
||||
identifier, details (dict), page_number (optional).
|
||||
"""
|
||||
pool = await get_pool()
|
||||
@@ -1345,11 +1354,12 @@ async def replace_appraiser_facts(
|
||||
for f in facts:
|
||||
await conn.execute(
|
||||
"""INSERT INTO appraiser_facts
|
||||
(case_id, document_id, appraiser_name, fact_type,
|
||||
identifier, details, page_number)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)""",
|
||||
(case_id, document_id, appraiser_name, appraiser_side,
|
||||
fact_type, identifier, details, page_number)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)""",
|
||||
case_id, document_id,
|
||||
f["appraiser_name"],
|
||||
f.get("appraiser_side", ""),
|
||||
f["fact_type"],
|
||||
f["identifier"],
|
||||
json.dumps(f.get("details", {}), ensure_ascii=False),
|
||||
@@ -1396,7 +1406,9 @@ async def detect_appraiser_conflicts(case_id: UUID) -> list[dict]:
|
||||
|
||||
A conflict exists when the SAME identifier (e.g., "תמ"א 38") was reported
|
||||
differently by two appraisers — different details, or one cited it and the
|
||||
other did not. Returns list of conflict groups.
|
||||
other did not. Returns list of conflict groups. Each entry in a group
|
||||
carries the appraiser's side so the caller can label it as committee /
|
||||
appellant / deciding.
|
||||
"""
|
||||
pool = await get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
@@ -1404,10 +1416,19 @@ async def detect_appraiser_conflicts(case_id: UUID) -> list[dict]:
|
||||
"""SELECT identifier, fact_type,
|
||||
json_agg(jsonb_build_object(
|
||||
'appraiser_name', appraiser_name,
|
||||
'appraiser_side', appraiser_side,
|
||||
'details', details,
|
||||
'page_number', page_number,
|
||||
'document_id', document_id
|
||||
) ORDER BY appraiser_name) AS entries,
|
||||
) ORDER BY
|
||||
CASE appraiser_side
|
||||
WHEN 'committee' THEN 1
|
||||
WHEN 'appellant' THEN 2
|
||||
WHEN 'deciding' THEN 3
|
||||
ELSE 4
|
||||
END,
|
||||
appraiser_name
|
||||
) AS entries,
|
||||
COUNT(DISTINCT appraiser_name) AS n_appraisers
|
||||
FROM appraiser_facts
|
||||
WHERE case_id = $1
|
||||
|
||||
Reference in New Issue
Block a user