render_test_charts.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. #!/usr/bin/env python3
  2. """
  3. Render test charts for two celebrities and save to /home/shared/.
  4. Usage:
  5. python3 render_test_charts.py [--format svg|png|jpg] [--size N]
  6. """
  7. import argparse
  8. import asyncio
  9. import json
  10. import sys
  11. import os
  12. # Ensure src is on the path
  13. sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
  14. from astro_mcp.tools import calculate_natal_chart, render_natal_chart
  15. # ── Birth data ───────────────────────────────────────────────────────
  16. PEOPLE = {
  17. "donald_trump": {
  18. "birth_datetime": "1946-06-14T11:00:00-04:00",
  19. "latitude": 40.70,
  20. "longitude": -73.80,
  21. "elevation": 10,
  22. },
  23. "chaka_khan": {
  24. "birth_datetime": "1953-03-23T11:00:00-06:00",
  25. "latitude": 41.88,
  26. "longitude": -87.63,
  27. "elevation": 180,
  28. },
  29. }
  30. async def render_person(name: str, birth_data: dict, fmt: str, size: int) -> dict:
  31. """Calculate and render a natal chart for one person.
  32. Returns a summary dict with moon info and output path.
  33. """
  34. # 1. Calculate chart data
  35. chart_data = await calculate_natal_chart(
  36. birth_datetime=birth_data["birth_datetime"],
  37. latitude=birth_data["latitude"],
  38. longitude=birth_data["longitude"],
  39. elevation=birth_data["elevation"],
  40. )
  41. if "error" in chart_data:
  42. print(f" ERROR calculating {name}: {chart_data['error']}")
  43. return None
  44. # 2. Render
  45. result = await render_natal_chart(
  46. birth_datetime=birth_data["birth_datetime"],
  47. latitude=birth_data["latitude"],
  48. longitude=birth_data["longitude"],
  49. elevation=birth_data["elevation"],
  50. color_mode="color",
  51. size=size,
  52. format=fmt,
  53. title=f"Natal Chart: {name.replace('_', ' ').title()}",
  54. )
  55. if "error" in result:
  56. print(f" ERROR rendering {name}: {result['error']}")
  57. return None
  58. # 3. Save
  59. ext = fmt
  60. filename = f"{name}_natal.{ext}"
  61. outdir = "/home/shared"
  62. os.makedirs(outdir, exist_ok=True)
  63. outpath = os.path.join(outdir, filename)
  64. content = result["content"]
  65. mode = "w" if isinstance(content, str) else "wb"
  66. with open(outpath, mode) as f:
  67. f.write(content)
  68. # 4. Extract moon info
  69. moon_info = None
  70. for p in chart_data.get("planets", []):
  71. if p.get("body") == "moon":
  72. moon_info = {
  73. "absolute_lon": p.get("absolute_lon"),
  74. "degree_within_sign": p.get("degree_within_sign"),
  75. "sign": p.get("sign"),
  76. "house": p.get("house"),
  77. "retrograde": p.get("retrograde"),
  78. }
  79. break
  80. return {
  81. "name": name,
  82. "output": outpath,
  83. "format": result["format"],
  84. "content_type": result["content_type"],
  85. "size_bytes": len(content) if isinstance(content, bytes) else len(content.encode("utf-8")),
  86. "moon": moon_info,
  87. "all_planets": [
  88. {"body": p.get("body"), "absolute_lon": p.get("absolute_lon"),
  89. "degree_within_sign": p.get("degree_within_sign"),
  90. "sign": p.get("sign"), "house": p.get("house"),
  91. "retrograde": p.get("retrograde")}
  92. for p in chart_data.get("planets", [])
  93. ],
  94. "houses_count": len(chart_data.get("houses", [])),
  95. "aspects_count": len(chart_data.get("aspects", [])),
  96. }
  97. async def main():
  98. parser = argparse.ArgumentParser(description="Render test natal charts")
  99. parser.add_argument("--format", default="svg", choices=["svg", "png", "jpg"],
  100. help="Output format (default: svg)")
  101. parser.add_argument("--size", type=int, default=600,
  102. help="Canvas size in pixels (default: 600)")
  103. parser.add_argument("--color-mode", default="color", choices=["color", "bw", "dark"],
  104. help="Color theme (default: color)")
  105. args = parser.parse_args()
  106. print(f"Rendering test charts — format={args.format}, size={args.size}px, theme={args.color_mode}")
  107. print(f"Output directory: /home/shared/")
  108. print()
  109. results = []
  110. for name, birth_data in PEOPLE.items():
  111. print(f"Processing {name.replace('_', ' ').title()}...")
  112. print(f" Birth: {birth_data['birth_datetime']} @ {birth_data['latitude']}, {birth_data['longitude']}")
  113. result = await render_person(name, birth_data, args.format, args.size)
  114. if result:
  115. results.append(result)
  116. print(f" Saved: {result['output']} ({result['size_bytes']:,} bytes)")
  117. if result["moon"]:
  118. m = result["moon"]
  119. print(f" MOON: {m['sign']} {m['degree_within_sign']:.1f}° (lon={m['absolute_lon']:.2f}°, house {m['house']})")
  120. else:
  121. print(f" MOON: not found in data!")
  122. print(f" Planets: {len(result['all_planets'])}, Houses: {result['houses_count']}, Aspects: {result['aspects_count']}")
  123. else:
  124. print(f" FAILED")
  125. print()
  126. # Summary comparison
  127. print("=" * 60)
  128. print("MOON COMPARISON")
  129. print("=" * 60)
  130. for r in results:
  131. m = r.get("moon", {})
  132. if m:
  133. print(f" {r['name']:20s} | Moon in {m.get('sign','?'):12s} {m.get('degree_within_sign',0):5.1f}° | House {m.get('house','?')} | Retrograde: {m.get('retrograde',False)}")
  134. else:
  135. print(f" {r['name']:20s} | Moon data MISSING")
  136. print()
  137. # Save JSON summary
  138. summary_path = "/home/shared/test_charts_summary.json"
  139. with open(summary_path, "w") as f:
  140. json.dump(results, f, indent=2, default=str)
  141. print(f"Summary saved to: {summary_path}")
  142. if __name__ == "__main__":
  143. asyncio.run(main())