Files
john 1aaef71f52 Initial commit: Happy Up monorepo through Sprint 5.
Document-driven MVP with FastAPI backend, Vue H5, WeChat mini shell, product demo, and Docker dev stack.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-23 11:42:40 +08:00

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 }