openapi: 3.0.3
info:
  title: Repzo API - AI Object Detection Mission Results
  version: 2.0.0
  description: |
    **Mission results** — the STORED outcome of mission executions: exactly
    ONE document per (session, mission), written by the metric evaluator and
    kept in step by every metric-result flag/override/confirm. Re-analyzing
    a session RE-POINTS its documents at the new analysis instead of adding
    rows — a session can never appear twice for the same mission, and the
    review state survives re-analysis.

    Each document carries: the `analysis` it currently reflects, the
    denormalized scan context (`user`/`user_name` = the scanning rep,
    `client`/`client_name`, `teams`, the scan's visit context `visit_id` /
    `route` / `business_day`), `time` (analysis time ≈ the visit),
    `scanned` (the session was STARTED FROM this mission), the outcome
    (`score` = the weighted EFFECTIVE metric scores with confirmed human
    overrides folded in, `min_score`, `completed` — a zero score never
    completes), `via_sets` (assignment snapshot at evaluation time), and the
    REVIEW WORKFLOW: `flagged` (DERIVED — any of the mission's metric
    results carries a flag; a fresh flag REOPENS a resolved document) and
    `resolved` (the admin's verdict, the only writable field here; resolving
    stamps `resolver` + `resolved_at`).

    Assignment is snapshotted AT EVALUATION TIME — assignment-rule edits
    affect future evaluations, not recorded history. The per-client "what's
    due now" read lives in `/ai-object-detection-assigned-missions` (it
    counts completions from these documents, so a re-analyzed visit can
    never double-count).

    **Reads.** The list defaults to a 30-day window on `time` (`from_time` /
    `to_time`, Unix ms) sorted newest first (`time` desc, `_id` desc — the
    `sort` param is ignored). There is NO create (documents are written by
    the evaluator only — 400) and no `PATCH` (400).

    **Roles.** Admins see everything and may narrow with `?rep=` (list and
    single reads); a REP token is FORCED to its own results (`user` = the
    rep) and is read-only (`PUT`/`DELETE` → 403). Scoped by
    `company_namespace`; soft-deleted via `disabled`.
servers:
  - url: https://sv.api.repzo.me
security:
  - ApiKeyAuth: []
  - JwtAuth: []
