diff --git a/internal/company/recommendation.go b/internal/company/recommendation.go index 3ff4191..7efc2f8 100644 --- a/internal/company/recommendation.go +++ b/internal/company/recommendation.go @@ -11,6 +11,7 @@ import ( "time" "github.com/redis/go-redis/v9" + "golang.org/x/sync/errgroup" "tm/pkg/ai_summarizer" ) @@ -220,6 +221,36 @@ func (s *companyService) GetAIRecommendations(ctx context.Context, companyID str return []RecommendedTenderResponse{}, nil } +func (s *companyService) validateRecommendationCompanyIDs(ctx context.Context, companyIDs []string) error { + companies, err := s.repository.GetByIDs(ctx, companyIDs) + if err != nil { + s.logger.Error("Failed to load companies for merged AI recommendations", map[string]interface{}{ + "company_ids": companyIDs, + "error": err.Error(), + }) + return errors.New("company not found") + } + if len(companies) != len(companyIDs) { + s.logger.Error("Some companies were not found for merged AI recommendations", map[string]interface{}{ + "requested_count": len(companyIDs), + "loaded_count": len(companies), + }) + return errors.New("company not found") + } + return nil +} + +func (s *companyService) getCachedAIRecommendationsOrEmpty(ctx context.Context, companyID string) []RecommendedTenderResponse { + if cached, ok := s.getCachedAIRecommendations(ctx, companyID); ok { + return cached + } + + s.logger.Info("AI recommendations cache miss; returning empty list", map[string]interface{}{ + "company_id": companyID, + }) + return []RecommendedTenderResponse{} +} + // GetMergedAIRecommendations unions cached AI recommendations across multiple companies. func (s *companyService) GetMergedAIRecommendations(ctx context.Context, companyIDs []string) ([]RecommendedTenderResponse, error) { if s.aiRecommendationClient == nil { @@ -231,13 +262,22 @@ func (s *companyService) GetMergedAIRecommendations(ctx context.Context, company return []RecommendedTenderResponse{}, nil } - recommendationLists := make([][]RecommendedTenderResponse, 0, len(normalized)) - for _, companyID := range normalized { - recommendations, err := s.GetAIRecommendations(ctx, companyID) - if err != nil { - return nil, err - } - recommendationLists = append(recommendationLists, recommendations) + if err := s.validateRecommendationCompanyIDs(ctx, normalized); err != nil { + return nil, err + } + + recommendationLists := make([][]RecommendedTenderResponse, len(normalized)) + group, groupCtx := errgroup.WithContext(ctx) + for i, companyID := range normalized { + i := i + companyID := companyID + group.Go(func() error { + recommendationLists[i] = s.getCachedAIRecommendationsOrEmpty(groupCtx, companyID) + return nil + }) + } + if err := group.Wait(); err != nil { + return nil, err } return mergeRecommendedTenders(recommendationLists...), nil diff --git a/internal/tender/recommendation_filter.go b/internal/tender/recommendation_filter.go index f55b517..162f82c 100644 --- a/internal/tender/recommendation_filter.go +++ b/internal/tender/recommendation_filter.go @@ -4,6 +4,8 @@ import ( "context" "fmt" "strings" + + "golang.org/x/sync/errgroup" ) // CompanyRejectedTenderLister returns tender IDs the company has rejected. @@ -46,18 +48,37 @@ func (s *tenderService) buildRejectedTenderIDSet(ctx context.Context, companyID func (s *tenderService) buildRejectedTenderIDSetForCompanies(ctx context.Context, companyIDs []string) (map[string]struct{}, error) { excluded := make(map[string]struct{}) - for _, companyID := range normalizeRecommendationCompanyScope(companyIDs) { - if s.rejectedTenderLister == nil { - return excluded, nil - } + if s.rejectedTenderLister == nil { + return excluded, nil + } - ids, err := s.rejectedTenderLister.ListRejectedTenderIDsByCompanyID(ctx, companyID) - if err != nil { - return nil, fmt.Errorf("failed to list rejected tender exclusions: %w", err) - } + normalized := normalizeRecommendationCompanyScope(companyIDs) + if len(normalized) == 0 { + return excluded, nil + } + results := make([][]string, len(normalized)) + group, groupCtx := errgroup.WithContext(ctx) + for i, companyID := range normalized { + i := i + companyID := companyID + group.Go(func() error { + ids, err := s.rejectedTenderLister.ListRejectedTenderIDsByCompanyID(groupCtx, companyID) + if err != nil { + return fmt.Errorf("failed to list rejected tender exclusions: %w", err) + } + results[i] = ids + return nil + }) + } + if err := group.Wait(); err != nil { + return nil, err + } + + for _, ids := range results { addTenderIDsToSet(excluded, ids) } + return excluded, nil }