openapi: 3.1.0
info:
  title: Solar Gate Energy Data Quality API
  version: 2026-07-31
  description: >-
    Read-only quality, issue and lineage endpoints for normalized Solar Gate
    telemetry. Billing-grade eligibility is advisory in this version.
servers:
  - url: https://uqkbxztsnvaajqsecsrw.supabase.co/functions/v1/energy-data-quality-api
    description: Solar Gate Supabase Edge Function
security:
  - SolarGateApiKey: []
  - SolarGateBearer: []
paths:
  /v1/sites/{site_id}/data-quality:
    get:
      operationId: getSiteDataQuality
      summary: Calculate data quality for a site window
      parameters:
        - $ref: '#/components/parameters/SiteId'
        - $ref: '#/components/parameters/From'
        - $ref: '#/components/parameters/To'
      responses:
        '200':
          description: Site quality summary
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SiteQualityResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/InvalidRequest'
        '500':
          $ref: '#/components/responses/InternalError'
  /v1/sites/{site_id}/quality-issues:
    get:
      operationId: listSiteQualityIssues
      summary: List site data-quality issues
      parameters:
        - $ref: '#/components/parameters/SiteId'
        - name: status
          in: query
          schema:
            type: string
            enum: [open, acknowledged, resolved, ignored, all]
            default: open
        - name: severity
          in: query
          schema:
            type: string
            enum: [info, warning, critical]
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 100
      responses:
        '200':
          description: Quality issue list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/QualityIssueListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/InvalidRequest'
        '500':
          $ref: '#/components/responses/InternalError'
  /v1/points/{point_id}/lineage:
    get:
      operationId: getPointLineage
      summary: Read recent sample lineage for one point
      parameters:
        - name: point_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 100
      responses:
        '200':
          description: Point lineage
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PointLineageResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Point not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          $ref: '#/components/responses/InternalError'
components:
  securitySchemes:
    SolarGateApiKey:
      type: apiKey
      in: header
      name: X-Solar-Gate-Key
    SolarGateBearer:
      type: http
      scheme: bearer
  parameters:
    SiteId:
      name: site_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    From:
      name: from
      in: query
      schema:
        type: string
        format: date-time
    To:
      name: to
      in: query
      schema:
        type: string
        format: date-time
  responses:
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Forbidden:
      description: Scope, product entitlement or project grant denied
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    InvalidRequest:
      description: Invalid time range or filter
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    InternalError:
      description: The query could not be completed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  schemas:
    ErrorResponse:
      type: object
      required: [error, request_id]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
            message:
              type: string
        request_id:
          type: string
          format: uuid
    SiteQualityResponse:
      type: object
      required: [data, request_id]
      properties:
        data:
          $ref: '#/components/schemas/SiteQualitySummary'
        meta:
          type: object
          additionalProperties: true
        request_id:
          type: string
          format: uuid
    SiteQualitySummary:
      type: object
      required:
        - project_id
        - window
        - overall_score
        - dimensions
        - counts
        - flags
        - billing_grade_eligible
        - advisory_only
        - quality_contract_version
      properties:
        project_id:
          type: string
          format: uuid
        window:
          type: object
          required: [from, to]
          properties:
            from:
              type: string
              format: date-time
            to:
              type: string
              format: date-time
        overall_score:
          type: number
          minimum: 0
          maximum: 100
        dimensions:
          type: object
          required:
            - sample_quality_score
            - completeness_percent
            - freshness_percent
            - quality_evaluation_coverage_percent
          properties:
            sample_quality_score:
              type: number
              minimum: 0
              maximum: 100
            completeness_percent:
              type: number
              minimum: 0
              maximum: 100
            freshness_percent:
              type: number
              minimum: 0
              maximum: 100
            quality_evaluation_coverage_percent:
              type: number
              minimum: 0
              maximum: 100
        counts:
          type: object
          additionalProperties:
            type: number
        flags:
          type: object
          additionalProperties:
            type: integer
            minimum: 0
        latest_recorded_at:
          type: [string, 'null']
          format: date-time
        billing_grade_eligible:
          type: boolean
        advisory_only:
          type: boolean
          const: true
        quality_contract_version:
          type: string
          const: energy-quality-v1
    QualityIssueListResponse:
      type: object
      required: [data, meta, request_id]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/QualityIssue'
        meta:
          type: object
          properties:
            count:
              type: integer
        request_id:
          type: string
          format: uuid
    QualityIssue:
      type: object
      required:
        - id
        - issue_type
        - severity
        - status
        - title
        - first_seen_at
        - last_seen_at
        - occurrence_count
      properties:
        id:
          type: string
          format: uuid
        issue_type:
          type: string
        severity:
          type: string
          enum: [info, warning, critical]
        status:
          type: string
          enum: [open, acknowledged, resolved, ignored]
        title:
          type: string
        connection_id:
          type: string
          format: uuid
        device_id:
          type: [string, 'null']
          format: uuid
        point_id:
          type: [string, 'null']
          format: uuid
        first_seen_at:
          type: string
          format: date-time
        last_seen_at:
          type: string
          format: date-time
        occurrence_count:
          type: integer
          minimum: 1
        metadata:
          type: object
          additionalProperties: true
    PointLineageResponse:
      type: object
      required: [data, meta, request_id]
      properties:
        data:
          type: object
          required: [point_id, data, count]
          properties:
            point_id:
              type: string
              format: uuid
            data:
              type: array
              items:
                $ref: '#/components/schemas/LineageRecord'
            count:
              type: integer
        meta:
          type: object
          additionalProperties: true
        request_id:
          type: string
          format: uuid
    LineageRecord:
      type: object
      required: [sample_id, recorded_at, received_at, value, source_quality_status]
      properties:
        sample_id:
          type: string
          format: uuid
        recorded_at:
          type: string
          format: date-time
        received_at:
          type: string
          format: date-time
        value:
          type: object
          additionalProperties: true
        source_quality_status:
          type: string
        quality_score:
          type: [integer, 'null']
          minimum: 0
          maximum: 100
        quality_flags:
          type: array
          items:
            type: string
        latency_seconds:
          type: [number, 'null']
        interval_gap_seconds:
          type: [number, 'null']
        source_payload_hash:
          type: [string, 'null']
        normalized_payload_hash:
          type: [string, 'null']
        lineage_hash:
          type: [string, 'null']
        normalization_version:
          type: [string, 'null']
