1aaef71f52
Document-driven MVP with FastAPI backend, Vue H5, WeChat mini shell, product demo, and Docker dev stack. Co-authored-by: Cursor <cursoragent@cursor.com>
671 lines
19 KiB
YAML
671 lines
19 KiB
YAML
openapi: 3.0.3
|
|
info:
|
|
title: Kids AI Posture Platform API
|
|
version: 1.0.0
|
|
description: |
|
|
儿童AI体态管理与运动康复平台 V1.0 API 契约。
|
|
所有报告用于健康管理建议,不构成医疗诊断。接口返回统一包含 requestId 以便追踪。
|
|
servers:
|
|
- url: https://api.example.com
|
|
description: production
|
|
- url: https://staging-api.example.com
|
|
description: staging
|
|
security:
|
|
- bearerAuth: []
|
|
|
|
x-error-model: &error-model
|
|
code: integer
|
|
message: string
|
|
details:
|
|
type: object
|
|
additionalProperties: true
|
|
requestId: string
|
|
|
|
tags:
|
|
- name: Auth
|
|
- name: Children
|
|
- name: Videos
|
|
- name: Analysis
|
|
- name: Reports
|
|
- name: Training
|
|
- name: Admin
|
|
|
|
paths:
|
|
/api/auth/login:
|
|
post:
|
|
tags: [Auth]
|
|
summary: 手机号或微信授权登录
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/LoginRequest'
|
|
examples:
|
|
phone:
|
|
summary: 手机验证码
|
|
value:
|
|
loginType: phone_code
|
|
credential: "186xxxx0000"
|
|
code: "682139"
|
|
wechat:
|
|
summary: 微信授权码
|
|
value:
|
|
loginType: wechat
|
|
credential: "wx_auth_code"
|
|
responses:
|
|
'200':
|
|
description: 登录成功
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/LoginResponse'
|
|
'401':
|
|
description: 登录失败
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
|
|
/api/children:
|
|
get:
|
|
tags: [Children]
|
|
summary: 获取当前用户可访问的儿童档案
|
|
parameters:
|
|
- name: page
|
|
in: query
|
|
schema: { type: integer, minimum: 1, default: 1 }
|
|
- name: pageSize
|
|
in: query
|
|
schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
|
|
responses:
|
|
'200':
|
|
description: 儿童档案分页列表
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ChildrenPagedResponse'
|
|
post:
|
|
tags: [Children]
|
|
summary: 创建儿童档案
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ChildCreateRequest'
|
|
responses:
|
|
'201':
|
|
description: 创建成功
|
|
headers:
|
|
Idempotency-Key:
|
|
description: 冪等键回显,用于重试去重核对
|
|
schema: { type: string }
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ChildResponse'
|
|
'400':
|
|
description: 请求参数错误
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
|
|
/api/children/{childId}:
|
|
parameters:
|
|
- $ref: '#/components/parameters/ChildId'
|
|
get:
|
|
tags: [Children]
|
|
summary: 获取儿童档案详情
|
|
responses:
|
|
'200':
|
|
description: 档案详情
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ChildResponse'
|
|
'404':
|
|
description: 资源不存在
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
patch:
|
|
tags: [Children]
|
|
summary: 更新儿童档案
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ChildUpdateRequest'
|
|
responses:
|
|
'200':
|
|
description: 更新成功
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ChildResponse'
|
|
'409':
|
|
description: 更新冲突(数据并发)
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
delete:
|
|
tags: [Children]
|
|
summary: 注销/归档儿童档案
|
|
responses:
|
|
'204':
|
|
description: 归档成功
|
|
|
|
/api/videos/upload-token:
|
|
post:
|
|
tags: [Videos]
|
|
summary: 获取视频直传凭证
|
|
description: 建议请求头携带 Idempotency-Key,避免重试产生重复录像对象。
|
|
parameters:
|
|
- name: Idempotency-Key
|
|
in: header
|
|
required: false
|
|
schema:
|
|
type: string
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/UploadTokenRequest'
|
|
responses:
|
|
'200':
|
|
description: 上传凭证
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/UploadTokenResponse'
|
|
'429':
|
|
description: 请求过于频繁
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
|
|
/api/videos:
|
|
post:
|
|
tags: [Videos]
|
|
summary: 登记已上传视频
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/VideoCreateRequest'
|
|
responses:
|
|
'201':
|
|
description: 视频登记成功
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/VideoResponse'
|
|
'409':
|
|
description: 去重/重复提交
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
|
|
/api/analysis/tasks:
|
|
post:
|
|
tags: [Analysis]
|
|
summary: 创建AI分析任务
|
|
description: 同一 childId+videoId+taskType 可复用幂等键创建。
|
|
parameters:
|
|
- name: Idempotency-Key
|
|
in: header
|
|
required: false
|
|
schema:
|
|
type: string
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/AnalysisTaskCreateRequest'
|
|
responses:
|
|
'201':
|
|
description: 任务创建成功
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/AnalysisTaskResponse'
|
|
'409':
|
|
description: 重复任务,返回已有任务
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/AnalysisTaskResponse'
|
|
|
|
/api/analysis/tasks/{taskId}:
|
|
get:
|
|
tags: [Analysis]
|
|
summary: 查询分析任务状态
|
|
parameters:
|
|
- $ref: '#/components/parameters/TaskId'
|
|
responses:
|
|
'200':
|
|
description: 任务状态
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/AnalysisTaskResponse'
|
|
'404':
|
|
description: 任务不存在
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
|
|
/api/analysis/tasks/{taskId}/cancel:
|
|
post:
|
|
tags: [Analysis]
|
|
summary: 取消分析任务
|
|
parameters:
|
|
- $ref: '#/components/parameters/TaskId'
|
|
responses:
|
|
'200':
|
|
description: 取消成功
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/StandardResponse'
|
|
|
|
/api/analysis/webhook:
|
|
post:
|
|
tags: [Analysis]
|
|
summary: 分析结果回调(异步)
|
|
description: 系统内部回调给后端事件网关,需使用 HMAC 签名。
|
|
security: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/AnalysisTaskWebhook'
|
|
responses:
|
|
'204':
|
|
description: 接收成功
|
|
|
|
/api/reports/{reportId}:
|
|
get:
|
|
tags: [Reports]
|
|
summary: 获取体态分析报告
|
|
parameters:
|
|
- $ref: '#/components/parameters/ReportId'
|
|
responses:
|
|
'200':
|
|
description: 报告详情
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ReportResponse'
|
|
|
|
/api/training/plans:
|
|
post:
|
|
tags: [Training]
|
|
summary: 创建训练计划
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/TrainingPlanCreateRequest'
|
|
responses:
|
|
'201':
|
|
description: 训练计划创建成功
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/TrainingPlanResponse'
|
|
|
|
/api/training/plans/{planId}:
|
|
parameters:
|
|
- name: planId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
get:
|
|
tags: [Training]
|
|
summary: 获取训练计划
|
|
responses:
|
|
'200':
|
|
description: 训练计划详情
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/TrainingPlanResponse'
|
|
|
|
/api/training/plans/{planId}/records:
|
|
post:
|
|
tags: [Training]
|
|
summary: 提交训练打卡记录
|
|
parameters:
|
|
- name: planId
|
|
in: path
|
|
required: true
|
|
schema: { type: integer }
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/TrainingRecordCreateRequest'
|
|
responses:
|
|
'201':
|
|
description: 打卡成功
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/TrainingRecordResponse'
|
|
|
|
/api/admin/dashboard:
|
|
get:
|
|
tags: [Admin]
|
|
summary: 获取机构运营看板
|
|
responses:
|
|
'200':
|
|
description: 看板指标
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/AdminDashboardResponse'
|
|
|
|
components:
|
|
securitySchemes:
|
|
bearerAuth:
|
|
type: http
|
|
scheme: bearer
|
|
bearerFormat: JWT
|
|
parameters:
|
|
ChildId:
|
|
name: childId
|
|
in: path
|
|
required: true
|
|
schema: { type: integer }
|
|
TaskId:
|
|
name: taskId
|
|
in: path
|
|
required: true
|
|
schema: { type: integer }
|
|
ReportId:
|
|
name: reportId
|
|
in: path
|
|
required: true
|
|
schema: { type: integer }
|
|
schemas:
|
|
ApiMeta:
|
|
type: object
|
|
properties:
|
|
requestId: { type: string }
|
|
timestamp: { type: string, format: date-time }
|
|
traceId: { type: string }
|
|
StandardResponse:
|
|
type: object
|
|
properties:
|
|
code: { type: integer }
|
|
message: { type: string }
|
|
data: { type: object, nullable: true }
|
|
meta: { $ref: '#/components/schemas/ApiMeta' }
|
|
ErrorResponse:
|
|
allOf:
|
|
- $ref: '#/components/schemas/StandardResponse'
|
|
- type: object
|
|
properties:
|
|
code: { type: integer, example: 10003 }
|
|
message: { type: string, example: parameter_validation_failed }
|
|
data:
|
|
type: object
|
|
properties:
|
|
errorCode: { type: string }
|
|
path: { type: string }
|
|
PagedMeta:
|
|
type: object
|
|
properties:
|
|
page: { type: integer }
|
|
pageSize: { type: integer }
|
|
total: { type: integer }
|
|
hasMore: { type: boolean }
|
|
LoginRequest:
|
|
type: object
|
|
required: [loginType, credential]
|
|
properties:
|
|
loginType: { type: string, enum: [phone_code, wechat] }
|
|
credential: { type: string }
|
|
code: { type: string }
|
|
LoginResponse:
|
|
allOf:
|
|
- $ref: '#/components/schemas/StandardResponse'
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
token: { type: string }
|
|
user: { $ref: '#/components/schemas/User' }
|
|
User:
|
|
type: object
|
|
properties:
|
|
id: { type: integer }
|
|
phoneMasked: { type: string }
|
|
role: { type: string, enum: [parent, coach, org_admin, platform_admin] }
|
|
ChildCreateRequest:
|
|
type: object
|
|
required: [name, birthday]
|
|
properties:
|
|
name: { type: string }
|
|
birthday: { type: string, format: date }
|
|
gender: { type: string, enum: [male, female, unknown] }
|
|
height: { type: number }
|
|
weight: { type: number }
|
|
contraindications: { type: string }
|
|
ChildUpdateRequest:
|
|
allOf:
|
|
- $ref: '#/components/schemas/ChildCreateRequest'
|
|
Child:
|
|
type: object
|
|
properties:
|
|
id: { type: integer }
|
|
name: { type: string }
|
|
birthday: { type: string, format: date }
|
|
age: { type: integer }
|
|
height: { type: number }
|
|
weight: { type: number }
|
|
status: { type: string }
|
|
ChildResponse:
|
|
allOf:
|
|
- $ref: '#/components/schemas/StandardResponse'
|
|
- type: object
|
|
properties:
|
|
data: { $ref: '#/components/schemas/Child' }
|
|
ChildrenPagedResponse:
|
|
allOf:
|
|
- $ref: '#/components/schemas/StandardResponse'
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
list:
|
|
type: array
|
|
items: { $ref: '#/components/schemas/Child' }
|
|
page: { $ref: '#/components/schemas/PagedMeta' }
|
|
UploadTokenRequest:
|
|
type: object
|
|
required: [childId, fileName, contentType, size]
|
|
properties:
|
|
childId: { type: integer }
|
|
fileName: { type: string }
|
|
contentType: { type: string, example: video/mp4 }
|
|
size: { type: integer }
|
|
UploadTokenResponse:
|
|
allOf:
|
|
- $ref: '#/components/schemas/StandardResponse'
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
uploadUrl: { type: string }
|
|
objectKey: { type: string }
|
|
expireAt: { type: string, format: date-time }
|
|
VideoCreateRequest:
|
|
type: object
|
|
required: [childId, objectKey, scene]
|
|
properties:
|
|
childId: { type: integer }
|
|
objectKey: { type: string }
|
|
scene:
|
|
type: string
|
|
enum: [front_posture, side_posture, squat, balance, gait]
|
|
captureHint: { type: string }
|
|
VideoResponse:
|
|
allOf:
|
|
- $ref: '#/components/schemas/StandardResponse'
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
id: { type: integer }
|
|
status: { type: string, enum: [uploaded, rejected, archived] }
|
|
AnalysisTaskCreateRequest:
|
|
type: object
|
|
required: [childId, videoId, taskType]
|
|
properties:
|
|
childId: { type: integer }
|
|
videoId: { type: integer }
|
|
taskType: { type: string, enum: [posture_screening, movement_scoring, reassessment] }
|
|
AnalysisTaskResponse:
|
|
allOf:
|
|
- $ref: '#/components/schemas/StandardResponse'
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
id: { type: integer }
|
|
status:
|
|
type: string
|
|
enum: [CREATED, QUEUED, PROCESSING, SUCCEEDED, FAILED, CANCELLED]
|
|
progress: { type: integer, minimum: 0, maximum: 100 }
|
|
errorCode: { type: string }
|
|
reportId: { type: integer }
|
|
retryCount: { type: integer }
|
|
AnalysisTaskWebhook:
|
|
type: object
|
|
required: [taskId, status]
|
|
properties:
|
|
taskId: { type: integer }
|
|
status:
|
|
type: string
|
|
enum: [SUCCEEDED, FAILED, CANCELLED]
|
|
progress: { type: integer }
|
|
errorCode: { type: string }
|
|
result: { type: object }
|
|
signature: { type: string }
|
|
ReportResponse:
|
|
allOf:
|
|
- $ref: '#/components/schemas/StandardResponse'
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
id: { type: integer }
|
|
childId: { type: integer }
|
|
taskId: { type: integer }
|
|
riskLevel: { type: string, enum: [low, medium, high, review_required] }
|
|
summary: { type: string }
|
|
metrics:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
name: { type: string }
|
|
value: { type: number }
|
|
level: { type: string }
|
|
confidence: { type: number }
|
|
recommendations:
|
|
type: array
|
|
items: { type: string }
|
|
disclaimer: { type: string }
|
|
reviewedBy: { type: integer, nullable: true }
|
|
TrainingPlanCreateRequest:
|
|
type: object
|
|
required: [childId, goal, cycleDays]
|
|
properties:
|
|
childId: { type: integer }
|
|
reportId: { type: integer }
|
|
goal: { type: string }
|
|
cycleDays: { type: integer, minimum: 7 }
|
|
exerciseIds:
|
|
type: array
|
|
items: { type: integer }
|
|
constraints:
|
|
type: object
|
|
properties:
|
|
maxDailyMinutes: { type: integer }
|
|
coachNotes: { type: string }
|
|
TrainingPlanResponse:
|
|
allOf:
|
|
- $ref: '#/components/schemas/StandardResponse'
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
id: { type: integer }
|
|
status: { type: string, enum: [draft, active, completed, paused] }
|
|
detail: { type: object }
|
|
startedAt: { type: string, format: date-time }
|
|
endedAt: { type: string, format: date-time, nullable: true }
|
|
TrainingRecordCreateRequest:
|
|
type: object
|
|
required: [exerciseId, completed]
|
|
properties:
|
|
exerciseId: { type: integer }
|
|
completed: { type: boolean }
|
|
score: { type: integer, minimum: 0, maximum: 100 }
|
|
durationSeconds: { type: integer }
|
|
note: { type: string }
|
|
media:
|
|
type: array
|
|
items:
|
|
type: string
|
|
TrainingRecordResponse:
|
|
allOf:
|
|
- $ref: '#/components/schemas/StandardResponse'
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
id: { type: integer }
|
|
planId: { type: integer }
|
|
createdAt: { type: string, format: date-time }
|
|
AdminDashboardResponse:
|
|
allOf:
|
|
- $ref: '#/components/schemas/StandardResponse'
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
newChildren: { type: integer }
|
|
uploadedVideos: { type: integer }
|
|
completedReports: { type: integer }
|
|
activePlans: { type: integer }
|
|
conversionRate: { type: number }
|
|
reassessmentCompletionRate: { type: number }
|