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"
|
"time"
|
||||||
|
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
"go.mongodb.org/mongo-driver/v2/bson"
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
|
|
||||||
"tm/internal/company"
|
"tm/internal/company"
|
||||||
"tm/pkg/response"
|
"tm/pkg/response"
|
||||||
)
|
)
|
||||||
@@ -92,7 +89,7 @@ func (s *tenderService) buildRecommendedTendersPageCache(ctx context.Context, co
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
responses := s.buildRecommendedTenderResponsesParallel(ctx, items, language)
|
responses := s.buildRecommendedTenderListResponses(items, language)
|
||||||
page := cachedRecommendedTendersPage{
|
page := cachedRecommendedTendersPage{
|
||||||
BuiltAt: time.Now().Unix(),
|
BuiltAt: time.Now().Unix(),
|
||||||
CompanyID: companyID,
|
CompanyID: companyID,
|
||||||
@@ -204,54 +201,6 @@ func (s *tenderService) buildAllRankedRecommendations(
|
|||||||
return out, nil
|
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(
|
func (s *tenderService) recommendFromPageCache(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
companyID string,
|
companyID string,
|
||||||
@@ -292,8 +241,11 @@ func (s *tenderService) recommendFromPageCache(
|
|||||||
end = len(filtered)
|
end = len(filtered)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
paged := append([]TenderResponse(nil), filtered[start:end]...)
|
||||||
|
paged = s.enrichRecommendedTenderResponsesParallel(ctx, paged, lang)
|
||||||
|
|
||||||
return &SearchResponse{
|
return &SearchResponse{
|
||||||
Tenders: append([]TenderResponse(nil), filtered[start:end]...),
|
Tenders: paged,
|
||||||
Metadata: pagination.ListMeta(total, "", end < len(filtered), start),
|
Metadata: pagination.ListMeta(total, "", end < len(filtered), start),
|
||||||
}, true
|
}, true
|
||||||
}
|
}
|
||||||
@@ -376,21 +328,6 @@ func filterCachedRecommendedTenders(
|
|||||||
return out
|
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.
|
// RecommendedTendersPageCacheKeyPrefix is shared with company invalidation logic.
|
||||||
const RecommendedTendersPageCacheKeyPrefix = recommendedTendersPageCacheKeyPrefix
|
const RecommendedTendersPageCacheKeyPrefix = recommendedTendersPageCacheKeyPrefix
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
"tm/pkg/ai_summarizer"
|
"tm/pkg/ai_summarizer"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -68,6 +71,72 @@ func (s *tenderService) buildRecommendedTenderListResponses(items []rankedRecomm
|
|||||||
return out
|
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(
|
func (s *tenderService) loadRecommendationExclusions(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
form *SearchForm,
|
form *SearchForm,
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ type tenderService struct {
|
|||||||
const (
|
const (
|
||||||
aiSummaryEnrichmentTimeout = 2 * time.Second
|
aiSummaryEnrichmentTimeout = 2 * time.Second
|
||||||
aiTranslationEnrichmentTimeout = 2 * time.Second
|
aiTranslationEnrichmentTimeout = 2 * time.Second
|
||||||
|
recommendTranslationEnrichmentTimeout = 5 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
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
|
// enrichWithTranslation overlays title/description from MinIO when the pipeline has finished
|
||||||
// for the requested language. Does not call the AI translate API on GET.
|
// 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) {
|
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 {
|
if t == nil || resp == nil {
|
||||||
return
|
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()
|
defer cancel()
|
||||||
|
|
||||||
stored, usedNotice, err := s.lookupTranslationForTender(enrichCtx, t, targetLanguage)
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
paged := s.buildRecommendedTenderResponsesParallel(ctx, pageResult.items, lang)
|
paged := s.buildRecommendedTenderListResponses(pageResult.items, lang)
|
||||||
|
paged = s.enrichRecommendedTenderResponsesParallel(ctx, paged, lang)
|
||||||
|
|
||||||
return &SearchResponse{
|
return &SearchResponse{
|
||||||
Tenders: paged,
|
Tenders: paged,
|
||||||
|
|||||||
Reference in New Issue
Block a user