openapi: 3.0.3
info:
  title: Repzo API - AI Object Detection Assignment Rule
  version: 1.0.0
  description: |
    **Assignment rules** attach one or more mission SETS to clients, built
    line by line:
    each condition is `key` / `operator` / `value` (ids), ALL lines must hold
    (AND) — every added line narrows the audience. ZERO lines = the set
    applies to every client. Keys: `client` (client ids), `client_tag`
    (client-type Tag ids), `area_tag` (area-type Tag ids — both tag kinds
    live in the client's `tags` array), `client_channel` (client-channel
    ids), `assigned_to` (rep ids — matches when the client is assigned to
    any of them), `chain` (chain-client ids, i.e. clients with
    `isChain: true` — matches the client's `chain` ref), `team` (team ids on
    the client). Operators: `in` (any overlap), `nin` (no overlap — a client
    missing the attribute passes `nin` and fails `in`).

    **Frequency.** Each rule carries how often its set is DEMANDED:
    `every_visit` (default — due on every visit, never pre-completed) or
    `times` completions per `day` / `week` / `month` / `quarter` (company
    timezone). The assigned-missions read uses it to mark missions `done`
    within the current interval.

    **Mission sets & requirement mode.** `mission_sets` is an array of
    lines — `{ mission_set, requirement_mode }`, at least one, each set
    listed once. `requirement_mode` says what the rule DEMANDS of that set's
    missions at the client: `not_required` (optional — available to scan,
    nothing demanded), `submission_required` (default — a scan must be
    SUBMITTED, i.e. a session analyzed for the mission, pass or fail) or
    `completion_required` (the submitted scan must COMPLETE the mission,
    weighted score ≥ `min_score`). The backend CARRIES the mode —
    the assigned-missions read turns each line into a per-mission
    requirement, reports the STRICTEST mode per rule and per mission, and
    keeps `done` counting completions; the MOBILE APP enforces the mode when
    the rep ends the visit. `mission_sets` is REQUIRED on write — the
    pre-array single `mission_set` key is not accepted (every stored rule
    carries `mission_sets`; filter by set with `mission_sets.mission_set`).

    Resolution happens at read time (mission `scores_for` and
    `/ai-object-detection-assigned-missions`) — rules are never stamped onto
    clients, so editing a rule re-targets instantly.

    **Writes are re-validated in full.** `PUT` runs the same validation as
    `POST`: `mission_sets` must be sent again, an omitted `conditions` resets
    the rule to "every client" and an omitted `frequency` resets it to
    `every_visit`; every violation is reported in one 400. `PATCH` is
    rejected (400 — use update). `DELETE` soft-deletes (`disabled: true`,
    `editor` stamped) and returns the row. The list returns active AND
    soft-deleted rows unless `disabled=false` is passed, and is always ordered
    by `_id` descending. `creator` / `editor` are stamped from the caller's
    token.

    Scoped by `company_namespace` (single-namespace token required for
    create / update), soft-deleted via `disabled`. Admin-facing.
servers:
  - url: https://sv.api.repzo.me
security:
  - ApiKeyAuth: []
  - JwtAuth: []
