{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://aiseoperth.net.au/data/observation-schema.json",
  "title": "AI SEO Perth observation record",
  "description": "One record per prompt run on one platform at one point in time. This is the unit of the Perth AI search observation programme. Methodology: https://aiseoperth.net.au/methodology/",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "observation_id",
    "research_project",
    "prompt",
    "query_class",
    "buyer_stage",
    "industry",
    "location",
    "platform",
    "model_or_surface",
    "observation_date",
    "observation_time",
    "timezone",
    "run_number",
    "session_profile",
    "brands_mentioned",
    "brands_recommended",
    "brand_order",
    "citations_present",
    "cited_urls",
    "cited_domains",
    "source_classes",
    "methodology_version",
    "evidence_type"
  ],
  "properties": {
    "observation_id": {
      "type": "string",
      "pattern": "^[a-z0-9]+(-[a-z0-9]+)*-[0-9]{8}-r[0-9]{2}$",
      "description": "Stable identifier: <project>-<platform>-<prompt-id>-<YYYYMMDD>-r<run>. Example: chatgpt-perth-chatgpt-p001-20261001-r01."
    },
    "research_project": {
      "type": "string",
      "description": "Slug of the research project the observation belongs to (matches the research collection entry)."
    },
    "prompt": {
      "type": "string",
      "minLength": 1,
      "description": "The exact prompt text submitted, unedited."
    },
    "prompt_id": {
      "type": "string",
      "description": "Identifier of the prompt within the project's fixed prompt set, so repeated runs of the same prompt can be grouped."
    },
    "query_class": {
      "type": "string",
      "enum": [
        "informational",
        "commercial",
        "comparison",
        "recommendation",
        "local",
        "brand",
        "competitor",
        "trust-validation"
      ]
    },
    "buyer_stage": {
      "type": "string",
      "enum": [
        "awareness",
        "consideration",
        "decision",
        "retention",
        "not-applicable"
      ]
    },
    "industry": {
      "type": "string",
      "description": "Industry slug from the observatory list (dentists, lawyers, mortgage-brokers, accountants, plumbers, electricians, builders, solar, real-estate, seo-agencies) or 'general'."
    },
    "location": {
      "type": "string",
      "description": "Geographic context named in the prompt or the session, e.g. 'Perth, Western Australia'."
    },
    "platform": {
      "type": "string",
      "enum": [
        "chatgpt",
        "google-ai-overviews",
        "google-ai-mode",
        "gemini",
        "perplexity",
        "claude",
        "copilot"
      ]
    },
    "model_or_surface": {
      "type": "string",
      "description": "The model or surface as the platform labelled it at the time, e.g. 'GPT-5 (web, search on)', 'AI Overview on google.com.au'."
    },
    "observation_date": {
      "type": "string",
      "format": "date"
    },
    "observation_time": {
      "type": "string",
      "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$",
      "description": "Local time of the run, HH:MM."
    },
    "timezone": {
      "type": "string",
      "default": "Australia/Perth"
    },
    "run_number": {
      "type": "integer",
      "minimum": 1,
      "description": "Sequence of this repeat within the observation round. Repeated runs are how session variance is measured."
    },
    "session_profile": {
      "type": "string",
      "enum": [
        "clean-baseline",
        "persona-a",
        "persona-b",
        "logged-out",
        "api"
      ],
      "description": "Which controlled session the run used. Divergence between profiles on the same prompt is itself a measurement."
    },
    "brands_mentioned": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Every business or brand named anywhere in the response."
    },
    "brands_recommended": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Brands the response presented as a recommendation or shortlist entry, a subset of brands_mentioned."
    },
    "brand_order": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "brands_recommended in the order the response listed them."
    },
    "citations_present": {
      "type": "boolean"
    },
    "cited_urls": {
      "type": "array",
      "items": {
        "type": "string",
        "format": "uri"
      }
    },
    "cited_domains": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Registrable domains of cited_urls, deduplicated, lower case."
    },
    "source_classes": {
      "type": "array",
      "description": "Classification of each cited domain.",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "domain",
          "class"
        ],
        "properties": {
          "domain": {
            "type": "string"
          },
          "class": {
            "type": "string",
            "enum": [
              "owned",
              "third-party",
              "platform-owned",
              "ugc",
              "directory",
              "publisher",
              "brand-source"
            ]
          }
        }
      }
    },
    "response_notes": {
      "type": "string",
      "description": "Free-text notes on the response: format, hedging, refusals, non-Perth results."
    },
    "region_or_session_notes": {
      "type": "string",
      "description": "Anything about location handling, personalisation or logged-in state that could affect the run."
    },
    "source_capture": {
      "type": "string",
      "description": "Path or URL of the screenshot or raw capture for this run."
    },
    "methodology_version": {
      "type": "string",
      "description": "Methodology version the run was collected under, e.g. '1.0'."
    },
    "evidence_type": {
      "type": "string",
      "const": "observed"
    }
  }
}