From 5aa3d4ed999dab51a62639b90f6633b044c02238 Mon Sep 17 00:00:00 2001 From: Chaim Date: Sun, 7 Jun 2026 13:40:05 +0000 Subject: [PATCH] feat(goldset): soft consistency warning between is_holding and type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "לא הלכה" + "מחייבת" (or any holding-type) is a logical contradiction — binding means it IS the holding. Likewise "הלכה" + application/obiter. The three controls are independent, so the combo was clickable with no signal. Adds a non-blocking amber warning under the type buttons when is_holding and correct_type contradict (holding ↔ binding/interpretive/procedural/persuasive; not-holding ↔ application/obiter). Soft by design — flags the inconsistency for the tagger to fix without forcing, leaving room for genuine edge cases. Verified: tsc --noEmit exits 0. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/components/goldset/goldset-panel.tsx | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/web-ui/src/components/goldset/goldset-panel.tsx b/web-ui/src/components/goldset/goldset-panel.tsx index 79f8265..6b2450c 100644 --- a/web-ui/src/components/goldset/goldset-panel.tsx +++ b/web-ui/src/components/goldset/goldset-panel.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useMemo, useState } from "react"; -import { Check, X, ChevronDown, ChevronLeft, Info } from "lucide-react"; +import { Check, X, ChevronDown, ChevronLeft, Info, AlertTriangle } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; @@ -21,6 +21,23 @@ const TYPES: { value: string; label: string }[] = [ { value: "persuasive", label: "משכנע" }, ]; +// Consistency between is_holding and the type (#81.7): a real holding is +// binding/interpretive/procedural/persuasive; a NON-holding is its reason — +// application (fact-bound) or obiter (not decided). Other pairings contradict. +const HOLDING_TYPES = new Set(["binding", "interpretive", "procedural", "persuasive"]); +const NON_HOLDING_TYPES = new Set(["application", "obiter"]); + +function inconsistentTag(it: GoldsetItem): string | null { + if (it.is_holding === null || !it.correct_type) return null; + if (it.is_holding === true && NON_HOLDING_TYPES.has(it.correct_type)) { + return "סימנת \"הלכה\" אך הסוג הוא יישום/אמרת-אגב — אלה דווקא הסיבות שמשהו אינו הלכה."; + } + if (it.is_holding === false && HOLDING_TYPES.has(it.correct_type)) { + return "סימנת \"לא הלכה\" אך הסוג מציין הלכה (מחייבת/פרשני/…); ל\"לא\" מתאים יישום או אמרת-אגב."; + } + return null; +} + const FLAG_LABELS: Record = { non_decision: "אי-הכרעה", truncated_quote: "ציטוט קטוע", thin_restatement: "ניסוח דק", quote_unverified: "ציטוט לא מאומת", nli_unsupported: "כלל לא נגזר", application: "יישום", @@ -232,6 +249,12 @@ function TagCard({ onClick={() => onTag({ correct_type: t.value })}>{t.label} ))} + {inconsistentTag(it) && ( +

+ + {inconsistentTag(it)} +

+ )} {/* quote_complete */}