openapi: 3.0.3 info: title: Patbond API — Pets Domain (M2 Draft) version: 1.2.0-draft description: | M2 宠物健康档案域契约草案(起草态,待 T2-03 权限/错误语义与 T2-08 聚合字段定型后冻结)。 本草案扩展既有 1.1.0 契约(6 端点:auth 4 + me 1 + events 1),增加 pets 域全部端点。 **未冻结,字段可能变更**;冻结后任何变更须显著上报、两端同步。 ## Pets 域端点概览(本草案范围) - 宠物 CRUD:GET/POST /api/v1/pets、GET/PATCH /api/v1/pets/{petId} - 品种目录:GET /api/v1/breeds(按 species 过滤) - 体重记录:GET/POST /api/v1/pets/{petId}/weights(cursor 分页) - 疫苗目录:GET /api/v1/vaccine-catalog(按 species 过滤) - 疫苗记录:GET/POST /api/v1/pets/{petId}/vaccinations、PATCH /api/v1/vaccinations/{vaccinationId} - 健康事件:GET/POST /api/v1/pets/{petId}/health-events、PATCH /api/v1/health-events/{eventId} - 照护提醒:GET/POST /api/v1/pets/{petId}/care-reminders、PATCH /api/v1/care-reminders/{reminderId} - 档案聚合:GET /api/v1/pets/{petId}/summary(最新体重、疫苗进度、下次接种、当月花费) ## 通用约定(继承既有契约) - 公开接口统一前缀 `/api/v1`;JSON 字段一律 `camelCase`;资源 ID 为 UUID 字符串。 - 所有时间字段为 ISO 8601 且带时区偏移(timestamptz 传输)。 - 统一响应信封 `{"code": 0, "message": "success", "data": …}`;错误同时携带正确的 HTTP 状态码与稳定业务码。 - Pets 域全部端点强制 Bearer 鉴权(`Authorization: Bearer `)。 ## Pets 域新增错误码(追加进既有表,不改号) | 业务码 | HTTP | 场景 | | --- | --- | --- | | 40300 | 403 | PET_ACCESS_DENIED:对可见宠物无相应操作权限(viewer 尝试写等) | | 40401 | 404 | PET_NOT_FOUND:宠物不存在或调用者不可见(防 ID 枚举,不区分) | | 40402 | 404 | RECORD_NOT_FOUND:宠物下的记录(体重/疫苗/事件/提醒)不存在 | | 40902 | 409 | VERSION_CONFLICT:乐观锁版本冲突(PATCH 操作,version 过期) | | 40903 | 409 | DUPLICATE_VACCINATION:同宠物同疫苗同系列同剂次非 cancelled 记录已存在 | | 42200 | 422 | BREED_CONSTRAINT_VIOLATION:breed_id 与 custom_breed_name 未互斥 | | 42201 | 422 | VACCINATION_STATE_INVALID:疫苗状态机非法迁移或日期约束违反 | ## 权限模型(ADR-015:owner/caregiver/viewer 三角色) # TODO-FREEZE: 等待 T2-03 权限校验实现定型后,补齐每个端点的权限规则描述与错误响应示例。 # 草案默认:owner 全权、caregiver 可读写记录不可改宠物档案、viewer 只读; # 无权限访问他人宠物返回 404/40401(不可见)或 403/40300(可见但越权)。 servers: - url: http://127.0.0.1:8082 description: patbond-pet 模块(本地开发,与 user 同服务或独立端口) tags: - name: pets description: 宠物档案 CRUD - name: dictionaries description: 品种与疫苗目录(只读) - name: health-records description: 体重、疫苗、健康事件、提醒 paths: /api/v1/pets: get: tags: [pets] summary: 当前用户可见宠物列表 description: | 返回当前用户拥有任何权限(owner/caregiver/viewer)的宠物列表。 # TODO-FREEZE: 列表是否分页待 T2-03 实现时定——量小建议不分页直接返回数组。 operationId: listPets security: - bearerAuth: [] responses: '200': description: 宠物列表 content: application/json: schema: $ref: '#/components/schemas/PetListEnvelope' '401': $ref: '#/components/responses/AccessTokenInvalid' post: tags: [pets] summary: 创建宠物 description: | 创建宠物,创建者自动成为 primary owner(pet_owners 自动写入)。 品种:breed_id 与 custom_breed_name 必须二选一且互斥(ck_pets_breed 约束)。 operationId: createPet security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreatePetRequest' responses: '201': description: 创建成功 content: application/json: schema: $ref: '#/components/schemas/PetEnvelope' '400': $ref: '#/components/responses/ValidationError' '401': $ref: '#/components/responses/AccessTokenInvalid' '422': description: 业务约束违反(breed_id/custom_breed_name 互斥等,code 42200) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' /api/v1/pets/{petId}: get: tags: [pets] summary: 宠物详情 description: | 返回宠物详情及调用者对该宠物的权限角色(role 字段)。 # TODO-FREEZE: 权限拒绝时 403 vs 404 的语义待 T2-03 定型。 operationId: getPet security: - bearerAuth: [] parameters: - name: petId in: path required: true schema: type: string format: uuid responses: '200': description: 宠物详情 content: application/json: schema: $ref: '#/components/schemas/PetDetailEnvelope' '401': $ref: '#/components/responses/AccessTokenInvalid' '404': description: 宠物不存在或不可见(code 40401) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' patch: tags: [pets] summary: 更新宠物档案 description: | 更新宠物档案(名称、品种、性别、生日等)。 请求体必须包含 `version` 乐观锁字段;冲突返回 409/40902。 # TODO-FREEZE: owner/caregiver 权限边界待 T2-03 定型(建议仅 owner 可写档案)。 operationId: updatePet security: - bearerAuth: [] parameters: - name: petId in: path required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdatePetRequest' responses: '200': description: 更新成功 content: application/json: schema: $ref: '#/components/schemas/PetEnvelope' '400': $ref: '#/components/responses/ValidationError' '401': $ref: '#/components/responses/AccessTokenInvalid' '403': description: 无权限更新(code 40300) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '404': description: 宠物不存在或不可见(code 40401) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '409': description: 版本冲突(code 40902) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '422': description: 业务约束违反(code 42200) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' /api/v1/breeds: get: tags: [dictionaries] summary: 品种目录 description: | 品种目录(只读),按 species 过滤。返回 enabled=true 的品种按 sort_order 排序。 operationId: listBreeds security: - bearerAuth: [] parameters: - name: species in: query required: false schema: type: string enum: [dog, cat, other] description: 过滤物种;不传则返回全部 responses: '200': description: 品种列表 content: application/json: schema: $ref: '#/components/schemas/BreedListEnvelope' '401': $ref: '#/components/responses/AccessTokenInvalid' /api/v1/pets/{petId}/weights: get: tags: [health-records] summary: 体重记录列表 description: | 体重记录按 measured_at DESC, id DESC 排序,cursor 分页。 operationId: listWeights security: - bearerAuth: [] parameters: - name: petId in: path required: true schema: type: string format: uuid - name: limit in: query schema: type: integer minimum: 1 maximum: 100 default: 20 - name: cursor in: query schema: type: string description: 上一页返回的 nextCursor,首次不传 responses: '200': description: 体重记录分页结果 content: application/json: schema: $ref: '#/components/schemas/WeightListEnvelope' '401': $ref: '#/components/responses/AccessTokenInvalid' '404': description: 宠物不存在或不可见(code 40401) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' post: tags: [health-records] summary: 添加体重记录 description: | 添加体重记录。支持 Idempotency-Key(可选但推荐,避免重复提交)。 operationId: createWeight security: - bearerAuth: [] parameters: - name: petId in: path required: true schema: type: string format: uuid - name: Idempotency-Key in: header required: false schema: type: string maxLength: 255 description: 幂等键(可选),相同键重试不产生重复记录 requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateWeightRequest' responses: '201': description: 创建成功 content: application/json: schema: $ref: '#/components/schemas/WeightEnvelope' '400': $ref: '#/components/responses/ValidationError' '401': $ref: '#/components/responses/AccessTokenInvalid' '403': description: 无权限添加(viewer 角色,code 40300) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '404': description: 宠物不存在或不可见(code 40401) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' /api/v1/vaccine-catalog: get: tags: [dictionaries] summary: 疫苗目录 description: | 疫苗目录(只读),按 species 过滤。返回 enabled=true 的疫苗。 operationId: listVaccineCatalog security: - bearerAuth: [] parameters: - name: species in: query required: false schema: type: string enum: [dog, cat, other] responses: '200': description: 疫苗目录列表 content: application/json: schema: $ref: '#/components/schemas/VaccineCatalogListEnvelope' '401': $ref: '#/components/responses/AccessTokenInvalid' /api/v1/pets/{petId}/vaccinations: get: tags: [health-records] summary: 疫苗记录列表 description: | 宠物的疫苗记录。不分页或按 administered_on/planned_on 排序分页(待定)。 # TODO-FREEZE: 分页策略待 T2-05 实现时定。 operationId: listVaccinations security: - bearerAuth: [] parameters: - name: petId in: path required: true schema: type: string format: uuid responses: '200': description: 疫苗记录列表 content: application/json: schema: $ref: '#/components/schemas/VaccinationListEnvelope' '401': $ref: '#/components/responses/AccessTokenInvalid' '404': description: 宠物不存在或不可见(code 40401) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' post: tags: [health-records] summary: 创建疫苗记录 description: | 创建疫苗记录(计划或已完成)。 状态机:scheduled 必须带 plannedOn;completed 必须带 administeredOn; 同宠物同疫苗同系列同剂次非 cancelled 记录唯一(uq_pet_vaccination_dose)。 # TODO-FREEZE: provider_id/booking_id/certificate_asset_id 按 ADR-010 不开放写入, # 待 M5/媒体上传流程补齐后再开放,字段在草案保留但标记只读或不出现在请求体。 operationId: createVaccination security: - bearerAuth: [] parameters: - name: petId in: path required: true schema: type: string format: uuid - name: Idempotency-Key in: header required: false schema: type: string maxLength: 255 requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateVaccinationRequest' responses: '201': description: 创建成功 content: application/json: schema: $ref: '#/components/schemas/VaccinationEnvelope' '400': $ref: '#/components/responses/ValidationError' '401': $ref: '#/components/responses/AccessTokenInvalid' '403': description: 无权限添加(code 40300) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '404': description: 宠物不存在或不可见(code 40401) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '409': description: 同系列同剂次记录已存在(code 40903) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '422': description: 状态机或日期约束违反(code 42201) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' /api/v1/vaccinations/{vaccinationId}: patch: tags: [health-records] summary: 更新疫苗记录 description: | 更新疫苗记录(状态流转、补充剂次标签、批号等)。 请求体必须包含 `version` 乐观锁;冲突返回 409/40902。 operationId: updateVaccination security: - bearerAuth: [] parameters: - name: vaccinationId in: path required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateVaccinationRequest' responses: '200': description: 更新成功 content: application/json: schema: $ref: '#/components/schemas/VaccinationEnvelope' '400': $ref: '#/components/responses/ValidationError' '401': $ref: '#/components/responses/AccessTokenInvalid' '403': description: 无权限更新(code 40300) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '404': description: 记录不存在(code 40402) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '409': description: 版本冲突(code 40902) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '422': description: 状态机或日期约束违反(code 42201) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' /api/v1/pets/{petId}/health-events: get: tags: [health-records] summary: 健康事件时间线 description: | 健康事件按 occurredAt DESC, id DESC 排序,cursor 分页。 operationId: listHealthEvents security: - bearerAuth: [] parameters: - name: petId in: path required: true schema: type: string format: uuid - name: limit in: query schema: type: integer minimum: 1 maximum: 100 default: 20 - name: cursor in: query schema: type: string responses: '200': description: 健康事件分页结果 content: application/json: schema: $ref: '#/components/schemas/HealthEventListEnvelope' '401': $ref: '#/components/responses/AccessTokenInvalid' '404': description: 宠物不存在或不可见(code 40401) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' post: tags: [health-records] summary: 添加健康事件 description: | 添加健康事件。六类事件类型:medical/feeding/deworming/grooming/measurement/note。 金额以整数分(amount_cents)传输,非负。 # TODO-FREEZE: provider_id/booking_id 按 ADR-010 不开放写入。 operationId: createHealthEvent security: - bearerAuth: [] parameters: - name: petId in: path required: true schema: type: string format: uuid - name: Idempotency-Key in: header required: false schema: type: string maxLength: 255 requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateHealthEventRequest' responses: '201': description: 创建成功 content: application/json: schema: $ref: '#/components/schemas/HealthEventEnvelope' '400': $ref: '#/components/responses/ValidationError' '401': $ref: '#/components/responses/AccessTokenInvalid' '403': description: 无权限添加(code 40300) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '404': description: 宠物不存在或不可见(code 40401) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' /api/v1/health-events/{eventId}: patch: tags: [health-records] summary: 更新健康事件 description: | 编辑健康事件(标题、备注、金额)。请求体必须包含 `version` 乐观锁。 operationId: updateHealthEvent security: - bearerAuth: [] parameters: - name: eventId in: path required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateHealthEventRequest' responses: '200': description: 更新成功 content: application/json: schema: $ref: '#/components/schemas/HealthEventEnvelope' '400': $ref: '#/components/responses/ValidationError' '401': $ref: '#/components/responses/AccessTokenInvalid' '403': description: 无权限更新(code 40300) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '404': description: 记录不存在(code 40402) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '409': description: 版本冲突(code 40902) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' /api/v1/pets/{petId}/care-reminders: get: tags: [health-records] summary: 照护提醒列表 description: | 照护提醒列表,按 dueAt 排序。四类提醒类型:deworming/checkup/medication/other。 # TODO-FREEZE: 是否分页、过滤待办(status=pending)的查询参数待 T2-07 实现时定。 operationId: listCareReminders security: - bearerAuth: [] parameters: - name: petId in: path required: true schema: type: string format: uuid responses: '200': description: 提醒列表 content: application/json: schema: $ref: '#/components/schemas/CareReminderListEnvelope' '401': $ref: '#/components/responses/AccessTokenInvalid' '404': description: 宠物不存在或不可见(code 40401) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' post: tags: [health-records] summary: 创建照护提醒 description: | 创建提醒(M2 仅 app 内数据,不做推送,ADR-010 说明)。 operationId: createCareReminder security: - bearerAuth: [] parameters: - name: petId in: path required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateCareReminderRequest' responses: '201': description: 创建成功 content: application/json: schema: $ref: '#/components/schemas/CareReminderEnvelope' '400': $ref: '#/components/responses/ValidationError' '401': $ref: '#/components/responses/AccessTokenInvalid' '403': description: 无权限添加(code 40300) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '404': description: 宠物不存在或不可见(code 40401) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' /api/v1/care-reminders/{reminderId}: patch: tags: [health-records] summary: 更新提醒状态 description: | 标记提醒完成或忽略(pending → completed/dismissed)。 completed 状态必须写入 completedAt(ck_care_reminder_completed 约束)。 operationId: updateCareReminder security: - bearerAuth: [] parameters: - name: reminderId in: path required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateCareReminderRequest' responses: '200': description: 更新成功 content: application/json: schema: $ref: '#/components/schemas/CareReminderEnvelope' '400': $ref: '#/components/responses/ValidationError' '401': $ref: '#/components/responses/AccessTokenInvalid' '403': description: 无权限更新(code 40300) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '404': description: 记录不存在(code 40402) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '422': description: 状态与 completedAt 一致性违反(code 42201 复用) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' /api/v1/pets/{petId}/summary: get: tags: [health-records] summary: 档案聚合摘要 description: | 实时聚合生成档案页摘要:最新体重、疫苗进度、下次接种、当月花费。 # TODO-FREEZE: 等待 T2-08 聚合字段定型——字段命名、月度边界口径(UTC 或指定时区)、 # 疫苗进度分母计算规则(总剂次 vs 已登记剂次)、下次接种取值优先级等。 # 草案字段为占位,冻结前可能变更。 operationId: getPetSummary security: - bearerAuth: [] parameters: - name: petId in: path required: true schema: type: string format: uuid responses: '200': description: 聚合摘要 content: application/json: schema: $ref: '#/components/schemas/PetSummaryEnvelope' '401': $ref: '#/components/responses/AccessTokenInvalid' '404': description: 宠物不存在或不可见(code 40401) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT description: 'Authorization: Bearer (RS256 JWT)' responses: ValidationError: description: 参数校验失败(code 40000) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' examples: validation: value: { code: 40000, message: 参数校验失败, data: null } AccessTokenInvalid: description: access token 缺失、无效或过期(code 40101) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' examples: tokenInvalid: value: { code: 40101, message: token 无效或过期, data: null } schemas: ErrorEnvelope: type: object required: [code, message] properties: code: type: integer description: 稳定业务错误码 example: 40401 message: type: string example: 宠物不存在或不可见 data: nullable: true example: null # ========== Pets CRUD ========== Pet: type: object required: - petId - name - species - sex - status - createdAt - updatedAt - version properties: petId: type: string format: uuid name: type: string minLength: 1 maxLength: 64 species: type: string enum: [dog, cat, other] breedId: type: string format: uuid nullable: true description: 品种 ID(与 customBreedName 互斥) customBreedName: type: string nullable: true minLength: 1 maxLength: 64 description: 自定义品种名(与 breedId 互斥) sex: type: string enum: [male, female, unknown] birthDate: type: string format: date nullable: true description: 生日(YYYY-MM-DD) birthDateEstimated: type: boolean description: 生日是否为估计值 personality: type: string nullable: true maxLength: 64 description: 性格标签 avatarAssetId: type: string format: uuid nullable: true description: 头像 asset ID(M2 不开放写入,ADR-010) microchipNo: type: string nullable: true description: 芯片号(唯一) sterilizedOn: type: string format: date nullable: true description: 绝育日期 status: type: string enum: [active, lost, deceased, archived, deleted] description: 状态(deleted 为软删除) createdAt: type: string format: date-time updatedAt: type: string format: date-time version: type: integer description: 乐观锁版本号(PATCH 时必须提交) PetDetail: allOf: - $ref: '#/components/schemas/Pet' - type: object required: [myRole] properties: myRole: type: string enum: [owner, caregiver, viewer] description: 调用者对该宠物的权限角色 CreatePetRequest: type: object required: [name, species, sex] properties: name: type: string minLength: 1 maxLength: 64 species: type: string enum: [dog, cat, other] breedId: type: string format: uuid description: 品种 ID(与 customBreedName 二选一) customBreedName: type: string minLength: 1 maxLength: 64 description: 自定义品种名(与 breedId 二选一) sex: type: string enum: [male, female, unknown] birthDate: type: string format: date nullable: true birthDateEstimated: type: boolean default: false personality: type: string maxLength: 64 microchipNo: type: string sterilizedOn: type: string format: date UpdatePetRequest: type: object required: [version] properties: version: type: integer description: 当前持有的版本号(乐观锁) name: type: string minLength: 1 maxLength: 64 breedId: type: string format: uuid nullable: true customBreedName: type: string minLength: 1 maxLength: 64 nullable: true sex: type: string enum: [male, female, unknown] birthDate: type: string format: date nullable: true birthDateEstimated: type: boolean personality: type: string maxLength: 64 nullable: true microchipNo: type: string nullable: true sterilizedOn: type: string format: date nullable: true status: type: string enum: [active, lost, deceased, archived] description: 状态流转(deleted 不开放) PetEnvelope: type: object required: [code, message, data] properties: code: type: integer enum: [0] message: type: string example: success data: $ref: '#/components/schemas/Pet' PetDetailEnvelope: type: object required: [code, message, data] properties: code: type: integer enum: [0] message: type: string example: success data: $ref: '#/components/schemas/PetDetail' PetListEnvelope: type: object required: [code, message, data] properties: code: type: integer enum: [0] message: type: string example: success data: type: array items: $ref: '#/components/schemas/Pet' # ========== Breeds ========== Breed: type: object required: [breedId, species, code, displayName] properties: breedId: type: string format: uuid species: type: string enum: [dog, cat, other] code: type: string description: 品种代码(唯一标识) displayName: type: string description: 展示名称 BreedListEnvelope: type: object required: [code, message, data] properties: code: type: integer enum: [0] message: type: string example: success data: type: array items: $ref: '#/components/schemas/Breed' # ========== Weights ========== WeightRecord: type: object required: [weightId, petId, weightKg, measuredAt, source, createdAt] properties: weightId: type: string format: uuid petId: type: string format: uuid weightKg: type: number format: double minimum: 0.01 maximum: 500 description: 体重(公斤),保留两位小数 measuredAt: type: string format: date-time description: 称重时间 source: type: string enum: [manual, clinic, device] description: 来源 note: type: string nullable: true maxLength: 500 createdAt: type: string format: date-time CreateWeightRequest: type: object required: [weightKg, measuredAt] properties: weightKg: type: number format: double minimum: 0.01 maximum: 500 measuredAt: type: string format: date-time source: type: string enum: [manual, clinic, device] default: manual note: type: string maxLength: 500 WeightEnvelope: type: object required: [code, message, data] properties: code: type: integer enum: [0] message: type: string example: success data: $ref: '#/components/schemas/WeightRecord' WeightListEnvelope: type: object required: [code, message, data] properties: code: type: integer enum: [0] message: type: string example: success data: type: object required: [items, hasMore] properties: items: type: array items: $ref: '#/components/schemas/WeightRecord' nextCursor: type: string nullable: true description: 下一页游标,无更多时为 null hasMore: type: boolean # ========== Vaccine Catalog ========== VaccineCatalogItem: type: object required: [vaccineId, code, name, species] properties: vaccineId: type: string format: uuid code: type: string description: 疫苗代码(唯一标识) name: type: string description: 疫苗名称 species: type: string enum: [dog, cat, other] description: type: string nullable: true maxLength: 500 VaccineCatalogListEnvelope: type: object required: [code, message, data] properties: code: type: integer enum: [0] message: type: string example: success data: type: array items: $ref: '#/components/schemas/VaccineCatalogItem' # ========== Vaccinations ========== Vaccination: type: object required: - vaccinationId - petId - vaccineId - seriesKey - doseNo - status - createdAt - updatedAt - version properties: vaccinationId: type: string format: uuid petId: type: string format: uuid vaccineId: type: string format: uuid seriesKey: type: string minLength: 1 maxLength: 64 description: 系列键(区分初次/加强等,与 doseNo 共同唯一) doseNo: type: integer minimum: 1 description: 剂次号 doseLabel: type: string nullable: true maxLength: 64 description: 剂次标签(如"第一针") status: type: string enum: [scheduled, completed, cancelled] plannedOn: type: string format: date nullable: true description: 计划接种日期(scheduled 必填) administeredOn: type: string format: date nullable: true description: 实际接种日期(completed 必填) nextDueOn: type: string format: date nullable: true description: 下次到期日期 manufacturer: type: string nullable: true maxLength: 128 batchNo: type: string nullable: true maxLength: 64 notes: type: string nullable: true maxLength: 1000 createdAt: type: string format: date-time updatedAt: type: string format: date-time version: type: integer CreateVaccinationRequest: type: object required: [vaccineId, seriesKey, doseNo, status] properties: vaccineId: type: string format: uuid seriesKey: type: string minLength: 1 maxLength: 64 doseNo: type: integer minimum: 1 doseLabel: type: string maxLength: 64 status: type: string enum: [scheduled, completed, cancelled] plannedOn: type: string format: date description: scheduled 状态必填 administeredOn: type: string format: date description: completed 状态必填 nextDueOn: type: string format: date manufacturer: type: string maxLength: 128 batchNo: type: string maxLength: 64 notes: type: string maxLength: 1000 UpdateVaccinationRequest: type: object required: [version] properties: version: type: integer status: type: string enum: [scheduled, completed, cancelled] plannedOn: type: string format: date nullable: true administeredOn: type: string format: date nullable: true nextDueOn: type: string format: date nullable: true doseLabel: type: string maxLength: 64 nullable: true manufacturer: type: string maxLength: 128 nullable: true batchNo: type: string maxLength: 64 nullable: true notes: type: string maxLength: 1000 nullable: true VaccinationEnvelope: type: object required: [code, message, data] properties: code: type: integer enum: [0] message: type: string example: success data: $ref: '#/components/schemas/Vaccination' VaccinationListEnvelope: type: object required: [code, message, data] properties: code: type: integer enum: [0] message: type: string example: success data: type: array items: $ref: '#/components/schemas/Vaccination' # ========== Health Events ========== HealthEvent: type: object required: - eventId - petId - eventType - occurredAt - title - createdByUserId - createdAt - updatedAt - version properties: eventId: type: string format: uuid petId: type: string format: uuid eventType: type: string enum: [medical, feeding, deworming, grooming, measurement, note] occurredAt: type: string format: date-time description: 事件发生时间 title: type: string minLength: 1 maxLength: 160 notes: type: string nullable: true description: 备注(text 长文本) amountCents: type: integer format: int64 nullable: true minimum: 0 description: 金额(整数分) createdByUserId: type: string format: uuid description: 创建者用户 ID createdAt: type: string format: date-time updatedAt: type: string format: date-time version: type: integer CreateHealthEventRequest: type: object required: [eventType, occurredAt, title] properties: eventType: type: string enum: [medical, feeding, deworming, grooming, measurement, note] occurredAt: type: string format: date-time title: type: string minLength: 1 maxLength: 160 notes: type: string amountCents: type: integer format: int64 minimum: 0 description: 金额(整数分) UpdateHealthEventRequest: type: object required: [version] properties: version: type: integer title: type: string minLength: 1 maxLength: 160 notes: type: string nullable: true amountCents: type: integer format: int64 minimum: 0 nullable: true HealthEventEnvelope: type: object required: [code, message, data] properties: code: type: integer enum: [0] message: type: string example: success data: $ref: '#/components/schemas/HealthEvent' HealthEventListEnvelope: type: object required: [code, message, data] properties: code: type: integer enum: [0] message: type: string example: success data: type: object required: [items, hasMore] properties: items: type: array items: $ref: '#/components/schemas/HealthEvent' nextCursor: type: string nullable: true hasMore: type: boolean # ========== Care Reminders ========== CareReminder: type: object required: - reminderId - petId - reminderType - title - dueAt - status - createdAt - updatedAt properties: reminderId: type: string format: uuid petId: type: string format: uuid reminderType: type: string enum: [deworming, checkup, medication, other] title: type: string minLength: 1 maxLength: 160 dueAt: type: string format: date-time description: 到期时间 status: type: string enum: [pending, completed, dismissed] completedAt: type: string format: date-time nullable: true description: 完成时间(completed 状态必填) createdAt: type: string format: date-time updatedAt: type: string format: date-time CreateCareReminderRequest: type: object required: [reminderType, title, dueAt] properties: reminderType: type: string enum: [deworming, checkup, medication, other] title: type: string minLength: 1 maxLength: 160 dueAt: type: string format: date-time UpdateCareReminderRequest: type: object required: [status] properties: status: type: string enum: [pending, completed, dismissed] completedAt: type: string format: date-time description: 标记 completed 时必填 CareReminderEnvelope: type: object required: [code, message, data] properties: code: type: integer enum: [0] message: type: string example: success data: $ref: '#/components/schemas/CareReminder' CareReminderListEnvelope: type: object required: [code, message, data] properties: code: type: integer enum: [0] message: type: string example: success data: type: array items: $ref: '#/components/schemas/CareReminder' # ========== Pet Summary (Aggregation) ========== # TODO-FREEZE: 等待 T2-08 定型,字段为占位草案 PetSummary: type: object properties: petId: type: string format: uuid latestWeight: type: object nullable: true description: 最新体重 properties: weightKg: type: number format: double measuredAt: type: string format: date-time vaccinationProgress: type: object nullable: true description: 疫苗进度(口径待定:已完成剂次/总预期剂次) properties: completedDoses: type: integer description: 已完成剂次 totalDoses: type: integer description: 总剂次(口径待定) nextVaccination: type: object nullable: true description: 下次接种(scheduled 中最近 plannedOn 或最近 nextDueOn,优先级待定) properties: vaccineName: type: string plannedOn: type: string format: date monthlyExpense: type: object nullable: true description: 当月花费(月度边界口径待定:UTC 月或指定时区月) properties: amountCents: type: integer format: int64 description: 金额(整数分) month: type: string description: 月份标识(格式待定,如 2026-09) PetSummaryEnvelope: type: object required: [code, message, data] properties: code: type: integer enum: [0] message: type: string example: success data: $ref: '#/components/schemas/PetSummary'