Files
smt/docs/api.md
T

429 lines
13 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`,系统以 `num=0` 作为“忍住”的判断条件(计入忍住次数,但不计入抽烟支数)。
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?page=1&page_size=20&start=2025-12-01&end=2025-12-31&type=all`
参数:
- `page`:页码,默认 `1`
- `page_size`:每页数量,默认 `20`,最大 `200`
- `start/end`:可选,按 `smoke_time` 过滤(格式 `YYYY-MM-DD`
- `type`:可选,默认 `all``smoke` 表示抽烟记录(`num>0`),`resisted` 表示忍住记录(`num=0`
说明:
- 列表按时间倒序返回(优先 `smoke_at`,其次 `createtime`,最后 `smoke_time`)。
成功响应示例:
```json
{
"code": 200,
"message": "success",
"data": {
"items": [],
"total": 0,
"page": 1,
"page_size": 20
}
}
```
## 3) 更新记录
`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`,后端会认为你没有修改该字段。
## 4) 删除记录(软删除)
`DELETE /api/v1/smoke/logs/:id`
成功响应:
```json
{
"code": 200,
"message": "success",
"data": {
"deleted": true
}
}
```
## 5) 首页整合接口(Home
`GET /api/v1/smoke/home`
此接口只返回首页、AI 时间页和 AI 日总结页正在消费的核心字段,避免生成或传递无用模块。返回示例:
```json
{
"code": 200,
"message": "success",
"data": {
"timer": {
"seconds_since_last": 9900,
"next_suggested_at": "2026-01-05T10:30:00+08:00",
"next_suggested_clock": "10:30",
"suggestion_source": "default"
},
"summary": {
"today_count": 3,
"daily_target": 10,
"reduced_from_yesterday": 2,
"exceeded_yesterday": false
},
"motivation": {
"message": "太棒了!你刚刚成功抵抗了一次烟瘾",
"type": "praise"
}
}
}
```
字段说明:
- `timer.seconds_since_last`:距上次抽烟的秒数(无记录返回 `-1`)。
- `timer.next_suggested_at`:建议下次抽烟时间(RFC3339)。
- `timer.next_suggested_clock`:仅时分显示(如“16:30”)。
- `timer.suggestion_source`:建议来源(`default`/`ai`)。
- `summary.today_count`:今日吸烟支数累加。
- `summary.daily_target`:每日目标。
- `summary.reduced_from_yesterday`:与昨日的绝对差值(非负)。
- `summary.exceeded_yesterday`:是否比昨天多。
- `daily_summary`:当天已缓存的 AI 日总结;无缓存时为 `null`
- `motivation.message`:激励语文案。
- `motivation.type`:激励语类型。
如需生成 AI 时间节点,请调用 `GET /api/v1/smoke/ai/next_smoke_time`;首页接口只读取缓存,不主动生成 AI 建议,避免额外性能成本。
## 10) 看广告解锁(用于非会员)
`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`)。
## 11) 获取用户基础信息(首次进入:判断是否需要补全)
`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",
"quit_date": "2026-02-28T00:00:00+08:00",
"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`(作息时间):用于自动规避睡眠时间,防止在用户睡觉时提醒其“坚持”。
- `quit_date`(目标戒烟日期):用于阶段规划或到期提醒。
## 12) 补全/更新用户基础信息(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",
"quit_date": "2026-02-28"
}
```
成功响应:同 `GET /api/v1/smoke/profile`(返回最新 `profile` + `is_completed` + `baseline_interval_minutes`)。
## 13) 获取 AI 下次抽烟建议
`GET /api/v1/smoke/ai/next_smoke_time`
说明:
- 用于 AI 建议页生成当天时间节点。
- 首页只通过 `GET /smoke/home` 读取已缓存的 AI 结果,不主动生成 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": "ai",
"suggested_at": "2026-01-05T10:28:00+08:00",
"time_nodes": ["10:30", "11:10", "14:00", "16:30"],
"advice": "先把这次冲动延后到10:28,期间做一次5分钟快走+喝水,压力场景用深呼吸替代。"
}
}
```
## 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`
- `money.expected_total`:按“统计周期内有记录的天数”×`baseline_cigs_per_day` 计算;不统计无日志的天数。
- `money.saved_cent`:按 `max(expected_total - actual_total, 0)` 计算,避免出现负值。
- `health.available=false`:表示无历史记录。
## 15) 成就主题与当前称号
`GET /api/v1/smoke/achievement/themes`
返回 onboarding 可选择的称号主题。每个主题包含 `icon/name/key/levels`,前端在“重填问卷”中展示。
`GET /api/v1/smoke/achievement`
返回当前用户所选主题下的当前等级、下一等级和进度。记录抽烟模式使用“少抽积分”进阶;戒烟打卡模式仍使用连续记录天数进阶。
记录模式少抽积分算法:
- 统计窗口:从 `onboarding_completed_at` 到今天,最多回看最近 90 天。
- 只统计有记录的日期,避免无记录日期被误判为 0 根。
- 每日基础分:`max(0, baseline_cigs_per_day - 当日抽烟支数)`
- 当日少抽比例 `>=30%` 额外 `+1` 分。
- 当日少抽比例 `>=60%` 额外 `+2` 分。
- 当日抽烟支数为 `0` 额外 `+3` 分。
- 总积分用于匹配所选主题的等级阈值;`fa_achievement_level.required_days` 在记录模式下作为 `required_score` 使用。
成功响应示例:
```json
{
"code": 200,
"message": "success",
"data": {
"achievement": {
"theme_id": 1,
"theme_name": "意志骑士",
"theme_key": "knight",
"theme_icon": "🛡️",
"metric_type": "score",
"score": 48,
"metrics": {
"baseline_cigs_per_day": 10,
"scored_days": 7,
"total_reduced_cigs": 36,
"today_reduced_cigs": 4,
"today_reduce_percent": 40,
"stable_days": 6
},
"current": {
"name": "见习骑士",
"icon": "🛡️",
"required_score": 30
},
"next": {
"name": "白银骑士",
"icon": "⚔️",
"required_score": 80
},
"progress": 0.36
}
}
}
```