feat(cases): תצוגת "פסיקה שצוטטה בהחלטה" בעמוד-התיק + שחזור חיווט-הרמס

UI שביקש חיים: בכניסה להחלטה רואים את הפסיקה שצוטטה בתוכה — מקושרת לספרייה
(קליק → /precedents/[id]) מול חסרה (סומנה אוטומטית להעלאה).

- web/app.py: GET /api/cases/{case}/citations — מהשורה internal_committee של
  ההחלטה ב-case_law → precedent_internal_citations: linked (join case_law) +
  missing (unresolved + האם flagged ב-missing_precedents).
- web-ui: lib/api/citations.ts (hook) + CitationsSection ב-drafts-panel
  (מוצג כשההחלטה בספרייה). מקושרת=ירוק/קליק, חסרה=ענבר "סומנה להעלאה".
- scripts/curator_apply_pipeline_branch.py: מקור-אמת לחיווט-הכפתורים של הרמס
  (ה-prompt חי רק ב-Paperclip DB). מקדים branch שמריץ את pipeline-ה-final
  ל-wake reason final_learning_*/final_halacha_* (HOME/DOTENV/DATA_DIR מוחלטים
  → מפתחות DeepSeek+Gemini + DATA_DIR נפתרים נכון). idempotent, שני הסוכנים.
  כבר הוחל ב-DB; הסקריפט לשחזור אחרי reset.

אומת: py_compile ✓ · tsc ✓ · החיווט אומת חי על 8126 (deepseek+gemini, dedup,
✓ pipeline הושלם). G2 (יכולת חסרה) · INV-LRN1/G10 נשמרים.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-08 11:59:21 +00:00
parent 2c4287fd3d
commit 98c5feff25
5 changed files with 273 additions and 0 deletions

View File

@@ -33,6 +33,7 @@ import {
BLOCK_LABELS,
type FeedbackCategory,
} from "@/lib/api/feedback";
import { useCaseCitations } from "@/lib/api/citations";
import type { CaseStatus } from "@/lib/api/cases";
import { toast } from "sonner";
import {
@@ -47,6 +48,8 @@ import {
Brain,
Scale,
Stamp,
Link2,
AlertTriangle,
} from "lucide-react";
/* Statuses at which a draft is considered ready */
@@ -336,6 +339,9 @@ export function DraftsPanel({
)}
</section>
{/* ── Precedents cited inside the signed decision ── */}
<CitationsSection caseNumber={caseNumber} />
{/* ── Exports list ── */}
<section>
<div className="flex items-center justify-between mb-3">
@@ -587,6 +593,69 @@ export function DraftsPanel({
);
}
/* ── Precedents cited inside the decision ──────────── */
function CitationsSection({ caseNumber }: { caseNumber: string }) {
const { data, isLoading } = useCaseCitations(caseNumber);
// Nothing to show until the signed decision is in the precedent library.
if (isLoading || !data?.in_library) return null;
if (!data.linked.length && !data.missing.length) return null;
return (
<section>
<div className="flex items-center gap-2 mb-3">
<Scale className="w-4 h-4 text-navy" />
<h3 className="text-navy text-base">פסיקה שצוטטה בהחלטה</h3>
<Badge variant="outline" className="text-[0.65rem]">
{data.linked.length} בספרייה · {data.missing.length} חסרות
</Badge>
</div>
<div className="rounded-lg border border-rule overflow-hidden divide-y divide-rule">
{data.linked.map((c) => (
<a
key={`l-${c.citation}`}
href={`/precedents/${c.cited_id}`}
className="flex items-center gap-2 px-4 py-2.5 text-sm hover:bg-rule-soft/20"
>
<Link2 className="w-3.5 h-3.5 text-success shrink-0" />
<span className="font-medium text-ink">{c.citation}</span>
{c.case_name && (
<span className="text-ink-muted truncate"> {c.case_name}</span>
)}
<Badge className="ms-auto bg-success-bg text-success border-success/40 text-[0.65rem] shrink-0">
בספרייה
</Badge>
</a>
))}
{data.missing.map((c) => (
<div
key={`m-${c.citation}`}
className="flex items-center gap-2 px-4 py-2.5 text-sm"
>
<AlertTriangle className="w-3.5 h-3.5 text-gold-deep shrink-0" />
<span className="font-medium text-ink">{c.citation}</span>
<Badge
className={`ms-auto text-[0.65rem] shrink-0 border ${
c.flagged
? "bg-gold-wash text-gold-deep border-gold/40"
: "bg-rule-soft text-ink-soft border-rule"
}`}
>
{c.flagged ? "חסרה — סומנה להעלאה" : "חסרה בספרייה"}
</Badge>
</div>
))}
</div>
<p className="text-[0.7rem] text-ink-muted mt-2">
פסיקה מקושרת מחזקת את ההלכות שלה (corroboration); חסרות מסומנות אוטומטית
להעלאה לספרייה.
</p>
</section>
);
}
/* ── New feedback dialog (case-scoped) ─────────────── */
function NewCaseFeedbackDialog({ caseNumber }: { caseNumber: string }) {