feat(x11): treatment-aware citation authority wired into research agents (#154)
The internal citation graph fed only RANKING (raw in-degree), and the per-citation
TREATMENT was never classified — so a precedent distinguished N times got the same
authority boost as one followed N times (INV-COR2 violation), and the signal never
reached the agents' reasoning. Wires the full path:
Phase 1 — scripts/classify_citation_treatments.py: classify each linked edge's
treatment (followed/distinguished/…) from its match_context via
corroboration.classify_treatment (Opus 4.8 @ xhigh, local), filling
precedent_internal_citations.treatment. Idempotent.
Phase 2 — db.refresh_verified_layer: count only NON-negative treatments toward
verified/cite_count (INV-COR2/COR4). Unclassified counts as neutral-positive so
the signal degrades gracefully before classification runs.
Phase 3 — db.citation_authority(ids): per-precedent {total, positive, negative,
unclassified, by_treatment}. Surfaced as `cited_by` in search_precedent_library
hits and precedent_library_get, and `treatment` per incoming citation.
Phase 4 — legal-researcher/analyst/writer prompts: weigh & ARGUE authority
("הלכה שאומצה ב-N החלטות ועדת-ערר"), flag distinguished/overruled, never invent
the count (INV-AH; writer is read-only of the analyst).
Auto-approval stays kill-switched off (chair gate preserved, INV-G10). No schema
change (treatment column already existed). Operational: run the classifier +
refresh_verified_layer over the 379 edges, then sync agents across companies.
Invariants: G2 (one classifier + one authority query, reused), INV-COR2/COR3/COR4
(negative never corroborates; point-specific; ≥N), INV-G10 (no auto-approval),
INV-AH (no invented numbers).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -166,7 +166,7 @@ tools:
|
||||
10. **הכנה ל-CREAC** — לכל סוגיה רשום:
|
||||
- כלל (Rule): הכלל המשפטי/תכנוני שיעמוד בבסיס הדיון
|
||||
- עובדות מפתח (Facts): העובדות שיופיעו בשלב היישום
|
||||
- תקדים מבהיר (אם נדרש): רק אם הכלל דורש הבהרה
|
||||
- תקדים מבהיר (אם נדרש): רק אם הכלל דורש הבהרה. **שקלל סמכות (X11):** כל תוצאת `search_precedent_library` נושאת `cited_by`/`verified`/`cite_count` — כמה ועדות-ערר אימצו את התקדים. העדף תקדים עם `cited_by.positive` גבוה, וסַמֵּן אותו ל-writer במפורש: *"אומץ ב-N החלטות ועדת-ערר"* (כדי שיבסס סמכות). תקדים עם `cited_by.negative>0` (אובחן/בוטל) — סמן זהירות, אל תציגו כ-good-law.
|
||||
11. **שאלות משפטיות** — 1-3 שאלות לפי הצורך (ראה שלב 4)
|
||||
12. **עמדת ועדת הערר** — שדה ריק שיו"ר הוועדה ימלא ידנית. **חובה להוסיף לכל סוגיה!** עמדה זו תשמש כהנחיה מחייבת לסוכן הכתיבה.
|
||||
|
||||
|
||||
@@ -304,6 +304,15 @@ search_internal_decisions(
|
||||
|
||||
**מינימום:** queries לקורפוס הסמכותי = מספר סוגיות מרכזיות שזוהו.
|
||||
|
||||
#### 2ב.4ב — שקלול סמכות לפי טיפול-שיפוטי-מצטבר (X11) ⚠️
|
||||
|
||||
כל תוצאה מ-`search_precedent_library` נושאת שדה **`cited_by`** + `verified`/`cite_count` — **כמה החלטות ועדת-ערר אחרות הסתמכו על אותו תקדים ואיך טיפלו בו**. זהו אות-סמכות אנושי-מצטבר (לא ניחוש-מודל), וחובה לשקלל אותו:
|
||||
|
||||
- **`cited_by.positive` גבוה (אומץ/הוסבר ע"י הרבה ועדות)** → תקדים-עוגן. **טַען את הסמכות במפורש** בפלט-המחקר: *"הלכת [שם] (עע"מ X/YY) — אומצה ב-N החלטות ועדת-ערר"*, כדי שה-writer יוכל לבסס עליה. בין שני תקדימים שקולים-תוכן — העדף את בעל ה-`positive` הגבוה.
|
||||
- **`cited_by.negative` > 0 (אובחן/בוקר/בוטל)** → **דגל זהירות**. אל תציג תקדים שבוטל (`overruled`) כ"good law"; ציין את האבחנה. תקדים שאובחן שוב-ושוב אינו ראיה חזקה לסוגיה — בדוק את ההקשר.
|
||||
- **`unclassified`** → הטיפול טרם סווג; התייחס ל-`cite_count` הגולמי בלבד.
|
||||
- האות הזה **מחדד תיעדוף, לא מחליף קריאה** — עדיין קרא את ההלכה ואת ההקשר (INV-COR3/COR5: סמכות לסוגיה הספציפית, לא לפסק כולו).
|
||||
|
||||
#### 2ב.4א — איתור החלטה ספציפית לפי שם — פרוטוקול לפני "לא בקורפוס" ⚠️
|
||||
|
||||
שם תיק לבדו (למשל `"אגסי"`) **אינו מפתח חיפוש אמין**. ההטמעה הסמנטית והאינדקס הלקסיקלי בנויים על תוכן ההלכה/הפסקה — כך ששאילתת-שם עלולה להחזיר דווקא החלטות ש**מצטטות** את התיק, ולא את התיק עצמו. לפני שמכריזים שהחלטה אינה בקורפוס:
|
||||
|
||||
@@ -274,7 +274,7 @@ case_update(case_number, status="drafted")
|
||||
### שלב ג: לכל סוגיה — מבנה סילוגיסטי (CREAC) בקול דפנה
|
||||
1. **מסקנה** — פתח בתשובה (בקול "אנחנו" — ראה טבלה למטה)
|
||||
2. **כלל** — ציטוט סעיף החוק במלואו (לא תמצית). אם רלוונטי — סעיפי משנה כולם.
|
||||
3. **הרחבה** — תקדים רלוונטי אחד **בציטוט מלא** (לא תמצית). דפנה תמיד מצטטת בני 4-15 שורות עם הפניה `(פורסם בנבו)`.
|
||||
3. **הרחבה** — תקדים רלוונטי אחד **בציטוט מלא** (לא תמצית). דפנה תמיד מצטטת בני 4-15 שורות עם הפניה `(פורסם בנבו)`. **כשהמנתח סימן תקדים כ"אומץ ב-N החלטות ועדת-ערר" (אות-סמכות X11)** — חזק את ההסתמכות בלשון-סמכות ("הלכה מושרשת שאומצה בשורת החלטות"); זהו טיפול-שיפוטי-מצטבר אמיתי, לא ניחוש. **אל** תמציא מספר בעצמך — השתמש רק במה שהמנתח מסר (INV-AH, read-only מהמנתח).
|
||||
4. **יישום** — החל את הכלל על העובדות. הפרד ממצא עובדתי ממסקנה משפטית. השתמש בנתונים (מספרים, מידות, אחוזים).
|
||||
5. **אישור-לפני-דחייה (חובה)** — הצג את הטענה הטובה ביותר של הצד המפסיד: **"אכן [נקודה תקפה]... אולם [למה לא מכריע]"**. השימוש ב-"אכן" (לא "אמנם") הוא הסטנדרט.
|
||||
6. **למעלה מן הצורך** (חובה לטענות מרכזיות) — "גם אם היינו מקבלים את פרשנות העורר... התוצאה הייתה זהה". סוגר חלון לערעור.
|
||||
|
||||
@@ -399,6 +399,7 @@ async def list_citations_to_case_law(case_law_id: UUID) -> list[dict]:
|
||||
pic.cited_case_number,
|
||||
pic.match_context,
|
||||
pic.match_pattern,
|
||||
pic.treatment,
|
||||
pic.confidence::float AS confidence,
|
||||
pic.created_at,
|
||||
cl.case_number AS source_case_number,
|
||||
|
||||
@@ -6536,8 +6536,13 @@ async def refresh_verified_layer() -> dict:
|
||||
"""Recompute the verified/cite_count layer from chair citations (#153).
|
||||
|
||||
'verified' = the principle's SOURCE precedent was cited by a chair (any
|
||||
committee decision). 'cite_count' = # distinct chair decisions citing it. This
|
||||
is the ONLY trust signal — never human review. Idempotent (full recompute).
|
||||
committee decision) WITHOUT a negative treatment. 'cite_count' = # distinct
|
||||
chair decisions citing it whose treatment is NOT negative (X11 §4 / INV-COR2:
|
||||
a precedent *distinguished*/*criticized*/*questioned*/*overruled* must never
|
||||
gain authority from those citations). Unclassified edges (treatment='') count
|
||||
as neutral-positive until ``classify_citation_treatments.py`` labels them, so
|
||||
the signal degrades gracefully before classification has run. This is the ONLY
|
||||
trust signal — never human review. Idempotent (full recompute).
|
||||
Returns {verified_principles, verified_precedents}.
|
||||
"""
|
||||
pool = await get_pool()
|
||||
@@ -6554,6 +6559,8 @@ async def refresh_verified_layer() -> dict:
|
||||
" JOIN case_law src ON src.id = pic.source_case_law_id "
|
||||
" WHERE src.source_kind='internal_committee' "
|
||||
" AND pic.cited_case_law_id IS NOT NULL "
|
||||
" AND coalesce(pic.treatment,'') NOT IN "
|
||||
" ('distinguished','criticized','questioned','overruled') "
|
||||
" GROUP BY pic.cited_case_law_id) "
|
||||
"UPDATE halachot h SET verified=true, cite_count=cc.n, updated_at=now() "
|
||||
"FROM cc WHERE h.case_law_id = cc.id")
|
||||
@@ -6564,6 +6571,55 @@ async def refresh_verified_layer() -> dict:
|
||||
return {"verified_principles": row["vp"], "verified_precedents": row["vc"]}
|
||||
|
||||
|
||||
# X11 §4 treatment buckets (mirrors corroboration.TREATMENT_POSITIVE/NEGATIVE) —
|
||||
# kept here so the SQL layer can label a breakdown without importing the service.
|
||||
_TREATMENT_POSITIVE = ("followed", "explained")
|
||||
_TREATMENT_NEGATIVE = ("distinguished", "criticized", "questioned", "overruled")
|
||||
|
||||
|
||||
async def citation_authority(case_law_ids: list["UUID"]) -> dict[str, dict]:
|
||||
"""Per-precedent incoming-citation breakdown by treatment (X11 Phase 2, #154).
|
||||
|
||||
For each precedent id → how many DISTINCT committee decisions cite it, split
|
||||
into positive (followed/explained), negative (distinguished/criticized/
|
||||
questioned/overruled) and unclassified (treatment not yet labelled). This is the
|
||||
'cited_by N (X אומצו, Y אובחנו)' authority signal surfaced to research agents so
|
||||
they can argue authority — and avoid leaning on a precedent that was repeatedly
|
||||
distinguished/overruled. Counts distinct sources; a source with no treatment yet
|
||||
falls in 'unclassified'. Returns {} for ids with no incoming committee citations.
|
||||
"""
|
||||
if not case_law_ids:
|
||||
return {}
|
||||
pool = await get_pool()
|
||||
rows = await pool.fetch(
|
||||
"SELECT pic.cited_case_law_id::text AS id, "
|
||||
" coalesce(NULLIF(pic.treatment, ''), 'unclassified') AS t, "
|
||||
" count(DISTINCT pic.source_case_law_id) AS n "
|
||||
"FROM precedent_internal_citations pic "
|
||||
"JOIN case_law src ON src.id = pic.source_case_law_id "
|
||||
"WHERE src.source_kind = 'internal_committee' "
|
||||
" AND pic.cited_case_law_id = ANY($1::uuid[]) "
|
||||
"GROUP BY 1, 2",
|
||||
case_law_ids,
|
||||
)
|
||||
out: dict[str, dict] = {}
|
||||
for r in rows:
|
||||
d = out.setdefault(r["id"], {
|
||||
"total": 0, "positive": 0, "negative": 0, "unclassified": 0,
|
||||
"by_treatment": {},
|
||||
})
|
||||
t, n = r["t"], int(r["n"])
|
||||
d["by_treatment"][t] = d["by_treatment"].get(t, 0) + n
|
||||
d["total"] += n
|
||||
if t in _TREATMENT_POSITIVE:
|
||||
d["positive"] += n
|
||||
elif t in _TREATMENT_NEGATIVE:
|
||||
d["negative"] += n
|
||||
else:
|
||||
d["unclassified"] += n
|
||||
return out
|
||||
|
||||
|
||||
async def list_canonical_instances(canonical_id: "UUID") -> list[dict]:
|
||||
"""List all halachot (instances) sharing a canonical_id — used by the UI accordion."""
|
||||
pool = await get_pool()
|
||||
|
||||
@@ -452,10 +452,19 @@ async def get_precedent(case_law_id: UUID | str) -> dict | None:
|
||||
r["source_date"].isoformat()
|
||||
if r.get("source_date") is not None else None
|
||||
),
|
||||
"treatment": r.get("treatment") or "",
|
||||
"confidence": r.get("confidence"),
|
||||
}
|
||||
for r in raw
|
||||
]
|
||||
# Authority signal (X11 Phase 2, #154): how the citing committee decisions
|
||||
# TREATED this ruling (followed/distinguished/…) — surfaced so the chair (and
|
||||
# research agents via the tool output) can argue authority and avoid leaning on
|
||||
# a repeatedly-distinguished precedent.
|
||||
record["cited_by"] = (await db.citation_authority([case_law_id])).get(
|
||||
str(case_law_id),
|
||||
{"total": 0, "positive": 0, "negative": 0, "unclassified": 0, "by_treatment": {}},
|
||||
)
|
||||
return record
|
||||
|
||||
|
||||
|
||||
@@ -300,6 +300,20 @@ async def search_precedent_library(
|
||||
limit=limit,
|
||||
include_halachot=include_halachot,
|
||||
)
|
||||
# X11 Phase 2 (#154): attach the incoming-citation authority breakdown so the
|
||||
# research agent can WEIGH and ARGUE authority ("הלכה שאומצה ב-N החלטות ועדת-ערר")
|
||||
# — and steer clear of a precedent that committees repeatedly distinguished /
|
||||
# overruled. Batched: one query for the whole result page.
|
||||
try:
|
||||
ids = {str(r.get("case_law_id")) for r in results if r.get("case_law_id")}
|
||||
if ids:
|
||||
auth = await db.citation_authority([UUID(i) for i in ids])
|
||||
for r in results:
|
||||
cb = auth.get(str(r.get("case_law_id")))
|
||||
if cb:
|
||||
r["cited_by"] = cb
|
||||
except Exception: # noqa: BLE001 — authority is an additive signal; never break search
|
||||
pass
|
||||
elapsed_ms = int((time.perf_counter() - t0) * 1000)
|
||||
telemetry.log_search_bg(
|
||||
search_type="precedent_library",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
| `pc.sh` | bash | **wrapper לכל קריאות Paperclip API מסוכנים** — מוסיף Authorization, X-Paperclip-Run-Id (audit trail), Content-Type, base URL. תחביר: `pc.sh <METHOD> <PATH> [BODY_JSON]`. אסור `curl` ישיר ל-`$PAPERCLIP_API_URL`. ראה `HEARTBEAT.md §0`. counterpart ב-Python: `web/paperclip_api.py`. | נקרא ע"י סוכנים |
|
||||
| `sync_agents_across_companies.py` | python | **סנכרון סוכנים מ-CMP (1xxx, master) ל-CMPA (8xxx, mirror)** — Gap #25. משווה adapter_config (model/timeout/instructions/skills/etc), runtime_config (heartbeat), ושדות top-level (budget/metadata/icon/title/role). מסנן אוטומטית local skills שלא קיימים ב-mirror. לוגיקת subset (mirror יכול להחזיק יותר skills כי ה-API מוסיף required runtime skills). תומך `--verify`/`--dry-run`/`--apply [--only NAME]`. גיבוי אוטומטי. דורש `PAPERCLIP_BOARD_API_KEY`. **להריץ אחרי כל שינוי הגדרות ב-CMP.** **⚠ אם `adapter_type` שונה בין CMP ל-CMPA — `--apply` מדלג על הסוכן; `--verify` מדווח אותו רם כ-DRIFT.** בעת מעבר adapter (למשל ל-`deepseek_local`) חובה לעדכן ידנית בשתי החברות. **`--verify` יוצא exit≠0 על כל drift** (needs-sync / adapter-mismatch / missing-in-mirror) — שמיש כ-gate ל-cron/CI (GAP-21/FU-8a). | ידני אחרי כל שינוי |
|
||||
| `fix_paperclipai_skills_drift.py` | python | סקריפט חד-פעמי (בוצע 2026-05-04) שניקה drift על `paperclipai/*` skills בין CMP ל-CMPA. הסיר `paperclip-dev` מכל 14 הסוכנים, ודאג ש-`paperclip-converting-plans-to-tasks` קיים רק על CEO ו-analyst. תומך `--apply` (ברירת מחדל: dry-run). דורש `PAPERCLIP_BOARD_API_KEY`. נשמר לרפרנס למקרה שhdrift חוזר. | חד-פעמי (בוצע) |
|
||||
| `classify_citation_treatments.py` | python | **סיווג-טיפול לקצוות-ציטוט (X11 Phase 2, #154)** — לכל קצה ב-`precedent_internal_citations` (החלטת-ועדה מצטטת תקדים) מסווג את ה-`treatment` מתוך ה-`match_context` דרך `corroboration.classify_treatment` (Opus 4.8 @ xhigh, claude_session **מקומי** — לא בקונטיינר): followed/explained=חיובי, distinguished/criticized/questioned/overruled=שלילי. ממלא `precedent_internal_citations.treatment` כך ש-`refresh_verified_layer` לא יספור ציטוט שלילי כסמכות (INV-COR2) ו-`db.citation_authority` יציג פירוק לסוכנים. אידמפוטנטי (מדלג על מסווגים). `--apply`/`--limit N`/`--case-law-id UUID`. **אחרי `--apply` הרץ `build_verified_layer.py`.** דורש `HOME=/home/chaim`. | ידני / אחרי גלי-ציטוט חדשים |
|
||||
| `adapter_profiles.py` | python (module) | **רישום-פרופילי-אדפטר** — מקור-אמת יחיד ל-3 צירי-הכשל של מעבר-אדפטר: provider/default_model, instructions_mode (`file_path` בטוח-frontmatter מול `content_arg` ששובר `---`), ו-tool_config (`gemini_global` excludeTools / `frontmatter` / `hermes` / `codex_home`). כולל `codex_local` עם משפחת מודלי OpenAI/Codex (`gpt-*`, `o3*`, `o4*`, `codex-*`). מיובא ע"י `migrate_agent_adapter.py`. הוספת אדפטר עתידי = רשומה אחת. לא מורץ ישירות. | תשתית |
|
||||
| `migrate_agent_adapter.py` | python | **מעבר-אדפטר בטוח לכל סוכן ← כל אדפטר, בשתי החברות יחד (INV-MC1)**. מיישב model↔provider, גורס frontmatter לעותק `.generated/<name>.nofm.md` ל-content_arg adapters (אחרת קריסת `gemini --prompt`/`hermes -q` על `---`), ומשחרר excludeTools גלובלי של gemini (`--relax-tools`). `--check` (preflight בלבד, exit≠0 על שגיאה — שער FU-8a) / `--apply` / `--revert` (שחזור מדויק מ-sidecar `data/adapter-migration-state.json`) / `--verify` (מסמן מצב לא-תואם/א-סימטרי, exit≠0). `--agent "<שם>"\|all --to <adapter> [--model X] [--relax-tools]`. PATCH דרך `/api/agents/{id}` (לא DB). דורש `PAPERCLIP_BOARD_API_KEY`. הרץ עם `mcp-server/.venv/bin/python`. **fallback-חירום כשנגמרים טוקני-Claude; החזר ל-claude_local כשחוזרים.** | ידני לפי צורך |
|
||||
|
||||
|
||||
99
scripts/classify_citation_treatments.py
Normal file
99
scripts/classify_citation_treatments.py
Normal file
@@ -0,0 +1,99 @@
|
||||
"""Classify the TREATMENT of each internal citation edge (X11 Phase 2, #154).
|
||||
|
||||
Each row in ``precedent_internal_citations`` records that a committee decision
|
||||
cited a precedent, with the surrounding ``match_context``. Until now the edge's
|
||||
``treatment`` column was empty, so the verified/authority layer counted every
|
||||
citation as if it were positive — a precedent *distinguished* N times got the
|
||||
same authority boost as one *followed* N times (an INV-COR2 violation).
|
||||
|
||||
This script fills ``treatment`` per edge by classifying the context with
|
||||
``corroboration.classify_treatment`` (Opus 4.8 @ xhigh via the local
|
||||
claude_session bridge — LOCAL ONLY, the claude CLI is not in the container):
|
||||
|
||||
followed | explained → positive (counts toward authority)
|
||||
distinguished | criticized |
|
||||
questioned | overruled → negative (never counts; overruled = demote)
|
||||
|
||||
Scope: only LINKED edges (``cited_case_law_id IS NOT NULL``) with an empty
|
||||
``treatment`` and a non-empty ``match_context``. Idempotent — a second run skips
|
||||
rows already classified. After applying, run ``scripts/build_verified_layer.py``
|
||||
(or ``db.refresh_verified_layer``) so the treatment-aware count takes effect.
|
||||
|
||||
Run (dry-run, default — classifies and PRINTS, writes nothing):
|
||||
HOME=/home/chaim mcp-server/.venv/bin/python scripts/classify_citation_treatments.py
|
||||
Apply:
|
||||
HOME=/home/chaim mcp-server/.venv/bin/python scripts/classify_citation_treatments.py --apply
|
||||
Options:
|
||||
--limit N classify at most N edges (smoke test)
|
||||
--case-law-id UUID restrict to citations TO this one precedent
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
from uuid import UUID
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "mcp-server", "src"))
|
||||
|
||||
from legal_mcp.services import corroboration, db # noqa: E402
|
||||
|
||||
|
||||
async def _pending(limit: int | None, case_law_id: str | None) -> list[dict]:
|
||||
pool = await db.get_pool()
|
||||
where = ["cited_case_law_id IS NOT NULL", "coalesce(treatment,'') = ''",
|
||||
"coalesce(match_context,'') <> ''"]
|
||||
params: list = []
|
||||
if case_law_id:
|
||||
params.append(UUID(case_law_id))
|
||||
where.append(f"cited_case_law_id = ${len(params)}")
|
||||
sql = (f"SELECT id, cited_case_number, match_context "
|
||||
f"FROM precedent_internal_citations WHERE {' AND '.join(where)} "
|
||||
f"ORDER BY created_at")
|
||||
if limit:
|
||||
sql += f" LIMIT {int(limit)}"
|
||||
rows = await pool.fetch(sql, *params)
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--apply", action="store_true", help="write changes (default: dry-run)")
|
||||
ap.add_argument("--limit", type=int, default=None)
|
||||
ap.add_argument("--case-law-id", type=str, default=None)
|
||||
args = ap.parse_args()
|
||||
|
||||
rows = await _pending(args.limit, args.case_law_id)
|
||||
print(f"קצוות לא-מסווגים לעיבוד: {len(rows)}\n")
|
||||
|
||||
pool = await db.get_pool()
|
||||
counts: dict[str, int] = {}
|
||||
errors = 0
|
||||
for r in rows:
|
||||
try:
|
||||
t = await corroboration.classify_treatment(
|
||||
r["cited_case_number"] or "", r["match_context"] or "")
|
||||
except Exception as e: # noqa: BLE001 — one bad row must not abort the batch
|
||||
errors += 1
|
||||
print(f" ✗ [error] {r['cited_case_number']}: {type(e).__name__}: {e}")
|
||||
continue
|
||||
counts[t] = counts.get(t, 0) + 1
|
||||
sign = "+" if corroboration.is_positive(t) else ("-" if corroboration.is_negative(t) else "·")
|
||||
print(f" {sign} {t:<14} {r['cited_case_number']}")
|
||||
if args.apply:
|
||||
await pool.execute(
|
||||
"UPDATE precedent_internal_citations SET treatment = $2 WHERE id = $1",
|
||||
r["id"], t,
|
||||
)
|
||||
|
||||
print(f"\nסיכום טיפול: {counts} שגיאות={errors}"
|
||||
+ ("" if args.apply else " (dry-run — לא נכתב)"))
|
||||
if args.apply:
|
||||
print("הרץ עכשיו: scripts/build_verified_layer.py (או db.refresh_verified_layer) "
|
||||
"כדי שהספירה מודעת-הטיפול תיכנס לתוקף.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user