feat(plans): משיכת תב"ע מ-מנהל-התכנון (mavat) — Phase C backend-slice
ליבת-המשיכה למרשם-התכניות (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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user