Refactor recommendation response handling and enhance enrichment logic
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
- Removed the `buildRecommendedTenderResponsesParallel` method and replaced it with `buildRecommendedTenderListResponses`, simplifying the response building process. - Introduced `enrichRecommendedTenderResponsesParallel` to handle enrichment of tender responses in parallel, improving performance. - Updated the `Recommend` method to utilize the new enrichment logic, ensuring timely and efficient processing of recommendations. - Cleaned up unused functions and imports in `recommendation_page_cache.go` and `recommendation_response.go`, enhancing code clarity and maintainability. This update streamlines the recommendation response handling and improves the overall efficiency of the tender recommendation service.
This commit is contained in:
@@ -8,9 +8,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"tm/internal/company"
|
||||
"tm/pkg/response"
|
||||
)
|
||||
@@ -92,7 +89,7 @@ func (s *tenderService) buildRecommendedTendersPageCache(ctx context.Context, co
|
||||
return nil
|
||||
}
|
||||
|
||||
responses := s.buildRecommendedTenderResponsesParallel(ctx, items, language)
|
||||
responses := s.buildRecommendedTenderListResponses(items, language)
|
||||
page := cachedRecommendedTendersPage{
|
||||
BuiltAt: time.Now().Unix(),
|
||||
CompanyID: companyID,
|
||||
@@ -204,54 +201,6 @@ func (s *tenderService) buildAllRankedRecommendations(
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *tenderService) buildRecommendedTenderResponsesParallel(
|
||||
ctx context.Context,
|
||||
items []rankedRecommendedTender,
|
||||
language string,
|
||||
) []TenderResponse {
|
||||
if len(items) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
out := make([]TenderResponse, len(items))
|
||||
if len(items) == 1 {
|
||||
out[0] = s.buildRecommendedTenderResponse(
|
||||
ctx, items[0].tender, items[0].rank, items[0].analysis, items[0].tenderRef, language,
|
||||
newRecommendationTranslationCache(),
|
||||
)
|
||||
return out
|
||||
}
|
||||
|
||||
translationCache := newRecommendationTranslationCache()
|
||||
group, groupCtx := errgroup.WithContext(ctx)
|
||||
group.SetLimit(recommendationPageCacheConcurrency)
|
||||
|
||||
for i := range items {
|
||||
i := i
|
||||
item := items[i]
|
||||
group.Go(func() error {
|
||||
out[i] = s.buildRecommendedTenderResponse(
|
||||
groupCtx, item.tender, item.rank, item.analysis, item.tenderRef, language, translationCache,
|
||||
)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if err := group.Wait(); err != nil {
|
||||
s.logger.Warn("Parallel recommendation page cache build degraded to sequential", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"count": len(items),
|
||||
})
|
||||
for i, item := range items {
|
||||
out[i] = s.buildRecommendedTenderResponse(
|
||||
ctx, item.tender, item.rank, item.analysis, item.tenderRef, language, translationCache,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *tenderService) recommendFromPageCache(
|
||||
ctx context.Context,
|
||||
companyID string,
|
||||
@@ -292,8 +241,11 @@ func (s *tenderService) recommendFromPageCache(
|
||||
end = len(filtered)
|
||||
}
|
||||
|
||||
paged := append([]TenderResponse(nil), filtered[start:end]...)
|
||||
paged = s.enrichRecommendedTenderResponsesParallel(ctx, paged, lang)
|
||||
|
||||
return &SearchResponse{
|
||||
Tenders: append([]TenderResponse(nil), filtered[start:end]...),
|
||||
Tenders: paged,
|
||||
Metadata: pagination.ListMeta(total, "", end < len(filtered), start),
|
||||
}, true
|
||||
}
|
||||
@@ -376,21 +328,6 @@ func filterCachedRecommendedTenders(
|
||||
return out
|
||||
}
|
||||
|
||||
func tenderFromRecommendationResponse(item TenderResponse) Tender {
|
||||
var tender Tender
|
||||
if id, err := bson.ObjectIDFromHex(strings.TrimSpace(item.ID)); err == nil {
|
||||
tender.ID = id
|
||||
}
|
||||
tender.ContractFolderID = item.ContractFolderID
|
||||
tender.NoticePublicationID = item.NoticePublicationID
|
||||
tender.RelatedNoticePublicationIDs = append([]string(nil), item.RelatedNoticePublicationIDs...)
|
||||
tender.Status = item.Status
|
||||
tender.PublicationDate = item.PublicationDate
|
||||
tender.SubmissionDeadline = item.SubmissionDeadline
|
||||
tender.TenderDeadline = item.TenderDeadline
|
||||
return tender
|
||||
}
|
||||
|
||||
// RecommendedTendersPageCacheKeyPrefix is shared with company invalidation logic.
|
||||
const RecommendedTendersPageCacheKeyPrefix = recommendedTendersPageCacheKeyPrefix
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"tm/pkg/ai_summarizer"
|
||||
)
|
||||
|
||||
@@ -68,6 +71,72 @@ func (s *tenderService) buildRecommendedTenderListResponses(items []rankedRecomm
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *tenderService) enrichRecommendedTenderResponsesParallel(ctx context.Context, responses []TenderResponse, language string) []TenderResponse {
|
||||
if len(responses) == 0 || s.aiSummarizerStorage == nil {
|
||||
return responses
|
||||
}
|
||||
|
||||
out := make([]TenderResponse, len(responses))
|
||||
if len(responses) == 1 {
|
||||
out[0] = s.enrichRecommendedTenderResponse(ctx, responses[0], language, newRecommendationTranslationCache())
|
||||
return out
|
||||
}
|
||||
|
||||
translationCache := newRecommendationTranslationCache()
|
||||
group, groupCtx := errgroup.WithContext(ctx)
|
||||
group.SetLimit(recommendationPageCacheConcurrency)
|
||||
|
||||
for i := range responses {
|
||||
i := i
|
||||
group.Go(func() error {
|
||||
out[i] = s.enrichRecommendedTenderResponse(groupCtx, responses[i], language, translationCache)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if err := group.Wait(); err != nil {
|
||||
for i, resp := range responses {
|
||||
out[i] = s.enrichRecommendedTenderResponse(ctx, resp, language, translationCache)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *tenderService) enrichRecommendedTenderResponse(
|
||||
ctx context.Context,
|
||||
resp TenderResponse,
|
||||
language string,
|
||||
cache *recommendationTranslationCache,
|
||||
) TenderResponse {
|
||||
enriched := resp
|
||||
tender := tenderFromRecommendationResponse(resp)
|
||||
s.enrichWithTranslationTimeout(
|
||||
ctx,
|
||||
&tender,
|
||||
&enriched,
|
||||
language,
|
||||
cache,
|
||||
recommendTranslationEnrichmentTimeout,
|
||||
)
|
||||
return enriched
|
||||
}
|
||||
|
||||
func tenderFromRecommendationResponse(item TenderResponse) Tender {
|
||||
var tender Tender
|
||||
if id, err := bson.ObjectIDFromHex(strings.TrimSpace(item.ID)); err == nil {
|
||||
tender.ID = id
|
||||
}
|
||||
tender.ContractFolderID = item.ContractFolderID
|
||||
tender.NoticePublicationID = item.NoticePublicationID
|
||||
tender.RelatedNoticePublicationIDs = append([]string(nil), item.RelatedNoticePublicationIDs...)
|
||||
tender.Status = item.Status
|
||||
tender.PublicationDate = item.PublicationDate
|
||||
tender.SubmissionDeadline = item.SubmissionDeadline
|
||||
tender.TenderDeadline = item.TenderDeadline
|
||||
return tender
|
||||
}
|
||||
|
||||
func (s *tenderService) loadRecommendationExclusions(
|
||||
ctx context.Context,
|
||||
form *SearchForm,
|
||||
|
||||
@@ -96,8 +96,9 @@ type tenderService struct {
|
||||
}
|
||||
|
||||
const (
|
||||
aiSummaryEnrichmentTimeout = 2 * time.Second
|
||||
aiTranslationEnrichmentTimeout = 2 * time.Second
|
||||
aiSummaryEnrichmentTimeout = 2 * time.Second
|
||||
aiTranslationEnrichmentTimeout = 2 * time.Second
|
||||
recommendTranslationEnrichmentTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -214,6 +215,17 @@ func (s *tenderService) enrichWithAISummary(ctx context.Context, t *Tender, resp
|
||||
// enrichWithTranslation overlays title/description from MinIO when the pipeline has finished
|
||||
// for the requested language. Does not call the AI translate API on GET.
|
||||
func (s *tenderService) enrichWithTranslation(ctx context.Context, t *Tender, resp *TenderResponse, language string, cache *recommendationTranslationCache) {
|
||||
s.enrichWithTranslationTimeout(ctx, t, resp, language, cache, aiTranslationEnrichmentTimeout)
|
||||
}
|
||||
|
||||
func (s *tenderService) enrichWithTranslationTimeout(
|
||||
ctx context.Context,
|
||||
t *Tender,
|
||||
resp *TenderResponse,
|
||||
language string,
|
||||
cache *recommendationTranslationCache,
|
||||
enrichmentTimeout time.Duration,
|
||||
) {
|
||||
if t == nil || resp == nil {
|
||||
return
|
||||
}
|
||||
@@ -233,7 +245,7 @@ func (s *tenderService) enrichWithTranslation(ctx context.Context, t *Tender, re
|
||||
}
|
||||
}
|
||||
|
||||
enrichCtx, cancel := context.WithTimeout(ctx, aiTranslationEnrichmentTimeout)
|
||||
enrichCtx, cancel := context.WithTimeout(ctx, enrichmentTimeout)
|
||||
defer cancel()
|
||||
|
||||
stored, usedNotice, err := s.lookupTranslationForTender(enrichCtx, t, targetLanguage)
|
||||
@@ -1027,7 +1039,8 @@ func (s *tenderService) Recommend(ctx context.Context, form *SearchForm, paginat
|
||||
return nil, err
|
||||
}
|
||||
|
||||
paged := s.buildRecommendedTenderResponsesParallel(ctx, pageResult.items, lang)
|
||||
paged := s.buildRecommendedTenderListResponses(pageResult.items, lang)
|
||||
paged = s.enrichRecommendedTenderResponsesParallel(ctx, paged, lang)
|
||||
|
||||
return &SearchResponse{
|
||||
Tenders: paged,
|
||||
|
||||
Reference in New Issue
Block a user