Files
nepiedg 9200600b1c Enhance smoking tracking API with new features and improvements
- Added a new API endpoint `GET /api/v1/smoke/home` to consolidate core modules for the home dashboard, reducing the need for multiple requests.
- Updated the `smoke` routes to include the new home endpoint and improved user profile management with the addition of a `quit_date` field.
- Enhanced the algorithm for calculating daily targets and next smoke suggestions, ensuring accurate future time handling and user-specific recommendations.
- Improved API documentation to reflect new endpoints, response formats, and detailed field descriptions for better clarity and usability.
- Refactored user authentication handling in various handlers to streamline the process and ensure consistent error responses.
2026-01-29 17:16:35 +00:00

327 lines
9.3 KiB
Markdown
Raw Permalink 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.
# 戒烟算法与 AI 策略说明
## 1. 核心理念
采用**渐进式递减**策略,而非突然戒断:
- 科学研究表明,逐步减少比冷火鸡戒断成功率更高
- 通过延长抽烟间隔,让身体逐渐适应尼古丁减少
- AI 分析个人模式,提供个性化的递减计划
---
## 2. 默认递减算法 (staircase_delay_v1)
### 2.1 算法概述
```
下次建议时间 = 上次抽烟时间 + 基础间隔 + 奖励间隔
```
### 2.2 参数说明
| 参数 | 来源 | 默认值 | 说明 |
|------|------|--------|------|
| base_interval | profile.baseline_interval_minutes | 60 分钟 | 用户初始平均抽烟间隔(为空/0 时默认 60) |
| resisted_7d | 近7天忍住次数 | 0 | level=0,num=0 的记录数 |
| bonus_interval | 计算得出 | 0 | 奖励延长时间 |
### 2.3 奖励机制
每累计 **5 次忍住**,基础间隔 **+5 分钟**,最多 **+60 分钟**
```
bonus_interval = min(floor(resisted_7d / 5) * 5, 60)
final_interval = clamp(base_interval + bonus_interval, 5, 240)
```
**示例**
- 用户基础间隔 48 分钟,近 7 天忍住 12 次
- bonus = floor(12/5) * 5 = 10 分钟
- 最终间隔 = 48 + 10 = 58 分钟
### 2.4 睡眠规避
若计算出的时间落在睡眠区间,顺延到**下一次**起床时间(可能是当天也可能是次日):
```
if suggested_time in [sleep_time, wake_up_time]:
suggested_time = next_day_wake_up_time
```
### 2.5 边界与兜底
- 若没有历史记录,则以“当前时间”作为 `last_smoke_at` 参与计算。
- 若生成未来日期计划(如明天),默认建议不早于该日起床时间;未配置作息时按 `07:00` 处理。
### 2.6 算法流程图
```
获取上次抽烟时间 (last_smoke_at, 若无记录则取当前时间)
获取用户基础间隔 (base_interval_minutes)
统计近7天忍住次数 (resisted_7d)
计算奖励间隔: bonus = min(floor(resisted_7d / 5) * 5, 60)
计算建议时间: suggested = last_smoke_at + base + bonus (并限制在 5~240 分钟区间)
检查是否在睡眠时间?
├── 是 → 顺延到起床时间
└── 否 → 返回建议时间
```
### 2.7 过期/未来记录兜底
- 如果用户补录了“未来时间”的抽烟记录,为了避免前端出现负倒计时,服务端会把 `last_smoke_at` 限制在当前 `as_of` 时刻以内。
-`plan_date` 是“今天”且 `last_smoke_at + interval` 早于当前时间时,会根据已过去的分钟数计算需要补齐的间隔,向前跳跃若干次直到结果落在未来。这样首页和 `GET /next_smoke_time` 都能返回一个“未来的下一次建议时间”,不会出现提示已经过期的时间点。
- 如果 `plan_date` 是将来日期(例如明天的日程),仍然按照指定日期的起床时间作为 `not_before_at`,不会使用上述补齐逻辑。
---
## 3. AI 增强算法
### 3.1 AI 时间节点规划
当用户解锁 AI 功能后,系统会:
1. **收集近 3 天数据**
- 每次抽烟的时间点
- 每次忍住的时间点
- 抽烟原因/场景标签
2. **分析抽烟模式**
- 高峰时段识别(如下午 2-4 点)
- 触发场景识别(如压力、无聊、社交)
- 间隔规律分析
3. **生成个性化时间节点**
- 避开高峰时段的前半小时
- 在用户通常能忍住的时段设置节点
- 逐日递增间隔
### 3.2 AI 建议内容
AI 会生成以下内容(实际接口格式):
```json
{
"not_before_at": "2026-01-05T10:18:00+08:00",
"suggested_at": "2026-01-05T10:28:00+08:00",
"time_nodes": ["09:30", "12:30", "15:30", "19:00", "22:00"],
"advice": "昨天你的吸烟量比限额少了2支,这是一个巨大的胜利!数据显示你的烟瘾在下午2点左右达到顶峰——今天试着那个时候去散散步。"
}
```
### 3.3 AI Prompt 设计
```
你是一位专业的戒烟辅导教练。基于用户的抽烟数据,提供个性化的戒烟建议。
用户档案:
- 日均吸烟量:{baseline_cigs_per_day} 支
- 烟龄:{smoking_years} 年
- 抽烟动机:{smoke_motivations}
- 戒烟动力:{quit_motivations}
- 作息:{wake_up_time} - {sleep_time}
近3天数据:
{recent_logs}
请分析:
1. 用户的抽烟规律和高峰时段
2. 主要的触发场景
3. 成功忍住的模式
然后生成:
1. 一段鼓励性的分析总结(2-3句话)
2. 明天的建议抽烟时间节点(比今天少1支)
3. 2-3条实用的应对建议
```
---
## 4. 阶段划分
### 4.1 三阶段戒烟计划
| 阶段 | 时间 | 目标 | 策略 |
|------|------|------|------|
| 记录期 | Day 1-7 | 建立基线 | 正常抽烟,但每次都记录 |
| 减量期 | Day 8-21 | 减少 50% | 每周目标递减,AI 指导 |
| 巩固期 | Day 22-30 | 维持/归零 | 强化抵抗,心理建设 |
### 4.2 阶段进度计算
```javascript
// utils/stage.js
function calculateStage(startDate) {
const daysSinceStart = daysBetween(startDate, new Date())
if (daysSinceStart <= 7) {
return {
stage: 1,
name: '记录期',
progress: daysSinceStart / 7,
daysLeft: 7 - daysSinceStart
}
} else if (daysSinceStart <= 21) {
return {
stage: 2,
name: '减量期',
progress: (daysSinceStart - 7) / 14,
daysLeft: 21 - daysSinceStart
}
} else {
return {
stage: 3,
name: '巩固期',
progress: Math.min((daysSinceStart - 21) / 9, 1),
daysLeft: Math.max(30 - daysSinceStart, 0)
}
}
}
```
### 4.3 每日目标计算(线性递减)
以 onboarding 完成日期为起点,到 `quit_date` 线性递减到 0
```javascript
// utils/target.js
function calculateDailyTarget(baseline, startDate, quitDate, today) {
if (!baseline || !startDate || !quitDate) return baseline
if (today >= quitDate) return 0
const totalDays = daysBetween(startDate, quitDate)
if (totalDays <= 0) return baseline
const remainingDays = daysBetween(today, quitDate)
const target = Math.round(baseline * (remainingDays / totalDays))
return remainingDays > 0 ? Math.max(target, 1) : 0
}
```
---
## 5. 健康恢复计算(后端统一)
接口:`GET /api/v1/smoke/stats?range=week|month|year`(详见 `docs/smoke/API.md`
基于医学研究的恢复时间线:
| 时间点 | 恢复指标 | 计算方式 |
|--------|----------|----------|
| 20分钟 | 心率血压恢复正常 | 固定 |
| 8小时 | 血氧水平恢复 | 固定 |
| 24小时 | 心脏病风险开始下降 | 固定 |
| 48小时 | 嗅觉味觉开始恢复 | 固定 |
| 2周 | 肺功能提升 15% | 线性计算 |
| 1月 | 肺功能提升 30% | 线性计算 |
| 3月 | 肺功能提升 50% | 线性计算 |
| 1年 | 心脏病风险降低 50% | 线性计算 |
```javascript
// utils/health.js
function calculateLungRecovery(smokeFreeMinutes) {
const days = smokeFreeMinutes / (24 * 60)
if (days < 14) {
return (days / 14) * 15
} else if (days < 30) {
return 15 + ((days - 14) / 16) * 15
} else if (days < 90) {
return 30 + ((days - 30) / 60) * 20
} else {
return Math.min(50 + ((days - 90) / 275) * 50, 100)
}
}
```
---
## 6. 省钱计算(后端统一)
接口:`GET /api/v1/smoke/stats?range=week|month|year`(详见 `docs/smoke/API.md`
```javascript
// utils/money.js
function calculateMoneySaved(packPriceCent, cigsPerPack, baselineCigsPerDay, actualCigsTotal, days) {
const expectedTotal = baselineCigsPerDay * days
const savedCigs = expectedTotal - actualCigsTotal
const savedPacks = savedCigs / cigsPerPack
return Math.round(savedPacks * packPriceCent)
}
```
---
## 7. 激励语生成(后端统一)
接口:`GET /api/v1/smoke/motivation`(详见 `docs/smoke/API.md`
根据用户状态生成不同的激励语:
```javascript
// utils/motivation.js
function getMotivationMessage(context) {
const {
minutesSinceLast,
todayCount,
dailyTarget,
resistedToday,
quitMotivations
} = context
if (resistedToday > 0 && minutesSinceLast < 30) {
return '太棒了!你刚刚成功抵抗了一次烟瘾'
}
if (todayCount < dailyTarget * 0.5) {
return '今天的表现非常出色,继续保持!'
}
if (todayCount === dailyTarget - 1) {
return '还剩最后一支配额,考虑把它留到睡前?'
}
if (todayCount > dailyTarget) {
return `没关系,明天是新的一天。记住你为什么要戒烟:${quitMotivations[0]}`
}
return '保持连胜纪录!'
}
```
---
## 8. 数据分析指标
### 8.1 关键指标
| 指标 | 计算方式 | 用途 |
|------|----------|------|
| 日均吸烟量 | 周期内总量 / 天数 | 趋势对比 |
| 周同比变化 | (本周 - 上周) / 上周 | 进度评估 |
| 忍住成功率 | 忍住次数 / (忍住+抽烟次数) | 意志力评估 |
| 平均间隔 | 总时长 / 抽烟次数 | 递减效果 |
| 最长无烟时长 | 最大间隔记录 | 成就激励 |
| 较昨日减少 | 昨日支数 - 今日支数(可为负) | 若为负,表示今天超出昨日 |
### 8.2 周报数据结构
```javascript
// 周报数据结构
const weeklyReport = {
period: { start: '2026-01-01', end: '2026-01-07' },
totalCigs: 35,
dailyAverage: 5,
comparedToLastWeek: -20, // 百分比变化
resistedCount: 12,
longestGap: 180, // 分钟
peakHours: ['14:00', '21:00'],
topTriggers: ['压力大', '无聊'],
achievements: ['连续7天记录', '单日忍住5次'],
nextWeekTarget: 4
}
```