diff --git a/mcp-server/src/legal_mcp/services/db.py b/mcp-server/src/legal_mcp/services/db.py index e86248d..a6d5528 100644 --- a/mcp-server/src/legal_mcp/services/db.py +++ b/mcp-server/src/legal_mcp/services/db.py @@ -5494,12 +5494,15 @@ async def list_halachot( h.cites, h.confidence, h.quote_verified, h.quality_flags, h.review_status, h.reviewer, h.reviewed_at, h.created_at, h.updated_at, + h.canonical_id, h.instance_type, + ch.canonical_statement, ch.instance_count, cl.case_number, cl.case_name, cl.court, cl.date AS decision_date, cl.precedent_level, COALESCE(cor.corroboration_count, 0)::int AS corroboration_count, COALESCE(cor.corroboration_negative, false) AS corroboration_negative, pr.verdict AS panel_verdict FROM halachot h + LEFT JOIN canonical_halachot ch ON ch.id = h.canonical_id LEFT JOIN case_law cl ON cl.id = h.case_law_id LEFT JOIN ( SELECT halacha_id, @@ -6144,6 +6147,21 @@ async def update_canonical_statement( return result.split()[-1] != "0" +async def list_canonical_instances(canonical_id: "UUID") -> list[dict]: + """List all halachot (instances) sharing a canonical_id — used by the UI accordion.""" + pool = await get_pool() + rows = await pool.fetch( + """SELECT h.id, h.instance_type, h.confidence, h.rule_statement, + cl.case_number, cl.case_name + FROM halachot h + LEFT JOIN case_law cl ON cl.id = h.case_law_id + WHERE h.canonical_id = $1 + ORDER BY h.instance_type, cl.case_number""", + canonical_id, + ) + return [dict(r) for r in rows] + + async def _annotate_equivalents(pool, out: list[dict]) -> None: """Attach an `equivalents` list to each row (#84.2) — parallel-authority links. diff --git a/web-ui/src/components/precedents/halacha-review-panel.tsx b/web-ui/src/components/precedents/halacha-review-panel.tsx index 331f4a3..c4aa39c 100644 --- a/web-ui/src/components/precedents/halacha-review-panel.tsx +++ b/web-ui/src/components/precedents/halacha-review-panel.tsx @@ -11,7 +11,8 @@ import { CorroborationBadge } from "./corroboration-badge"; import { practiceAreaLabel } from "./practice-area"; import { useHalachotPending, useHalachotByStatus, useUpdateHalacha, useBatchReviewHalachot, - useLibraryStats, isExtractionFixItem, type Halacha, + useLibraryStats, isExtractionFixItem, useCanonicalInstances, + type Halacha, type CanonicalInstance, } from "@/lib/api/precedent-library"; import { AuthorityBadge, ruleTypeLabel } from "./halacha-meta"; @@ -135,6 +136,131 @@ function PanelDeliberation({ round }: { round: NonNullable = { + original: "עיקרון מקורי", + citation: "ציטוט", + application: "יישום", +}; +const INSTANCE_TYPE_CLS: Record = { + original: "bg-navy text-parchment", + citation: "bg-info text-white", + application: "bg-ink-muted text-white", +}; + +function CanonicalSection({ + h, onSaveCanonical, +}: { + h: Halacha; + onSaveCanonical: (stmt: string) => Promise; +}) { + const [editingCanon, setEditingCanon] = useState(false); + const [canonDraft, setCanonDraft] = useState(h.canonical_statement ?? ""); + const [showInstances, setShowInstances] = useState(false); + const { data: instances, isLoading: instLoading } = useCanonicalInstances( + showInstances ? h.canonical_id : null, + ); + + useEffect(() => { + // eslint-disable-next-line react-hooks/set-state-in-effect + setCanonDraft(h.canonical_statement ?? ""); + }, [h.canonical_id, h.canonical_statement]); + + const handleSave = async () => { + await onSaveCanonical(canonDraft); + setEditingCanon(false); + }; + + const instanceCount = h.instance_count ?? 1; + + return ( +
+
+ כ + ניסוח קנוני — העיקרון הרחב (V41) + + {instanceCount > 1 ? `מאחד ${instanceCount} פסיקות` : "instance יחיד"} + {h.review_status && ` · ${h.review_status}`} + +
+ + {editingCanon ? ( + <> +