🧪 Emotional Chart (Dev Mode)

Dev Preview — not public
© Copyright 2025–2026 www.dailylifeastrology.com
[[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()