132 lines
3.5 KiB
Go
132 lines
3.5 KiB
Go
|
|
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)
|
||
|
|
}
|