feat(halacha): review-queue triage — defer + batch group actions + quality-flag badges (#84)
Make the chair's pending-halacha review faster and less exhausting. Backend: - New 'deferred' review_status (snooze): stays out of the active library AND out of the default pending queue, without the finality of 'rejected'. update_halacha stamps reviewer+reviewed_at on defer; HALACHA_REVIEW_STATUSES is the single source of valid statuses (PATCH validation now uses it). - db.update_halachot_batch(ids, status, reviewer) — one atomic UPDATE for a whole group; invalid status / empty ids are a no-op. - POST /api/halachot/batch (HalachaBatchReviewRequest) wraps it. - update_halacha now RETURNs quality_flags too (parity with list_halachot). Frontend (halacha-review-panel): - Quality-flag badges (#81: non_decision / truncated_quote / thin_restatement / quote_unverified) so the chair sees WHY an item was held back. - Defer action — button + keyboard 'D' — to snooze without rejecting (fixes the 'leave in pending forever' anti-pattern; reject stays the junk verb). - Per-precedent batch bar: 'אשר הכל' / 'דחה הכל' via useBatchReviewHalachot (one request, one refetch) with confirm guards. - Halacha/HalachaPatch types gain quality_flags + 'deferred'. Verified: mcp-server suite 156 passed; web build green; end-to-end integration against dev DB (batch approve/reject, defer sets status+timestamp, pending excludes approved+deferred, deferred queryable, invalid status no-op). Note: api:types regen deferred until deploy (the batch hook is hand-typed, not dependent on generated types). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -659,7 +659,7 @@ CREATE TABLE IF NOT EXISTS halachot (
|
||||
confidence NUMERIC(3,2) DEFAULT 0.0,
|
||||
quote_verified BOOLEAN DEFAULT FALSE,
|
||||
review_status TEXT DEFAULT 'pending_review',
|
||||
-- pending_review | approved | rejected | published
|
||||
-- pending_review | approved | rejected | published | deferred (#84 snooze)
|
||||
reviewer TEXT DEFAULT '',
|
||||
reviewed_at TIMESTAMPTZ,
|
||||
quality_flags TEXT[] DEFAULT '{}',
|
||||
@@ -3520,7 +3520,7 @@ async def update_halacha(
|
||||
set_parts.append(f"review_status = ${idx}")
|
||||
params.append(review_status)
|
||||
idx += 1
|
||||
if review_status in ("approved", "rejected", "published"):
|
||||
if review_status in ("approved", "rejected", "published", "deferred"):
|
||||
set_parts.append(f"reviewed_at = now()")
|
||||
set_parts.append(f"reviewer = ${idx}")
|
||||
params.append(reviewer)
|
||||
@@ -3552,13 +3552,50 @@ async def update_halacha(
|
||||
RETURNING id, case_law_id, halacha_index, rule_statement, rule_type,
|
||||
reasoning_summary, supporting_quote, page_reference,
|
||||
practice_areas, subject_tags, cites, confidence,
|
||||
quote_verified, review_status, reviewer, reviewed_at,
|
||||
created_at, updated_at
|
||||
quote_verified, quality_flags, review_status, reviewer,
|
||||
reviewed_at, created_at, updated_at
|
||||
"""
|
||||
row = await pool.fetchrow(sql, *params)
|
||||
return dict(row) if row else None
|
||||
|
||||
|
||||
# Statuses the chair can set via review (batch or single). 'deferred' = snooze:
|
||||
# stays out of the active library AND out of the default pending queue, without
|
||||
# the finality of 'rejected'. #84 review-queue triage.
|
||||
HALACHA_REVIEW_STATUSES = {
|
||||
"pending_review", "approved", "rejected", "published", "deferred",
|
||||
}
|
||||
|
||||
|
||||
async def update_halachot_batch(
|
||||
halacha_ids: list[str], review_status: str, reviewer: str = "",
|
||||
) -> int:
|
||||
"""Bulk-set review_status for many halachot in one atomic statement.
|
||||
|
||||
Powers the #84 "approve/reject/defer the whole group" action — one request,
|
||||
one transaction, one refetch (vs N PATCH round-trips). Only the status +
|
||||
reviewer + reviewed_at are touched (no content edits in batch). Returns the
|
||||
number of rows updated.
|
||||
"""
|
||||
if not halacha_ids or review_status not in HALACHA_REVIEW_STATUSES:
|
||||
return 0
|
||||
ids = [UUID(str(i)) for i in halacha_ids]
|
||||
stamp = review_status in ("approved", "rejected", "published", "deferred")
|
||||
pool = await get_pool()
|
||||
result = await pool.execute(
|
||||
f"""UPDATE halachot
|
||||
SET review_status = $2,
|
||||
updated_at = now()
|
||||
{", reviewed_at = now(), reviewer = $3" if stamp else ""}
|
||||
WHERE id = ANY($1::uuid[])""",
|
||||
ids, review_status, *( [reviewer] if stamp else [] ),
|
||||
)
|
||||
try:
|
||||
return int(result.split()[-1])
|
||||
except (ValueError, IndexError):
|
||||
return 0
|
||||
|
||||
|
||||
async def approve_halacha_by_corroboration(
|
||||
halacha_id: UUID, n_sources: int, min_cites: int,
|
||||
) -> bool:
|
||||
|
||||
Reference in New Issue
Block a user