{
  "$defs": {
    "DailySummary": {
      "additionalProperties": false,
      "description": "One local calendar day (America/Denver) of feeder activity.",
      "properties": {
        "day": {
          "format": "date",
          "title": "Day",
          "type": "string"
        },
        "events": {
          "minimum": 0,
          "title": "Events",
          "type": "integer"
        },
        "first_hour_local": {
          "anyOf": [
            {
              "maximum": 23,
              "minimum": 0,
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "description": "Local hour of the first event.",
          "title": "First Hour Local"
        },
        "last_hour_local": {
          "anyOf": [
            {
              "maximum": 23,
              "minimum": 0,
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "description": "Local hour of the last event.",
          "title": "Last Hour Local"
        },
        "quality": {
          "description": "Data quality for the day: ok, degraded or unknown.",
          "enum": [
            "ok",
            "degraded",
            "unknown"
          ],
          "title": "Quality",
          "type": "string"
        },
        "species_richness": {
          "description": "Distinct bird species seen (category bird; unknown, mammals and domestic animals excluded).",
          "minimum": 0,
          "title": "Species Richness",
          "type": "integer"
        },
        "visits": {
          "minimum": 0,
          "title": "Visits",
          "type": "integer"
        }
      },
      "required": [
        "day",
        "events",
        "visits",
        "species_richness",
        "first_hour_local",
        "last_hour_local",
        "quality"
      ],
      "title": "DailySummary",
      "type": "object"
    },
    "EffortHourly": {
      "additionalProperties": false,
      "description": "How much of one UTC hour the camera was observing: streaming, with decodable video.\n\nAn hour without a row is unknown, not zero. Added after v1 shipped (ADR 0016).",
      "properties": {
        "hour_start": {
          "description": "UTC timestamp aligned to the hour (minutes and seconds are zero).",
          "format": "date-time",
          "title": "Hour Start",
          "type": "string"
        },
        "observed_minutes": {
          "description": "Whole minutes of the hour the camera was observing.",
          "maximum": 60,
          "minimum": 0,
          "title": "Observed Minutes",
          "type": "integer"
        }
      },
      "required": [
        "hour_start",
        "observed_minutes"
      ],
      "title": "EffortHourly",
      "type": "object"
    },
    "EnvironmentHourly": {
      "additionalProperties": false,
      "description": "Hourly means of the feeder's environment sensor, deliberately coarse.",
      "properties": {
        "hour_start": {
          "description": "UTC timestamp aligned to the hour (minutes and seconds are zero).",
          "format": "date-time",
          "title": "Hour Start",
          "type": "string"
        },
        "humidity_pct": {
          "anyOf": [
            {
              "description": "Mean relative humidity, whole %.",
              "maximum": 100,
              "minimum": 0,
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "title": "Humidity Pct"
        },
        "pressure_hpa": {
          "anyOf": [
            {
              "description": "Mean sea-level-adjusted pressure, whole hPa.",
              "maximum": 1100,
              "minimum": 850,
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "title": "Pressure Hpa"
        },
        "samples": {
          "description": "Sensor samples behind the means.",
          "minimum": 1,
          "title": "Samples",
          "type": "integer"
        },
        "temp_c": {
          "anyOf": [
            {
              "description": "Mean air temperature in 0.5 °C steps.",
              "maximum": 60,
              "minimum": -60,
              "multipleOf": 0.5,
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Temp C"
        }
      },
      "required": [
        "hour_start",
        "temp_c",
        "humidity_pct",
        "pressure_hpa",
        "samples"
      ],
      "title": "EnvironmentHourly",
      "type": "object"
    },
    "HoldoutClass": {
      "additionalProperties": false,
      "description": "One class's F1 on the holdout set and how many holdout detections carried that label.",
      "properties": {
        "f1": {
          "description": "F1 score in [0, 1], rounded to 3 decimals.",
          "maximum": 1,
          "minimum": 0,
          "title": "F1",
          "type": "number"
        },
        "n": {
          "minimum": 0,
          "title": "N",
          "type": "integer"
        }
      },
      "required": [
        "f1",
        "n"
      ],
      "title": "HoldoutClass",
      "type": "object"
    },
    "ModelQuality": {
      "additionalProperties": false,
      "description": "Classifier quality: the latest run in detail and the run history.",
      "properties": {
        "latest": {
          "$ref": "#/$defs/ModelRun"
        },
        "runs": {
          "items": {
            "$ref": "#/$defs/ModelRunSummary"
          },
          "title": "Runs",
          "type": "array"
        }
      },
      "required": [
        "latest",
        "runs"
      ],
      "title": "ModelQuality",
      "type": "object"
    },
    "ModelRun": {
      "additionalProperties": false,
      "description": "The latest classifier training run, with per-class detail.",
      "properties": {
        "backbone": {
          "description": "Model architecture name, for example efficientnet_b0.",
          "maxLength": 80,
          "minLength": 1,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9 _.-]*$",
          "title": "Backbone",
          "type": "string"
        },
        "confidence_threshold": {
          "anyOf": [
            {
              "description": "Classifier confidence in [0, 1], rounded to 2 decimals.",
              "maximum": 1,
              "minimum": 0,
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Below this confidence a prediction is published as unknown; null when the exporter used its default.",
          "title": "Confidence Threshold"
        },
        "ece": {
          "anyOf": [
            {
              "description": "Expected calibration error in [0, 1], rounded to 3 decimals.",
              "maximum": 1,
              "minimum": 0,
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Calibration error of the confidences on the holdout set.",
          "title": "Ece"
        },
        "holdout_macro_f1": {
          "anyOf": [
            {
              "description": "F1 score in [0, 1], rounded to 3 decimals.",
              "maximum": 1,
              "minimum": 0,
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Holdout Macro F1"
        },
        "holdout_macro_f1_ci": {
          "anyOf": [
            {
              "maxItems": 2,
              "minItems": 2,
              "prefixItems": [
                {
                  "description": "F1 score in [0, 1], rounded to 3 decimals.",
                  "maximum": 1,
                  "minimum": 0,
                  "type": "number"
                },
                {
                  "description": "F1 score in [0, 1], rounded to 3 decimals.",
                  "maximum": 1,
                  "minimum": 0,
                  "type": "number"
                }
              ],
              "type": "array"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "95 % confidence interval of holdout_macro_f1, low then high.",
          "title": "Holdout Macro F1 Ci"
        },
        "holdout_n": {
          "anyOf": [
            {
              "minimum": 0,
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Holdout N"
        },
        "holdout_per_class": {
          "additionalProperties": {
            "$ref": "#/$defs/HoldoutClass"
          },
          "description": "Holdout F1 and support per class, keyed like per_class_f1.",
          "propertyNames": {
            "enum": [
              "house-sparrow",
              "house-finch",
              "eurasian-collared-dove",
              "song-sparrow",
              "mourning-dove",
              "black-capped-chickadee",
              "rufous-crowned-sparrow",
              "european-starling",
              "lazuli-bunting",
              "squirrel",
              "chicken",
              "cat",
              "unknown",
              "not_a_bird"
            ]
          },
          "title": "Holdout Per Class",
          "type": "object"
        },
        "macro_f1": {
          "description": "F1 score in [0, 1], rounded to 3 decimals.",
          "maximum": 1,
          "minimum": 0,
          "title": "Macro F1",
          "type": "number"
        },
        "n_train": {
          "minimum": 0,
          "title": "N Train",
          "type": "integer"
        },
        "n_val": {
          "minimum": 0,
          "title": "N Val",
          "type": "integer"
        },
        "per_class_f1": {
          "additionalProperties": {
            "description": "F1 score in [0, 1], rounded to 3 decimals.",
            "maximum": 1,
            "minimum": 0,
            "type": "number"
          },
          "description": "F1 per class, keyed by species id; 'not_a_bird' is the abstain class.",
          "propertyNames": {
            "enum": [
              "house-sparrow",
              "house-finch",
              "eurasian-collared-dove",
              "song-sparrow",
              "mourning-dove",
              "black-capped-chickadee",
              "rufous-crowned-sparrow",
              "european-starling",
              "lazuli-bunting",
              "squirrel",
              "chicken",
              "cat",
              "unknown",
              "not_a_bird"
            ]
          },
          "title": "Per Class F1",
          "type": "object"
        },
        "run_label": {
          "description": "Ordinal training-run label such as run-07. Never a raw run id.",
          "pattern": "^run-(?:[0-9]{2}|[1-9][0-9]{2,3})$",
          "title": "Run Label",
          "type": "string"
        },
        "split_scheme": {
          "anyOf": [
            {
              "description": "How the run split its data, such as block1800; null before it was recorded.",
              "pattern": "^[a-z][a-z0-9]{0,15}$",
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Split Scheme"
        },
        "trained_on": {
          "format": "date",
          "title": "Trained On",
          "type": "string"
        }
      },
      "required": [
        "run_label",
        "trained_on",
        "macro_f1",
        "n_train",
        "n_val",
        "backbone",
        "per_class_f1"
      ],
      "title": "ModelRun",
      "type": "object"
    },
    "ModelRunSummary": {
      "additionalProperties": false,
      "description": "Headline numbers for one classifier training run.\n\n``macro_f1`` is measured on the validation split, which also chose the kept weights. The\n``holdout_*`` and ``split_scheme`` fields were added after v1 shipped, so they are optional:\n``holdout_macro_f1`` is scored on owner-labelled detections from held-out half-hour blocks\nthe run never trained on, and is null for runs from before the tracker measured it.",
      "properties": {
        "holdout_macro_f1": {
          "anyOf": [
            {
              "description": "F1 score in [0, 1], rounded to 3 decimals.",
              "maximum": 1,
              "minimum": 0,
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Holdout Macro F1"
        },
        "holdout_n": {
          "anyOf": [
            {
              "minimum": 0,
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Holdout N"
        },
        "macro_f1": {
          "description": "F1 score in [0, 1], rounded to 3 decimals.",
          "maximum": 1,
          "minimum": 0,
          "title": "Macro F1",
          "type": "number"
        },
        "n_train": {
          "minimum": 0,
          "title": "N Train",
          "type": "integer"
        },
        "n_val": {
          "minimum": 0,
          "title": "N Val",
          "type": "integer"
        },
        "run_label": {
          "description": "Ordinal training-run label such as run-07. Never a raw run id.",
          "pattern": "^run-(?:[0-9]{2}|[1-9][0-9]{2,3})$",
          "title": "Run Label",
          "type": "string"
        },
        "split_scheme": {
          "anyOf": [
            {
              "description": "How the run split its data, such as block1800; null before it was recorded.",
              "pattern": "^[a-z][a-z0-9]{0,15}$",
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Split Scheme"
        },
        "trained_on": {
          "format": "date",
          "title": "Trained On",
          "type": "string"
        }
      },
      "required": [
        "run_label",
        "trained_on",
        "macro_f1",
        "n_train",
        "n_val"
      ],
      "title": "ModelRunSummary",
      "type": "object"
    },
    "QualityWindow": {
      "additionalProperties": false,
      "description": "A curated date range during which counts are known to be degraded.",
      "properties": {
        "end": {
          "format": "date",
          "title": "End",
          "type": "string"
        },
        "label": {
          "description": "1..80 printable ASCII characters; never contains a forbidden substring.",
          "maxLength": 80,
          "minLength": 1,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9 _.-]*$",
          "title": "Label",
          "type": "string"
        },
        "start": {
          "format": "date",
          "title": "Start",
          "type": "string"
        }
      },
      "required": [
        "start",
        "end",
        "label"
      ],
      "title": "QualityWindow",
      "type": "object"
    },
    "SpeciesCatalogEntry": {
      "additionalProperties": false,
      "description": "A species the feeder can report, with the day it was first and the hour it was last seen.\n\n``last_seen_at`` is the start of the hour of the species' newest event, truncated like\n``Status.last_event_at``. It was added after v1 shipped, so it is optional: an exporter that\npredates it omits the key and the entry reads ``None``, as for a species never seen.",
      "properties": {
        "category": {
          "enum": [
            "bird",
            "mammal",
            "domestic"
          ],
          "title": "Category",
          "type": "string"
        },
        "common_name": {
          "description": "1..80 printable ASCII characters; never contains a forbidden substring.",
          "maxLength": 80,
          "minLength": 1,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9 _.-]*$",
          "title": "Common Name",
          "type": "string"
        },
        "first_seen_day": {
          "anyOf": [
            {
              "format": "date",
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "First Seen Day"
        },
        "id": {
          "description": "Whitelisted species id.",
          "enum": [
            "house-sparrow",
            "house-finch",
            "eurasian-collared-dove",
            "song-sparrow",
            "mourning-dove",
            "black-capped-chickadee",
            "rufous-crowned-sparrow",
            "european-starling",
            "lazuli-bunting",
            "squirrel",
            "chicken",
            "cat",
            "unknown"
          ],
          "title": "Id",
          "type": "string"
        },
        "last_seen_at": {
          "anyOf": [
            {
              "description": "UTC timestamp aligned to the hour (minutes and seconds are zero).",
              "format": "date-time",
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Last Seen At"
        }
      },
      "required": [
        "id",
        "common_name",
        "category",
        "first_seen_day"
      ],
      "title": "SpeciesCatalogEntry",
      "type": "object"
    },
    "SpeciesHourly": {
      "additionalProperties": false,
      "description": "Events and feeder visits for one species in one UTC hour.",
      "properties": {
        "events": {
          "description": "Tracker events (one per detected object track).",
          "minimum": 0,
          "title": "Events",
          "type": "integer"
        },
        "hour_start": {
          "description": "UTC timestamp aligned to the hour (minutes and seconds are zero).",
          "format": "date-time",
          "title": "Hour Start",
          "type": "string"
        },
        "mean_confidence": {
          "description": "Classifier confidence in [0, 1], rounded to 2 decimals.",
          "maximum": 1,
          "minimum": 0,
          "title": "Mean Confidence",
          "type": "number"
        },
        "provisional": {
          "description": "True while the bucket may still change (recent, not yet authoritative).",
          "title": "Provisional",
          "type": "boolean"
        },
        "species": {
          "description": "Whitelisted species id.",
          "enum": [
            "house-sparrow",
            "house-finch",
            "eurasian-collared-dove",
            "song-sparrow",
            "mourning-dove",
            "black-capped-chickadee",
            "rufous-crowned-sparrow",
            "european-starling",
            "lazuli-bunting",
            "squirrel",
            "chicken",
            "cat",
            "unknown"
          ],
          "title": "Species",
          "type": "string"
        },
        "visits": {
          "description": "Visits after 15 s start-to-start clustering.",
          "minimum": 0,
          "title": "Visits",
          "type": "integer"
        }
      },
      "required": [
        "hour_start",
        "species",
        "events",
        "visits",
        "mean_confidence",
        "provisional"
      ],
      "title": "SpeciesHourly",
      "type": "object"
    },
    "Status": {
      "additionalProperties": false,
      "description": "Exporter status. ``last_event_at`` is truncated to the hour on purpose.",
      "properties": {
        "contract_version": {
          "const": 1,
          "title": "Contract Version",
          "type": "integer"
        },
        "last_event_at": {
          "anyOf": [
            {
              "description": "UTC timestamp aligned to the hour (minutes and seconds are zero).",
              "format": "date-time",
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Last Event At"
        }
      },
      "required": [
        "last_event_at",
        "contract_version"
      ],
      "title": "Status",
      "type": "object"
    },
    "TimelineEvent": {
      "additionalProperties": false,
      "description": "One event in the rolling 24-hour timeline. Carries no identifier of any kind.",
      "properties": {
        "confidence": {
          "description": "Classifier confidence in [0, 1], rounded to 2 decimals.",
          "maximum": 1,
          "minimum": 0,
          "title": "Confidence",
          "type": "number"
        },
        "duration_s": {
          "anyOf": [
            {
              "description": "Duration in seconds, at most 86400, rounded to 1 decimal.",
              "maximum": 86400,
              "minimum": 0,
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "description": "Event duration, or null when the end is not yet known.",
          "title": "Duration S"
        },
        "species": {
          "description": "Whitelisted species id.",
          "enum": [
            "house-sparrow",
            "house-finch",
            "eurasian-collared-dove",
            "song-sparrow",
            "mourning-dove",
            "black-capped-chickadee",
            "rufous-crowned-sparrow",
            "european-starling",
            "lazuli-bunting",
            "squirrel",
            "chicken",
            "cat",
            "unknown"
          ],
          "title": "Species",
          "type": "string"
        },
        "start_time": {
          "description": "UTC timestamp with second precision (no fractional seconds).",
          "format": "date-time",
          "title": "Start Time",
          "type": "string"
        }
      },
      "required": [
        "start_time",
        "duration_s",
        "species",
        "confidence"
      ],
      "title": "TimelineEvent",
      "type": "object"
    },
    "WeatherHourly": {
      "additionalProperties": false,
      "description": "Modelled outdoor weather for one UTC hour at Salt Lake City's city centre (Open-Meteo).\n\nThe API fetches it itself (ADR 0017); it never comes from home and describes the city,\nnot the yard. Added after v1 shipped, to the read model only.",
      "properties": {
        "cloud_pct": {
          "anyOf": [
            {
              "description": "Mean total cloud cover, whole %.",
              "maximum": 100,
              "minimum": 0,
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "title": "Cloud Pct"
        },
        "hour_start": {
          "description": "UTC timestamp aligned to the hour (minutes and seconds are zero).",
          "format": "date-time",
          "title": "Hour Start",
          "type": "string"
        },
        "humidity_pct": {
          "anyOf": [
            {
              "description": "Mean relative humidity, whole %.",
              "maximum": 100,
              "minimum": 0,
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "title": "Humidity Pct"
        },
        "precip_mm": {
          "anyOf": [
            {
              "description": "Precipitation over the hour (rain and melted snow), mm, 1 decimal.",
              "maximum": 300,
              "minimum": 0,
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Precip Mm"
        },
        "pressure_hpa": {
          "anyOf": [
            {
              "description": "Mean sea-level-adjusted pressure, whole hPa.",
              "maximum": 1100,
              "minimum": 850,
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "title": "Pressure Hpa"
        },
        "shortwave_wm2": {
          "anyOf": [
            {
              "description": "Mean global shortwave radiation over the hour, whole W/m².",
              "maximum": 1500,
              "minimum": 0,
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "title": "Shortwave Wm2"
        },
        "temp_c": {
          "anyOf": [
            {
              "description": "Mean air temperature in 0.5 °C steps.",
              "maximum": 60,
              "minimum": -60,
              "multipleOf": 0.5,
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Temp C"
        },
        "wind_kmh": {
          "anyOf": [
            {
              "description": "Mean wind speed 10 m above ground, whole km/h.",
              "maximum": 300,
              "minimum": 0,
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "title": "Wind Kmh"
        }
      },
      "required": [
        "hour_start",
        "temp_c",
        "humidity_pct",
        "precip_mm",
        "cloud_pct",
        "wind_kmh",
        "pressure_hpa",
        "shortwave_wm2"
      ],
      "title": "WeatherHourly",
      "type": "object"
    }
  },
  "$id": "https://averyuslaner.com/schema/v1/DashboardBundle.schema.json",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "additionalProperties": false,
  "description": "The public read model served at ``/api/v1/dashboard``: documents plus API-side context.",
  "properties": {
    "daily_summary": {
      "items": {
        "$ref": "#/$defs/DailySummary"
      },
      "title": "Daily Summary",
      "type": "array"
    },
    "effort_hourly": {
      "default": [],
      "description": "Observed minutes per UTC hour; optional, added after v1 shipped.",
      "items": {
        "$ref": "#/$defs/EffortHourly"
      },
      "title": "Effort Hourly",
      "type": "array"
    },
    "environment_hourly": {
      "items": {
        "$ref": "#/$defs/EnvironmentHourly"
      },
      "title": "Environment Hourly",
      "type": "array"
    },
    "freshness": {
      "description": "Hour-granularity staleness of the latest push.",
      "enum": [
        "fresh",
        "stale",
        "offline"
      ],
      "title": "Freshness",
      "type": "string"
    },
    "generated_at": {
      "description": "The newest accepted envelope's generated_at floored to the UTC hour, or the server's start hour (UTC) before any push.",
      "format": "date-time",
      "title": "Generated At",
      "type": "string"
    },
    "model_quality": {
      "anyOf": [
        {
          "$ref": "#/$defs/ModelQuality"
        },
        {
          "type": "null"
        }
      ]
    },
    "quality_windows": {
      "items": {
        "$ref": "#/$defs/QualityWindow"
      },
      "title": "Quality Windows",
      "type": "array"
    },
    "species_catalog": {
      "items": {
        "$ref": "#/$defs/SpeciesCatalogEntry"
      },
      "title": "Species Catalog",
      "type": "array"
    },
    "species_hourly": {
      "items": {
        "$ref": "#/$defs/SpeciesHourly"
      },
      "title": "Species Hourly",
      "type": "array"
    },
    "status": {
      "$ref": "#/$defs/Status"
    },
    "timeline_24h": {
      "items": {
        "$ref": "#/$defs/TimelineEvent"
      },
      "title": "Timeline 24H",
      "type": "array"
    },
    "weather_hourly": {
      "default": [],
      "description": "Open-Meteo weather per UTC hour for the city centre, fetched by the API; optional, added after v1 shipped (ADR 0017).",
      "items": {
        "$ref": "#/$defs/WeatherHourly"
      },
      "title": "Weather Hourly",
      "type": "array"
    }
  },
  "required": [
    "species_hourly",
    "timeline_24h",
    "environment_hourly",
    "daily_summary",
    "model_quality",
    "species_catalog",
    "status",
    "freshness",
    "generated_at",
    "quality_windows"
  ],
  "title": "DashboardBundle",
  "type": "object"
}
