openapi: 3.0.3
info:
  title: Repzo API - Object Detection Model Version Epochs
  version: 1.0.0
  description: |
    Per-epoch training telemetry for one object-detection model version — the
    series behind the training curves.

    **Where the rows come from.** They are NOT written through this service.
    The Ultralytics training run (the "Run on agent" snippet from
    `ai-object-detection-model-version-train-agent`) streams metrics to
    `POST /ul-hub/v1/models/:id` on every epoch via the Ultralytics HUB client;
    that ingest de-slashes Ultralytics' native keys (`train/box_loss` →
    `train_loss_metrics.box_loss`, `metrics/mAP50-95(B)` →
    `performance_metrics.mAP50_95_B`) and upserts one row per epoch index. This
    service is strictly READ-ONLY — create/update/patch/remove all return 400.

    **Who calls it.** Back-office admins via the dashboard's version detail
    screen (Metrics tab). Scoped by `company_namespace[]`.

    **Shape of the response.** `find` is NOT paginated: a curve needs every
    point and a run is bounded (hundreds of epochs at most), so the full series
    is returned in `_index` order alongside a derived `summary` (best epoch,
    best vs final metrics, architecture cost, final generalization gap).
    `per_page` / `page` / `sort` are ignored. The summary is computed on read,
    so versions trained before this endpoint existed summarize correctly too.

    **Key relationships.** `model_version` → `ai-object-detection-model-version`.
    The same best-epoch roll-up is snapshotted onto that version's `metrics` /
    `model_stats` when its weights upload completes.
servers:
  - url: https://sv.api.repzo.me
security:
  - ApiKeyAuth: []
  - JwtAuth: []
paths:
  /ai-object-detection-model-version-epoch:
    get:
      summary: List every reported epoch of one training run
      description: |
        Returns the full epoch series for `model_version`, ordered by `_index`
        ascending, plus a derived `summary`. Not paginated.
      operationId: findAiObjectDetectionModelVersionEpochs
      parameters:
        - in: query
          name: model_version
          required: true
          description: |
            The `ai-object-detection-model-version` `_id` whose run to read.
            Required — epochs are only meaningful per version (400 without it).
          schema: { type: string }
          example: 6a6e0687de060479c01c9e49
        - in: query
          name: _id
          description: "Filter to specific epoch row id(s). Pass once or as `?_id[]=...`."
          schema:
            oneOf:
              - type: string
              - type: array
                items: { type: string }
        - in: query
          name: _index
          description: Filter to one or more epoch indexes (0-based).
          schema:
            oneOf:
              - type: integer
                minimum: 0
              - type: array
                items: { type: integer, minimum: 0 }
        - in: query
          name: type
          description: Payload kind reported by the trainer. Only `metrics` today.
          schema: { type: string, enum: [metrics] }
        - in: query
          name: disabled
          description: Include disabled rows. Defaults to `false`.
          schema: { type: boolean, default: false }
      responses:
        "200":
          description: The ordered epoch series and its derived summary.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EpochFindResult"
        "400":
          description: "`model_version` was not supplied."
  /ai-object-detection-model-version-epoch/{id}:
    get:
      summary: Get one epoch row
      operationId: getAiObjectDetectionModelVersionEpoch
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: The epoch document.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EpochSchema"
        "400":
          description: No epoch with that id.
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: api-key
      description: |
        Server-issued API key. Also accepted via the `x-api-key` header or the
        `?apiKey=` query parameter as fallbacks.
    JwtAuth:
      type: apiKey
      in: header
      name: Authorization
      description: |
        Raw JWT in the `Authorization` header — **no `Bearer ` prefix**.
        Obtained from `POST /authenticate` (admin / rep / client login).
  schemas:
    LossMetrics:
      type: object
      description: Ultralytics loss components for one epoch.
      properties:
        box_loss: { type: number, description: Bounding-box regression loss. }
        cls_loss: { type: number, description: Classification loss. }
        dfl_loss: { type: number, description: Distribution focal loss. }
    PerformanceMetrics:
      type: object
      description: |
        Validation metrics for one epoch. The `_B` suffix is Ultralytics' own —
        it denotes the Box (detection) task, as opposed to segmentation/pose.
      properties:
        precision_B: { type: number }
        recall_B: { type: number }
        mAP50_B:
          type: number
          description: mAP at IoU 0.50 — the lenient, headline number.
        mAP50_95_B:
          type: number
          description: |
            mAP averaged over IoU 0.50:0.95 — the strict number `best.pt` is
            selected on.
    ModelStats:
      type: object
      description: |
        Architecture cost. Ultralytics reports it only on some epochs, so it can
        be absent on any individual row.
      properties:
        parameters: { type: integer, description: Total weight count. }
        GFLOPs: { type: number, description: Forward-pass cost per image. }
        speed_PyTorch_ms:
          type: number
          description: Per-image PyTorch inference latency measured during validation.
    EpochSchema:
      type: object
      properties:
        _id: { type: string }
        _index:
          {
            type: integer,
            description: "0-based epoch number; unique per `model_version`.",
          }
        model_version: { type: string }
        type: { type: string, enum: [metrics] }
        train_loss_metrics: { $ref: "#/components/schemas/LossMetrics" }
        val_loss_metrics: { $ref: "#/components/schemas/LossMetrics" }
        performance_metrics: { $ref: "#/components/schemas/PerformanceMetrics" }
        model_stats: { $ref: "#/components/schemas/ModelStats" }
        disabled: { type: boolean }
        company_namespace: { type: array, items: { type: string } }
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }
    EpochPerformanceRollup:
      type: object
      properties:
        mAP50: { type: number }
        mAP50_95: { type: number }
        precision: { type: number }
        recall: { type: number }
    EpochSummary:
      type: object
      description: Derived on read from the returned series.
      properties:
        epochs_reported:
          type: integer
          description: |
            How many epochs the trainer has reported so far — less than the
            configured `epochs` while a run is still in flight.
        best_epoch:
          type: integer
          description: "`_index` of the highest-mAP50-95 epoch — what `best.pt` holds."
        best: { $ref: "#/components/schemas/EpochPerformanceRollup" }
        final:
          allOf:
            - $ref: "#/components/schemas/EpochPerformanceRollup"
            - description: Last reported epoch — diverges from `best` when the run overfit.
        model_stats: { $ref: "#/components/schemas/ModelStats" }
        final_generalization_gap:
          type: number
          description: |
            Total validation loss minus total training loss at the final epoch.
            Positive and widening is the classic overfitting signature — the run
            kept fitting the training set while validation stopped improving.
    EpochFindResult:
      type: object
      properties:
        data:
          type: array
          description: Every reported epoch, ascending by `_index`.
          items: { $ref: "#/components/schemas/EpochSchema" }
        total_result: { type: integer }
        summary: { $ref: "#/components/schemas/EpochSummary" }
