openapi: 3.0.3
info:
  title: Repzo API - AI Object Detection Mission
  version: 1.0.0
  description: |
    **Missions** group planogram metrics (`/ai-object-detection-metric`)
    with a WEIGHT each — the mission score is the weighted mean of the
    metrics' effective result scores (0..1). A metric without a result on an
    analysis counts as 0 (a demanded check that never ran must not score as
    passed). Missions are grouped into mission SETS
    (`/ai-object-detection-mission-set`) which assignment rules attach to
    clients; a session started FROM a mission carries the mission's optional
    detection `category`, which drives the auto-analysis.

    **Scores read.** `GET ?scores_for=<analysisId>` resolves the assignment
    rules against that analysis's session CLIENT and returns every enabled
    mission with `assigned` (attached via a rule), `via_sets`,
    `requirement_mode` (the strictest demand the matched rules place on it —
    `not_required` / `submission_required` / `completion_required`; absent
    when unassigned), the weighted `score` 0..1, `min_score` + `completed`
    (a NON-ZERO score ≥ min_score; a zero score never completes — checks
    that never ran are not achievements; `min_score` is the mission's
    optional completion threshold, default 0) and the per-metric breakdown
    (weight, effective score, `missing`). Resolution happens at read time —
    recalculations and rule edits are reflected immediately, nothing is
    persisted on the client. The STORED per-session outcome lives in
    `/ai-object-detection-mission-results`.

    **Validation.** Create AND update require `metrics` with at least one
    row of `{ metric: <valid id>, weight: <number ≥ 0> }`; `min_score` must
    be within 0..1 when given; `category` must be a valid id, or `null`/""
    to clear it. Every violation is listed in one 400.

    **Multi-tenancy & lifecycle.** Scoped by `company_namespace` (injected
    from the caller's token; SDK callers may pass an explicit value on
    create), soft-deleted via `disabled`. `enabled: false` hides the
    mission from evaluation and `scores_for`. `PATCH` is not supported
    (400 — use `PUT`). Admin-facing.
servers:
  - url: https://sv.api.repzo.me
security:
  - ApiKeyAuth: []
  - JwtAuth: []
