openapi: 3.1.0
info:
  title: Popcast Change API
  version: 0.1.0
  description: |
    Change layer of The Conscience Stack. Per-block-group physical change
    velocity: building permits, business open/close events, real-estate
    aggregates. Sources: Atlanta/NYC/LA/Chicago/SF Socrata portals (real),
    state SoS business registries (per-state stub), real-estate aggregate
    (synthetic placeholder).
  contact:
    name: Motionworks AI
    url: https://docs.mworks.com

servers:
  - url: https://api.mworks.com

tags:
  - name: change
    description: Block group change signal endpoints

paths:
  /v2/popcast/change/{fips}:
    get:
      operationId: getChangeSignal
      summary: Current change signal for a block group
      description: Returns rolling-window permit + business + real-estate rollup.
      tags: [change]
      x-motionworks-status: roadmap
      x-motionworks-data-maturity: synthetic-only
      x-credit-cost: 1
      parameters:
        - $ref: '#/components/parameters/FipsParam'
        - name: window
          in: query
          schema: { type: integer, enum: [30, 90, 180, 365], default: 90 }
      responses:
        '200':
          description: Change signal
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ChangeSignalResponse' }
        '404': { $ref: '#/components/responses/NotFound' }
        '400': { $ref: '#/components/responses/ValidationError' }

  /v2/popcast/change/{fips}/history:
    get:
      operationId: getChangeHistory
      summary: Historical monthly change snapshots
      tags: [change]
      x-motionworks-status: roadmap
      x-motionworks-data-maturity: synthetic-only
      x-credit-cost: 3
      parameters:
        - $ref: '#/components/parameters/FipsParam'
        - { name: from, in: query, schema: { type: string, format: date } }
        - { name: to, in: query, schema: { type: string, format: date } }
        - { name: cursor, in: query, schema: { type: string } }
        - { name: limit, in: query, schema: { type: integer, maximum: 24 } }
      responses:
        '200':
          description: Change history
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ChangeHistoryResponse' }

  /v2/popcast/change/batch:
    post:
      operationId: batchChangeSignals
      summary: Batch change signal lookup
      description: Up to 100 FIPS per call.
      tags: [change]
      x-motionworks-status: roadmap
      x-motionworks-data-maturity: synthetic-only
      x-credit-cost: 100
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ChangeBatchRequest' }
      responses:
        '200':
          description: Batch results
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ChangeBatchResponse' }

components:
  parameters:
    FipsParam:
      name: fips
      in: path
      required: true
      schema: { type: string, pattern: '^\d{12}$' }

  schemas:
    PermitEvent:
      type: object
      properties:
        id: { type: string }
        permit_type: { type: string, enum: [new-construction, addition, renovation, demolition, signage, electrical, plumbing, mechanical, other] }
        description: { type: string }
        issue_date: { type: string }
        estimated_value_usd: { type: number }
        latitude: { type: number }
        longitude: { type: number }
        address: { type: string }
        source: { type: string }

    BusinessEvent:
      type: object
      properties:
        id: { type: string }
        change_type: { type: string, enum: [new-open, closed, renovation, ownership-transfer, rebranding] }
        business_name: { type: string }
        effective_date: { type: string }
        source: { type: string }

    RealEstateSnapshot:
      type: object
      properties:
        active_listings: { type: integer }
        median_list_price_usd: { type: number, nullable: true }
        median_days_on_market: { type: number, nullable: true }
        price_trend_30d_pct: { type: number, nullable: true }
        new_listings_window: { type: integer }
        sold_window: { type: integer }
        source: { type: string }

    ChangePayload:
      type: object
      properties:
        permits:
          type: object
          properties:
            total: { type: integer }
            total_value_usd: { type: number }
            by_type: { type: object, additionalProperties: { type: integer } }
            recent:
              type: array
              items: { $ref: '#/components/schemas/PermitEvent' }
        business_changes:
          type: object
          properties:
            total: { type: integer }
            by_type: { type: object, additionalProperties: { type: integer } }
            recent:
              type: array
              items: { $ref: '#/components/schemas/BusinessEvent' }
        real_estate: { $ref: '#/components/schemas/RealEstateSnapshot' }
        change_velocity: { type: number, minimum: 0, maximum: 100 }

    ChangeSignal:
      type: object
      required: [fips, signal_layer, period_end, window_days, refreshed_at, payload]
      properties:
        fips: { type: string }
        signal_layer: { type: string, enum: [change] }
        period_end: { type: string }
        window_days: { type: integer, enum: [30, 90, 180, 365] }
        refreshed_at: { type: string, format: date-time }
        data_quality: { type: string, enum: [high, medium, low] }
        source_breakdown: { type: object, additionalProperties: { type: integer } }
        payload: { $ref: '#/components/schemas/ChangePayload' }

    Meta:
      type: object
      properties:
        request_id: { type: string }
        credits_used: { type: integer }
        credits_remaining: { type: integer }
        product: { type: string }
        version: { type: string }
        provenance:
          type: object
          properties:
            source: { type: string }
            data_maturity: { type: string }

    ChangeSignalResponse:
      type: object
      properties:
        data: { $ref: '#/components/schemas/ChangeSignal' }
        meta: { $ref: '#/components/schemas/Meta' }

    ChangeHistorySnapshot:
      type: object
      properties:
        fips: { type: string }
        period_start: { type: string }
        period_end: { type: string }
        window_days: { type: integer }
        permit_count: { type: integer }
        business_event_count: { type: integer }
        change_velocity: { type: number }
        data_quality: { type: string }

    ChangeHistoryResponse:
      type: object
      properties:
        data:
          type: array
          items: { $ref: '#/components/schemas/ChangeHistorySnapshot' }
        pagination:
          type: object
          properties:
            cursor: { type: string, nullable: true }
            has_more: { type: boolean }
            total: { type: integer }
        meta: { $ref: '#/components/schemas/Meta' }

    ChangeBatchRequest:
      type: object
      required: [fips_codes]
      properties:
        fips_codes:
          type: array
          maxItems: 100
          items: { type: string, pattern: '^\d{12}$' }
        window: { type: integer, enum: [30, 90, 180, 365] }

    ChangeBatchResponse:
      type: object
      properties:
        data:
          type: array
          items: { $ref: '#/components/schemas/ChangeSignal' }
        meta: { $ref: '#/components/schemas/Meta' }

    ApiError:
      type: object
      properties:
        error:
          type: object
          properties:
            code: { type: string }
            message: { type: string }
            status: { type: integer }
            request_id: { type: string }

  responses:
    NotFound:
      description: Not found
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ApiError' }
    ValidationError:
      description: Validation error
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ApiError' }
