CINDER LOGIC
~/cinderlogic — work / miomio

// case_study :: miomio_api

miomio-api

Hawaii-specific weather intelligence, built on a Go + PostGIS spine.

api.staging.miomio.devREADY

Overview

miomio-apiis a spatial JSON weather API for the Hawaiian Islands — point-in-time and 25-step hourly forecasts plus real-time observations from ground weather stations, queryable by any lat/lon. Built and operated by Cinder Logic as internal R&D: a working production system we use to sharpen the same platform-engineering practice we ship for clients.

Problem

Generic global weather APIs collapse Hawaii's microclimates and trade-wind regimes into a single coarse cell. NOAA publishes a higher-fidelity ~3 km nested grid (NAM Nest-HI), but it ships as raw GRIB2 files that consumer apps can't use directly.

Ground-station observations from the Hawaii Mesonet live in a separate format again. The opportunity: a small, fast, spatial JSON layer over the primary sources — one endpoint that knows both the forecast grid and the live stations, and serves either for any coordinate in the islands.

Architecture


   ┌─────────────────────────┐        ┌─────────────────────────┐
   │   NOAA NAM Nest-HI      │        │   Mesonet stations      │
   │   GRIB2 (3 km, every 6h)│        │   (live observations)   │
   └────────────┬────────────┘        └────────────┬────────────┘
                │ byte-range fetch                  │ pull
                ▼                                   ▼
   ┌─────────────────────────┐        ┌─────────────────────────┐
   │   noaa-worker           │        │   mesonet-worker        │
   │   cron  0 */2 * * *     │        │   long-running daemon   │
   │   wgrib2 + errgroup     │        │   filter inactive sites │
   └────────────┬────────────┘        └────────────┬────────────┘
                │                                   │
                └───────────────┬───────────────────┘
                                ▼
                  ┌──────────────────────────┐
                  │   PostgreSQL + PostGIS   │   ◀── Redis (cache)
                  │   GIST index, ST_Distance│
                  └──────────────┬───────────┘
                                 ▼
                  ┌──────────────────────────┐
                  │   miomio-api (Echo)      │
                  │   stateless · /v1/health │
                  └──────────────┬───────────┘
                                 ▼
                       api.staging.miomio.dev

One Go binary, four subcommands — api, worker, mesonet, and migrate. Same image deployed three different ways: a stateless API, a cron-driven NOAA ingester, and a long-running Mesonet daemon. One build to test, one binary to roll back, one schema the binary owns end-to-end.

Engineering Highlights

$ 01GRIB2 byte-range fetch

Two-request strategy: pull the .idx, parse byte offsets for the variables we want, then range-fetch only those bytes from NOAA's CDN. Custom UA, exponential backoff on 403/429.

$ 02Bilinear interpolation

When a query lands more than 1.5 km from the nearest 3 km NAM grid node, fetch the four surrounding nodes and interpolate every scalar field to the exact coordinate. Falls back to nearest-neighbor at coastal edges.

$ 03PostGIS spatial lookups

GEOGRAPHY column with a GIST index; ST_Distance ordering for nearest-node selection. Time-step selection is arithmetic on reference_time — no full-table sorts.# postgis earns its keep

$ 04Bounded concurrency

Worker fans out 4 GRIB2 fetches via errgroup + a semaphore channel. First error cancels the group and stops in-flight work cleanly.

$ 05Single-binary deploys

One Go binary with subcommands (api / worker / mesonet / migrate). Migrations embedded via embed.FS, so a deploy is one image — and Postgres can't drift from the schema the binary expects.

$ 06Trade-wind detection

Wind direction is derived from U/V components, then bucketed into a 16-point compass and a boolean trade-wind flag (30°–120° at ≥2 m/s). Useful primitives for downstream consumers.

$ 07Derived enrichments

Raw model output gets turned into actionable labels at query time: precip_summary buckets accumulation into Dry / Light / Moderate / Heavy, and CAPE (J/kg) is surfaced from NAM so consumers can flag convective potential — the afternoon thunderstorm risk that matters in Hawaii but doesn't fit on a temperature dial.# raw numbers → useful answers

API Surface

Point-in-time forecast — closest valid_time to now

$ curl "https://api.staging.miomio.dev/v1/forecast?lat=21.3069&lon=-157.8583"

{
  "reference_time": "2026-05-02T00:00:00Z",
  "valid_time":     "2026-05-02T04:00:00Z",
  "grid_lat":       21.3161,
  "grid_lon":       -157.864,
  "dist_km":        1.2,
  "interpolated":   false,
  "temp_c":         24.2,
  "humidity_pct":   77.1,
  "wind_u":         -4.95,
  "wind_v":         -3.57,
  "wind_speed_ms":  6.1,
  "wind_dir_deg":   54.2,
  "wind_cardinal":  "NE",
  "trade_winds":    true,
  "precip_mm":      0,
  "precip_summary": { "label": "Dry", "intensity": "dry" },
  "cape_jkg":       679,
  "source":         "NOAA NAM Nest-HI"
}

25-step hourly time series — f00 through f24

$ curl "https://api.staging.miomio.dev/v1/forecast/hourly?lat=21.3069&lon=-157.8583"

[
  {
    "valid_time":     "2026-05-02T05:00:00Z",
    "temp_c":         23.9,
    "wind_speed_ms":  5.8,
    "wind_cardinal":  "NE",
    "trade_winds":    true,
    "precip_summary": { "label": "Dry", "intensity": "dry" },
    "cape_jkg":       612,
    ...
  },
  ...
  // 25 entries, f00 through f24
]

Stack

00 / 02

Runtime

  • +Go 1.25 · Echo HTTP
  • +zap structured logging
  • +Distroless image (~10 MB)
01 / 02

Data

  • +PostgreSQL 16 + PostGIS
  • +Redis 8 (cache)
  • +embed.FS migrations
02 / 02

Platform

  • +Railway · us-west2
  • +Cron + daemon workers
  • +Healthcheck /v1/health

Status

Staging

api.staging.miomio.dev

READY

Production

api.miomio.dev

STANDBY

Contact

// start_a_project

Ship a platform you won't have to rebuild.

A 30-minute call to walk your architecture, scope a phase, and decide whether we're a fit.

or — hello@cinderlogic.com