A Fishing Log That Knows the Tide
Fishing Tracker Pro doesn't just record what you caught — every trip is stamped with the moon phase, tide state, and weather at the moment you logged it.
The first version of Fishing Tracker Pro had a form with six fields: date, location, species, count, bait, notes. It worked. It was also useless for the one question every angler actually wants answered: what conditions produced this catch? Six months later the same form has three more fields you never type into — moon phase, tide state, and live weather — because the app fills them in for you the moment you hit submit.
The context is captured once, at log time — not recomputed later.
Why context, not just a catch count
Mauritius has real tidal and lunar variation, and anglers already reason about it informally — "the couler was better on the full moon," "nothing bites at low water on the west coast." That knowledge lives in people's heads and gets lost between trips. The point of Fishing Tracker Pro isn't to be a spreadsheet with nicer buttons; it's to make that tacit knowledge queryable. A log of 40 trips with environmental context attached is a dataset. A log of 40 trips without it is a diary.
That distinction shaped the schema from day one. The fishing_logs table doesn't just store species and count — it stores moon_phase, tide_state, wind_speed, wave_height, and sea_temperature as columns, not as a JSON blob to backfill later:
CREATE TABLE fishing_logs (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
date DATE NOT NULL,
location VARCHAR(255),
fishing_type VARCHAR(50),
caught_fish BOOLEAN,
fish_count INTEGER,
moon_phase VARCHAR(50),
tide_state VARCHAR(50),
wind_speed NUMERIC,
wave_height NUMERIC,
sea_temperature NUMERIC,
created_at TIMESTAMP DEFAULT NOW()
);Context is captured, not looked up later
The important design decision is when that enrichment happens: at submit time, not at read time. If we recomputed tide state whenever someone viewed an old trip, every historical log would silently drift as tide predictions got revised or as our own calculation logic changed. Instead, the values are frozen into the row the moment the trip is logged. A trip from three months ago shows exactly what the tide was doing that day, forever — even if the underlying tide model improves next year.
That single decision is why the "Plan Trip" recommendation engine (a later post in this series) can trust historical data as ground truth: it is training on what conditions actually were, not on a live recalculation that changes underneath it.
Three sources, one write
Getting moon phase, tide, and weather into the same request means calling three independent things that fail independently — a lunar calculation that never fails, a tide API that sometimes rate-limits, and a weather API (plus a government site scrape, covered later in this series) that occasionally times out. The enrichment step uses Promise.allSettled rather than Promise.all, so a slow weather call doesn't block the trip from saving with whatever tide and moon data did resolve. A trip with two out of three fields filled in is still useful; a trip that fails to save because one upstream service hiccuped is not.
What this unlocks later
Once every trip carries its own weather and tide snapshot, several other features become straightforward instead of speculative: a "Plan Trip" tab that recommends a time window based on solunar theory and forecast data, a community "Predictions" tab that compares today's conditions against the conditions of your most successful past trips, and a leaderboard that can correlate catch rate with tide state across every angler using the app, not just one person's memory.
None of that is possible if the log is just a catch count. The environmental context isn't a feature bolted onto a fishing log — it's the reason the log is worth keeping.
Series: Fishing Tracker Pro. Next: how three unrelated data sources — moon, tide, and weather — get stitched into that one enrichment step.