thread-master/backend/internal/library/viral/known_accounts.go

132 lines
3.5 KiB
Go
Raw Normal View History

2026-06-28 08:28:42 +00:00
package viral
import (
"strings"
missionentity "haixun-backend/internal/model/copy_mission/domain/entity"
accountentity "haixun-backend/internal/model/threads_account/domain/entity"
)
// ExcludedKnownAccountUsernames returns usernames marked excluded in cross-mission memory.
func ExcludedKnownAccountUsernames(known map[string]accountentity.KnownAccountProfile) []string {
if len(known) == 0 {
return nil
}
out := make([]string, 0)
seen := map[string]struct{}{}
for key, item := range known {
if item.Status != accountentity.KnownAccountStatusExcluded {
continue
}
user := strings.TrimSpace(key)
if user == "" {
continue
}
lower := strings.ToLower(user)
if _, ok := seen[lower]; ok {
continue
}
seen[lower] = struct{}{}
out = append(out, user)
}
return out
}
// ApplyKnownAccountMemory applies cross-mission memory to freshly merged similar
// accounts: inherit excluded status and boost confidence for repeat sightings.
func ApplyKnownAccountMemory(
accounts []missionentity.SimilarAccount,
known map[string]accountentity.KnownAccountProfile,
) []missionentity.SimilarAccount {
if len(accounts) == 0 || len(known) == 0 {
return accounts
}
out := make([]missionentity.SimilarAccount, len(accounts))
copy(out, accounts)
for i := range out {
key := strings.ToLower(strings.TrimSpace(out[i].Username))
if key == "" {
continue
}
profile, ok := known[key]
if !ok {
continue
}
if profile.Status == accountentity.KnownAccountStatusExcluded {
out[i].Status = missionentity.SimilarAccountStatusExcluded
}
if len(profile.Missions) >= 2 || profile.SeenCount >= 2 {
if out[i].Confidence != "high" {
out[i].Confidence = "high"
}
}
}
return out
}
// MergeKnownAccountsFromScan upserts similar-account sightings into the account-level
// known_accounts map.
func MergeKnownAccountsFromScan(
prev map[string]accountentity.KnownAccountProfile,
accounts []missionentity.SimilarAccount,
missionID, personaID string,
tags []string,
now int64,
) map[string]accountentity.KnownAccountProfile {
out := map[string]accountentity.KnownAccountProfile{}
for key, item := range prev {
out[key] = item
}
for _, acc := range accounts {
user := strings.TrimSpace(acc.Username)
if user == "" {
continue
}
key := strings.ToLower(user)
profile := out[key]
if profile.FirstSeenAt == 0 {
profile.FirstSeenAt = now
}
profile.LastSeenAt = now
profile.Missions = appendUnique(profile.Missions, missionID)
profile.PersonaIds = appendUnique(profile.PersonaIds, personaID)
for _, tag := range tags {
profile.Tags = appendUnique(profile.Tags, tag)
}
prevCount := profile.SeenCount
if prevCount < 0 {
prevCount = 0
}
eng := float64(acc.EngagementScore)
if prevCount == 0 {
profile.AvgEngagement = eng
} else {
profile.AvgEngagement = (profile.AvgEngagement*float64(prevCount) + eng) / float64(prevCount+1)
}
profile.SeenCount = prevCount + 1
switch strings.TrimSpace(acc.Status) {
case missionentity.SimilarAccountStatusExcluded:
profile.Status = accountentity.KnownAccountStatusExcluded
case missionentity.SimilarAccountStatusRecommended:
if profile.Status == accountentity.KnownAccountStatusExcluded {
profile.Status = ""
}
}
out[key] = profile
}
return out
}
func appendUnique(items []string, value string) []string {
value = strings.TrimSpace(value)
if value == "" {
return items
}
for _, item := range items {
if item == value {
return items
}
}
return append(items, value)
}