f68b6d7787
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.
164 lines
4.4 KiB
Go
164 lines
4.4 KiB
Go
package tender
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
|
|
"go.mongodb.org/mongo-driver/v2/bson"
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
"tm/pkg/ai_summarizer"
|
|
)
|
|
|
|
type rankedRecommendedTender struct {
|
|
tender Tender
|
|
rank int
|
|
analysis string
|
|
tenderRef string
|
|
}
|
|
|
|
type recommendationTranslationCache struct {
|
|
mu sync.Mutex
|
|
items map[string]ai_summarizer.StoredTranslation
|
|
}
|
|
|
|
func newRecommendationTranslationCache() *recommendationTranslationCache {
|
|
return &recommendationTranslationCache{
|
|
items: make(map[string]ai_summarizer.StoredTranslation),
|
|
}
|
|
}
|
|
|
|
func recommendationTranslationCacheKey(tenderID, language string) string {
|
|
return strings.TrimSpace(tenderID) + "|" + strings.TrimSpace(language)
|
|
}
|
|
|
|
func (c *recommendationTranslationCache) get(tenderID, language string) (ai_summarizer.StoredTranslation, bool) {
|
|
if c == nil {
|
|
return ai_summarizer.StoredTranslation{}, false
|
|
}
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
stored, ok := c.items[recommendationTranslationCacheKey(tenderID, language)]
|
|
return stored, ok
|
|
}
|
|
|
|
func (c *recommendationTranslationCache) set(tenderID, language string, stored ai_summarizer.StoredTranslation) {
|
|
if c == nil {
|
|
return
|
|
}
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.items[recommendationTranslationCacheKey(tenderID, language)] = stored
|
|
}
|
|
|
|
// buildRecommendedTenderListResponses builds list responses without MinIO translation lookups.
|
|
// Recommendation pages use MongoDB text; tender detail endpoints still enrich from storage.
|
|
func (s *tenderService) buildRecommendedTenderListResponses(items []rankedRecommendedTender, language string) []TenderResponse {
|
|
if len(items) == 0 {
|
|
return nil
|
|
}
|
|
|
|
out := make([]TenderResponse, len(items))
|
|
for i, item := range items {
|
|
resp := item.tender.ToResponseWithLanguage(language)
|
|
resp.Rank = item.rank
|
|
resp.Analysis = item.analysis
|
|
resp.ProcedureRef = item.tenderRef
|
|
out[i] = *resp
|
|
}
|
|
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,
|
|
companyID string,
|
|
companyIDs []string,
|
|
) (map[string]struct{}, map[string]map[string]struct{}, error) {
|
|
if !form.ExcludeRejectedTenders {
|
|
return nil, nil, nil
|
|
}
|
|
|
|
if len(companyIDs) > 0 {
|
|
excludedByCompany, err := s.buildRejectedTenderIDSetsForCompanies(ctx, companyIDs)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to build recommendation exclusions: %w", err)
|
|
}
|
|
return nil, excludedByCompany, nil
|
|
}
|
|
|
|
excluded, err := s.buildRejectedTenderIDSet(ctx, companyID)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to build recommendation exclusions: %w", err)
|
|
}
|
|
return excluded, nil, nil
|
|
}
|