From 0c78e30e07a172e24fa7dcd3110681d073fa0945 Mon Sep 17 00:00:00 2001 From: Chaim Date: Tue, 16 Jun 2026 08:48:10 +0000 Subject: [PATCH] =?UTF-8?q?fix(halachot):=20=D7=A1=D7=A4=D7=99=D7=A8=D7=AA?= =?UTF-8?q?-=D7=AA=D7=95=D7=A8=20=D7=90=D7=9E=D7=99=D7=AA=D7=99=D7=AA=20+?= =?UTF-8?q?=20=D7=A2=D7=93=D7=9B=D7=95=D7=9F-=D7=97=D7=99=20=D7=91=D7=AA?= =?UTF-8?q?=D7=92=D7=99-=D7=94=D7=9B=D7=A8=D7=A2=D7=94=20(#6/#7/#8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit המספרים בתגי תור-ההלכות היו תקרות-שאילתה ולא ספירה אמיתית: - "ממתינות 500" = pendingData.items.length עם limit=500 - "נדחו 1000 / אושרו 1000" = useHalachotByStatus(...,1000) — תקרה 1000 ובלי refetchInterval התגים התעדכנו רק בכניסה לדף. המקור האמיתי כבר קיים: /api/precedent-library/stats מריץ COUNT(*) אמיתי (pending=1373, approved=2100). מוסיף לו halachot_rejected + halachot_deferred, מחבר את תגי-ה-HalachaReviewPanel למקור הזה, ומוסיף polling (30s) כדי שהם יתעדכנו חי. מסיר את useHalachaCount המיותר. תור-העבודה עצמו עדיין נטען עד 500 פריטים (cap-עבודה לגיטימי); רק תצוגת הספירות תוקנה להציג את הסך-האמיתי. Invariants: מקיים G1 (נרמול-במקור — ספירה אמיתית מ-COUNT(*) במקום len(rows) מתוקרת בקריאה) ו-G2 (מאחד על מקור-הספירה הקיים, ללא endpoint-ספירה מקביל). Co-Authored-By: Claude Opus 4.8 (1M context) --- mcp-server/src/legal_mcp/services/db.py | 8 +++++++ .../precedents/halacha-review-panel.tsx | 23 +++++++------------ web-ui/src/lib/api/precedent-library.ts | 8 ++++++- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/mcp-server/src/legal_mcp/services/db.py b/mcp-server/src/legal_mcp/services/db.py index c0eecca..238490b 100644 --- a/mcp-server/src/legal_mcp/services/db.py +++ b/mcp-server/src/legal_mcp/services/db.py @@ -6719,6 +6719,12 @@ async def precedent_library_stats() -> dict: halachot_approved = await conn.fetchval( "SELECT COUNT(*) FROM halachot WHERE review_status IN ('approved', 'published')" ) + halachot_rejected = await conn.fetchval( + "SELECT COUNT(*) FROM halachot WHERE review_status = 'rejected'" + ) + halachot_deferred = await conn.fetchval( + "SELECT COUNT(*) FROM halachot WHERE review_status = 'deferred'" + ) return { "precedents_total": int(total or 0), "by_practice_area": [ @@ -6732,6 +6738,8 @@ async def precedent_library_stats() -> dict: "halachot_total": int(halachot_total or 0), "halachot_pending": int(halachot_pending or 0), "halachot_approved": int(halachot_approved or 0), + "halachot_rejected": int(halachot_rejected or 0), + "halachot_deferred": int(halachot_deferred or 0), } diff --git a/web-ui/src/components/precedents/halacha-review-panel.tsx b/web-ui/src/components/precedents/halacha-review-panel.tsx index c520c98..fb715e1 100644 --- a/web-ui/src/components/precedents/halacha-review-panel.tsx +++ b/web-ui/src/components/precedents/halacha-review-panel.tsx @@ -11,7 +11,7 @@ import { CorroborationBadge } from "./corroboration-badge"; import { practiceAreaLabel } from "./practice-area"; import { useHalachotPending, useHalachotByStatus, useUpdateHalacha, useBatchReviewHalachot, - isExtractionFixItem, type Halacha, + useLibraryStats, isExtractionFixItem, type Halacha, } from "@/lib/api/precedent-library"; import { AuthorityBadge, ruleTypeLabel } from "./halacha-meta"; @@ -979,13 +979,6 @@ function PendingPanel() { ); } -// ─── Count badge for tabs ───────────────────────────────────────────────────── - -function useHalachaCount(status: string) { - const { data } = useHalachotByStatus(status, 1000); - return data?.count ?? data?.items.length ?? null; -} - // ─── Main export ────────────────────────────────────────────────────────────── type Tab = "pending" | "rejected" | "approved"; @@ -998,16 +991,16 @@ const TAB_LABELS: Record = { export function HalachaReviewPanel() { const [tab, setTab] = useState("pending"); - const { data: pendingData } = useHalachotPending({ limit: 500 }); - const rejectedCount = useHalachaCount("rejected"); - const approvedCount = useHalachaCount("approved"); - const pendingCount = pendingData?.items.length ?? null; + // Real COUNT(*) totals from the stats endpoint — not the limit-capped + // /api/halachot len(rows), which pinned these chips at 500/1000/1000 and only + // refreshed on navigation. The stats query polls, so the chips track live (#6/#7/#8). + const { data: stats } = useLibraryStats(); const counts: Record = { - pending: pendingCount, - rejected: rejectedCount, - approved: approvedCount, + pending: stats?.halachot_pending ?? null, + rejected: stats?.halachot_rejected ?? null, + approved: stats?.halachot_approved ?? null, }; return ( diff --git a/web-ui/src/lib/api/precedent-library.ts b/web-ui/src/lib/api/precedent-library.ts index 089cdce..ab5a8bd 100644 --- a/web-ui/src/lib/api/precedent-library.ts +++ b/web-ui/src/lib/api/precedent-library.ts @@ -188,6 +188,8 @@ export type LibraryStats = { halachot_total: number; halachot_pending: number; halachot_approved: number; + halachot_rejected: number; + halachot_deferred: number; }; export type ListFilters = { @@ -301,7 +303,11 @@ export function useLibraryStats() { queryKey: libraryKeys.stats(), queryFn: ({ signal }) => apiRequest("/api/precedent-library/stats", { signal }), - staleTime: 60_000, + staleTime: 30_000, + // The halacha-review tab counts ride on this (real COUNT(*) — not the + // limit-capped /api/halachot len), so poll so the chips track approvals + // live instead of only refreshing on navigation (#6/#7/#8). + refetchInterval: 30_000, }); }