# =============================================================================
# B4: URT API Contract
# Universal Review Taxonomy v5.1
# =============================================================================
#
# Complete OpenAPI 3.0.3 specification for the URT classification and
# issue management API.
#
# Status: Production Ready
# Version: 1.0.0
# Date: 2026-01-23
# Depends On: B1-urt-codes.yaml, B2-database-schema.sql, B3-owner-routing.yaml
# =============================================================================

openapi: 3.0.3
info:
  title: Universal Review Taxonomy API
  description: |
    API for managing customer review classification using the Universal Review Taxonomy (URT) v5.1.

    ## Overview

    This API enables:
    - **Review Management**: CRUD operations for customer reviews
    - **Span Classification**: Classification of review text segments using URT codes
    - **Issue Lifecycle**: Full issue tracking per the C1 framework
    - **Code Registry**: Lookup and validation of URT taxonomy codes
    - **Analytics**: Domain/category rollups and time-series metrics
    - **Owner Routing**: Resolution of responsible teams per B3 routing matrix

    ## Implementation Profiles

    The API supports four implementation profiles with varying complexity:
    - **URT-Lite**: 7 domains, minimal metadata
    - **URT-Core**: 28 categories, basic metadata
    - **URT-Standard**: 140 subcodes, full metadata
    - **URT-Full**: 156 codes including causal analysis

    ## Rate Limiting

    All endpoints are rate limited. Check response headers for current limits:
    - `X-RateLimit-Limit`: Requests allowed per window
    - `X-RateLimit-Remaining`: Requests remaining in current window
    - `X-RateLimit-Reset`: Unix timestamp when window resets

  version: 1.0.0
  contact:
    name: URT API Support
    email: api-support@example.com
    url: https://docs.example.com/urt-api
  license:
    name: Proprietary
    url: https://example.com/license
  x-logo:
    url: https://example.com/logo.png

servers:
  - url: https://api.example.com/urt/v1
    description: Production server
  - url: https://api.staging.example.com/urt/v1
    description: Staging server
  - url: http://localhost:8080/urt/v1
    description: Local development

tags:
  - name: Reviews
    description: Customer review management
  - name: Spans
    description: Classified text segments
  - name: Issues
    description: Issue lifecycle management (C1 framework)
  - name: Codes
    description: URT code registry and validation
  - name: Analytics
    description: Aggregations and metrics
  - name: Routing
    description: Owner routing resolution
  - name: Health
    description: System health and status

# =============================================================================
# SECURITY SCHEMES
# =============================================================================

