Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -9,158 +9,200 @@ from timezonefinder import TimezoneFinder
|
|
| 9 |
import pytz
|
| 10 |
import swisseph as swe
|
| 11 |
|
|
|
|
|
|
|
| 12 |
|
| 13 |
# Russian translations for planets
|
| 14 |
-
|
| 15 |
-
'Sun': 'Солнце', 'Moon': 'Луна', 'Mercury': 'Меркурий',
|
| 16 |
-
'Mars': 'Марс', 'Jupiter': 'Юпитер',
|
|
|
|
| 17 |
}
|
| 18 |
|
| 19 |
# Planet symbols for plotting
|
| 20 |
-
|
| 21 |
'Sun': '☉', 'Moon': '☾', 'Mercury': '☿', 'Venus': '♀',
|
| 22 |
'Mars': '♂', 'Jupiter': '♃', 'Saturn': '♄'
|
| 23 |
}
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
def parse_query(query):
|
| 27 |
-
"""
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
try:
|
|
|
|
|
|
|
| 34 |
dt = parser.parse(f"{date_str} {time_str}")
|
| 35 |
return dt, location, None
|
| 36 |
-
except ValueError:
|
| 37 |
-
return None, None, "Invalid
|
| 38 |
|
| 39 |
def get_utc_time(dt, location):
|
| 40 |
-
"""
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
try:
|
| 43 |
-
loc = geolocator.geocode(location)
|
| 44 |
if not loc:
|
| 45 |
-
return None, None, None, "Location not found
|
|
|
|
| 46 |
lat, lon = loc.latitude, loc.longitude
|
| 47 |
-
|
| 48 |
-
tz_str = tf.timezone_at(lng=lon, lat=lat)
|
| 49 |
if not tz_str:
|
| 50 |
-
return None, None, None, "Time zone not found
|
|
|
|
| 51 |
tz = pytz.timezone(tz_str)
|
| 52 |
local_dt = tz.localize(dt)
|
| 53 |
-
utc_dt = local_dt.astimezone(pytz.
|
| 54 |
return utc_dt, lat, lon, None
|
|
|
|
| 55 |
except Exception as e:
|
| 56 |
-
return None, None, None, f"
|
| 57 |
|
| 58 |
def format_coords(lat, lon):
|
| 59 |
-
"""
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
return f"{dms(lat, 'N', 'S')}, {dms(lon, 'E', 'W')}"
|
| 68 |
|
| 69 |
-
def lon_to_sign(
|
| 70 |
-
"""
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
def PLadder_ZSizes(utc_dt, lat, lon):
|
| 79 |
-
"""
|
| 80 |
-
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
if not -13000 <= utc_dt.year <= 17000:
|
| 83 |
-
return {"error": "Date out of
|
| 84 |
|
| 85 |
-
#
|
| 86 |
-
|
| 87 |
-
swe.set_jpl_file('de441.eph') # Use DE441 for best modern accuracy (optional)
|
| 88 |
-
|
| 89 |
-
# Planet mapping (Swiss Ephemeris constants)
|
| 90 |
-
planet_objects = {
|
| 91 |
'Sun': swe.SUN, 'Moon': swe.MOON, 'Mercury': swe.MERCURY,
|
| 92 |
'Venus': swe.VENUS, 'Mars': swe.MARS,
|
| 93 |
'Jupiter': swe.JUPITER, 'Saturn': swe.SATURN
|
| 94 |
}
|
| 95 |
|
| 96 |
-
#
|
| 97 |
jd_utc = swe.julday(
|
| 98 |
utc_dt.year, utc_dt.month, utc_dt.day,
|
| 99 |
utc_dt.hour + utc_dt.minute/60 + utc_dt.second/3600
|
| 100 |
)
|
| 101 |
|
| 102 |
-
# Calculate
|
| 103 |
longitudes = {}
|
| 104 |
-
for planet, planet_id in
|
| 105 |
-
flags = swe.FLG_SWIEPH | swe.FLG_SPEED
|
| 106 |
xx, _ = swe.calc_ut(jd_utc, planet_id, flags)
|
| 107 |
lon = xx[0] % 360 # Normalize to 0-360°
|
| 108 |
-
|
| 109 |
-
# Convert to degrees, minutes, seconds (DMS)
|
| 110 |
-
d = int(lon)
|
| 111 |
-
m = int((lon - d) * 60)
|
| 112 |
-
s = round(((lon - d) * 60 - m) * 60)
|
| 113 |
-
|
| 114 |
-
# Handle rounding (e.g., 59.999" → 60" → increment minute)
|
| 115 |
-
if s >= 60:
|
| 116 |
-
s -= 60
|
| 117 |
-
m += 1
|
| 118 |
-
if m >= 60:
|
| 119 |
-
m -= 60
|
| 120 |
-
d += 1
|
| 121 |
-
|
| 122 |
-
longitudes[planet] = f"{d}°{m:02d}'{s:02d}\""
|
| 123 |
|
| 124 |
-
# Sort planets by longitude
|
| 125 |
-
sorted_planets = sorted(
|
| 126 |
-
longitudes.items(),
|
| 127 |
-
key=lambda x: float(x[1].split('°')[0]) # Sort by degrees
|
| 128 |
-
)
|
| 129 |
PLadder = [p for p, _ in sorted_planets]
|
| 130 |
-
sorted_lons = [
|
| 131 |
-
|
| 132 |
-
# Calculate zone sizes (angular distances)
|
| 133 |
-
zone_sizes = (
|
| 134 |
-
[sorted_lons[0]] +
|
| 135 |
-
[sorted_lons[i+1] - sorted_lons[i] for i in range(6)] +
|
| 136 |
-
[360 - sorted_lons[6]]
|
| 137 |
-
)
|
| 138 |
|
| 139 |
-
#
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
[[PLadder[6]]]
|
| 144 |
-
)
|
| 145 |
|
|
|
|
| 146 |
ZSizes = []
|
| 147 |
for i, size in enumerate(zone_sizes):
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
|
|
|
|
|
|
|
|
|
| 152 |
|
| 153 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
d = int(size)
|
| 155 |
m = int((size - d) * 60)
|
| 156 |
-
s = round(((size - d) * 60 - m) * 60)
|
|
|
|
|
|
|
| 157 |
if s >= 60:
|
| 158 |
s -= 60
|
| 159 |
m += 1
|
| 160 |
if m >= 60:
|
| 161 |
m -= 60
|
| 162 |
d += 1
|
| 163 |
-
|
| 164 |
classification = (
|
| 165 |
'Swallowed' if size <= 1 else
|
| 166 |
'Tiny' if size <= X else
|
|
@@ -170,72 +212,135 @@ def PLadder_ZSizes(utc_dt, lat, lon):
|
|
| 170 |
'Big'
|
| 171 |
)
|
| 172 |
|
| 173 |
-
ZSizes.append((f"{d}°{m:
|
| 174 |
|
| 175 |
return {
|
| 176 |
'PLadder': PLadder,
|
| 177 |
'ZSizes': ZSizes,
|
| 178 |
-
'longitudes':
|
| 179 |
}
|
| 180 |
|
| 181 |
def plot_pladder(PLadder):
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
ax.set_xlim(-0.5, 3.5)
|
| 195 |
ax.set_ylim(-0.5, 3.5)
|
| 196 |
ax.set_aspect('equal')
|
| 197 |
ax.axis('off')
|
|
|
|
|
|
|
| 198 |
return fig
|
| 199 |
|
| 200 |
def chat_interface(query):
|
| 201 |
-
"""
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
if error:
|
| 207 |
return error, None
|
|
|
|
|
|
|
| 208 |
utc_dt, lat, lon, error = get_utc_time(dt, location)
|
| 209 |
if error:
|
| 210 |
return error, None
|
|
|
|
|
|
|
| 211 |
result = PLadder_ZSizes(utc_dt, lat, lon)
|
| 212 |
if "error" in result:
|
| 213 |
return result["error"], None
|
|
|
|
|
|
|
| 214 |
PLadder = result["PLadder"]
|
| 215 |
ZSizes = result["ZSizes"]
|
| 216 |
longitudes = result["longitudes"]
|
| 217 |
-
|
| 218 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
coords_text = format_coords(lat, lon)
|
|
|
|
|
|
|
| 220 |
fig = plot_pladder(PLadder)
|
| 221 |
buf = BytesIO()
|
| 222 |
-
fig.savefig(buf, format='png', bbox_inches='tight')
|
| 223 |
buf.seek(0)
|
| 224 |
img = Image.open(buf)
|
| 225 |
plt.close(fig)
|
| 226 |
-
|
| 227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
|
| 229 |
-
# Gradio
|
| 230 |
-
with gr.Blocks() as interface:
|
|
|
|
| 231 |
with gr.Row():
|
| 232 |
with gr.Column(scale=2):
|
| 233 |
-
output_text = gr.Textbox(label="
|
| 234 |
with gr.Column(scale=1):
|
| 235 |
-
output_image = gr.Image(label="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
with gr.Row():
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
submit_button.click(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
|
| 241 |
-
|
|
|
|
|
|
| 9 |
import pytz
|
| 10 |
import swisseph as swe
|
| 11 |
|
| 12 |
+
# Constants and Configuration
|
| 13 |
+
swe.set_ephe_path(None) # Initialize Swiss Ephemeris once at startup
|
| 14 |
|
| 15 |
# Russian translations for planets
|
| 16 |
+
PLANET_RU = {
|
| 17 |
+
'Sun': 'Солнце', 'Moon': 'Луна', 'Mercury': 'Меркурий',
|
| 18 |
+
'Venus': 'Венера', 'Mars': 'Марс', 'Jupiter': 'Юпитер',
|
| 19 |
+
'Saturn': 'Сатурн'
|
| 20 |
}
|
| 21 |
|
| 22 |
# Planet symbols for plotting
|
| 23 |
+
PLANET_SYMBOLS = {
|
| 24 |
'Sun': '☉', 'Moon': '☾', 'Mercury': '☿', 'Venus': '♀',
|
| 25 |
'Mars': '♂', 'Jupiter': '♃', 'Saturn': '♄'
|
| 26 |
}
|
| 27 |
|
| 28 |
+
# Zodiac signs in Russian
|
| 29 |
+
ZODIAC_SIGNS = [
|
| 30 |
+
"Овен", "Телец", "Близнецы", "Рак", "Лев", "Дева",
|
| 31 |
+
"Весы", "Скорпион", "Стрелец", "Козерог", "Водолей", "Рыбы"
|
| 32 |
+
]
|
| 33 |
|
| 34 |
def parse_query(query):
|
| 35 |
+
"""
|
| 36 |
+
Parse the query into date, time, and location.
|
| 37 |
+
Args:
|
| 38 |
+
query: String in format "PLadder YYYY-MM-DD HH:MM Location"
|
| 39 |
+
Returns:
|
| 40 |
+
tuple: (datetime, location, error_message)
|
| 41 |
+
"""
|
| 42 |
+
if not query.startswith("PLadder "):
|
| 43 |
+
return None, None, "Query must start with 'PLadder'"
|
| 44 |
+
|
| 45 |
try:
|
| 46 |
+
_, date_str, time_str, *location_parts = query.split(maxsplit=3)
|
| 47 |
+
location = location_parts[0] if location_parts else ""
|
| 48 |
dt = parser.parse(f"{date_str} {time_str}")
|
| 49 |
return dt, location, None
|
| 50 |
+
except ValueError as e:
|
| 51 |
+
return None, None, f"Invalid format: {str(e)}"
|
| 52 |
|
| 53 |
def get_utc_time(dt, location):
|
| 54 |
+
"""
|
| 55 |
+
Convert local time to UTC using location's time zone.
|
| 56 |
+
Args:
|
| 57 |
+
dt: Local datetime
|
| 58 |
+
location: String location
|
| 59 |
+
Returns:
|
| 60 |
+
tuple: (utc_dt, lat, lon, error_message)
|
| 61 |
+
"""
|
| 62 |
+
geolocator = Nominatim(user_agent="pladder_app")
|
| 63 |
try:
|
| 64 |
+
loc = geolocator.geocode(location, timeout=10)
|
| 65 |
if not loc:
|
| 66 |
+
return None, None, None, "Location not found"
|
| 67 |
+
|
| 68 |
lat, lon = loc.latitude, loc.longitude
|
| 69 |
+
tz_str = TimezoneFinder().timezone_at(lng=lon, lat=lat)
|
|
|
|
| 70 |
if not tz_str:
|
| 71 |
+
return None, None, None, "Time zone not found"
|
| 72 |
+
|
| 73 |
tz = pytz.timezone(tz_str)
|
| 74 |
local_dt = tz.localize(dt)
|
| 75 |
+
utc_dt = local_dt.astimezone(pytz.UTC)
|
| 76 |
return utc_dt, lat, lon, None
|
| 77 |
+
|
| 78 |
except Exception as e:
|
| 79 |
+
return None, None, None, f"Geocoding error: {str(e)}"
|
| 80 |
|
| 81 |
def format_coords(lat, lon):
|
| 82 |
+
"""
|
| 83 |
+
Format coordinates as degrees, minutes, seconds.
|
| 84 |
+
Args:
|
| 85 |
+
lat: Latitude in degrees
|
| 86 |
+
lon: Longitude in degrees
|
| 87 |
+
Returns:
|
| 88 |
+
str: Formatted coordinates (e.g., "12°34'56" N, 98°45'32" E")
|
| 89 |
+
"""
|
| 90 |
+
def dms(value, pos_dir, neg_dir):
|
| 91 |
+
direction = pos_dir if value >= 0 else neg_dir
|
| 92 |
+
abs_value = abs(value)
|
| 93 |
+
degrees = int(abs_value)
|
| 94 |
+
minutes = int((abs_value - degrees) * 60)
|
| 95 |
+
seconds = round(((abs_value - degrees) * 60 - minutes) * 60)
|
| 96 |
+
|
| 97 |
+
# Handle rounding overflow
|
| 98 |
+
if seconds >= 60:
|
| 99 |
+
seconds -= 60
|
| 100 |
+
minutes += 1
|
| 101 |
+
if minutes >= 60:
|
| 102 |
+
minutes -= 60
|
| 103 |
+
degrees += 1
|
| 104 |
+
|
| 105 |
+
return f"{degrees}°{minutes:02}'{seconds:02}\" {direction}"
|
| 106 |
+
|
| 107 |
return f"{dms(lat, 'N', 'S')}, {dms(lon, 'E', 'W')}"
|
| 108 |
|
| 109 |
+
def lon_to_sign(lon_deg):
|
| 110 |
+
"""
|
| 111 |
+
Convert ecliptic longitude to zodiac sign with position.
|
| 112 |
+
Args:
|
| 113 |
+
lon_deg: Longitude in degrees (0-360)
|
| 114 |
+
Returns:
|
| 115 |
+
str: Formatted sign and position (e.g., "Лев 12°34'")
|
| 116 |
+
"""
|
| 117 |
+
sign_idx = int(lon_deg // 30)
|
| 118 |
+
degrees_in_sign = lon_deg % 30
|
| 119 |
+
degrees = int(degrees_in_sign)
|
| 120 |
+
minutes = int((degrees_in_sign - degrees) * 60)
|
| 121 |
+
|
| 122 |
+
# Handle rounding
|
| 123 |
+
if minutes >= 60:
|
| 124 |
+
minutes -= 60
|
| 125 |
+
degrees += 1
|
| 126 |
+
|
| 127 |
+
return f"{ZODIAC_SIGNS[sign_idx]} {degrees}°{minutes:02}'"
|
| 128 |
|
| 129 |
def PLadder_ZSizes(utc_dt, lat, lon):
|
| 130 |
+
"""
|
| 131 |
+
Calculate Planetary Ladder and Zone Sizes using Swiss Ephemeris.
|
| 132 |
+
Args:
|
| 133 |
+
utc_dt: UTC datetime
|
| 134 |
+
lat: Latitude in degrees
|
| 135 |
+
lon: Longitude in degrees
|
| 136 |
+
Returns:
|
| 137 |
+
dict: Contains PLadder, ZSizes, and longitudes
|
| 138 |
+
"""
|
| 139 |
+
# Validate date range
|
| 140 |
if not -13000 <= utc_dt.year <= 17000:
|
| 141 |
+
return {"error": "Date out of supported range (-13,000–17,000 CE)"}
|
| 142 |
|
| 143 |
+
# Planet mapping with Swiss Ephemeris constants
|
| 144 |
+
PLANET_OBJECTS = {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
'Sun': swe.SUN, 'Moon': swe.MOON, 'Mercury': swe.MERCURY,
|
| 146 |
'Venus': swe.VENUS, 'Mars': swe.MARS,
|
| 147 |
'Jupiter': swe.JUPITER, 'Saturn': swe.SATURN
|
| 148 |
}
|
| 149 |
|
| 150 |
+
# Calculate Julian Day with high precision
|
| 151 |
jd_utc = swe.julday(
|
| 152 |
utc_dt.year, utc_dt.month, utc_dt.day,
|
| 153 |
utc_dt.hour + utc_dt.minute/60 + utc_dt.second/3600
|
| 154 |
)
|
| 155 |
|
| 156 |
+
# Calculate planetary positions
|
| 157 |
longitudes = {}
|
| 158 |
+
for planet, planet_id in PLANET_OBJECTS.items():
|
| 159 |
+
flags = swe.FLG_SWIEPH | swe.FLG_SPEED
|
| 160 |
xx, _ = swe.calc_ut(jd_utc, planet_id, flags)
|
| 161 |
lon = xx[0] % 360 # Normalize to 0-360°
|
| 162 |
+
longitudes[planet] = lon
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
|
| 164 |
+
# Sort planets by longitude
|
| 165 |
+
sorted_planets = sorted(longitudes.items(), key=lambda x: x[1])
|
|
|
|
|
|
|
|
|
|
| 166 |
PLadder = [p for p, _ in sorted_planets]
|
| 167 |
+
sorted_lons = [lon for _, lon in sorted_planets]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
|
| 169 |
+
# Calculate zone sizes
|
| 170 |
+
zone_sizes = [sorted_lons[0]] # First zone
|
| 171 |
+
zone_sizes.extend(sorted_lons[i+1] - sorted_lons[i] for i in range(6)) # Middle zones
|
| 172 |
+
zone_sizes.append(360 - sorted_lons[-1]) # Last zone
|
|
|
|
|
|
|
| 173 |
|
| 174 |
+
# Classify zone sizes
|
| 175 |
ZSizes = []
|
| 176 |
for i, size in enumerate(zone_sizes):
|
| 177 |
+
# Get bordering planets
|
| 178 |
+
if i == 0:
|
| 179 |
+
bord = [PLadder[0]]
|
| 180 |
+
elif i == len(zone_sizes)-1:
|
| 181 |
+
bord = [PLadder[-1]]
|
| 182 |
+
else:
|
| 183 |
+
bord = [PLadder[i-1], PLadder[i]]
|
| 184 |
|
| 185 |
+
# Determine zone classification
|
| 186 |
+
if any(p in ['Sun', 'Moon'] for p in bord):
|
| 187 |
+
X = 7
|
| 188 |
+
elif any(p in ['Mercury', 'Venus', 'Mars'] for p in bord):
|
| 189 |
+
X = 6
|
| 190 |
+
else:
|
| 191 |
+
X = 5
|
| 192 |
+
|
| 193 |
+
# Format size and classification
|
| 194 |
d = int(size)
|
| 195 |
m = int((size - d) * 60)
|
| 196 |
+
s = int(round(((size - d) * 60 - m) * 60))
|
| 197 |
+
|
| 198 |
+
# Handle rounding overflow
|
| 199 |
if s >= 60:
|
| 200 |
s -= 60
|
| 201 |
m += 1
|
| 202 |
if m >= 60:
|
| 203 |
m -= 60
|
| 204 |
d += 1
|
| 205 |
+
|
| 206 |
classification = (
|
| 207 |
'Swallowed' if size <= 1 else
|
| 208 |
'Tiny' if size <= X else
|
|
|
|
| 212 |
'Big'
|
| 213 |
)
|
| 214 |
|
| 215 |
+
ZSizes.append((f"{d}°{m:02}'{s:02}\"", classification))
|
| 216 |
|
| 217 |
return {
|
| 218 |
'PLadder': PLadder,
|
| 219 |
'ZSizes': ZSizes,
|
| 220 |
+
'longitudes': {k: v for k, v in longitudes.items()} # Raw degrees
|
| 221 |
}
|
| 222 |
|
| 223 |
def plot_pladder(PLadder):
|
| 224 |
+
"""
|
| 225 |
+
Generate visualization of the planetary ladder.
|
| 226 |
+
Args:
|
| 227 |
+
PLadder: Ordered list of planets
|
| 228 |
+
Returns:
|
| 229 |
+
matplotlib Figure
|
| 230 |
+
"""
|
| 231 |
+
fig, ax = plt.subplots(figsize=(8, 8))
|
| 232 |
+
|
| 233 |
+
# Draw main triangle
|
| 234 |
+
ax.plot([0, 1.5, 3, 0], [0, 3, 0, 0], 'k-', linewidth=2)
|
| 235 |
+
|
| 236 |
+
# Draw horizontal divisions
|
| 237 |
+
ax.plot([0.5, 2.5], [1, 1], 'k--', alpha=0.5)
|
| 238 |
+
ax.plot([1, 2], [2, 2], 'k--', alpha=0.5)
|
| 239 |
+
|
| 240 |
+
# Planet symbol positions
|
| 241 |
+
symbol_positions = [
|
| 242 |
+
(-0.2, 0.2), (0.3, 1.2), (0.8, 2.2),
|
| 243 |
+
(1.5, 3.2), (2.2, 2.2), (2.7, 1.2), (3.2, 0.2)
|
| 244 |
+
]
|
| 245 |
+
|
| 246 |
+
# Add planet symbols
|
| 247 |
+
for (x, y), planet in zip(symbol_positions, PLadder):
|
| 248 |
+
ax.text(x, y, PLANET_SYMBOLS[planet],
|
| 249 |
+
ha='center', va='center',
|
| 250 |
+
fontsize=24, fontweight='bold')
|
| 251 |
+
|
| 252 |
+
# Configure plot appearance
|
| 253 |
ax.set_xlim(-0.5, 3.5)
|
| 254 |
ax.set_ylim(-0.5, 3.5)
|
| 255 |
ax.set_aspect('equal')
|
| 256 |
ax.axis('off')
|
| 257 |
+
plt.tight_layout()
|
| 258 |
+
|
| 259 |
return fig
|
| 260 |
|
| 261 |
def chat_interface(query):
|
| 262 |
+
"""
|
| 263 |
+
Main processing function for the Gradio interface.
|
| 264 |
+
Args:
|
| 265 |
+
query: User input string
|
| 266 |
+
Returns:
|
| 267 |
+
tuple: (text_response, image)
|
| 268 |
+
"""
|
| 269 |
+
# Parse input
|
| 270 |
+
dt, location, error = parse_query(query)
|
| 271 |
if error:
|
| 272 |
return error, None
|
| 273 |
+
|
| 274 |
+
# Get UTC time and coordinates
|
| 275 |
utc_dt, lat, lon, error = get_utc_time(dt, location)
|
| 276 |
if error:
|
| 277 |
return error, None
|
| 278 |
+
|
| 279 |
+
# Calculate planetary positions
|
| 280 |
result = PLadder_ZSizes(utc_dt, lat, lon)
|
| 281 |
if "error" in result:
|
| 282 |
return result["error"], None
|
| 283 |
+
|
| 284 |
+
# Format output
|
| 285 |
PLadder = result["PLadder"]
|
| 286 |
ZSizes = result["ZSizes"]
|
| 287 |
longitudes = result["longitudes"]
|
| 288 |
+
|
| 289 |
+
# Generate planet list text
|
| 290 |
+
planet_list = "\n".join(
|
| 291 |
+
f"{PLANET_RU[p]}: {lon_to_sign(longitudes[p])}"
|
| 292 |
+
for p in PLadder
|
| 293 |
+
)
|
| 294 |
+
|
| 295 |
+
# Generate zone sizes text
|
| 296 |
+
zones_text = "\n".join(
|
| 297 |
+
f"Zone {i+1}: {size} ({cls})"
|
| 298 |
+
for i, (size, cls) in enumerate(ZSizes)
|
| 299 |
+
)
|
| 300 |
+
|
| 301 |
+
# Generate coordinates text
|
| 302 |
coords_text = format_coords(lat, lon)
|
| 303 |
+
|
| 304 |
+
# Create visualization
|
| 305 |
fig = plot_pladder(PLadder)
|
| 306 |
buf = BytesIO()
|
| 307 |
+
fig.savefig(buf, format='png', dpi=120, bbox_inches='tight')
|
| 308 |
buf.seek(0)
|
| 309 |
img = Image.open(buf)
|
| 310 |
plt.close(fig)
|
| 311 |
+
|
| 312 |
+
# Compose final output
|
| 313 |
+
output_text = (
|
| 314 |
+
f"Planetary Ladder:\n{planet_list}\n\n"
|
| 315 |
+
f"Zone Sizes:\n{zones_text}\n\n"
|
| 316 |
+
f"Coordinates: {coords_text}\n"
|
| 317 |
+
f"Calculation Time: {utc_dt.strftime('%Y-%m-%d %H:%M:%S UTC')}"
|
| 318 |
+
)
|
| 319 |
+
|
| 320 |
+
return output_text, img
|
| 321 |
|
| 322 |
+
# Gradio Interface
|
| 323 |
+
with gr.Blocks(title="Planetary Ladder Calculator") as interface:
|
| 324 |
+
gr.Markdown("## Planetary Ladder Calculator")
|
| 325 |
with gr.Row():
|
| 326 |
with gr.Column(scale=2):
|
| 327 |
+
output_text = gr.Textbox(label="Astrological Data", lines=12)
|
| 328 |
with gr.Column(scale=1):
|
| 329 |
+
output_image = gr.Image(label="Visualization")
|
| 330 |
+
with gr.Row():
|
| 331 |
+
query_text = gr.Textbox(
|
| 332 |
+
label="Input Query",
|
| 333 |
+
placeholder="Example: PLadder 2023-10-10 12:00 New York",
|
| 334 |
+
max_lines=1
|
| 335 |
+
)
|
| 336 |
with gr.Row():
|
| 337 |
+
submit_button = gr.Button("Calculate", variant="primary")
|
| 338 |
+
|
| 339 |
+
submit_button.click(
|
| 340 |
+
fn=chat_interface,
|
| 341 |
+
inputs=query_text,
|
| 342 |
+
outputs=[output_text, output_image]
|
| 343 |
+
)
|
| 344 |
|
| 345 |
+
if __name__ == "__main__":
|
| 346 |
+
interface.launch()
|