Compare commits
3 Commits
e096c51037
...
worktree-s
| Author | SHA1 | Date | |
|---|---|---|---|
| e4651a9d06 | |||
| a571ad535b | |||
| afc1548bca |
@@ -463,6 +463,7 @@ The draft's biggest structural error was adding the "נבאר" doctrinal paragra
|
|||||||
- **Problem:** legal-writer updates `decision_blocks` in the DB, but legal-qa reads from `drafts/decision.md` on disk. In CMPA-62 the writer reported updating block headers in DB but the file did not re-sync, causing QA-2 to fail on exactly the same issue twice.
|
- **Problem:** legal-writer updates `decision_blocks` in the DB, but legal-qa reads from `drafts/decision.md` on disk. In CMPA-62 the writer reported updating block headers in DB but the file did not re-sync, causing QA-2 to fail on exactly the same issue twice.
|
||||||
- **Lesson:** Single source of truth is mandatory — either the writer must write to BOTH the DB and the decision.md file in one atomic step, or there must be an automatic `regenerate-draft` hook that runs after every block update so the file always reflects the latest DB state. Two unsynchronized sources will keep producing the same false-fail loop.
|
- **Lesson:** Single source of truth is mandatory — either the writer must write to BOTH the DB and the decision.md file in one atomic step, or there must be an automatic `regenerate-draft` hook that runs after every block update so the file always reflects the latest DB state. Two unsynchronized sources will keep producing the same false-fail loop.
|
||||||
- **Owner:** Infrastructure task — not a writer/QA prompt fix.
|
- **Owner:** Infrastructure task — not a writer/QA prompt fix.
|
||||||
|
- **✅ RESOLVED (GAP-88, 2026-06-06):** `block_writer._update_draft_file` is now an automatic regenerate hook called from `store_block` (every persist) **and** `renumber_all_blocks` — so `drafts/decision.md` always reflects `decision_blocks`. legal-qa already validates against the DB; both sides are now identical.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1088,37 +1088,39 @@ async def save_block_content(case_id: UUID, block_id: str, content: str) -> dict
|
|||||||
result["generation_type"] = "claude-code"
|
result["generation_type"] = "claude-code"
|
||||||
result["model_used"] = "claude-code"
|
result["model_used"] = "claude-code"
|
||||||
|
|
||||||
await store_block(UUID(decision["id"]), result)
|
await store_block(UUID(decision["id"]), result) # store_block syncs the file (#35)
|
||||||
await db.mark_blocks_stale(case_id, False)
|
await db.mark_blocks_stale(case_id, False)
|
||||||
|
|
||||||
# Also write/update the draft file on disk
|
|
||||||
await _update_draft_file(case_id, UUID(decision["id"]))
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
async def _update_draft_file(case_id: UUID, decision_id: UUID) -> None:
|
async def _update_draft_file(decision_id: UUID) -> None:
|
||||||
"""Rebuild drafts/decision.md from all blocks in DB."""
|
"""Rebuild drafts/decision.md from all blocks in DB — the single
|
||||||
from pathlib import Path
|
regenerate-draft hook (lessons #35 / GAP-88). Called after EVERY
|
||||||
|
decision_blocks mutation (store_block, renumber) so the on-disk file never
|
||||||
case = await db.get_case(case_id)
|
drifts from the DB. legal-qa validates against the DB; export and the chair
|
||||||
if not case:
|
read the file — keeping them identical kills the "QA fails twice on the same
|
||||||
return
|
already-fixed issue" loop (CMPA-62). Resolves case from decision_id so no
|
||||||
|
caller has to thread case_id through."""
|
||||||
case_dir = config.find_case_dir(case["case_number"])
|
|
||||||
draft_dir = case_dir / "drafts"
|
|
||||||
draft_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
pool = await db.get_pool()
|
pool = await db.get_pool()
|
||||||
async with pool.acquire() as conn:
|
async with pool.acquire() as conn:
|
||||||
|
case_row = await conn.fetchrow(
|
||||||
|
"SELECT c.case_number FROM decisions d JOIN cases c ON c.id = d.case_id "
|
||||||
|
"WHERE d.id = $1",
|
||||||
|
decision_id,
|
||||||
|
)
|
||||||
|
if not case_row:
|
||||||
|
return
|
||||||
rows = await conn.fetch(
|
rows = await conn.fetch(
|
||||||
"SELECT content FROM decision_blocks WHERE decision_id = $1 AND content != '' ORDER BY block_index",
|
"SELECT content FROM decision_blocks WHERE decision_id = $1 AND content != '' ORDER BY block_index",
|
||||||
decision_id,
|
decision_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
draft_dir = config.find_case_dir(case_row["case_number"]) / "drafts"
|
||||||
|
draft_dir.mkdir(parents=True, exist_ok=True)
|
||||||
draft_path = draft_dir / "decision.md"
|
draft_path = draft_dir / "decision.md"
|
||||||
draft_path.write_text("\n\n".join(row["content"] for row in rows if row["content"]), encoding="utf-8")
|
draft_path.write_text("\n\n".join(row["content"] for row in rows if row["content"]), encoding="utf-8")
|
||||||
logger.info("Draft file updated: %s (%d blocks)", draft_path, len(rows))
|
logger.info("Draft file synced: %s (%d blocks)", draft_path, len(rows))
|
||||||
|
|
||||||
|
|
||||||
# ── Renumbering ───────────────────────────────────────────────────
|
# ── Renumbering ───────────────────────────────────────────────────
|
||||||
@@ -1172,6 +1174,11 @@ async def renumber_all_blocks(decision_id: UUID) -> dict:
|
|||||||
)
|
)
|
||||||
updated += 1
|
updated += 1
|
||||||
|
|
||||||
|
# #35 — renumber mutates content via raw UPDATE (bypasses store_block), so
|
||||||
|
# sync the draft file here too, otherwise the file keeps stale numbering.
|
||||||
|
if updated:
|
||||||
|
await _update_draft_file(decision_id)
|
||||||
|
|
||||||
return {"total_paragraphs": current_num - 1, "blocks_updated": updated}
|
return {"total_paragraphs": current_num - 1, "blocks_updated": updated}
|
||||||
|
|
||||||
|
|
||||||
@@ -1204,6 +1211,9 @@ async def store_block(decision_id: UUID, block_result: dict) -> None:
|
|||||||
block_result["model_used"],
|
block_result["model_used"],
|
||||||
block_result["temperature"],
|
block_result["temperature"],
|
||||||
)
|
)
|
||||||
|
# #35 — regenerate the on-disk draft on every persist so DB and file stay
|
||||||
|
# identical (legal-qa reads DB; export/chair read the file).
|
||||||
|
await _update_draft_file(decision_id)
|
||||||
|
|
||||||
|
|
||||||
async def write_and_store_block(
|
async def write_and_store_block(
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ CLAIMS_CHECK_PROMPT = """אתה בודק איכות החלטות משפטיות.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
async def check_claims_coverage(blocks: list[dict], claims: list[dict]) -> dict:
|
async def check_claims_coverage(blocks: list[dict], claims: list[dict], outcome: str = "") -> dict:
|
||||||
"""בדיקה סמנטית (Claude) שכל טענה נענתה בדיון."""
|
"""בדיקה סמנטית (Claude) שכל טענה נענתה בדיון."""
|
||||||
yod = next((b for b in blocks if b["block_id"] == "block-yod"), None)
|
yod = next((b for b in blocks if b["block_id"] == "block-yod"), None)
|
||||||
if not yod or not yod.get("content"):
|
if not yod or not yod.get("content"):
|
||||||
@@ -114,16 +114,26 @@ async def check_claims_coverage(blocks: list[dict], claims: list[dict]) -> dict:
|
|||||||
if not claims:
|
if not claims:
|
||||||
return {"name": "claims_coverage", "passed": True, "errors": [], "severity": "critical"}
|
return {"name": "claims_coverage", "passed": True, "errors": [], "severity": "critical"}
|
||||||
|
|
||||||
# Filter: only APPELLANT claims from original pleadings.
|
# #87/GAP-87 — only the appellant's claims from the APPEAL PLEADING itself
|
||||||
# Committee/permit_applicant claims are defensive positions, not claims
|
# must be addressed. claim_type: 'claim'=כתב ערר (mandatory), 'response'=כתב
|
||||||
# that need to be "addressed" in the discussion.
|
# תשובה, 'reply'=תגובה/השלמת-טיעון/תכתובת (supplementary correspondence — NOT
|
||||||
|
# a standalone duty to answer, especially on full acceptance). Counting reply/
|
||||||
|
# correspondence claims as "unanswered" produced false QA fails (1033-25).
|
||||||
source_claims = [
|
source_claims = [
|
||||||
c for c in claims
|
c for c in claims
|
||||||
if c.get("source_document", "") != "block-zayin"
|
if c.get("source_document", "") != "block-zayin"
|
||||||
and c.get("party_role") in ("appellant", "respondent")
|
and c.get("claim_type") == "claim"
|
||||||
|
and c.get("party_role") == "appellant"
|
||||||
]
|
]
|
||||||
if not source_claims:
|
if not source_claims:
|
||||||
# Fallback: all non-block-zayin claims
|
# Fallback: appellant/respondent pleadings, excluding supplementary replies.
|
||||||
|
source_claims = [
|
||||||
|
c for c in claims
|
||||||
|
if c.get("source_document", "") != "block-zayin"
|
||||||
|
and c.get("claim_type") != "reply"
|
||||||
|
and c.get("party_role") in ("appellant", "respondent")
|
||||||
|
]
|
||||||
|
if not source_claims:
|
||||||
source_claims = [c for c in claims if c.get("source_document", "") != "block-zayin"]
|
source_claims = [c for c in claims if c.get("source_document", "") != "block-zayin"]
|
||||||
if not source_claims:
|
if not source_claims:
|
||||||
source_claims = claims
|
source_claims = claims
|
||||||
@@ -165,9 +175,14 @@ async def check_claims_coverage(blocks: list[dict], claims: list[dict]) -> dict:
|
|||||||
total = len(source_claims)
|
total = len(source_claims)
|
||||||
covered = len(addressed) + len(partial)
|
covered = len(addressed) + len(partial)
|
||||||
|
|
||||||
|
# On full acceptance the appellant prevailed in full — not every sub-claim
|
||||||
|
# needs individual treatment (the chair noted this for correspondence claims,
|
||||||
|
# 1033-25). Relax the missing-tolerance accordingly.
|
||||||
|
allowed_missing_ratio = 0.4 if outcome == "full_acceptance" else 0.2
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"name": "claims_coverage",
|
"name": "claims_coverage",
|
||||||
"passed": len(missing) <= total * 0.2, # Allow up to 20% missing
|
"passed": len(missing) <= total * allowed_missing_ratio,
|
||||||
"errors": errors,
|
"errors": errors,
|
||||||
"severity": "critical",
|
"severity": "critical",
|
||||||
"details": f"{covered}/{total} טענות נענו ({covered/total*100:.0f}%), {len(partial)} חלקית, {len(missing)} חסרות",
|
"details": f"{covered}/{total} טענות נענו ({covered/total*100:.0f}%), {len(partial)} חלקית, {len(missing)} חסרות",
|
||||||
@@ -361,8 +376,10 @@ async def validate_decision(case_id: UUID) -> dict:
|
|||||||
# Get claims
|
# Get claims
|
||||||
claims = await db.get_claims(case_id)
|
claims = await db.get_claims(case_id)
|
||||||
|
|
||||||
# Determine appeal type
|
# Determine appeal type + outcome (outcome relaxes claims coverage on full acceptance — #87)
|
||||||
appeal_type = case.get("appeal_type", "licensing")
|
appeal_type = case.get("appeal_type", "licensing")
|
||||||
|
from legal_mcp.services.lessons import canonical_outcome
|
||||||
|
outcome = canonical_outcome(decision.get("outcome", "") or "")
|
||||||
|
|
||||||
# Run all checks
|
# Run all checks
|
||||||
# Run sync checks
|
# Run sync checks
|
||||||
@@ -370,7 +387,7 @@ async def validate_decision(case_id: UUID) -> dict:
|
|||||||
check_neutral_background(blocks),
|
check_neutral_background(blocks),
|
||||||
]
|
]
|
||||||
# Async check: claims coverage with Claude
|
# Async check: claims coverage with Claude
|
||||||
results.append(await check_claims_coverage(blocks, claims))
|
results.append(await check_claims_coverage(blocks, claims, outcome))
|
||||||
# More sync checks
|
# More sync checks
|
||||||
results.extend([
|
results.extend([
|
||||||
check_weight_compliance(blocks, appeal_type),
|
check_weight_compliance(blocks, appeal_type),
|
||||||
|
|||||||
@@ -27,6 +27,62 @@ _BLOCK_TO_SECTION = {
|
|||||||
"block-yod-alef": "summary",
|
"block-yod-alef": "summary",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# chunker section_type → golden-ratio section (for corpus measurement, T10)
|
||||||
|
_CHUNK_SECTION_TO_GOLDEN = {
|
||||||
|
"facts": "background", "intro": "background",
|
||||||
|
"appellant_claims": "claims", "respondent_claims": "claims",
|
||||||
|
"legal_analysis": "discussion",
|
||||||
|
"conclusion": "summary", "ruling": "summary",
|
||||||
|
}
|
||||||
|
|
||||||
|
_CORPUS_RATIOS_CACHE: dict | None = None
|
||||||
|
|
||||||
|
|
||||||
|
async def measure_corpus_ratios() -> dict:
|
||||||
|
"""Measure ACTUAL section %-of-total from Dafna's style_corpus, averaged per
|
||||||
|
outcome — the empirical counterpart to lessons.GOLDEN_RATIOS (T10). Splits each
|
||||||
|
decision via chunker (accurate, not the filtered exemplars). Cached for the
|
||||||
|
process. Returns {outcome: {"n": int, "sections": {sec: pct}}}."""
|
||||||
|
global _CORPUS_RATIOS_CACHE
|
||||||
|
if _CORPUS_RATIOS_CACHE is not None:
|
||||||
|
return _CORPUS_RATIOS_CACHE
|
||||||
|
|
||||||
|
from legal_mcp.services.chunker import _split_into_sections
|
||||||
|
pool = await db.get_pool()
|
||||||
|
async with pool.acquire() as conn:
|
||||||
|
rows = await conn.fetch("SELECT full_text, outcome FROM style_corpus WHERE full_text <> ''")
|
||||||
|
|
||||||
|
# Per-outcome AND an "_all" aggregate. style_corpus.outcome is currently
|
||||||
|
# unpopulated for the imported corpus, so per-outcome may be empty — "_all"
|
||||||
|
# is the meaningful signal today, and per-outcome becomes live once outcomes
|
||||||
|
# are backfilled. No silent loss: callers see which buckets have data via n.
|
||||||
|
by_outcome: dict[str, list[dict]] = {}
|
||||||
|
for r in rows:
|
||||||
|
sect_words: dict[str, int] = {}
|
||||||
|
for stype, stext in _split_into_sections(r["full_text"]):
|
||||||
|
g = _CHUNK_SECTION_TO_GOLDEN.get(stype)
|
||||||
|
if g:
|
||||||
|
sect_words[g] = sect_words.get(g, 0) + len(stext.split())
|
||||||
|
total = sum(sect_words.values())
|
||||||
|
if total < 100: # sections didn't parse — skip
|
||||||
|
continue
|
||||||
|
pct = {s: w / total * 100 for s, w in sect_words.items()}
|
||||||
|
by_outcome.setdefault("_all", []).append(pct)
|
||||||
|
outcome = canonical_outcome(r["outcome"] or "")
|
||||||
|
if outcome:
|
||||||
|
by_outcome.setdefault(outcome, []).append(pct)
|
||||||
|
|
||||||
|
result: dict = {}
|
||||||
|
for outcome, decs in by_outcome.items():
|
||||||
|
avg = {}
|
||||||
|
for sec in ("background", "claims", "discussion", "summary"):
|
||||||
|
vals = [d.get(sec, 0.0) for d in decs]
|
||||||
|
if vals:
|
||||||
|
avg[sec] = round(sum(vals) / len(vals), 1)
|
||||||
|
result[outcome] = {"n": len(decs), "sections": avg}
|
||||||
|
_CORPUS_RATIOS_CACHE = result
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def count_anti_patterns(text: str) -> dict:
|
def count_anti_patterns(text: str) -> dict:
|
||||||
"""Count each anti-pattern occurrence in text. Lower = closer to Dafna."""
|
"""Count each anti-pattern occurrence in text. Lower = closer to Dafna."""
|
||||||
|
|||||||
@@ -170,6 +170,41 @@ async def get_style_guide() -> str:
|
|||||||
)
|
)
|
||||||
result += "\n"
|
result += "\n"
|
||||||
|
|
||||||
|
# T10 — measured-from-corpus ratios alongside the targets, ⚠️ flags a gap
|
||||||
|
# (actual average outside the target range → revisit the target or the corpus).
|
||||||
|
try:
|
||||||
|
from legal_mcp.services.style_distance import measure_corpus_ratios
|
||||||
|
measured = await measure_corpus_ratios()
|
||||||
|
if measured:
|
||||||
|
result += "### נמדד מהקורפוס בפועל (ממוצע) — ⚠️ = פער מהיעד\n\n"
|
||||||
|
result += "| קבוצה | רקע | טענות | דיון | סיכום |\n|---|------|-------|------|-------|\n"
|
||||||
|
# Per-outcome rows (flagged vs that outcome's target), when outcomes exist.
|
||||||
|
for outcome in VALID_OUTCOMES:
|
||||||
|
m = measured.get(outcome)
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
tgt = GOLDEN_RATIOS[outcome]
|
||||||
|
cells = []
|
||||||
|
for sec in ("background", "claims", "discussion", "summary"):
|
||||||
|
val = m["sections"].get(sec)
|
||||||
|
if val is None:
|
||||||
|
cells.append("—")
|
||||||
|
continue
|
||||||
|
lo, hi = tgt[sec]
|
||||||
|
cells.append(f"{val}%" + ("" if lo <= val <= hi else " ⚠️"))
|
||||||
|
result += f"| {outcome_labels[outcome]} (n={m['n']}) | " + " | ".join(cells) + " |\n"
|
||||||
|
# "_all" aggregate — the meaningful row today (corpus outcome unpopulated);
|
||||||
|
# shown informationally (no single target to flag against).
|
||||||
|
allm = measured.get("_all")
|
||||||
|
if allm:
|
||||||
|
cells = [f"{allm['sections'].get(s, '—')}%" if allm['sections'].get(s) is not None else "—"
|
||||||
|
for s in ("background", "claims", "discussion", "summary")]
|
||||||
|
result += f"| כל ההחלטות (n={allm['n']}) | " + " | ".join(cells) + " |\n"
|
||||||
|
result += ("\n_⚠️ = הממוצע בפועל חורג מטווח-היעד; שקול לעדכן יעד ב-/methodology או לבדוק את הקורפוס. "
|
||||||
|
"פיצול לפי-תוצאה יופיע כש-`style_corpus.outcome` יאוכלס._\n\n")
|
||||||
|
except Exception as e: # surfaced, not swallowed
|
||||||
|
result += f"_מדידת יחסי-זהב מהקורפוס נכשלה: {e}_\n\n"
|
||||||
|
|
||||||
# Opening and summary strategies
|
# Opening and summary strategies
|
||||||
result += "## אסטרטגיות פתיחה וסיכום לפי תוצאה\n\n"
|
result += "## אסטרטגיות פתיחה וסיכום לפי תוצאה\n\n"
|
||||||
for outcome in VALID_OUTCOMES:
|
for outcome in VALID_OUTCOMES:
|
||||||
|
|||||||
@@ -1113,6 +1113,52 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
"/api/cases/{case_number}/decision-blocks": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Api Get Decision Blocks
|
||||||
|
* @description Return all 12 decision blocks as JSON (empty blocks included).
|
||||||
|
*
|
||||||
|
* Read path for the interactive block viewer — content lives in
|
||||||
|
* decision_blocks but was previously only reachable via DOCX export.
|
||||||
|
*/
|
||||||
|
get: operations["api_get_decision_blocks_api_cases__case_number__decision_blocks_get"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/cases/{case_number}/decision-blocks/{block_id}": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get?: never;
|
||||||
|
/**
|
||||||
|
* Api Update Decision Block
|
||||||
|
* @description Save inline-edited content for a single decision block.
|
||||||
|
*
|
||||||
|
* Writes to decision_blocks (upsert, status='draft') and rebuilds the
|
||||||
|
* on-disk decision.md. Creates a decision row if none exists yet.
|
||||||
|
*/
|
||||||
|
put: operations["api_update_decision_block_api_cases__case_number__decision_blocks__block_id__put"];
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
"/api/cases/{case_number}/learn": {
|
"/api/cases/{case_number}/learn": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@@ -1959,6 +2005,88 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
"/api/learning/pairs": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Api Learning Pairs
|
||||||
|
* @description פנקס-ההתאמה (INV-LRN4) — כל ההחלטות וסטטוס ההשוואה מול הסופי.
|
||||||
|
* status אופציונלי: final_received / analyzed / lessons_folded.
|
||||||
|
*/
|
||||||
|
get: operations["api_learning_pairs_api_learning_pairs_get"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/learning/style-distance/{case_number}": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Api Learning Style Distance
|
||||||
|
* @description מדד מרחק-סגנון (T7) לתיק — האם הטיוטה מתכנסת לדפנה.
|
||||||
|
*/
|
||||||
|
get: operations["api_learning_style_distance_api_learning_style_distance__case_number__get"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/learning/pairs/{pair_id}": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Api Learning Pair Detail
|
||||||
|
* @description פירוט שורת-פנקס כולל הצעת-הדיסטילציה (analysis) לאישור יו"ר (T14).
|
||||||
|
*/
|
||||||
|
get: operations["api_learning_pair_detail_api_learning_pairs__pair_id__get"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/learning/pairs/{pair_id}/promote": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get?: never;
|
||||||
|
put?: never;
|
||||||
|
/**
|
||||||
|
* Api Learning Promote
|
||||||
|
* @description שער-יו"ר (INV-G10/LRN1): מאשר לקחי-סגנון + ביטויי-מעבר מהצעת-הדיסטילציה
|
||||||
|
* ומטמיע אותם בערוצים שהכותב צורך (methodology overrides → T15). מקדם status.
|
||||||
|
*/
|
||||||
|
post: operations["api_learning_promote_api_learning_pairs__pair_id__promote_post"];
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
"/api/admin/skills": {
|
"/api/admin/skills": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@@ -2254,7 +2382,14 @@ export interface paths {
|
|||||||
head?: never;
|
head?: never;
|
||||||
/**
|
/**
|
||||||
* Api Resolve Feedback
|
* Api Resolve Feedback
|
||||||
* @description Mark feedback as resolved.
|
* @description Mark feedback as resolved. When ``fold`` is true (default) and the entry
|
||||||
|
* has an extracted lesson, also wake the CEO to fold that lesson into the
|
||||||
|
* right knowledge file (the feedback→agent-knowledge loop).
|
||||||
|
*
|
||||||
|
* The fold is fire-and-forget (BackgroundTask) and best-effort — resolving
|
||||||
|
* never fails because Paperclip is down. Pass ``fold=false`` for pure
|
||||||
|
* bookkeeping resolves (e.g. from the per-case drafts panel) to avoid
|
||||||
|
* spawning a CEO run per click.
|
||||||
*/
|
*/
|
||||||
patch: operations["api_resolve_feedback_api_feedback__feedback_id__resolve_patch"];
|
patch: operations["api_resolve_feedback_api_feedback__feedback_id__resolve_patch"];
|
||||||
trace?: never;
|
trace?: never;
|
||||||
@@ -2566,7 +2701,13 @@ export interface paths {
|
|||||||
path?: never;
|
path?: never;
|
||||||
cookie?: never;
|
cookie?: never;
|
||||||
};
|
};
|
||||||
/** Halachot List */
|
/**
|
||||||
|
* Halachot List
|
||||||
|
* @description List halachot. ``exclude_low_quality`` hides flagged items (#84.1) and
|
||||||
|
* ``order_by_priority`` switches to the active-learning order (#84.3). Both
|
||||||
|
* default off so existing callers are unaffected; the review-queue view opts
|
||||||
|
* in.
|
||||||
|
*/
|
||||||
get: operations["halachot_list_api_halachot_get"];
|
get: operations["halachot_list_api_halachot_get"];
|
||||||
put?: never;
|
put?: never;
|
||||||
post?: never;
|
post?: never;
|
||||||
@@ -2746,6 +2887,11 @@ export interface components {
|
|||||||
/** Issue Id */
|
/** Issue Id */
|
||||||
issue_id?: string | null;
|
issue_id?: string | null;
|
||||||
};
|
};
|
||||||
|
/** BlockUpdateRequest */
|
||||||
|
BlockUpdateRequest: {
|
||||||
|
/** Content */
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
/** Body_api_create_feedback_api_feedback_post */
|
/** Body_api_create_feedback_api_feedback_post */
|
||||||
Body_api_create_feedback_api_feedback_post: {
|
Body_api_create_feedback_api_feedback_post: {
|
||||||
/**
|
/**
|
||||||
@@ -3475,6 +3621,19 @@ export interface components {
|
|||||||
/** Citation Formatted */
|
/** Citation Formatted */
|
||||||
citation_formatted?: string | null;
|
citation_formatted?: string | null;
|
||||||
};
|
};
|
||||||
|
/** PromoteLearningRequest */
|
||||||
|
PromoteLearningRequest: {
|
||||||
|
/**
|
||||||
|
* Lessons
|
||||||
|
* @default []
|
||||||
|
*/
|
||||||
|
lessons: string[];
|
||||||
|
/**
|
||||||
|
* Phrases
|
||||||
|
* @default []
|
||||||
|
*/
|
||||||
|
phrases: string[];
|
||||||
|
};
|
||||||
/** ReviseRequest */
|
/** ReviseRequest */
|
||||||
ReviseRequest: {
|
ReviseRequest: {
|
||||||
/** Revisions */
|
/** Revisions */
|
||||||
@@ -5263,6 +5422,73 @@ export interface operations {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
api_get_decision_blocks_api_cases__case_number__decision_blocks_get: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
case_number: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description Successful Response */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": unknown;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Validation Error */
|
||||||
|
422: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["HTTPValidationError"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
api_update_decision_block_api_cases__case_number__decision_blocks__block_id__put: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
case_number: string;
|
||||||
|
block_id: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["BlockUpdateRequest"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description Successful Response */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": unknown;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Validation Error */
|
||||||
|
422: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["HTTPValidationError"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
api_learn_api_cases__case_number__learn_post: {
|
api_learn_api_cases__case_number__learn_post: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@@ -6575,6 +6801,135 @@ export interface operations {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
api_learning_pairs_api_learning_pairs_get: {
|
||||||
|
parameters: {
|
||||||
|
query?: {
|
||||||
|
status?: string;
|
||||||
|
limit?: number;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description Successful Response */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": unknown;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Validation Error */
|
||||||
|
422: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["HTTPValidationError"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
api_learning_style_distance_api_learning_style_distance__case_number__get: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
case_number: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description Successful Response */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": unknown;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Validation Error */
|
||||||
|
422: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["HTTPValidationError"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
api_learning_pair_detail_api_learning_pairs__pair_id__get: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
pair_id: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description Successful Response */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": unknown;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Validation Error */
|
||||||
|
422: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["HTTPValidationError"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
api_learning_promote_api_learning_pairs__pair_id__promote_post: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
pair_id: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["PromoteLearningRequest"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description Successful Response */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": unknown;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Validation Error */
|
||||||
|
422: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["HTTPValidationError"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
api_list_skills_api_admin_skills_get: {
|
api_list_skills_api_admin_skills_get: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@@ -7580,6 +7935,8 @@ export interface operations {
|
|||||||
practice_area?: string;
|
practice_area?: string;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
|
exclude_low_quality?: boolean;
|
||||||
|
order_by_priority?: boolean;
|
||||||
};
|
};
|
||||||
header?: never;
|
header?: never;
|
||||||
path?: never;
|
path?: never;
|
||||||
|
|||||||
Reference in New Issue
Block a user