ADR 001: API Middleware Architecture and Open-Meteo Tide Prediction Proxy¶
- Status: Accepted. Amended 2026-10-01: the Open-Meteo provider and caching stand, but the watch no longer calls
GET /api/v1/environment/tides. That endpoint is removed. Tides are returned with each SLTB segment upload for the watch's current position, so they are a paired-watch feature (seetrack-sync.md§7). - Date: 2026-08-06
- Deciders: SailEver Architecture & Engineering Team
- Technical Context:
sailever-railsBackend API, Garmin CIQ Watch App (sailever-watchapp)
1. Context & Problem Statement¶
SailEver requires high/low tide predictions for a given latitude and longitude to present on the Garmin watch face (EnvironmentView.mc) and web dashboard.
Key constraints:
1. Pure Ruby lacks established harmonic tide prediction libraries (equivalent to Python's pytides/utide or C's xtide).
2. Smartwatches have constrained battery, CPU, and storage, making full embedded global harmonic constants datasets impractical.
3. API endpoints serving smartwatches must maintain minimal payload size, low latency, and low power consumption.
2. Considered Options¶
Option A: Pure On-Device Moon Transit + LTI Heuristic¶
- Pros: 100% offline, zero server cost.
- Cons: Requires regional Luni-Tidal Interval (LTI) tables or user configuration; error can exceed 1–2 hours in complex coastal areas.
Option B: Rails Server Proxy to Open-Meteo Marine API with Two-Tier Caching (Chosen)¶
- Pros: High spatial accuracy using global marine hydrodynamic models (
sea_level_height_msl), zero API key management (free/open tier), centralized caching. - Cons: Requires watch-to-phone network connectivity for live updates.
Option C: Server-Side XTide Binary Wrapper¶
- Pros: High local accuracy, offline on server.
- Cons: Requires system-level binary dependencies (
xtide,xtide-data) in deployment containers.
3. Decision Drivers & Middleware Choices¶
3.1 ActionController::API Base Controller Architecture¶
To optimize throughput and reduce memory overhead for API requests from smartwatches and mobile clients, all API controllers (Api::V1::*) will inherit from Api::V1::BaseController < ActionController::API rather than ApplicationController < ActionController::Base.
Middleware Stripped for API Scope:
- Session storage & cookies (Rack::Session, ActionDispatch::Cookies)
- Flash messages (ActionDispatch::Flash)
- CSRF form tokens (RequestForgeryProtection)
- HTML template layout rendering
3.2 Two-Tier Caching Strategy¶
- Application Cache (
solid_cache): - Quantizes latitude and longitude to a 0.1° grid (~11.1 km) to maximize cache hits for nearby vessels.
- Cache key format:
"tides:#{grid_lat}:#{grid_lon}:#{date}"with 6-hour TTL (expires_in: 6.hours). - Stored directly in PostgreSQL via
solid_cache, eliminating single-node Redis dependencies. - HTTP Conditional GETs (
ETag&304 Not Modified): - Controller calls
fresh_when(etag: cache_key)and setsCache-Control: public, max-age=21600. - Connected clients sending matching
If-None-Matchheaders receive an instant304 Not Modifiedwithout re-downloading JSON payloads.
3.3 Extrema Peak & Trough Interpolation¶
Open-Meteo provides hourly sea_level_height_msl points. To report precise High (Hi) and Low (Lo) tide times to the watch display, the backend applies a 3-point quadratic interpolation algorithm around local maxima and minima:
$$\Delta t = \frac{h_{i-1} - h_{i+1}}{2(h_{i-1} - 2h_i + h_{i+1})}$$
This estimates peak/trough timestamps to minute-level accuracy.
4. Consequences¶
- Positive: High accuracy tide times, sub-5ms cache hits, minimal server bandwidth, compliance with HTTP caching standards.
- Negative: Watch depends on server connectivity for updated tide predictions (mitigated by retaining the last fetched tide schedule on watch storage).