{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://gen318.com/ops-1/annex-b.schema.json",
  "title": "OPS-1 v1.0 Annex B: minimum reporting schema (per site, per Reporting Period)",
  "description": "Machine-readable form of Table B.1 of OPS-1 v1.0. Where this schema and Table B.1 differ, Table B.1 governs. A field that cannot be produced is absent and its companion <field>_status carries a refusal value; a numeric field never carries zero, null or a default in place of a refusal. Ratios and percentiles carry the count of trusted records behind them in <field>_n. Validation against this schema establishes only that a report is well formed; it does not establish conformance, which requires the evidence, the declared parameters and the refusals of each clause (Annex B, Refusals and statuses).",
  "type": "object",
  "additionalProperties": true,
  "$defs": {
    "date": {
      "type": "string",
      "format": "date"
    },
    "pct": {
      "type": "number",
      "minimum": 0,
      "maximum": 100
    },
    "kwh": {
      "type": "number"
    },
    "minutes": {
      "type": "integer",
      "minimum": 0
    },
    "count": {
      "type": "integer",
      "minimum": 0
    },
    "fraction": {
      "type": "number",
      "minimum": 0,
      "maximum": 1
    },
    "sha256": {
      "type": "string",
      "pattern": "^[0-9a-f]{64}$",
      "description": "SHA-256, lowercase hex, over the canonical form under hash_profile"
    },
    "refusal": {
      "type": "string",
      "enum": [
        "undefined",
        "unpriced",
        "withheld",
        "no_baseline",
        "insufficient_coverage",
        "unclassified",
        "unverified"
      ]
    },
    "byClassMinutes": {
      "type": "object",
      "description": "Fault class name to minutes",
      "additionalProperties": {
        "$ref": "#/$defs/minutes"
      }
    },
    "byClassCount": {
      "type": "object",
      "additionalProperties": {
        "$ref": "#/$defs/count"
      }
    },
    "seriesCoverage": {
      "type": "object",
      "required": [
        "series_id",
        "cadence_s",
        "expected",
        "delivered",
        "trusted",
        "rejected_by_reason"
      ],
      "properties": {
        "series_id": {
          "type": "string"
        },
        "cadence_s": {
          "type": "integer",
          "minimum": 1
        },
        "expected": {
          "$ref": "#/$defs/count"
        },
        "delivered": {
          "$ref": "#/$defs/count"
        },
        "trusted": {
          "$ref": "#/$defs/count"
        },
        "rejected_by_reason": {
          "type": "object",
          "description": "Reason (out_of_range, stuck_run, timestamp_tolerance, flagged_bad, duplicate) to count",
          "additionalProperties": {
            "$ref": "#/$defs/count"
          }
        }
      }
    },
    "registerChange": {
      "type": "object",
      "required": [
        "series_id",
        "action",
        "date",
        "evidence_ref"
      ],
      "properties": {
        "series_id": {
          "type": "string"
        },
        "action": {
          "type": "string",
          "enum": [
            "added",
            "retired"
          ]
        },
        "date": {
          "$ref": "#/$defs/date"
        },
        "evidence_ref": {
          "type": "string"
        }
      }
    },
    "resourceModel": {
      "type": "object",
      "required": [
        "irradiance_provenance",
        "transposition_model",
        "temperature_model",
        "gamma_pct_per_k",
        "p_stc_kwp",
        "irradiance_uncertainty_pct"
      ],
      "properties": {
        "irradiance_provenance": {
          "type": "string",
          "enum": [
            "poa_measured",
            "ghi_transposed",
            "satellite",
            "refused"
          ]
        },
        "transposition_model": {
          "type": "string"
        },
        "temperature_model": {
          "type": "string"
        },
        "gamma_pct_per_k": {
          "type": "number"
        },
        "p_stc_kwp": {
          "type": "number",
          "exclusiveMinimum": 0
        },
        "irradiance_uncertainty_pct": {
          "$ref": "#/$defs/pct"
        }
      }
    },
    "scorecardDimension": {
      "type": "object",
      "required": [
        "dimension",
        "evidence_status"
      ],
      "properties": {
        "dimension": {
          "type": "string",
          "enum": [
            "C1",
            "C2",
            "C3",
            "C4",
            "C5",
            "C6",
            "C7",
            "C8",
            "C13"
          ]
        },
        "value": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "object",
              "description": "Named components (delivered/trusted, raw/adjusted) or fault classes to numbers",
              "additionalProperties": {
                "anyOf": [
                  {
                    "type": "number"
                  },
                  {
                    "type": "object",
                    "additionalProperties": {
                      "type": "number"
                    }
                  }
                ]
              }
            }
          ]
        },
        "value_status": {
          "$ref": "#/$defs/refusal"
        },
        "evidence_status": {
          "type": "string",
          "enum": [
            "evidenced",
            "partial",
            "refused"
          ]
        },
        "records_used": {
          "$ref": "#/$defs/count"
        }
      }
    },
    "clauseStatus": {
      "type": "object",
      "required": [
        "clause",
        "status"
      ],
      "properties": {
        "clause": {
          "type": "integer",
          "minimum": 1,
          "maximum": 13
        },
        "status": {
          "type": "string",
          "enum": [
            "reported",
            "refused",
            "not_reported"
          ]
        },
        "refusal": {
          "$ref": "#/$defs/refusal"
        },
        "since": {
          "$ref": "#/$defs/date"
        },
        "remediation_owner": {
          "type": "string",
          "enum": [
            "owner",
            "operator",
            "manufacturer",
            "regulator",
            "other"
          ]
        }
      }
    },
    "byClassRefusal": {
      "type": "object",
      "description": "Fault class name to refusal, for a by-class field whose value some classes cannot produce",
      "additionalProperties": {
        "$ref": "#/$defs/refusal"
      }
    }
  },
  "properties": {
    "period_start": {
      "$ref": "#/$defs/date"
    },
    "period_end": {
      "$ref": "#/$defs/date"
    },
    "service_model": {
      "type": "string",
      "enum": [
        "continuous",
        "daylight",
        "contracted_hours"
      ]
    },
    "interval_resolution_min": {
      "type": "integer",
      "minimum": 1,
      "maximum": 15
    },
    "in_service_capacity_share_pct": {
      "$ref": "#/$defs/pct"
    },
    "delivery_point": {
      "type": "string"
    },
    "coverage_by_series": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/seriesCoverage"
      }
    },
    "coverage_delivered_pct": {
      "$ref": "#/$defs/pct"
    },
    "coverage_trusted_pct": {
      "$ref": "#/$defs/pct"
    },
    "series_register_changes": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/registerChange"
      }
    },
    "silent_series": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "provisional": {
      "type": "boolean"
    },
    "minutes_contracted": {
      "$ref": "#/$defs/minutes"
    },
    "minutes_in_service": {
      "$ref": "#/$defs/minutes"
    },
    "minutes_plant_fault": {
      "$ref": "#/$defs/minutes"
    },
    "minutes_scheduled_maintenance": {
      "$ref": "#/$defs/minutes"
    },
    "minutes_grid_outage_excused": {
      "$ref": "#/$defs/minutes"
    },
    "minutes_client_curtailment": {
      "$ref": "#/$defs/minutes"
    },
    "minutes_force_majeure": {
      "$ref": "#/$defs/minutes"
    },
    "minutes_no_data": {
      "$ref": "#/$defs/minutes"
    },
    "availability_raw_pct": {
      "$ref": "#/$defs/pct"
    },
    "availability_adjusted_pct": {
      "$ref": "#/$defs/pct"
    },
    "e_resource_kwh": {
      "$ref": "#/$defs/kwh"
    },
    "e_resource_kwh_low": {
      "$ref": "#/$defs/kwh"
    },
    "e_resource_kwh_high": {
      "$ref": "#/$defs/kwh"
    },
    "resource_model": {
      "$ref": "#/$defs/resourceModel"
    },
    "e_pv_metered_kwh": {
      "$ref": "#/$defs/kwh"
    },
    "e_pv_derived_kwh": {
      "$ref": "#/$defs/kwh"
    },
    "e_curtailed_kwh": {
      "$ref": "#/$defs/kwh"
    },
    "e_loss_kwh": {
      "$ref": "#/$defs/kwh"
    },
    "e_delivered_kwh": {
      "$ref": "#/$defs/kwh"
    },
    "e_diesel_kwh": {
      "$ref": "#/$defs/kwh"
    },
    "e_import_kwh": {
      "$ref": "#/$defs/kwh"
    },
    "e_export_kwh": {
      "$ref": "#/$defs/kwh"
    },
    "e_storage_charge_kwh": {
      "$ref": "#/$defs/kwh"
    },
    "e_storage_discharge_kwh": {
      "$ref": "#/$defs/kwh"
    },
    "e_auxiliary_kwh": {
      "$ref": "#/$defs/kwh"
    },
    "e_balance_residual_kwh": {
      "$ref": "#/$defs/kwh"
    },
    "loss_attributed_kwh": {
      "$ref": "#/$defs/kwh"
    },
    "loss_unattributed_kwh": {
      "$ref": "#/$defs/kwh"
    },
    "pr_pct": {
      "$ref": "#/$defs/pct"
    },
    "pr_tc_pct": {
      "$ref": "#/$defs/pct"
    },
    "tc_reference_temp": {
      "type": "string",
      "enum": [
        "25C",
        "annual_equivalent"
      ]
    },
    "tc_gamma_pct_per_k": {
      "type": "number"
    },
    "monitoring_class": {
      "type": "string",
      "enum": [
        "A",
        "B",
        "unclassified"
      ]
    },
    "irradiance_provenance": {
      "type": "string",
      "enum": [
        "poa_measured",
        "ghi_transposed",
        "satellite",
        "refused"
      ]
    },
    "pr_records_used": {
      "$ref": "#/$defs/count"
    },
    "pr_records_excluded": {
      "$ref": "#/$defs/count"
    },
    "pr_missing_data_rule": {
      "type": "string"
    },
    "irradiance_sensor": {
      "type": "string"
    },
    "irradiance_sensor_calibrated_on": {
      "$ref": "#/$defs/date"
    },
    "module_temp_sensor": {
      "type": "string"
    },
    "module_temp_sensor_calibrated_on": {
      "$ref": "#/$defs/date"
    },
    "corrective_closures": {
      "$ref": "#/$defs/count"
    },
    "confidence_mix": {
      "type": "object",
      "properties": {
        "confirmed": {
          "$ref": "#/$defs/count"
        },
        "probable": {
          "$ref": "#/$defs/count"
        },
        "suspected": {
          "$ref": "#/$defs/count"
        }
      },
      "additionalProperties": false
    },
    "share_no_cause_established_pct": {
      "$ref": "#/$defs/pct"
    },
    "closures_held": {
      "$ref": "#/$defs/count"
    },
    "evidence_completeness_mean": {
      "$ref": "#/$defs/fraction"
    },
    "post_repair_verified_pct": {
      "$ref": "#/$defs/pct"
    },
    "device_read_share_pct": {
      "$ref": "#/$defs/pct"
    },
    "counterparty_meter_share_pct": {
      "$ref": "#/$defs/pct"
    },
    "asset_record_completeness_pct": {
      "$ref": "#/$defs/pct"
    },
    "install_dates_estimated_pct": {
      "$ref": "#/$defs/pct"
    },
    "register_filing_divergences": {
      "$ref": "#/$defs/count"
    },
    "savings_liters": {
      "type": "number"
    },
    "savings_ngn": {
      "type": "number"
    },
    "savings_ngn_status": {
      "$ref": "#/$defs/refusal"
    },
    "savings_baseline_id": {
      "type": "string"
    },
    "baseline_established_on": {
      "$ref": "#/$defs/date"
    },
    "savings_baseline_coefficients": {
      "type": "object",
      "additionalProperties": {
        "type": "number"
      }
    },
    "savings_measurement_confidence": {
      "$ref": "#/$defs/fraction"
    },
    "soh_pct": {
      "$ref": "#/$defs/pct"
    },
    "soh_pct_low": {
      "$ref": "#/$defs/pct"
    },
    "soh_pct_high": {
      "$ref": "#/$defs/pct"
    },
    "soh_method": {
      "type": "string"
    },
    "eol_criterion": {
      "type": "string"
    },
    "rul_days_low": {
      "$ref": "#/$defs/count"
    },
    "rul_days_mid": {
      "$ref": "#/$defs/count"
    },
    "rul_days_high": {
      "$ref": "#/$defs/count"
    },
    "rul_basis": {
      "type": "string",
      "enum": [
        "observed_replacements",
        "projected"
      ]
    },
    "method_version": {
      "type": "string"
    },
    "content_sha256": {
      "$ref": "#/$defs/sha256",
      "description": "Digest of this report over the canonical form; the only field, with the generation timestamp, outside the hashed payload"
    },
    "previous_sha256": {
      "oneOf": [
        {
          "$ref": "#/$defs/sha256"
        },
        {
          "type": "null"
        }
      ],
      "description": "Digest of the preceding report in the chain, inside the hashed payload; null for the first report"
    },
    "scorecard": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/scorecardDimension"
      }
    },
    "composite_weights": {
      "type": "object",
      "additionalProperties": {
        "$ref": "#/$defs/fraction"
      }
    },
    "population_size": {
      "$ref": "#/$defs/count"
    },
    "identity_divergence_pct": {
      "type": "number",
      "minimum": 0,
      "description": "C3 divergence, per cent of metered E_pv; may exceed 100; undefined where metered E_pv is zero"
    },
    "acknowledgement_minutes_p50_by_class": {
      "$ref": "#/$defs/byClassMinutes"
    },
    "acknowledgement_minutes_p90_by_class": {
      "$ref": "#/$defs/byClassMinutes"
    },
    "intervention_minutes_p50_by_class": {
      "$ref": "#/$defs/byClassMinutes"
    },
    "intervention_minutes_p90_by_class": {
      "$ref": "#/$defs/byClassMinutes"
    },
    "response_minutes_p50_by_class": {
      "$ref": "#/$defs/byClassMinutes"
    },
    "response_minutes_p90_by_class": {
      "$ref": "#/$defs/byClassMinutes"
    },
    "resolution_minutes_p50_by_class": {
      "$ref": "#/$defs/byClassMinutes"
    },
    "resolution_minutes_p90_by_class": {
      "$ref": "#/$defs/byClassMinutes"
    },
    "resolution_verified_minutes_p50_by_class": {
      "$ref": "#/$defs/byClassMinutes"
    },
    "resolution_verified_minutes_p90_by_class": {
      "$ref": "#/$defs/byClassMinutes"
    },
    "closures_by_class": {
      "$ref": "#/$defs/byClassCount"
    },
    "closures_by_detection_source": {
      "type": "object",
      "description": "Closures in the period by the source of their detection timestamp (Clause 13.4); undetected closures are in closures_undetected, not here",
      "properties": {
        "alarm": {
          "$ref": "#/$defs/count"
        },
        "interval": {
          "$ref": "#/$defs/count"
        },
        "customer": {
          "$ref": "#/$defs/count"
        },
        "owner": {
          "$ref": "#/$defs/count"
        },
        "staff": {
          "$ref": "#/$defs/count"
        }
      },
      "additionalProperties": false
    },
    "closures_undetected": {
      "$ref": "#/$defs/count"
    },
    "open_faults_by_class": {
      "$ref": "#/$defs/byClassCount"
    },
    "open_fault_age_days_by_class": {
      "type": "object",
      "additionalProperties": {
        "type": "array",
        "items": {
          "$ref": "#/$defs/count"
        }
      }
    },
    "overdue_faults_by_class": {
      "$ref": "#/$defs/byClassCount"
    },
    "remote_interventions": {
      "$ref": "#/$defs/count"
    },
    "clause_status": {
      "type": "array",
      "minItems": 13,
      "items": {
        "$ref": "#/$defs/clauseStatus"
      }
    },
    "hash_profile": {
      "type": "string",
      "minLength": 1,
      "default": "rfc8785",
      "description": "Canonicalisation profile: rfc8785, or the published identifier of an alternative profile that fixes key order, whitespace, string escaping and number serialisation. Inside the hashed payload."
    }
  },
  "patternProperties": {
    "^(?!clause_status$)[a-z0-9_]+_status$": {
      "anyOf": [
        {
          "$ref": "#/$defs/refusal"
        },
        {
          "$ref": "#/$defs/byClassRefusal"
        }
      ],
      "description": "Refusal for the field, or a map of fault class to refusal for a by-class field; a refused class is absent from the field itself"
    },
    "^[a-z0-9_]+_n$": {
      "anyOf": [
        {
          "$ref": "#/$defs/count"
        },
        {
          "$ref": "#/$defs/byClassCount"
        }
      ],
      "description": "Trusted records behind the field, or a map of fault class to count for a by-class field"
    }
  },
  "required": [
    "period_start",
    "period_end",
    "service_model",
    "method_version",
    "hash_profile",
    "content_sha256",
    "clause_status",
    "previous_sha256"
  ]
}
