From 326e49886be5e6abf74fa8e0fef7102f955be683 Mon Sep 17 00:00:00 2001 From: Mazyar Date: Tue, 23 Jun 2026 13:33:33 +0330 Subject: [PATCH] Implement asynchronous AI recommendation cache refresh in company service - Introduced `refreshAIRecommendationsCacheAsync` method to refresh AI recommendations in the background, improving responsiveness by serving the previous cache until the refresh completes. - Updated `StartAIOnboarding` to call the new asynchronous cache refresh method instead of invalidating the cache directly. - Added logging for cache refresh operations, including success and error handling, to enhance observability. This update enhances the AI recommendation caching mechanism, providing a smoother onboarding experience and reducing latency in recommendation retrieval. --- internal/company/recommendation.go | 58 ++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/internal/company/recommendation.go b/internal/company/recommendation.go index 619c77b..f9f4f5a 100644 --- a/internal/company/recommendation.go +++ b/internal/company/recommendation.go @@ -85,7 +85,8 @@ func (s *companyService) StartAIOnboarding(ctx context.Context, companyID string return nil, fmt.Errorf("failed to start AI onboarding: %w", err) } - s.invalidateAIRecommendationCache(ctx, companyID) + // Prefetch recommendations in the background; keep serving the previous cache until refresh completes. + s.refreshAIRecommendationsCacheAsync(companyID) return &OnboardingResponse{Status: result.Status}, nil } @@ -143,6 +144,48 @@ func (s *companyService) GetAIRecommendations(ctx context.Context, companyID str return nil, errors.New("company not found") } + return s.fetchAndCacheAIRecommendations(ctx, companyID) +} + +func (s *companyService) refreshAIRecommendationsCacheAsync(companyID string) { + if s.aiRecommendationClient == nil { + return + } + + companyID = strings.TrimSpace(companyID) + if companyID == "" { + return + } + + go func() { + ctx := context.Background() + s.logger.Info("Refreshing AI recommendation cache in background", map[string]interface{}{ + "company_id": companyID, + }) + + if _, err := s.repository.GetByID(ctx, companyID); err != nil { + s.logger.Error("Failed to load company for AI recommendation refresh", map[string]interface{}{ + "company_id": companyID, + "error": err.Error(), + }) + return + } + + if _, err := s.fetchAndCacheAIRecommendations(ctx, companyID); err != nil { + s.logger.Error("Failed to refresh AI recommendation cache", map[string]interface{}{ + "company_id": companyID, + "error": err.Error(), + }) + return + } + + s.logger.Info("AI recommendation cache refreshed", map[string]interface{}{ + "company_id": companyID, + }) + }() +} + +func (s *companyService) fetchAndCacheAIRecommendations(ctx context.Context, companyID string) ([]RecommendedTenderResponse, error) { s.logger.Info("Fetching AI tender recommendations", map[string]interface{}{ "company_id": companyID, }) @@ -228,19 +271,6 @@ func (s *companyService) cacheAIRecommendations(ctx context.Context, companyID s } } -func (s *companyService) invalidateAIRecommendationCache(ctx context.Context, companyID string) { - if s.redisClient == nil || s.recommendationCacheTTL <= 0 { - return - } - - if err := s.redisClient.Del(ctx, aiRecommendationCacheKey(companyID)); err != nil { - s.logger.Warn("Failed to invalidate AI recommendation cache", map[string]interface{}{ - "company_id": companyID, - "error": err.Error(), - }) - } -} - func (s *companyService) buildOnboardingDocuments(fileIDs []string) (string, error) { if len(fileIDs) == 0 { return "[]", nil