{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/alert-camp/json-schema/alert-camp-campground-response-schema.json",
  "title": "CampgroundResponse",
  "description": "Schema for campground response.",
  "x-generated": "2026-11-01",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/alert-camp-openapi.json#/components/schemas/CampgroundResponse",
  "properties": {
    "id": {
      "type": "string",
      "title": "Id"
    },
    "park_id": {
      "type": "string",
      "title": "Park Id"
    },
    "name": {
      "type": "string",
      "title": "Name"
    },
    "name_en": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Name En"
    },
    "description": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Description"
    },
    "url": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Url"
    },
    "is_active": {
      "type": "boolean",
      "title": "Is Active"
    },
    "latitude": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "title": "Latitude"
    },
    "longitude": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "title": "Longitude"
    },
    "max_watched_sites": {
      "anyOf": [
        {
          "type": "integer"
        },
        {
          "type": "null"
        }
      ],
      "title": "Max Watched Sites",
      "description": "Max sites ONE alert may watch in this campground, narrowing the tier's sites-per-alert limit. null = tier limit only."
    },
    "max_sites_per_night": {
      "anyOf": [
        {
          "type": "integer"
        },
        {
          "type": "null"
        }
      ],
      "title": "Max Sites Per Night",
      "description": "Max sites one ROUTE NIGHT may offer here. null = no limit."
    },
    "has_roofed_sites": {
      "type": "boolean",
      "title": "Has Roofed Sites",
      "description": "Whether any zone in this campground contains roofed accommodation sites.",
      "default": false
    },
    "has_canoe_sites": {
      "type": "boolean",
      "title": "Has Canoe Sites",
      "description": "Whether any zone in this campground contains canoe-camping sites.",
      "default": false
    },
    "has_backcountry_sites": {
      "type": "boolean",
      "title": "Has Backcountry Sites",
      "description": "Whether any zone in this campground contains backcountry sites.",
      "default": false
    },
    "has_site_map": {
      "type": "boolean",
      "title": "Has Site Map",
      "description": "Whether THIS campground has a photo-overlay site map, so the picker can route to it per campground instead of per platform. Maps are sparse within a provider — Recreation.gov has exactly one (Yosemite's permit trailheads, from the NPS wilderness map) against 92,467 mapless sites — so `Platform.has_site_map` is the wrong unit and a per-park override would only restate what the map rows already say. Defaults False, so a client that ignores it behaves exactly as before.",
      "default": false
    },
    "has_day_use_sites": {
      "type": "boolean",
      "title": "Has Day Use Sites",
      "description": "Whether any zone in this campground contains day-use sites (Parks Canada parking time slots, Banff shuttle departures). Defaults False, so builds that predate the activity are unaffected — the v1 campgrounds route hides day-use-only campgrounds from them entirely.",
      "default": false
    },
    "seasons": {
      "items": {
        "$ref": "#/$defs/SeasonSpan"
      },
      "type": "array",
      "title": "Seasons",
      "description": "Distinct operating seasons among this campground's active sites — usually one, occasionally several (a campground can open its loops on different dates). Empty when no site publishes a season. Data only: the server does not act on it, and a client that ignores it behaves exactly as before."
    },
    "has_unknown_season": {
      "type": "boolean",
      "title": "Has Unknown Season",
      "description": "Whether ANY active site here has no published season. Most providers publish none at all (Aspira, UseDirect, and every Recreation.gov site not yet swept), and unknown must never read as closed. Defaults TRUE so a client treats the campground as always open unless told otherwise — the safe direction, since the only use for this is warning that dates can never match.",
      "default": true
    },
    "notices": {
      "items": {
        "$ref": "#/$defs/ProviderNotice"
      },
      "type": "array",
      "title": "Notices",
      "description": "The provider's own notices on this campground (closures, stay rules), as last seen by the scanner. Empty when none — and for every provider that publishes none. Data only."
    },
    "provider_closed": {
      "type": "boolean",
      "title": "Provider Closed",
      "description": "The provider says this campground is closed (a closure notice AND nothing bookable): its sites read as fully booked, but no cancellation can open them. Defaults False — unknown is open.",
      "default": false
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "title": "Created At"
    },
    "updated_at": {
      "type": "string",
      "format": "date-time",
      "title": "Updated At"
    }
  },
  "type": "object",
  "required": [
    "id",
    "park_id",
    "name",
    "name_en",
    "description",
    "url",
    "is_active",
    "created_at",
    "updated_at"
  ],
  "$defs": {
    "ProviderNotice": {
      "properties": {
        "title": {
          "type": "string",
          "title": "Title"
        },
        "text": {
          "type": "string",
          "title": "Text"
        },
        "closure": {
          "type": "boolean",
          "title": "Closure",
          "description": "The notice closes the accommodation itself (not a road, trail or restaurant).",
          "default": false
        }
      },
      "type": "object",
      "required": [
        "title",
        "text"
      ],
      "title": "ProviderNotice",
      "description": "A banner the provider shows on this campground's booking page."
    },
    "SeasonSpan": {
      "properties": {
        "start": {
          "type": "string",
          "title": "Start",
          "description": "Season start as \"MM-DD\"."
        },
        "end": {
          "type": "string",
          "title": "End",
          "description": "Season end as \"MM-DD\"."
        }
      },
      "type": "object",
      "required": [
        "start",
        "end"
      ],
      "title": "SeasonSpan",
      "description": "One recurring operating season, WITHOUT a year.\n\n\"05-01\" / \"10-31\".  A season recurs, so a year would be wrong the moment\nit turned — see ``app/utils/season.py``.  ``start`` > ``end`` is legal and\nmeans the season wraps the new year (Nov 2 - Apr 30); 373 seeded sites are\nshaped that way, so a client comparing ``start <= d <= end`` would call\nevery one of them closed all year."
    }
  }
}