SergeyO7's picture
Update app.py
a21f5fe verified
import json
import gradio as gr
import datetime
import pytz
from skyfield.api import load, Topos, load_file
from skyfield import almanac
# Load ephemeris and timescale
planets = load('https://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/planets/de440.bsp')
ts = load.timescale()
# [Keep all your constants and coefficient dictionaries here - they remain unchanged]
# Define Zodiac signs and their boundaries (0° to 360° ecliptic longitude)
ZODIAC_SIGNS = [
("Aries", 0, 30),
("Taurus", 30, 60),
("Gemini", 60, 90),
("Cancer", 90, 120),
("Leo", 120, 150),
("Virgo", 150, 180),
("Libra", 180, 210),
("Scorpio", 210, 240),
("Sagittarius", 240, 270),
("Capricorn", 270, 300),
("Aquarius", 300, 330),
("Pisces", 330, 360),
]
# Moon phase boundaries (0° to 360° phase angle) for display purposes
MOON_PHASES = [
("New Moon", 0, 45),
("Waxing Crescent", 45, 90),
("First Quarter", 90, 135),
("Waxing Gibbous", 135, 180),
("Full Moon", 180, 225),
("Waning Gibbous", 225, 270),
("Last Quarter", 270, 315),
("Waning Crescent", 315, 360),
]
# Fertility sign coefficients (applicable to all plants)
FERTILITY_SIGN_COEFFS = {
"Aries": 1,
"Taurus": 2,
"Gemini": 0,
"Cancer": 2,
"Leo": 1,
"Virgo": 0,
"Libra": 0.5,
"Scorpio": 1.5,
"Sagittarius": 1,
"Capricorn": 1,
"Aquarius": 0,
"Pisces": 2,
}
# Pruning sign coefficients (applicable to all plants)
PRUNING_SIGN_COEFFS = {
"Aries": 1,
"Taurus": 0,
"Gemini": 2,
"Cancer": 0,
"Leo": 1,
"Virgo": 2,
"Libra": 1.5,
"Scorpio": 0.5,
"Sagittarius": 1,
"Capricorn": 1,
"Aquarius": 2,
"Pisces": 0,
}
# Fertility phase coefficients for above-ground plants
FERTILITY_PHASE_COEFFS_ABOVE = {
"New Moon": 0,
"Waxing Moon": 1,
"Full Moon": 0,
"Waning Moon": 0.5,
}
# Fertility phase coefficients for root crops
FERTILITY_PHASE_COEFFS_ROOT = {
"New Moon": 0,
"Waxing Moon": 0.5,
"Full Moon": 0,
"Waning Moon": 1,
}
# Pruning phase coefficients
PRUNING_PHASE_COEFFS = {
"New Moon": 0,
"Waxing Moon": 1,
"Full Moon": 0,
"Waning Moon": 0.5,
}
def get_moon_info(date_input) -> str:
"""
Returns Moon's Zodiac position, phase, and fertility and pruning indices for the given date/time.
The fertility and pruning indices are calculated as sum of sign and phase fertility values of the Moon position. Moon sign fertility
amounts up to 2.0 value and phase fertility value could be 1.0 max.
It is observed that when Moon is in different Zodiac signs, the fertility of new plants and impact of pruning differs.
When Moon is in fertile sign the plant is in the active phase, when all processes are particularly intense, and any intervention
such as pruning can be very traumatic for the plant.
Fertility indices ranges from 0.0 to 3.0 where proportionaly
0 - minimal expected fertility
3.0 - most favorable fertility for platining,
and depends on type of plant (root crop or produce above ground).
Pruning indices ranges from 0 to 3 where proportionaly:
0 - pruning is not recommended as it causes most damage to tree and can lead to:
Increased sap production from the cut points
Increased vulnerability to infections
Delayed wound healing
Possible weakening of the plant.
Instead of pruning into fertile signs, you can do:
Crown formation
Pinching the shoots
Removing dead branches
Sanitary treatment
1.0 - pruning is not recommended,
2.0 - allowed only minimum or sanitary pruning,
3.0 - most favorable time for pruning.
Args:
date_input: Can be a datetime object, float (timestamp), or string, depending on Gradio configuration
Returns:
A JSON string containing zodiac_position, moon_phase, fertility_above_ground, fertility_root_crop, pruning
"""
try:
# Handle different input types from Gradio DateTime component
if isinstance(date_input, datetime.datetime):
# Already a datetime object - ensure it's UTC
if date_input.tzinfo is None:
user_time = pytz.utc.localize(date_input)
else:
user_time = date_input.astimezone(pytz.UTC)
elif isinstance(date_input, (float, int)):
# Unix timestamp in milliseconds - convert to datetime
user_time = datetime.datetime.fromtimestamp(date_input / 1000, pytz.UTC)
elif isinstance(date_input, str):
# String input - parse accordingly
try:
user_time = datetime.datetime.fromisoformat(date_input.replace('Z', '+00:00'))
if user_time.tzinfo is None:
user_time = pytz.utc.localize(user_time)
else:
user_time = user_time.astimezone(pytz.UTC)
except (ValueError, AttributeError):
raise ValueError(f"Unsupported input format: {date_input}")
else:
raise ValueError(f"Unsupported input type: {type(date_input)}")
# Use loaded ephemeris and timescale
t = ts.from_datetime(user_time)
# Define celestial bodies
earth = planets['earth']
moon = planets['moon']
# Calculate Moon's ecliptic longitude
astrometric = earth.at(t).observe(moon)
ecliptic_lat, ecliptic_lon, distance = astrometric.ecliptic_latlon()
lon_deg = ecliptic_lon.degrees % 360
# Calculate the phase angle using almanac.moon_phase
phase = almanac.moon_phase(planets, t)
phase_angle = phase.degrees
# Determine Zodiac sign and position
zodiac_sign = "Unknown"
position_degrees = 0
for sign, start, end in ZODIAC_SIGNS:
if start <= lon_deg < end:
zodiac_sign = sign
position_degrees = lon_deg - start
break
# Format position to degrees and minutes
degrees = int(position_degrees)
minutes = int((position_degrees % 1) * 60)
position_str = f"{zodiac_sign} {degrees}°{minutes:02}'"
# Determine moon phase for display
moon_phase = "Unknown"
for phase, start, end in MOON_PHASES:
if start <= phase_angle < end:
moon_phase = phase
break
# Determine phase category for indices with 15° orbis for New and Full Moon
if (phase_angle >= 345 or phase_angle < 15):
phase_category = "New Moon" # 345° to 15° (30° total orbis)
elif 15 <= phase_angle < 165:
phase_category = "Waxing Moon"
elif 165 <= phase_angle < 195:
phase_category = "Full Moon" # 165° to 195° (30° total orbis)
elif 195 <= phase_angle < 345:
phase_category = "Waning Moon"
else:
phase_category = "Unknown"
# Calculate fertility and pruning indices
if zodiac_sign in FERTILITY_SIGN_COEFFS and phase_category in FERTILITY_PHASE_COEFFS_ABOVE:
fertility_above_ground = FERTILITY_SIGN_COEFFS[zodiac_sign] + FERTILITY_PHASE_COEFFS_ABOVE[phase_category]
fertility_root_crop = FERTILITY_SIGN_COEFFS[zodiac_sign] + FERTILITY_PHASE_COEFFS_ROOT[phase_category]
pruning = PRUNING_SIGN_COEFFS[zodiac_sign] + PRUNING_PHASE_COEFFS[phase_category]
else:
fertility_above_ground = None
fertility_root_crop = None
pruning = None
result = {
"zodiac_position": position_str,
"moon_phase": moon_phase,
"fertility_above_ground": fertility_above_ground,
"fertility_root_crop": fertility_root_crop,
"pruning": pruning
}
return json.dumps(result)
except Exception as e:
print(f"Error: {str(e)}")
raise ValueError(f"Error in get_moon_info: {str(e)}")
# Create the Gradio interface with explicit type specification
demo = gr.Interface(
fn=get_moon_info,
inputs=gr.DateTime(
label="Date and Time",
type="datetime", # Explicitly set to return datetime object
timezone="UTC"
),
outputs=gr.Textbox(),
title="Garden Magus",
description="Enter date and time to calculate Moon's position and phase, fertility and pruning indices."
)
# Launch the interface
if __name__ == "__main__":
demo.launch(mcp_server=True)