security:
  - BearerAuth: []
  - ApiKeyAuth: []

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        JWT Bearer token authentication. Obtain tokens via OAuth 2.0 flow.

        Example: `Authorization: Bearer <token>`

    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: |
        API key authentication for server-to-server communication.

        Example: `X-API-Key: your-api-key`

  # ===========================================================================
  # SCHEMAS
  # ===========================================================================
  schemas:

    # -------------------------------------------------------------------------
    # Core Entity Schemas
    # -------------------------------------------------------------------------

    Review:
      type: object
      required:
        - review_id
        - source
        - review_text
        - created_at
      properties:
        review_id:
          type: string
          format: uuid
          description: Unique review identifier
          example: "550e8400-e29b-41d4-a716-446655440000"
        external_id:
          type: string
          maxLength: 255
          description: ID from source system (e.g., Google place_id)
          example: "ChIJN1t_tDeuEmsRUsoyG83frY4"
        source:
          type: string
          maxLength: 50
          description: Review source platform
          enum: [google, yelp, tripadvisor, facebook, trustpilot, custom]
          example: "google"
        business_id:
          type: string
          maxLength: 255
          description: Business identifier
          example: "BUS-2026-0001"
        author_name:
          type: string
          maxLength: 255
          description: Review author display name
          example: "John D."
        author_id:
          type: string
          maxLength: 255
          description: Author identifier from source
        review_text:
          type: string
          description: Full review text content
          example: "Great food but the service was slow. Would recommend the pasta."
        star_rating:
          type: number
          format: float
          minimum: 1.0
          maximum: 5.0
          description: Star rating (1.0-5.0)
          example: 4.0
        review_date:
          type: string
          format: date
          description: Date review was posted
          example: "2026-01-15"
        language_code:
          type: string
          maxLength: 5
          description: ISO 639-1 language code
          example: "en"
        raw_metadata:
          type: object
          additionalProperties: true
          description: Source-specific metadata
        span_count:
          type: integer
          minimum: 0
          description: Number of classified spans
          readOnly: true
        created_at:
          type: string
          format: date-time
          readOnly: true
        updated_at:
          type: string
          format: date-time
          readOnly: true

    ReviewCreate:
      type: object
      required:
        - source
        - review_text
      properties:
        external_id:
          type: string
          maxLength: 255
        source:
          type: string
          enum: [google, yelp, tripadvisor, facebook, trustpilot, custom]
        business_id:
          type: string
          maxLength: 255
        author_name:
          type: string
          maxLength: 255
        author_id:
          type: string
          maxLength: 255
        review_text:
          type: string
          minLength: 1
        star_rating:
          type: number
          minimum: 1.0
          maximum: 5.0
        review_date:
          type: string
          format: date
        language_code:
          type: string
          maxLength: 5
        raw_metadata:
          type: object

    Span:
      type: object
      required:
        - span_id
        - review_id
        - span_text
        - profile
        - primary_code
        - valence
        - intensity
      properties:
        span_id:
          type: string
          format: uuid
          description: Unique span identifier
        review_id:
          type: string
          format: uuid
          description: Parent review ID
        span_text:
          type: string
          description: Classified text segment
          example: "the service was slow"
        char_start:
          type: integer
          minimum: 0
          description: Start position in review text
        char_end:
          type: integer
          minimum: 0
          description: End position in review text
        span_order:
          type: integer
          minimum: 1
          description: Order within review
          example: 2
        profile:
          $ref: '#/components/schemas/ImplementationProfile'
        primary_code:
          type: string
          description: Primary URT classification code
          example: "J1.02"
        primary_tier:
          type: integer
          enum: [1, 2, 3]
          description: "Code tier: 1=domain, 2=category, 3=subcode"
        secondary_codes:
          type: array
          maxItems: 2
          items:
            type: string
          description: Secondary URT codes (max 2)
          example: ["A1.04"]
        valence:
          $ref: '#/components/schemas/Valence'
        intensity:
          $ref: '#/components/schemas/Intensity'
        specificity:
          $ref: '#/components/schemas/Specificity'
        actionability:
          $ref: '#/components/schemas/Actionability'
        temporal:
          $ref: '#/components/schemas/Temporal'
        evidence:
          $ref: '#/components/schemas/Evidence'
        comparative:
          $ref: '#/components/schemas/Comparative'
        entity_reference:
          type: string
          maxLength: 255
          description: Referenced product, person, or feature
        location_reference:
          type: string
          maxLength: 255
          description: Physical or logical location reference
        causal_chain:
          $ref: '#/components/schemas/CausalChain'
        confidence_score:
          type: number
          format: float
          minimum: 0.0
          maximum: 1.0
          description: Classification confidence (0.0-1.0)
          example: 0.85
        annotator_notes:
          type: string
          description: Annotator comments
        annotation_source:
          type: string
          enum: [human, llm, hybrid, rule]
          description: Classification source
        created_at:
          type: string
          format: date-time
          readOnly: true
        updated_at:
          type: string
          format: date-time
          readOnly: true

    SpanCreate:
      type: object
      required:
        - review_id
        - span_text
        - primary_code
        - valence
        - intensity
      properties:
        review_id:
          type: string
          format: uuid
        span_text:
          type: string
          minLength: 1
        char_start:
          type: integer
          minimum: 0
        char_end:
          type: integer
          minimum: 0
        span_order:
          type: integer
          minimum: 1
          default: 1
        profile:
          $ref: '#/components/schemas/ImplementationProfile'
        primary_code:
          type: string
          pattern: '^[OPJEAVR]([1-4](\.[0-9]{2})?)?$'
        secondary_codes:
          type: array
          maxItems: 2
          items:
            type: string
        valence:
          $ref: '#/components/schemas/Valence'
        intensity:
          $ref: '#/components/schemas/Intensity'
        specificity:
          $ref: '#/components/schemas/Specificity'
        actionability:
          $ref: '#/components/schemas/Actionability'
        temporal:
          $ref: '#/components/schemas/Temporal'
        evidence:
          $ref: '#/components/schemas/Evidence'
        comparative:
          $ref: '#/components/schemas/Comparative'
        entity_reference:
          type: string
          maxLength: 255
        location_reference:
          type: string
          maxLength: 255
        causal_chain:
          $ref: '#/components/schemas/CausalChainCreate'
        confidence_score:
          type: number
          minimum: 0.0
          maximum: 1.0
        annotator_notes:
          type: string
        annotation_source:
          type: string
          enum: [human, llm, hybrid, rule]

    SpanClassification:
      type: object
      description: Individual code assignment within a span
      properties:
        classification_id:
          type: string
          format: uuid
        span_id:
          type: string
          format: uuid
        code:
          type: string
          example: "J1.02"
        code_tier:
          type: integer
          enum: [1, 2, 3]
        is_primary:
          type: boolean
        classification_order:
          type: integer
          minimum: 1
          maximum: 3

    # -------------------------------------------------------------------------
    # Issue Schemas (C1 Lifecycle)
    # -------------------------------------------------------------------------

    Issue:
      type: object
      required:
        - issue_id
        - primary_subcode
        - domain_code
        - state
        - span_count
        - max_intensity
        - priority_score
        - confidence_score
      properties:
        issue_id:
          type: string
          pattern: '^ISSUE-[0-9]{4}-[0-9]{4}$'
          description: Issue ID in format ISSUE-YYYY-NNNN
          example: "ISSUE-2026-0042"
        primary_subcode:
          type: string
          description: Primary URT subcode
          example: "J1.02"
        domain_code:
          type: string
          enum: [O, P, J, E, A, V, R]
          description: Experience domain
        entity_reference:
          type: string
          description: Aggregation entity
        location_reference:
          type: string
          description: Aggregation location
        state:
          $ref: '#/components/schemas/IssueState'
        state_changed_at:
          type: string
          format: date-time
        span_count:
          type: integer
          minimum: 1
          description: Number of contributing spans
        max_intensity:
          $ref: '#/components/schemas/Intensity'
        priority_score:
          type: number
          format: float
          description: Calculated priority score
          example: 8.75
        confidence_score:
          type: number
          format: float
          minimum: 0.0
          maximum: 1.0
          description: Issue confidence score
          example: 0.85
        owner_team:
          type: string
          description: Assigned team
          example: "operations"
        owner_individual:
          type: string
          description: Assigned individual
        resolution_code:
          type: string
          description: Resolution code if resolved
        resolution_notes:
          type: string
        decline_reason:
          $ref: '#/components/schemas/DeclineReason'
        causal_codes:
          type: array
          items:
            type: string
          description: Attributed causal codes
          example: ["CD-O", "MG-P"]
        reopen_count:
          type: integer
          minimum: 0
          description: Times issue was reopened
        verification_window_days:
          type: integer
          enum: [30, 60, 90]
          default: 60
        contributing_spans:
          type: array
          items:
            $ref: '#/components/schemas/SpanSummary'
          description: Spans contributing to this issue
        created_at:
          type: string
          format: date-time
        acknowledged_at:
          type: string
          format: date-time
        work_started_at:
          type: string
          format: date-time
        resolved_at:
          type: string
          format: date-time
        verified_at:
          type: string
          format: date-time
        declined_at:
          type: string
          format: date-time

    IssueCreate:
      type: object
      required:
        - primary_subcode
        - span_ids
      properties:
        primary_subcode:
          type: string
          pattern: '^[OPJEAVR][1-4]\.[0-9]{2}$'
        entity_reference:
          type: string
        location_reference:
          type: string
        span_ids:
          type: array
          minItems: 1
          items:
            type: string
            format: uuid
          description: Initial contributing span IDs
        owner_team:
          type: string
        owner_individual:
          type: string
        verification_window_days:
          type: integer
          enum: [30, 60, 90]
          default: 60

    IssueState:
      type: string
      enum:
        - DETECTED
        - ACKNOWLEDGED
        - IN_PROGRESS
        - RESOLVED
        - VERIFIED
        - DECLINED
        - REOPENED
        - STALE
      description: |
        Issue lifecycle state per C1 framework:
        - DETECTED: Initial detection from negative spans
        - ACKNOWLEDGED: Team has acknowledged the issue
        - IN_PROGRESS: Active work underway
        - RESOLVED: Fix implemented, awaiting verification
        - VERIFIED: Resolution confirmed via positive feedback
        - DECLINED: Issue declined with reason code
        - REOPENED: Issue recurred after resolution
        - STALE: Issue aged out without resolution

    IssueTransition:
      type: object
      required:
        - to_state
      properties:
        to_state:
          $ref: '#/components/schemas/IssueState'
        trigger_type:
          type: string
          enum: [manual, auto, span, sla]
          default: manual
        trigger_span_id:
          type: string
          format: uuid
          description: Span that triggered transition (for span trigger)
        notes:
          type: string
          description: Transition notes
        resolution:
          $ref: '#/components/schemas/IssueResolutionCreate'

    IssueResolution:
      type: object
      properties:
        resolution_id:
          type: string
          format: uuid
        issue_id:
          type: string
        resolution_code:
          type: string
          example: "FIX-2026-0015"
        resolution_type:
          type: string
          enum: [fix, workaround, policy_change, training, process_improvement, equipment, other]
        resolution_summary:
          type: string
          example: "Implemented queue management system to reduce wait times"
        resolution_details:
          type: string
        prevention_notes:
          type: string
        process_changes:
          type: string
        verified_by_span_id:
          type: string
          format: uuid
        verification_method:
          type: string
          enum: [cr_b, positive_span, time_based]
          description: How resolution was verified
        resolved_by:
          type: string
        resolved_at:
          type: string
          format: date-time
        verified_at:
          type: string
          format: date-time

    IssueResolutionCreate:
      type: object
      required:
        - resolution_type
        - resolution_summary
      properties:
        resolution_type:
          type: string
          enum: [fix, workaround, policy_change, training, process_improvement, equipment, other]
        resolution_summary:
          type: string
          minLength: 10
        resolution_details:
          type: string
        prevention_notes:
          type: string
        process_changes:
          type: string

    DeclineReason:
      type: string
      enum:
        - DEC-DUP
        - DEC-OOS
        - DEC-INS
        - DEC-NAR
        - DEC-EXT
        - DEC-POL
        - DEC-OLD
      description: |
        Issue decline reason codes:
        - DEC-DUP: Duplicate of existing issue
        - DEC-OOS: Out of scope
        - DEC-INS: Insufficient information
        - DEC-NAR: Not actionable/reproducible
        - DEC-EXT: External factor (beyond control)
        - DEC-POL: Policy decision (intentional)
        - DEC-OLD: Stale/outdated feedback

    IssueStateHistory:
      type: object
      properties:
        history_id:
          type: string
          format: uuid
        issue_id:
          type: string
        from_state:
          $ref: '#/components/schemas/IssueState'
        to_state:
          $ref: '#/components/schemas/IssueState'
        trigger_type:
          type: string
          enum: [manual, auto, span, sla]
        trigger_span_id:
          type: string
          format: uuid
        actor_id:
          type: string
        actor_type:
          type: string
          enum: [user, system, rule]
        notes:
          type: string
        transitioned_at:
          type: string
          format: date-time

    # -------------------------------------------------------------------------
    # URT Code Schemas
    # -------------------------------------------------------------------------

    URTDomain:
      type: object
      properties:
        domain_code:
          type: string
          enum: [O, P, J, E, A, V, R]
        name:
          type: string
          example: "Journey"
        description:
          type: string
        core_question:
          type: string
          example: "Is the experience smooth, timely, and friction-free?"
        default_owner:
          type: string
          example: "Operations / Process"
        categories:
          type: array
          items:
            $ref: '#/components/schemas/URTCategory'

    URTCategory:
      type: object
      properties:
        category_code:
          type: string
          pattern: '^[OPJEAVR][1-4]$'
          example: "J1"
        domain_code:
          type: string
        name:
          type: string
          example: "Timing"
        definition:
          type: string
          example: "Speed, punctuality, and time management"
        subcodes:
          type: array
          items:
            $ref: '#/components/schemas/URTSubcode'

    URTSubcode:
      type: object
      properties:
        subcode:
          type: string
          pattern: '^[OPJEAVR][1-4]\.[0-9]{2}$'
          example: "J1.02"
        category_code:
          type: string
        domain_code:
          type: string
        name:
          type: string
          example: "Service Speed"
        definition:
          type: string
          example: "Time for delivery/completion"
        positive_example:
          type: string
          example: "Next day delivery"
        negative_example:
          type: string
          example: "Took three weeks"
        dont_confuse_with:
          type: string
          example: "J4.03"
        dont_confuse_reason:
          type: string
          example: "J4.03 is resolution speed, J1.02 is service speed"

    URTCausalCode:
      type: object
      properties:
        causal_code:
          type: string
          pattern: '^(CD|MG|SY)-[A-Z]$'
          example: "CD-O"
        layer:
          type: string
          enum: [conditions, management, systemic]
        layer_prefix:
          type: string
          enum: ["CD-", "MG-", "SY-"]
        name:
          type: string
          example: "Operational"
        definition:
          type: string
          example: "Understaffing, demand surge, time pressure"

    CausalChain:
      type: object
      description: Root cause analysis chain (Full profile only)
      properties:
        chain_id:
          type: string
          format: uuid
        span_id:
          type: string
          format: uuid
        condition_code:
          type: string
          pattern: '^CD-[A-Z]$'
          description: Conditions layer code
          example: "CD-O"
        management_code:
          type: string
          pattern: '^MG-[A-Z]$'
          description: Management layer code
          example: "MG-P"
        systemic_code:
          type: string
          pattern: '^SY-[A-Z]$'
          description: Systemic layer code
          example: "SY-R"
        chain_confidence:
          type: number
          format: float
          minimum: 0.0
          maximum: 1.0
        analyst_notes:
          type: string

    CausalChainCreate:
      type: object
      properties:
        condition_code:
          type: string
          pattern: '^CD-[A-Z]$'
        management_code:
          type: string
          pattern: '^MG-[A-Z]$'
        systemic_code:
          type: string
          pattern: '^SY-[A-Z]$'
        chain_confidence:
          type: number
          minimum: 0.0
          maximum: 1.0
        analyst_notes:
          type: string

    CodeValidationRequest:
      type: object
      required:
        - primary_code
      properties:
        primary_code:
          type: string
        secondary_codes:
          type: array
          items:
            type: string
        profile:
          $ref: '#/components/schemas/ImplementationProfile'
        causal_chain:
          $ref: '#/components/schemas/CausalChainCreate'

    CodeValidationResult:
      type: object
      properties:
        valid:
          type: boolean
        errors:
          type: array
          items:
            type: object
            properties:
              code:
                type: string
              field:
                type: string
              message:
                type: string
        warnings:
          type: array
          items:
            type: object
            properties:
              code:
                type: string
              field:
                type: string
              message:
                type: string
        normalized:
          type: object
          description: Normalized code values if valid
          properties:
            primary_code:
              type: string
            primary_tier:
              type: integer
            secondary_codes:
              type: array
              items:
                type: string

    # -------------------------------------------------------------------------
    # Metadata Dimension Enums
    # -------------------------------------------------------------------------

    ImplementationProfile:
      type: string
      enum: [lite, core, standard, full]
      default: standard
      description: |
        Implementation profile determining available fields:
        - lite: 7 domains, valence only
        - core: 28 categories, valence + intensity
        - standard: 140 subcodes, all metadata
        - full: 156 codes including causal analysis

    Valence:
      type: string
      enum: ["V+", "V-", "V0", "V+-"]
      description: |
        Sentiment direction:
        - V+: Positive (praise, satisfaction)
        - V-: Negative (complaint, dissatisfaction)
        - V0: Neutral (observation)
        - V+-: Mixed (both positive and negative)
      example: "V-"

    Intensity:
      type: string
      enum: [I1, I2, I3]
      description: |
        Sentiment strength:
        - I1: Mild
        - I2: Moderate
        - I3: Strong/emphatic
      example: "I2"

    Specificity:
      type: string
      enum: [S1, S2, S3]
      description: |
        Level of detail:
        - S1: Vague (general impression)
        - S2: Moderate (some details)
        - S3: Specific (concrete details)

    Actionability:
      type: string
      enum: [A1, A2, A3]
      description: |
        How actionable the feedback is:
        - A1: Low (feeling only)
        - A2: Medium (suggests area)
        - A3: High (specific action)

    Temporal:
      type: string
      enum: [TC, TR, TH, TF]
      default: TC
      description: |
        Time frame reference:
        - TC: Current (this visit)
        - TR: Recent (recent pattern)
        - TH: Historical (long-standing)
        - TF: Future (expectations)

    Evidence:
      type: string
      enum: [ES, EI, EC]
      default: ES
      description: |
        Evidence type:
        - ES: Stated (explicitly said)
        - EI: Inferred (logically entailed)
        - EC: Contextual (requires context)

    Comparative:
      type: string
      enum: [CR-N, CR-B, CR-W, CR-S]
      default: CR-N
      description: |
        Comparison to previous state:
        - CR-N: None (no comparison)
        - CR-B: Better (improvement)
        - CR-W: Worse (decline)
        - CR-S: Same (unchanged)

    # -------------------------------------------------------------------------
    # Analytics Schemas
    # -------------------------------------------------------------------------

    DomainSummary:
      type: object
      properties:
        domain_code:
          type: string
        domain_name:
          type: string
        default_owner:
          type: string
        total_spans:
          type: integer
        positive_spans:
          type: integer
        negative_spans:
          type: integer
        neutral_spans:
          type: integer
        mixed_spans:
          type: integer
        critical_spans:
          type: integer
          description: Spans with intensity I3
        avg_confidence:
          type: number
          format: float
        open_issues:
          type: integer
        last_span_at:
          type: string
          format: date-time

    CategoryBreakdown:
      type: object
      properties:
        category_code:
          type: string
        category_name:
          type: string
        domain_code:
          type: string
        domain_name:
          type: string
        total_spans:
          type: integer
        negative_count:
          type: integer
        positive_count:
          type: integer
        negative_pct:
          type: number
          format: float
        total_issues:
          type: integer
        resolved_verified:
          type: integer
        avg_intensity:
          type: number
          format: float

    TrendDataPoint:
      type: object
      properties:
        period_start:
          type: string
          format: date
        period_end:
          type: string
          format: date
        domain_code:
          type: string
        category_code:
          type: string
        valence:
          type: string
        span_count:
          type: integer
        review_count:
          type: integer
        avg_intensity:
          type: number
          format: float
        cr_better_count:
          type: integer
        cr_worse_count:
          type: integer
        cr_same_count:
          type: integer

    AnalyticsSummaryResponse:
      type: object
      properties:
        generated_at:
          type: string
          format: date-time
        period:
          type: object
          properties:
            start:
              type: string
              format: date
            end:
              type: string
              format: date
        domains:
          type: array
          items:
            $ref: '#/components/schemas/DomainSummary'
        categories:
          type: array
          items:
            $ref: '#/components/schemas/CategoryBreakdown'
        totals:
          type: object
          properties:
            total_reviews:
              type: integer
            total_spans:
              type: integer
            total_issues:
              type: integer
            open_issues:
              type: integer
            avg_resolution_hours:
              type: number
              format: float

    TrendsResponse:
      type: object
      properties:
        generated_at:
          type: string
          format: date-time
        granularity:
          type: string
          enum: [day, week, month]
        data:
          type: array
          items:
            $ref: '#/components/schemas/TrendDataPoint'

    # -------------------------------------------------------------------------
    # Routing Schemas
    # -------------------------------------------------------------------------

    RoutingRequest:
      type: object
      required:
        - primary_code
        - valence
        - intensity
      properties:
        primary_code:
          type: string
        secondary_codes:
          type: array
          items:
            type: string
        valence:
          $ref: '#/components/schemas/Valence'
        intensity:
          $ref: '#/components/schemas/Intensity'
        causal_codes:
          type: array
          items:
            type: string

    RoutingResponse:
      type: object
      properties:
        primary_owner:
          type: object
          properties:
            team:
              type: string
              example: "operations"
            escalation:
              type: string
              example: "operations_director"
        co_owners:
          type: array
          items:
            type: object
            properties:
              team:
                type: string
              reason:
                type: string
        sla:
          type: object
          properties:
            priority:
              type: string
              enum: [critical, high, normal, low]
            initial_response_hours:
              type: integer
            resolution_hours:
              type: integer
            update_frequency_hours:
              type: integer
        auto_escalate:
          type: boolean
        notifications:
          type: array
          items:
            type: object
            properties:
              channel:
                type: string
                enum: [email, slack]
              target:
                type: string

    # -------------------------------------------------------------------------
    # Classification Request/Response
    # -------------------------------------------------------------------------

    ClassifyReviewRequest:
      type: object
      required:
        - profile
      properties:
        profile:
          $ref: '#/components/schemas/ImplementationProfile'
        options:
          type: object
          properties:
            split_spans:
              type: boolean
              default: true
              description: Automatically split review into spans
            include_causal:
              type: boolean
              default: false
              description: Include causal analysis (Full profile)
            confidence_threshold:
              type: number
              minimum: 0.0
              maximum: 1.0
              default: 0.7
              description: Minimum confidence for auto-classification

    ClassifyReviewResponse:
      type: object
      properties:
        review_id:
          type: string
          format: uuid
        status:
          type: string
          enum: [completed, partial, pending_review]
        spans:
          type: array
          items:
            $ref: '#/components/schemas/Span'
        issues_created:
          type: array
          items:
            type: string
          description: IDs of any issues created
        processing_time_ms:
          type: integer

    BatchClassifyRequest:
      type: object
      required:
        - spans
      properties:
        profile:
          $ref: '#/components/schemas/ImplementationProfile'
        spans:
          type: array
          minItems: 1
          maxItems: 100
          items:
            $ref: '#/components/schemas/SpanCreate'

    BatchClassifyResponse:
      type: object
      properties:
        total:
          type: integer
        successful:
          type: integer
        failed:
          type: integer
        results:
          type: array
          items:
            type: object
            properties:
              index:
                type: integer
              span:
                $ref: '#/components/schemas/Span'
              error:
                $ref: '#/components/schemas/Error'

    # -------------------------------------------------------------------------
    # Common Schemas
    # -------------------------------------------------------------------------

    SpanSummary:
      type: object
      properties:
        span_id:
          type: string
          format: uuid
        span_text:
          type: string
        primary_code:
          type: string
        valence:
          type: string
        intensity:
          type: string
        created_at:
          type: string
          format: date-time

    Pagination:
      type: object
      properties:
        cursor:
          type: string
          description: Cursor for next page
        has_more:
          type: boolean
        total:
          type: integer
          description: Total count (when available)
        limit:
          type: integer

    PaginatedResponse:
      type: object
      properties:
        data:
          type: array
          items: {}
        pagination:
          $ref: '#/components/schemas/Pagination'

    # -------------------------------------------------------------------------
    # Error Schemas
    # -------------------------------------------------------------------------

    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: string
          description: Machine-readable error code
        message:
          type: string
          description: Human-readable error message
        details:
          type: object
          additionalProperties: true
          description: Additional error context
        request_id:
          type: string
          description: Request ID for debugging

    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          $ref: '#/components/schemas/Error'

    ValidationError:
      type: object
      properties:
        code:
          type: string
          example: "VALIDATION_ERROR"
        message:
          type: string
          example: "Request validation failed"
        field_errors:
          type: array
          items:
            type: object
            properties:
              field:
                type: string
              message:
                type: string
              code:
                type: string

    HealthStatus:
      type: object
      properties:
        status:
          type: string
          enum: [healthy, degraded, unhealthy]
        version:
          type: string
          example: "1.0.0"
        timestamp:
          type: string
          format: date-time
        components:
          type: object
          properties:
            database:
              type: object
              properties:
                status:
                  type: string
                latency_ms:
                  type: integer
            cache:
              type: object
              properties:
                status:
                  type: string
                latency_ms:
                  type: integer
            classifier:
              type: object
              properties:
                status:
                  type: string
                model_version:
                  type: string

  # ===========================================================================
  # RESPONSE HEADERS
  # ===========================================================================
  headers:
    X-RateLimit-Limit:
      description: Maximum requests allowed per window
      schema:
        type: integer
      example: 1000
    X-RateLimit-Remaining:
      description: Requests remaining in current window
      schema:
        type: integer
      example: 998
    X-RateLimit-Reset:
      description: Unix timestamp when rate limit resets
      schema:
        type: integer
      example: 1706054400
    X-Request-Id:
      description: Unique request identifier for debugging
      schema:
        type: string
        format: uuid

  # ===========================================================================
  # COMMON PARAMETERS
  # ===========================================================================
  parameters:
    ReviewId:
      name: review_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
      description: Review UUID
    SpanId:
      name: span_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
      description: Span UUID
    IssueId:
      name: issue_id
      in: path
      required: true
      schema:
        type: string
        pattern: '^ISSUE-[0-9]{4}-[0-9]{4}$'
      description: Issue ID (format ISSUE-YYYY-NNNN)
    Cursor:
      name: cursor
      in: query
      schema:
        type: string
      description: Pagination cursor
    Limit:
      name: limit
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
      description: Results per page
    DomainFilter:
      name: domain
      in: query
      schema:
        type: string
        enum: [O, P, J, E, A, V, R]
      description: Filter by domain code
    ValenceFilter:
      name: valence
      in: query
      schema:
        type: string
        enum: ["V+", "V-", "V0", "V+-"]
      description: Filter by valence
    IntensityFilter:
      name: intensity
      in: query
      schema:
        type: string
        enum: [I1, I2, I3]
      description: Filter by intensity
    DateFrom:
      name: from
      in: query
      schema:
        type: string
        format: date
      description: Start date (inclusive)
    DateTo:
      name: to
      in: query
      schema:
        type: string
        format: date
      description: End date (inclusive)

  # ===========================================================================
  # COMMON RESPONSES
  # ===========================================================================
  responses:
    BadRequest:
      description: Bad request - validation error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ValidationError'
          example:
            code: "VALIDATION_ERROR"
            message: "Request validation failed"
            field_errors:
              - field: "primary_code"
                message: "Invalid URT code format"
                code: "INVALID_CODE_FORMAT"
    Unauthorized:
      description: Authentication required
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: "UNAUTHORIZED"
              message: "Authentication required"
    Forbidden:
      description: Insufficient permissions
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: "FORBIDDEN"
              message: "Insufficient permissions for this operation"
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: "NOT_FOUND"
              message: "Resource not found"
    Conflict:
      description: Conflict - invalid state transition
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: "INVALID_TRANSITION"
              message: "Cannot transition from VERIFIED to IN_PROGRESS"
    RateLimited:
      description: Rate limit exceeded
      headers:
        X-RateLimit-Limit:
          $ref: '#/components/headers/X-RateLimit-Limit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/X-RateLimit-Remaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/X-RateLimit-Reset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: "RATE_LIMITED"
              message: "Rate limit exceeded. Retry after reset."
    InternalError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: "INTERNAL_ERROR"
              message: "An unexpected error occurred"
              request_id: "req_abc123"

