KLIMAT-P
Seasonal Weather Probability Platform for Poland
Solo-designed and built a live, bilingual, Bayesian weather-probability platform for Poland, from market positioning through UX, visual system, data architecture, and deployment. Public beta, 8 regions, real government data sources, weekly self-verification against ground truth.
- Role
- End-to-end positioning, UX research and IA, visual design, frontend/backend build, data pipeline design, quality assurance, deployment.
Video presentation and logo design: Hanna Adamczyk, Senior Creative Designer - Timeline
- Solo build, iterative beta and actively evolving based on real usage
- Stack
- Next.js
- Supabase
- Claude API
- IMGW-PIB
- NOAA CPC
- Open-Meteo
- Resend
- Vercel
The problem
Every consumer weather product on the market is optimized for the same 48-hour window, because that's what's technically easiest to be confident about, and what most people check daily.
But the people who actually lose money to weather don't operate on a 48-hour horizon. A farmer decides what to plant based on the season ahead. A logistics manager schedules routes weeks out. An energy operator forecasts wind and solar output for the next quarter. An event organizer books a venue months in advance.
For all of them, the honest answer to "what will the weather do" isn't a number, it's a probability, over a much longer window than any consumer app addresses. Nobody was building for that gap, and the ones that came close hid their uncertainty behind false precision rather than showing it.
Who this is for
Rather than design for "everyone who checks the weather," I scoped the product around people whose decisions carry real financial weight and whose planning horizon genuinely is seasonal, not daily.
This shaped almost every downstream decision, from what indicators to build first to how confidence is communicated.
Farmer
Real questionWill there be a late frost after I plant?
Is drought likely this season?
How KLIMAT-P answersLate frost risk and drought risk indicators, tied to the actual regional 30-year baseline, not a national average.
Logistics planner
Real questionWhich weeks are likely to disrupt delivery schedules?
How KLIMAT-P answersPrecipitation deficit + heat wave window, framed as planning risk rather than daily conditions.
Energy operator
Real questionHow should I expect wind/solar output to trend this quarter?
How KLIMAT-P answersTeleconnection indices (NAO/AO/ENSO) surfaced directly, since these are the actual signals operators already use informally.
Event organizer
Real questionShould I book this outdoor date months out?
How KLIMAT-P answersSeasonal probability framing instead of an unreliable 90-day forecast pretending to be precise.
Purpose, mission, vision
- Purpose
Give operational planners an honest, checkable answer to “how likely is X this season?”, replacing false 48-hour precision with calibrated probability people can actually act on.
- Mission
Build the seasonal forecasting layer that Poland's public weather data makes possible but no consumer product currently offers, grounded entirely in real, open, independently verifiable sources, never synthetic data.
- Vision
A future where seasonal risk, drought, frost, heat, flood, is something any farmer or planner can check in thirty seconds, with full transparency into how the number was produced and a public record of how often it's actually been right.
Process and approach
This was a solo build, so process had to stay lightweight enough to move fast, but I still worked in deliberate phases rather than jumping straight to UI, mostly because the biggest risk to a data-trust product isn't visual polish, it's shipping something that quietly lies to people.
- 01
Positioning and data audit. Before any UI, I mapped what real, freely-available Polish and international data sources actually existed (IMGW-PIB, NOAA CPC) and what they could and couldn't support, this ruled out day-ahead forecasting from the outset and defined the seasonal-probability niche.
- 02
Information architecture. I split the product into four distinct surfaces with different trust jobs to do: a marketing site (persuade), a dashboard (daily instrument), a sources page (prove the inputs are real), and a verification page (prove the outputs are accurate).
- 03
Visual system. I designed a shared typographic and colour system, then deliberately forked it into two registers - dark/dense for the working dashboard, light/calm for the pitch - rather than forcing one aesthetic to serve two different jobs.
- 04
The build. I built the data pipeline (station sync → climatological comparison → Bayesian model → verification) alongside the UI, since the two are inseparable in a product whose entire value proposition is data integrity.
- 05
Verify and iterate. I shipped to beta, then used real usage to find and fix integrity bugs (see below), added bilingual support and a day-ahead layer once the core seasonal model proved stable, and restructured mobile navigation as the feature set grew.
Key design decisions
1. A dual visual identity, on purpose
The dashboard and the marketing site look deliberately different: dark, dense, amber-accented for the dashboard; light, spacious, marine-blue for the site.
Why? it maps to two different cognitive modes.
The dashboard is checked daily by someone who already trusts the product and wants density and speed. The marketing site has roughly five seconds to earn trust from someone who's never heard of KLIMAT-P and is naturally skeptical of another weather app's claims. Forcing one visual register to do both jobs would have undersold the credibility pitch or over-cluttered the daily-use tool. Both share the same type system, spacing scale, and a single recurring arc-gauge motif, so the split reads as intentional rather than fragmented.
2. Never fabricate: "Pending" as a first-class UI state
This was the single non-negotiable content and interaction rule across the whole build, applied consistently across every indicator card, the teleconnection panel, and historical analog matching: if the model doesn't yet have enough real data for a given region or indicator, the UI says so plainly rather than interpolating a plausible-looking placeholder.
This is a harder design constraint than it sounds - it means every card needs a fully-designed "not yet" state that looks intentional rather than broken, since for a beta covering 8 regions, "pending" is often the majority state, not the exception.
{pending ? (
<div>Requires 30-year IMGW-PIB climatological baseline -
no estimate yet for this indicator in this region.</div>
) : (
<div>{d.p}%</div> // only ever a real, computed value
)}The payoff: when KLIMAT-P does show a number, users can trust it's real because they've already seen the product admit when it doesn't know something.
3. Bilingual and accessible as defaults, not features
English and Polish are both first-class from launch, including AI-generated chat and summary responses via a single shared language preference that persists across the dashboard, marketing site, sources page, and verification page.
Scroll animations respect prefers-reduced-motion by default (content is fully visible unless motion is explicitly permitted by the user's own OS setting), touch targets meet the 44px minimum, and every icon-only control has a real accessible name. None of this was scoped as a "nice to have later" for a product whose entire pitch is trustworthiness, shipping an inaccessible or English-only build would have undercut the message before anyone read a single probability.
4. Mobile navigation as an evolving information-architecture problem
Early mobile navigation used a simple inline tab row (Data / Chat).
As the feature set grew, a Verification page, a language toggle, a way back to the marketing site, that row ran out of room and started competing with content for space.
I consolidated secondary navigation into a single menu control, keeping only the most time-critical, glanceable elements (region, forecast horizon, live sync status) permanently visible outside it. This is a fairly ordinary IA pattern, but the decision of what stays outside the menu versus what moves inside it was deliberate: anything the user checks constantly stays a single tap away; anything navigational moves into the menu.
Problem-solving in practice
Three examples that show the kind of thinking this product demanded, less "write the code," more "notice that the UI is confidently showing something untrue."
Silent region-scoping bugs
Several backend routes were technically functioning but silently defaulting to one region's data regardless of what a user had selected on screen.
Nothing crashed, nothing errored - a user in Gdańsk could be looking at Warsaw's station readings with full visual confidence and no indication anything was wrong. I traced this by deliberately switching regions and checking whether returned station names actually matched, rather than trusting that a 200 response meant correct data. The fix pattern, applied consistently everywhere the same mistake appeared:
// Before: pulls latest rows from the WHOLE table, ignoring region
.from("station_readings").select("*").order("recorded_at")
// After: scoped to the stations that belong to the selected region
.from("station_readings").select("*")
.in("station_id", regionStationIds)
.order("recorded_at", { ascending: false })The design lesson: a dashboard that shows the wrong region's weather with total visual confidence is a worse product than one that shows nothing.
"It returned data" and "it returned true data" are different bars, and only the second one matters for a trust-based product.
A wrong-column data bug hiding as an impossible number
The teleconnection panel briefly showed an ENSO reading of +28, a value that's meteorologically impossible (real ENSO readings sit roughly between −4 and +4).
Rather than just clamp the number to a "safe" range, which would have silently hidden a real problem, I traced it back to the parser reading the wrong column from NOAA's data file: it was reading raw sea-surface temperature (which genuinely does sit around 26–29°C) instead of the temperature anomaly the index actually reports.
The fix was one column index, but finding it required treating an obviously-wrong number as a signal to investigate the data source directly rather than something to cosmetically suppress in the UI.
A deliberate positioning tension: adding a forecast layer without diluting the core message
Users reasonably expected to ask the built-in assistant "what's the temperature tomorrow?"
But KLIMAT-P's whole premise is seasonal probability, explicitly not day-ahead forecasting. Adding real short-term data risked quietly turning the product into "just another forecast app" and undercutting the positioning that makes it different.
The resolution: a separate, clearly-labelled day-ahead layer (sourced from Open-Meteo, since Poland's own open station API doesn't publish forecasts) that the assistant uses only for short-horizon questions, while every seasonal question still routes through the Bayesian model. The UI and the assistant's own language both make the distinction explicit, so the product gains a genuinely useful feature without blurring the line that justifies its existence.
Constraints and tradeoffs
Solo build. Building solo meant every scoping decision had a real opportunity cost - free-tier services (Resend, Supabase, Vercel) were chosen deliberately so infrastructure cost couldn't become a reason to compromise on data honesty later.
Deliberate scope cuts. A day-ahead forecast layer, and full drought/flood modelling, both required data sources beyond what a beta could ingest cleanly, both were explicitly deferred rather than shipped half-working, with the UI stating that plainly instead of hiding the gap.
Uneven data density. Some regions have far fewer IMGW stations than others (as few as two), which limits statistical confidence unevenly across Poland, the confidence rating shown per indicator reflects this honestly rather than presenting every region as equally certain.
The outcome
KLIMAT-P is live in public beta at klimat-p.website, with a separate dashboard subdomain, real IMGW-PIB and NOAA data flowing daily across all eight Polish regions, weekly Brier-score verification against real outcomes, a bilingual conversational layer that distinguishes seasonal probability from day-ahead forecast, and a feedback loop built directly into both surfaces, designed, built, and shipped end-to-end by one person, from market positioning through deployment.
Reflection
The hardest design problems on this project were rarely visual.
They were about deciding what the product is allowed to claim, and building the discipline, in the UI, in the content, in the data pipeline, to never claim more than the data actually supports. That's the throughline across the pending-state pattern, the region-scoping fixes, the ENSO investigation, and the forecast-positioning decision: treating honesty as a design constraint with the same weight as usability or visual craft, not as an afterthought to be handled by a disclaimer.