feat: 健康洞察——把散落的记录变成能看懂的结论
之前用户记几十条数据只换回一条折线,数据没有回到他身上,所以不记也不亏。 新增 GET /api/pets/:id/insights,只看最近 90 天(更早的对「现在怎么样」 没参考价值,还会让关联分析找出一堆巧合),产出最多 6 条按严重度排序的结论: 1. 体重趋势 —— 单次数字没意义,跨度 ≥14 天的连续同向变化才算。 跌 10% 报警建议血检,涨 15% 提醒控制,跌 5% 持续观察 2. 换粮 → 软便的时间关联 —— 找异常排便前 72 小时内的饮食记录。 这是用户最看不出来的一类:两条记录隔两三天、分属不同类型,翻时间轴翻不出来 3. 异常扎堆 —— 两周内 ≥2 次异常。偶发一次和反复出现完全不是一回事 4. 逾期提醒 —— 设了不看等于没设 5. 同龄体重对比 —— 同物种、月龄差 2 个月内其它宠物的中位数。 样本 <8 只直接不显示:拿 3 只算出来的「中位数」比不给还糟 6. 记录习惯 —— 数据不足时negative空手而归,给一句「再记几条就能看出规律」 每条都带 evidence(支撑它的具体数据)和 action(点击直接跳到对应记录入口), 结论不能只是断言。文案沿用 AI 助手同一套护栏:不做诊断,异常一律写清 什么时候必须就医。 前端落在记录页体重趋势卡上方,三档配色区分严重度。 实测(造 4 条体重下滑 + 换粮 + 软便 + 两次异常): 🔴 体重掉了 13%,建议就医检查(39 天内 4.50→3.90kg) 🔴 两周内记了 2 次异常 🟠 这次「软便」之前 2 天有过饮食变化(7月20日换粮 → 7月22日软便) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -93,6 +93,12 @@ function buildChart(points) {
|
||||
return { svg: 'data:image/svg+xml,' + encodeURIComponent(svg), dots };
|
||||
}
|
||||
|
||||
// 洞察等级 → 图标。alert 用警告号,其余按类别给
|
||||
const INS_ICON = { alert: 'warn', warn: 'warn', info: 'trend' };
|
||||
function mapInsight(i) {
|
||||
return { ...i, icon: i.level === 'info' ? (INS_ICON.info) : INS_ICON.alert };
|
||||
}
|
||||
|
||||
const REC_PAGE = 6;
|
||||
|
||||
Page({
|
||||
@@ -105,6 +111,7 @@ Page({
|
||||
recFilters: REC_FILTERS,
|
||||
recFilterIdx: 0,
|
||||
recordItems: RECORD_ITEMS,
|
||||
insights: [],
|
||||
trendSvg: '',
|
||||
trendStats: null,
|
||||
trendDots: [],
|
||||
@@ -165,6 +172,19 @@ Page({
|
||||
},
|
||||
});
|
||||
},
|
||||
// 健康洞察:把散落的记录变成结论。点一条直接跳到对应的记录入口
|
||||
loadInsights() {
|
||||
const id = store.currentPetId();
|
||||
if (!id) return this.setData({ insights: [] });
|
||||
api
|
||||
.petInsights(id)
|
||||
.then((list) => this.setData({ insights: (list || []).map(mapInsight) }))
|
||||
.catch(() => this.setData({ insights: [] }));
|
||||
},
|
||||
onTapInsight(e) {
|
||||
const it = this.data.insights[e.currentTarget.dataset.index];
|
||||
if (it && it.action) this.openSheetType(it.action);
|
||||
},
|
||||
loadRecords() {
|
||||
const id = store.currentPetId();
|
||||
if (!id) return;
|
||||
@@ -176,6 +196,7 @@ Page({
|
||||
})
|
||||
.catch((e) => toastErr(e));
|
||||
this.loadTrend();
|
||||
this.loadInsights();
|
||||
},
|
||||
loadMoreRecords() {
|
||||
const id = store.currentPetId();
|
||||
|
||||
@@ -20,6 +20,18 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{insights.length}}" class="card">
|
||||
<view class="section-head"><view class="sh-title">健康洞察</view><view class="tiny">来自你的记录</view></view>
|
||||
<view wx:for="{{insights}}" wx:key="title" class="ins ins-{{item.level}}" data-index="{{index}}" bindtap="onTapInsight">
|
||||
<view class="ins-head">
|
||||
<view class="ins-ic"><pt-icon name="{{item.icon}}" size="{{30}}"></pt-icon></view>
|
||||
<view class="ins-title">{{item.title}}</view>
|
||||
</view>
|
||||
<view class="ins-detail">{{item.detail}}</view>
|
||||
<view class="ins-ev">{{item.evidence}}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<view class="section-head"><view class="sh-title">体重趋势</view><view class="link" data-type="weight" bindtap="openSheet">记录</view></view>
|
||||
<view wx:if="{{trendStats}}" class="trend-stats">最新 {{trendStats.latest}}kg · 较首次 {{trendStats.delta >= 0 ? '+' : ''}}{{trendStats.delta}}kg · 共 {{trendStats.count}} 次</view>
|
||||
|
||||
@@ -42,3 +42,18 @@
|
||||
margin-top:var(--sp-3);padding:var(--sp-3);text-align:center;color:var(--primary-dark);
|
||||
background:var(--surface-2);border-radius:var(--r-md);font-size:var(--fs-sm);font-weight:var(--fw-b);
|
||||
}
|
||||
|
||||
/* 健康洞察 */
|
||||
.ins{border-radius:var(--r-md);padding:var(--sp-4);margin-bottom:var(--sp-2)}
|
||||
.ins:last-child{margin-bottom:0}
|
||||
.ins-info{background:var(--surface-2)}
|
||||
.ins-warn{background:var(--primary-soft)}
|
||||
.ins-alert{background:var(--red-soft)}
|
||||
.ins-head{display:flex;align-items:flex-start;gap:var(--sp-2)}
|
||||
.ins-ic{flex:none;margin-top:2rpx}
|
||||
.ins-info .ins-ic{color:var(--green-ink)}
|
||||
.ins-warn .ins-ic{color:var(--primary-ink)}
|
||||
.ins-alert .ins-ic{color:var(--red-ink)}
|
||||
.ins-title{flex:1;font-size:var(--fs-md);font-weight:var(--fw-b);line-height:1.4}
|
||||
.ins-detail{color:var(--text-2);font-size:var(--fs-sm);line-height:1.6;margin-top:var(--sp-2)}
|
||||
.ins-ev{color:var(--muted);font-size:var(--fs-cap);margin-top:var(--sp-2)}
|
||||
|
||||
@@ -66,6 +66,7 @@ const api = {
|
||||
},
|
||||
createRecord: (id, body) => request({ url: `/api/pets/${id}/records`, method: 'POST', data: body }),
|
||||
deleteRecord: (recordId) => request({ url: `/api/records/${recordId}`, method: 'DELETE' }),
|
||||
petInsights: (id) => request({ url: `/api/pets/${id}/insights` }),
|
||||
weightTrend: (id) => request({ url: `/api/pets/${id}/records/weight-trend` }),
|
||||
|
||||
// 任务
|
||||
|
||||
Reference in New Issue
Block a user