feat(goldset): separate court rulings from committee decisions in tagging

Tagging is easier one source-type at a time. goldset_list now returns
case_law.source_type; the page adds:
- a filter (הכל / פסקי דין / ועדת ערר) with live counts,
- a group-sort so even in "הכל" all court rulings come first, then all
  committee decisions,
- a per-card source badge (פסק-דין / ועדת ערר).

Verified: tsc --noEmit 0; source_type splits the live batch 58 court / 92 committee.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-07 13:55:06 +00:00
parent f60fdc2c6d
commit 632fe73857
3 changed files with 48 additions and 5 deletions

View File

@@ -49,6 +49,17 @@ function cleanCitation(s: string | null | undefined): string {
return s.replace(/[--]/g, "").trim();
}
// Source separation (פסקי-דין מול החלטות ועדת-ערר) for convenient tagging.
function sourceLabel(s: string | null): string {
return s === "court_ruling" ? "פסק-דין"
: s === "appeals_committee" ? "ועדת ערר" : "אחר";
}
const SOURCE_FILTERS: { value: "all" | "court_ruling" | "appeals_committee"; label: string }[] = [
{ value: "all", label: "הכל" },
{ value: "court_ruling", label: "פסקי דין" },
{ value: "appeals_committee", label: "ועדת ערר" },
];
function isTagged(it: GoldsetItem): boolean {
// Fully tagged only when ALL THREE answers are set — otherwise, in
// "hide tagged" mode, a card would vanish the moment is_holding is clicked,
@@ -201,6 +212,12 @@ function TagCard({
>
<div className="flex items-center gap-2 text-[0.72rem] text-ink-muted flex-wrap">
<span className="font-semibold text-navy">{cleanCitation(it.case_number)}</span>
<Badge variant="outline"
className={`text-[0.65rem] ${it.source_type === "court_ruling"
? "bg-navy-soft/30 text-navy border-navy/30"
: "bg-gold-wash text-gold-deep border-gold/40"}`}>
{sourceLabel(it.source_type)}
</Badge>
<Badge variant="outline" className="text-[0.65rem]">מכונה: {it.rule_type}</Badge>
{it.confidence != null && (
<Badge variant="outline" className="text-[0.65rem] tabular-nums">ביטחון {it.confidence.toFixed(2)}</Badge>
@@ -282,13 +299,24 @@ export function GoldsetPanel() {
const createSample = useCreateGoldsetSample(batch);
const [focusedId, setFocusedId] = useState<string | null>(null);
const [hideTagged, setHideTagged] = useState(false);
const [sourceFilter, setSourceFilter] =
useState<"all" | "court_ruling" | "appeals_committee">("all");
const items = useMemo(() => data?.items ?? [], [data]);
const taggedCount = items.filter(isTagged).length;
const visible = useMemo(
() => (hideTagged ? items.filter((i) => !isTagged(i)) : items),
[items, hideTagged],
);
const sourceCounts = useMemo(() => ({
court_ruling: items.filter((i) => i.source_type === "court_ruling").length,
appeals_committee: items.filter((i) => i.source_type === "appeals_committee").length,
}), [items]);
const visible = useMemo(() => {
let v = items;
if (sourceFilter !== "all") v = v.filter((i) => i.source_type === sourceFilter);
if (hideTagged) v = v.filter((i) => !isTagged(i));
// group-sort: כל פסקי-הדין יחד, ואז כל החלטות ועדת-הערר (הפרדה ברורה).
const order = (s: string | null) =>
s === "court_ruling" ? 0 : s === "appeals_committee" ? 1 : 2;
return [...v].sort((a, b) => order(a.source_type) - order(b.source_type));
}, [items, hideTagged, sourceFilter]);
const focused = focusedId ? visible.find((i) => i.id === focusedId) ?? null : null;
@@ -363,6 +391,20 @@ export function GoldsetPanel() {
<div className="space-y-4">
<ScorePanel batch={batch} />
{/* source separation — פסקי-דין מול החלטות ועדת-ערר */}
<div className="flex items-center gap-1 rounded-lg border border-rule p-0.5 bg-rule-soft/30 w-fit">
{SOURCE_FILTERS.map((s) => (
<Button key={s.value} size="sm"
variant={sourceFilter === s.value ? "default" : "ghost"}
className={sourceFilter === s.value ? "bg-gold text-navy hover:bg-gold-deep" : ""}
onClick={() => setSourceFilter(s.value)}>
{s.label}
{s.value === "court_ruling" && ` (${sourceCounts.court_ruling})`}
{s.value === "appeals_committee" && ` (${sourceCounts.appeals_committee})`}
</Button>
))}
</div>
<div className="flex items-center gap-3 flex-wrap text-sm">
<span className="text-navy font-semibold tabular-nums">{taggedCount}/{items.length} תויגו</span>
<div className="h-2 w-40 rounded-full bg-rule-soft overflow-hidden">