Enhance AI recommendations handling with validation and caching improvements
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
- Introduced a new method to validate company IDs for AI recommendations, ensuring all requested companies exist before processing. - Added caching logic to retrieve AI recommendations efficiently, reducing unnecessary calls and improving performance. - Utilized errgroup for concurrent processing of AI recommendations across multiple companies, enhancing responsiveness. - Updated the tender service to handle rejected tenders more effectively by incorporating concurrent fetching of rejected tender IDs. This update significantly improves the AI recommendations process by ensuring accurate company validation and optimizing data retrieval through caching and concurrency, enhancing overall system performance and user experience.
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
"tm/pkg/ai_summarizer"
|
"tm/pkg/ai_summarizer"
|
||||||
)
|
)
|
||||||
@@ -220,6 +221,36 @@ func (s *companyService) GetAIRecommendations(ctx context.Context, companyID str
|
|||||||
return []RecommendedTenderResponse{}, nil
|
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.
|
// GetMergedAIRecommendations unions cached AI recommendations across multiple companies.
|
||||||
func (s *companyService) GetMergedAIRecommendations(ctx context.Context, companyIDs []string) ([]RecommendedTenderResponse, error) {
|
func (s *companyService) GetMergedAIRecommendations(ctx context.Context, companyIDs []string) ([]RecommendedTenderResponse, error) {
|
||||||
if s.aiRecommendationClient == nil {
|
if s.aiRecommendationClient == nil {
|
||||||
@@ -231,13 +262,22 @@ func (s *companyService) GetMergedAIRecommendations(ctx context.Context, company
|
|||||||
return []RecommendedTenderResponse{}, nil
|
return []RecommendedTenderResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
recommendationLists := make([][]RecommendedTenderResponse, 0, len(normalized))
|
if err := s.validateRecommendationCompanyIDs(ctx, normalized); err != nil {
|
||||||
for _, companyID := range normalized {
|
return nil, err
|
||||||
recommendations, err := s.GetAIRecommendations(ctx, companyID)
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
recommendationLists := make([][]RecommendedTenderResponse, len(normalized))
|
||||||
}
|
group, groupCtx := errgroup.WithContext(ctx)
|
||||||
recommendationLists = append(recommendationLists, recommendations)
|
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
|
return mergeRecommendedTenders(recommendationLists...), nil
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CompanyRejectedTenderLister returns tender IDs the company has rejected.
|
// 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) {
|
func (s *tenderService) buildRejectedTenderIDSetForCompanies(ctx context.Context, companyIDs []string) (map[string]struct{}, error) {
|
||||||
excluded := make(map[string]struct{})
|
excluded := make(map[string]struct{})
|
||||||
for _, companyID := range normalizeRecommendationCompanyScope(companyIDs) {
|
if s.rejectedTenderLister == nil {
|
||||||
if s.rejectedTenderLister == nil {
|
return excluded, nil
|
||||||
return excluded, nil
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ids, err := s.rejectedTenderLister.ListRejectedTenderIDsByCompanyID(ctx, companyID)
|
normalized := normalizeRecommendationCompanyScope(companyIDs)
|
||||||
if err != nil {
|
if len(normalized) == 0 {
|
||||||
return nil, fmt.Errorf("failed to list rejected tender exclusions: %w", err)
|
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)
|
addTenderIDsToSet(excluded, ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
return excluded, nil
|
return excluded, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user