Skip to main content
Glama
README.md
# Moon & Tides — Tideline

Tidal calendar for the US coast. Generates ICS calendar files with high/low tides, king tide alerts, tidepooling windows, flood risk events, Perigean spring tides, and more. Comes with a web dashboard, a CLI, and an MCP server so Claude can answer tide questions directly.

Data source: NOAA CO-OPS API — free, no account or API key required. US coastal stations only.

---

## Requirements

- **Python 3.11 or later** — for the backend, CLI, and MCP server
- **Node.js 18 or later** — for the web dashboard (optional if you only use the CLI)
- Windows, macOS, or Linux

---

## Installation

### Step 1 — Get the code

```powershell
git clone https://github.com/Earththing/Moon_and_Tides.git
cd Moon_and_Tides
```

### Step 2 — Install Python dependencies

```powershell
pip install -r requirements.txt
```

This installs FastAPI, httpx, ephem (for sunrise/sunset), icalendar, the MCP SDK, and everything else the backend needs.

### Step 3 — Register the `tideline` command

```powershell
pip install -e .
```

This makes `tideline` available as a command in your terminal. If you skip this step, replace every `tideline` command below with `python -m app.cli` — they are otherwise identical.

### Step 4 (optional) — Install the web dashboard

```powershell
cd frontend
npm install
cd ..
```

This downloads the React/TypeScript frontend dependencies (~150 MB in `node_modules`). Skip if you only plan to use the CLI or API.

---

## Getting the app running

The app has two parts: a **backend** (Python/FastAPI) that handles all data fetching, caching, and ICS generation, and a **frontend** (React) that provides the interactive dashboard. You need both running to use the web interface.

### Terminal 1 — Start the backend

```powershell
python -m uvicorn app.main:app --reload
```

**What this does:** starts the FastAPI data server on port 8000. It handles all communication with NOAA, builds ICS calendar files, computes sunrise/sunset times, and serves the REST API that the frontend calls. The `--reload` flag means it automatically restarts when you change a Python file — you never need to stop and restart it manually during development.

You'll see:
```
INFO:     Uvicorn running on http://127.0.0.1:8000
INFO:     Application startup complete.
```

You can verify it's working at `http://localhost:8000/docs` — that's the interactive API explorer.

### Terminal 2 — Start the frontend

```powershell
cd frontend
npm run dev
```

**What this does:** starts the Vite development server on port 5173. Vite serves the React dashboard and also acts as a proxy — when the dashboard calls `/api/v1/...` or `/calendar/...`, Vite forwards those requests to the backend on port 8000. This means you only ever need to open one URL in your browser.

You'll see:
```
  VITE v8.x  ready in 300ms
  ➜  Local:   http://localhost:5173/
```

### Step 3 — Open the dashboard

Go to **http://localhost:5173** in your browser.

Search for a city or address, and the dashboard will find the nearest NOAA tide station and show today's tide curve, nearby stations, upcoming events, and a calendar download button.

---

## Quick start — CLI only

If you just want tide data without the web interface:

```powershell
# Today's tides near you (fastest way to check)
tideline preview "Seattle WA"

# Download a full-year calendar file
tideline ics "Seattle WA" --year 2026 --out seattle_tides_2026.ics
```

Import the `.ics` file into Google Calendar, Apple Calendar, or Outlook.

---

## CLI reference

All commands accept a place name, a station ID, or coordinates. If no NOAA station exists at the location you search, the nearest one is used automatically and you'll be told how far away it is.

### `tideline preview` — Today's tides at a glance

```powershell
tideline preview "Seattle WA"
tideline preview "San Francisco, CA"
tideline preview --station 9447130
tideline preview --lat 47.81 --lon -122.38
```

Output:
```
Today's tides at 9447130:
  02:06 (2:06 AM)  H  10.32 ft
  09:32 (9:32 AM)  L  -0.87 ft
  17:41 (5:41 PM)  H  10.69 ft
  22:34 (10:34 PM)  L  7.91 ft
```

---

### `tideline tides` — Browse predictions

