package usecase import ( "context" "fmt" "apps/backend/internal/module/radar/domain" ) // PromoteFromScout creates an Opportunity from a scout post snapshot (copy, not shared state). func (s *Service) PromoteFromScout(ctx context.Context, ownerUID int64, scoutPostID, externalID, permalink, author, text string, postedAt int64) (*domain.Opportunity, error) { if ownerUID <= 0 || externalID == "" { return nil, fmt.Errorf("%w: owner and external_id required", domain.ErrValidation) } profile, _ := s.Repo.GetServiceProfile(ctx, ownerUID) cand := &domain.CandidatePost{ ExternalID: externalID, Permalink: permalink, AuthorHandle: author, Text: text, PostedAt: postedAt, MatchedTerm: "scout_promote", Classification: "seeking_help", } res, _, err := s.JudgeCandidate(ctx, ownerUID, profile, nil, cand) if err != nil || res == nil { // still store as qualified mid with minimal reasons if judge fails res = &JudgeResult{ Status: domain.OppQualified, IntentScore: 55, IntentBand: domain.BandMid, Reasons: []domain.OpportunityReason{ {Dimension: domain.DimAuthenticity, Score: 20, Reason: "由海巡提升,人工可覆寫"}, {Dimension: domain.DimIntent, Score: 15, Reason: "海巡已標記為值得跟進"}, {Dimension: domain.DimRegion, Score: 7, Reason: "未重判地區"}, {Dimension: domain.DimFreshness, Score: 8, Reason: "沿用貼文時間"}, {Dimension: domain.DimFit, Score: 5, Reason: "海巡提升"}, }, RegionMatch: domain.RegionUnknown, } } o := &domain.Opportunity{ ID: domain.NewID(), OwnerUID: ownerUID, Source: domain.OppSourceScoutPromote, SourceScoutPostID: scoutPostID, ExternalID: externalID, Permalink: permalink, AuthorHandle: author, Text: text, PostedAt: postedAt, Status: res.Status, IntentScore: res.IntentScore, IntentBand: res.IntentBand, Reasons: res.Reasons, RegionDetected: res.RegionDetected, RegionMatch: res.RegionMatch, FreshnessHours: res.FreshnessHours, MatchedService: res.MatchedService, MatchedTerms: []string{"scout_promote"}, RejectReason: res.RejectReason, } return s.Repo.UpsertByExternalID(ctx, o) }