feat(halacha): #81.7 — gold-set labeled by tri-model consensus (Opus+DeepSeek+Gemini)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 6s
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 6s
מבטל את ה-man-in-the-loop בתיוג ה-gold-set (הנחיית-יו"ר 2026-06-11): במקום תיוג ידני של חיים/דפנה, אמת-המידה נקבעת בקונצנזוס שלוש שושלות-מודל עצמאיות — אותו פאנל שמערכת האישור החיה כבר משתמשת בו (halacha_panel_approve), עם 92% הסכמה חוצת-מודלים על הציר הגס. למה לא מעגלי: הוולידטורים הנמדדים ב-#81.8 (compute_quality_flags / is_fact_dependent / is_quote_truncated / is_thin_restatement) הם היוריסטיקות **rule-based** — משפחת-שיטה שונה מה-LLM-judges. שני שומרי-יושר: (1) פיצול-קולות (אין רוב 2/3) לא כותב לייבל — הפריט נשאר NULL ומוסלם ליו"ר (INV-G10); (2) מבחן-אנונימיזציה — שיפוט-מחדש עם מזהה-התיק ממוסך, flip בקונצנזוס = שינון ולא הנמקה (arXiv:2505.02172). - db.py: עמודות per-lineage (ds_*/gm_*; ai_*=claude קיים) + consensus/agreement/anon + goldset_set_panel_label() שכותב רוב-2/3 ל-is_holding/correct_type (tagged_by='panel:…', לא דורס tagged_by='chair'). goldset_score נשאר ללא שינוי — קורא is_holding (G2, אין מסלול ניקוד מקביל). עדכון הערת-הסכמה (בוטלה דרישת "MUST be human"). - scripts/goldset_panel_label.py: 3 שופטים (מיובאים מ-halacha_panel_approve, מקור-אמת יחיד) + prompt עשיר (מיובא מ-goldset_ai_recommend) + Fleiss κ + מבחן-אנונימיזציה. דוח→data/audit/. - SCRIPTS.md: סקריפט חדש; goldset_ai_recommend/independent_judge מסומנים single-model נבלעים. invariants: G2 (שופטים+prompt מיובאים, אין כפילות; ניקוד יחיד) · INV-G10 (פיצול→יו"ר) · INV-LRN2/LRN3 (איכות-במקור, לכידה מובנית). מקור: PoLL · Trust-or-Escalate (ICLR 2025) · arXiv:2505.02172. tests: 18 offline (consensus/type/Fleiss-κ/anonymize). live labeling = צעד תפעולי אחרי deploy. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -48,7 +48,8 @@
|
||||
| `backfill_nevo_preamble.py` | python | **#86.2** — מיגרציית-נתונים: חיתוך preamble/רציו של נבו שדלף לפסיקה שהוטמעה לפני תיקון #86.1. מאתר כל `case_law` ש-`strip_nevo_preamble(full_text)` עדיין מקצר (דליפה היסטורית), ומבצע: (1) לכידת ה-מיני-רציו ל-`case_law.nevo_ratio` (gold-set ל-#86.3); (2) שכתוב `full_text` החתוך + חישוב-מחדש של `content_hash`; (3) `reindex_case_law` (re-chunk+embed, ללא re-OCR/LLM); (4) **סימון (לא מחיקה)** הלכות ש-`supporting_quote` שלהן בתוך ה-preamble שהוסר → `pending_review` + quality_flag `nevo_preamble_leak`. **שומר-בטיחות:** שורות עם keep%<`--min-keep` (ברירת-מחדל 60) מוחרגות מ-`--apply` כחשד over-strip (אלא אם `--include-suspicious`). **dry-run כברירת-מחדל**; `--apply` כותב backup JSON + manifest CSV ל-`data/audit/` תחילה. idempotent. רץ עם venv של mcp-server. **chair-gated** (לאמת manifest לפני apply) | מיגרציית-נתונים — dry-run בוצע (19 פסקים, 27 הלכות מזוהמות); apply ממתין לאישור |
|
||||
| `nevo_ratio_benchmark.py` | python | **#86.3** — מדידת איכות חילוץ-הלכות מול ה-מיני-רציו של נבו (gold-set מקצועי חינמי). לכל פסק עם `nevo_ratio` (או נגזר מ-`full_text` אם טרם בוצע backfill): LLM-judge מקומי (`claude_session`, אפס עלות) ממפה סמנטית את הלכות-המערכת מול הלכות-נבו ומפיק **recall** (כיסוי הלכות-נבו), **precision** (אחוז הלכותינו הממופות), **granularity** (יחס פירוק — איתות over-extraction ל-#81.5). `--case <num>` / `--all [--limit N]` / `--model` / `--out`. כותב CSV ל-`data/audit/`. רץ עם venv של mcp-server (דורש Claude CLI מקומי). אומת על בג"ץ 1764/05: recall 0.875, precision 1.0, granularity 1.75x | ידני — מדידת-איכות (CI/ad-hoc) |
|
||||
| `halacha_goldset.py` | python | **#81.7** — הארנס gold-set לאיכות חילוץ-הלכות. `export --n N` מייצא מדגם מרובד (לפי precedent×rule_type) ל-CSV עם עמודות-תיוג ריקות (`is_holding`/`correct_type`/`quote_complete`) לתיוג ידני (חיים/דפנה). `score --in <csv>` קורא את ה-CSV המתויג ומודד כל ולידטור (`compute_quality_flags`/`is_fact_dependent`/`is_quote_truncated`/`is_thin_restatement`) מול אמת-המידה האנושית: P/R/F1 + confusion. בסיס ל-#81.8 (כיול סף האישור). מייבא את אותם ולידטורים שה-extractor מריץ. רץ עם venv של mcp-server. **הערה:** קיים גם דף-תיוג אינטראקטיבי DB-backed (`/goldset`) — זה ה-CSV-fallback | ידני — export→תיוג→score |
|
||||
| `goldset_ai_recommend.py` | python | **#81.7 QA** — מייצר **חוות-דעת-AI שנייה** (claude מקומי, אפס עלות) לכל פריט ב-`halacha_goldset`: `is_holding`+`type`+נימוק, נשמר ב-`ai_*` ומוצג בדף לצד התיוג האנושי לזיהוי אי-הסכמות. **עצמאי** מהוולידטורים שנמדדים (אין מעגליות) ו**לא** מוחל אוטומטית. `--force` (חידוש)/`--limit N`. **חובה מקומי** (claude_session). | ידני — לאחר יצירת/הרחבת batch |
|
||||
| `goldset_panel_label.py` | python | **#81.7 — תיוג ה-gold-set בקונצנזוס תלת-מודלי (ללא man-in-the-loop, הנחיית-יו"ר 2026-06-11).** מריץ את שלושת השופטים העצמאיים (Opus/claude_session · DeepSeek · Gemini, מיובאים מ-`halacha_panel_approve`) עם ה-prompt העשיר (`is_holding`+`type`+נימוק מ-`goldset_ai_recommend`) על כל פריט; **רוב 2/3 נכתב ל-`is_holding`/`correct_type`** עם `tagged_by='panel:opus+deepseek+gemini'` (פיצול→NULL→יו"ר, INV-G10). מודד **Fleiss κ** (3 מעריכים) ומריץ **מבחן-אנונימיזציה** (שמות-תיק ממוסכים→שיפוט-מחדש; flip=שינון). לא מעגלי — הוולידטורים הנמדדים rule-based. כותב per-model+consensus+anon ל-DB ודוח ל-`data/audit/`. **מחליף** תיוג-ידני; `goldset_ai_recommend`/`goldset_independent_judge` נשארים כבדיקות single-model. `--limit`/`--no-anon`/`--force`. **חובה מקומי**. | ידני — לאחר יצירת/הרחבת batch |
|
||||
| `goldset_ai_recommend.py` | python | **#81.7 QA (single-model, נבלע ב-panel)** — חוות-דעת claude בלבד ל-`ai_*`. כעת לינאז' 1/3 בתוך `goldset_panel_label`; נשאר כבדיקת-claude עצמאית/חידוש נקודתי. `--force`/`--limit`. **חובה מקומי**. | ידני — בדיקה נקודתית |
|
||||
| `goldset_independent_judge.py` | python | **INV-DM7 ולידציה** — שופט-תפקיד **עצמאי שני** ממודל אחר (DeepSeek API ישיר, OpenAI-compatible) ששובר את עיגון-ה-AI: מסווג rule_role **בעיוור** (בלי לראות תיוג-אדם או המלצת-claude) ומחשב מטריצת-הסכמה (deepseek↔אדם מול ai↔אדם) + ציר-גס (כלל-בר-הכללה מול application/obiter). **ממצא (2026-06-07):** ai↔אדם=100% (מעוגן), deepseek↔אדם=50% מדויק אך **92% גס** → תת-הסוג holding/interpretive/procedural עמום-מטבעו (לא לשער עליו); הציר-הגס אמין חוצה-מודלים. read-only על הזהב. `--model`/`--limit`/`--concurrency`. מפתח מ-`~/.hermes/profiles/deepseek/.env`. raw→`/tmp/goldset_judge_raw.json`. | ידני — ולידציית אמינות-תוויות |
|
||||
| `halacha_panel_approve.py` | python | **פאנל-אישור הלכות (Trust-or-Escalate, dry-run).** 3 שופטים בלתי-תלויי-לינאז' (Opus/claude_session · DeepSeek · Gemini-2.5-flash) מצביעים על ה**ציר-הגס האמין** (92% חוצה-מודלים): נקיות→"הלכה לשמירה?"; nli_unsupported→"הציטוט תומך בכלל?" (שיפוט-מחדש); פגומות→re-extraction. רק ורדיקט מוסכם פועל אוטומטית, **פיצול מסלים ליו"ר** (INV-G10). `--apply` **מחווט** (clean: רוב 2/3; nli: פה-אחד-entailed מנקה flag) — הפיך, מגבה ל-`data/audit/` קודם. מפתחות: DeepSeek מ-`~/.hermes/...`, Gemini מ-`~/.env`. **חובה מקומי**. dry-run 2026-06-07: 197→103 אוטו (פה-אחד) / ~15 (רוב). | ידני / שלב-אימות-הלכות במסלול-הסופי |
|
||||
| `style_lesson_panel.py` | python | **פאנל-סגנון דו-סוכני (למידה כפולה).** על-גבי דיסטילציית-ה-Opus (draft↔final ב-`draft_final_pairs.analysis`), שני שופטים בלתי-תלויים — DeepSeek + Gemini-2.5-flash — מצביעים לכל לקח על השאלה הגסה "האם זו הנחיית-סגנון מופשטת ובת-הכללה (INV-LRN5 — קול ולא מהות)?". הסכמה 2/2-keep → נכתב כ-`decision_lesson` (`source=panel:deepseek+gemini`); 2/2-drop → לא נכתב; פיצול/substance → מוסלם ליו"ר. `--apply` הפיך, מגבה ל-`data/audit/`. הטמעה ל-SKILL.md/lessons.md נשארת שער-יו"ר ידני (INV-G10). מפתחות כמו פאנל-ההלכות. **חובה מקומי**. `--case <num>` / `--pair-id <uuid>`. | שלב-למידה במסלול-הסופי |
|
||||
|
||||
270
scripts/goldset_panel_label.py
Normal file
270
scripts/goldset_panel_label.py
Normal file
@@ -0,0 +1,270 @@
|
||||
#!/usr/bin/env python3
|
||||
"""#81.7 — label the halacha gold-set by TRI-MODEL CONSENSUS (no man-in-the-loop).
|
||||
|
||||
Chair directive (2026-06-11): replace manual chair/Dafna tagging of the gold-set
|
||||
with the agreement of three INDEPENDENT model lineages. This is the same panel
|
||||
the live approval triage uses (``halacha_panel_approve.py``), proven to agree on
|
||||
the coarse "is this a real, keepable rule?" axis across models (92%):
|
||||
|
||||
- claude (Opus via claude_session — local CLI, zero marginal cost) [Anthropic]
|
||||
- deepseek (api.deepseek.com) [DeepSeek]
|
||||
- gemini (gemini-2.5-flash) [Google]
|
||||
|
||||
Why this is NOT circular: the validators measured downstream (#81.8 —
|
||||
compute_quality_flags / is_fact_dependent / is_quote_truncated /
|
||||
is_thin_restatement) are RULE-BASED heuristics, a different method family from
|
||||
the LLM judges. Two honesty guards:
|
||||
1. SPLIT vote (no 2/3 agreement) writes NO ground-truth label — the item
|
||||
stays NULL and escalates to the chair (INV-G10).
|
||||
2. ANONYMIZATION probe — every item is re-judged with the case identifier
|
||||
masked/faked; if the consensus flips, the verdict was keying on the
|
||||
identifier (memorization), not the legal reasoning. Reported as a
|
||||
stability rate (arXiv:2505.02172).
|
||||
|
||||
Reuses the model callers from halacha_panel_approve and the rich is_holding+type
|
||||
prompt from goldset_ai_recommend — single source, no parallel path (G2).
|
||||
|
||||
Run locally (claude_session needs the CLI; DeepSeek/Gemini keys from ~/.env):
|
||||
|
||||
cd ~/legal-ai/mcp-server
|
||||
.venv/bin/python ../scripts/goldset_panel_label.py --limit 8 # smoke
|
||||
.venv/bin/python ../scripts/goldset_panel_label.py # full, with anon
|
||||
.venv/bin/python ../scripts/goldset_panel_label.py --no-anon # skip the anon probe
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from collections import Counter
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from uuid import UUID
|
||||
|
||||
import httpx
|
||||
|
||||
from legal_mcp.services import claude_session, db
|
||||
|
||||
# Reuse the model callers (DeepSeek/Gemini HTTP) and the rich gold-set prompt —
|
||||
# importing them keeps ONE source of truth for each (G2).
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
from halacha_panel_approve import judge_deepseek, judge_gemini # noqa: E402
|
||||
from goldset_ai_recommend import SYSTEM, VALID_TYPES, _prompt # noqa: E402
|
||||
|
||||
|
||||
# ── consensus aggregation (pure — unit-tested) ────────────────────────────────
|
||||
|
||||
def consensus(votes: list[bool | None]) -> tuple[bool | None, str]:
|
||||
"""Majority of the (up to three) is_holding votes.
|
||||
|
||||
Returns ``(consensus_bool_or_None, agreement_tag)`` where the tag is one of
|
||||
'3/3' / '2/3' / 'split' / 'incomplete'. A consensus is returned only on a
|
||||
real majority; 'split' and 'incomplete' return None (→ chair).
|
||||
"""
|
||||
valid = [v for v in votes if v is not None]
|
||||
if len(valid) < 2:
|
||||
return None, "incomplete"
|
||||
yes = sum(1 for v in valid if v)
|
||||
no = len(valid) - yes
|
||||
if yes == no:
|
||||
return None, "split"
|
||||
decision = yes > no
|
||||
if len(valid) == 3 and (yes == 3 or no == 3):
|
||||
return decision, "3/3"
|
||||
return decision, "2/3"
|
||||
|
||||
|
||||
def consensus_type(per_model: list[dict | None], decided: bool | None) -> str:
|
||||
"""Most-common rule_type among the models, constrained to be consistent with
|
||||
the is_holding consensus (holding/interpretive/procedural ↔ True;
|
||||
application/obiter ↔ False). '' when undecided or no agreement."""
|
||||
if decided is None:
|
||||
return ""
|
||||
consistent = (
|
||||
{"holding", "interpretive", "procedural"} if decided
|
||||
else {"application", "obiter"}
|
||||
)
|
||||
types = [
|
||||
str(d.get("type") or "") for d in per_model
|
||||
if isinstance(d, dict) and str(d.get("type") or "") in consistent
|
||||
]
|
||||
if not types:
|
||||
return ""
|
||||
return Counter(types).most_common(1)[0][0]
|
||||
|
||||
|
||||
def fleiss_kappa(rows: list[tuple[int, int]]) -> float | None:
|
||||
"""Fleiss' kappa for binary ratings (yes_count, no_count) per item.
|
||||
|
||||
Only items rated by ALL raters (here: 3) should be passed. Returns None if
|
||||
there isn't enough data. Standard formula (Fleiss 1971)."""
|
||||
rows = [(y, n) for (y, n) in rows if (y + n) > 0]
|
||||
N = len(rows)
|
||||
if N == 0:
|
||||
return None
|
||||
n = rows[0][0] + rows[0][1]
|
||||
if n < 2 or any((y + n_) != n for (y, n_) in rows):
|
||||
return None # ragged rater counts — not well-defined
|
||||
# P_i: agreement within item i
|
||||
P = [(y * (y - 1) + nn * (nn - 1)) / (n * (n - 1)) for (y, nn) in rows]
|
||||
Pbar = sum(P) / N
|
||||
# p_j: marginal proportion per category
|
||||
p_yes = sum(y for (y, _) in rows) / (N * n)
|
||||
p_no = sum(nn for (_, nn) in rows) / (N * n)
|
||||
Pe = p_yes ** 2 + p_no ** 2
|
||||
if Pe >= 1.0:
|
||||
return 1.0 # degenerate (all one category) → perfect by convention
|
||||
return (Pbar - Pe) / (1 - Pe)
|
||||
|
||||
|
||||
# ── anonymization probe (pure — unit-tested) ──────────────────────────────────
|
||||
|
||||
_FAKE_CASE = "12345-67-89"
|
||||
_FAKE_NAME = "פלוני נ' אלמוני"
|
||||
|
||||
|
||||
def anonymize(text: str, case_number: str | None, case_name: str | None) -> str:
|
||||
"""Mask the case identifier so the model can't key on a memorized case.
|
||||
|
||||
Replaces the literal case_number and case_name (if they appear) with fake
|
||||
plausible tokens. Legal substance (the rule + quote) is untouched — only the
|
||||
identifiers that enable memorization are swapped (arXiv:2505.02172)."""
|
||||
out = text
|
||||
if case_number:
|
||||
out = out.replace(case_number, _FAKE_CASE)
|
||||
# also catch a bare nnnn-nn-nn / nnnn/nn pattern of the same case
|
||||
out = re.sub(re.escape(case_number).replace(r"\-", r"[-/]"), _FAKE_CASE, out)
|
||||
if case_name:
|
||||
out = out.replace(case_name, _FAKE_NAME)
|
||||
return out
|
||||
|
||||
|
||||
# ── one panel pass over a single item ─────────────────────────────────────────
|
||||
|
||||
def _parse(d: dict | None) -> dict | None:
|
||||
if not isinstance(d, dict) or "is_holding" not in d:
|
||||
return None
|
||||
t = str(d.get("type") or "").strip()
|
||||
return {
|
||||
"is_holding": bool(d["is_holding"]),
|
||||
"type": t if t in VALID_TYPES else "",
|
||||
"rationale": str(d.get("rationale") or "")[:300],
|
||||
}
|
||||
|
||||
|
||||
async def _judge_claude(user: str) -> dict | None:
|
||||
try:
|
||||
return await claude_session.query_json(user, system=SYSTEM, effort="low")
|
||||
except Exception: # noqa: BLE001
|
||||
return None
|
||||
|
||||
|
||||
async def panel_pass(client: httpx.AsyncClient, user: str) -> tuple[list[dict | None], bool | None, str]:
|
||||
"""Run the three judges on one prompt; return (per_model, consensus, tag)."""
|
||||
c, ds, gm = await asyncio.gather(
|
||||
_judge_claude(user),
|
||||
judge_deepseek(client, SYSTEM, user),
|
||||
judge_gemini(client, SYSTEM, user),
|
||||
)
|
||||
per = [_parse(c), _parse(ds), _parse(gm)]
|
||||
decided, tag = consensus([m["is_holding"] if m else None for m in per])
|
||||
return per, decided, tag
|
||||
|
||||
|
||||
async def main(args: argparse.Namespace) -> int:
|
||||
print(f"keys — deepseek:{bool(db and True)} (see panel) · claude:local · anon:{not args.no_anon}\n",
|
||||
flush=True)
|
||||
items = await db.goldset_list(args.batch)
|
||||
todo = [it for it in items if args.force or not it.get("panel_generated_at")]
|
||||
if args.limit:
|
||||
todo = todo[: args.limit]
|
||||
print(f"gold-set '{args.batch}': {len(items)} items, {len(todo)} to label by panel", flush=True)
|
||||
|
||||
sem = asyncio.Semaphore(args.concurrency)
|
||||
tags: Counter = Counter()
|
||||
kappa_rows: list[tuple[int, int]] = []
|
||||
anon_checked = anon_stable = 0
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
async def run(i: int, it: dict) -> None:
|
||||
nonlocal anon_checked, anon_stable
|
||||
async with sem:
|
||||
user = _prompt(it)
|
||||
per, decided, tag = await panel_pass(client, user)
|
||||
|
||||
anon_hold = anon_st = None
|
||||
if not args.no_anon and decided is not None:
|
||||
anon_user = anonymize(user, it.get("case_number"), it.get("case_name"))
|
||||
_, anon_decided, _ = await panel_pass(client, anon_user)
|
||||
if anon_decided is not None:
|
||||
anon_hold = anon_decided
|
||||
anon_st = (anon_decided == decided)
|
||||
anon_checked += 1
|
||||
anon_stable += int(anon_st)
|
||||
|
||||
ctype = consensus_type(per, decided)
|
||||
await db.goldset_set_panel_label(
|
||||
UUID(str(it["id"])),
|
||||
claude=per[0], deepseek=per[1], gemini=per[2],
|
||||
consensus_is_holding=decided, consensus_type=ctype,
|
||||
agreement=tag, anon_is_holding=anon_hold, anon_stable=anon_st,
|
||||
)
|
||||
tags[tag] += 1
|
||||
# κ counts only items all three judged
|
||||
nv = [m for m in per if m is not None]
|
||||
if len(nv) == 3:
|
||||
y = sum(1 for m in nv if m["is_holding"])
|
||||
kappa_rows.append((y, 3 - y))
|
||||
mark = {"3/3": "✓✓✓", "2/3": "✓✓", "split": "⚖", "incomplete": "…"}[tag]
|
||||
astr = "" if anon_st is None else (" anon✓" if anon_st else " anon✗FLIP")
|
||||
print(f"[{i}/{len(todo)}] {it.get('case_number')}: {mark} {tag} "
|
||||
f"→ {decided}/{ctype}{astr}", flush=True)
|
||||
|
||||
tasks = [run(i, it) for i, it in enumerate(todo, 1)]
|
||||
for j in range(0, len(tasks), args.concurrency):
|
||||
await asyncio.gather(*tasks[j : j + args.concurrency])
|
||||
|
||||
kappa = fleiss_kappa(kappa_rows)
|
||||
decided_n = tags["3/3"] + tags["2/3"]
|
||||
print("\n" + "=" * 60)
|
||||
print(f"PANEL LABELING — gold-set '{args.batch}'")
|
||||
print("=" * 60)
|
||||
print(f" 3/3 unanimous : {tags['3/3']}")
|
||||
print(f" 2/3 majority : {tags['2/3']}")
|
||||
print(f" ⚖ split→chair : {tags['split']}")
|
||||
print(f" … incomplete : {tags['incomplete']}")
|
||||
print(f" DECIDED (labels written): {decided_n}/{len(todo)}")
|
||||
if kappa is not None:
|
||||
interp = ("almost-perfect" if kappa >= 0.8 else "substantial" if kappa >= 0.6
|
||||
else "moderate" if kappa >= 0.4 else "fair/poor")
|
||||
print(f" Fleiss κ (3 raters, is_holding, n={len(kappa_rows)}): {kappa:.3f} ({interp})")
|
||||
if anon_checked:
|
||||
rate = anon_stable / anon_checked
|
||||
print(f" anonymization stability: {anon_stable}/{anon_checked} = {rate:.1%} "
|
||||
f"({'robust' if rate >= 0.9 else 'CHECK memorization'})")
|
||||
|
||||
ts = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
||||
report = Path(__file__).resolve().parent.parent / "data" / "audit" / f"goldset-panel-{args.batch}-{ts}.json"
|
||||
report.parent.mkdir(parents=True, exist_ok=True)
|
||||
report.write_text(json.dumps({
|
||||
"batch": args.batch, "labeled": len(todo), "agreement": dict(tags),
|
||||
"decided": decided_n, "fleiss_kappa": kappa,
|
||||
"anon_checked": anon_checked, "anon_stable": anon_stable,
|
||||
}, ensure_ascii=False, indent=2))
|
||||
print(f"\nreport → {report}")
|
||||
print("next: .venv/bin/python ../scripts/halacha_goldset.py score "
|
||||
"(measures validators vs the consensus labels — #81.8)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ap = argparse.ArgumentParser(description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
ap.add_argument("--batch", default="default")
|
||||
ap.add_argument("--force", action="store_true", help="re-label even if already paneled")
|
||||
ap.add_argument("--limit", type=int, default=0)
|
||||
ap.add_argument("--concurrency", type=int, default=4)
|
||||
ap.add_argument("--no-anon", action="store_true", help="skip the anonymization probe")
|
||||
raise SystemExit(asyncio.run(main(ap.parse_args())))
|
||||
Reference in New Issue
Block a user