# Non-Functional Requirements (NFRs) These requirements are binding for implementation milestones. Each has an ID for traceability in issues and PRs (e.g. "Fixes NFR-P2"). ## 1. Performance | ID | Requirement | Target | Measured how | |----|-------------|--------|--------------| | NFR-P1 | First contentful paint of map view on mid-range mobile (Moto G-class, 4G) | ≤ 2.5 s | Lighthouse CI, throttled profile | | NFR-P2 | Map interactive (pan/zoom responsive) | ≤ 4 s after load | Lighthouse TTI | | NFR-P3 | API p95 latency, cached forecast/score (`GET /v1/spots/{id}/forecast`, `/score`) | ≤ 150 ms | server histogram metrics | | NFR-P4 | API p95 latency, cache-miss forecast (includes upstream fetch) | ≤ 2 s | server histogram metrics | | NFR-P5 | Viewport spot search (`GET /v1/spots?bbox=…`) p95 with 50k spots in DB | ≤ 100 ms | pgbench-style fixture test | | NFR-P6 | Initial JS bundle (gzipped, excluding map style/tiles) | ≤ 350 KB | bundle-size CI check | | NFR-P7 | Spot markers rendered without frame drops below 30 fps while panning | up to 5,000 in viewport (clustered) | manual + automated perf test | ## 2. Availability & capacity - **NFR-A1** — Reference deployment targets **99.5% monthly availability** (community project, single-region; no SLA, but the target drives design: stateless API, health checks, automatic restart). - **NFR-A2** — Design capacity for v1: **100 concurrent users / ~50 req/s** sustained on a 2 vCPU / 4 GB node, with headroom from the grid-snapped cache (ADR-006). This is an explicit assumption; exceeding it triggers the Redis-cache option noted in ADR-003. - **NFR-A3** — Graceful degradation order when upstream weather is unavailable: serve stale cache with `"stale": true` and `Warning` header (up to 6 h old) → show spots without scores and a banner; **never** show a score computed from missing wind data. ## 3. Accessibility (WCAG 2.1 AA) - **NFR-X1** — All non-map UI (spot detail, forecast panel, forms) meets WCAG 2.1 AA: contrast ≥ 4.5:1, full keyboard operability, visible focus, labeled inputs. - **NFR-X2** — The map itself is exempt from full keyboard navigation (accepted WebGL-map limitation), but every map-discoverable datum must be reachable without the map: the spot **list view** (`docs/03-wireframes.md`, list toggle) is the accessible equivalent and is a launch requirement, not an enhancement. - **NFR-X3** — Flyability is never conveyed by color alone: score badges always include the numeric value and label ("72 · Good"); wind arrows include text bearing ("NW, 315°"). - **NFR-X4** — All units respect user preference (NFR-I1) and are announced with full words to screen readers (`aria-label="wind 12 knots gusting 18"`). ## 4. Privacy & data protection - **NFR-D1** — **No account required** to view the map, spots, forecasts, or scores. Accounts exist only for contributions (spots, reviews, hazard reports, session logs). - **NFR-D2** — Geolocation is requested only on explicit user action ("locate me" button), never on page load; denial degrades to map-center search. - **NFR-D3** — User location is used client-side only; the API receives bounding boxes and spot IDs, not raw user GPS traces. No location history is stored server-side except opt-in session logs explicitly created by the user. - **NFR-D4** — No third-party analytics or trackers. Optional self-hosted, cookie-less metrics (e.g. Plausible CE) may be enabled by deployers; the reference deployment documents whatever it runs. - **NFR-D5** — GDPR posture: data minimization as above; account deletion endpoint removes PII and reattributes public contributions to "deleted user" (content remains under its open license, authorship pseudonymized) — mirrors OSM/Wikipedia practice and is stated in the Terms. - **NFR-D6** — Logs must not contain precise user coordinates or auth tokens; IPs in access logs are truncated to /24 (IPv4) / /48 (IPv6) before storage. ## 5. Security - **NFR-S1** — No secrets in the client. The default stack needs zero API keys (ADR-001/002); any deployer-added keys live server-side only. - **NFR-S2** — Rate limiting on all endpoints: anonymous 60 req/min/IP for reads, 10 req/min for writes; authenticated limits configurable. 429 with `Retry-After`. - **NFR-S3** — All write input validated against the OpenAPI schemas server-side; user-supplied text rendered with strict escaping (no raw HTML in reviews/descriptions; a safe Markdown subset is the only rich text allowed). - **NFR-S4** — Auth (Milestone for community features): email+password with argon2id, or OAuth via deployer-configured providers; sessions are httpOnly, SameSite=Lax cookies; CSRF tokens on state-changing form posts. - **NFR-S5** — Dependencies scanned in CI (e.g. `npm audit` / `cargo audit` / `pip-audit` per chosen stack in `docs/08`); high-severity findings block release. - **NFR-S6** — Spot edits and hazard reports are moderated content: new contributors' first N submissions enter a review queue (anti-vandalism, mirrors the trust model in `docs/02-feature-spec.md`). ## 6. Internationalization & units - **NFR-I1** — Unit preferences are first-class and persisted locally: wind in **kn / m/s / km/h / mph / Beaufort**; temperature °C/°F; distance km/mi. Default inferred from locale, always overridable. The API always uses SI (m/s, °C, mm) — conversion is a client concern. - **NFR-I2** — All UI strings externalized from day one (message catalog); English is the source language; community translations via standard PO/JSON workflow. - **NFR-I3** — Dates/times shown in the **spot's local timezone** (stored per spot, `docs/06`), with the user's offset shown when it differs — a forecast for "Saturday 3 pm" must mean 3 pm *at the beach*. ## 7. Offline & degraded modes - **NFR-O1** — The app is a PWA: app shell and last-viewed spot details/forecasts cached via service worker, so a flyer at a beach with poor signal can re-open what they already loaded. Cached forecasts display their fetch age prominently ("as of 09:40"). - **NFR-O2** — No fabricated freshness: anything served from cache is visibly stamped; scores older than 3 h render in a "stale" visual state. ## 8. Browser & device support - **NFR-B1** — Evergreen Chrome/Edge/Firefox/Safari, latest two major versions; iOS Safari ≥ 16; Android Chrome ≥ same era. WebGL required for the default map (ADR-001); the no-WebGL fallback is the list view + static spot pages. - **NFR-B2** — Fully usable at 360 px width; map controls within thumb reach (bottom-anchored on mobile per `docs/03-wireframes.md`). ## 9. Operability (reference deployment) - **NFR-M1** — Structured JSON logs; request ID propagation; log levels configurable. - **NFR-M2** — `/healthz` (liveness) and `/readyz` (DB + cache reachability) endpoints, excluded from rate limits and logs. - **NFR-M3** — Prometheus-format metrics: request latency histograms per route, upstream weather fetch counts/failures, cache hit ratio, scoring computations/s. - **NFR-M4** — Nightly logical backups of Postgres (`pg_dump`), 14-day retention, restore procedure documented and tested in the deployment guide milestone. - **NFR-M5** — Single-command local bring-up (`docker compose up`) with seeded demo spots is a hard requirement for contributor onboarding (see `CONTRIBUTING.md`). ## 10. Traceability Every NFR above must map to at least one CI check, test, or runbook item by the end of the implementation milestones. The roadmap (`docs/13-roadmap.md`) assigns each group to a milestone.