thread-master/apps/backend/internal/module/radar/domain/sweep.go

119 lines
4.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package domain
import (
"fmt"
"strings"
)
// Sweep fetch path — reuses the dual-path split (api | crawler); no third path.
const (
SweepPathAPI = "api"
SweepPathCrawler = "crawler"
)
/*
RadarSweep 是一次每日巡(或手動觸發)的執行紀錄。
計數以累加更新為主Job 中途失敗時已寫入的進度保留,供續跑判斷
judged_external_ids 標記已判過的貼文,避免重跑)。
*/
type RadarSweep struct {
ID string `bson:"_id" json:"id"`
OwnerUID int64 `bson:"owner_uid" json:"owner_uid"`
WatchID string `bson:"watch_id" json:"watch_id"`
JobID string `bson:"job_id,omitempty" json:"job_id,omitempty"`
Path string `bson:"path" json:"path"`
HitCount int `bson:"hit_count" json:"hit_count"`
JudgedCount int `bson:"judged_count" json:"judged_count"`
CreatedCount int `bson:"created_count" json:"created_count"`
TruncatedCount int `bson:"truncated_count" json:"truncated_count"`
MatchEvaluatedCount int `bson:"match_evaluated_count" json:"match_evaluated_count"`
MatchMergedCount int `bson:"match_merged_count" json:"match_merged_count"`
FitRejectedCount int `bson:"fit_rejected_count" json:"fit_rejected_count"`
FailedReason string `bson:"failed_reason,omitempty" json:"failed_reason,omitempty"`
CreditsUsed int `bson:"credits_used" json:"credits_used"`
DedupedCount int `bson:"deduped_count" json:"deduped_count"`
PrefilterPassCount int `bson:"prefilter_pass_count" json:"prefilter_pass_count"`
PrefilterReviewCount int `bson:"prefilter_review_count" json:"prefilter_review_count"`
PrefilterRejectedCount int `bson:"prefilter_rejected_count" json:"prefilter_rejected_count"`
CachedJudgmentCount int `bson:"cached_judgment_count" json:"cached_judgment_count"`
TombstoneMatchedCount int `bson:"tombstone_matched_count" json:"tombstone_matched_count"`
BudgetDeferredCount int `bson:"budget_deferred_count" json:"budget_deferred_count"`
DemandInputVersion string `bson:"demand_input_version,omitempty" json:"demand_input_version,omitempty"`
DemandMapVersion int64 `bson:"demand_map_version,omitempty" json:"demand_map_version,omitempty"`
CreditSearch int `bson:"credit_search" json:"credit_search"`
CreditDemandMap int `bson:"credit_demand_map" json:"credit_demand_map"`
CreditJudge int `bson:"credit_judge" json:"credit_judge"`
CreditReply int `bson:"credit_reply" json:"credit_reply"`
JudgedExternalIDs []string `bson:"judged_external_ids,omitempty" json:"judged_external_ids,omitempty"`
StartedAt int64 `bson:"started_at" json:"started_at"`
EndedAt int64 `bson:"ended_at,omitempty" json:"ended_at,omitempty"`
}
// SweepDelta is an incremental progress patch applied with $inc / $addToSet.
type SweepDelta struct {
HitCount int
JudgedCount int
CreatedCount int
TruncatedCount int
MatchEvaluatedCount int
MatchMergedCount int
FitRejectedCount int
CreditsUsed int
DedupedCount int
PrefilterPassCount int
PrefilterReviewCount int
PrefilterRejectedCount int
CachedJudgmentCount int
TombstoneMatchedCount int
BudgetDeferredCount int
CreditSearch int
CreditDemandMap int
CreditJudge int
CreditReply int
JudgedExternalIDs []string
FailedReason *string // nil = leave unchanged; non-nil (incl. empty) = set
EndedAt int64 // 0 = leave unchanged
}
// SweepListFilter pages sweeps for an owner, optionally scoped to one watch.
type SweepListFilter struct {
WatchID string
Page int
PageSize int
}
func IsSweepPath(s string) bool {
switch s {
case SweepPathAPI, SweepPathCrawler:
return true
}
return false
}
func (s *RadarSweep) Normalize() error {
if s.OwnerUID <= 0 {
return fmt.Errorf("%w: owner_uid required", ErrValidation)
}
// WatchID may be empty for on-demand explore (no subscription); required for scheduled sweeps.
s.WatchID = strings.TrimSpace(s.WatchID)
if s.Path == "" {
s.Path = SweepPathAPI
}
if !IsSweepPath(s.Path) {
return fmt.Errorf("%w: unknown sweep path %q", ErrValidation, s.Path)
}
// failed_reason must never hold tokens; strip obvious bearer-like blobs is out of scope —
// callers are responsible. We only reject empty path/owner.
if s.FailedReason != "" {
s.FailedReason = strings.TrimSpace(s.FailedReason)
}
s.JudgedExternalIDs = dedupeStrings(s.JudgedExternalIDs)
return nil
}
// MergeJudgedExternalIDs appends new IDs without duplicates (order preserved).
func MergeJudgedExternalIDs(existing, add []string) []string {
return dedupeStrings(append(append([]string{}, existing...), add...))
}