paths:
  /ai-object-detection-mission-results:
    get:
      summary: The mission-results listing
      operationId: findAiObjectDetectionMissionResults
      parameters:
        - in: query
          name: from_time
          description: Window start on `time` (ms). Default = 30 days before to_time.
          schema: { type: number }
        - in: query
          name: to_time
          description: Window end (ms). Default = now.
          schema: { type: number }
        - in: query
          name: _id
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: client
          description: Restrict to one (or more) client.
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: session
          description: Restrict to one (or more) session.
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: mission
          description: Restrict to one (or more) mission.
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: analysis
          description: Restrict to documents currently pointing at this analysis.
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: user
          description: "The scanning rep's id. Overridden by `rep`; a rep token is always forced to itself."
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: rep
          description: "Restrict to one rep's results (admin only — a rep token is always forced to itself). Ignored unless a valid ObjectId; maps onto `user`."
          schema: { type: string }
        - in: query
          name: teams
          description: "The scanning rep's team id(s) at evaluation time — pass once or as `?teams[]=`."
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: visit_id
          description: Restrict to scans made in one visit (device `visits.visit_id`).
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: route
          description: Restrict to scans made on one route (`sv.routes` `_id`).
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: flagged
          description: Only results whose mission metrics carry a flag (or none).
          schema: { type: boolean }
        - in: query
          name: resolved
          description: Filter by the admin review verdict.
          schema: { type: boolean }
        - in: query
          name: completed
          description: Only completed (or only missed) missions.
          schema: { type: boolean }
        - in: query
          name: scanned
          description: 'Only sessions started FROM their mission ("SCAN THIS MISSION").'
          schema: { type: boolean }
        - in: query
          name: disabled
          description: "Include soft-deleted documents (`true`) or only live ones (`false`). Omitted = no filter."
          schema: { type: boolean }
        - in: query
          name: from_createdAt
          schema: { type: number }
        - in: query
          name: to_createdAt
          schema: { type: number }
        - in: query
          name: per_page
          schema: { type: integer, minimum: 1 }
        - in: query
          name: page
          schema: { type: integer, minimum: 1 }
      responses:
        "200":
          description: Paginated mission-result documents, newest first.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MissionResultFindResult"
    post:
      summary: Not supported
      description: "Always 400 — mission results are written by the metric evaluator (`POST /ai-object-detection-metric-result` recalculates them)."
      operationId: createAiObjectDetectionMissionResult
      x-scalar-ignore: true
      responses:
        "400":
          description: "`Object Detection Mission Results: written by the metric evaluator — create is not allowed`."
  /ai-object-detection-mission-results/{id}:
    get:
      summary: Get one mission result
      operationId: getAiObjectDetectionMissionResult
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
        - in: query
          name: rep
          description: "Admin only — the document must belong to this rep (`user`). A rep token is forced to itself."
          schema: { type: string }
      responses:
        "200":
          description: The mission-result document.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MissionResult"
        "400":
          description: Not found (or not owned by the rep).
    put:
      summary: Review verdict — resolved only
      description: |
        ADMIN ONLY. The single writable field is `resolved` — everything
        else (score, completed, flagged, context) is derived from the metric
        results and the evaluator. Resolving stamps `resolver` +
        `resolved_at`; un-resolving clears both; a FRESH metric-result flag
        automatically reopens the document (resolved flips back to false).
      operationId: updateAiObjectDetectionMissionResult
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [resolved]
              properties:
                resolved: { type: boolean }
      responses:
        "200":
          description: The updated document.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MissionResult"
        "400":
          description: "`resolved` missing / not a boolean, or not found."
        "403":
          description: Rep tokens may not resolve.
    delete:
      summary: Soft-delete a mission result (admin)
      operationId: removeAiObjectDetectionMissionResult
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: The soft-deleted document.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MissionResult"
        "400":
          description: Not found.
        "403":
          description: Rep tokens may not remove.
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: api-key
    JwtAuth:
      type: apiKey
      in: header
      name: Authorization
  schemas:
    UserStamp:
      type: object
      description: "A user stamp from the token (`resolver`, `creator`, `editor`)."
      properties:
        _id: { type: string }
        name: { type: string }
        type:
          type: string
          enum: [admin, rep, client, tenant]
        admin: { type: string }
        rep: { type: string }
        client: { type: string }
        tenant: { type: string }
    MissionResult:
      type: object
      properties:
        _id: { type: string }
        session: { type: string }
        mission: { type: string }
        analysis:
          type: string
          description: The analysis this result CURRENTLY reflects (the latest evaluated one — re-analysis re-points it).
        user:
          type: string
          nullable: true
          description: The SCANNING rep (unset for admin-scanned sessions).
        user_name: { type: string, nullable: true }
        client: { type: string, nullable: true }
        client_name: { type: string, nullable: true }
        teams:
          type: array
          items: { type: string }
        visit_id:
          type: string
          description: Device visit id of the visit the scan happened in (copied from the session; absent on scans made without a visit).
        route:
          type: string
          description: "`sv.routes` `_id` of the visit's route."
        business_day:
          type: string
          description: "Business day of the scan, `YYYY-MM-DD` (copied from the session)."
        time:
          type: number
          description: Analysis time (ms) ≈ the visit.
        scanned:
          type: boolean
          description: 'The session was started FROM this mission ("SCAN THIS MISSION").'
        mission_name: { type: string }
        score:
          type: number
          description: Weighted EFFECTIVE metric scores 0..1 (confirmed human overrides folded in; a demanded metric without a result counts 0).
        min_score:
          type: number
          description: Mission threshold snapshot (default 0).
        completed:
          type: boolean
          description: score reached min_score (a ZERO score never completes).
        via_sets:
          type: array
          items: { type: string }
          description: Mission-set names that assigned it (snapshot at evaluation time).
        flagged:
          type: boolean
          description: DERIVED — any of the mission's metric results on `analysis` carries a flag.
        resolved:
          type: boolean
          description: Admin review verdict; auto-reopens (false) when a fresh flag arrives.
        resolver:
          nullable: true
          allOf:
            - $ref: "#/components/schemas/UserStamp"
        resolved_at:
          type: number
          nullable: true
          description: Unix ms; null when un-resolved.
        creator:
          $ref: "#/components/schemas/UserStamp"
        editor:
          $ref: "#/components/schemas/UserStamp"
        disabled: { type: boolean }
        company_namespace:
          type: array
          items: { type: string }
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }
    MissionResultFindResult:
      type: object
      description: Standard paginated result envelope.
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/MissionResult"
        total_result: { type: number }
        current_count: { type: number }
        total_pages: { type: number }
        current_page: { type: number }
        per_page: { type: number }
        first_page_url: { type: string }
        last_page_url: { type: string }
        next_page_url: { type: string, nullable: true }
        prev_page_url: { type: string, nullable: true }
        path: { type: string }