```powershell
# Today's tides (first 20 events)
tideline tides "Seattle WA"

# Specific year — shows first 20 events
tideline tides "Seattle WA" --year 2026

# Single date
tideline tides "Seattle WA" --date 2026-06-21

# By station ID
tideline tides --station 9447130 --date 2026-06-21

# By coordinates
tideline tides --lat 47.81 --lon -122.38 --date 2026-06-21

# Show more rows (default is 20)
tideline tides "Seattle WA" --year 2026 --count 100
```

Times are shown in both 24-hour and 12-hour AM/PM format. Use `tideline ics` (below) to save a full year as a calendar file.

---

### `tideline ics` — Generate a calendar file

```powershell
# Full year, saves to tides_9447130_2026.ics in current folder
tideline ics "Seattle WA" --year 2026

# Choose the output filename
tideline ics "Seattle WA" --year 2026 --out seattle_2026.ics

# Alerts only — all-day events for king tides, extremes, Perigean tides
# Good for overlaying on top of a main calendar without clutter
tideline ics "Seattle WA" --year 2026 --alerts-only --out alerts_2026.ics

# By station ID
tideline ics --station 9447130 --year 2026 --out seattle_2026.ics

# By coordinates
tideline ics --lat 47.81 --lon -122.38 --year 2026 --out tides.ics
```

After saving, the command shows a breakdown of what's in the file:

```
Saved 432,817 bytes to seattle_2026.ics

Events included:
  🌊 High tides                    1410
  🏖 Low tides                     1410
  ⚠️  King tides                     52
  ⚠️  Extreme lows                   52
  🌕 Perigean spring tides            6
  🚨 Flood risk days                  0
  🦀 Tidepooling windows            124
  Total                            3054
```

**Event types in the ICS file:**

| Icon | Event | Type | When |
|---|---|---|---|
| 🌊 | High Tide | Timed (15 min) | Every high tide |
| 🏖 | Low Tide | Timed (15 min) | Every low tide |
| ⚠️ | King Tide | All-day | Top 10% of highs |
| ⚠️ | Extreme Low | All-day | Bottom 10% of lows |
| 🌕 | Perigean Spring Tide | All-day | Moon at perigee + new/full moon |
| 🚨 | Flood Risk | All-day | Predicted high exceeds NOAA flood threshold |
| 🦀 | Tidepooling Window | All-day | Low tide during daylight hours |
| ⚓ | Slack Water | Timed (10 min) | Tide reversal (harmonic stations only) |
| 🌊 | King Tide Nearby | All-day | King tide at a station within your radius |

---

### `tideline stations` — Find NOAA stations near you

```powershell
# All stations within 50 miles (default)
tideline stations "Seattle WA"

# Wider search
tideline stations "Seattle WA" --radius 100

# Limit results
tideline stations "Seattle WA" --radius 50 --count 3
```

Output:
```
Location: Seattle, King County, Washington, United States

ID         Name                           Miles    Harmonic
------------------------------------------------------------
9447130    Seattle                         0.4    yes
9445958    Bremerton                      13.9    yes
9444900    Port Townsend                  27.3    yes
9446484    Tacoma                         23.8    yes
```

**Harmonic** stations have full tidal constituent data. Tidepooling windows and slack water events require a harmonic station.

---

### `tideline extremes` — Highest and lowest tides of the year

```powershell
tideline extremes "Seattle WA" --year 2026
tideline extremes --station 9447130 --year 2026
```

Output:
```
Station: 9447130  |  Year: 2026

Top 10 Highest Tides:
  2026-01-05 07:07 (7:07 AM)  13.01 ft
  2026-12-26 07:22 (7:22 AM)  13.01 ft
  ...

Top 10 Lowest Tides:
  2026-06-15 11:33 (11:33 AM)  -4.30 ft
  2026-06-16 12:22 (12:22 PM)  -4.20 ft
  ...
```

---

## Web dashboard

The web dashboard at `http://localhost:5173` gives you an interactive view of all the same data.

### Features

