3 Commits

Author SHA1 Message Date
e4651a9d06 feat(#99 / T10): get_style_guide — יחסי-זהב נמדדים מהקורפוס לצד היעד
style_distance.measure_corpus_ratios(): מפצל כל החלטה ב-style_corpus לסעיפים
(chunker) ומחשב ממוצע %-סעיף — אגרגט "_all" + פר-תוצאה (כשיש). cached.
get_style_guide מציג שורת "נמדד בפועל" עם ⚠️ על פער מטווח-היעד.

מצב נוכחי: style_corpus.outcome לא מאוכלס → מוצג אגרגט כל-ההחלטות (n=48:
רקע 26.4% / טענות 9.7% / דיון 43.8% / סיכום 20.1%); פיצול לפי-תוצאה future-ready.
המדידה גם מאירה מגבלות זיהוי-סעיפים (כוונת T10 — לסמן פער לבדיקה). חופף-חלקית
ל-T7 שמודד adherence per-draft; זה מודד את הקורפוס. כשל מדידה מוצג, לא נבלע.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 21:01:42 +00:00
a571ad535b fix(#88+#87): סנכרון DB↔file אוטומטי + claims_coverage מבחין כתב-ערר מתכתובת
#88 (DB↔file, lessons #35): drafts/decision.md דרסה את עצמה רק ב-save_block_content;
renumber_all_blocks + נתיבי store_block אחרים השאירו את הקובץ stale → QA נכשל
פעמיים על אותה בעיה (CMPA-62). תיקון: _update_draft_file הפך ל-hook אוטומטי
(מקבל decision_id, מאתר case פנימית) שנקרא מ-store_block (כל persist) ומ-
renumber_all_blocks. legal-qa ממילא קורא מ-DB → שני הצדדים זהים תמיד.

#87 (claims_coverage, 1033-25): טענות מתכתובת (claim_type='reply' — תגובה/
השלמת-טיעון) סומנו "לא נענו" כ-false-positive. תיקון: check_claims_coverage
דורש מענה רק לטענות כתב-הערר (claim_type='claim', appellant); reply/תכתובת
מוחרגות. בקבלה מלאה הסף מוקל (0.2→0.4) כי העורר זכה במלואו.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 20:54:31 +00:00
afc1548bca chore(style-acq T11): regen API types (learning + methodology endpoints)
npm run api:types — מסנכרן types.ts המחולל עם ה-endpoints החדשים
(/api/learning/pairs, style-distance, promote). הקוד משתמש בטיפוסים ידניים
(learning.ts) אז זה היגיינה לעתיד, לא תלות. סוגר את T11.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 20:44:41 +00:00
6 changed files with 504 additions and 28 deletions

View File

@@ -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.
--- ---

View File

@@ -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(

View File

@@ -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),

View File

@@ -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."""

View File

@@ -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:

View File

@@ -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;