fix(backthink): 主动推送降频(15min心跳+60min≤1次+推后30min冷却+刚聊过10min冷避) + 情绪约束(分类限频/活跃感知) + 剥离'我决定:push'内部标记
This commit is contained in:
1 parent
03d7050e1e
commit
12031bd13c
14 files changed
+662
-149
No files matched your search
@@ -137,3 +137,80 @@ func TestInQuietHours(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestClassifyPush verifies the proactive message body maps to the intended flavor
|
||||
// (问候/提醒/分享/关心) so the frequency gate can limit each flavor separately.
|
||||
func TestClassifyPush(t *testing.T) {
|
||||
t.Parallel()
|
||||
cases := []struct {
|
||||
in string
|
||||
want PushKind
|
||||
}{
|
||||
{in: "记得明天体检", want: KindReminder},
|
||||
{in: "提醒你到点吃药了", want: KindReminder},
|
||||
{in: "最近累不累,注意身体", want: KindCare},
|
||||
{in: "想你了,照顾好自己", want: KindCare},
|
||||
{in: "早安,昨晚睡得好吗", want: KindGreeting},
|
||||
{in: "晚上早点睡,晚安", want: KindGreeting},
|
||||
{in: "发现一个有意思的地方,分享给你", want: KindShare},
|
||||
{in: "跟往常一样没什么特别", want: KindCare}, // no keyword → generic fallback
|
||||
}
|
||||
for _, tc := range cases {
|
||||
if got := classifyPush(tc.in); got != tc.want {
|
||||
t.Fatalf("classifyPush(%q) = %v, want %v", tc.in, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestDecide_ActivityGate verifies the activity-aware soft gate: a fresh
|
||||
// conversation holds trivia (unless critical), a long-idle owner is only woken by
|
||||
// an important event, and the in-between band does not over-suppress.
|
||||
func TestDecide_ActivityGate(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := testBackthink()
|
||||
|
||||
// Just chatted (within RecentChatCooldown=10min): a normal daytime boredom
|
||||
// push is held unless the event is safety-critical.
|
||||
fresh := DecisionInput{
|
||||
Import: ImportanceNormal,
|
||||
At: dayAt(),
|
||||
UserState: UserState{Boredom: 0.9},
|
||||
LastActivity: dayAt().Add(-5 * time.Minute),
|
||||
HasActivity: true,
|
||||
}
|
||||
if got := b.Decide(fresh); got.ShouldPush {
|
||||
t.Fatalf("fresh conversation should hold a normal push, got %+v", got)
|
||||
}
|
||||
fresh.Import = ImportanceCritical
|
||||
if got := b.Decide(fresh); !got.ShouldPush {
|
||||
t.Fatalf("critical event should push even in a fresh conversation, got %+v", got)
|
||||
}
|
||||
|
||||
// Away a long while (> longIdleThreshold): only an important event is worth it.
|
||||
idle := DecisionInput{
|
||||
Import: ImportanceNormal,
|
||||
At: dayAt(),
|
||||
UserState: UserState{Boredom: 0.9},
|
||||
LastActivity: dayAt().Add(-3 * time.Hour),
|
||||
HasActivity: true,
|
||||
}
|
||||
if got := b.Decide(idle); got.ShouldPush {
|
||||
t.Fatalf("long idle should hold a normal push, got %+v", got)
|
||||
}
|
||||
idle.Import = ImportanceHigh
|
||||
if got := b.Decide(idle); !got.ShouldPush {
|
||||
t.Fatalf("long idle high-importance should push, got %+v", got)
|
||||
}
|
||||
|
||||
// Middling activity (30 min ago) is not over-suppressed.
|
||||
middle := DecisionInput{
|
||||
Import: ImportanceNormal,
|
||||
At: dayAt(),
|
||||
UserState: UserState{Boredom: 0.9},
|
||||
LastActivity: dayAt().Add(-30 * time.Minute),
|
||||
HasActivity: true,
|
||||
}
|
||||
if got := b.Decide(middle); !got.ShouldPush {
|
||||
t.Fatalf("middle-band activity should allow a normal push, got %+v", got)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user