All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m27s
Adds a third corpus of legal authority distinct from style_corpus (Daphna's prior decisions for voice) and case_precedents (chair-attached quotes per case). The new corpus holds chair-uploaded court rulings and other appeals committee decisions, with binding rules (הלכות) extracted automatically and queued for chair approval. Pipeline (web/app.py + services/precedent_library.py): file → extract → chunk → Voyage embed → halacha_extractor → store + publish progress over the existing Redis SSE channel. Schema V7 (services/db.py): extends case_law with source_kind + extraction status fields under a CHECK constraint pinning practice_area to the three appeals committee domains (rishuy_uvniya, betterment_levy, compensation_197). New precedent_chunks (vector(1024)) and halachot tables (vector(1024) over rule_statement, IVFFlat indexes, gin on practice_areas/subject_tags). Halachot start as pending_review; only approved/published rows are visible to search_precedent_library. Agents: legal-writer, legal-researcher, legal-analyst, legal-ceo, legal-qa get search_precedent_library. legal-writer prompt explains the three-corpus distinction and CREAC use; legal-qa now verifies that every cited halacha resolves to an approved row in the corpus. UI: /precedents page with four tabs — library / semantic search / pending review (J/K nav, A/R/E shortcuts, badge count) / stats. Reuses the existing upload-sheet progress + SSE pattern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
90 lines
3.3 KiB
TypeScript
90 lines
3.3 KiB
TypeScript
"use client";
|
||
|
||
import { Skeleton } from "@/components/ui/skeleton";
|
||
import { useLibraryStats } from "@/lib/api/precedent-library";
|
||
import { practiceAreaLabel } from "./practice-area";
|
||
|
||
function StatCard({ label, value, accent }: { label: string; value: number | string; accent?: boolean }) {
|
||
return (
|
||
<div
|
||
className={`
|
||
rounded-lg border p-5 bg-surface shadow-sm
|
||
${accent ? "border-gold bg-gold-wash/40" : "border-rule"}
|
||
`}
|
||
>
|
||
<div className="text-[0.78rem] text-ink-muted mb-1">{label}</div>
|
||
<div className={`text-3xl font-bold tabular-nums ${accent ? "text-gold-deep" : "text-navy"}`}>
|
||
{value}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function LibraryStatsPanel() {
|
||
const { data, isPending, error } = useLibraryStats();
|
||
|
||
if (error) {
|
||
return (
|
||
<div className="rounded bg-danger-bg border border-danger/40 px-6 py-5 text-danger text-center">
|
||
{error.message}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (isPending || !data) {
|
||
return (
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||
{[...Array(4)].map((_, i) => <Skeleton key={i} className="h-28 w-full" />)}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||
<StatCard label="פסיקה בקורפוס" value={data.precedents_total} />
|
||
<StatCard label="הלכות בסך הכל" value={data.halachot_total} />
|
||
<StatCard
|
||
label="ממתינות לאישור" value={data.halachot_pending}
|
||
accent={data.halachot_pending > 0}
|
||
/>
|
||
<StatCard label="מאושרות (זמינות לסוכנים)" value={data.halachot_approved} />
|
||
</div>
|
||
|
||
<div className="grid md:grid-cols-2 gap-4">
|
||
<div className="rounded-lg border border-rule bg-surface p-5">
|
||
<h3 className="text-navy font-semibold mb-3">פילוח לפי תחום</h3>
|
||
{data.by_practice_area.length === 0 ? (
|
||
<p className="text-ink-muted text-sm">אין נתונים</p>
|
||
) : (
|
||
<ul className="space-y-2">
|
||
{data.by_practice_area.map((row) => (
|
||
<li key={row.practice_area || "—"} className="flex items-center gap-2 text-sm">
|
||
<span className="text-ink">{practiceAreaLabel(row.practice_area || null)}</span>
|
||
<span className="ms-auto tabular-nums text-navy font-semibold">{row.count}</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
)}
|
||
</div>
|
||
|
||
<div className="rounded-lg border border-rule bg-surface p-5">
|
||
<h3 className="text-navy font-semibold mb-3">פילוח לפי רמת תקדים</h3>
|
||
{data.by_precedent_level.length === 0 ? (
|
||
<p className="text-ink-muted text-sm">אין נתונים</p>
|
||
) : (
|
||
<ul className="space-y-2">
|
||
{data.by_precedent_level.map((row) => (
|
||
<li key={row.precedent_level || "—"} className="flex items-center gap-2 text-sm">
|
||
<span className="text-ink">{row.precedent_level || "—"}</span>
|
||
<span className="ms-auto tabular-nums text-navy font-semibold">{row.count}</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|