test_step2_local_ephemeris.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. """
  2. Step 2: Call the LOCAL ephemeris-mcp server (run.sh) via MCP SSE.
  3. Compare returned houses/angles against ground truth from Step 1.
  4. Requires: ephemeris-mcp running locally via run.sh on port 7015.
  5. """
  6. from __future__ import annotations
  7. import asyncio
  8. import json
  9. import os
  10. import sys
  11. from datetime import timedelta
  12. from mcp import ClientSession
  13. from mcp.client.sse import sse_client
  14. # ── Ground truth from Step 1 ──────────────────────────────────────────
  15. REFERENCE = {
  16. "trump": {
  17. "label": "Donald Trump",
  18. "datetime_utc": "1946-06-14T14:54:00",
  19. "lat": 40.6413,
  20. "lon": -73.7781,
  21. "asc": 149.972371,
  22. "mc": 54.387979,
  23. },
  24. "einstein": {
  25. "label": "Albert Einstein",
  26. "datetime_utc": "1879-03-14T10:37:00",
  27. "lat": 48.39841,
  28. "lon": 9.99155,
  29. "asc": 98.923507,
  30. "mc": 339.337618,
  31. },
  32. "chaka": {
  33. "label": "Chaka Khan",
  34. "datetime_utc": "1953-03-24T03:05:00",
  35. "lat": 41.87811,
  36. "lon": -87.62980,
  37. "asc": 218.923115,
  38. "mc": 137.470885,
  39. },
  40. }
  41. TOLERANCE = 0.01 # degrees
  42. EPHEMERIS_URL = os.environ.get(
  43. "EPHEMERIS_MCP_URL", "http://127.0.0.1:7015/mcp/sse"
  44. )
  45. def _payload_from_result(result):
  46. payload = getattr(result, "structuredContent", None)
  47. if isinstance(payload, dict):
  48. return payload
  49. content_items = getattr(result, "content", []) or []
  50. for item in content_items:
  51. text = getattr(item, "text", None)
  52. if isinstance(text, str) and text.strip():
  53. try:
  54. decoded = json.loads(text)
  55. except Exception:
  56. continue
  57. if isinstance(decoded, dict):
  58. return decoded
  59. return {}
  60. async def call_ephemeris(datetime_utc, lat, lon, house_system="placidus"):
  61. url = EPHEMERIS_URL
  62. if not url.endswith("/mcp/sse"):
  63. url = url.rstrip("/") + "/mcp/sse"
  64. async with sse_client(url, timeout=15.0, sse_read_timeout=15.0) as streams:
  65. async with ClientSession(
  66. *streams, read_timeout_seconds=timedelta(seconds=15)
  67. ) as session:
  68. await session.initialize()
  69. result = await session.call_tool(
  70. "get_sky_state",
  71. {
  72. "datetime": datetime_utc,
  73. "lat": lat,
  74. "lon": lon,
  75. "elevation": 0.0,
  76. "geocentric": True,
  77. "house_system": house_system,
  78. },
  79. )
  80. return _payload_from_result(result)
  81. def _fmt(lon):
  82. signs = ["Ari", "Tau", "Gem", "Can", "Leo", "Vir",
  83. "Lib", "Sco", "Sag", "Cap", "Aqu", "Pis"]
  84. idx = int(lon // 30) % 12
  85. deg = lon % 30.0
  86. return f"{signs[idx]} {deg:05.2f}° (abs {lon:.6f}°)"
  87. async def main():
  88. print("=" * 70)
  89. print("STEP 2: Local ephemeris-mcp server (run.sh) via MCP")
  90. print(f" URL: {EPHEMERIS_URL}")
  91. print("=" * 70)
  92. all_ok = True
  93. for key, ref in REFERENCE.items():
  94. print(f"\n--- {ref['label']} ---")
  95. print(f" Input: UTC={ref['datetime_utc']} lat={ref['lat']} lon={ref['lon']}")
  96. try:
  97. sky = await call_ephemeris(ref["datetime_utc"], ref["lat"], ref["lon"])
  98. except Exception as e:
  99. print(f" ERROR: Could not reach server: {e}")
  100. all_ok = False
  101. continue
  102. houses_data = sky.get("houses", {})
  103. if not houses_data or "error" in houses_data:
  104. print(f" ERROR: Server returned error: {houses_data}")
  105. all_ok = False
  106. continue
  107. server_asc = houses_data.get("ascendant")
  108. server_mc = houses_data.get("midheaven")
  109. cusps = houses_data.get("cusps", [])
  110. server_cusp1 = cusps[0]["absolute_lon"] if cusps else None
  111. print(f" Server ASC: {_fmt(server_asc)}" if server_asc else " Server ASC: MISSING")
  112. print(f" Server MC: {_fmt(server_mc)}" if server_mc else " Server MC: MISSING")
  113. print(f" Server H1: {_fmt(server_cusp1)}" if server_cusp1 else " Server H1: MISSING")
  114. print(f" Expected ASC: {_fmt(ref['asc'])}")
  115. print(f" Expected MC: {_fmt(ref['mc'])}")
  116. asc_diff = abs(server_asc - ref["asc"]) if server_asc else float("inf")
  117. mc_diff = abs(server_mc - ref["mc"]) if server_mc else float("inf")
  118. asc_ok = asc_diff < TOLERANCE
  119. mc_ok = mc_diff < TOLERANCE
  120. print(f" ASC diff: {asc_diff:.6f}° {'OK' if asc_ok else 'MISMATCH'}")
  121. print(f" MC diff: {mc_diff:.6f}° {'OK' if mc_ok else 'MISMATCH'}")
  122. if not asc_ok or not mc_ok:
  123. all_ok = False
  124. print("\n" + "=" * 70)
  125. if all_ok:
  126. print("RESULT: LOCAL EPHEMERIS SERVER IS CORRECT")
  127. else:
  128. print("RESULT: LOCAL EPHEMERIS SERVER HAS MISMATCHES")
  129. print("=" * 70)
  130. if __name__ == "__main__":
  131. asyncio.run(main())