[[fetch]]
from = "/static/my_package/"
files = ["ephemeris.py"]
to_folder = "local_package"
import js
from js import document, drawChart
from local_package import ephemeris
from pyodide.ffi import to_js, create_proxy
from pyodide.ffi import to_js
from pyodide.ffi import to_js
from js import Object
def is_retrograde_from_position(pos_str: str) -> bool:
"""Return True if '(r)' appears in the formatted position string."""
return "(r)" in pos_str
def decimal_to_deg_min(value):
deg = int(value)
minutes = int((abs(value) - abs(deg)) * 60)
return deg, minutes
def compute_chart1():
js.localStorage.removeItem("planet_display_json")
js.localStorage.removeItem("planet_raw_degrees")
js.document.getElementById("chartSummary").innerHTML = ""
js.document.getElementById("chartAspects").innerHTML = ""
js.document.getElementById("chartPlanets").innerHTML = ""
# ==========================================================
# 🔒 AUTHORITATIVE HOUSES FROM URL (if present)
# ==========================================================
houses_from_url = None
try:
raw_houses = js.params.get("houses_list")
if raw_houses:
parts = [p for p in raw_houses.split("*") if p.strip() != ""]
js.alert(str(parts))
if len(parts) == 12:
houses_from_url = [float(p) for p in parts]
print("🔒 Using AUTHORITATIVE houses_list from URL:", houses_from_url)
else:
print("⚠️ Invalid houses_list length, ignoring")
except Exception as e:
print("⚠️ Failed to parse houses_list:", e)
#alert(houses_from_url)
xhouseslist = js.getAuthoritativeHousesFromURL();
# 🔒 Authoritative houses from URL (JS → Python)
xhouseslist = js.getAuthoritativeHousesFromURL()
if xhouseslist is not None:
try:
xhouseslist = list(xhouseslist.to_py())
except Exception:
xhouseslist = None
#js.alert(str(xhouseslist));
dt = str(document.getElementById('dt').value)
date_part, time_part = dt.split("T")
y, m, d = date_part.split("-")
hh, mm = time_part.split(":")[:2]
ephemeris.set_params(y, m, d, hh, mm)
ephemeris.say_hello(0)
#tr = js.T.to_py() # Access the pre-selected translation object
#signs = list(tr['signs'])
# Only get the signs list from JS (safe)
#signs = list(js.T.signs.to_py())
#signs = list(js.T.get("signs").to_py())
# === UNIVERSAL FIX ===
# Pyodide-safe way to read JS arrays, even when attributes are not exposed
from js import Object
# ==========================================================
# 🌍 SAFE TRANSLATION ACCESS FOR PYTHON (FR/EN)
# ==========================================================
# Pyodide-safe way to access JS translation arrays
try:
# JS helper gives the correct signs array in current language
signs = list(js.window.getSignsForPython().to_py())
except Exception:
# fallback to EN zodiac if JS bridge fails
signs = [
"Aries","Taurus","Gemini","Cancer","Leo","Virgo",
"Libra","Scorpio","Sagittarius","Capricorn","Aquarius","Pisces"
]
# Python wrappers for JS translation helpers
translate_planet = js.window.translatePlanet
translate_sign = js.window.translateSign
"""
planets = [
{"name": "Moon", "label": "☽", "deg": ephemeris.L1},
{"name": "Sun", "label": "☉", "deg": ephemeris.LG1},
{"name": "Mercury", "label": "☿", "deg": ephemeris.LG3},
{"name": "Venus", "label": "♀", "deg": ephemeris.LG4},
{"name": "Mars", "label": "♂", "deg": ephemeris.LG5},
{"name": "Jupiter", "label": "♃", "deg": ephemeris.LG6},
{"name": "Saturn", "label": "♄", "deg": ephemeris.LG7},
{"name": "Uranus", "label": "♅", "deg": ephemeris.LG8},
{"name": "Neptune", "label": "♆", "deg": ephemeris.LG9},
{"name": "Pluto", "label": "♇", "deg": ephemeris.LG10},
]
"""
planets = [
{
"name": "Moon",
"label": "☽",
"deg": ephemeris.L1,
"retrograde": False, # Moon never retrograde
},
{
"name": "Sun",
"label": "☉",
"deg": ephemeris.LG1,
"retrograde": False,
},
{
"name": "Mercury",
"label": "☿",
"deg": ephemeris.LG3,
"retrograde": is_retrograde_from_position(ephemeris.x_position_mercure),
},
{
"name": "Venus",
"label": "♀",
"deg": ephemeris.LG4,
"retrograde": is_retrograde_from_position(ephemeris.x_position_venus),
},
{
"name": "Mars",
"label": "♂",
"deg": ephemeris.LG5,
"retrograde": is_retrograde_from_position(ephemeris.x_position_mars),
},
{
"name": "Jupiter",
"label": "♃",
"deg": ephemeris.LG6,
"retrograde": is_retrograde_from_position(ephemeris.x_position_jupiter),
},
{
"name": "Saturn",
"label": "♄",
"deg": ephemeris.LG7,
"retrograde": is_retrograde_from_position(ephemeris.x_position_saturne),
},
{
"name": "Uranus",
"label": "♅",
"deg": ephemeris.LG8,
"retrograde": is_retrograde_from_position(ephemeris.x_position_uranus),
},
{
"name": "Neptune",
"label": "♆",
"deg": ephemeris.LG9,
"retrograde": is_retrograde_from_position(ephemeris.x_position_neptune),
},
{
"name": "Pluto",
"label": "♇",
"deg": ephemeris.LG10,
"retrograde": is_retrograde_from_position(ephemeris.x_position_pluton),
},
]
# ==========================================================
# 🌍 Store raw planetary longitudes for JS aspect engine
# ==========================================================
raw_planets = []
for p in planets:
try:
deg = float(p["deg"])
except Exception:
continue # skip invalid planet safely
raw_planets.append([p["name"], deg])
#js.alert(raw_planets)
from pyodide.ffi import to_js
js_planets = to_js(raw_planets, depth=2)
js.localStorage.setItem(
"planet_raw_degrees",
js.JSON.stringify(js_planets)
)
#js.alert(js.JSON.stringify(js_planets));
print(f"✅ Computed {len(planets)} planetary positions")
# --- Mocked placeholder for geographic coordinates (Paris example) ---
# --- Geographic coordinates (Paris example) ---
#latitude = 48.8566
#longitude = -2.3522 # negative because East
xhour = int(hh)
xminute = int(mm)
#latitude = float(js.localStorage.getItem('user_lat') or 48.8566)
#longitude = float(js.localStorage.getItem('user_lon') or -2.3522)
# ✅ Read coordinates from URL first
params = js.URLSearchParams.new(js.window.location.search)
latitude = float(params.get("lat") or js.localStorage.getItem("user_lat") or 48.8566)
longitude = float(params.get("lon") or js.localStorage.getItem("user_lon") or 2.3522)
timezone = params.get("timezone") or js.localStorage.getItem("timezone") or "Unknown"
js.localStorage.setItem('timezone', timezone)
#name = params.get("name") or js.localStorage.getItem("name") or 'Anonymous'
# ✅ Always prefer the current input field
name_field = js.document.getElementById("emotional_chart_name")
name = name_field.value.strip() or params.get("name") or js.localStorage.getItem("name") or "Anonymous"
# store for later reuse
js.localStorage.setItem("name", name)
# ✅ IMPORTANT: convert East longitudes to negative (for ephemeris convention)
longitude = -abs(longitude)
print(f"🌍 Coordinates adjusted for ephemeris: lat={latitude}, lon={longitude}")
# --- Extract UTC date parts ---
y, m, d = map(int, date_part.split("-"))
# --- Compute the 12 houses using the new sidereal-time-aware version ---
houses = []
#for i in range(1, 13):
# angle = ephemeris.get_houses(latitude, longitude, 0, 0, xhour, xminute, i, y, m, d)
# houses.append(angle)
#js.alert(xhouseslist);
#js.alert(houses);
#arrayhouses= js.xhouseslist.replace(',', '')
#js.alert(arrayhouses);
# ==========================================================
# 🏠 HOUSE CUSPS — AUTHORITATIVE OR COMPUTED
# ==========================================================
if xhouseslist and len(xhouseslist) == 12:
houses = xhouseslist
print("🔒 Houses taken from URL (authoritative)")
else:
houses = []
for i in range(1, 13):
angle = ephemeris.get_houses(
latitude,
longitude,
0, 0,
xhour,
xminute,
i,
y, m, d
)
houses.append(float(angle))
print("🧮 Houses computed via ephemeris")
def zodiac_label(deg):
sign_index = int(deg // 30) % 12
sign_deg = deg % 30
sign_name = signs[sign_index]
return f"{sign_deg:.1f}° {translate_sign(sign_name)}"
print("\n📘 Computed Houses (Placidus):")
for i, deg in enumerate(houses, start=1):
print(f"House {i:2}: {zodiac_label(deg)}")
#print("\n📘 Computed Houses (Placidus):")
#for i, deg in enumerate(xhouseslist, start=1):
# print(f"House {i:2}: {zodiac_label(deg)}")
print(f"✅ Computed house cusps: {houses}")
#print(f"✅ Computed house cusps: {xhouseslist}")
T = js.window.T
def t(section, key):
try:
# Convert JS translation object → Python dict
T_py = js.window.T.to_py()
if section in T_py and key in T_py[section]:
return T_py[section][key]
return key # fallback
except Exception as e:
print("⚠️ Translation error in t():", e)
return key
# ==========================================================
# 📍 Prepare DMS formatting for display
# ==========================================================
def to_dms(value, is_lat):
deg = int(abs(value))
minutes = int((abs(value) - deg) * 60)
seconds = int(((abs(value) - deg) * 60 - minutes) * 60)
hemi = (
'N' if (is_lat and value >= 0) else
'S' if is_lat else
('E' if value >= 0 else 'W')
)
return f"{deg}°{minutes}'{seconds}\" {hemi}"
lat_dms = to_dms(latitude, True)
lon_dms = to_dms(-longitude, False) # East positive for display
#js.alert(xdms_lon)
# ==========================================================
# 🌅 Ascendant — EXACT astro.html logic (no interpretation)
# ==========================================================
def split_deg_min_signed(value):
"""
astro.html convention:
- latitude: North + / South -
- longitude: East + / West -
"""
deg = int(value) # KEEP SIGN
minutes = int(abs(value - deg) * 60)
return deg, minutes
# Use coordinates EXACTLY as passed in URL
lat_deg, lat_min = split_deg_min_signed(latitude)
lon_deg, lon_min = split_deg_min_signed(longitude)
xdms_lon = js.getDmsLon()
if('W' in xdms_lon):
lon_deg = int(lon_deg)*(-1)
# Direct ascendant call (numHOUSE = 1)
#asc_abs = float(
# ephemeris.get_houses(
# lat_deg,
# lon_deg,
# lat_min,
# lon_min,
# xhour,
# xminute,
# 1,
# Y=int(y),
# M=int(m),
# D=int(d)
# )
#)
asc_abs = houses[0]
# Convert to sign + degree
asc_index = int(asc_abs // 30) % 12
asc_deg = round(asc_abs % 30, 1)
asc_sign_name = translate_sign(signs[asc_index])
assert 0 <= asc_abs < 360
# ✅ Build the info box HTML (top summary)
T = js.window.T
info_html = f"""
🧑 {t("info", "name")}: {name} 📅 {t("inputs", "utc")}: {dt.replace('T',' ')} ⏰ {t("info","timezone")}: {timezone} 📍 {t("info", "coords")}: {lat_dms}, {xdms_lon} 🌅 {t('info','ascendant')}: {asc_deg}° {asc_sign_name}
"""
# 🌅 {t('info','ascendant')}: {asc_deg}° {asc_sign_name} "
js.document.getElementById("emotional_chart_name").value=name;
# ✅ Prepare planet data only (no HTML building here)
planet_display = []
for p in planets:
try:
deg = float(p["deg"])
except Exception:
deg = 0.0
sign_index = int(deg // 30) % 12
sign_name = signs[sign_index]
sign_deg = round(deg % 30, 1)
retro = "℞" if p.get("retrograde", False) else ""
#planet_display.append([p["label"], p["name"], sign_deg, sign_name, retro])
planet_display.append([
p["label"],
translate_planet(p["name"]),
sign_deg,
translate_sign(sign_name),
retro
])
# Store as JSON string in JS memory for rendering later
js.localStorage.setItem("planet_display_json", str(planet_display))
# ✅ Add houses list (as HTML table)
# ==========================================================
# 🏠 Houses — exact astro.html logic
# ==========================================================
houses_list = []
#lon_deg = int(lon_deg)*(-1)
xlat = js.getDmsLat()
if('S' in xlat):
lat_deg = int(lat_deg)*(-1)
#xlon = js.getDmsLon()
#if('W' in xlon):
# lon_deg = int(lon_deg)*(-1)
#js.alert(lat_deg);
#js.alert(lon_deg);
#for house_num in range(1, 13):
# angle = float(
# ephemeris.get_houses(
# lat_deg,
# lon_deg,
# lat_min,
# lon_min,
# xhour,
# xminute,
# house_num,
# Y=int(y),
# M=int(m),
# D=int(d)
# )
# )
# insign_deg = angle % 30
# if house_num == 1:
# #js.alert(str(insign_deg))
# js.localStorage.setItem("asc_in_sign_deg", str(insign_deg))
# js.alert(insign_deg)
# houses_list.append(angle)
houses_list = houses
insign_deg = houses_list[0] % 30
js.localStorage.setItem("asc_in_sign_deg", str(insign_deg))
#js.alert(insign_deg)
def zodiac_label(deg):
sign_index = int(deg // 30) % 12
sign_deg = deg % 30
return f"{sign_deg:.1f}° {translate_sign(signs[sign_index])}"
# --- Roman numeral helper ---
def to_roman(num):
romans = ["I","II","III","IV","V","VI","VII","VIII","IX","X","XI","XII"]
return romans[(num - 1) % 12]
house_lines = "".join([
f"
{i}
"
f"
{zodiac_label(deg)}
"
#for i, deg in enumerate(houses_list, start=1)
for i, deg in enumerate(houses, start=1)
])
# === House Cusps (translated title) ===
houses_label = t("info", "houses") # "Houses" / "Maisons"
house_text = " ".join([
f"[{to_roman(i)}] {zodiac_label(deg)}"
#for i, deg in enumerate(houses_list, start=1)
for i, deg in enumerate(houses, start=1)
])
info_html += f" 🏠 {houses_label} (Placidus): {house_text}"
# ✅ Inject the HTML into the ± div
js.document.getElementById("chartSummary").innerHTML = "" + info_html
# 🧭 Optional: Debug in browser console
print(f"✅ Computed house cusps: {houses}")
# Store in JS localStorage for chart.js
for i, deg in enumerate(houses, start=1):
js.localStorage.setItem(f"house_{i}", str(deg))
js.drawChart(planets)
js.renderPlanetPositions()
js.setChartPalette(js.document.getElementById("paletteSelector").value)
print("✅ drawChart() executed successfully")
from js import window
# Make compute_chart1 accessible from JavaScript
window.compute_chart1 = compute_chart1
print("✅ compute_chart1 exported to JS")
js.setCurvedName(name)
js.drawChart(planets)
js.renderPlanetPositions()
js.renderAspectsSafely()