feat(plans): משיכת תב"ע מ-מנהל-התכנון (mavat) — Phase C backend-slice
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 3s
Lint — undefined names / undefined-names (pull_request) Successful in 10s

ליבת-המשיכה למרשם-התכניות (V38): מספר-תכנית → זהות+תוקף מ-mavat דרך
גשר-Camoufox הקיים (G2 — אותו שירות/פורט/סוד כמו X13, בלי חדשים).

- court_fetch_service/mavat_client.py (חדש): דרייבר Camoufox מול mavat —
  עוקף F5-ASM (דפדפן-JS), search→auto-nav ל-SV4, לוכד GET /rest/api/SV4/1,
  מפענח planDetails (E_NAME/AUTH/ENTITY_SUBTYPE/GOALS) + rsInternet
  (פרסום-לאישור→ED_PUBLICATION_FILE=י"פ + DETAILS→תאריך/עמוד). מלכודת-
  דרייבר: init-script window.onerror swallow. reCAPTCHA נשאר דלוק (token).
- court_fetch_service/server.py: POST /plan-fetch (אותו Bearer).
- services/plans_fetch.py (חדש): צד-קונטיינר — httpx לגשר, מנרמל שדות.
- tools/plans.py + server.py: כלי-MCP plan_fetch (מועמד, לא כותב).
- web/app.py: POST /api/plans/fetch (503 גשר-למטה, 404 לא-נמצא).

אומת חי מול mavat: 101-1031020→י"פ 13697 (עמ' 8758, 30/07/2025),
101-1053933→י"פ 13836. מקור-אמת עשיר מתב"ע-עכשיו (שחסר י"פ).

INV-AH: כל ערך נושא source_url; שדה-חסר ריק לא מומצא. G10: מחזיר
מועמד בלבד — שער-יו"ר (review_status) נשמר. G2: מרחיב גשר+מרשם קיימים.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-17 11:09:39 +00:00
parent 20a51c572a
commit a55ffd59eb
6 changed files with 442 additions and 0 deletions

View File

@@ -43,6 +43,7 @@ if _pkg_root not in sys.path:
sys.path.insert(0, _pkg_root)
from legal_mcp.court_fetch_service import camofox_client # noqa: E402
from legal_mcp.court_fetch_service import mavat_client # noqa: E402
from legal_mcp.services import usage_limits # noqa: E402
from legal_mcp.services import script_runner # noqa: E402
@@ -273,6 +274,37 @@ async def fetch(request: web.Request) -> web.Response:
return web.json_response({"ok": False, "reason": f"unexpected: {e}"}, status=200)
async def plan_fetch(request: web.Request) -> web.Response:
"""Fetch one תב"ע's identity + validity from mavat (מנהל התכנון).
Body ``{plan_number}`` → ``{ok, plan: {...}, reason}``. Same Bearer + bind as
/fetch. The browser work (Camoufox over Xvfb past F5 ASM) lives in
``mavat_client``; expected failures (not found / blocked) come back ok=false
at HTTP 200 so the caller renders a reason rather than treating it as a 5xx.
"""
unauth = _check_bearer(request)
if unauth is not None:
return unauth
try:
body = await request.json()
except json.JSONDecodeError:
return web.json_response({"error": "invalid JSON body"}, status=400)
plan_number = str(body.get("plan_number", "")).strip()
if not plan_number:
return web.json_response({"ok": False, "reason": "missing plan_number"}, status=400)
try:
plan = await mavat_client.fetch_plan(plan_number)
return web.json_response({"ok": True, "plan": plan})
except (mavat_client.MavatUnavailable, mavat_client.MavatFlowError) as e:
# Expected, recoverable (browser unavailable / plan not found / blocked).
return web.json_response({"ok": False, "reason": str(e)}, status=200)
except Exception as e: # noqa: BLE001
logger.exception("plan_fetch failed")
return web.json_response({"ok": False, "reason": f"unexpected: {e}"}, status=200)
# ─── adapter-migration: host-side runner for scripts/migrate_agent_adapter.py ───
# The legal-ai container can't perform the migration itself (it needs the host
# filesystem — generated instruction copies, the gemini settings file — plus the
@@ -406,6 +438,7 @@ def build_app() -> web.Application:
app.router.add_get("/usage", usage_status)
app.router.add_post("/pm2/control", pm2_control)
app.router.add_post("/fetch", fetch)
app.router.add_post("/plan-fetch", plan_fetch)
app.router.add_post("/adapter-migration", adapter_migration)
app.router.add_post("/run-script", run_script)
return app