{
  "openapi": "3.1.0",
  "info": {
    "title": "SOMO 健康助手",
    "version": "1.0.0",
    "description": "本人饮食、补剂、实际服药和实际运动收件箱。先创建草稿，核对后确认，写入原有 SOMO 数据。"
  },
  "servers": [
    {
      "url": "https://sega-low-useful-cents.trycloudflare.com"
    }
  ],
  "security": [
    {
      "somoBearer": []
    }
  ],
  "paths": {
    "/api/v1/assistant/status": {
      "get": {
        "operationId": "getSomoAssistantStatus",
        "summary": "检查 SOMO 助手连接和上海时区",
        "parameters": [],
        "x-openai-isConsequential": false,
        "responses": {
          "200": {
            "description": "成功返回；只有 committed 与 receipt 表示已经入账",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {},
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "输入需核对"
          },
          "401": {
            "description": "连接未授权"
          },
          "403": {
            "description": "不能访问其他成员"
          },
          "409": {
            "description": "版本冲突、重复消息或运动需核对"
          },
          "422": {
            "description": "仍有阻止入账的未知或歧义"
          }
        }
      }
    },
    "/api/v1/assistant/context": {
      "get": {
        "operationId": "searchSomoContext",
        "summary": "查询本人产品、食物参考、营养目录或同日运动",
        "parameters": [
          {
            "name": "kind",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "products",
                "foods",
                "nutrients",
                "exercises"
              ]
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "产品或食物关键词；查询运动时填 YYYY-MM-DD 日期"
          }
        ],
        "x-openai-isConsequential": false,
        "responses": {
          "200": {
            "description": "成功返回；只有 committed 与 receipt 表示已经入账",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {},
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "输入需核对"
          },
          "401": {
            "description": "连接未授权"
          },
          "403": {
            "description": "不能访问其他成员"
          },
          "409": {
            "description": "版本冲突、重复消息或运动需核对"
          },
          "422": {
            "description": "仍有阻止入账的未知或歧义"
          }
        }
      }
    },
    "/api/v1/assistant/foods/{foodCode}": {
      "get": {
        "operationId": "getSomoFoodReference",
        "summary": "查看具体食物品种、形态和参考营养",
        "parameters": [
          {
            "name": "foodCode",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "x-openai-isConsequential": false,
        "responses": {
          "200": {
            "description": "成功返回；只有 committed 与 receipt 表示已经入账",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {},
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "输入需核对"
          },
          "401": {
            "description": "连接未授权"
          },
          "403": {
            "description": "不能访问其他成员"
          },
          "409": {
            "description": "版本冲突、重复消息或运动需核对"
          },
          "422": {
            "description": "仍有阻止入账的未知或歧义"
          }
        }
      }
    },
    "/api/v1/assistant/drafts": {
      "get": {
        "operationId": "listSomoDrafts",
        "summary": "查看待核对或已保存的本人助手记录",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "committed",
                "dismissed"
              ]
            }
          },
          {
            "name": "date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          }
        ],
        "x-openai-isConsequential": false,
        "responses": {
          "200": {
            "description": "成功返回；只有 committed 与 receipt 表示已经入账",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {},
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "输入需核对"
          },
          "401": {
            "description": "连接未授权"
          },
          "403": {
            "description": "不能访问其他成员"
          },
          "409": {
            "description": "版本冲突、重复消息或运动需核对"
          },
          "422": {
            "description": "仍有阻止入账的未知或歧义"
          }
        }
      },
      "post": {
        "operationId": "createSomoDraft",
        "summary": "提交一类实际记录的待核对草稿，不进入摄入统计",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Draft"
              }
            }
          }
        },
        "x-openai-isConsequential": false,
        "responses": {
          "200": {
            "description": "成功返回；只有 committed 与 receipt 表示已经入账",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {},
                  "additionalProperties": true
                }
              }
            }
          },
          "201": {
            "description": "成功返回；只有 committed 与 receipt 表示已经入账",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {},
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "输入需核对"
          },
          "401": {
            "description": "连接未授权"
          },
          "403": {
            "description": "不能访问其他成员"
          },
          "409": {
            "description": "版本冲突、重复消息或运动需核对"
          },
          "422": {
            "description": "仍有阻止入账的未知或歧义"
          }
        }
      }
    },
    "/api/v1/assistant/drafts/{id}": {
      "get": {
        "operationId": "getSomoDraft",
        "summary": "读取草稿、原描述、计算依据和阻止入账的项目",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "x-openai-isConsequential": false,
        "responses": {
          "200": {
            "description": "成功返回；只有 committed 与 receipt 表示已经入账",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {},
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "输入需核对"
          },
          "401": {
            "description": "连接未授权"
          },
          "403": {
            "description": "不能访问其他成员"
          },
          "409": {
            "description": "版本冲突、重复消息或运动需核对"
          },
          "422": {
            "description": "仍有阻止入账的未知或歧义"
          }
        }
      },
      "patch": {
        "operationId": "updateSomoDraft",
        "summary": "更新或撤回待核对草稿，不能改写已保存记录",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "revision": {
                    "type": "integer"
                  },
                  "payload": {
                    "$ref": "#/components/schemas/Payload"
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "dismissed"
                    ]
                  }
                },
                "required": [
                  "revision"
                ]
              }
            }
          }
        },
        "x-openai-isConsequential": false,
        "responses": {
          "200": {
            "description": "成功返回；只有 committed 与 receipt 表示已经入账",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {},
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "输入需核对"
          },
          "401": {
            "description": "连接未授权"
          },
          "403": {
            "description": "不能访问其他成员"
          },
          "409": {
            "description": "版本冲突、重复消息或运动需核对"
          },
          "422": {
            "description": "仍有阻止入账的未知或歧义"
          }
        }
      }
    },
    "/api/v1/assistant/drafts/{id}/confirm": {
      "post": {
        "operationId": "confirmSomoDraft",
        "summary": "用户核对本次实际内容后写入 SOMO，并返回保存凭据",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "revision": {
                    "type": "integer"
                  },
                  "reviewed": {
                    "type": "boolean"
                  },
                  "duplicateChecked": {
                    "type": "boolean",
                    "description": "运动经过核对，确定不是 Garmin 或已有手动记录的重复"
                  }
                },
                "required": [
                  "revision",
                  "reviewed"
                ]
              }
            }
          }
        },
        "x-openai-isConsequential": true,
        "responses": {
          "200": {
            "description": "成功返回；只有 committed 与 receipt 表示已经入账",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {},
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "输入需核对"
          },
          "401": {
            "description": "连接未授权"
          },
          "403": {
            "description": "不能访问其他成员"
          },
          "409": {
            "description": "版本冲突、重复消息或运动需核对"
          },
          "422": {
            "description": "仍有阻止入账的未知或歧义"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "somoBearer": {
        "type": "http",
        "scheme": "bearer"
      }
    },
    "schemas": {
      "Nutrient": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "value": {
            "type": [
              "number",
              "null"
            ]
          },
          "unit": {
            "type": "string",
            "description": "营养素目录中的标准单位",
            "maxLength": 12
          },
          "status": {
            "type": "string",
            "enum": [
              "numeric",
              "trace",
              "missing"
            ]
          },
          "source": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "label"
                ]
              },
              "label": {
                "type": "string",
                "description": "实际包装标签来源",
                "maxLength": 200
              }
            },
            "required": [
              "kind",
              "label"
            ]
          }
        },
        "required": [
          "unit",
          "status",
          "source"
        ]
      },
      "Food": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "description": "具体食物名称",
            "maxLength": 80
          },
          "amount": {
            "type": [
              "number",
              "null"
            ],
            "description": "实际可食份量，未知用 null；不可从一把、一瓶猜克数",
            "minimum": 0,
            "maximum": 10000
          },
          "unit": {
            "type": "string",
            "enum": [
              "g",
              "ml",
              "个",
              "份",
              "碗",
              "杯"
            ]
          },
          "accuracy": {
            "type": "string",
            "enum": [
              "measured",
              "estimated",
              "unknown"
            ]
          },
          "preparation": {
            "type": "string",
            "description": "生熟、鲜干或烹饪形态",
            "maxLength": 80
          },
          "foodCode": {
            "type": "string",
            "description": "从搜索结果选择的参考编号",
            "maxLength": 80
          },
          "referenceConfirmed": {
            "type": "boolean",
            "description": "用户已明确确认参考食物的品种与形态"
          },
          "gramsPerUnit": {
            "type": [
              "number",
              "null"
            ],
            "description": "有依据时每单位可食克数",
            "minimum": 0,
            "maximum": 10000
          },
          "conversionSource": {
            "type": "string",
            "description": "称量、份量或密度换算依据",
            "maxLength": 200
          },
          "labelBasis": {
            "type": "string",
            "enum": [
              "portion",
              "100g",
              "100ml"
            ]
          },
          "labelSource": {
            "type": "string",
            "description": "实际包装标签来源",
            "maxLength": 200
          },
          "labelNutrients": {
            "type": "object",
            "description": "营养素 key 来自目录，只填标签实际数值，禁止模型估算",
            "additionalProperties": {
              "$ref": "#/components/schemas/Nutrient"
            },
            "properties": {}
          }
        },
        "required": [
          "name",
          "unit"
        ]
      },
      "Payload": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "date": {
            "type": "string",
            "format": "date",
            "description": "实际记录日期，按上海时区；不明确需询问"
          },
          "time": {
            "type": [
              "string",
              "null"
            ],
            "description": "上海时间 HH:mm，未知为 null"
          },
          "actual": {
            "type": "boolean",
            "description": "用户明确实际吃下、服药或已完成运动才为 true"
          },
          "note": {
            "type": "string",
            "description": "补充说明",
            "maxLength": 200
          },
          "mealType": {
            "type": "string",
            "enum": [
              "breakfast",
              "lunch",
              "dinner",
              "snack",
              "unknown"
            ]
          },
          "foods": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "$ref": "#/components/schemas/Food"
            }
          },
          "name": {
            "type": "string",
            "description": "产品或运动的具体名称，选产品时与目录名称一致",
            "maxLength": 100
          },
          "supplementId": {
            "type": "string",
            "description": "本人补剂产品 ID，不是药品 ID",
            "maxLength": 160
          },
          "medicationId": {
            "type": "string",
            "description": "本人药品目录 ID，必须确认具体规格",
            "maxLength": 160
          },
          "dose": {
            "type": [
              "number",
              "null"
            ],
            "description": "本次实际服用数量，不从计划默认推断",
            "minimum": 0,
            "maximum": 10000
          },
          "unit": {
            "type": "string",
            "description": "本次服用单位，应与所选补剂标签一致",
            "maxLength": 16
          },
          "labelSource": {
            "type": "string",
            "description": "实际标签来源",
            "maxLength": 200
          },
          "labelNutrients": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/Nutrient"
            },
            "properties": {}
          },
          "type": {
            "type": "string",
            "enum": [
              "running",
              "walking",
              "cycling",
              "swimming",
              "strength",
              "basketball",
              "general"
            ]
          },
          "durationMinutes": {
            "type": [
              "number",
              "null"
            ],
            "description": "已完成运动时长，分钟，未知为 null",
            "minimum": 0,
            "maximum": 10000
          },
          "distanceKm": {
            "type": [
              "number",
              "null"
            ],
            "description": "实际距离，公里，未知为 null",
            "minimum": 0,
            "maximum": 10000
          },
          "sets": {
            "type": [
              "number",
              "null"
            ],
            "description": "实际组数",
            "minimum": 0,
            "maximum": 10000
          },
          "reps": {
            "type": [
              "number",
              "null"
            ],
            "description": "每组实际次数",
            "minimum": 0,
            "maximum": 10000
          },
          "loadKg": {
            "type": [
              "number",
              "null"
            ],
            "description": "实际负重 kg，允许真实零，未知为 null",
            "minimum": 0,
            "maximum": 10000
          }
        },
        "required": [
          "date",
          "actual"
        ]
      },
      "Draft": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "requestId": {
            "type": "string",
            "description": "为本次消息及记录类型生成唯一编号，重试必须使用同一编号",
            "maxLength": 160
          },
          "kind": {
            "type": "string",
            "enum": [
              "food",
              "supplement",
              "medication",
              "exercise"
            ]
          },
          "payload": {
            "$ref": "#/components/schemas/Payload"
          },
          "source": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "text": {
                "type": "string",
                "description": "保留用户原描述；照片使用识别摘要，禁止发送 base64 或文件内容",
                "maxLength": 2000
              },
              "mode": {
                "type": "string",
                "enum": [
                  "text",
                  "photo",
                  "voice"
                ]
              }
            },
            "required": [
              "text",
              "mode"
            ]
          }
        },
        "required": [
          "requestId",
          "kind",
          "payload",
          "source"
        ]
      }
    }
  }
}