2026-07-01 08:42:51 +00:00
|
|
|
package placement
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
libkg "haixun-backend/internal/library/knowledge"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestHasCategoryConflictLaundryVsDish(t *testing.T) {
|
|
|
|
|
ctx := PostScanContextFromPatrolInput(libkg.PatrolTagInput{
|
|
|
|
|
ProductName: "天然洗衣精",
|
|
|
|
|
MatchTags: []string{"洗衣精", "衣物清潔"},
|
|
|
|
|
}, nil)
|
|
|
|
|
text := "請問大家洗碗機清潔劑有推薦嗎?碗盤油污洗不掉"
|
|
|
|
|
if !HasCategoryConflict(text, ctx) {
|
|
|
|
|
t.Fatal("expected dishwasher post to conflict with laundry product")
|
|
|
|
|
}
|
|
|
|
|
if PassesPostScanFilters(text, "洗衣精 推薦", "", QueryRecency, 80, ctx) {
|
|
|
|
|
t.Fatal("expected conflicting post to be filtered out")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPassesPostScanFiltersAcceptsMatchingLaundry(t *testing.T) {
|
|
|
|
|
ctx := PostScanContextFromPatrolInput(libkg.PatrolTagInput{
|
|
|
|
|
ProductName: "天然洗衣精",
|
|
|
|
|
MatchTags: []string{"洗衣精", "衣物"},
|
|
|
|
|
Pillars: []string{"衣物清潔", "敏感皮膚洗衣"},
|
|
|
|
|
}, []string{"洗碗精等其他品類"})
|
|
|
|
|
text := "最近衣服洗不乾淨,洗衣精有推薦嗎?污漬一直洗不掉"
|
|
|
|
|
if !PassesPostScanFilters(text, "洗衣精 推薦", time.Now().Format("2006-01-02"), QueryRecency, 70, ctx) {
|
|
|
|
|
t.Fatal("expected matching laundry post to pass")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPassesPostAgeAllowsMissingDateOnRelevance(t *testing.T) {
|
|
|
|
|
if !PassesPostAge("", QueryRelevance, 0) {
|
|
|
|
|
t.Fatal("expected missing posted_at to pass age gate at ingest")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPassesDiscoverFiltersAllowsWebSnippetWithoutDate(t *testing.T) {
|
|
|
|
|
ctx := PostScanContextFromPatrolInput(libkg.PatrolTagInput{
|
|
|
|
|
ProductName: "天然洗衣精",
|
|
|
|
|
MatchTags: []string{"洗衣精", "衣物"},
|
|
|
|
|
}, nil)
|
|
|
|
|
text := "最近衣服洗不乾淨,洗衣精有推薦嗎?污漬一直洗不掉"
|
|
|
|
|
if !PassesDiscoverFilters(text, "洗衣精 推薦", "", QueryRelevance, 70, 0, ctx) {
|
|
|
|
|
t.Fatal("expected web snippet without date to pass discover filters")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPassesPostAgeRejectsOldRelevancePost(t *testing.T) {
|
|
|
|
|
old := time.Now().AddDate(0, 0, -20).Format("2006-01-02")
|
|
|
|
|
if PassesPostAge(old, QueryRelevance, 0) {
|
|
|
|
|
t.Fatal("expected old relevance post to fail age gate")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestHasPainPointDemandRejectsCasualChat(t *testing.T) {
|
|
|
|
|
if HasPainPointDemand("今天天氣真好,晒照給大家看") {
|
|
|
|
|
t.Fatal("expected casual chat without demand to fail")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestHasPainPointDemandAcceptsHelpSeeking(t *testing.T) {
|
|
|
|
|
text := "最近皮膚一直癢,沐浴乳有推薦嗎?"
|
|
|
|
|
if !HasPainPointDemand(text) {
|
|
|
|
|
t.Fatal("expected help-seeking post with pain signal to pass")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPostSolvedByProductRejectsCategoryMismatch(t *testing.T) {
|
|
|
|
|
ctx := PostScanContextFromPatrolInput(libkg.PatrolTagInput{
|
|
|
|
|
ProductName: "天然洗衣精",
|
|
|
|
|
MatchTags: []string{"洗衣精"},
|
|
|
|
|
}, nil)
|
|
|
|
|
text := "洗碗精有推薦嗎?碗盤油污洗不掉"
|
|
|
|
|
if PostSolvedByProduct(text, 80, ctx) {
|
|
|
|
|
t.Fatal("expected dish post not to be solved by laundry product")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestHasTangentialTopicMismatchRejectsWashingMachine(t *testing.T) {
|
|
|
|
|
ctx := PostScanContextFromPatrolInput(libkg.PatrolTagInput{
|
|
|
|
|
ProductName: "抗敏無香洗衣精",
|
|
|
|
|
ProductFeatures: "完全無香、抗敏、適合敏感肌與化療族群",
|
|
|
|
|
MatchTags: []string{"無香", "抗敏", "洗衣精"},
|
|
|
|
|
Pillars: []string{"化療後衣物清潔", "無香洗衣"},
|
|
|
|
|
}, nil)
|
|
|
|
|
text := "請問大家有推薦的洗衣機嗎?想買洗脫烘"
|
|
|
|
|
if !HasTangentialTopicMismatch(text, ctx) {
|
|
|
|
|
t.Fatal("expected washing machine post to be tangential mismatch for hypoallergenic detergent")
|
|
|
|
|
}
|
|
|
|
|
if PassesPostScanFilters(text, "無香 洗衣精 推薦", time.Now().Format("2006-01-02"), QueryRecency, 80, ctx) {
|
|
|
|
|
t.Fatal("expected washing machine post to be filtered out")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestHasTangentialTopicMismatchAcceptsDetergentPain(t *testing.T) {
|
|
|
|
|
ctx := PostScanContextFromPatrolInput(libkg.PatrolTagInput{
|
|
|
|
|
ProductName: "抗敏無香洗衣精",
|
|
|
|
|
ProductFeatures: "完全無香、抗敏",
|
|
|
|
|
MatchTags: []string{"無香", "洗衣精"},
|
|
|
|
|
}, nil)
|
|
|
|
|
text := "化療後對香味很敏感,有無香洗衣精推薦嗎?"
|
|
|
|
|
if HasTangentialTopicMismatch(text, ctx) {
|
|
|
|
|
t.Fatal("expected detergent pain post to pass tangential check")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestHasTangentialTopicMismatchAcceptsMachineWithDetergentNeed(t *testing.T) {
|
|
|
|
|
ctx := PostScanContextFromPatrolInput(libkg.PatrolTagInput{
|
|
|
|
|
ProductName: "抗敏無香洗衣精",
|
|
|
|
|
ProductFeatures: "完全無香、抗敏",
|
|
|
|
|
MatchTags: []string{"無香", "洗衣精"},
|
|
|
|
|
}, nil)
|
|
|
|
|
text := "買了洗衣機,但想找不會讓皮膚癢的無香洗衣精"
|
|
|
|
|
if HasTangentialTopicMismatch(text, ctx) {
|
|
|
|
|
t.Fatal("expected combined machine + detergent pain post to pass")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPostSolvedByProductAcceptsAlignedDemand(t *testing.T) {
|
|
|
|
|
ctx := PostScanContextFromPatrolInput(libkg.PatrolTagInput{
|
|
|
|
|
ProductName: "天然洗衣精",
|
|
|
|
|
MatchTags: []string{"洗衣精", "衣物"},
|
|
|
|
|
Pillars: []string{"衣物清潔"},
|
|
|
|
|
}, nil)
|
|
|
|
|
text := "衣服污漬洗不掉,洗衣精有推薦嗎?"
|
|
|
|
|
if !PostSolvedByProduct(text, 70, ctx) {
|
|
|
|
|
t.Fatal("expected aligned laundry demand to be solved by product")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestExpandExclusionTokensIncludesDishSynonyms(t *testing.T) {
|
|
|
|
|
tokens := ExpandExclusionTokens([]string{"洗碗精等其他品類"})
|
|
|
|
|
found := false
|
|
|
|
|
for _, token := range tokens {
|
|
|
|
|
if token == "洗碗機" || token == "洗碗精" {
|
|
|
|
|
found = true
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !found {
|
|
|
|
|
t.Fatalf("expected dish synonyms, got %v", tokens)
|
|
|
|
|
}
|
2026-07-03 14:42:19 +00:00
|
|
|
}
|