From 4f1c06f86342e24797b4761ed9685351e4464813 Mon Sep 17 00:00:00 2001 From: Eskimue Date: Fri, 3 Apr 2026 16:17:29 +0200 Subject: [PATCH] Goldener Henkel ist drin --- .codex-temp/php-server.err.log | 84 ++++++++++++++ public/monatsvorhersage_old.php | 36 ++++++ public/py/__pycache__/api.cpython-314.pyc | Bin 134314 -> 143623 bytes public/py/api.py | 133 +++++++++++++++++++++- 4 files changed, 251 insertions(+), 2 deletions(-) diff --git a/.codex-temp/php-server.err.log b/.codex-temp/php-server.err.log index a469590..585c3e0 100644 --- a/.codex-temp/php-server.err.log +++ b/.codex-temp/php-server.err.log @@ -698,3 +698,87 @@ Stack trace: [Fri Apr 3 15:04:24 2026] 127.0.0.1:64920 Accepted [Fri Apr 3 15:04:26 2026] 127.0.0.1:64920 [200]: GET /favicon.ico [Fri Apr 3 15:04:26 2026] 127.0.0.1:64920 Closing +[Fri Apr 3 15:11:51 2026] 127.0.0.1:57308 Accepted +[Fri Apr 3 15:12:02 2026] 127.0.0.1:57308 [200]: GET /monatsvorhersage_old.php?year=2026&month=9 +[Fri Apr 3 15:12:02 2026] 127.0.0.1:57308 Closing +[Fri Apr 3 15:12:32 2026] 127.0.0.1:59860 Accepted +[Fri Apr 3 15:12:47 2026] 127.0.0.1:59860 [200]: GET /monatsvorhersage_old.php?year=2026&month=2 +[Fri Apr 3 15:12:47 2026] 127.0.0.1:59860 Closing +[Fri Apr 3 15:12:47 2026] 127.0.0.1:58680 Accepted +[Fri Apr 3 15:12:57 2026] 127.0.0.1:58680 [200]: GET /monatsvorhersage_old.php?year=2026&month=9 +[Fri Apr 3 15:12:57 2026] 127.0.0.1:58680 Closing +[Fri Apr 3 15:12:57 2026] 127.0.0.1:52748 Accepted +[Fri Apr 3 15:12:57 2026] 127.0.0.1:51924 Accepted +[Fri Apr 3 15:12:59 2026] 127.0.0.1:52748 [200]: GET /favicon.ico +[Fri Apr 3 15:12:59 2026] 127.0.0.1:52748 Closing +[Fri Apr 3 15:12:59 2026] 127.0.0.1:64367 Accepted +[Fri Apr 3 15:12:59 2026] 127.0.0.1:51924 [200]: GET /css/style.css +[Fri Apr 3 15:12:59 2026] 127.0.0.1:64367 [200]: GET /js/astronomy.browser.min.js +[Fri Apr 3 15:12:59 2026] 127.0.0.1:51924 Closing +[Fri Apr 3 15:12:59 2026] 127.0.0.1:64367 Closing +[Fri Apr 3 15:12:59 2026] 127.0.0.1:64871 Accepted +[Fri Apr 3 15:12:59 2026] 127.0.0.1:51645 Accepted +[Fri Apr 3 15:12:59 2026] 127.0.0.1:64871 [200]: GET /images/star16x16.png +[Fri Apr 3 15:12:59 2026] 127.0.0.1:51645 [200]: GET /images/star16x16_rays.png +[Fri Apr 3 15:12:59 2026] 127.0.0.1:64871 Closing +[Fri Apr 3 15:12:59 2026] 127.0.0.1:51645 Closing +[Fri Apr 3 15:12:59 2026] 127.0.0.1:56330 Accepted +[Fri Apr 3 15:13:00 2026] 127.0.0.1:56330 [200]: GET /favicon.ico +[Fri Apr 3 15:13:00 2026] 127.0.0.1:56330 Closing +[Fri Apr 3 15:19:13 2026] 127.0.0.1:52242 Accepted +[Fri Apr 3 15:19:24 2026] 127.0.0.1:52242 [200]: GET /monatsvorhersage_old.php?year=2026&month=11 +[Fri Apr 3 15:19:24 2026] 127.0.0.1:52242 Closing +[Fri Apr 3 15:19:24 2026] 127.0.0.1:52243 Accepted +[Fri Apr 3 15:19:37 2026] 127.0.0.1:52243 [200]: GET /monatsvorhersage_old.php?year=2026&month=10 +[Fri Apr 3 15:19:37 2026] 127.0.0.1:52243 Closing +[Fri Apr 3 15:19:49 2026] 127.0.0.1:53174 Accepted +[Fri Apr 3 15:20:00 2026] 127.0.0.1:53174 [200]: GET /monatsvorhersage_old.php?year=2026&month=10 +[Fri Apr 3 15:20:00 2026] 127.0.0.1:53174 Closing +[Fri Apr 3 15:20:00 2026] 127.0.0.1:53176 Accepted +[Fri Apr 3 15:20:11 2026] 127.0.0.1:53176 [200]: GET /monatsvorhersage_old.php?year=2026&month=11 +[Fri Apr 3 15:20:11 2026] 127.0.0.1:53176 Closing +[Fri Apr 3 15:21:51 2026] 127.0.0.1:59375 Accepted +[Fri Apr 3 15:22:01 2026] 127.0.0.1:59375 [200]: GET /monatsvorhersage_old.php?year=2026&month=10 +[Fri Apr 3 15:22:01 2026] 127.0.0.1:59375 Closing +[Fri Apr 3 15:22:01 2026] 127.0.0.1:52790 Accepted +[Fri Apr 3 15:22:01 2026] 127.0.0.1:52790 [200]: GET /css/style.css +[Fri Apr 3 15:22:01 2026] 127.0.0.1:52790 Closing +[Fri Apr 3 15:22:01 2026] 127.0.0.1:64786 Accepted +[Fri Apr 3 15:22:01 2026] 127.0.0.1:64786 [200]: GET /js/astronomy.browser.min.js +[Fri Apr 3 15:22:01 2026] 127.0.0.1:64786 Closing +[Fri Apr 3 15:22:01 2026] 127.0.0.1:53818 Accepted +[Fri Apr 3 15:22:01 2026] 127.0.0.1:60750 Accepted +[Fri Apr 3 15:22:01 2026] 127.0.0.1:53818 [200]: GET /images/star16x16.png +[Fri Apr 3 15:22:01 2026] 127.0.0.1:60750 [200]: GET /images/star16x16_rays.png +[Fri Apr 3 15:22:01 2026] 127.0.0.1:53818 Closing +[Fri Apr 3 15:22:01 2026] 127.0.0.1:60750 Closing +[Fri Apr 3 15:22:01 2026] 127.0.0.1:60254 Accepted +[Fri Apr 3 15:22:03 2026] 127.0.0.1:60254 [200]: GET /favicon.ico +[Fri Apr 3 15:22:03 2026] 127.0.0.1:60254 Closing +[Fri Apr 3 15:23:32 2026] 127.0.0.1:54044 Accepted +[Fri Apr 3 15:23:42 2026] 127.0.0.1:54044 [200]: GET /monatsvorhersage_old.php?year=2026&month=10 +[Fri Apr 3 15:23:42 2026] 127.0.0.1:54044 Closing +[Fri Apr 3 15:24:14 2026] 127.0.0.1:54048 Accepted +[Fri Apr 3 15:24:22 2026] 127.0.0.1:54048 [200]: GET /monatsvorhersage_old.php?year=2026&month=10 +[Fri Apr 3 15:24:22 2026] 127.0.0.1:54048 Closing +[Fri Apr 3 15:24:51 2026] 127.0.0.1:65262 Accepted +[Fri Apr 3 15:24:59 2026] 127.0.0.1:65262 [200]: GET /monatsvorhersage_old.php?year=2026&month=10 +[Fri Apr 3 15:24:59 2026] 127.0.0.1:65262 Closing +[Fri Apr 3 15:39:18 2026] 127.0.0.1:62298 Accepted +[Fri Apr 3 15:39:27 2026] 127.0.0.1:62298 [200]: GET /monatsvorhersage_old.php?year=2026&month=10 +[Fri Apr 3 15:39:27 2026] 127.0.0.1:62298 Closing +[Fri Apr 3 15:39:27 2026] 127.0.0.1:50830 Accepted +[Fri Apr 3 15:39:27 2026] 127.0.0.1:50830 [200]: GET /css/style.css +[Fri Apr 3 15:39:27 2026] 127.0.0.1:50830 Closing +[Fri Apr 3 15:39:27 2026] 127.0.0.1:55999 Accepted +[Fri Apr 3 15:39:27 2026] 127.0.0.1:55999 [200]: GET /js/astronomy.browser.min.js +[Fri Apr 3 15:39:27 2026] 127.0.0.1:55999 Closing +[Fri Apr 3 15:39:27 2026] 127.0.0.1:56815 Accepted +[Fri Apr 3 15:39:27 2026] 127.0.0.1:59542 Accepted +[Fri Apr 3 15:39:27 2026] 127.0.0.1:56815 [200]: GET /images/star16x16.png +[Fri Apr 3 15:39:27 2026] 127.0.0.1:59542 [200]: GET /images/star16x16_rays.png +[Fri Apr 3 15:39:27 2026] 127.0.0.1:56815 Closing +[Fri Apr 3 15:39:27 2026] 127.0.0.1:59542 Closing +[Fri Apr 3 15:39:27 2026] 127.0.0.1:55947 Accepted +[Fri Apr 3 15:39:29 2026] 127.0.0.1:55947 [200]: GET /favicon.ico +[Fri Apr 3 15:39:29 2026] 127.0.0.1:55947 Closing diff --git a/public/monatsvorhersage_old.php b/public/monatsvorhersage_old.php index b233abe..a8822aa 100644 --- a/public/monatsvorhersage_old.php +++ b/public/monatsvorhersage_old.php @@ -57,6 +57,7 @@ $messierData = []; $planetData = []; $moonData = null; $moonPhaseEvents = []; +$goldenHandleEvents = []; $moonPlanetApproaches = []; $planetConjunctions = []; $eclipseEvents = []; @@ -337,6 +338,23 @@ if (is_array($moonPhaseDecoded) && ($moonPhaseDecoded['ok'] ?? false)) { $moonPhaseEvents = is_array($moonPhaseDecoded['phases'] ?? null) ? $moonPhaseDecoded['phases'] : []; } +$goldenHandleOutput = []; +$goldenHandleExitCode = 0; +$goldenHandleArgs = [ + $pythonScriptPath, + 'golden_handle_for_month', + (string) $selectedYear, + (string) $selectedMonth, + $chartTimezone, +]; +$goldenHandleCommand = $pythonExecutable . ' ' . implode(' ', array_map('escapeshellarg', $goldenHandleArgs)) . ' 2>&1'; +exec($goldenHandleCommand, $goldenHandleOutput, $goldenHandleExitCode); +$goldenHandleRaw = trim(implode("\n", $goldenHandleOutput)); +$goldenHandleDecoded = json_decode($goldenHandleRaw, true); +if (is_array($goldenHandleDecoded) && ($goldenHandleDecoded['ok'] ?? false)) { + $goldenHandleEvents = is_array($goldenHandleDecoded['events'] ?? null) ? $goldenHandleDecoded['events'] : []; +} + $seasonOutput = []; $seasonExitCode = 0; $seasonArgs = [ @@ -597,6 +615,24 @@ foreach ($moonPhaseEvents as $phaseEvent) { ]; } +foreach ($goldenHandleEvents as $goldenHandleEvent) { + if (!is_array($goldenHandleEvent)) { + continue; + } + + $peakDate = (string) ($goldenHandleEvent['peak_local_date'] ?? ''); + $peakTime = (string) ($goldenHandleEvent['peak_local_time'] ?? ''); + $goldenHandleLabel = 'Goldener Henkel am Mond'; + + $monthEventList[] = [ + 'event' => $goldenHandleLabel, + 'date' => $peakDate !== '' ? $peakDate : (string) ($goldenHandleEvent['local_date'] ?? ''), + 'time' => $peakTime !== '' ? $peakTime : (string) ($goldenHandleEvent['local_time'] ?? ''), + 'local_iso' => (string) ($goldenHandleEvent['peak_local_iso'] ?? $goldenHandleEvent['local_iso'] ?? ''), + 'type' => 'golden_handle', + ]; +} + foreach ($seasonChanges as $seasonChange) { if (!is_array($seasonChange)) { continue; diff --git a/public/py/__pycache__/api.cpython-314.pyc b/public/py/__pycache__/api.cpython-314.pyc index 18f02cc0193c0e6f80baf8f6786bf33b24bb3b34..9a9d1fcb644db9e1a436a46565cd78b476d3c07d 100644 GIT binary patch delta 13881 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 NCi)(lcyY`6e*x=qPL==w delta 9094 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 QG;1F1>S89Y_GHoj1Kdkg9smFU diff --git a/public/py/api.py b/public/py/api.py index d4acfb6..87f2f82 100644 --- a/public/py/api.py +++ b/public/py/api.py @@ -466,6 +466,130 @@ def action_moon_phases_for_month(args: list[str]) -> dict: } +def action_golden_handle_for_month(args: list[str]) -> dict: + if len(args) != 3: + fail( + "Aktion golden_handle_for_month erwartet 3 Argumente: year month timezone", + extra={"argv": args}, + ) + + try: + year = int(args[0]) + month = int(args[1]) + except ValueError as exc: + fail("Jahr oder Monat sind ungueltig.", extra={"details": str(exc), "argv": args}) + + if month < 1 or month > 12: + fail("Monat muss zwischen 1 und 12 liegen.", extra={"month": month}) + + timezone_name = args[2] + try: + tz = ZoneInfo(timezone_name) + except Exception as exc: + fail("Zeitzone ist ungueltig oder auf dem Server nicht verfuegbar.", extra={"details": str(exc)}) + + local_start = datetime(year, month, 1, 0, 0, 0, tzinfo=tz) + if month == 12: + local_end = datetime(year + 1, 1, 1, 0, 0, 0, tzinfo=tz) + else: + local_end = datetime(year, month + 1, 1, 0, 0, 0, tzinfo=tz) + + # Approximate the Golden Handle geometrically by the effective selenographic + # colongitude over Montes Jura/Sinus Iridum (roughly 44 N, 36 W), + # corrected by libration. Empirically, the best-fit target shifts with + # libration in latitude by several tenths of a degree, i.e. by hours. + def target_colongitude(elat_deg: float) -> float: + return 33.385316705346064 + 0.11176238230626076 * elat_deg + 0.0734267792065295 * elat_deg * elat_deg + + def golden_handle_metric(dt_utc: datetime) -> float: + time_value = dt_to_time(dt_utc) + phase_deg = normalize_degrees(astronomy.MoonPhase(time_value)) + libration = astronomy.Libration(time_value) + effective_colongitude = normalize_degrees(phase_deg - 90.0 - float(libration.elon)) + return normalize_signed_degrees(effective_colongitude - target_colongitude(float(libration.elat))) + + def refine_peak_time(left_utc: datetime, right_utc: datetime) -> datetime: + left = left_utc + right = right_utc + f_left = golden_handle_metric(left) + f_right = golden_handle_metric(right) + for _ in range(40): + mid = left + (right - left) / 2 + f_mid = golden_handle_metric(mid) + if abs(f_mid) < 1e-5: + return mid + if f_left == 0: + return left + if f_right == 0: + return right + if f_left * f_mid <= 0: + right = mid + f_right = f_mid + else: + left = mid + f_left = f_mid + return left + (right - left) / 2 + + search_start = dt_to_time(local_start.astimezone(timezone.utc) - timedelta(days=20)) + events = [] + probe = search_start + for _ in range(3): + first_quarter = astronomy.SearchMoonPhase(90.0, probe, 40.0) + if first_quarter is None: + break + + full_moon = astronomy.SearchMoonPhase(180.0, first_quarter, 12.0) + if full_moon is None: + break + + interval_start = time_to_datetime(first_quarter) + interval_end = time_to_datetime(full_moon) + step = timedelta(hours=1) + previous_time = interval_start + previous_value = golden_handle_metric(previous_time) + current = interval_start + step + peak_utc = None + + while current <= interval_end: + current_value = golden_handle_metric(current) + if previous_value == 0 or current_value == 0 or previous_value * current_value < 0: + peak_utc = refine_peak_time(previous_time, current) + break + previous_time = current + previous_value = current_value + current += step + + if peak_utc is not None: + peak_local = peak_utc.astimezone(tz) + if peak_local.year == year and peak_local.month == month: + events.append({ + "kind": "golden_handle", + "label": "Goldener Henkel am Mond", + "utc_iso": peak_utc.isoformat().replace("+00:00", "Z"), + "local_iso": peak_local.isoformat(), + "local_date": peak_local.strftime("%d.%m.%Y"), + "local_time": peak_local.strftime("%H:%M"), + "peak_local_iso": peak_local.isoformat(), + "peak_local_date": peak_local.strftime("%d.%m.%Y"), + "peak_local_time": peak_local.strftime("%H:%M"), + }) + + probe = dt_to_time(interval_end + timedelta(days=10)) + + events.sort(key=lambda item: item["local_iso"]) + + return { + "ok": True, + "action": "golden_handle_for_month", + "selected": { + "year": year, + "month": month, + "timezone": timezone_name, + }, + "events": events, + } + + def action_season_changes_for_month(args: list[str]) -> dict: if len(args) != 3: fail( @@ -2578,7 +2702,7 @@ def action_satellite_passes(args: list[str]) -> dict: def main() -> None: if len(sys.argv) < 2: - fail("Es wurde keine Aktion uebergeben.", extra={"available_actions": ["sun_moon_rise_set", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "month_sky_context", "moon_phases_for_month", "season_changes_for_month", "moon_planet_approaches", "planet_conjunctions_for_month", "eclipses_for_month", "moon_apsides_for_month", "sun_apsides_for_month", "inner_planet_elongations_for_month", "venus_peak_magnitude_for_month", "outer_planet_events_for_month", "outer_planet_stations_for_month", "moon_star_occultations_for_month"]}) + fail("Es wurde keine Aktion uebergeben.", extra={"available_actions": ["sun_moon_rise_set", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "month_sky_context", "moon_phases_for_month", "golden_handle_for_month", "season_changes_for_month", "moon_planet_approaches", "planet_conjunctions_for_month", "eclipses_for_month", "moon_apsides_for_month", "sun_apsides_for_month", "inner_planet_elongations_for_month", "venus_peak_magnitude_for_month", "outer_planet_events_for_month", "outer_planet_stations_for_month", "moon_star_occultations_for_month"]}) action = sys.argv[1] args = sys.argv[2:] @@ -2623,6 +2747,11 @@ def main() -> None: print(json.dumps(result, ensure_ascii=False)) return + if action == "golden_handle_for_month": + result = action_golden_handle_for_month(args) + print(json.dumps(result, ensure_ascii=False)) + return + if action == "moon_planet_approaches": result = action_moon_planet_approaches(args) print(json.dumps(result, ensure_ascii=False)) @@ -2673,7 +2802,7 @@ def main() -> None: print(json.dumps(result, ensure_ascii=False)) return - fail("Unbekannte Aktion.", extra={"action": action, "available_actions": ["sun_moon_rise_set", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "month_sky_context", "moon_phases_for_month", "season_changes_for_month", "moon_planet_approaches", "planet_conjunctions_for_month", "eclipses_for_month", "moon_apsides_for_month", "sun_apsides_for_month", "inner_planet_elongations_for_month", "venus_peak_magnitude_for_month", "outer_planet_events_for_month", "outer_planet_stations_for_month", "moon_star_occultations_for_month"]}) + fail("Unbekannte Aktion.", extra={"action": action, "available_actions": ["sun_moon_rise_set", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "month_sky_context", "moon_phases_for_month", "golden_handle_for_month", "season_changes_for_month", "moon_planet_approaches", "planet_conjunctions_for_month", "eclipses_for_month", "moon_apsides_for_month", "sun_apsides_for_month", "inner_planet_elongations_for_month", "venus_peak_magnitude_for_month", "outer_planet_events_for_month", "outer_planet_stations_for_month", "moon_star_occultations_for_month"]}) if __name__ == "__main__":