package discovery import ( "context" "sort" "time" ) func (s *Service) fillSignaturesFromStoredMessages(ctx context.Context, email string, agg *addressAgg) { if len(agg.fromMessageRefs) == 0 { return } ids := make([]string, 0, len(agg.fromMessageRefs)) for _, ref := range agg.fromMessageRefs { ids = append(ids, ref.id) } rows, err := s.db.Query(ctx, ` SELECT id::text, LEFT(COALESCE(body_text, ''), $2), LEFT(COALESCE(body_html, ''), $2), date FROM messages WHERE id = ANY($1::uuid[]) ORDER BY date DESC LIMIT $3 `, ids, maxBodyChars, maxSignatureMsgsPerEmail) if err != nil { return } defer rows.Close() agg.Signatures = agg.Signatures[:0] for rows.Next() { var id, bodyText, bodyHTML string var msgDate time.Time if err := rows.Scan(&id, &bodyText, &bodyHTML, &msgDate); err != nil { continue } sigText, conf := extractSignature(bodyText, bodyHTML, email, agg.DisplayName) if sigText == "" { continue } agg.Signatures = append(agg.Signatures, signatureCandidate{ MessageID: id, SignatureText: sigText, MessageDate: msgDate, Confidence: conf, }) } sort.Slice(agg.Signatures, func(i, j int) bool { return agg.Signatures[i].MessageDate.After(agg.Signatures[j].MessageDate) }) if len(agg.Signatures) > 5 { agg.Signatures = agg.Signatures[:5] } } func (s *Service) fillSignaturesForEmail(ctx context.Context, externalUserID, email string, agg *addressAgg) { if len(agg.fromMessageRefs) > 0 { s.fillSignaturesFromStoredMessages(ctx, email, agg) return } rows, err := s.db.Query(ctx, ` SELECT m.id::text, LEFT(COALESCE(m.body_text, ''), $3), LEFT(COALESCE(m.body_html, ''), $3), m.date FROM messages m JOIN mail_accounts ma ON m.account_id = ma.id WHERE ma.user_id = (SELECT id FROM users WHERE external_id = $1) AND EXISTS ( SELECT 1 FROM jsonb_array_elements(m.from_addr) a WHERE lower(coalesce(a->>'address', '')) = lower($2) ) ORDER BY m.date DESC LIMIT $4 `, externalUserID, email, maxBodyChars, maxSignatureMsgsPerEmail) if err != nil { return } defer rows.Close() for rows.Next() { var id, bodyText, bodyHTML string var msgDate time.Time if err := rows.Scan(&id, &bodyText, &bodyHTML, &msgDate); err != nil { continue } sigText, conf := extractSignature(bodyText, bodyHTML, email, agg.DisplayName) if sigText == "" { continue } agg.Signatures = append(agg.Signatures, signatureCandidate{ MessageID: id, SignatureText: sigText, MessageDate: msgDate, Confidence: conf, }) } sort.Slice(agg.Signatures, func(i, j int) bool { return agg.Signatures[i].MessageDate.After(agg.Signatures[j].MessageDate) }) if len(agg.Signatures) > 5 { agg.Signatures = agg.Signatures[:5] } } type enrichCandidate struct { email string agg *addressAgg } func selectPreEnrichCandidates(aggs map[string]*addressAgg, limit int) []enrichCandidate { var candidates []enrichCandidate for email, agg := range aggs { isML, isDisp, isSpamHeavy, _ := classifyAddress(agg) if isML || isDisp || isSpamHeavy { continue } if agg.MessageCount < minMessagesForLLM { continue } if _, ok := agg.Roles["from"]; !ok { continue } candidates = append(candidates, enrichCandidate{email: email, agg: agg}) } sort.Slice(candidates, func(i, j int) bool { return candidates[i].agg.MessageCount > candidates[j].agg.MessageCount }) if len(candidates) > limit { candidates = candidates[:limit] } return candidates } func enrichCandidateSet(candidates []enrichCandidate) map[string]bool { out := make(map[string]bool, len(candidates)) for _, c := range candidates { out[c.email] = true } return out }