Case study · 01
Status: OngoingFishing Forecast
A fishing planner for Koszalin and the surrounding area, running on my own server. For a chosen spot and species it scores conditions from 0 to 100, explains every factor, finds the best time windows of the day and gives a 7-day outlook. Sea spots get an extra mode with waves and a safety label.

01 · Problem
What needed solving
Weather apps show numbers, not an answer. The real question is simpler: is it worth going today, when exactly, and why. And the answer has to be the same on the phone and on the laptop.
02 · Solution
How it works
A Fastify API with PostgreSQL stores accounts, spots and preferences, and is the only part that talks to Open-Meteo, caching each spot's forecast for 15 minutes. A React PWA shows the result of a deterministic, documented algorithm: eight weighted factors, species rules capped at ±12 points, and time windows cut to at most four hours.
03 · Key features
What it does
- Accounts with bcrypt-hashed passwords and httpOnly session cookies; tokens stored as HMACs
- Spots and settings saved per account and synced across devices
- Score from 8 weighted factors, plus waves for sea spots when marine data exists
- Best time windows for today and a 7-day outlook with expandable details
- Installable PWA with an offline UI cache and a read-only mode from a local copy
04 · Tech stack
Built with
- TypeScript
- Fastify
- PostgreSQL
- React
- Vitest
- Docker
- Status
- Ongoing
- Code
- github.com/KacperBlok1/fishing-forecast-koszalin
- Demo
- Not hosted yet · run locally
05 · Challenges
What was tricky
When Open-Meteo is down
Forecasts are cached in PostgreSQL: fresh for 15 minutes, kept as a fallback for 24 hours. If Open-Meteo is down, the server returns the last good response and the app shows how old it is. Missing marine data is reported, never invented.
Time zones
Open-Meteo returns times in the spot's local time without a zone. The app works on those strings instead of parsing them with the browser clock, so dawn lands on the right hour even when the phone is set to another time zone.
Scoring a whole day
Almost every day has one good hour at dawn, so the best hour alone made all days look the same. The daily score blends the best window (65%) with the daytime average (35%).
06 · Screenshots
How it looks
Try it
Run it locally
No hosted demo yet. The source and README are on GitHub, and these commands start it locally from the repo root.
$ cp .env.example .env
# set POSTGRES_PASSWORD and SESSION_SECRET
$ docker compose up -d --build
$ curl http://localhost:8090/api/health