{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://shadehop-park.pages.dev/schema/park.schema.json",
  "title": "ShadeHop Park",
  "description": "A children's playground with shade / rain cover, nearby parking and EV chargers. Every factual claim carries provenance (method, verified_at, confidence, source).",
  "type": "object",
  "required": ["id", "name", "location", "osm", "level", "rain_cover", "shade_types", "shade_timetable", "best_shade_windows", "parking", "chargers", "charger_summary", "amenities", "updated_at"],
  "properties": {
    "id": { "type": "string", "pattern": "^[a-z]{2,4}-[nwro][0-9]+$", "description": "Stable ID: region prefix + OSM element type initial + OSM id, e.g. lk-w123456; or region prefix + 'o' + official registry ref for playgrounds known only from government data, e.g. tp-o3041252769501." },
    "name": { "type": "string" },
    "kind": { "enum": ["playground", "park"], "description": "park = a park with no playground mapped in OSM or the official registry (play equipment unconfirmed)." },
    "playground_mapped": { "type": "boolean", "description": "The playground itself is mapped in OSM (false for parks and for playgrounds known only from official data)." },
    "name_source": { "enum": ["osm_name", "park_name", "nearest_street", "official", "none"] },
    "park_name": { "type": ["string", "null"] },
    "region": { "type": "object", "required": ["id", "city", "district"], "properties": { "id": { "type": "string", "description": "Region id, e.g. linkou, taipei." }, "city": { "type": "string" }, "district": { "type": "string" } } },
    "location": { "$ref": "#/$defs/LatLng" },
    "address": { "type": ["string", "null"] },
    "official": {
      "type": ["object", "null"],
      "description": "Matching entry in the city's official registry (government open data). method: name_match = park list without coordinates matched by name (New Taipei); location_match = official playground within distance_m of the OSM playground (Taipei); official_record = playground known only from official data. null when not matched.",
      "required": ["name", "method", "verified_at", "source"],
      "properties": {
        "name": { "type": "string" },
        "address": { "type": ["string", "null"] },
        "management": { "type": ["string", "null"] },
        "phone": { "type": ["string", "null"] },
        "equipment": { "type": ["object", "null"], "additionalProperties": { "type": "integer" }, "description": "Official equipment counts, e.g. {\"鞦韆\": 2}." },
        "registry_points": { "type": "integer", "description": "How many registry rows (points) were merged into this record; equipment is their sum." },
        "method": { "enum": ["name_match", "location_match", "official_record"] },
        "distance_m": { "type": ["integer", "null"] },
        "verified_at": { "type": "string" },
        "source": { "type": "string" }
      }
    },
    "osm": { "oneOf": [{ "$ref": "#/$defs/OsmRef" }, { "type": "null" }], "description": "null for playgrounds known only from official data." },
    "indoor": { "enum": [true, false, "possible"], "description": "true = tagged indoor in OSM (e.g. 親子館); \"possible\" = inside a building outline but not tagged (indoor venue or courtyard, unconfirmed)." },
    "geometry_estimated": { "type": "boolean", "description": "True when the playground is only a point (OSM node or official coordinate); area is approximated by a circle." },
    "area_m2": { "type": "integer" },
    "level": { "enum": ["A", "B", "C", "none"], "description": "A = rain-proof (viaduct/roof), B = partial fixed shade or trees, C = computed building shadow for much of the day, none = mostly exposed." },
    "level_label": { "type": "string" },
    "rain_cover": {
      "allOf": [
        { "$ref": "#/$defs/Provenance" },
        {
          "type": "object",
          "required": ["value", "covered_ratio"],
          "properties": {
            "value": { "enum": [true, false, "partial"] },
            "covered_ratio": { "type": "number", "minimum": 0, "maximum": 1 },
            "note": { "type": "string" }
          }
        }
      ]
    },
    "shade_types": {
      "type": "array",
      "items": {
        "allOf": [
          { "$ref": "#/$defs/Provenance" },
          {
            "type": "object",
            "required": ["type", "level"],
            "properties": {
              "type": { "enum": ["under_viaduct", "roof", "pavilion", "shade_sail", "tree_canopy", "building_shadow", "indoor"] },
              "level": { "enum": ["A", "B", "C"] },
              "rain_proof": { "type": "boolean" },
              "covered_ratio": { "type": "number" },
              "trees": { "type": "integer" },
              "daytime_shaded_share": { "type": "number" },
              "osm": { "type": "array", "items": { "$ref": "#/$defs/OsmRef" } }
            }
          }
        ]
      }
    },
    "daytime_shaded_share": { "type": "number", "minimum": 0, "maximum": 1, "description": "Share of 09:00-16:00 half-hour slots with >= 60% shade, averaged over the year." },
    "shade_timetable": {
      "allOf": [
        { "$ref": "#/$defs/Provenance" },
        {
          "type": "object",
          "required": ["slots", "slot_start", "slot_minutes"],
          "properties": {
            "slot_start": { "const": "06:00" },
            "slot_minutes": { "const": 30 },
            "building_height_known_share": { "type": "number" },
            "buildings_considered": { "type": "integer" },
            "slots": {
              "type": "object",
              "description": "Month (\"1\"..\"12\", sampled on the 15th) -> 24 values for slots starting 06:00, 06:30 ... 17:30. Value = shaded share of the playground (0-1); null = sun below 3 degrees.",
              "patternProperties": { "^(1[0-2]|[1-9])$": { "type": "array", "minItems": 24, "maxItems": 24, "items": { "type": ["number", "null"], "minimum": 0, "maximum": 1 } } },
              "additionalProperties": false
            }
          }
        }
      ]
    },
    "best_shade_windows": {
      "type": "object",
      "description": "Month -> longest continuous run of >= 60% shade, or null.",
      "patternProperties": {
        "^(1[0-2]|[1-9])$": {
          "oneOf": [
            { "type": "null" },
            { "type": "object", "required": ["start", "end", "mean"], "properties": { "start": { "type": "string" }, "end": { "type": "string" }, "start_slot": { "type": "integer" }, "end_slot": { "type": "integer" }, "mean": { "type": "number" } } }
          ]
        }
      }
    },
    "playground": { "type": "object" },
    "amenities": {
      "type": "object",
      "properties": {
        "toilet": { "$ref": "#/$defs/Provenance" },
        "drinking_water": { "$ref": "#/$defs/Provenance" }
      }
    },
    "parking": { "type": "array", "items": { "$ref": "#/$defs/Parking" } },
    "chargers": { "type": "array", "items": { "$ref": "#/$defs/Charger" } },
    "charger_summary": {
      "type": "object",
      "required": ["radius_m", "total", "fast", "slow", "nearest_fast_m", "nearest_slow_m"],
      "properties": {
        "radius_m": { "type": "integer" },
        "total": { "type": "integer" },
        "fast": { "type": "integer" },
        "slow": { "type": "integer" },
        "unknown": { "type": "integer" },
        "nearest_fast_m": { "type": ["integer", "null"] },
        "nearest_slow_m": { "type": ["integer", "null"] },
        "nearest_any_m": { "type": ["integer", "null"] }
      }
    },
    "updated_at": { "type": "string", "format": "date" },
    "sources": { "type": "array" }
  },
  "$defs": {
    "LatLng": {
      "type": "object",
      "required": ["lat", "lng"],
      "properties": { "lat": { "type": "number", "minimum": 21.5, "maximum": 26.5 }, "lng": { "type": "number", "minimum": 118, "maximum": 122.5 } }
    },
    "OsmRef": {
      "type": "object",
      "required": ["type", "id", "url"],
      "properties": { "type": { "enum": ["node", "way", "relation"] }, "id": { "type": "integer" }, "url": { "type": "string" } }
    },
    "Provenance": {
      "type": "object",
      "required": ["method", "verified_at", "confidence", "source"],
      "properties": {
        "method": { "enum": ["site_visit", "user_report", "computed", "official_data", "satellite", "osm"] },
        "source": { "type": "string" },
        "verified_at": { "type": "string", "format": "date" },
        "confidence": { "enum": ["high", "medium", "low"] }
      }
    },
    "Parking": {
      "type": "object",
      "required": ["name", "distance_m", "distance_method", "location", "osm"],
      "properties": {
        "name": { "type": "string" },
        "kind": { "type": ["string", "null"] },
        "distance_m": { "type": "integer" },
        "distance_method": { "const": "straight_line" },
        "capacity": { "type": ["integer", "null"] },
        "fee": { "type": ["string", "null"] },
        "maxheight_m": { "type": ["number", "null"] },
        "access": { "type": ["string", "null"] },
        "location": { "$ref": "#/$defs/LatLng" },
        "osm": { "$ref": "#/$defs/OsmRef" }
      }
    },
    "Charger": {
      "type": "object",
      "required": ["name", "speed", "sockets", "distance_m", "location", "osm"],
      "properties": {
        "name": { "type": "string" },
        "operator": { "type": ["string", "null"] },
        "speed": { "enum": ["fast", "slow", "both", "unknown"], "description": "fast = DC (CCS/CHAdeMO/Supercharger), slow = AC (Type 1/Type 2), unknown = not tagged." },
        "speed_label": { "type": "string" },
        "speed_source": { "enum": ["sockets", "output", "brand", "name", null], "description": "Evidence for speed: socket tags, tagged power, a DC-only operator, or the station name saying 快充/超充." },
        "max_kw": { "type": ["number", "null"] },
        "sockets": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": { "type": { "type": "string" }, "label": { "type": "string" }, "current": { "enum": ["AC", "DC", null] }, "count": { "type": "integer" }, "output_kw": { "type": ["number", "null"] } }
          }
        },
        "distance_m": { "type": "integer" },
        "location": { "$ref": "#/$defs/LatLng" },
        "osm": { "$ref": "#/$defs/OsmRef" }
      }
    }
  }
}
