{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://skycruncher.io/myscope/report.schema.json",
  "title": "SkyCruncher scope report, version 1",
  "description": "One scope's standard report, as skycruncher.io/myscope/ shows it and offers it for download. One file per serial hash. Characterization only: no site, no time of observation, no owner and no list of nights. Version 1 is a proposal: the layout may still change before the first reports are built.",
  "type": "object",
  "required": ["schema", "serial_hash", "built_date", "scope", "counts", "sections"],
  "additionalProperties": false,
  "properties": {
    "$schema": { "const": "https://skycruncher.io/myscope/report.schema.json" },
    "schema": { "const": "skycruncher.myscope-report/1" },
    "serial_hash": {
      "description": "The hash of the one serial this file answers, lowercase hex: SHA-256 over a private salt line followed by the line serial=<serial>, so the serial itself is never stored with the report.",
      "type": "string", "pattern": "^[0-9a-f]{64}$"
    },
    "built_date": { "description": "The day this report was built (UTC date, no time).", "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" },
    "builder": {
      "description": "What built it.",
      "type": "object", "additionalProperties": false,
      "properties": { "name": { "type": "string" }, "version": { "type": "string" } }
    },
    "scope": {
      "type": "object", "additionalProperties": false, "required": ["model"],
      "properties": {
        "model": { "description": "The scope's model, such as Seestar S50.", "type": "string" },
        "aperture_mm": { "type": ["number", "null"] },
        "focal_length_mm": { "type": ["number", "null"] },
        "optics_model": { "description": "The optics model version the numbers come from, such as v0.", "type": ["string", "null"] },
        "all_in_one": { "description": "True when the telescope and camera are one unit (it reads as one scope).", "type": "boolean" }
      }
    },
    "counts": {
      "type": "object", "additionalProperties": false, "required": ["nights", "sessions", "pairings"],
      "properties": {
        "nights": { "type": "integer", "minimum": 0 },
        "sessions": { "type": "integer", "minimum": 0 },
        "pairings": { "description": "How many telescope and camera pairings this serial is part of.", "type": "integer", "minimum": 0 }
      }
    },
    "sections": {
      "description": "The four sections in their fixed order. A section or measure that is absent reads as not yet measured.",
      "type": "object", "additionalProperties": false,
      "properties": {
        "optics": { "$ref": "#/$defs/section" },
        "brightness_color": { "$ref": "#/$defs/section" },
        "depth_position": { "$ref": "#/$defs/section" },
        "sensor_tracking": { "$ref": "#/$defs/section" }
      }
    }
  },
  "$defs": {
    "section": {
      "type": "object", "additionalProperties": false,
      "properties": {
        "nights_needed": { "description": "Roughly how many more nights the section needs before it is ready; null when ready.", "type": ["integer", "null"], "minimum": 0 },
        "note": { "type": "string" },
        "measures": { "type": "array", "items": { "$ref": "#/$defs/measure" } }
      }
    },
    "measure": {
      "description": "One number, or a small set of numbers, about the scope. Optics pool across every pairing on the telescope; brightness and sensor numbers split per camera (one measure per camera, named in camera).",
      "type": "object", "additionalProperties": false, "required": ["key", "status"],
      "properties": {
        "key": {
          "description": "The slot this fills. Known keys: optics: star_sharpness, lens_curve, pixel_scale, sensor_tilt, corner_darkening, color_fringing; brightness_color: zero_point, color_term, gaia_scatter; depth_position: depth, position_precision; sensor_tracking: gain, read_noise, hot_pixels, saturation, tracking. Other keys are shown after the known ones.",
          "type": "string", "pattern": "^[a-z][a-z0-9_]*$"
        },
        "label": { "type": "string" },
        "status": { "enum": ["measured", "stated", "not_yet_measured"] },
        "camera": { "description": "The camera model this measure is for, when the number splits per camera.", "type": ["string", "null"] },
        "value": { "type": ["number", "string", "null"] },
        "error": { "description": "One standard error, in the value's unit.", "type": ["number", "null"] },
        "unit": { "type": ["string", "null"] },
        "zones": {
          "description": "A 3 by 3 grid across the field, top row first.",
          "type": "array", "minItems": 3, "maxItems": 3,
          "items": { "type": "array", "minItems": 3, "maxItems": 3, "items": { "type": ["number", "null"] } }
        },
        "rows": {
          "description": "A few labeled numbers, such as one per color band or center, halfway and corner.",
          "type": "array",
          "items": {
            "type": "object", "additionalProperties": false, "required": ["label"],
            "properties": {
              "label": { "type": "string" }, "value": { "type": ["number", "string", "null"] },
              "error": { "type": ["number", "null"] }, "unit": { "type": ["string", "null"] }
            }
          }
        },
        "versus_model": {
          "description": "Where this scope sits among others of its model, never naming or linking another scope. Omitted until enough have shared.",
          "type": ["object", "null"], "additionalProperties": false, "required": ["percentile", "of"],
          "properties": { "percentile": { "type": "number", "minimum": 0, "maximum": 100 }, "of": { "type": "integer", "minimum": 2 } }
        },
        "nights_needed": { "type": ["integer", "null"], "minimum": 0 },
        "note": { "type": "string" }
      }
    }
  }
}
