{
  "openapi": "3.1.0",
  "info": {
    "title": "爱口扫 AI 开放平台 API",
    "version": "1.0.0",
    "description": "患者建档、口内影像上传、异步 AI 分析与品牌化报告。所有接口使用 Bearer API Key，按租户隔离。",
    "contact": { "name": "平台技术支持", "email": "support@aikousao.com" }
  },
  "servers": [
    { "url": "https://api.aikousao.com/v1", "description": "生产环境（示例域名，上线时替换）" }
  ],
  "security": [{ "bearerAuth": [] }],
  "tags": [
    { "name": "Patients", "description": "患者建档与查询" },
    { "name": "Scans", "description": "口内影像上传" },
    { "name": "Jobs", "description": "异步任务状态" },
    { "name": "Reports", "description": "报告元数据与下载" },
    { "name": "Webhooks", "description": "报告完成回调" },
    { "name": "Usage", "description": "租户用量" }
  ],
  "paths": {
    "/patients": {
      "post": {
        "tags": ["Patients"],
        "summary": "创建患者",
        "operationId": "createPatient",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/PatientCreate" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "创建成功",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Patient" }
              }
            }
          },
          "409": { "$ref": "#/components/responses/Conflict" },
          "422": { "$ref": "#/components/responses/ValidationError" }
        }
      }
    },
    "/patients/{patient_id}": {
      "get": {
        "tags": ["Patients"],
        "summary": "查询患者",
        "operationId": "getPatient",
        "parameters": [
          { "name": "patient_id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "患者信息",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Patient" }
              }
            }
          },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/scans": {
      "post": {
        "tags": ["Scans"],
        "summary": "提交 8 张口内影像",
        "operationId": "createScan",
        "parameters": [
          { "name": "Idempotency-Key", "in": "header", "required": false, "schema": { "type": "string" }, "description": "幂等键，防止重复创建任务" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["patient_id", "files", "types"],
                "properties": {
                  "patient_id": { "type": "string", "example": "pat_8f3k2m" },
                  "files": {
                    "type": "array",
                    "items": { "type": "string", "format": "binary" },
                    "minItems": 8,
                    "maxItems": 8,
                    "description": "8 张 JPEG/PNG，单张 ≤ 10 MB"
                  },
                  "types": {
                    "type": "array",
                    "items": { "$ref": "#/components/schemas/ScanPosition" },
                    "minItems": 8,
                    "maxItems": 8,
                    "description": "与 files 顺序一致"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "已进入队列",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Job" }
              }
            }
          },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/jobs/{job_id}": {
      "get": {
        "tags": ["Jobs"],
        "summary": "查询任务状态",
        "operationId": "getJob",
        "parameters": [
          { "name": "job_id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "任务状态",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Job" }
              }
            }
          },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/reports/{report_id}": {
      "get": {
        "tags": ["Reports"],
        "summary": "报告元数据",
        "operationId": "getReport",
        "parameters": [
          { "name": "report_id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "报告信息",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Report" }
              }
            }
          },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/reports/{report_id}/download": {
      "get": {
        "tags": ["Reports"],
        "summary": "获取报告签名下载地址",
        "operationId": "getReportDownload",
        "parameters": [
          { "name": "report_id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "type", "in": "query", "required": false, "schema": { "type": "string", "enum": ["simple", "detailed"], "default": "detailed" } }
        ],
        "responses": {
          "200": {
            "description": "签名 URL",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ReportDownload" }
              }
            }
          },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" }
        }
      }
    },
    "/webhooks": {
      "post": {
        "tags": ["Webhooks"],
        "summary": "配置 Webhook",
        "operationId": "createWebhook",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/WebhookCreate" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "创建成功",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Webhook" }
              }
            }
          }
        }
      }
    },
    "/usage": {
      "get": {
        "tags": ["Usage"],
        "summary": "租户用量",
        "operationId": "getUsage",
        "parameters": [
          { "name": "from", "in": "query", "required": true, "schema": { "type": "string", "format": "date" } },
          { "name": "to", "in": "query", "required": true, "schema": { "type": "string", "format": "date" } }
        ],
        "responses": {
          "200": {
            "description": "用量",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Usage" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "API Key"
      }
    },
    "schemas": {
      "ScanPosition": {
        "type": "string",
        "enum": ["closed_left", "closed_center", "closed_right", "open_left", "open_center", "open_right", "upper", "lower"]
      },
      "PatientCreate": {
        "type": "object",
        "required": ["name", "gender", "phone", "consent"],
        "properties": {
          "name": { "type": "string", "example": "张三" },
          "gender": { "type": "string", "enum": ["male", "female", "unknown"] },
          "birthdate": { "type": "string", "format": "date", "example": "1990-01-01" },
          "phone": { "type": "string", "example": "13800000000" },
          "consent": { "type": "boolean", "example": true },
          "external_id": { "type": "string", "example": "clinic-a-0001" }
        }
      },
      "Patient": {
        "type": "object",
        "properties": {
          "patient_id": { "type": "string", "example": "pat_8f3k2m" },
          "vendor_status": { "type": "string", "enum": ["pending", "bound", "failed"] },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "Job": {
        "type": "object",
        "properties": {
          "job_id": { "type": "string", "example": "job_9d2x7q" },
          "status": { "type": "string", "enum": ["queued", "analyzing", "rendering", "completed", "failed"] },
          "queue_position": { "type": "integer", "example": 7 },
          "estimated_wait_seconds": { "type": "integer", "example": 420 },
          "report_ids": { "type": "array", "items": { "type": "string" } },
          "error": { "$ref": "#/components/schemas/Error" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "Report": {
        "type": "object",
        "properties": {
          "report_id": { "type": "string", "example": "rep_7k4m2p" },
          "job_id": { "type": "string", "example": "job_9d2x7q" },
          "type": { "type": "string", "enum": ["simple", "detailed"] },
          "grade": { "type": "string", "example": "IV" },
          "teeth_count": { "type": "integer", "example": 28 },
          "pages": { "type": "integer", "example": 7 },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "ReportDownload": {
        "type": "object",
        "properties": {
          "report_id": { "type": "string" },
          "type": { "type": "string", "enum": ["simple", "detailed"] },
          "url": { "type": "string", "format": "uri" },
          "expires_at": { "type": "string", "format": "date-time" },
          "content_type": { "type": "string", "example": "application/pdf" }
        }
      },
      "WebhookCreate": {
        "type": "object",
        "required": ["url", "events", "secret"],
        "properties": {
          "url": { "type": "string", "format": "uri", "example": "https://your-server.com/webhook" },
          "events": { "type": "array", "items": { "type": "string", "enum": ["report.ready", "job.failed", "job.status_changed"] } },
          "secret": { "type": "string", "example": "whsec_xxxxxxxx" }
        }
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "webhook_id": { "type": "string" },
          "url": { "type": "string", "format": "uri" },
          "events": { "type": "array", "items": { "type": "string" } },
          "status": { "type": "string", "enum": ["active", "disabled"] }
        }
      },
      "Usage": {
        "type": "object",
        "properties": {
          "tenant_id": { "type": "string" },
          "from": { "type": "string", "format": "date" },
          "to": { "type": "string", "format": "date" },
          "scans": { "type": "integer" },
          "ai_tasks": { "type": "integer" },
          "api_calls": { "type": "integer" },
          "storage_bytes": { "type": "integer" }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "code": { "type": "string", "example": "SCAN_INCOMPLETE" },
          "message": { "type": "string", "example": "必须提交 8 张影像" },
          "request_id": { "type": "string", "example": "req_8d2k" },
          "details": { "type": "object", "additionalProperties": true }
        }
      }
    },
    "responses": {
      "NotFound": {
        "description": "资源不存在",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Conflict": {
        "description": "冲突（重复患者/报告未就绪）",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "ValidationError": {
        "description": "参数校验失败",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "RateLimited": {
        "description": "限流或队列已满",
        "headers": {
          "Retry-After": { "schema": { "type": "integer" }, "description": "建议重试秒数" }
        },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    }
  }
}
