fix(halacha): split authority (derived) from rule_role — stop source-conflation (INV-DM7)
The extractor classified rule_type by SOURCE bindingness (higher-court→binding, committee→persuasive) instead of by rule KIND. The gold-set proved it: 'binding' appeared on 19/19 external rulings & 0 committees; 'persuasive' on 13/13 committees & 0 external — only 58% agreement with the human role tags. The two axes (authority vs rule role) were crammed into one enum. This splits them per INV-DM7: - authority (binding/persuasive) — DERIVED from case_law.precedent_level (עליון/מנהלי→binding, ועדת_ערר_מחוזית→persuasive), never stored, never LLM-guessed. New helper halacha_quality.derive_authority; surfaced read-only in list_halachot / goldset_list / search results. - rule_type — now the rule ROLE only: holding/interpretive/procedural/ application/obiter. Both extractor prompts unified to this vocabulary; _coerce_halacha no longer defaults rule_type from the source; legacy binding→holding / persuasive→interpretive fold for safety. UI: authority shown as a separate read-only badge (gold=מחייב / muted=משכנע) across the review queue, precedent detail, and gold-set; the gold-set role selector drops binding/persuasive and adds מהותי (holding). Migration: scripts/halacha_rule_role_backfill.py re-classifies the 276 pre-split binding/persuasive rows into a genuine role via local claude_session (run after deploy). Gold-set correct_type/ai_correct_type 'binding'→'holding' via SQL. Sources (≥3, per research-decision policy): OASIS LegalRuleML v1.0 (appliesAuthority/Strength as metadata orthogonal to rule logic) · SemEval-2023 Task 6 LegalEval (rhetorical roles by function, authority kept separate) · Bluebook signals (weight-of-authority is a separate dimension). Invariants: ESTABLISHES INV-DM7. Upholds G1 (normalize at source — extractor classifies role, system derives authority) and G2 (single source of truth — authority derived, not a parallel stored field). Tests: 211 pass + new derive_authority/coerce coverage. web-ui build + tsc clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,20 +11,22 @@ import {
|
||||
useGoldset, useGoldsetScore, useTagGoldset, useCreateGoldsetSample,
|
||||
type GoldsetItem,
|
||||
} from "@/lib/api/goldset";
|
||||
import { AuthorityBadge } from "@/components/precedents/halacha-meta";
|
||||
|
||||
// rule ROLE only (INV-DM7) — authority (binding/persuasive) is a SEPARATE,
|
||||
// derived axis, shown read-only and never tagged here.
|
||||
const TYPES: { value: string; label: string }[] = [
|
||||
{ value: "binding", label: "מחייבת" },
|
||||
{ value: "holding", label: "מהותי" },
|
||||
{ value: "interpretive", label: "פרשני" },
|
||||
{ value: "procedural", label: "פרוצדורלי" },
|
||||
{ value: "application", label: "יישום" },
|
||||
{ value: "obiter", label: "אמרת-אגב" },
|
||||
{ value: "procedural", label: "פרוצדורלי" },
|
||||
{ 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 —
|
||||
// Consistency between is_holding and the role (#81.7): a real holding is
|
||||
// holding/interpretive/procedural; 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 HOLDING_TYPES = new Set(["holding", "interpretive", "procedural"]);
|
||||
const NON_HOLDING_TYPES = new Set(["application", "obiter"]);
|
||||
|
||||
function inconsistentTag(it: GoldsetItem): string | null {
|
||||
@@ -33,7 +35,7 @@ function inconsistentTag(it: GoldsetItem): string | null {
|
||||
return "סימנת \"הלכה\" אך הסוג הוא יישום/אמרת-אגב — אלה דווקא הסיבות שמשהו אינו הלכה.";
|
||||
}
|
||||
if (it.is_holding === false && HOLDING_TYPES.has(it.correct_type)) {
|
||||
return "סימנת \"לא הלכה\" אך הסוג מציין הלכה (מחייבת/פרשני/…); ל\"לא\" מתאים יישום או אמרת-אגב.";
|
||||
return "סימנת \"לא הלכה\" אך הסוג מציין הלכה (מהותי/פרשני/…); ל\"לא\" מתאים יישום או אמרת-אגב.";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -139,11 +141,13 @@ function ScorePanel({ batch }: { batch: string }) {
|
||||
|
||||
// ─── Rule-type help (info popover) ────────────────────────────────────────────
|
||||
|
||||
// Role only — "כמה מחייב" (מחייב/משכנע) is the SEPARATE authority axis, derived
|
||||
// automatically from the court's identity and shown as a read-only badge.
|
||||
const TYPE_HELP: { label: string; def: string; test: string; example: string }[] = [
|
||||
{
|
||||
label: "מחייבת",
|
||||
def: "העיקרון שהיה הכרחי להכרעה — ה-holding האמיתי. בר-הסתמכות מלא.",
|
||||
test: "מבחן וומבו: הפוך את הכלל — אם התוצאה הייתה משתנה → מחייבת.",
|
||||
label: "מהותי",
|
||||
def: "העיקרון המהותי שהיה הכרחי להכרעה — ה-ratio האמיתי. בר-הסתמכות מלא.",
|
||||
test: "מבחן וומבו: הפוך את הכלל — אם התוצאה הייתה משתנה → מהותי.",
|
||||
example: "נטל ההוכחה בהיטל השבחה מוטל על הוועדה המקומית.",
|
||||
},
|
||||
{
|
||||
@@ -152,6 +156,12 @@ const TYPE_HELP: { label: string; def: string; test: string; example: string }[]
|
||||
test: "עונה ל'מה פירוש הנורמה?' ולא ל'מה הדין?'.",
|
||||
example: "תכלית הפטור לפי ס' 19(ב)(4) היא לעודד פעילות ציבורית.",
|
||||
},
|
||||
{
|
||||
label: "פרוצדורלי",
|
||||
def: "כלל סדר-דין: מועדים, סמכות, זכות-עמידה, מיצוי הליכים, נטל.",
|
||||
test: "עוסק ב'איך' מתנהל ההליך, לא במהות התכנונית.",
|
||||
example: "המועד להגשת ערר הוא 30 יום.",
|
||||
},
|
||||
{
|
||||
label: "יישום",
|
||||
def: "החלת כלל על עובדות התיק הספציפי — תלוי-עובדות, לא בר-הכללה (לרוב 'לא הלכה').",
|
||||
@@ -160,22 +170,10 @@ const TYPE_HELP: { label: string; def: string; test: string; example: string }[]
|
||||
},
|
||||
{
|
||||
label: "אמרת-אגב",
|
||||
def: "נאמר אגב אורחא, לא הכרחי להכרעה; הערכאה לא הכריעה בו. לא מחייב.",
|
||||
def: "נאמר אגב אורחא, לא הכרחי להכרעה; הערכאה לא הכריעה בו.",
|
||||
test: "מבחן וומבו הפוך: היפוך הכלל לא משנה את התוצאה. דגלים: 'למעלה מן הצורך', 'מבלי לקבוע מסמרות'.",
|
||||
example: "אף שאיננו נדרשים להכריע, נעיר כי ייתכן ש...",
|
||||
},
|
||||
{
|
||||
label: "פרוצדורלי",
|
||||
def: "כלל סדר-דין: מועדים, סמכות, זכות-עמידה, מיצוי הליכים, נטל.",
|
||||
test: "עוסק ב'איך' מתנהל ההליך, לא במהות התכנונית.",
|
||||
example: "המועד להגשת ערר הוא 30 יום.",
|
||||
},
|
||||
{
|
||||
label: "משכנע",
|
||||
def: "אסמכתה לא-מחייבת את הערכאה — שכנוע בלבד.",
|
||||
test: "מקור שאינו כובל: ועדת-ערר אחרת, דעת-מיעוט, ספרות.",
|
||||
example: "ועדת ערר ירושלים מסתמכת על החלטת ועדת ערר ממחוז אחר.",
|
||||
},
|
||||
];
|
||||
|
||||
function RuleTypeHelp() {
|
||||
@@ -195,7 +193,7 @@ function RuleTypeHelp() {
|
||||
<div className="p-3 border-b border-rule">
|
||||
<p className="font-semibold text-navy text-sm">סוגי ההלכה — במה הם נבדלים</p>
|
||||
<p className="text-[0.72rem] text-ink-muted mt-0.5">
|
||||
כלל-אצבע: סימנת "הלכה" → לרוב מחייבת / פרשני / פרוצדורלי / משכנע. סימנת "לא" → לרוב יישום / אמרת-אגב.
|
||||
כלל-אצבע: סימנת “הלכה” → לרוב מהותי / פרשני / פרוצדורלי. סימנת “לא” → לרוב יישום / אמרת-אגב.
|
||||
</p>
|
||||
</div>
|
||||
<ul className="divide-y divide-rule-soft">
|
||||
@@ -238,6 +236,7 @@ function TagCard({
|
||||
{sourceLabel(it.source_type)}
|
||||
</Badge>
|
||||
<Badge variant="outline" className="text-[0.65rem]">מכונה: {it.rule_type}</Badge>
|
||||
<AuthorityBadge authority={it.authority} />
|
||||
{it.confidence != null && (
|
||||
<Badge variant="outline" className="text-[0.65rem] tabular-nums">ביטחון {it.confidence.toFixed(2)}</Badge>
|
||||
)}
|
||||
|
||||
@@ -7,15 +7,7 @@ import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CorroborationBadge } from "@/components/precedents/corroboration-badge";
|
||||
import { useUpdateHalacha, type Halacha } from "@/lib/api/precedent-library";
|
||||
|
||||
const RULE_TYPE_LABELS: Record<string, string> = {
|
||||
binding: "הלכה מחייבת",
|
||||
interpretive: "פרשני",
|
||||
procedural: "פרוצדורלי",
|
||||
obiter: "אמרת אגב",
|
||||
application: "יישום הלכה",
|
||||
persuasive: "משכנע",
|
||||
};
|
||||
import { AuthorityBadge, ruleTypeLabel } from "./halacha-meta";
|
||||
|
||||
type StatusFilter = "all" | "approved" | "pending" | "rejected";
|
||||
|
||||
@@ -172,8 +164,9 @@ export function ExtractedHalachotSection({ halachot }: { halachot: Halacha[] })
|
||||
</span>
|
||||
<ReviewStatusPill status={h.review_status} />
|
||||
<Badge variant="outline" className="text-[0.65rem]">
|
||||
{RULE_TYPE_LABELS[h.rule_type] ?? h.rule_type}
|
||||
{ruleTypeLabel(h.rule_type)}
|
||||
</Badge>
|
||||
<AuthorityBadge authority={h.authority} />
|
||||
<Badge variant="outline" className="text-[0.65rem] tabular-nums">
|
||||
ביטחון {h.confidence.toFixed(2)}
|
||||
</Badge>
|
||||
|
||||
54
web-ui/src/components/precedents/halacha-meta.tsx
Normal file
54
web-ui/src/components/precedents/halacha-meta.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
"use client";
|
||||
/**
|
||||
* Shared halacha-classification labels + badge (INV-DM7 — two orthogonal axes).
|
||||
*
|
||||
* rule_type holds the rule ROLE (what KIND of statement). authority (binding vs
|
||||
* persuasive) is a SEPARATE, DERIVED axis (where it came from) — rendered as a
|
||||
* distinct read-only badge, never mixed into the role label.
|
||||
*/
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
/** rule ROLE labels. Legacy authority values (binding/persuasive) are kept as a
|
||||
* fallback so pre-backfill rows still render a Hebrew word during rollout. */
|
||||
export const RULE_TYPE_LABELS: Record<string, string> = {
|
||||
holding: "עיקרון מהותי",
|
||||
interpretive: "פרשני",
|
||||
procedural: "פרוצדורלי",
|
||||
application: "יישום",
|
||||
obiter: "אמרת אגב",
|
||||
// legacy (pre-split) — fold to role wording until backfill completes
|
||||
binding: "עיקרון מהותי",
|
||||
persuasive: "פרשני",
|
||||
};
|
||||
|
||||
export function ruleTypeLabel(t: string | null | undefined): string {
|
||||
return (t && RULE_TYPE_LABELS[t]) || t || "—";
|
||||
}
|
||||
|
||||
export const AUTHORITY_LABELS: Record<string, string> = {
|
||||
binding: "מחייב",
|
||||
persuasive: "משכנע",
|
||||
};
|
||||
|
||||
/** Read-only authority badge — derived from the source, the chair never sets it. */
|
||||
export function AuthorityBadge({
|
||||
authority,
|
||||
}: {
|
||||
authority?: string | null;
|
||||
}) {
|
||||
if (!authority || !AUTHORITY_LABELS[authority]) return null;
|
||||
const isBinding = authority === "binding";
|
||||
return (
|
||||
<Badge
|
||||
variant="outline"
|
||||
title="דרגת-המחייבות נגזרת אוטומטית מזהות הערכאה"
|
||||
className={
|
||||
isBinding
|
||||
? "text-[0.65rem] bg-gold/15 text-navy border-gold/50"
|
||||
: "text-[0.65rem] bg-muted text-ink-muted border-border"
|
||||
}
|
||||
>
|
||||
{AUTHORITY_LABELS[authority]}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import { practiceAreaLabel } from "./practice-area";
|
||||
import {
|
||||
useHalachotPending, useHalachotByStatus, useUpdateHalacha, useBatchReviewHalachot, type Halacha,
|
||||
} from "@/lib/api/precedent-library";
|
||||
import { AuthorityBadge, ruleTypeLabel } from "./halacha-meta";
|
||||
|
||||
/** #81 strict-rubric flags — why an item was held back from auto-approval. */
|
||||
const QUALITY_FLAG_LABELS: Record<string, string> = {
|
||||
@@ -40,19 +41,6 @@ function cleanCitation(s: string | null | undefined): string {
|
||||
return s.replace(/[--]/g, "").trim();
|
||||
}
|
||||
|
||||
const RULE_TYPE_LABELS: Record<string, string> = {
|
||||
binding: "הלכה מחייבת",
|
||||
interpretive: "פרשני",
|
||||
procedural: "פרוצדורלי",
|
||||
obiter: "אמרת אגב",
|
||||
application: "יישום הלכה",
|
||||
persuasive: "משכנע",
|
||||
};
|
||||
|
||||
function ruleTypeLabel(t: string): string {
|
||||
return RULE_TYPE_LABELS[t] ?? t;
|
||||
}
|
||||
|
||||
type EditState = { rule_statement: string; reasoning_summary: string };
|
||||
|
||||
// ─── Pending-queue card (full interactions) ───────────────────────────────────
|
||||
@@ -118,6 +106,7 @@ function HalachaCard({
|
||||
<Badge variant="outline" className="text-[0.65rem]">
|
||||
{ruleTypeLabel(h.rule_type)}
|
||||
</Badge>
|
||||
<AuthorityBadge authority={h.authority} />
|
||||
{variants.length > 0 && (
|
||||
<Badge variant="outline"
|
||||
className="text-[0.65rem] bg-navy-soft/30 text-navy border-navy/30">
|
||||
@@ -326,6 +315,7 @@ function HalachaRestoreCard({
|
||||
<Badge variant="outline" className="text-[0.65rem]">
|
||||
{ruleTypeLabel(h.rule_type)}
|
||||
</Badge>
|
||||
<AuthorityBadge authority={h.authority} />
|
||||
<CorroborationBadge halacha={h} />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user