Merge pull request 'feat(goldset): separate court rulings from committee decisions' (#105) from worktree-goldset-source-split into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m26s
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m26s
This commit was merged in pull request #105.
This commit is contained in:
@@ -4340,7 +4340,7 @@ async def goldset_list(batch: str = "default") -> list[dict]:
|
|||||||
" g.correct_type, g.quote_complete, g.tagged_by, g.tagged_at, "
|
" g.correct_type, g.quote_complete, g.tagged_by, g.tagged_at, "
|
||||||
" h.rule_statement, h.supporting_quote, h.reasoning_summary, "
|
" h.rule_statement, h.supporting_quote, h.reasoning_summary, "
|
||||||
" h.rule_type, h.confidence, h.quality_flags, h.review_status, "
|
" h.rule_type, h.confidence, h.quality_flags, h.review_status, "
|
||||||
" cl.case_number, cl.case_name "
|
" cl.case_number, cl.case_name, cl.source_type "
|
||||||
"FROM halacha_goldset g JOIN halachot h ON h.id = g.halacha_id "
|
"FROM halacha_goldset g JOIN halachot h ON h.id = g.halacha_id "
|
||||||
"LEFT JOIN case_law cl ON cl.id = h.case_law_id "
|
"LEFT JOIN case_law cl ON cl.id = h.case_law_id "
|
||||||
"WHERE g.batch = $1 ORDER BY g.created_at, g.id", batch,
|
"WHERE g.batch = $1 ORDER BY g.created_at, g.id", batch,
|
||||||
|
|||||||
@@ -49,6 +49,17 @@ function cleanCitation(s: string | null | undefined): string {
|
|||||||
return s.replace(/[--]/g, "").trim();
|
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 {
|
function isTagged(it: GoldsetItem): boolean {
|
||||||
// Fully tagged only when ALL THREE answers are set — otherwise, in
|
// Fully tagged only when ALL THREE answers are set — otherwise, in
|
||||||
// "hide tagged" mode, a card would vanish the moment is_holding is clicked,
|
// "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">
|
<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>
|
<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>
|
<Badge variant="outline" className="text-[0.65rem]">מכונה: {it.rule_type}</Badge>
|
||||||
{it.confidence != null && (
|
{it.confidence != null && (
|
||||||
<Badge variant="outline" className="text-[0.65rem] tabular-nums">ביטחון {it.confidence.toFixed(2)}</Badge>
|
<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 createSample = useCreateGoldsetSample(batch);
|
||||||
const [focusedId, setFocusedId] = useState<string | null>(null);
|
const [focusedId, setFocusedId] = useState<string | null>(null);
|
||||||
const [hideTagged, setHideTagged] = useState(false);
|
const [hideTagged, setHideTagged] = useState(false);
|
||||||
|
const [sourceFilter, setSourceFilter] =
|
||||||
|
useState<"all" | "court_ruling" | "appeals_committee">("all");
|
||||||
|
|
||||||
const items = useMemo(() => data?.items ?? [], [data]);
|
const items = useMemo(() => data?.items ?? [], [data]);
|
||||||
const taggedCount = items.filter(isTagged).length;
|
const taggedCount = items.filter(isTagged).length;
|
||||||
const visible = useMemo(
|
const sourceCounts = useMemo(() => ({
|
||||||
() => (hideTagged ? items.filter((i) => !isTagged(i)) : items),
|
court_ruling: items.filter((i) => i.source_type === "court_ruling").length,
|
||||||
[items, hideTagged],
|
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;
|
const focused = focusedId ? visible.find((i) => i.id === focusedId) ?? null : null;
|
||||||
|
|
||||||
@@ -363,6 +391,20 @@ export function GoldsetPanel() {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<ScorePanel batch={batch} />
|
<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">
|
<div className="flex items-center gap-3 flex-wrap text-sm">
|
||||||
<span className="text-navy font-semibold tabular-nums">{taggedCount}/{items.length} תויגו</span>
|
<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">
|
<div className="h-2 w-40 rounded-full bg-rule-soft overflow-hidden">
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export type GoldsetItem = {
|
|||||||
review_status: string;
|
review_status: string;
|
||||||
case_number: string | null;
|
case_number: string | null;
|
||||||
case_name: string | null;
|
case_name: string | null;
|
||||||
|
source_type: string | null; // 'court_ruling' | 'appeals_committee' | ''
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GoldsetScore = {
|
export type GoldsetScore = {
|
||||||
|
|||||||
Reference in New Issue
Block a user