diff --git a/mcp-server/src/legal_mcp/services/citation_extractor.py b/mcp-server/src/legal_mcp/services/citation_extractor.py index 323f5d8..0368a93 100644 --- a/mcp-server/src/legal_mcp/services/citation_extractor.py +++ b/mcp-server/src/legal_mcp/services/citation_extractor.py @@ -404,7 +404,10 @@ async def list_citations_to_case_law(case_law_id: UUID) -> list[dict]: cl.case_number AS source_case_number, cl.case_name AS source_case_name, cl.chair_name AS source_chair_name, - cl.district AS source_district + cl.district AS source_district, + cl.precedent_level AS source_precedent_level, + cl.court AS source_court, + cl.date AS source_date FROM precedent_internal_citations pic JOIN case_law cl ON cl.id = pic.source_case_law_id WHERE pic.cited_case_law_id = $1 diff --git a/mcp-server/src/legal_mcp/services/precedent_library.py b/mcp-server/src/legal_mcp/services/precedent_library.py index d153000..8fca14a 100644 --- a/mcp-server/src/legal_mcp/services/precedent_library.py +++ b/mcp-server/src/legal_mcp/services/precedent_library.py @@ -411,6 +411,28 @@ async def get_precedent(case_law_id: UUID | str) -> dict | None: return None record["halachot"] = await db.list_halachot(case_law_id=case_law_id, limit=500) record["related_cases"] = await db.get_case_law_relations(case_law_id) + # Auto-detected citation graph: which decisions cite THIS ruling (incoming + # edges in precedent_internal_citations). Reuses the canonical query (G2) — + # no parallel resolution path. Shaped to match the front-end RelatedCase row + # so the existing "ציטוטים מקושרים" panel renders them with no visual change. + from legal_mcp.services import citation_extractor as _ce + raw = await _ce.list_citations_to_case_law(case_law_id) + record["incoming_citations"] = [ + { + "id": r["source_case_law_id"], + "case_number": r.get("source_case_number") or r.get("cited_case_number") or "", + "case_name": r.get("source_case_name") or "", + "court": r.get("source_court") or "", + "precedent_level": r.get("source_precedent_level") or "", + "chair_name": r.get("source_chair_name") or "", + "date": ( + r["source_date"].isoformat() + if r.get("source_date") is not None else None + ), + "confidence": r.get("confidence"), + } + for r in raw + ] return record diff --git a/web-ui/src/app/precedents/[id]/page.tsx b/web-ui/src/app/precedents/[id]/page.tsx index 768932e..8c12d4d 100644 --- a/web-ui/src/app/precedents/[id]/page.tsx +++ b/web-ui/src/app/precedents/[id]/page.tsx @@ -236,7 +236,11 @@ export default function PrecedentDetailPage({ {/* side rail — citations + corroboration */}
- +
diff --git a/web-ui/src/components/precedents/link-related-dialog.tsx b/web-ui/src/components/precedents/link-related-dialog.tsx index a123f27..911752c 100644 --- a/web-ui/src/components/precedents/link-related-dialog.tsx +++ b/web-ui/src/components/precedents/link-related-dialog.tsx @@ -18,6 +18,7 @@ import { useLinkRelatedCase, useUnlinkRelatedCase, RelatedCase, + IncomingCitation, } from "@/lib/api/precedent-library"; const LEVEL_LABELS: Record = { @@ -133,19 +134,28 @@ function LinkDialog({ caseId, currentRelated, open, onOpenChange }: DialogProps) type SectionProps = { caseId: string; related: RelatedCase[]; + /** Decisions that cite THIS ruling — auto-detected from the citation graph + * (read-only). Optional so existing call-sites stay valid. */ + incoming?: IncomingCitation[]; }; /* Rail-styled citations card (mockup 08 side rail). Renders linked related * decisions as a navy-headed card with arrow-prefixed rows; keeps the full - * link/unlink logic. Used in the precedent-detail side rail. */ -export function RelatedCasesSection({ caseId, related }: SectionProps) { + * link/unlink logic. Auto-detected incoming citations (decisions that cite this + * ruling) render in the SAME row style, read-only (no unlink). Used in the + * precedent-detail side rail. */ +export function RelatedCasesSection({ caseId, related, incoming = [] }: SectionProps) { const [dialogOpen, setDialogOpen] = useState(false); + // De-dupe: a decision already linked manually shouldn't appear twice. + const manualIds = new Set(related.map((r) => r.id)); + const autoCites = incoming.filter((c) => !manualIds.has(c.id)); + const total = related.length + autoCites.length; return (

- ציטוטים מקושרים{related.length > 0 ? ` (${related.length})` : ""} + ציטוטים מקושרים{total > 0 ? ` (${total})` : ""}

- {related.length === 0 ? ( + {total === 0 ? (

אין החלטות קשורות עדיין

) : (