**Location search** — type any city, address, or landmark. The app geocodes it and finds the nearest NOAA station. If no station exists at your exact location, a banner tells you which station is being used and how far away it is.

**Tide curve chart** — shows the tidal pattern for the selected date with:
- 24-hour and 12-hour times on hover
- Colored dots for high (blue) and low (green) tides with height labels
- A "now" line showing the current time (day view only)
- Flood threshold line if NOAA has one for the station
- Day/night shading (toggle with the 🌙 button) — night hours are shaded dark blue with a gradual dawn/dusk transition showing sunrise and sunset times

**Four chart views** — select with the Day / Week / Month / Year buttons (hover each for a description):
- **Day** — 6-minute tide curve for a single day
- **Week** — high & low tides across 7 days (use ← → to navigate weeks)
- **Month** — high & low tides for the full month
- **Year** — monthly average high & low bars

**Date navigation** — ← and → arrows move by one day/week/month/year depending on the view. The date picker jumps to a specific date. A **Today** button appears when you're not on today.

**Nearby stations panel** — lists the 5 nearest NOAA stations with live tide badges showing current height and direction. Click any station to switch to it.

**Today's tides list** — the current tide event is highlighted. Shows times in 12-hour format.

**Calendar options** — choose which event types to include in the downloaded ICS. Options disabled for your station (e.g. slack water on a non-harmonic station) show a tooltip explaining why.

**Download button** — shows the total event count before you download. Expand "Show event breakdown" to see counts by type.

**Coming up feed** — upcoming events in the next 30 days: king tides (amber), tidepooling windows (green), and Perigean spring tides. Shows up to 6; click "View all" to see the full list on the Alerts page.

**Alerts page** — full list of all upcoming events for the selected station and year. Also reachable from the Alerts tab in the nav bar — it remembers your last station.

---

## Web API

The backend exposes a REST API at `http://localhost:8000`. Open `http://localhost:8000/docs` for an interactive explorer where you can try any endpoint.

| Endpoint | What it does |
|---|---|
| `POST /geocode` | Convert address → lat/lon |
| `GET /api/v1/stations/nearest?lat=&lon=&count=5` | Nearest NOAA stations with distance |
| `GET /api/v1/stations/radius?lat=&lon=&radius_miles=50` | All stations within a radius |
| `GET /api/v1/station/{id}` | Station metadata, tidal range, flood thresholds |
| `GET /api/v1/tides/day?station_id=&date=YYYY-MM-DD` | 6-min curve + hilo for one day |
| `GET /api/v1/tides/range?station_id=&begin=&end=` | Hilo data for a date range |
| `GET /api/v1/tides/year-summary?station_id=&year=` | Monthly means and extremes |
| `GET /api/v1/special-events?station_id=&year=` | King tides, Perigean tides, flood risk, tidepooling |
| `GET /api/v1/solar-day?lat=&lon=&date=` | Sunrise/sunset in local time |
| `GET /api/v1/solar-range?lat=&lon=&begin=&end=` | Sunrise/sunset for a date range |
| `GET /api/v1/ics-preview?station_id=&year=&options=` | Event counts without building the file |
| `GET /calendar/2026/47.81,-122.38.ics?options=hilo,alerts` | Download ICS by coordinates |
| `GET /calendar/2026/47.81,-122.38-alerts.ics` | Alerts-only ICS by coordinates |

**Example — try this in your browser while the backend is running:**
```
http://localhost:8000/api/v1/tides/day?station_id=9447130&date=2026-07-04
http://localhost:8000/api/v1/solar-day?lat=47.81&lon=-122.38&date=2026-07-04
http://localhost:8000/api/v1/special-events?station_id=9447130&year=2026
```

---

## MCP server (for Claude Desktop)

The MCP server lets Claude answer tide questions by calling real NOAA data directly. Once configured, you can ask Claude things like:

- "When is high tide in Seattle tomorrow?"
- "What are the best tidepooling days near Bodega Bay this summer?"
- "Find the lowest tide within 50 miles of San Francisco in July 2026"
- "Are there any king tides in Puget Sound this month?"

### Setup

