fix(learning): honest 3-channel curator card + capture curator findings (source='curator')
תיקוני-מבנה ללולאת-הלמידה (TaskMaster #157): 1. כרטיס ה-curator (/training טאב "אוצֵר") ספר decision_lessons WHERE source='curator' — ערך שאף מסלול-קוד לא כתב → תמיד 0. עוצב-מחדש (דרך שער-העיצוב Claude Design, אושר) להציג ביושר את שלושת ערוצי-ההזנה לכותב: דיסטילציה→appeal_type_rules (180, זורם), פאנל→decision_lessons (81, ממתין), אוצֵר→source='curator'. get_curator_stats שוכתב. 2. drift ספ↔סוכן: 07-learning.md §1.1 + INV-LRN3 קבעו שהאוצֵר רושם ממצאים כ-decision_lesson source='curator', אך הסוכן כתב comments בלבד — הממצאים האיכותיים אבדו. נוסף כלי-MCP record_curator_findings + §A.5b ב-hermes-curator.md (read-only נשמר; הצעה מגודרת-שער). 3. get_recent_decision_lessons(limit=15) חתך בשקט — נוסף WARN על מה שנחתך (חוקה §6); הפתרון האמיתי = סינתזת-לקחים (TaskMaster #158). Invariants: מקיים INV-LRN1/G10 (שער-יו"ר), INV-LRN3 (לכידה מובנית), INV-IA2/IA5 (מקור-אמת יחיד), G12 (leak-guard עובר), G2 (אין מסלול מקביל — אותם מאגרים). פער-מימוש פתוח מתועד: §A לא רץ אוטומטית על mark-final (pipeline-wake exits) → נדחה לתכנון נפרד. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2651,6 +2651,22 @@ async def add_decision_lesson(
|
||||
return dict(row) if row else {}
|
||||
|
||||
|
||||
async def get_style_corpus_id_by_decision(decision_number: str) -> UUID | None:
|
||||
"""Resolve the style_corpus row id for a final decision by its number.
|
||||
|
||||
The curator knows the case_number; lessons attach to the corpus row the
|
||||
learning pipeline enrolled (enroll_style_corpus). Returns None if the final
|
||||
has not been enrolled yet (caller surfaces it — no silent attach).
|
||||
"""
|
||||
pool = await get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
row = await conn.fetchrow(
|
||||
"SELECT id FROM style_corpus WHERE decision_number = $1 LIMIT 1",
|
||||
decision_number,
|
||||
)
|
||||
return row["id"] if row else None
|
||||
|
||||
|
||||
async def update_decision_lesson(
|
||||
lesson_id: UUID,
|
||||
*,
|
||||
@@ -2866,6 +2882,12 @@ async def get_recent_decision_lessons(limit: int = 15, practice_area: str = "")
|
||||
Gate (INV-LRN1/G10): returns only CHAIR-APPROVED lessons (review_status='approved').
|
||||
Panel-written proposals stay out of the writer's context until the chair approves
|
||||
them in /training — the chair's review is the gate, not the panel's 2/2 vote.
|
||||
|
||||
No silent cap (חוקה §6): when more approved lessons match than `limit`, the
|
||||
overflow is dropped by ORDER BY created_at DESC — log it so coverage loss is
|
||||
observable, not invisible. The real fix is lesson synthesis (TaskMaster #158):
|
||||
consolidate overlapping lessons into fewer, richer ones so the set fits without
|
||||
truncation. Until then this WARN is the audit trail.
|
||||
"""
|
||||
pool = await get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
@@ -2880,6 +2902,20 @@ async def get_recent_decision_lessons(limit: int = 15, practice_area: str = "")
|
||||
LIMIT $1""",
|
||||
limit, practice_area,
|
||||
)
|
||||
if len(rows) >= limit:
|
||||
total = await conn.fetchval(
|
||||
"""SELECT count(*) FROM decision_lessons dl
|
||||
JOIN style_corpus sc ON sc.id = dl.style_corpus_id
|
||||
WHERE dl.review_status = 'approved'
|
||||
AND ($1 = '' OR sc.practice_area = $1)""",
|
||||
practice_area,
|
||||
) or 0
|
||||
if total > limit:
|
||||
logger.warning(
|
||||
"get_recent_decision_lessons: capped %d→%d approved lessons "
|
||||
"(practice_area=%r) — %d not reaching the writer. See TaskMaster #158 (synthesis).",
|
||||
total, limit, practice_area or "*", total - limit,
|
||||
)
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user