{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://devcannabis.com/static/spec/adapter.schema.json",
  "$comment": "License: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/) — DevCannabis Normalization Spec. Free to use/implement/cite with attribution.",
  "title": "DevCannabis ADAPTER export contract",
  "description": "The single top-level ADAPTER dict every jurisdiction adapter package must export from its __init__.py. Authoritative key sets come from app/tests/test_state_adapters.py (REQUIRED_*_KEYS). Extra keys are permitted (additionalProperties true).",
  "type": "object",
  "additionalProperties": true,
  "properties": {
    "code": {
      "type": "string",
      "description": "Short jurisdiction code. Must equal state_info.code and jurisdiction_profile.code; looked up case-insensitively (code.upper())."
    },
    "name": {
      "type": "string",
      "description": "Full jurisdiction name. Must equal state_info.name and jurisdiction_profile.name."
    },
    "status": {
      "type": "string",
      "enum": ["planned", "active"],
      "description": "Lifecycle. 'planned' = metadata resolves, Coming-Soon page, ingestion off, is_active False (template default). 'active' = live dashboard; MUST have config.refresh dict OR a @register_ingestor loader."
    },
    "state_info": {
      "type": "object",
      "additionalProperties": true,
      "description": "Regulator/program metadata. Contract-test minimum keys below; canonical_entity must be 'operator' where present (enforced by validate_state_data.py, not the contract test).",
      "properties": {
        "code": { "type": "string" },
        "name": { "type": "string" },
        "operator_term": { "type": "string", "description": "Local term for the licensed business (MMTC, Licensee, ATC, Dispensary...)." },
        "canonical_entity": { "type": "string", "enum": ["operator"] },
        "program_type": { "type": "string", "enum": ["medical", "adult_use", "both"] }
      },
      "required": ["code", "name", "operator_term"]
    },
    "jurisdiction_profile": {
      "type": "object",
      "additionalProperties": true,
      "description": "Serialized JurisdictionProfile frozen dataclass.",
      "properties": {
        "code": { "type": "string" },
        "name": { "type": "string" },
        "country_code": { "type": "string" },
        "jurisdiction_type": {
          "type": "string",
          "enum": ["country", "state", "province", "territory", "tribal", "county", "municipality", "region"]
        },
        "jurisdiction_level": { "type": "string" },
        "parent_jurisdiction": { "type": "string" },
        "subdivision_kind": { "type": "string" },
        "regulator": { "type": "string" },
        "regulator_short": { "type": "string" },
        "program": {},
        "market": {},
        "local_operator_term": { "type": "string" },
        "normalized_operator_term": { "type": "string", "const": "operator" }
      },
      "required": [
        "code", "name", "country_code", "jurisdiction_type", "jurisdiction_level",
        "parent_jurisdiction", "subdivision_kind", "regulator", "regulator_short",
        "program", "market", "local_operator_term", "normalized_operator_term"
      ]
    },
    "data_access_profile": {
      "type": "object",
      "additionalProperties": true,
      "description": "Serialized DataAccessProfile frozen dataclass.",
      "properties": {
        "access": { "type": "string" },
        "source_types": { "type": "array" },
        "update_frequency": {},
        "granularity": { "type": "array" },
        "legal_visibility": { "type": "string" },
        "licensing_visibility": { "type": "string" },
        "normalization_confidence": { "type": "string" },
        "consumer_usefulness": { "type": "string" },
        "caveats": { "type": "array" }
      },
      "required": [
        "access", "source_types", "update_frequency", "granularity",
        "legal_visibility", "licensing_visibility", "normalization_confidence",
        "consumer_usefulness", "caveats"
      ]
    },
    "program_model": {
      "type": "object",
      "additionalProperties": true,
      "description": "Serialized JurisdictionProgramModel frozen dataclass.",
      "properties": {
        "code": { "type": "string" },
        "model_version": {},
        "programs": { "type": "array" },
        "market_structure": {},
        "license_classes": { "type": "array" },
        "regulatory_features": { "type": "object" },
        "data_features": { "type": "object" },
        "policy_features": { "type": "object" },
        "source_features": { "type": "object" },
        "module_gates": {
          "type": "object",
          "description": "Map module_key -> {enabled: bool, reason: truthy str}. MUST include the five required gates.",
          "additionalProperties": {
            "type": "object",
            "properties": {
              "enabled": { "type": "boolean" },
              "reason": { "type": "string", "minLength": 1 }
            },
            "required": ["enabled", "reason"]
          },
          "required": ["timeline", "licensing_rounds", "claim_ledger", "jurisdiction_blueprint", "source_workbench"]
        },
        "temporal_axes": { "type": "array" },
        "change_monitoring": { "type": "object" },
        "caveats": { "type": "array" }
      },
      "required": [
        "code", "model_version", "programs", "market_structure", "license_classes",
        "regulatory_features", "data_features", "policy_features", "source_features",
        "module_gates", "temporal_axes", "change_monitoring", "caveats"
      ]
    },
    "research_profile": {
      "type": "object",
      "additionalProperties": true,
      "description": "Serialized ResearchProfile frozen dataclass. modules must include the five required module keys.",
      "properties": {
        "code": { "type": "string" },
        "profile_version": {},
        "terminology": {
          "type": "object",
          "properties": { "operator": { "type": "string", "minLength": 1 } },
          "required": ["operator"]
        },
        "modules": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "module_key": { "type": "string" },
              "label": { "type": "string" },
              "route": { "type": "string", "minLength": 1 },
              "mode": { "type": "string", "enum": ["reader", "research"] },
              "enabled": { "type": "boolean" }
            },
            "required": ["module_key", "label", "route", "mode", "enabled"]
          }
        },
        "timeline_eras": { "type": "array" },
        "licensing_rounds": { "type": "array" },
        "source_model": { "type": "object" },
        "source_collections": { "type": "array" },
        "legal_anchors": { "type": "array" },
        "event_why": { "type": "object" },
        "page_copy": { "type": "object" },
        "caveats": { "type": "array" }
      },
      "required": [
        "code", "profile_version", "terminology", "modules", "timeline_eras",
        "licensing_rounds", "source_model", "source_collections", "legal_anchors",
        "event_why", "page_copy", "caveats"
      ]
    },
    "capabilities": {
      "type": "object",
      "description": "Machine-readable feature/metric booleans. Contract test asserts only that this is a dict and capabilities.sales is a dict. The flag names below are the convention, not an enforced schema.",
      "properties": {
        "operator_metrics": { "type": "boolean" },
        "locations": { "type": "boolean" },
        "patient_counts": { "type": "boolean" },
        "physician_counts": { "type": "boolean" },
        "source_documents": { "type": "boolean" },
        "source_registry": { "type": "boolean" },
        "sales": {
          "type": "object",
          "properties": {
            "thc": { "type": "boolean" },
            "cbd": { "type": "boolean" },
            "flower": { "type": "boolean" }
          }
        },
        "legal": { "type": "object" },
        "reports": { "type": "object" }
      },
      "required": ["sales"],
      "additionalProperties": true
    },
    "config": {
      "type": "object",
      "additionalProperties": true,
      "properties": {
        "data_sources": {
          "type": "object",
          "description": "Official source map keyed by short slug; each entry {url, format, cadence, notes}. Every active_years entry must have a matching key.",
          "additionalProperties": {
            "type": "object",
            "properties": {
              "url": { "type": "string" },
              "format": {
                "type": "string",
                "description": "Fetcher dispatch key. The contract test does NOT validate this; it is convention, not an enforced enum. Observed values: csv, json, socrata, xlsx, tableau_csv, tableau_twbx, pdf, html, google_sheets, html_index_pdf."
              },
              "cadence": {},
              "notes": {}
            }
          }
        },
        "active_years": {
          "type": "array",
          "items": { "type": "integer" },
          "description": "Year-paged archives only; MUST be ascending-sorted; each year must have a data_sources entry. Rolling/monthly states leave []."
        },
        "refresh": {
          "description": "Either null (ingestion via a @register_ingestor loader) OR a WeeklyPdfRefreshProfile-shaped dict.",
          "oneOf": [
            { "type": "null" },
            {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "source_key": { "type": "string", "minLength": 1 },
                "source_slug": { "type": "string", "minLength": 1 },
                "report_type": { "type": "string", "const": "weekly_reports" },
                "source_profile": {},
                "integrity_source": {},
                "years_back": { "type": "integer", "minimum": 0 },
                "archive_filename_template": { "type": "string", "pattern": "\\{date\\}" }
              },
              "required": [
                "source_key", "source_slug", "report_type", "source_profile",
                "integrity_source", "years_back", "archive_filename_template"
              ]
            }
          ]
        }
      },
      "required": ["data_sources", "active_years", "refresh"]
    },
    "entities": {
      "type": "object",
      "additionalProperties": true,
      "properties": {
        "operators": {
          "type": "array",
          "description": "Canonical operator records. Only 'name' is required (unique, non-empty). Neutral field names only (mmtc_name/licensee_name/atc_name/mmtc_count banned).",
          "items": {
            "type": "object",
            "properties": {
              "name": { "type": "string", "minLength": 1 },
              "status": { "type": "string", "enum": ["active", "inactive", "acquired", "pending", "pre-operational"] }
            },
            "required": ["name"]
          }
        },
        "aliases": {
          "type": "object",
          "description": "Lowercase raw-name variant -> canonical operator name (which must exist in operators).",
          "additionalProperties": { "type": "string" }
        },
        "lineage": {
          "type": "array",
          "description": "Corporate-continuity records. Authoritative field shape per validator + Florida (NOT the stale template comment).",
          "items": {
            "type": "object",
            "properties": {
              "predecessor": { "type": "string" },
              "operator": { "type": "string", "description": "Successor/current entity; must exist in operators." },
              "change_type": {
                "type": "string",
                "enum": ["acquisition", "rebrand", "acquisition_rebrand", "merger", "spinoff", "license_transfer"]
              },
              "transition_date": {}
            },
            "required": ["predecessor", "operator", "change_type", "transition_date"]
          }
        },
        "junk_names": {
          "type": "array",
          "description": "Names/artifacts (table headers, totals) excluded from ingestion. Serialized from a Python set."
        }
      },
      "required": ["operators", "aliases", "lineage", "junk_names"]
    },
    "regulatory": {
      "type": "object",
      "additionalProperties": true,
      "description": "Regulatory sub-dict {license_types, timeline}. The key is required; internal shapes are not asserted by the contract test."
    }
  },
  "required": [
    "code", "name", "status", "state_info", "jurisdiction_profile",
    "data_access_profile", "program_model", "research_profile",
    "capabilities", "config", "entities", "regulatory"
  ]
}