# =============================================================================
# PATHS
# =============================================================================

paths:

  # ---------------------------------------------------------------------------
  # REVIEWS
  # ---------------------------------------------------------------------------

  /reviews:
    get:
      tags: [Reviews]
      summary: List reviews
      description: Retrieve paginated list of reviews with optional filters
      operationId: listReviews
      parameters:
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/DateFrom'
        - $ref: '#/components/parameters/DateTo'
        - name: source
          in: query
          schema:
            type: string
          description: Filter by source platform
        - name: business_id
          in: query
          schema:
            type: string
          description: Filter by business ID
        - name: has_spans
          in: query
          schema:
            type: boolean
          description: Filter by classification status
      responses:
        '200':
          description: List of reviews
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Review'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
              example:
                data:
                  - review_id: "550e8400-e29b-41d4-a716-446655440000"
                    source: "google"
                    review_text: "Great food but slow service"
                    star_rating: 3.5
                    review_date: "2026-01-15"
                    span_count: 2
                pagination:
                  cursor: "eyJpZCI6MTAwfQ"
                  has_more: true
                  limit: 20
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'

    post:
      tags: [Reviews]
      summary: Create review
      description: Create a new review record
      operationId: createReview
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReviewCreate'
            example:
              source: "google"
              business_id: "BUS-2026-0001"
              author_name: "Jane D."
              review_text: "Excellent service, very professional staff. The wait time could be improved."
              star_rating: 4.0
              review_date: "2026-01-20"
              language_code: "en"
      responses:
        '201':
          description: Review created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Review'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'

  /reviews/{review_id}:
    get:
      tags: [Reviews]
      summary: Get review
      description: Retrieve a single review by ID
      operationId: getReview
      parameters:
        - $ref: '#/components/parameters/ReviewId'
        - name: include_spans
          in: query
          schema:
            type: boolean
            default: true
          description: Include classified spans
      responses:
        '200':
          description: Review details
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Review'
                  - type: object
                    properties:
                      spans:
                        type: array
                        items:
                          $ref: '#/components/schemas/Span'
        '404':
          $ref: '#/components/responses/NotFound'

    patch:
      tags: [Reviews]
      summary: Update review
      description: Update review metadata
      operationId: updateReview
      parameters:
        - $ref: '#/components/parameters/ReviewId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                business_id:
                  type: string
                star_rating:
                  type: number
                review_date:
                  type: string
                  format: date
                language_code:
                  type: string
                raw_metadata:
                  type: object
      responses:
        '200':
          description: Review updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Review'
        '404':
          $ref: '#/components/responses/NotFound'

    delete:
      tags: [Reviews]
      summary: Delete review
      description: Soft delete a review and its spans
      operationId: deleteReview
      parameters:
        - $ref: '#/components/parameters/ReviewId'
      responses:
        '204':
          description: Review deleted
        '404':
          $ref: '#/components/responses/NotFound'

  /reviews/{review_id}/classify:
    post:
      tags: [Reviews]
      summary: Classify review
      description: |
        Submit a review for automatic classification. The review will be split into
        spans and classified according to the specified profile.
      operationId: classifyReview
      parameters:
        - $ref: '#/components/parameters/ReviewId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ClassifyReviewRequest'
            example:
              profile: "standard"
              options:
                split_spans: true
                include_causal: false
                confidence_threshold: 0.7
      responses:
        '200':
          description: Classification complete
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ClassifyReviewResponse'
              example:
                review_id: "550e8400-e29b-41d4-a716-446655440000"
                status: "completed"
                spans:
                  - span_id: "660e8400-e29b-41d4-a716-446655440001"
                    span_text: "Great food"
                    primary_code: "O2.01"
                    valence: "V+"
                    intensity: "I2"
                  - span_id: "660e8400-e29b-41d4-a716-446655440002"
                    span_text: "slow service"
                    primary_code: "J1.02"
                    valence: "V-"
                    intensity: "I2"
                issues_created: []
                processing_time_ms: 342
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # SPANS
  # ---------------------------------------------------------------------------

  /spans:
    get:
      tags: [Spans]
      summary: List spans
      description: Retrieve paginated list of classified spans
      operationId: listSpans
      parameters:
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/DomainFilter'
        - $ref: '#/components/parameters/ValenceFilter'
        - $ref: '#/components/parameters/IntensityFilter'
        - $ref: '#/components/parameters/DateFrom'
        - $ref: '#/components/parameters/DateTo'
        - name: review_id
          in: query
          schema:
            type: string
            format: uuid
          description: Filter by review ID
        - name: primary_code
          in: query
          schema:
            type: string
          description: Filter by primary code (exact or prefix)
        - name: comparative
          in: query
          schema:
            type: string
            enum: [CR-N, CR-B, CR-W, CR-S]
          description: Filter by comparative reference
      responses:
        '200':
          description: List of spans
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Span'
                  pagination:
                    $ref: '#/components/schemas/Pagination'

    post:
      tags: [Spans]
      summary: Create span
      description: Create a new classified span
      operationId: createSpan
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SpanCreate'
            example:
              review_id: "550e8400-e29b-41d4-a716-446655440000"
              span_text: "the service was slow"
              char_start: 15
              char_end: 35
              span_order: 2
              profile: "standard"
              primary_code: "J1.02"
              secondary_codes: ["A1.04"]
              valence: "V-"
              intensity: "I2"
              specificity: "S2"
              actionability: "A2"
              temporal: "TC"
              evidence: "ES"
              comparative: "CR-N"
              confidence_score: 0.85
              annotation_source: "llm"
      responses:
        '201':
          description: Span created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Span'
        '400':
          $ref: '#/components/responses/BadRequest'

  /spans/{span_id}:
    get:
      tags: [Spans]
      summary: Get span
      description: Retrieve a single span by ID
      operationId: getSpan
      parameters:
        - $ref: '#/components/parameters/SpanId'
      responses:
        '200':
          description: Span details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Span'
        '404':
          $ref: '#/components/responses/NotFound'

    patch:
      tags: [Spans]
      summary: Update span
      description: Update span classification
      operationId: updateSpan
      parameters:
        - $ref: '#/components/parameters/SpanId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                primary_code:
                  type: string
                secondary_codes:
                  type: array
                  items:
                    type: string
                valence:
                  $ref: '#/components/schemas/Valence'
                intensity:
                  $ref: '#/components/schemas/Intensity'
                specificity:
                  $ref: '#/components/schemas/Specificity'
                actionability:
                  $ref: '#/components/schemas/Actionability'
                temporal:
                  $ref: '#/components/schemas/Temporal'
                evidence:
                  $ref: '#/components/schemas/Evidence'
                comparative:
                  $ref: '#/components/schemas/Comparative'
                annotator_notes:
                  type: string
      responses:
        '200':
          description: Span updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Span'
        '404':
          $ref: '#/components/responses/NotFound'

    delete:
      tags: [Spans]
      summary: Delete span
      description: Soft delete a span
      operationId: deleteSpan
      parameters:
        - $ref: '#/components/parameters/SpanId'
      responses:
        '204':
          description: Span deleted
        '404':
          $ref: '#/components/responses/NotFound'

  /spans/batch:
    post:
      tags: [Spans]
      summary: Batch create spans
      description: Create multiple spans in a single request (max 100)
      operationId: batchCreateSpans
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BatchClassifyRequest'
      responses:
        '200':
          description: Batch results
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchClassifyResponse'
              example:
                total: 5
                successful: 4
                failed: 1
                results:
                  - index: 0
                    span:
                      span_id: "770e8400-e29b-41d4-a716-446655440001"
                      primary_code: "J1.02"
                  - index: 1
                    error:
                      code: "INVALID_CODE"
                      message: "Invalid primary_code: X9.99"
        '400':
          $ref: '#/components/responses/BadRequest'

  # ---------------------------------------------------------------------------
  # ISSUES
  # ---------------------------------------------------------------------------

  /issues:
    get:
      tags: [Issues]
      summary: List issues
      description: Retrieve paginated list of issues
      operationId: listIssues
      parameters:
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/DomainFilter'
        - $ref: '#/components/parameters/DateFrom'
        - $ref: '#/components/parameters/DateTo'
        - name: state
          in: query
          schema:
            type: array
            items:
              $ref: '#/components/schemas/IssueState'
          style: form
          explode: true
          description: Filter by state(s)
        - name: owner_team
          in: query
          schema:
            type: string
          description: Filter by owner team
        - name: priority_min
          in: query
          schema:
            type: number
          description: Minimum priority score
        - name: sort
          in: query
          schema:
            type: string
            enum: [priority_desc, created_desc, created_asc]
            default: priority_desc
          description: Sort order
      responses:
        '200':
          description: List of issues
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Issue'
                  pagination:
                    $ref: '#/components/schemas/Pagination'

    post:
      tags: [Issues]
      summary: Create issue
      description: Manually create an issue from spans
      operationId: createIssue
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IssueCreate'
            example:
              primary_subcode: "J1.02"
              entity_reference: "Downtown location"
              span_ids:
                - "660e8400-e29b-41d4-a716-446655440001"
                - "660e8400-e29b-41d4-a716-446655440002"
              owner_team: "operations"
      responses:
        '201':
          description: Issue created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Issue'
        '400':
          $ref: '#/components/responses/BadRequest'

  /issues/{issue_id}:
    get:
      tags: [Issues]
      summary: Get issue
      description: Retrieve issue details including history
      operationId: getIssue
      parameters:
        - $ref: '#/components/parameters/IssueId'
        - name: include_history
          in: query
          schema:
            type: boolean
            default: false
          description: Include state transition history
        - name: include_spans
          in: query
          schema:
            type: boolean
            default: true
          description: Include contributing spans
      responses:
        '200':
          description: Issue details
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Issue'
                  - type: object
                    properties:
                      state_history:
                        type: array
                        items:
                          $ref: '#/components/schemas/IssueStateHistory'
                      resolution:
                        $ref: '#/components/schemas/IssueResolution'
        '404':
          $ref: '#/components/responses/NotFound'

    patch:
      tags: [Issues]
      summary: Update issue
      description: Update issue metadata (not state - use transition endpoint)
      operationId: updateIssue
      parameters:
        - $ref: '#/components/parameters/IssueId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                owner_team:
                  type: string
                owner_individual:
                  type: string
                entity_reference:
                  type: string
                location_reference:
                  type: string
                causal_codes:
                  type: array
                  items:
                    type: string
                verification_window_days:
                  type: integer
                  enum: [30, 60, 90]
      responses:
        '200':
          description: Issue updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Issue'
        '404':
          $ref: '#/components/responses/NotFound'

  /issues/{issue_id}/transition:
    post:
      tags: [Issues]
      summary: Transition issue state
      description: |
        Transition issue to a new state according to the C1 lifecycle state machine.

        Valid transitions:
        - DETECTED -> ACKNOWLEDGED, DECLINED
        - ACKNOWLEDGED -> IN_PROGRESS, DECLINED
        - IN_PROGRESS -> RESOLVED, DECLINED
        - RESOLVED -> VERIFIED, REOPENED
        - DECLINED -> (terminal)
        - VERIFIED -> REOPENED
        - REOPENED -> IN_PROGRESS, DECLINED
        - STALE -> ACKNOWLEDGED, DECLINED
      operationId: transitionIssue
      parameters:
        - $ref: '#/components/parameters/IssueId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IssueTransition'
            examples:
              acknowledge:
                summary: Acknowledge issue
                value:
                  to_state: "ACKNOWLEDGED"
                  notes: "Assigned to operations team for investigation"
              resolve:
                summary: Resolve with details
                value:
                  to_state: "RESOLVED"
                  notes: "Implemented queue management system"
                  resolution:
                    resolution_type: "process_improvement"
                    resolution_summary: "Added digital queue display and SMS notifications"
              decline:
                summary: Decline issue
                value:
                  to_state: "DECLINED"
                  notes: "External factor - weather related"
      responses:
        '200':
          description: Transition successful
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Issue'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'

  /issues/{issue_id}/spans:
    get:
      tags: [Issues]
      summary: Get issue spans
      description: List all spans contributing to an issue
      operationId: getIssueSpans
      parameters:
        - $ref: '#/components/parameters/IssueId'
      responses:
        '200':
          description: Contributing spans
          content:
            application/json:
              schema:
                type: object
                properties:
                  issue_id:
                    type: string
                  spans:
                    type: array
                    items:
                      $ref: '#/components/schemas/Span'

    post:
      tags: [Issues]
      summary: Link span to issue
      description: Add a span as a contributor to an existing issue
      operationId: linkSpanToIssue
      parameters:
        - $ref: '#/components/parameters/IssueId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - span_id
              properties:
                span_id:
                  type: string
                  format: uuid
                link_type:
                  type: string
                  enum: [contributor, verification, reopen_trigger]
                  default: contributor
      responses:
        '200':
          description: Span linked
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Issue'
        '404':
          $ref: '#/components/responses/NotFound'

  # ---------------------------------------------------------------------------
  # CODES
  # ---------------------------------------------------------------------------

  /codes:
    get:
      tags: [Codes]
      summary: List all codes
      description: Retrieve the full URT code registry
      operationId: listCodes
      parameters:
        - name: tier
          in: query
          schema:
            type: integer
            enum: [1, 2, 3]
          description: Filter by tier (1=domain, 2=category, 3=subcode)
        - name: domain
          in: query
          schema:
            type: string
            enum: [O, P, J, E, A, V, R]
          description: Filter by domain
        - name: include_causal
          in: query
          schema:
            type: boolean
            default: false
          description: Include causal codes
      responses:
        '200':
          description: Code registry
          content:
            application/json:
              schema:
                type: object
                properties:
                  version:
                    type: string
                    example: "5.1"
                  domains:
                    type: array
                    items:
                      $ref: '#/components/schemas/URTDomain'
                  causal_codes:
                    type: array
                    items:
                      $ref: '#/components/schemas/URTCausalCode'

  /codes/domains:
    get:
      tags: [Codes]
      summary: List domains
      description: Retrieve all 7 experience domains
      operationId: listDomains
      responses:
        '200':
          description: Domain list
          content:
            application/json:
              schema:
                type: object
                properties:
                  domains:
                    type: array
                    items:
                      $ref: '#/components/schemas/URTDomain'

  /codes/domains/{domain_code}:
    get:
      tags: [Codes]
      summary: Get domain
      description: Retrieve domain with its categories and subcodes
      operationId: getDomain
      parameters:
        - name: domain_code
          in: path
          required: true
          schema:
            type: string
            enum: [O, P, J, E, A, V, R]
      responses:
        '200':
          description: Domain details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/URTDomain'
        '404':
          $ref: '#/components/responses/NotFound'

  /codes/categories/{category_code}:
    get:
      tags: [Codes]
      summary: Get category
      description: Retrieve category with its subcodes
      operationId: getCategory
      parameters:
        - name: category_code
          in: path
          required: true
          schema:
            type: string
            pattern: '^[OPJEAVR][1-4]$'
      responses:
        '200':
          description: Category details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/URTCategory'
        '404':
          $ref: '#/components/responses/NotFound'

  /codes/subcodes/{subcode}:
    get:
      tags: [Codes]
      summary: Get subcode
      description: Retrieve subcode details with disambiguation guidance
      operationId: getSubcode
      parameters:
        - name: subcode
          in: path
          required: true
          schema:
            type: string
            pattern: '^[OPJEAVR][1-4]\.[0-9]{2}$'
      responses:
        '200':
          description: Subcode details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/URTSubcode'
        '404':
          $ref: '#/components/responses/NotFound'

  /codes/causal:
    get:
      tags: [Codes]
      summary: List causal codes
      description: Retrieve all 16 causal codes across 3 layers
      operationId: listCausalCodes
      parameters:
        - name: layer
          in: query
          schema:
            type: string
            enum: [conditions, management, systemic]
          description: Filter by layer
      responses:
        '200':
          description: Causal code list
          content:
            application/json:
              schema:
                type: object
                properties:
                  causal_codes:
                    type: array
                    items:
                      $ref: '#/components/schemas/URTCausalCode'

  /codes/validate:
    post:
      tags: [Codes]
      summary: Validate code combination
      description: |
        Validate that a code combination is valid for the specified profile.
        Returns normalized codes and any warnings.
      operationId: validateCodes
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CodeValidationRequest'
            example:
              primary_code: "J1.02"
              secondary_codes: ["A1.04"]
              profile: "standard"
      responses:
        '200':
          description: Validation result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CodeValidationResult'
              examples:
                valid:
                  summary: Valid combination
                  value:
                    valid: true
                    errors: []
                    warnings: []
                    normalized:
                      primary_code: "J1.02"
                      primary_tier: 3
                      secondary_codes: ["A1.04"]
                invalid:
                  summary: Invalid code
                  value:
                    valid: false
                    errors:
                      - code: "INVALID_CODE"
                        field: "primary_code"
                        message: "Code X9.99 does not exist"
                    warnings: []

  # ---------------------------------------------------------------------------
  # ANALYTICS
  # ---------------------------------------------------------------------------

  /analytics/summary:
    get:
      tags: [Analytics]
      summary: Get analytics summary
      description: Retrieve aggregated metrics by domain and category
      operationId: getAnalyticsSummary
      parameters:
        - $ref: '#/components/parameters/DateFrom'
        - $ref: '#/components/parameters/DateTo'
        - name: business_id
          in: query
          schema:
            type: string
          description: Filter by business
      responses:
        '200':
          description: Analytics summary
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AnalyticsSummaryResponse'
              example:
                generated_at: "2026-01-23T10:30:00Z"
                period:
                  start: "2026-01-01"
                  end: "2026-01-23"
                domains:
                  - domain_code: "J"
                    domain_name: "Journey"
                    total_spans: 145
                    negative_spans: 98
                    positive_spans: 42
                    open_issues: 12
                totals:
                  total_reviews: 523
                  total_spans: 847
                  total_issues: 45
                  open_issues: 28
                  avg_resolution_hours: 36.5

  /analytics/trends:
    get:
      tags: [Analytics]
      summary: Get trend data
      description: Retrieve time-series metrics for trend analysis
      operationId: getAnalyticsTrends
      parameters:
        - $ref: '#/components/parameters/DateFrom'
        - $ref: '#/components/parameters/DateTo'
        - $ref: '#/components/parameters/DomainFilter'
        - name: granularity
          in: query
          schema:
            type: string
            enum: [day, week, month]
            default: week
          description: Time period granularity
        - name: metrics
          in: query
          schema:
            type: array
            items:
              type: string
              enum: [span_count, issue_count, avg_intensity, cr_signals]
          style: form
          explode: true
          description: Metrics to include
      responses:
        '200':
          description: Trend data
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TrendsResponse'

  # ---------------------------------------------------------------------------
  # ROUTING
  # ---------------------------------------------------------------------------

  /routing/resolve:
    post:
      tags: [Routing]
      summary: Resolve owner routing
      description: |
        Determine the responsible team and SLA for a classification based on
        the B3 owner routing matrix.
      operationId: resolveRouting
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RoutingRequest'
            example:
              primary_code: "E4.01"
              secondary_codes: []
              valence: "V-"
              intensity: "I3"
      responses:
        '200':
          description: Routing resolution
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RoutingResponse'
              example:
                primary_owner:
                  team: "safety"
                  escalation: "safety_officer"
                co_owners: []
                sla:
                  priority: "critical"
                  initial_response_hours: 1
                  resolution_hours: 4
                  update_frequency_hours: 1
                auto_escalate: true
                notifications:
                  - channel: "slack"
                    target: "#safety"
                  - channel: "email"
                    target: "safety@example.com"

  # ---------------------------------------------------------------------------
  # HEALTH
  # ---------------------------------------------------------------------------

  /health:
    get:
      tags: [Health]
      summary: Health check
      description: Check API and component health status
      operationId: healthCheck
      security: []
      responses:
        '200':
          description: System healthy
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthStatus'
              example:
                status: "healthy"
                version: "1.0.0"
                timestamp: "2026-01-23T10:30:00Z"
                components:
                  database:
                    status: "healthy"
                    latency_ms: 5
                  cache:
                    status: "healthy"
                    latency_ms: 1
                  classifier:
                    status: "healthy"
                    model_version: "urt-classifier-v5.1"
        '503':
          description: System unhealthy
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthStatus'
              example:
                status: "unhealthy"
                version: "1.0.0"
                timestamp: "2026-01-23T10:30:00Z"
                components:
                  database:
                    status: "unhealthy"
                    latency_ms: null
                  cache:
                    status: "healthy"
                    latency_ms: 1

