diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index e48c817..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,29 +0,0 @@ -# Development Guidelines - -## Project Overview -This is a personal agenda web application built with Flask that tracks various events and important dates: -- Events: birthdays, holidays, travel itineraries, conferences, waste collection schedules -- Space launches, meteor showers, astronomical events -- Financial information (FX rates, stock market) -- UK-specific features (holidays, waste collection, railway schedules) -- Authentication via UniAuth -- Frontend uses Bootstrap 5, Leaflet for maps, FullCalendar for calendar views - -## Python Environment -- Always use `python3` directly, never `python` -- Run `black` code formatter after creating or modifying Python files -- Main entry point: `python3 web_view.py` (Flask app on port 5000) -- Tests: Use `pytest` (tests in `/tests/` directory) - -## Project Structure -- `agenda/` - Main Python package with modules for different event types -- `web_view.py` - Flask web application entry point -- `templates/` - Jinja2 HTML templates -- `static/` - CSS, JS, and frontend assets -- `config/` - Configuration files -- `personal-data/` - User's personal data (not in git) - -## Git Workflow -- Avoid committing unrelated untracked files (e.g., `node_modules/`, build artifacts) -- Only commit relevant project files -- Personal data directory (`personal-data/`) is excluded from git \ No newline at end of file diff --git a/agenda/meteors.py b/agenda/meteors.py deleted file mode 100644 index b40ce15..0000000 --- a/agenda/meteors.py +++ /dev/null @@ -1,267 +0,0 @@ -"""Meteor shower data calculations.""" - -import typing -from datetime import datetime, timedelta - -import ephem # type: ignore - -MeteorShower = dict[str, typing.Any] - - -# Meteor shower definitions with parent comet orbital elements -METEOR_SHOWERS = { - "Quadrantids": { - "name": "Quadrantids", - "radiant_ra": "15h20m", # Right ascension at peak - "radiant_dec": "+49.5°", # Declination at peak - "peak_solar_longitude": 283.16, # Solar longitude at peak - "activity_start": 283.16 - 10, # Activity period - "activity_end": 283.16 + 10, - "rate_max": 120, - "rate_min": 50, - "parent_body": "2003 EH1", - "visibility": "Northern Hemisphere", - "description": "The year kicks off with the Quadrantids, known for their brief but intense peak lasting only about 4 hours.", - "velocity_kms": 41, - }, - "Lyrids": { - "name": "Lyrids", - "radiant_ra": "18h04m", - "radiant_dec": "+32.32°", - "peak_solar_longitude": 32.32, - "activity_start": 32.32 - 10, - "activity_end": 32.32 + 10, - "rate_max": 18, - "rate_min": 10, - "parent_body": "C/1861 G1 (Thatcher)", - "visibility": "Both hemispheres", - "description": "The Lyrids are one of the oldest recorded meteor showers, with observations dating back 2,700 years.", - "velocity_kms": 49, - }, - "Eta Aquariids": { - "name": "Eta Aquariids", - "radiant_ra": "22h32m", - "radiant_dec": "-1.0°", - "peak_solar_longitude": 45.5, - "activity_start": 45.5 - 15, - "activity_end": 45.5 + 15, - "rate_max": 60, - "rate_min": 30, - "parent_body": "1P/Halley", - "visibility": "Southern Hemisphere (best)", - "description": "Created by debris from Halley's Comet, these meteors are fast and often leave glowing trails.", - "velocity_kms": 66, - }, - "Perseids": { - "name": "Perseids", - "radiant_ra": "03h04m", - "radiant_dec": "+58.0°", - "peak_solar_longitude": 140.0, - "activity_start": 140.0 - 15, - "activity_end": 140.0 + 15, - "rate_max": 100, - "rate_min": 50, - "parent_body": "109P/Swift-Tuttle", - "visibility": "Northern Hemisphere", - "description": "One of the most popular meteor showers, viewing conditions vary by year based on moon phase.", - "velocity_kms": 59, - }, - "Orionids": { - "name": "Orionids", - "radiant_ra": "06h20m", - "radiant_dec": "+16.0°", - "peak_solar_longitude": 208.0, - "activity_start": 208.0 - 15, - "activity_end": 208.0 + 15, - "rate_max": 25, - "rate_min": 15, - "parent_body": "1P/Halley", - "visibility": "Both hemispheres", - "description": "Another shower created by Halley's Comet debris, known for their speed and brightness.", - "velocity_kms": 66, - }, - "Geminids": { - "name": "Geminids", - "radiant_ra": "07h28m", - "radiant_dec": "+32.0°", - "peak_solar_longitude": 262.2, - "activity_start": 262.2 - 10, - "activity_end": 262.2 + 10, - "rate_max": 120, - "rate_min": 60, - "parent_body": "3200 Phaethon", - "visibility": "Both hemispheres", - "description": "The best shower of most years with the highest rates. Unusual for being caused by an asteroid rather than a comet.", - "velocity_kms": 35, - }, - "Ursids": { - "name": "Ursids", - "radiant_ra": "14h28m", - "radiant_dec": "+75.0°", - "peak_solar_longitude": 270.7, - "activity_start": 270.7 - 5, - "activity_end": 270.7 + 5, - "rate_max": 10, - "rate_min": 5, - "parent_body": "8P/Tuttle", - "visibility": "Northern Hemisphere", - "description": "A minor shower that closes out the year, best viewed from dark locations away from city lights.", - "velocity_kms": 33, - }, -} - - -def calculate_solar_longitude_date(year: int, target_longitude: float) -> datetime: - """Calculate the date when the Sun reaches a specific longitude for a given year.""" - # Start from beginning of year - start_date = datetime(year, 1, 1) - - # Use PyEphem to calculate solar longitude - observer = ephem.Observer() - observer.lat = "0" # Equator - observer.lon = "0" # Greenwich - observer.date = start_date - - sun = ephem.Sun(observer) - - # Search for the date when solar longitude matches target - # Solar longitude 0° = Spring Equinox (around March 20) - # We need to find when sun reaches the target longitude - - # Approximate: start search from reasonable date based on longitude - if target_longitude < 90: # Spring (Mar-Jun) - search_start = datetime(year, 3, 1) - elif target_longitude < 180: # Summer (Jun-Sep) - search_start = datetime(year, 6, 1) - elif target_longitude < 270: # Fall (Sep-Dec) - search_start = datetime(year, 9, 1) - else: # Winter (Dec-Mar) - search_start = datetime(year, 12, 1) - - observer.date = search_start - - # Search within a reasonable range (±60 days) - for day_offset in range(-60, 61): - test_date = search_start + timedelta(days=day_offset) - observer.date = test_date - sun.compute(observer) - - # Convert ecliptic longitude to degrees - sun_longitude = float(sun.hlon) * 180 / ephem.pi - - # Check if we're close to target longitude (within 0.5 degrees) - if abs(sun_longitude - target_longitude) < 0.5: - return test_date - - # Fallback: return approximation based on solar longitude - # Rough approximation: solar longitude increases ~1° per day - days_from_equinox = target_longitude - equinox_date = datetime(year, 3, 20) # Approximate spring equinox - return equinox_date + timedelta(days=days_from_equinox) - - -def calculate_moon_phase(date_obj: datetime) -> tuple[float, str]: - """Calculate moon phase for a given date.""" - observer = ephem.Observer() - observer.date = date_obj - - moon = ephem.Moon(observer) - moon.compute(observer) - - # Moon phase (0 = new moon, 0.5 = full moon, 1 = new moon again) - phase = moon.phase / 100.0 - - # Determine moon phase name and viewing quality - if phase < 0.1: - phase_name = "New Moon" - viewing_quality = "excellent" - elif phase < 0.3: - phase_name = "Waxing Crescent" - viewing_quality = "good" - elif phase < 0.7: - phase_name = "First Quarter" if phase < 0.5 else "Waxing Gibbous" - viewing_quality = "moderate" - elif phase < 0.9: - phase_name = "Full Moon" - viewing_quality = "poor" - else: - phase_name = "Waning Crescent" - viewing_quality = "good" - - return phase, f"{phase_name} ({viewing_quality} viewing)" - - -def calculate_meteor_shower_data(year: int) -> list[MeteorShower]: - """Calculate meteor shower data for a given year using astronomical calculations.""" - meteor_data = [] - - for shower_id, shower_info in METEOR_SHOWERS.items(): - # Calculate peak date based on solar longitude - peak_date = calculate_solar_longitude_date( - year, shower_info["peak_solar_longitude"] - ) - - # Calculate activity period - activity_start = calculate_solar_longitude_date( - year, shower_info["activity_start"] - ) - activity_end = calculate_solar_longitude_date(year, shower_info["activity_end"]) - - # Calculate moon phase at peak - moon_illumination, moon_phase_desc = calculate_moon_phase(peak_date) - - # Format dates - peak_formatted = peak_date.strftime("%B %d") - if peak_date.day != (peak_date + timedelta(days=1)).day: - peak_formatted += f"-{(peak_date + timedelta(days=1)).strftime('%d')}" - - active_formatted = ( - f"{activity_start.strftime('%B %d')} - {activity_end.strftime('%B %d')}" - ) - - # Determine viewing quality based on moon phase - viewing_quality = ( - "excellent" - if moon_illumination < 0.3 - else ( - "good" - if moon_illumination < 0.7 - else "moderate" if moon_illumination < 0.9 else "poor" - ) - ) - - meteor_shower = { - "name": shower_info["name"], - "peak": peak_formatted, - "active": active_formatted, - "rate": f"{shower_info['rate_min']}-{shower_info['rate_max']} meteors per hour", - "radiant": shower_info["radiant_ra"].split("h")[0] - + "h " - + shower_info["radiant_dec"], - "moon_phase": moon_phase_desc, - "visibility": shower_info["visibility"], - "description": shower_info["description"], - "peak_date": peak_date.strftime("%Y-%m-%d"), - "start_date": activity_start.strftime("%Y-%m-%d"), - "end_date": activity_end.strftime("%Y-%m-%d"), - "rate_min": shower_info["rate_min"], - "rate_max": shower_info["rate_max"], - "moon_illumination": moon_illumination, - "viewing_quality": viewing_quality, - "parent_body": shower_info["parent_body"], - "velocity_kms": shower_info["velocity_kms"], - } - - meteor_data.append(meteor_shower) - - # Sort by peak date - meteor_data.sort(key=lambda x: datetime.strptime(x["peak_date"], "%Y-%m-%d")) - - return meteor_data - - -def get_meteor_data(year: int | None = None) -> list[MeteorShower]: - """Get meteor shower data for a specific year using astronomical calculations.""" - if year is None: - year = datetime.now().year - return calculate_meteor_shower_data(year) diff --git a/web_view.py b/web_view.py index 91a0e94..5afb099 100755 --- a/web_view.py +++ b/web_view.py @@ -23,7 +23,6 @@ import agenda.data import agenda.error_mail import agenda.fx import agenda.holidays -import agenda.meteors import agenda.stats import agenda.thespacedevs import agenda.trip @@ -217,7 +216,78 @@ def launch_list() -> str: @app.route("/meteors") def meteor_list() -> str: """Web page showing meteor shower information.""" - meteors = agenda.meteors.get_meteor_data() + meteors = [ + { + "name": "Quadrantids", + "peak": "January 3-4", + "active": "December 28 - January 12", + "rate": "50-100 meteors per hour", + "radiant": "Boötes", + "moon_phase": "New Moon (excellent viewing)", + "visibility": "Northern Hemisphere", + "description": "The year kicks off with the Quadrantids, known for their brief but intense peak lasting only about 4 hours.", + }, + { + "name": "Lyrids", + "peak": "April 21-22", + "active": "April 14 - April 30", + "rate": "10-15 meteors per hour", + "radiant": "Lyra", + "moon_phase": "Waning Crescent (good viewing)", + "visibility": "Both hemispheres", + "description": "The Lyrids are one of the oldest recorded meteor showers, with observations dating back 2,700 years.", + }, + { + "name": "Eta Aquariids", + "peak": "May 5-6", + "active": "April 15 - May 27", + "rate": "30-60 meteors per hour", + "radiant": "Aquarius", + "moon_phase": "First Quarter (moderate viewing)", + "visibility": "Southern Hemisphere (best)", + "description": "Created by debris from Halley's Comet, these meteors are fast and often leave glowing trails.", + }, + { + "name": "Perseids", + "peak": "August 12-13", + "active": "July 17 - August 24", + "rate": "50-100 meteors per hour", + "radiant": "Perseus", + "moon_phase": "Full Moon (poor viewing)", + "visibility": "Northern Hemisphere", + "description": "One of the most popular meteor showers, though 2025 viewing will be hampered by bright moonlight.", + }, + { + "name": "Orionids", + "peak": "October 21-22", + "active": "October 2 - November 7", + "rate": "15-25 meteors per hour", + "radiant": "Orion", + "moon_phase": "Waning Crescent (good viewing)", + "visibility": "Both hemispheres", + "description": "Another shower created by Halley's Comet debris, known for their speed and brightness.", + }, + { + "name": "Geminids", + "peak": "December 13-14", + "active": "December 4 - December 20", + "rate": "60-120 meteors per hour", + "radiant": "Gemini", + "moon_phase": "Waxing Gibbous (moderate viewing)", + "visibility": "Both hemispheres", + "description": "The best shower of 2025 with the highest rates. Unusual for being caused by an asteroid (3200 Phaethon) rather than a comet.", + }, + { + "name": "Ursids", + "peak": "December 22-23", + "active": "December 17 - December 26", + "rate": "5-10 meteors per hour", + "radiant": "Ursa Minor", + "moon_phase": "New Moon (excellent viewing)", + "visibility": "Northern Hemisphere", + "description": "A minor shower that closes out the year, best viewed from dark locations away from city lights.", + }, + ] return flask.render_template( "meteors.html",