How SaltCast Works
SaltCast analyzes real-time tidal, weather, lunar, and water temperature data to predict the best saltwater fishing windows — updated every hour for 10 coastal locations.
The Overall Score
Every hour at every location gets a score from 0–100. This score combines four factors, each weighted by how much it actually influences saltwater fish behavior:
The raw weighted score is then run through a power curve that pulls average conditions down significantly. This means a 90+ score is genuinely exceptional — not just a good tide on a calm day.
What Each Factor Measures
Tides (30%)
Rising tides score highest — baitfish push into the shallows, predators follow. Falling and slack-low tides score lowest. Data comes from NOAA tide prediction stations closest to each location.
| Tide Condition | Score |
|---|---|
| Rising, upper half | 90 |
| Rising, lower half | 65 |
| Slack high | 40 |
| Falling, upper half | 25 |
| Falling, lower half | 15 |
| Slack low | 5 |
Weather (30%)
Light winds (under 10 kts) and stable high pressure produce the best scores. High winds chop up the surface, disrupt baitfish, and make fish lock tight to structure. Heavy cloud cover slightly reduces scores as it limits light penetration.
Moon / Solunar (25%)
Solunar theory holds that fish feed most aggressively during major periods (around moonrise and moonset, roughly every 12.4 hours) and less intensely during minor periods (every 6 hours). Between periods, fish tend to be inactive.
| Solunar Period | Score |
|---|---|
| Major period | 90 |
| Minor period | 50 |
| Between periods | 10 |
Water Temperature (15%)
The sweet spot for most Gulf Coast and Atlantic inshore species is 65–78°F. Below 55°F or above 85°F, fish become lethargic and feeding activity drops significantly.
The Fish Scale
Each hour in the slideout panel shows 0–4 fish icons representing fishing quality for that specific hour:
The Heatmap Strip
Each day card in the 7-day forecast shows a 24-segment color strip — one segment per hour. The color tells you at a glance when the bite turns on:
The AI Fishing Forecast
Each day in the slideout panel includes a plain-English forecast written by Claude (Haiku) — Anthropic's AI. The forecast synthesizes the day's conditions into specific advice: which species will be active, what tactics to use, and what time to be on the water. It's generated fresh with every pipeline run.
Data Sources
Score Alert Emails
SaltCast automatically emails bob@rensho.ai whenever any location scores 90 or above in the next 24 hours. The email includes the location, peak score, best time windows, tide direction, and wind speed — so you can plan a trip on short notice when conditions align perfectly.
Technical Architecture
SaltCast is a fully automated prediction engine built on a Hetzner VPS using n8n for orchestration, Supabase (self-hosted) for storage, and a static HTML frontend served by Caddy.
Stack Overview
Data pipeline + AI
Single source of truth
saltcast.rensho.ai
| Component | Details |
|---|---|
| Server | Hetzner VPS — root@5.161.91.134 |
| n8n | Docker container (custom image with ffmpeg). Access at n8n.rensho.ai. Do NOT restart with docker compose — use docker restart n8n |
| Supabase | Self-hosted Docker stack at supabase.rensho.ai. Schema: saltcast |
| Caddy | Docker container. Config: /opt/rensho/caddy/Caddyfile. Reload: docker exec caddy caddy reload --config /etc/caddy/Caddyfile |
| Frontend | /opt/rensho/www/saltcast/ — single index.html |
scp from your Mac to write directly to the original file inode. Using cat > or Python open('w') creates a new inode that the Docker bind mount won't follow.
n8n Workflows
SaltCast — Fishing Prediction Pipeline (HlkH6OgQRoD9BUgJ)
Currently inactive (manual trigger while testing). Set to hourly once stable. Takes 5–8 minutes per full run across all 10 locations. Max concurrency = 1 prevents overlapping runs.
| Node | Purpose |
|---|---|
| Every Hour | Schedule trigger — change interval here |
| Get Active Locations | Fetches all locations where active=true from Supabase |
| Loop Over Locations | Iterates one location at a time |
| Fetch Weather | Open-Meteo API — wind, pressure, clouds (7-day hourly) |
| Fetch Tides | NOAA Tides & Currents — hourly predictions (7-day) |
| Fetch Water Temp | NOAA water temperature. continueOnFail=true, retryOnFail=true (NOAA timeouts are common) |
| Calculate Scores | Core scoring algorithm — weather, tide, solunar, water temp → power curve → overall score |
| Batch by Day / Upsert Predictions | Upserts 168 hourly predictions per location to Supabase |
| Build Daily Summaries | Aggregates 24 hours into daily summary stats |
| Build Narrative Prompts | Constructs plain-text prompt for each day |
| Generate Narratives | Calls Claude Haiku for each day's AI forecast (max 300 tokens) |
| Merge Narratives | Merges narrative text back onto summary objects |
| Upsert Summaries | Writes to daily_summaries table. Empty response = success (204 No Content) |
SaltCast — Score Alert (90+) (NFEHqoCMw5GdLaTI)
Active, runs hourly. Queries predictions ≥90 in next 24 hours, sends styled HTML email from saltcast@rensho.ai via Resend SMTP (credential ID: dRfO8HPussxphvHM).
Supabase Schema
All tables live in the saltcast schema. PostgREST is configured to expose this schema via PGRST_DB_SCHEMAS.
locations
name text (e.g. "St. George Island, FL")
lat numeric
lng numeric
noaa_station_id text (NOAA tide gauge ID)
timezone text (e.g. "America/Chicago")
active boolean (false = excluded from pipeline)
predictions
location_id uuid REFERENCES locations
forecast_time timestamptz
overall_score integer (0–100, power-curved)
weather_score integer
tide_score integer
moon_score integer
water_temp_score integer
wind_speed numeric (knots)
tide_height_ft numeric
tide_direction text (rising/falling/slack-high/slack-low)
water_temp_f numeric
moon_phase numeric (0–1)
is_solunar_major boolean
is_solunar_minor boolean
UNIQUE (location_id, forecast_time)
daily_summaries
location_id uuid REFERENCES locations
forecast_date date
avg_score integer
max_score integer
best_window_start integer (hour 0–23)
best_window_end integer (hour 0–23)
moon_phase numeric
moon_name text
tide_highs_count integer
ai_narrative text (Claude-generated)
UNIQUE (location_id, forecast_date)
Scoring Algorithm
The scoring pipeline runs entirely in the Calculate Scores n8n Code node. Key design decisions:
Frontend Architecture
The frontend is a single static HTML file — no build step, no framework, no dependencies beyond Supabase JS and Google Fonts. The entire app is one file: /opt/rensho/www/saltcast/index.html.
Data flow
On load, the frontend resolves the selected location's UUID from Supabase, then fetches 7 days of predictions. If Supabase is unavailable, it falls back to locally-generated mock data so the UI is never blank.
populateLocationDropdown() // load locations from Supabase
resolveDefaultLocation() // get UUID for St. George Island
fetchData() // load 7-day predictions
render() // draw heatmaps, gauge, conditions
// On day card click
openPanel() // show slideout
renderHourly() // draw 24-hour fish timeline
fetchNarrative() // read ai_narrative from daily_summaries
Deploy pattern
scp ~/Downloads/deploy.tar.gz root@5.161.91.134:/tmp/
ssh root@5.161.91.134 'cd /opt/rensho/www && tar xzf /tmp/deploy.tar.gz && rm /tmp/deploy.tar.gz'
# tar structure
saltcast/
saltcast/index.html
saltcast/images/saltcast-logo.svg
saltcast/images/saltcast-favicon.svg
saltcast/images/rensho-logo.png
Credentials
| Service | Credential | n8n ID |
|---|---|---|
| Anthropic | Anthropic account | Lguuwcjilf20kdRK |
| Resend SMTP | SMTP account | dRfO8HPussxphvHM |
Adding a Location
Insert a row into saltcast.locations with the location's coordinates, NOAA station ID, and timezone. The pipeline will automatically pick it up on the next run.
VALUES ('Your Location, ST', 00.0000, -00.0000, '0000000', 'America/Chicago', true);
Find NOAA station IDs at tidesandcurrents.noaa.gov — search for the nearest tide gauge to your location.