feat(halachot): חיפוש/איתור בתור-ההלכות + הערת חלון-תצוגה (פסיקה מחוץ ל-500 נגישה)
תור-ההלכות שלף רק 500 ממתינות בעלות-עדיפות מתוך ~1,372, בלי שום דרך לאתר פס"ד מסוים. הלכה מדורגת מתחת לחלון (למשל 1180-11-25, מקומות 921/1305) פשוט נעלמה — הספירה בספרייה הציגה "2 ממתינות" אך התור לא הראה אותן. - list_halachot: פרמטר search (case_number/case_name/rule_statement, ILIKE) — סינון בצד-השרת כך שפריט מתחת לחלון נשאר נגיש. מרחיב את מסלול-השליפה היחיד, לא יוצר מסלול מקביל (G2). - count_halachot: ספירה מלאה לאותו פילטר (ל-"N מתוך TOTAL"). - /api/halachot: search + with_total (ברירת-מחדל off; קוראים קיימים לא מושפעים). - UI (תור-הלכות): שורת-חיפוש מהוקצבת (debounce 300ms), הערת "חלון התצוגה" (500 מתוך הסך), ושורת-הקשר-תוצאה עם ניקוי. בחיפוש — כל הקבוצות התואמות נפתחות; הניווט המקלדתי והשערים (G10) ללא שינוי. מאומת מול ה-DB: search='1180-11-25' → 2 הממתינות (index 20 נקייה→להכרעתך, index 23 nli_unsupported→דורש תיקון-חילוץ); count=2. עיצוב: עבר שער Claude Design (X17, כרטיס 19-halacha-queue-unified) ואושר. Invariants: מקיים G2 (מסלול-שליפה יחיד), INV-G10 (שער-יו"ר ללא שינוי), INV-IA (מקור-אמת יחיד לתור). ללא בליעת-שגיאות. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5284,6 +5284,7 @@ async def list_halachot(
|
||||
cluster: bool = False,
|
||||
include_equivalents: bool = False,
|
||||
include_panel_round: bool = False,
|
||||
search: str | None = None,
|
||||
) -> list[dict]:
|
||||
"""List halachot with optional triage controls (#84).
|
||||
|
||||
@@ -5291,6 +5292,10 @@ async def list_halachot(
|
||||
truncated_quote / quote_unverified / non_decision / thin_restatement /
|
||||
nli_unsupported / near_duplicate). These belong in a 'needs extraction
|
||||
fix' bucket, not the chair's approve queue (#84.1).
|
||||
search — free-text locate within the queue (case_number / case_name /
|
||||
rule_statement, case-insensitive). Filters server-side so a pending
|
||||
halacha ranked below the display window is still reachable; without it
|
||||
the queue only ever shows the top ``limit`` by priority.
|
||||
order_by_priority — replace FIFO with an active-learning order (#84.3, #133/FU-3):
|
||||
panel-disagreement first (the panel SPLIT, then ran INCOMPLETE — the
|
||||
labels of highest learning value: the chair's call resolves a genuine
|
||||
@@ -5320,6 +5325,14 @@ async def list_halachot(
|
||||
if exclude_low_quality:
|
||||
# a clean item has an empty/NULL quality_flags array
|
||||
conditions.append("COALESCE(array_length(h.quality_flags, 1), 0) = 0")
|
||||
if search and search.strip():
|
||||
# locate by decision identity OR rule text — server-side so an item
|
||||
# ranked below the display window is still reachable (no client-only filter).
|
||||
conditions.append(
|
||||
f"(cl.case_number ILIKE ${idx} OR cl.case_name ILIKE ${idx} "
|
||||
f"OR h.rule_statement ILIKE ${idx})")
|
||||
params.append(f"%{search.strip()}%")
|
||||
idx += 1
|
||||
where_sql = f"WHERE {' AND '.join(conditions)}" if conditions else ""
|
||||
# #133/FU-3: rank the panel's latest verdict so splits/incompletes — the
|
||||
# highest-value active-learning labels — float to the top of the queue.
|
||||
@@ -5387,6 +5400,48 @@ async def list_halachot(
|
||||
return out
|
||||
|
||||
|
||||
async def count_halachot(
|
||||
review_status: str | None = None,
|
||||
practice_area: str | None = None,
|
||||
exclude_low_quality: bool = False,
|
||||
search: str | None = None,
|
||||
) -> int:
|
||||
"""Full count for the same filter ``list_halachot`` uses (sans limit/offset).
|
||||
|
||||
Powers the queue's "showing N of TOTAL" note so the chair knows how many
|
||||
pending items sit beyond the display window. Mirrors the WHERE exactly —
|
||||
one source of truth, no parallel counting logic.
|
||||
"""
|
||||
pool = await get_pool()
|
||||
conditions = []
|
||||
params: list = []
|
||||
idx = 1
|
||||
if review_status:
|
||||
conditions.append(f"h.review_status = ${idx}")
|
||||
params.append(review_status)
|
||||
idx += 1
|
||||
if practice_area:
|
||||
conditions.append(f"${idx} = ANY(h.practice_areas)")
|
||||
params.append(practice_area)
|
||||
idx += 1
|
||||
if exclude_low_quality:
|
||||
conditions.append("COALESCE(array_length(h.quality_flags, 1), 0) = 0")
|
||||
if search and search.strip():
|
||||
conditions.append(
|
||||
f"(cl.case_number ILIKE ${idx} OR cl.case_name ILIKE ${idx} "
|
||||
f"OR h.rule_statement ILIKE ${idx})")
|
||||
params.append(f"%{search.strip()}%")
|
||||
idx += 1
|
||||
where_sql = f"WHERE {' AND '.join(conditions)}" if conditions else ""
|
||||
sql = f"""
|
||||
SELECT count(*)
|
||||
FROM halachot h
|
||||
LEFT JOIN case_law cl ON cl.id = h.case_law_id
|
||||
{where_sql}
|
||||
"""
|
||||
return await pool.fetchval(sql, *params) or 0
|
||||
|
||||
|
||||
async def _annotate_panel_rounds(pool, out: list[dict]) -> None:
|
||||
"""Attach the LATEST 3-judge panel round to each row (#133/FU-2), display-only.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user