feat(plans): הרשאות-סוכנים + dedup/merge/edit + API לתור-אישור תכניות (backend)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 3s
Lint — undefined names / undefined-names (pull_request) Successful in 10s

Follow-ups למרשם-התכניות (PR #252), חלק ה-backend (ללא-UI):
- סוכנים: הוספת extract_plans/plan_get/plan_search/plan_list ל-CEO ול-חוקר
  (+ plan_upsert לחוקר); plan_review נשאר אנושי בלבד (G10). .claude/agents/*.md.
- db: _plan_core_token + find_similar_plans (הצפת כפילות-וריאנט לאישור ידני, בלי
  מיזוג-אוטומטי), update_plan (עריכה+renumber, guard התנגשות→merge), merge_plans
  (איחוד aliases, מילוי-חוסר, סתירות→discrepancies, מחיקת מקור).
- plans_extractor: צירוף possible_duplicates לפלט החילוץ.
- web/app.py: GET /api/plans(+/{id},/{id}/duplicates) · POST /api/plans · PATCH
  /api/plans/{id} · POST /api/plans/{id}/review · POST /api/plans/merge; +קטגוריית
  "תכניות הממתינות לאישור" ב-/api/chair/pending.

תיקון-נתונים (DB, מחוץ ל-PR): הל/מח/250 ד' → 7.1.2002, י"פ 5045.
ה-UI (טאב /precedents + מונה /approvals) ב-PR נפרד אחרי שער Claude Design.

Invariants: G1 (נרמול בכתיבה/עריכה) · G2 · G3 · G10 (review_status + מיזוג ידני,
אישור אנושי) · INV-DM2/DM5 · INV-AH · X9 · אין בליעה שקטה (discrepancies).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-14 14:39:46 +00:00
parent 09ea1ee599
commit 46bcaa8fa3
5 changed files with 340 additions and 2 deletions

View File

@@ -182,12 +182,25 @@ async def extract_plans_for_case(case_id: UUID) -> dict:
"status": "completed", "candidates": len(cands),
})
# Surface near-duplicates for the chair to merge manually (G10) — never
# auto-merged. A variant of an existing plan written differently won't share
# the normalized key, so flag it here instead of silently creating a dup.
plans_out = list(seen_numbers.values())
dup_hits = 0
for p in plans_out:
sims = await db.find_similar_plans(
p["plan_number"], p.get("display_name", ""), exclude_id=UUID(p["id"]),
)
p["possible_duplicates"] = sims
dup_hits += len(sims)
return {
"status": "completed",
"case_number": source_case_number,
"documents_scanned": len(by_doc),
"total_candidates": total_candidates,
"distinct_plans": len(seen_numbers),
"plans": list(seen_numbers.values()),
"distinct_plans": len(plans_out),
"possible_duplicate_hits": dup_hits,
"plans": plans_out,
"by_document": by_doc,
}