|
|
@@ -708,6 +708,7 @@ async def person_manage(
|
|
|
name: str | None = None,
|
|
|
nickname: str | None = None,
|
|
|
birth_datetime: str | None = None,
|
|
|
+ birthplace: str | None = None,
|
|
|
latitude: float | None = None,
|
|
|
longitude: float | None = None,
|
|
|
elevation: float | None = None,
|
|
|
@@ -720,6 +721,7 @@ async def person_manage(
|
|
|
name: Person's full name (required for add).
|
|
|
nickname: Optional short name for quick lookup.
|
|
|
birth_datetime: ISO 8601 UTC datetime (required for add).
|
|
|
+ birthplace: Optional birth place name (e.g., "Zurich, Switzerland").
|
|
|
latitude: Birth latitude (required for add).
|
|
|
longitude: Birth longitude (required for add).
|
|
|
elevation: Birth elevation in meters.
|
|
|
@@ -741,6 +743,7 @@ async def person_manage(
|
|
|
longitude=longitude,
|
|
|
elevation=elevation if elevation is not None else 0.0,
|
|
|
nickname=nickname,
|
|
|
+ birthplace=birthplace,
|
|
|
)
|
|
|
return {"action": "add", "person": person}
|
|
|
|
|
|
@@ -764,6 +767,7 @@ async def person_manage(
|
|
|
name=name,
|
|
|
nickname=nickname,
|
|
|
birth_datetime=birth_datetime,
|
|
|
+ birthplace=birthplace,
|
|
|
latitude=latitude,
|
|
|
longitude=longitude,
|
|
|
elevation=elevation,
|
|
|
@@ -784,6 +788,118 @@ async def person_manage(
|
|
|
return {"error": f"unknown action: {action}. Use: add, get, list, update, delete"}
|
|
|
|
|
|
|
|
|
+# ── Tool: calculate_composite_chart ──────────────────────────────────
|
|
|
+
|
|
|
+@mcp.tool()
|
|
|
+async def calculate_composite_chart(
|
|
|
+ person1_datetime: str,
|
|
|
+ person1_latitude: float,
|
|
|
+ person1_longitude: float,
|
|
|
+ person2_datetime: str,
|
|
|
+ person2_latitude: float,
|
|
|
+ person2_longitude: float,
|
|
|
+ elevation: float = 0.0,
|
|
|
+ house_system: str = "placidus",
|
|
|
+ orb_limits: dict[str, float] | None = None,
|
|
|
+) -> dict[str, Any]:
|
|
|
+ """Calculate a composite chart (midpoint method) for two people.
|
|
|
+
|
|
|
+ The composite chart represents the relationship itself as a single chart,
|
|
|
+ calculated by taking the midpoint of each pair of planetary positions.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ person1_datetime, person1_latitude, person1_longitude: Person 1 birth data.
|
|
|
+ person2_datetime, person2_latitude, person2_longitude: Person 2 birth data.
|
|
|
+ elevation: Birth elevation in meters.
|
|
|
+ house_system: House system (default: Placidus).
|
|
|
+ orb_limits: Optional orb configuration.
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ Composite chart with planets, houses, aspects, and angles.
|
|
|
+ """
|
|
|
+ sky1 = await call_sky_state(datetime=person1_datetime, lat=person1_latitude, lon=person1_longitude, elevation=elevation)
|
|
|
+ sky2 = await call_sky_state(datetime=person2_datetime, lat=person2_latitude, lon=person2_longitude, elevation=elevation)
|
|
|
+
|
|
|
+ if "error" in sky1:
|
|
|
+ return {"error": f"person1: {sky1['error']}"}
|
|
|
+ if "error" in sky2:
|
|
|
+ return {"error": f"person2: {sky2['error']}"}
|
|
|
+
|
|
|
+ bodies1 = extract_bodies(sky1)
|
|
|
+ bodies2 = extract_bodies(sky2)
|
|
|
+
|
|
|
+ # Composite planets via midpoint method
|
|
|
+ composite_bodies = astrology.compute_composite_chart(
|
|
|
+ [{"name": b["body"], "lon": b.get("ecliptic_lon", 0.0)} for b in bodies1],
|
|
|
+ [{"name": b["body"], "lon": b.get("ecliptic_lon", 0.0)} for b in bodies2],
|
|
|
+ )
|
|
|
+
|
|
|
+ # Composite location: midpoint of birth locations
|
|
|
+ comp_lat = (person1_latitude + person2_latitude) / 2
|
|
|
+ comp_lon = (person1_longitude + person2_longitude) / 2
|
|
|
+
|
|
|
+ # Use composite datetime for house calculation
|
|
|
+ davison = astrology.compute_davison_chart(0.0, 0.0, person1_datetime, person2_datetime)
|
|
|
+
|
|
|
+ # Get composite sky state for houses and angles
|
|
|
+ # Use a date near the midpoint for house calculation
|
|
|
+ comp_sky = await call_sky_state(
|
|
|
+ datetime=person1_datetime, lat=comp_lat, lon=comp_lon, elevation=elevation,
|
|
|
+ )
|
|
|
+ if "error" in comp_sky:
|
|
|
+ return {"error": f"composite ephemeris error: {comp_sky['error']}"}
|
|
|
+
|
|
|
+ sidereal = comp_sky.get("sidereal_time", {})
|
|
|
+ lst_hours = sidereal.get("local_sidereal_time", 0.0)
|
|
|
+ houses = astrology.calculate_houses(lst_hours, comp_lat, house_system)
|
|
|
+
|
|
|
+ # Build composite planet list with house placement
|
|
|
+ composite_planets = []
|
|
|
+ for cb in composite_bodies:
|
|
|
+ ecl_lon = cb["lon"]
|
|
|
+ zodiac = astrology.ecliptic_to_zodiac(ecl_lon)
|
|
|
+ house = astrology.get_house_placement(ecl_lon, houses)
|
|
|
+ composite_planets.append({
|
|
|
+ "body": cb["name"],
|
|
|
+ "sign": zodiac["sign"],
|
|
|
+ "sign_abbreviation": zodiac["abbreviation"],
|
|
|
+ "degree_within_sign": zodiac["degree"],
|
|
|
+ "absolute_lon": zodiac["absolute_lon"],
|
|
|
+ "house": house,
|
|
|
+ })
|
|
|
+
|
|
|
+ # Aspects
|
|
|
+ aspect_bodies = [{"name": p["body"], "lon": p["absolute_lon"]} for p in composite_planets]
|
|
|
+ aspects = astrology.compute_aspects(aspect_bodies, orb_limits)
|
|
|
+ formatted_aspects = []
|
|
|
+ for asp in aspects:
|
|
|
+ formatted_aspects.append({
|
|
|
+ "body1": asp["body1"],
|
|
|
+ "body2": asp["body2"],
|
|
|
+ "aspect": asp["aspect"],
|
|
|
+ "orb": asp["orb"],
|
|
|
+ "applying": asp["applying"],
|
|
|
+ "exactness": asp["exactness"],
|
|
|
+ })
|
|
|
+
|
|
|
+ # Angles
|
|
|
+ angles = astrology.calculate_angles(lst_hours, comp_lat)
|
|
|
+
|
|
|
+ return {
|
|
|
+ "input": {
|
|
|
+ "person1": {"datetime": person1_datetime, "latitude": person1_latitude, "longitude": person1_longitude},
|
|
|
+ "person2": {"datetime": person2_datetime, "latitude": person2_latitude, "longitude": person2_longitude},
|
|
|
+ "house_system": house_system,
|
|
|
+ },
|
|
|
+ "chart_type": "composite",
|
|
|
+ "planets": composite_planets,
|
|
|
+ "houses": houses,
|
|
|
+ "aspects": formatted_aspects,
|
|
|
+ "angles": angles,
|
|
|
+ "composite_location": {"latitude": comp_lat, "longitude": comp_lon},
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
# ── Tool: list_house_systems ─────────────────────────────────────────
|
|
|
|
|
|
@mcp.tool()
|