feat(ui): אינדיקטור התקדמות לחילוץ מטא-דאטה + מתג-מקטעים בספריית הפסיקה
שתי בעיות UX בדף /precedents:
1. חילוץ מטא-דאטה לא נתן שום אינדיקציה שהוא רץ. בניגוד לחילוץ טקסט/הלכות
(extraction_status / halacha_extraction_status) למטא-דאטה היתה רק חותמת-זמן
metadata_extraction_requested_at — אין מצב "processing", לכן StatusPill לא
הציג כלום. נוספה עמודת metadata_extraction_status ('pending'|'processing'|
'completed'|'failed') במתכונת העמודות הקיימות, וה-worker
(process_pending_extractions + reextract_metadata) מעדכן אותה: processing
בתחילת פריט, completed בסיום (מנקה גם את החותמת), pending בכשל (לריטריי).
ה-UI מציג תג "מחלץ מטא-דאטה" + באנר מונה-אצווה עם אחוז התקדמות (high-water-mark
של עומק-התור) שמתעדכן אוטומטית דרך ה-polling הקיים (5ש').
2. שתי טבלאות מוערמות (בתי משפט / ועדות ערר) חייבו גלילה ארוכה. הוחלפו במתג-
מקטעים — טבלה אחת בכל פעם, עם שמירה על העמודות הייעודיות לכל סוג.
Invariants: G2 (מרחיב מנגנון-סטטוס קיים, לא מסלול מקביל), INV-TOOL4/GAP-45
(המשך חשיפת תור-החילוץ הסמוי). אין נגיעה בתוכן משפטי (G11).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -610,6 +610,11 @@ ALTER TABLE case_law ADD COLUMN IF NOT EXISTS document_id UUID REFERENCES docume
|
||||
ALTER TABLE case_law ADD COLUMN IF NOT EXISTS extraction_status TEXT DEFAULT 'pending';
|
||||
-- 'pending' | 'processing' | 'completed' | 'failed'
|
||||
ALTER TABLE case_law ADD COLUMN IF NOT EXISTS halacha_extraction_status TEXT DEFAULT 'pending';
|
||||
ALTER TABLE case_law ADD COLUMN IF NOT EXISTS metadata_extraction_status TEXT DEFAULT 'pending';
|
||||
-- 'pending' | 'processing' | 'completed' | 'failed'. Mirrors the
|
||||
-- text/halacha status columns so the UI can show a live badge while the
|
||||
-- local-MCP worker drains the metadata queue (previously only the
|
||||
-- metadata_extraction_requested_at timestamp existed — no 'processing').
|
||||
ALTER TABLE case_law ADD COLUMN IF NOT EXISTS practice_area TEXT DEFAULT '';
|
||||
ALTER TABLE case_law ADD COLUMN IF NOT EXISTS appeal_subtype TEXT DEFAULT '';
|
||||
ALTER TABLE case_law ADD COLUMN IF NOT EXISTS headnote TEXT DEFAULT '';
|
||||
@@ -3070,6 +3075,27 @@ async def set_case_law_halacha_status(case_law_id: UUID, status: str) -> None:
|
||||
)
|
||||
|
||||
|
||||
async def set_case_law_metadata_status(case_law_id: UUID, status: str) -> None:
|
||||
"""Set metadata-extraction status. Mirrors ``set_case_law_halacha_status``:
|
||||
on terminal states ('completed'/'failed') we also clear
|
||||
``metadata_extraction_requested_at`` so the local-MCP queue
|
||||
(`process_pending_extractions`, which scans ``WHERE *_requested_at IS NOT
|
||||
NULL``) stops re-picking the row and the UI's ``isPrecedentActive`` check
|
||||
settles."""
|
||||
pool = await get_pool()
|
||||
if status in ("completed", "failed"):
|
||||
await pool.execute(
|
||||
"UPDATE case_law SET metadata_extraction_status = $2, "
|
||||
"metadata_extraction_requested_at = NULL WHERE id = $1",
|
||||
case_law_id, status,
|
||||
)
|
||||
else:
|
||||
await pool.execute(
|
||||
"UPDATE case_law SET metadata_extraction_status = $2 WHERE id = $1",
|
||||
case_law_id, status,
|
||||
)
|
||||
|
||||
|
||||
async def list_external_case_law(
|
||||
practice_area: str = "",
|
||||
court: str = "",
|
||||
@@ -3126,6 +3152,7 @@ async def list_external_case_law(
|
||||
summary, headnote, subject_tags, source_kind,
|
||||
chair_name, district, citation_formatted,
|
||||
extraction_status, halacha_extraction_status,
|
||||
metadata_extraction_status,
|
||||
metadata_extraction_requested_at,
|
||||
halacha_extraction_requested_at,
|
||||
created_at,
|
||||
@@ -4274,8 +4301,12 @@ async def request_metadata_extraction(case_law_id: UUID) -> bool:
|
||||
fills empty fields), so this is safe.
|
||||
"""
|
||||
pool = await get_pool()
|
||||
# Reset the status to 'pending' alongside the timestamp so a re-request
|
||||
# after a prior 'completed'/'failed' run shows "בתור" again in the UI
|
||||
# instead of a stale terminal badge.
|
||||
result = await pool.execute(
|
||||
"UPDATE case_law SET metadata_extraction_requested_at = now() "
|
||||
"UPDATE case_law SET metadata_extraction_requested_at = now(), "
|
||||
"metadata_extraction_status = 'pending' "
|
||||
"WHERE id = $1",
|
||||
case_law_id,
|
||||
)
|
||||
|
||||
@@ -235,6 +235,12 @@ async def process_pending_extractions(kind: str = "metadata", limit: int = 20) -
|
||||
attempts = 0
|
||||
result: dict = {}
|
||||
try:
|
||||
# Flip to 'processing' so the UI badge shows live progress while
|
||||
# this row is being worked (metadata has no per-chunk status of
|
||||
# its own — this is the only signal). Halacha already sets its own
|
||||
# 'processing' inside the extractor.
|
||||
if kind == "metadata":
|
||||
await db.set_case_law_metadata_status(cid, "processing")
|
||||
result = await _run_once(cid)
|
||||
# Retry only on systematic extraction failure (rate-limit storm).
|
||||
# Don't retry on 'no_halachot' — that means Claude looked and
|
||||
@@ -259,9 +265,15 @@ async def process_pending_extractions(kind: str = "metadata", limit: int = 20) -
|
||||
# Finalise: success or terminal failure both clear the request
|
||||
# so the queue moves on. (Use 'failed' DB state for terminal
|
||||
# extraction_failed so the UI shows the warning chip.)
|
||||
if kind == "halacha" and result.get("status") == "extraction_failed":
|
||||
await db.set_case_law_halacha_status(cid, "failed")
|
||||
await db.clear_extraction_request(cid, kind=kind)
|
||||
if kind == "halacha":
|
||||
if result.get("status") == "extraction_failed":
|
||||
await db.set_case_law_halacha_status(cid, "failed")
|
||||
await db.clear_extraction_request(cid, kind=kind)
|
||||
else:
|
||||
# metadata — set terminal 'completed' status (also clears the
|
||||
# request timestamp) so the UI badge settles instead of
|
||||
# lingering on 'processing'.
|
||||
await db.set_case_law_metadata_status(cid, "completed")
|
||||
processed += 1
|
||||
results.append({
|
||||
"case_law_id": str(cid),
|
||||
@@ -273,6 +285,15 @@ async def process_pending_extractions(kind: str = "metadata", limit: int = 20) -
|
||||
})
|
||||
except Exception as e:
|
||||
logger.exception("process_pending_extractions failed for %s: %s", cid, e)
|
||||
# Don't clear the request — it stays for the next run. But for
|
||||
# metadata, revert the badge from 'processing' back to 'pending'
|
||||
# (the timestamp is preserved) so the row shows "בתור" rather than
|
||||
# a stuck "מחלץ" until the retry picks it up.
|
||||
if kind == "metadata":
|
||||
try:
|
||||
await db.set_case_law_metadata_status(cid, "pending")
|
||||
except Exception:
|
||||
logger.exception("failed to revert metadata status for %s", cid)
|
||||
results.append({
|
||||
"case_law_id": str(cid),
|
||||
"case_number": row.get("case_number", ""),
|
||||
@@ -280,7 +301,6 @@ async def process_pending_extractions(kind: str = "metadata", limit: int = 20) -
|
||||
"error": str(e),
|
||||
"retry_attempts": attempts,
|
||||
})
|
||||
# Don't clear the request — it stays for the next run.
|
||||
|
||||
return {
|
||||
"status": "completed",
|
||||
@@ -314,12 +334,18 @@ async def reextract_metadata(
|
||||
raise ValueError("precedent not found")
|
||||
# See note in db.request_metadata_extraction — opened to all source kinds.
|
||||
|
||||
# Mark 'processing' so a concurrent UI poll shows the live badge.
|
||||
await db.set_case_law_metadata_status(case_law_id, "processing")
|
||||
await progress("extracting_metadata", 40, "מחלץ מטא-דאטה (תקציר, תגיות)")
|
||||
result = await precedent_metadata_extractor.extract_and_apply(case_law_id)
|
||||
# Clear the queue timestamp so the UI / worker stop showing this row.
|
||||
# See note in reextract_halachot.
|
||||
# Settle to terminal 'completed' (also NULLs the queue timestamp) so the
|
||||
# UI / worker stop showing this row. See note in reextract_halachot.
|
||||
if result.get("status") in ("completed", "no_changes"):
|
||||
await db.clear_extraction_request(case_law_id, kind="metadata")
|
||||
await db.set_case_law_metadata_status(case_law_id, "completed")
|
||||
else:
|
||||
# e.g. 'no_metadata' (no full_text) — don't leave the badge stuck on
|
||||
# 'processing'; revert to 'pending' (preserves any queue timestamp).
|
||||
await db.set_case_law_metadata_status(case_law_id, "pending")
|
||||
fields = result.get("fields") or []
|
||||
msg = (
|
||||
f"מולאו {len(fields)} שדות: {', '.join(fields)}"
|
||||
|
||||
Reference in New Issue
Block a user