Files
wx_service/docs/smoke/API.md
T
nepiedg c9ebfd5873 Update algorithm and API documentation for smoking recovery and motivation features
- Added unified backend calculations for health recovery, savings, and motivation generation in the algorithm documentation.
- Updated API documentation to include new endpoints for retrieving statistics and motivation messages, enhancing clarity on data retrieval processes.
- Revised product documentation to reflect changes in API usage for health recovery and savings calculations, ensuring consistency across all related files.
2026-01-25 09:33:10 +00:00

516 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 戒烟/抽烟记录 API
所有接口前缀:`/api/v1/smoke`
除登录外都需要:`Authorization: Bearer <session_key>`(见:`docs/common/auth.md`
## 1) 新增记录
`POST /api/v1/smoke/logs`
请求体:
```json
{
"smoke_time": "2025-12-31",
"smoke_at": "2025-12-31 08:30:00",
"remark": "压力大",
"level": 2,
"num": 3
}
```
说明:
- `smoke_time` 可选;不传则默认“当天”。
- `smoke_at` 可选;真实抽烟时间(格式 `YYYY-MM-DD HH:MM:SS`)。用于“按时间节点分析/AI 建议”;不传则可用 `createtime` 近似。
- `level/num` 可选;不传时后端会按 `1` 处理。
- 如果要记录“想抽但忍住了”,请传 `level=0``num=0`(会在 `fa_smoke_log` 中展示为一条记录,但不会影响看板的支数累加)。
curl 示例:
```bash
curl -X POST 'http://127.0.0.1:8080/api/v1/smoke/logs' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer wx-session-key' \
-d '{"smoke_time":"2025-12-31","smoke_at":"2025-12-31 08:30:00","remark":"压力大","level":2,"num":3}'
```
成功响应示例(字段以实际为准):
```json
{
"code": 200,
"message": "success",
"data": {
"id": 5202,
"smoke_time": "2025-12-31T00:00:00+08:00",
"smoke_at": "2025-12-31T08:30:00+08:00",
"remark": "压力大",
"createtime": 1735600000,
"updatetime": 1735600000,
"deletetime": null,
"level": 2,
"num": 3
}
}
```
## 2) 获取单条记录
`GET /api/v1/smoke/logs/:id`
curl 示例:
```bash
curl -X GET 'http://127.0.0.1:8080/api/v1/smoke/logs/5202' \
-H 'Authorization: Bearer wx-session-key'
```
## 3) 列表查询(分页)
`GET /api/v1/smoke/logs?page=1&page_size=20&start=2025-12-01&end=2025-12-31`
参数:
- `page`:页码,默认 `1`
- `page_size`:每页数量,默认 `20`,最大 `200`
- `start/end`:可选,按 `smoke_time` 过滤(格式 `YYYY-MM-DD`
成功响应示例:
```json
{
"code": 200,
"message": "success",
"data": {
"items": [],
"total": 0,
"page": 1,
"page_size": 20
}
}
```
## 4) 获取看板概览
`GET /api/v1/smoke/dashboard?start=2026-01-01&end=2026-01-07`
参数:
- `start`:起始日期(含,格式 `YYYY-MM-DD`),默认“本周一”
- `end`:截止日期(含,格式 `YYYY-MM-DD`),默认“本周日”。若只传 `start``end` 默认为 `start + 6 天`
成功响应示例:
```json
{
"code": 200,
"message": "success",
"data": {
"today_count": 6,
"minutes_since_last": 42,
"weekly": [
{ "date": "2026-01-01", "count": 2, "is_today": false },
{ "date": "2026-01-02", "count": 1, "is_today": false },
{ "date": "2026-01-03", "count": 0, "is_today": false },
{ "date": "2026-01-04", "count": 0, "is_today": false },
{ "date": "2026-01-05", "count": 3, "is_today": true },
{ "date": "2026-01-06", "count": 0, "is_today": false },
{ "date": "2026-01-07", "count": 0, "is_today": false }
]
}
}
```
字段说明:
- `today_count`:当天吸烟总支数(累加 `num`
- `minutes_since_last`:距最后一次“实际抽烟”(忽略 `level=0 && num=0` 的忍住记录)的分钟数,通过最近一条 `smoke_at/smoke_time/createtime` 计算;若历史为空则字段不存在
- `weekly`:起止日期内每日汇总,`count` 为当日总支数,`is_today` 标记当前日期(即便不在 `start/end` 范围内也会标记为 `false`
## 5) 最近记录列表(轻量版)
`GET /api/v1/smoke/logs/latest?limit=20`
参数:
- `limit`:返回条数,默认 `20`,最大 `100`
成功响应示例:
```json
{
"code": 200,
"message": "success",
"data": {
"items": [
{
"id": 123,
"smoke_time": "2026-01-05T00:00:00+08:00",
"smoke_at": "2026-01-05T09:12:00+08:00",
"remark": "压力大",
"level": 3,
"num": 2,
"createtime": 1736049120
}
]
}
}
```
## 6) 更新记录
`POST /api/v1/smoke/logs/:id`
请求体(字段可选,按需传):
```json
{
"smoke_time": "2026-01-01",
"smoke_at": "2026-01-01 21:10:00",
"remark": "聚会",
"level": 3,
"num": 1
}
```
注意:
- 如果你想“清空 smoke_time”,请传空字符串:`{"smoke_time":""}`
- 如果你想“清空 smoke_at”,请传空字符串:`{"smoke_at":""}`
- 如果传 `null` 或者不传 `smoke_time`,后端会认为你没有修改该字段。
## 7) 删除记录(软删除)
`DELETE /api/v1/smoke/logs/:id`
成功响应:
```json
{
"code": 200,
"message": "success",
"data": {
"deleted": true
}
}
```
## 8) 获取 AI 戒烟建议(会员 + 广告解锁并行)
`GET /api/v1/smoke/ai/advice?date=2026-01-02`
说明:
- `date` 可选,默认“昨天”(建议针对哪一天的数据)。
- 权限:会员用户直接可用;非会员需要先对该 `date` 完成“看广告解锁”(见下一个接口)。
- 建议结果会按 `uid + date + prompt_version` 缓存(表:`fa_smoke_ai_advice`)。
未满足权限时的建议响应(示例):
```json
{
"code": 403,
"message": "需要会员或观看广告解锁后才可生成建议",
"data": {
"date": "2026-01-02",
"need": "vip_or_ad"
}
}
```
成功响应(示例):
```json
{
"code": 200,
"message": "success",
"data": {
"date": "2026-01-02",
"advice": "..."
}
}
```
## 9) 看广告解锁(用于非会员)
`POST /api/v1/smoke/ai/advice_unlocks`
请求体:
```json
{
"date": "2026-01-02",
"ad_watched_at": "2026-01-03 09:00:00"
}
```
说明:
- 该接口用于记录“已完成观看广告”,落库到 `fa_smoke_ai_advice_unlocks``uid + unlock_date` 唯一)。
- `ad_watched_at` 可由后端取当前时间;如需审计/对账可保留前端上报并做校验。
- 解锁是“按天”的:观看一次广告解锁一天内的 AI 生成功能(可用于「每日 AI 建议」以及「AI 下次抽烟时间节点」)。
- 如果你要生成“明天”的 AI 时间节点,请把 `date` 传为明天日期(例如 `2026-01-06`)。
## 10) 获取用户基础信息(首次进入:判断是否需要补全)
`GET /api/v1/smoke/profile`
说明:
- 首次进入小程序建议先调用该接口:若 `exists=false``is_completed=false`,前端进入“信息补全”流程。
- `baseline_interval_minutes` 用于建立初始基准:在用户清醒时段内的“平均间隔(分钟)”。计算:`awake_minutes / baseline_cigs_per_day`
- 若未提供作息时间(起床/入睡),后端会用默认清醒时长 `16*60=960` 分钟参与计算。
成功响应(示例):
```json
{
"code": 200,
"message": "success",
"data": {
"exists": true,
"profile": {
"id": 1,
"created_at": "2026-01-05T10:00:00+08:00",
"updated_at": "2026-01-05T10:00:00+08:00",
"baseline_cigs_per_day": 20,
"smoking_years": 8,
"pack_price_cent": 2500,
"smoke_motivations": ["压力大", "社交"],
"quit_motivations": ["身体健康", "省钱"],
"wake_up_time": "07:30",
"sleep_time": "23:30",
"onboarding_completed_at": "2026-01-05T10:00:00+08:00"
},
"is_completed": true,
"awake_minutes": 960,
"baseline_interval_minutes": 48
}
}
```
`exists=false`(尚未补全)时,响应示例:
```json
{
"code": 200,
"message": "success",
"data": {
"exists": false,
"is_completed": false,
"awake_minutes": 960,
"baseline_interval_minutes": 0
}
}
```
字段用途(补全页面可参考):
- `baseline_cigs_per_day`(基础烟量/日均抽烟支数):建立初始基准,计算初始建议间隔时长。
- `smoking_years`(烟龄/年)+ `pack_price_cent`(单包价格/分):用于看板计算“已省金额”和“恢复时长”等指标(公式可在看板端实现)。
- `smoke_motivations`(抽烟动机):如 `压力大/无聊/社交/提神`,用于 AI 在分析 remark 时更有针对性。
- `quit_motivations`(戒烟动力):如 `身体健康/家人孩子/省钱`,当用户产生动摇时 AI 可用这些信息做“情感阻断/自我提醒”。
- `wake_up_time` + `sleep_time`(作息时间):用于自动规避睡眠时间,防止在用户睡觉时提醒其“坚持”。
## 11) 补全/更新用户基础信息(Upsert)
`POST /api/v1/smoke/profile`
说明:
- 字段按需传;首次进入建议一次性补全。
- 作息时间格式:`HH:MM`24 小时制),例如 `07:30``23:10`
- `pack_price_cent` 为“分”;若前端用“元”,请乘以 100。
请求体(示例):
```json
{
"baseline_cigs_per_day": 20,
"smoking_years": 8,
"pack_price_cent": 2500,
"smoke_motivations": ["压力大", "社交"],
"quit_motivations": ["身体健康", "省钱"],
"wake_up_time": "07:30",
"sleep_time": "23:30"
}
```
成功响应:同 `GET /api/v1/smoke/profile`(返回最新 `profile` + `is_completed` + `baseline_interval_minutes`)。
## 12) 想抽但忍住了(写入一条 level=0,num=0 的记录)
`POST /api/v1/smoke/logs/resisted`
请求体(示例):
```json
{
"smoke_time": "2026-01-05",
"smoke_at": "2026-01-05 10:20:00",
"remark": "压力大,想抽但忍住了"
}
```
说明:
- 该接口会在 `fa_smoke_log` 中新增一条记录:`level=0``num=0`,用于更直观记录“想抽/忍住”的过程。
- 这类记录不会影响 `today_count/weekly.count` 的支数统计(因为 `num=0`)。
## 13) 获取“下次抽烟记录时间”(默认 + AI 自动切换)
`GET /api/v1/smoke/next_smoke_time`
说明:
- 用于首页展示“建议的下次记录时间”。
- 已整合首页所需汇总字段(上次抽烟时间/今日抽烟支数/今日克制次数/较昨日减少支数)。
- 如果指定日期存在 AI 给出的时间节点(`time_nodes` 不为空),则优先使用 AI 的建议;否则使用默认策略。
- 可选参数:
- `date`:计划日期(默认今天),支持 `YYYY-MM-DD``today/tomorrow`
- `mode`(默认 `auto`
- `auto`:只在已存在 AI 时间节点时使用 AI(不主动生成)
- `ai`:生成该 `date` 的 AI 时间节点(需要先看广告解锁;生成一次缓存一天)
- `default`:永远返回默认策略
默认策略(不使用 AI):
- 基础间隔:优先使用 `GET /api/v1/smoke/profile` 返回的 `baseline_interval_minutes`;若不存在则默认 `60` 分钟。
- 阶梯式延时:最近 7 天内每累计 `5` 条“忍住记录(level=0,num=0)”,在基础间隔上 `+5` 分钟(最多 `+60` 分钟)。
- 间隔兜底:最终间隔会限制在 `5~240` 分钟之间。
- 若用户已补全作息时间,会自动规避睡眠区间:若计算出的时间落在睡眠区间,顺延到下一次起床时间。
AI 生成说明:
-`mode=ai` 时,会把最近 3 天的抽烟数据(含“忍住记录”)作为输入提供给 AI,用于更贴合近期模式生成时间节点。
- 未解锁时会返回 `403`:提示需要观看广告解锁。
成功响应(示例:回落到默认):
```json
{
"code": 200,
"message": "success",
"data": {
"source": "default",
"not_before_at": "2026-01-05T10:18:00+08:00",
"suggested_at": "2026-01-05T10:18:00+08:00",
"last_smoke_at": "2026-01-05T09:30:00+08:00",
"today_count": 3,
"resisted_count": 1,
"reduced_from_yesterday": 2,
"exceeded_yesterday": false,
"default": {
"last_smoke_at": "2026-01-05T09:30:00+08:00",
"next_smoke_at": "2026-01-05T10:18:00+08:00",
"base_interval_minutes": 48,
"interval_minutes": 48,
"stage": 0,
"resisted_7d": 3,
"sleep_adjusted": false,
"algorithm": "staircase_delay_v1",
"as_of": "2026-01-05T10:00:00+08:00"
}
}
}
```
当存在 AI 建议且包含 `time_nodes` 时,响应会是(示例):
```json
{
"code": 200,
"message": "success",
"data": {
"source": "ai",
"not_before_at": "2026-01-05T10:18:00+08:00",
"suggested_at": "2026-01-05T10:28:00+08:00",
"last_smoke_at": "2026-01-05T09:30:00+08:00",
"today_count": 3,
"resisted_count": 1,
"reduced_from_yesterday": 2,
"exceeded_yesterday": false,
"time_nodes": ["10:30", "11:10", "14:00", "16:30"],
"advice": "先把这次冲动延后到10:28,期间做一次5分钟快走+喝水,压力场景用深呼吸替代。",
"default": { "algorithm": "staircase_delay_v1" },
"ai": {
"plan_date": "2026-01-05",
"not_before_at": "2026-01-05T10:18:00+08:00",
"suggested_at": "2026-01-05T10:28:00+08:00",
"time_nodes": ["10:30", "11:10", "14:00", "16:30"],
"advice": "先把这次冲动延后到10:28,期间做一次5分钟快走+喝水,压力场景用深呼吸替代。",
"prompt_version": "v1",
"model": "gpt-4.1-mini",
"provider": "openai-compatible"
}
}
}
```
字段说明(新增首页字段):
- `last_smoke_at`:上次“实际抽烟”时间(忽略忍住记录),格式 `RFC3339`(含时区)。
- `today_count`:今日抽烟支数(累加 `num`)。
- `resisted_count`:今日克制次数(`level=0 && num=0`)。
- `reduced_from_yesterday`:较昨日减少的支数(允许为负数;为负时表示“今天超出昨日”)。
- `exceeded_yesterday`:是否超出昨日(`true` 表示今天超出昨日,前端可用作单独标识)。
## 14) 数据统计分析(趋势 + 健康 + 省钱)
`GET /api/v1/smoke/stats?range=week|month|year&date=2026-01-07`
参数:
- `range``week|month|year`,默认 `week`
- `date`:锚点日期(`YYYY-MM-DD`),默认今天
说明:
- 用于“统计页”一屏数据整合(趋势、均值、环比、健康恢复、省钱、连续记录、已拒绝次数)。
- `trend_unit``day``month`,用于前端图表横轴显示。
成功响应(示例):
```json
{
"code": 200,
"message": "success",
"data": {
"range": "week",
"start": "2026-01-01",
"end": "2026-01-07",
"trend_unit": "day",
"trend": [
{ "label": "2026-01-01", "count": 2 },
{ "label": "2026-01-02", "count": 1 },
{ "label": "2026-01-03", "count": 0 },
{ "label": "2026-01-04", "count": 0 },
{ "label": "2026-01-05", "count": 3 },
{ "label": "2026-01-06", "count": 0 },
{ "label": "2026-01-07", "count": 0 }
],
"daily_average": 4,
"change_percent": -20,
"money": {
"available": true,
"pack_price_cent": 2500,
"cigs_per_pack": 20,
"expected_total": 140,
"actual_total": 92,
"saved_cent": 6000
},
"health": {
"available": true,
"smoke_free_minutes": 420,
"lung_recovery_percent": 12,
"milestones": [
{ "name": "心率血压恢复正常", "minutes": 20, "reached": true },
{ "name": "血氧水平恢复", "minutes": 480, "reached": false }
]
},
"streak_days": 12,
"resisted_total": 24
}
}
```
字段说明:
- `change_percent`:与上一个同周期对比的变化比例(可为负)。
- `money.available=false`:表示缺少 `baseline_cigs_per_day``pack_price_cent`
- `health.available=false`:表示无历史记录。
## 15) 激励语(后端统一生成)
`GET /api/v1/smoke/motivation`
说明:
- 基于当日数据(如 `today_count``resisted_count``last_smoke_at`)与 `quit_motivations` 生成一句激励语。
成功响应(示例):
```json
{
"code": 200,
"message": "success",
"data": {
"message": "今天的表现很稳,继续保持!记住你的目标:身体健康。",
"type": "encourage"
}
}
```