paths:
  /ai-object-detection-mission:
    get:
      summary: List missions (or resolved scores for an analysis)
      operationId: findMission
      parameters:
        - in: query
          name: _id
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: name
          description: Exact name match.
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: search
          description: Case-insensitive substring search on `name`.
          schema: { type: string }
        - in: query
          name: enabled
          schema: { type: boolean }
        - in: query
          name: disabled
          description: "Include soft-deleted missions (`true`) or only live ones (`false`). Omitted = no filter."
          schema: { type: boolean }
        - in: query
          name: from_updatedAt
          schema: { type: number }
        - in: query
          name: to_updatedAt
          schema: { type: number }
        - in: query
          name: from_createdAt
          schema: { type: number }
        - in: query
          name: to_createdAt
          schema: { type: number }
        - in: query
          name: scores_for
          description: |
            Analysis _id — returns RESOLVED MISSION SCORES for it instead of
            documents (see `MissionScores`). `completed` = a NON-ZERO score ≥
            the mission's min_score (computed at read, never stamped). All
            other filters and pagination are ignored in this mode.
          schema: { type: string }
        - in: query
          name: per_page
          schema: { type: integer, minimum: 1 }
        - in: query
          name: page
          schema: { type: integer, minimum: 1 }
      responses:
        "200":
          description: "Paginated documents (newest `_id` first), or `MissionScores` when `scores_for` is given."
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/MissionFindResult"
                  - $ref: "#/components/schemas/MissionScores"
    post:
      summary: Create a mission
      operationId: createMission
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MissionCreateBody"
      responses:
        "201":
          description: The created document.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Mission"
        "400":
          description: Validation failed — the message lists every violation.
  /ai-object-detection-mission/{id}:
    get:
      summary: Get a mission
      operationId: getMission
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: The document.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Mission"
        "400":
          description: Not found.
    put:
      summary: Update a mission (re-validated)
      description: "Same validation as create — `metrics` (≥ 1 row) is required again. `category: null` clears the category; omitting it leaves the stored value. Set `disabled: true` to soft-delete."
      operationId: updateMission
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MissionUpdateBody"
      responses:
        "200":
          description: The updated document.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Mission"
        "400":
          description: Validation failed.
        "404":
          description: Not found.
    delete:
      summary: Soft-delete a mission
      operationId: removeMission
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: The disabled document.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Mission"
        "404":
          description: Not found.
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: api-key
    JwtAuth:
      type: apiKey
      in: header
      name: Authorization
  schemas:
    UserStamp:
      type: object
      description: "Who created / last edited the document (server-stamped from the token)."
      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 }
    MissionMetricRow:
      type: object
      required: [metric, weight]
      properties:
        metric:
          type: string
          description: Metric id (`/ai-object-detection-metric`).
        weight:
          type: number
          minimum: 0
          description: Weight in the weighted mean.
    MissionFields:
      type: object
      properties:
        name: { type: string }
        description: { type: string }
        metrics:
          type: array
          minItems: 1
          description: "Weighted metric rows — the mission's composition (required, ≥ 1 row)."
          items:
            $ref: "#/components/schemas/MissionMetricRow"
        min_score:
          type: number
          minimum: 0
          maximum: 1
          default: 0
          description: 'Optional completion threshold: the mission counts as COMPLETED on an analysis when its NON-ZERO weighted score ≥ this. 0 (default) = any non-zero score completes. null/"" = leave default.'
        category:
          type: string
          nullable: true
          description: "Optional detection category (`/ai-object-detection-category`). The mobile stamps it on a session started FROM this mission, so the category pipeline drives the auto-analysis. null clears it."
        enabled: { type: boolean, default: true }
    MissionCreateBody:
      allOf:
        - $ref: "#/components/schemas/MissionFields"
        - type: object
          required: [name, metrics]
          properties:
            company_namespace:
              type: array
              items: { type: string }
              description: Optional tenant namespace override for SDK callers.
    MissionUpdateBody:
      allOf:
        - $ref: "#/components/schemas/MissionFields"
        - type: object
          required: [metrics]
          properties:
            disabled:
              type: boolean
              description: Soft-delete flag.
    Mission:
      allOf:
        - $ref: "#/components/schemas/MissionFields"
        - type: object
          properties:
            _id: { type: string }
            disabled: { type: boolean }
            creator:
              $ref: "#/components/schemas/UserStamp"
            editor:
              $ref: "#/components/schemas/UserStamp"
            company_namespace:
              type: array
              items: { type: string }
            createdAt: { type: string, format: date-time }
            updatedAt: { type: string, format: date-time }
    MissionScores:
      type: object
      description: "Response of `GET ?scores_for=<analysisId>` — every enabled mission scored against the analysis, assigned ones first, then by name."
      properties:
        analysis: { type: string }
        client:
          type: string
          nullable: true
          description: The analysis's session client (null when the session has none).
        missions:
          type: array
          items:
            type: object
            properties:
              _id: { type: string }
              name: { type: string }
              assigned:
                type: boolean
                description: An enabled assignment rule attaches a set containing this mission to the client.
              via_sets:
                type: array
                items: { type: string }
                description: Names of the assigned sets this mission arrived through.
              requirement_mode:
                type: string
                enum: [not_required, submission_required, completion_required]
                description: Strictest demand the matched rules place on it; absent when unassigned.
              score:
                type: number
                description: Weighted mean 0..1 of the metrics' effective scores (missing metrics count 0).
              min_score: { type: number }
              completed:
                type: boolean
                description: NON-ZERO score ≥ min_score.
              metrics:
                type: array
                items:
                  type: object
                  properties:
                    metric: { type: string }
                    name:
                      type: string
                      description: Metric-name snapshot from its result (absent when missing).
                    weight: { type: number }
                    score:
                      type: number
                      description: Effective 0..1 score of the metric's result; absent when missing.
                    missing:
                      type: boolean
                      description: The metric has no result on this analysis.
    MissionFindResult:
      type: object
      description: Standard paginated result envelope.
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Mission"
        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 }