Find your Claude Desktop config file:
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`

Add this (replace the path with your actual path):

```json
{
  "mcpServers": {
    "tideline": {
      "command": "python",
      "args": ["C:\\full\\path\\to\\Moon_and_Tides\\mcp_server.py"]
    }
  }
}
```

On macOS/Linux use forward slashes:
```json
{
  "mcpServers": {
    "tideline": {
      "command": "python",
      "args": ["/full/path/to/Moon_and_Tides/mcp_server.py"]
    }
  }
}
```

Restart Claude Desktop after saving. The MCP server starts automatically when Claude opens — you do not need to run it yourself.

### Available tools

| Tool | What you can ask |
|---|---|
| `geocode` | Find lat/lon for an address |
| `find_stations` | "What NOAA stations are near Portland OR?" |
| `get_tides_day` | "What are today's tides in Bodega Bay?" |
| `get_tides_range` | "Show me tides for the week of July 4th in Seattle" |
| `get_station_info` | "What's the tidal range at station 9447130?" |
| `get_tidal_type` | "Is Astoria OR diurnal or semidiurnal?" |
| `get_extreme_tides` | "What's the highest tide in Seattle this summer?" |
| `find_extreme_in_radius` | "Find king tides within 75 miles of San Francisco in January" |
| `check_flood_risk` | "Any flood risk tides in Miami this winter?" |
| `get_slack_water` | "When is slack water in Puget Sound on June 21?" |
| `get_tidepooling_windows` | "Best tidepooling days near Monterey in July?" |
| `get_perigean_spring_tides` | "When are the Perigean spring tides in 2026?" |
| `generate_ics_url` | "Give me a calendar link for Seattle tides 2026" |

---

## Caching

All data is cached locally in `cache/tides.db` (SQLite, created automatically). The cache keeps data indefinitely and only re-fetches when something is likely to have changed.

| Data | Kept | Re-fetched when |
|---|---|---|
| Tide predictions — past years | Forever | Never (NOAA doesn't revise completed years) |
| Tide predictions — current/future year | Forever | Once a year (harmonic constants may be updated) |
| Station list | Forever | Every 30 days (new stations added occasionally) |
| Tidal ranges | Forever | Only if manually deleted |
| Flood level thresholds | Forever | Only if manually deleted |
| Perigean spring tide dates | Forever | Astronomical — one computation per year |
| Sunrise/sunset times | Forever | Astronomical — never changes for a given date |

The cache is fully transparent — repeat commands are instant and tests never hit the network. To force a full refresh, delete `cache/tides.db`.

---

## Running tests

```powershell
# Fast tests — no network required, runs in ~5 seconds
python -m pytest tests/ -m "not live and not slow" -v

# Tests that hit the real NOAA API (requires internet)
python -m pytest tests/ -m "live" -v

