{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://boston-energy-atlas.org/schema/common.schema.json",
  "title": "Boston Energy Atlas — shared definitions",
  "description": "Vocabulary shared by all four entry types. Every controlled list in the atlas is defined here once and referenced, so that adding a value is a deliberate, reviewable act rather than a typo.",
  "$defs": {
    "id": {
      "type": "string",
      "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",
      "description": "Stable kebab-case identifier, unique within its entry type. Never reused, never renamed once published; a superseded entry is marked superseded and keeps its id."
    },
    "era": {
      "type": "string",
      "enum": [
        "lighting",
        "traction",
        "appliance",
        "cooling",
        "gas-heat",
        "colocation",
        "distributed-energy",
        "heat-pump",
        "compute"
      ],
      "description": "The load era this entry evidences. See eras.json for each era's definition and dates. An entry may carry more than one; an entry that evidences none carries an empty list rather than a guess."
    },
    "eras": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/era"
      },
      "uniqueItems": true
    },
    "tier": {
      "type": "string",
      "enum": [
        "primary",
        "secondary",
        "unverified",
        "modeled"
      ],
      "description": "primary — every load-bearing value read from the primary instrument or the issuing body's own file. secondary — taken from a reliable account of the primary source, primary not opened. unverified — asserted somewhere but not corroborated; must never be presented as fact. modeled — computed or estimated here, with the method published; not an observation. 'primary' was called 'verified' until 30 Sep 2026; it means the primary source was opened and read, not that the figure was audited."
    },
    "grain": {
      "type": "string",
      "enum": [
        "region",
        "state",
        "county",
        "municipality",
        "neighbourhood",
        "zip",
        "tract",
        "feeder",
        "substation",
        "building",
        "point",
        "block-group",
        "parcel",
        "segment",
        "district",
        "precinct",
        "zone",
        "none"
      ],
      "description": "The unit at which the source itself reports. Declared, never inferred, and never silently changed. An atlas user who joins across grains must be able to see that they are doing so; the crosswalks in /atlas/crosswalks carry the method and the error characteristic for each join. Added 30 Sep 2026 after the accuracy check: block-group, parcel, segment (a line such as a street or a transmission span), district and precinct (electoral), zone (an area drawn by analysis or by a company: a walkshed, a service or rate territory)."
    },
    "geometry_type": {
      "type": "string",
      "enum": [
        "Point",
        "LineString",
        "MultiLineString",
        "Polygon",
        "MultiPolygon",
        "none"
      ]
    },
    "rights": {
      "type": "object",
      "description": "What may be done with this material, stated per entry. The atlas is coursework: it displays and cites third-party material under academic use, and claims a licence only over what it creates itself.",
      "required": [
        "holder",
        "redistributable"
      ],
      "properties": {
        "holder": {
          "type": "string",
          "description": "Who holds the rights. 'Public domain (US Government)' and 'Created by this project' are both valid values."
        },
        "licence": {
          "type": "string",
          "description": "The licence as the holder states it, verbatim where possible. 'Not stated' is an honest and common value; do not infer one."
        },
        "redistributable": {
          "type": "string",
          "enum": [
            "yes",
            "no",
            "unclear",
            "created-here"
          ],
          "description": "yes — may be republished with attribution. no — cite and link only. unclear — the holder publishes no terms; treated as 'no' in practice and flagged for a permission request. created-here — this project made it and licenses it CC BY 4.0."
        },
        "attribution": {
          "type": "string"
        },
        "url": {
          "type": "string",
          "format": "uri"
        },
        "notes": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "source": {
      "type": "object",
      "description": "One citable source. A number that cannot name one of these does not go in the atlas.",
      "required": [
        "id",
        "title"
      ],
      "properties": {
        "id": {
          "type": "string",
          "description": "Short handle used to cite this source from a field, e.g. 'celt-2026'."
        },
        "title": {
          "type": "string"
        },
        "publisher": {
          "type": "string"
        },
        "date": {
          "type": "string",
          "description": "Publication or vintage date, ISO 8601 where known, free text where the source is itself vague."
        },
        "url": {
          "type": "string"
        },
        "locator": {
          "type": "string",
          "description": "Page, table, tab, section or docket item. The thing that lets a reader land on the number, not just the document."
        },
        "accessed": {
          "type": "string",
          "format": "date"
        },
        "tier": {
          "$ref": "#/$defs/tier"
        },
        "note": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "provenance": {
      "type": "object",
      "description": "How this material got here. Point Luna's contribution to the method, adopted wholesale: pull date, what was retrieved, what cleaning changed, and what is known to be wrong with it.",
      "required": [
        "retrieved",
        "method"
      ],
      "properties": {
        "retrieved": {
          "type": "string",
          "format": "date",
          "description": "The date the material was pulled, not the date the source published it."
        },
        "method": {
          "type": "string",
          "description": "How it was obtained, specifically enough to be repeated."
        },
        "script": {
          "type": "string",
          "description": "Path to the script that performs the pull and clean, where one exists."
        },
        "script_note": {
          "type": "string",
          "description": "Where no script exists, or only part of the step is scripted, say so here rather than naming a file that is not there. An honest gap in reproducibility is better than a citation that does not resolve."
        },
        "cleaning": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Every transformation between the source file and the published file. A user who disagrees with one must be able to find it here."
        },
        "refresh": {
          "type": "string",
          "description": "How often the source changes: 'annual', 'continuous', 'static', 'superseded-in-place', 'none — historical'."
        },
        "stale_after": {
          "type": "string",
          "description": "The date after which this entry should be treated as out of date unless refreshed."
        }
      },
      "additionalProperties": false
    },
    "limitation": {
      "type": "string",
      "description": "A known, specific defect in the material — not a disclaimer. 'Resolution is the feeder, so no address-level claim is possible' is a limitation; 'data may contain errors' is not."
    },
    "status": {
      "type": "string",
      "enum": [
        "published",
        "draft",
        "superseded",
        "withdrawn"
      ],
      "default": "published"
    },
    "entry_ref": {
      "type": "string",
      "pattern": "^(dataset|decision|site|gap):[a-z0-9]+(-[a-z0-9]+)*$",
      "description": "A reference to another entry, as type:id. The atlas is relational; this is the edge."
    }
  }
}
