openapi: 3.1.0
info:
  title: Popcast Signal API
  version: 0.1.0
  description: |
    Block group social signal layer. Returns rolling word clouds, sentiment
    splits, and topic clusters keyed to US Census block group FIPS codes.

    Signal is aggregated from geo-tagged public posts (Reddit, Yelp, Google
    Places) via NLP topic extraction and sentiment classification. Refreshed
    on a daily cadence with 7, 14, and 30-day rolling windows.

    **Signal type distinction**
    - `at-place`: posts originating within the block group boundary.
      Represents mindstate of people physically present — the audience
      seeing OOH inventory in that geography.
    - `residential`: posts from users whose home location is inferred to
      be the block group. Represents mindstate of the resident audience.

    Consumers must distinguish signal type. Viewcast viewshed enrichment
    uses `at-place`. Popcast audience profiling uses `residential`.

    **Data sources (planned)**
    - Reddit API (subreddit-to-block-group crosswalk)
    - Yelp Fusion API (place lat/long → point-in-polygon FIPS attribution)
    - Google Places reviews (place lat/long → FIPS attribution)

    All endpoints are roadmap status (TF-96).
  contact:
    name: Motionworks AI
    url: https://docs.mworks.com
servers:
  - url: https://api.mworks.com
    description: Production
tags:
  - name: signal
    description: Block group social signal endpoints
paths: {}
components:
  schemas:
    WordCloudTerm:
      type: object
      required:
        - term
        - frequency
        - sentiment
        - source
      properties:
        term:
          type: string
          description: Extracted topic term or phrase
          example: traffic
        frequency:
          type: number
          format: float
          minimum: 0
          maximum: 100
          description: Normalized frequency score within the window (0–100)
        sentiment:
          type: string
          enum:
            - positive
            - neutral
            - negative
        source:
          type: string
          enum:
            - reddit
            - yelp
            - google_places
            - combined
          description: Data source this term was primarily extracted from
    TopTopic:
      type: object
      required:
        - topic
        - post_count
        - sentiment
      properties:
        topic:
          type: string
          example: commute_frustration
        post_count:
          type: integer
        sentiment:
          type: string
          enum:
            - positive
            - neutral
            - negative
    Sentiment:
      type: object
      required:
        - positive_pct
        - neutral_pct
        - negative_pct
      properties:
        positive_pct:
          type: number
          format: float
          minimum: 0
          maximum: 100
        neutral_pct:
          type: number
          format: float
          minimum: 0
          maximum: 100
        negative_pct:
          type: number
          format: float
          minimum: 0
          maximum: 100
    BlockGroupSignal:
      type: object
      required:
        - fips
        - signal_type
        - window_days
        - refreshed_at
        - post_count
        - sentiment
        - word_cloud
        - top_topics
        - data_quality
      properties:
        fips:
          type: string
          pattern: ^\d{12}$
          description: 12-digit US Census block group FIPS code
          example: '130890030001'
        signal_type:
          type: string
          enum:
            - at-place
            - residential
            - combined
          description: Attribution method applied to the returned signal
        window_days:
          type: integer
          enum:
            - 7
            - 14
            - 30
        refreshed_at:
          type: string
          format: date-time
          description: UTC timestamp of last ingestion refresh
        post_count:
          type: integer
          description: Total posts ingested in the window for this block group
        sentiment:
          $ref: '#/components/schemas/Sentiment'
        word_cloud:
          type: array
          items:
            $ref: '#/components/schemas/WordCloudTerm'
          maxItems: 100
        top_topics:
          type: array
          items:
            $ref: '#/components/schemas/TopTopic'
          maxItems: 10
        data_quality:
          type: string
          enum:
            - high
            - medium
            - low
          default: medium
          description: |
            Confidence band for this signal, parallel to Placecast's
            `is_focused`. Derived from post_count: >=50 = high,
            10-49 = medium, <10 = low. Consumers should down-weight
            'low' signals in aggregate analysis.
    SignalResponse:
      type: object
      required:
        - data
        - meta
      properties:
        data:
          $ref: '#/components/schemas/BlockGroupSignal'
        meta:
          $ref: '#/components/schemas/ResponseMeta'
    SignalHistorySnapshot:
      type: object
      required:
        - period_start
        - period_end
        - fips
        - signal_type
        - post_count
        - sentiment
        - word_cloud
      properties:
        period_start:
          type: string
          format: date
        period_end:
          type: string
          format: date
        fips:
          type: string
          pattern: ^\d{12}$
        signal_type:
          type: string
          enum:
            - at-place
            - residential
            - combined
        post_count:
          type: integer
        sentiment:
          $ref: '#/components/schemas/Sentiment'
        word_cloud:
          type: array
          items:
            $ref: '#/components/schemas/WordCloudTerm'
          maxItems: 50
    SignalHistoryResponse:
      type: object
      required:
        - data
        - pagination
        - meta
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/SignalHistorySnapshot'
        pagination:
          type: object
          properties:
            cursor:
              type: string
              nullable: true
            has_more:
              type: boolean
        meta:
          $ref: '#/components/schemas/ResponseMeta'
    SignalBatchRequest:
      type: object
      required:
        - fips_codes
      properties:
        fips_codes:
          type: array
          items:
            type: string
            pattern: ^\d{12}$
          minItems: 1
          maxItems: 100
          description: Array of 12-digit block group FIPS codes
        window:
          type: integer
          enum:
            - 7
            - 14
            - 30
          default: 30
        signal_type:
          type: string
          enum:
            - at-place
            - residential
            - combined
          default: combined
        fields:
          type: array
          items:
            type: string
          description: Optional sparse fieldset — return only specified fields per result
    SignalBatchResponse:
      type: object
      required:
        - data
        - meta
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/BlockGroupSignal'
        meta:
          $ref: '#/components/schemas/ResponseMeta'
    ResponseMeta:
      type: object
      required:
        - request_id
        - credits_used
        - credits_remaining
        - product
        - version
        - provenance
      properties:
        request_id:
          type: string
        credits_used:
          type: integer
        credits_remaining:
          type: integer
        product:
          type: string
          example: popcast.signal
        version:
          type: string
          example: 2.0.0
        provenance:
          type: object
          description: TF-93 provenance block
          properties:
            source:
              type: string
            source_doc:
              type: string
              format: uri
            methodology_version:
              type: string
            data_vintage:
              type: string
            data_freshness:
              type: string
            data_latency_days:
              type: integer
            data_maturity:
              type: string
              enum:
                - production
                - synthetic-only
                - roadmap
  responses:
    ValidationError:
      description: Validation error
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: object
                properties:
                  code:
                    type: string
                    example: VALIDATION_ERROR
                  message:
                    type: string
                  status:
                    type: integer
                    example: 400
                  request_id:
                    type: string
    NotFound:
      description: Block group not found or no signal available
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: object
                properties:
                  code:
                    type: string
                    example: NOT_FOUND
                  message:
                    type: string
                  status:
                    type: integer
                    example: 404
                  request_id:
                    type: string
    RoadmapNotAvailable:
      description: Roadmap endpoint — not yet available
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: object
                properties:
                  code:
                    type: string
                    example: ROADMAP_NOT_AVAILABLE
                  message:
                    type: string
                  roadmap_issue:
                    type: string
                    example: TF-96
                  status:
                    type: integer
                    example: 501
                  request_id:
                    type: string
