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:
@@ -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">
|
||||
|
||||
@@ -28,6 +28,7 @@ export type GoldsetItem = {
|
||||
review_status: string;
|
||||
case_number: string | null;
|
||||
case_name: string | null;
|
||||
source_type: string | null; // 'court_ruling' | 'appeals_committee' | ''
|
||||
};
|
||||
|
||||
export type GoldsetScore = {
|
||||
|
||||
Reference in New Issue
Block a user