# =============================================================================
# WEBHOOKS
# =============================================================================

webhooks:
  issueStateChanged:
    post:
      summary: Issue state changed
      description: |
        Triggered when an issue transitions to a new state.

        Subscribe via the webhook configuration endpoint (not shown).
      operationId: onIssueStateChanged
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                event_type:
                  type: string
                  enum: [issue.state_changed]
                event_id:
                  type: string
                  format: uuid
                timestamp:
                  type: string
                  format: date-time
                data:
                  type: object
                  properties:
                    issue_id:
                      type: string
                    from_state:
                      $ref: '#/components/schemas/IssueState'
                    to_state:
                      $ref: '#/components/schemas/IssueState'
                    trigger_type:
                      type: string
                    actor_id:
                      type: string
                    issue:
                      $ref: '#/components/schemas/Issue'
            example:
              event_type: "issue.state_changed"
              event_id: "evt_abc123"
              timestamp: "2026-01-23T10:30:00Z"
              data:
                issue_id: "ISSUE-2026-0042"
                from_state: "IN_PROGRESS"
                to_state: "RESOLVED"
                trigger_type: "manual"
                actor_id: "user_123"
                issue:
                  issue_id: "ISSUE-2026-0042"
                  state: "RESOLVED"
                  primary_subcode: "J1.02"
      responses:
        '200':
          description: Webhook received

  issueCreated:
    post:
      summary: Issue created
      description: Triggered when a new issue is detected or created
      operationId: onIssueCreated
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                event_type:
                  type: string
                  enum: [issue.created]
                event_id:
                  type: string
                  format: uuid
                timestamp:
                  type: string
                  format: date-time
                data:
                  type: object
                  properties:
                    issue:
                      $ref: '#/components/schemas/Issue'
                    contributing_spans:
                      type: array
                      items:
                        $ref: '#/components/schemas/SpanSummary'
      responses:
        '200':
          description: Webhook received

  spanClassified:
    post:
      summary: Span classified
      description: Triggered when a new span is classified with negative valence
      operationId: onSpanClassified
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                event_type:
                  type: string
                  enum: [span.classified]
                event_id:
                  type: string
                  format: uuid
                timestamp:
                  type: string
                  format: date-time
                data:
                  type: object
                  properties:
                    span:
                      $ref: '#/components/schemas/Span'
                    review:
                      $ref: '#/components/schemas/Review'
                    routing:
                      $ref: '#/components/schemas/RoutingResponse'
      responses:
        '200':
          description: Webhook received

  slaBreach:
    post:
      summary: SLA breach warning
      description: Triggered when an issue approaches or exceeds SLA thresholds
      operationId: onSlaBreach
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                event_type:
                  type: string
                  enum: [sla.warning, sla.breach, sla.critical]
                event_id:
                  type: string
                  format: uuid
                timestamp:
                  type: string
                  format: date-time
                data:
                  type: object
                  properties:
                    issue_id:
                      type: string
                    issue:
                      $ref: '#/components/schemas/Issue'
                    sla_type:
                      type: string
                      enum: [initial_response, resolution, update]
                    threshold_percent:
                      type: integer
                    hours_elapsed:
                      type: number
                    hours_allowed:
                      type: number
      responses:
        '200':
          description: Webhook received

# =============================================================================
# END OF SPECIFICATION
# =============================================================================