paths:
  /ai-object-detection-assignment-rule:
    get:
      summary: List assignment rules
      operationId: findAssignmentRule
      parameters:
        - in: query
          name: _id
          description: "Filter by document `_id`. Pass once or as `?_id[]=...` for multiple."
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: name
          description: Exact name match (one or many).
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: enabled
          schema: { type: boolean }
        - in: query
          name: mission_sets.mission_set
          description: "Filter by a targeted mission set id — matches rules whose `mission_sets` list it (the array path; there is no `mission_set` filter). One or many."
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: search
          description: "Case-insensitive regex search on `name`."
          schema: { type: string }
        - in: query
          name: disabled
          description: "Omit to get active AND soft-deleted rows; `false` = active only; `true` = soft-deleted only."
          schema: { type: boolean }
        - in: query
          name: from_updatedAt
          description: "ms epoch (or date string). Snapped to the start of that day in the company time zone unless `exact_time=true`."
          schema: { type: number }
        - in: query
          name: to_updatedAt
          description: "ms epoch (or date string). Snapped to the end of that day unless `exact_time=true`."
          schema: { type: number }
        - in: query
          name: from_createdAt
          schema: { type: number }
        - in: query
          name: to_createdAt
          schema: { type: number }
        - in: query
          name: exact_time
          description: "Use the exact instants of the `from_*` / `to_*` bounds instead of whole days."
          schema: { type: boolean }
        - in: query
          name: per_page
          schema: { type: integer, minimum: 1 }
        - in: query
          name: page
          schema: { type: integer, minimum: 1, default: 1 }
      responses:
        "200":
          description: "Paginated documents, ordered by `_id` descending (`sort` / `sortPageOrder` are ignored)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedAssignmentRules"
    post:
      summary: Create an assignment rule
      operationId: createAssignmentRule
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AssignmentRuleCreate"
      responses:
        "201":
          description: "The created document (`creator` stamped, `frequency` / `mission_sets` normalized)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AssignmentRule"
        "400":
          description: "Validation failed — the message lists every violation (`errors[]` in the error data). Also returned when the token spans several namespaces."
  /ai-object-detection-assignment-rule/{id}:
    get:
      summary: Get an assignment rule
      operationId: getAssignmentRule
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: The document.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AssignmentRule"
        "400":
          description: "No rule with that id in the caller's namespace (the backend answers 400, not 404)."
    put:
      summary: Update an assignment rule (re-validated in full)
      operationId: updateAssignmentRule
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AssignmentRuleUpdate"
      responses:
        "200":
          description: "The updated document (`editor` stamped)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AssignmentRule"
        "400":
          description: Validation failed — the message lists every violation.
        "404":
          description: No rule with that id.
    delete:
      summary: Soft-delete an assignment rule
      operationId: removeAssignmentRule
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: "The disabled document (`disabled: true`, `editor` stamped)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AssignmentRule"
        "404":
          description: No rule with that id.
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: api-key
    JwtAuth:
      type: apiKey
      in: header
      name: Authorization
  schemas:
    RuleMissionSet:
      type: object
      required: [mission_set]
      properties:
        mission_set:
          type: string
          description: The mission set id.
        requirement_mode:
          type: string
          enum: [not_required, submission_required, completion_required]
          default: submission_required
          description: "What the rule demands of this set's missions at the client: not_required (optional), submission_required (a scan must be submitted, pass or fail), completion_required (the scan must complete the mission)."
    RuleCondition:
      type: object
      required: [key, operator, value]
      properties:
        _id:
          type: string
          description: Subdocument id — server-generated on stored rows, ignored on input.
        key:
          type: string
          enum:
            [
              client,
              client_tag,
              client_channel,
              assigned_to,
              chain,
              area_tag,
              team,
            ]
        operator:
          type: string
          enum: [in, nin]
        value:
          type: array
          minItems: 1
          items: { type: string }
          description: "Ids matching the key: clients / client-type tags / channels / reps / chain clients (isChain) / area-type tags / teams."
    RuleFrequency:
      type: object
      description: "How often the set is demanded. Absent = every_visit."
      properties:
        interval:
          type: string
          enum: [every_visit, day, week, month, quarter]
          default: every_visit
        times:
          type: integer
          minimum: 1
          default: 1
          description: "Completions required per interval (pinned to 1 for every_visit)."
    UserStamp:
      type: object
      description: "Who created / last edited the row — taken from the caller's token."
      properties:
        _id: { type: string }
        type:
          type: string
          enum: [admin, rep, client, tenant]
        name: { type: string }
        admin: { type: string }
        rep: { type: string }
        client: { type: string }
        tenant: { type: string }
    AssignmentRuleWrite:
      type: object
      required: [mission_sets]
      properties:
        name: { type: string }
        mission_sets:
          type: array
          minItems: 1
          description: "REQUIRED on every write. The mission sets this rule assigns, one line per set (a set may appear once)."
          items:
            $ref: "#/components/schemas/RuleMissionSet"
        conditions:
          type: array
          description: "AND-ed lines; empty or omitted = applies to every client."
          items:
            $ref: "#/components/schemas/RuleCondition"
        frequency:
          $ref: "#/components/schemas/RuleFrequency"
        enabled: { type: boolean, default: true }
    AssignmentRuleCreate:
      allOf:
        - $ref: "#/components/schemas/AssignmentRuleWrite"
        - type: object
          required: [name]
          properties:
            company_namespace:
              type: array
              items: { type: string }
              description: Optional tenant namespace override for SDK callers.
    AssignmentRuleUpdate:
      allOf:
        - $ref: "#/components/schemas/AssignmentRuleWrite"
        - type: object
          description: "Full re-validation: `mission_sets` is required again; an omitted `conditions` resets to every client and an omitted `frequency` resets to every_visit."
          properties:
            disabled:
              type: boolean
              description: "Set `true` to soft-delete through the update (same effect as DELETE)."
    AssignmentRule:
      allOf:
        - $ref: "#/components/schemas/AssignmentRuleWrite"
        - 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 }
    PaginatedAssignmentRules:
      type: object
      properties:
        total_result: { type: integer }
        current_count: { type: integer }
        total_pages: { type: integer }
        current_page: { type: integer }
        per_page: { type: integer }
        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 }
        data:
          type: array
          items:
            $ref: "#/components/schemas/AssignmentRule"
