{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/smarterweather-com/json-schema/smarterweather-com-route-weather-response-schema.json",
  "title": "RouteWeatherResponse",
  "description": "Values are in the selected `units`: imperial is °F, mph, miles and\ninches; metric is °C, kph, kilometres and millimetres.\nProbabilities are percent and directions are degrees clockwise\nfrom north.\n",
  "x-generated": "2026-10-30",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/smarterweather-com-openapi.yaml#/components/schemas/RouteWeatherResponse",
  "type": "object",
  "required": [
    "units",
    "path",
    "best",
    "departures",
    "meta"
  ],
  "properties": {
    "units": {
      "type": "string",
      "enum": [
        "imperial",
        "metric"
      ]
    },
    "path": {
      "type": "object",
      "required": [
        "length",
        "vertices",
        "samples",
        "spacing"
      ],
      "properties": {
        "length": {
          "type": "number",
          "description": "Path length (mi or km)."
        },
        "vertices": {
          "type": "integer",
          "description": "Vertex count after decoding."
        },
        "samples": {
          "type": "integer",
          "minimum": 2,
          "maximum": 100
        },
        "spacing": {
          "type": "number",
          "description": "Distance between samples (mi or km)."
        }
      }
    },
    "best": {
      "type": "string",
      "format": "date-time",
      "description": "The `departure` of the best departure (the ranking is in the operation description)."
    },
    "departures": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/RouteDeparture"
      }
    },
    "meta": {
      "type": "object",
      "required": [
        "sources",
        "data_age_seconds"
      ],
      "properties": {
        "sources": {
          "type": "array",
          "description": "Each product used: the forecast products with the newest model run used, `nws_alerts` (alerts, `run` null), and each outlook product with a band in the answer (`run` is its issue time). No outlook product means no band was found at the samples' ETAs, not that there is no risk (see the known gap under Outlooks).",
          "items": {
            "type": "object",
            "required": [
              "product",
              "run"
            ],
            "properties": {
              "product": {
                "type": "string"
              },
              "run": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          }
        },
        "data_age_seconds": {
          "type": [
            "integer",
            "null"
          ],
          "description": "Age of the newest model run used, in seconds. `null` when unknown."
        }
      }
    }
  },
  "$defs": {
    "RouteAlert": {
      "type": "object",
      "additionalProperties": false,
      "description": "One crossing of an alert in effect when this departure travels it.",
      "required": [
        "id",
        "event",
        "severity",
        "headline",
        "onset",
        "onset_local",
        "expiration",
        "expiration_local",
        "enter_distance",
        "exit_distance",
        "enter_time",
        "enter_time_local",
        "exit_time",
        "exit_time_local"
      ],
      "properties": {
        "id": {
          "type": "string",
          "description": "The alert id for `GET /v1/alerts/{id}`."
        },
        "event": {
          "type": "string"
        },
        "severity": {
          "$ref": "#/$defs/RouteAlertSeverity"
        },
        "headline": {
          "type": "string",
          "description": "The alert headline; may be empty."
        },
        "onset": {
          "type": [
            "string",
            "null"
          ],
          "format": "date-time",
          "description": "When the alert takes effect (its effective time when no onset is given). `null` when neither is known."
        },
        "onset_local": {
          "type": [
            "string",
            "null"
          ],
          "format": "date-time",
          "description": "`onset` in the time zone of the covered sample nearest `enter_distance`, with its UTC offset at `onset`. `null` with `onset`."
        },
        "expiration": {
          "type": "string",
          "format": "date-time",
          "description": "The end of the alert's window for this crossing, the time the keep rule used. It is the event's end (the NWS event ending time, else the alert's `ends`); when neither is given, it is the product's expiry."
        },
        "expiration_local": {
          "type": "string",
          "format": "date-time",
          "description": "`expiration` in the time zone of the covered sample nearest `enter_distance`, with its UTC offset at `expiration`."
        },
        "enter_distance": {
          "type": "number",
          "minimum": 0,
          "description": "Distance from the start of the path where the crossing begins (mi or km); at most `exit_distance`."
        },
        "exit_distance": {
          "type": "number",
          "minimum": 0,
          "description": "Distance from the start of the path where the crossing ends (mi or km)."
        },
        "enter_time": {
          "type": "string",
          "format": "date-time",
          "description": "ETA at `enter_distance`."
        },
        "enter_time_local": {
          "type": "string",
          "format": "date-time",
          "description": "`enter_time` in the time zone of the covered sample nearest `enter_distance`, with its UTC offset."
        },
        "exit_time": {
          "type": "string",
          "format": "date-time",
          "description": "ETA at `exit_distance`."
        },
        "exit_time_local": {
          "type": "string",
          "format": "date-time",
          "description": "`exit_time` in the time zone of the covered sample nearest `exit_distance`, with its UTC offset."
        }
      }
    },
    "RouteAlertSeverity": {
      "type": "string",
      "enum": [
        "Extreme",
        "Severe",
        "Moderate",
        "Minor",
        "Unknown"
      ]
    },
    "RouteConvectiveOutlook": {
      "type": "string",
      "enum": [
        "TSTM",
        "MRGL",
        "SLGT",
        "ENH",
        "MDT",
        "HIGH"
      ],
      "description": "Storm Prediction Center categorical convective outlook band, least to most severe: General thunderstorms, Marginal, Slight, Enhanced, Moderate, High."
    },
    "RouteDeparture": {
      "type": "object",
      "required": [
        "departure",
        "departure_local",
        "origin_timezone",
        "arrival",
        "arrival_local",
        "destination_timezone",
        "eta_source",
        "summary",
        "alerts",
        "segments",
        "samples"
      ],
      "properties": {
        "departure": {
          "type": "string",
          "format": "date-time",
          "description": "The departure, in UTC."
        },
        "departure_local": {
          "type": "string",
          "format": "date-time",
          "description": "The departure in `origin_timezone`, with its UTC offset."
        },
        "origin_timezone": {
          "type": "string",
          "description": "The IANA time zone of the first sample inside coverage (the start of the path when it is inside coverage)."
        },
        "arrival": {
          "type": "string",
          "format": "date-time",
          "description": "The end of the trip. With `times`, the last vertex time, so a\nwait at the destination is included; the last sample's `eta` is\nthe first arrival there.\n"
        },
        "arrival_local": {
          "type": "string",
          "format": "date-time",
          "description": "`arrival` in `destination_timezone`, with its UTC offset."
        },
        "destination_timezone": {
          "type": "string",
          "description": "The IANA time zone of the last sample inside coverage (the end of the path when it is inside coverage)."
        },
        "eta_source": {
          "type": "string",
          "enum": [
            "speed",
            "times"
          ]
        },
        "summary": {
          "type": "object",
          "required": [
            "missing_samples",
            "hazard_minutes",
            "worst_hazard",
            "max_wind_gust",
            "max_crosswind_gust",
            "min_visibility",
            "max_precipitation_probability",
            "worst_alert_severity",
            "alert_minutes",
            "severe_alert_minutes",
            "worst_convective_outlook",
            "worst_excessive_rainfall_outlook"
          ],
          "description": "Forecast fields are over the `ok` samples, alert fields over this departure's `alerts`, and outlook fields over the samples inside coverage; a value is `null` when none has it.",
          "properties": {
            "missing_samples": {
              "type": "integer",
              "minimum": 0,
              "description": "Samples inside coverage without a full forecast for this\ndeparture (`partial`, `beyond_horizon` or `unavailable`).\n`best` ranks on it first.\n"
            },
            "hazard_minutes": {
              "type": "integer",
              "minimum": 0
            },
            "worst_hazard": {
              "oneOf": [
                {
                  "$ref": "#/$defs/RouteHazard"
                },
                {
                  "type": "null"
                }
              ]
            },
            "max_wind_gust": {
              "type": [
                "number",
                "null"
              ]
            },
            "max_crosswind_gust": {
              "type": [
                "number",
                "null"
              ]
            },
            "min_visibility": {
              "type": [
                "number",
                "null"
              ]
            },
            "max_precipitation_probability": {
              "type": [
                "number",
                "null"
              ]
            },
            "worst_alert_severity": {
              "oneOf": [
                {
                  "$ref": "#/$defs/RouteAlertSeverity"
                },
                {
                  "type": "null"
                }
              ]
            },
            "alert_minutes": {
              "type": "integer",
              "minimum": 0,
              "description": "Wall-clock minutes inside any alert in `alerts` while it is in effect, overlaps counted once, rounded up (any time inside is at least 1)."
            },
            "severe_alert_minutes": {
              "type": "integer",
              "minimum": 0,
              "description": "The same as `alert_minutes`, over the Severe and Extreme alerts only."
            },
            "worst_convective_outlook": {
              "oneOf": [
                {
                  "$ref": "#/$defs/RouteConvectiveOutlook"
                },
                {
                  "type": "null"
                }
              ]
            },
            "worst_excessive_rainfall_outlook": {
              "oneOf": [
                {
                  "$ref": "#/$defs/RouteExcessiveRainfallOutlook"
                },
                {
                  "type": "null"
                }
              ]
            }
          }
        },
        "alerts": {
          "type": "array",
          "description": "The alerts the path crosses while they are in effect at this departure's ETA, by `enter_distance`, then severity (worst first), then `id`. Empty when none.",
          "items": {
            "$ref": "#/$defs/RouteAlert"
          }
        },
        "segments": {
          "type": "array",
          "description": "Runs of consecutive samples sharing a hazard, by start distance then severity.",
          "items": {
            "type": "object",
            "required": [
              "hazard",
              "from_distance",
              "to_distance",
              "start",
              "start_local",
              "end",
              "end_local"
            ],
            "properties": {
              "hazard": {
                "$ref": "#/$defs/RouteHazard"
              },
              "from_distance": {
                "type": "number"
              },
              "to_distance": {
                "type": "number"
              },
              "start": {
                "type": "string",
                "format": "date-time"
              },
              "start_local": {
                "type": "string",
                "format": "date-time",
                "description": "`start` in the time zone of the segment's first sample, with its UTC offset."
              },
              "end": {
                "type": "string",
                "format": "date-time"
              },
              "end_local": {
                "type": "string",
                "format": "date-time",
                "description": "`end` in the time zone of the segment's last sample, with its UTC offset."
              }
            }
          }
        },
        "samples": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/RouteSample"
          }
        }
      }
    },
    "RouteExcessiveRainfallOutlook": {
      "type": "string",
      "enum": [
        "MRGL",
        "SLGT",
        "MDT",
        "HIGH"
      ],
      "description": "Weather Prediction Center excessive rainfall outlook band, least to most severe: Marginal, Slight, Moderate, High."
    },
    "RouteHazard": {
      "type": "string",
      "enum": [
        "freezing_rain",
        "snow",
        "thunderstorms",
        "low_visibility",
        "high_wind",
        "heavy_rain"
      ]
    },
    "RouteSample": {
      "type": "object",
      "required": [
        "distance",
        "lat",
        "lon",
        "eta",
        "heading",
        "status",
        "hazards"
      ],
      "properties": {
        "distance": {
          "type": "number",
          "description": "Distance from the start of the path (mi or km)."
        },
        "lat": {
          "type": "number"
        },
        "lon": {
          "type": "number"
        },
        "eta": {
          "type": "string",
          "format": "date-time"
        },
        "timezone": {
          "type": "string",
          "description": "The IANA time zone at the sample. Absent outside coverage."
        },
        "eta_local": {
          "type": "string",
          "format": "date-time",
          "description": "`eta` in `timezone`, with its UTC offset. Absent outside coverage."
        },
        "valid_time": {
          "type": "string",
          "format": "date-time",
          "description": "The forecast hour used. Only on `ok` samples."
        },
        "heading": {
          "type": "number",
          "description": "Direction of travel."
        },
        "status": {
          "type": "string",
          "enum": [
            "ok",
            "partial",
            "outside_coverage",
            "beyond_horizon",
            "unavailable"
          ]
        },
        "temperature": {
          "type": "number"
        },
        "apparent_temperature": {
          "type": "number"
        },
        "precipitation_probability": {
          "type": "number"
        },
        "precipitation": {
          "type": "number",
          "description": "Precipitation in the hour."
        },
        "snowfall": {
          "type": "number",
          "description": "Snowfall in the hour."
        },
        "freezing_rain": {
          "type": "number",
          "description": "Freezing rain in the hour."
        },
        "precip_type_rain_prob": {
          "type": "number"
        },
        "precip_type_snow_prob": {
          "type": "number"
        },
        "precip_type_freezing_rain_prob": {
          "type": "number"
        },
        "precip_type_ice_pellets_prob": {
          "type": "number"
        },
        "visibility": {
          "type": "number"
        },
        "visibility_prob_below_1mi": {
          "type": "number"
        },
        "wind_speed": {
          "type": "number"
        },
        "wind_gust": {
          "type": "number"
        },
        "wind_direction": {
          "type": "number",
          "description": "Direction the wind blows from."
        },
        "crosswind": {
          "type": "number",
          "description": "Sustained wind across the direction of travel."
        },
        "crosswind_gust": {
          "type": "number",
          "description": "Gust across the direction of travel."
        },
        "thunderstorm_probability": {
          "type": "number"
        },
        "convective_outlook": {
          "$ref": "#/$defs/RouteConvectiveOutlook"
        },
        "excessive_rainfall_outlook": {
          "$ref": "#/$defs/RouteExcessiveRainfallOutlook"
        },
        "hazards": {
          "type": "array",
          "description": "Hazard codes at this sample, worst first. Empty unless `status` is `ok` or `partial`.",
          "items": {
            "$ref": "#/$defs/RouteHazard"
          }
        },
        "unevaluated_hazards": {
          "type": "array",
          "description": "On `partial` samples, the hazards the forecast at this hour cannot rule out because their inputs are missing, worst first.",
          "items": {
            "$ref": "#/$defs/RouteHazard"
          }
        }
      }
    }
  }
}