|
|
@@ -0,0 +1,161 @@
|
|
|
+"""
|
|
|
+Step 4: Call the LOCAL astro-mcp server via MCP SSE.
|
|
|
+Tests both paths (DB lookup + direct call) and compares angles against ground truth.
|
|
|
+"""
|
|
|
+from __future__ import annotations
|
|
|
+
|
|
|
+import asyncio
|
|
|
+import json
|
|
|
+import os
|
|
|
+from datetime import timedelta
|
|
|
+
|
|
|
+from mcp import ClientSession
|
|
|
+from mcp.client.sse import sse_client
|
|
|
+
|
|
|
+# ── Ground truth from Step 1 (Placidus, Swiss Ephemeris direct) ─────
|
|
|
+
|
|
|
+REFERENCE = {
|
|
|
+ "trump": {
|
|
|
+ "label": "Donald Trump",
|
|
|
+ "birth_datetime": "1946-06-14T10:54:00-04:00",
|
|
|
+ "lat": 40.72677,
|
|
|
+ "lon": -73.74152,
|
|
|
+ # Ground truth: swe.houses(jd, 40.72677, -73.74152, b'P')
|
|
|
+ "asc": 150.025101,
|
|
|
+ "mc": 54.423678,
|
|
|
+ },
|
|
|
+ "einstein": {
|
|
|
+ "label": "Albert Einstein",
|
|
|
+ "birth_datetime": "1879-03-14T11:30:00+00:53",
|
|
|
+ "lat": 48.39841,
|
|
|
+ "lon": 9.99155,
|
|
|
+ "asc": 98.923507,
|
|
|
+ "mc": 339.337618,
|
|
|
+ },
|
|
|
+ "chaka": {
|
|
|
+ "label": "Chaka Khan",
|
|
|
+ "birth_datetime": "1953-03-23T21:05:00-06:00",
|
|
|
+ "lat": 41.87811,
|
|
|
+ "lon": -87.62980,
|
|
|
+ "asc": 218.923115,
|
|
|
+ "mc": 137.470885,
|
|
|
+ },
|
|
|
+}
|
|
|
+
|
|
|
+TOLERANCE = 0.01
|
|
|
+
|
|
|
+ASTRO_URL = os.environ.get("ASTRO_MCP_URL", "http://127.0.0.1:7016/mcp/sse")
|
|
|
+
|
|
|
+
|
|
|
+def _payload(result):
|
|
|
+ p = getattr(result, "structuredContent", None)
|
|
|
+ if isinstance(p, dict):
|
|
|
+ return p
|
|
|
+ for item in getattr(result, "content", []) or []:
|
|
|
+ t = getattr(item, "text", None)
|
|
|
+ if isinstance(t, str) and t.strip():
|
|
|
+ try:
|
|
|
+ d = json.loads(t)
|
|
|
+ if isinstance(d, dict):
|
|
|
+ return d
|
|
|
+ except Exception:
|
|
|
+ continue
|
|
|
+ return {}
|
|
|
+
|
|
|
+
|
|
|
+def _fmt(lon):
|
|
|
+ signs = ["Ari", "Tau", "Gem", "Can", "Leo", "Vir",
|
|
|
+ "Lib", "Sco", "Sag", "Cap", "Aqu", "Pis"]
|
|
|
+ idx = int(lon // 30) % 12
|
|
|
+ deg = lon % 30.0
|
|
|
+ return f"{signs[idx]} {deg:05.2f} (abs {lon:.6f})"
|
|
|
+
|
|
|
+
|
|
|
+def check(ref, angles, houses, path_label):
|
|
|
+ asc = angles.get("ascendant", {}).get("absolute_lon")
|
|
|
+ mc = angles.get("midheaven", {}).get("absolute_lon")
|
|
|
+ h1 = houses[0]["absolute_lon"] if houses else None
|
|
|
+
|
|
|
+ asc_diff = abs(asc - ref["asc"]) if asc else float("inf")
|
|
|
+ mc_diff = abs(mc - ref["mc"]) if mc else float("inf")
|
|
|
+ asc_ok = asc_diff < TOLERANCE
|
|
|
+ mc_ok = mc_diff < TOLERANCE
|
|
|
+
|
|
|
+ print(f"\n [{path_label}]")
|
|
|
+ if asc:
|
|
|
+ print(f" ASC: {_fmt(asc)} exp: {_fmt(ref['asc'])} diff={asc_diff:.6f} {'OK' if asc_ok else 'MISMATCH'}")
|
|
|
+ else:
|
|
|
+ print(f" ASC: MISSING")
|
|
|
+ if mc:
|
|
|
+ print(f" MC: {_fmt(mc)} exp: {_fmt(ref['mc'])} diff={mc_diff:.6f} {'OK' if mc_ok else 'MISMATCH'}")
|
|
|
+ else:
|
|
|
+ print(f" MC: MISSING")
|
|
|
+
|
|
|
+ if not asc_ok or not mc_ok:
|
|
|
+ print(f" FULL ANGLES: {json.dumps(angles, indent=6, default=str)[:600]}")
|
|
|
+
|
|
|
+ return asc_ok and mc_ok
|
|
|
+
|
|
|
+
|
|
|
+async def main():
|
|
|
+ print("=" * 70)
|
|
|
+ print("STEP 4: Local astro-mcp — find where angles get corrupted")
|
|
|
+ print(f" URL: {ASTRO_URL}")
|
|
|
+ print("=" * 70)
|
|
|
+
|
|
|
+ url = ASTRO_URL
|
|
|
+ if not url.endswith("/mcp/sse"):
|
|
|
+ url = url.rstrip("/") + "/mcp/sse"
|
|
|
+
|
|
|
+ all_ok = True
|
|
|
+
|
|
|
+ async with sse_client(url, timeout=30, sse_read_timeout=30) as streams:
|
|
|
+ async with ClientSession(*streams, read_timeout_seconds=timedelta(seconds=30)) as session:
|
|
|
+ await session.initialize()
|
|
|
+
|
|
|
+ for key, ref in REFERENCE.items():
|
|
|
+ print(f"\n{'='*50}")
|
|
|
+ print(f" {ref['label']}")
|
|
|
+ print(f" Input: {ref['birth_datetime']} lat={ref['lat']} lon={ref['lon']}")
|
|
|
+
|
|
|
+ # Path A: DB lookup
|
|
|
+ chart_a = _payload(await session.call_tool(
|
|
|
+ "calculate_natal_chart_by_id",
|
|
|
+ {"person_id": key, "house_system": "placidus"},
|
|
|
+ ))
|
|
|
+ if "error" in chart_a:
|
|
|
+ print(f"\n [DB PATH] ERROR: {chart_a['error']}")
|
|
|
+ all_ok = False
|
|
|
+ else:
|
|
|
+ ok = check(ref, chart_a.get("angles", {}), chart_a.get("houses", []), "DB by_id")
|
|
|
+ if not ok:
|
|
|
+ all_ok = False
|
|
|
+
|
|
|
+ # Path B: Direct call
|
|
|
+ chart_b = _payload(await session.call_tool(
|
|
|
+ "calculate_natal_chart",
|
|
|
+ {
|
|
|
+ "birth_datetime": ref["birth_datetime"],
|
|
|
+ "latitude": ref["lat"],
|
|
|
+ "longitude": ref["lon"],
|
|
|
+ "house_system": "placidus",
|
|
|
+ },
|
|
|
+ ))
|
|
|
+ if "error" in chart_b:
|
|
|
+ print(f"\n [DIRECT PATH] ERROR: {chart_b['error']}")
|
|
|
+ all_ok = False
|
|
|
+ else:
|
|
|
+ ok = check(ref, chart_b.get("angles", {}), chart_b.get("houses", []), "DIRECT")
|
|
|
+ if not ok:
|
|
|
+ all_ok = False
|
|
|
+
|
|
|
+ print("\n" + "=" * 70)
|
|
|
+ if all_ok:
|
|
|
+ print("RESULT: LOCAL ASTRO-MCP IS CORRECT")
|
|
|
+ else:
|
|
|
+ print("RESULT: LOCAL ASTRO-MCP HAS MISMATCHES")
|
|
|
+ print("=" * 70)
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ asyncio.run(main())
|