# Frontend tests
cd frontend
npm test
```

Current counts: 45 Python tests, 21 TypeScript tests, all passing.

---

## Importing your tides calendar

### Google Calendar

1. Go to calendar.google.com → Settings (gear icon) → **Import & export**
2. Click **Import**, select your `.ics` file, choose which calendar to add it to
3. Click **Import**

For a live-updating subscription instead of a one-time import, use the calendar URL:
```
http://localhost:8000/calendar/2026/47.81,-122.38.ics?options=hilo,alerts,perigean
```
In Google Calendar: **Other calendars → From URL**, paste the link.

### Apple Calendar

- **One-time import:** File → Import, select the `.ics` file
- **Subscribe (updates automatically):** File → New Calendar Subscription, paste the URL above

### Outlook

File → Open & Export → Import/Export → Import an iCalendar file.

---

## Imported events into the wrong Google Calendar?

If you added the `.ics` to the wrong calendar and want to remove all tide events:

**The script:** `scripts/delete_tide_events.gs`

**Steps:**

1. Go to [script.google.com](https://script.google.com) and click **New project**
2. Delete any existing code and paste the entire contents of `scripts/delete_tide_events.gs`
3. Set `CALENDAR_NAME` at the top to the exact name of the calendar you imported into (find it in the left sidebar of Google Calendar)
4. Leave `DRY_RUN = true` for now
5. Click **Save**, then **Run** — select `deleteTideEvents` in the dropdown
6. On first run, approve the permissions Google asks for
7. Check the **Execution log** at the bottom — it lists everything it *would* delete
8. When satisfied, change `DRY_RUN = false` and run again to actually delete
9. If you hit Google's rate limits mid-run, just run again — it resumes where it left off

The script removes all event types that Tideline creates:

| Title prefix | Event type |
|---|---|
| `🌊 High Tide` | High tide events |
| `🏖 Low Tide` | Low tide events |
| `⚠️ King Tide` | King tide alerts |
| `⚠️ Extreme Low` | Extreme low tide alerts |
| `🌕 Perigean Spring Tide` | Perigean events |
| `🚨 Flood Risk` | Flood risk events |
| `🦀 Tidepooling Window` | Tidepooling windows |
| `⚓ Slack Water` | Slack water events |
| `🌊 King Tide Nearby` | Radius alerts |

It will not touch any other events in the calendar.

---

## Project structure

```
app/
  tide_data.py       — NOAA fetch + SQLite cache + to_12h() helper
  station_finder.py  — Station search, haversine distance, tidal type
  ics_builder.py     — RFC-5545 ICS: all 9 event types, returns event counts
  alert_engine.py    — Extreme tide detection, flood risk, radius sweep
  special_events.py  — Slack water, tidepooling windows, Perigean tides
  solar_cache.py     — Sunrise/sunset via ephem, cached in SQLite
  geocode.py         — Nominatim address → lat/lon
  cli.py             — tideline CLI (preview/tides/ics/stations/extremes)
  main.py            — FastAPI web server + all REST endpoints

mcp_server.py        — MCP server for Claude Desktop (13 tools)

frontend/
  src/
    pages/
      Dashboard.tsx  — Main dashboard (chart, stations, tides, feed)
      AlertsPage.tsx — Full upcoming events list
      HelpPage.tsx   — Calendar import instructions
      MapPage.tsx    — Station map (stub — see TODO.md)
    components/
      TideCurveChart.tsx   — Day/week/month/year chart with day/night shading
      DateNav.tsx          — Date navigation + day/night toggle
      NearbyStations.tsx   — Station list with live tide badges
      TideEventList.tsx    — Today's tide events with current highlight
      CalendarOptions.tsx  — ICS event type checkboxes
      AlertRadiusSlider.tsx
      DownloadButton.tsx   — Shows event counts, links to ICS
      ComingUpFeed.tsx     — Upcoming alerts and opportunities
      LocationSearch.tsx   — Debounced geocode autocomplete
      NavBar.tsx / TopBar.tsx
    api.ts           — All fetch functions + TypeScript types
    station-store.ts — localStorage for last-selected station
    tokens.ts        — Design system colors

scripts/
  delete_tide_events.gs  — Google Apps Script: bulk-remove tide events

tests/               — 45 Python tests, 21 TypeScript tests
cache/               — SQLite DB (auto-created, gitignored)
TODO.md              — Remaining work: map page, missing tests, viz layer
```

---

## Notes and limitations

- **US only** — NOAA CO-OPS covers the contiguous US, Alaska, Hawaii, and US territories. No international stations.
- **Nearest station substitution** — if you search "Edmonds WA" and there's no station there, the app uses the nearest one (Seattle, 14 miles). The CLI prints a note; the web app shows a banner.
- **Tidal heights are relative to MLLW** (Mean Lower Low Water) — the average of all lower low water heights. Negative heights are normal and mean the tide is below the average low.
- **Times are in the station's local time** — standard or daylight, whichever applies on that date.
- **Harmonic stations** — slack water and tidepooling features require a harmonic station. These are the primary stations with full tidal constituent analysis. Subordinate stations (marked "no" in the Harmonic column) have predicted tides but not the 6-minute curves needed for those features.
- **The `tideline` command** — available after `pip install -e .`. Without it, use `python -m app.cli` as a drop-in for every example above.