Enhance tender recommendation service with improved caching and pagination
continuous-integration/drone/push Build is passing

- Introduced a context with a timeout for Redis cache retrieval in the `getCachedAIRecommendations` method, ensuring more robust handling of potential delays.
- Added a new file `recommendation_page.go` to encapsulate the logic for building recommendation pages, improving code organization and readability.
- Implemented a `buildRecommendationPage` method to handle pagination and batch processing of recommendations, enhancing performance and user experience.
- Created a new test file `recommendation_page_test.go` to validate the functionality of the recommendation page building process, ensuring correct pagination and batch resolution.

This update significantly improves the efficiency and maintainability of the tender recommendation service by optimizing caching and implementing structured pagination handling.
This commit is contained in:
Mazyar
2026-07-08 01:32:27 +03:30
parent 2980a3beb4
commit f108733c2a
5 changed files with 271 additions and 96 deletions
@@ -0,0 +1,86 @@
package tender
import (
"context"
"fmt"
"testing"
"go.mongodb.org/mongo-driver/v2/bson"
"tm/internal/company"
)
type stubRecommendTenderRepository struct {
TenderRepository
batchCalls int
}
func (s *stubRecommendTenderRepository) GetByProcedureReferences(_ context.Context, refs []ProcedureReference) (map[string]Tender, error) {
s.batchCalls++
out := make(map[string]Tender, len(refs))
for i, ref := range refs {
tender := Tender{
ContractFolderID: ref.ContractFolderID,
NoticePublicationID: ref.NoticePublicationID,
Title: ref.Ref,
Status: TenderStatusActive,
}
tender.ID = bsonObjectIDFromCounter(i + s.batchCalls*1000)
out[ref.Ref] = tender
}
return out, nil
}
func bsonObjectIDFromCounter(n int) bson.ObjectID {
var id [12]byte
id[11] = byte(n)
id[10] = byte(n >> 8)
return bson.ObjectID(id)
}
func TestBuildRecommendationPageResolvesInBatchesAndPaginates(t *testing.T) {
repo := &stubRecommendTenderRepository{}
service := &tenderService{
repository: repo,
logger: noopTenderTestLogger{},
}
recommendations := make([]company.RecommendedTenderResponse, 0, 120)
for i := 0; i < 120; i++ {
recommendations = append(recommendations, company.RecommendedTenderResponse{
Rank: i + 1,
TenderID: fmt.Sprintf("PROC_folder/notice-%d", i),
Analysis: "fit",
})
}
form := &SearchForm{
OnlyActiveDeadlines: false,
ExcludeRejectedTenders: false,
Status: []string{string(TenderStatusActive)},
}
got, err := service.buildRecommendationPage(
context.Background(),
recommendations,
form,
nil,
nil,
nil,
0,
10,
10,
)
if err != nil {
t.Fatalf("buildRecommendationPage() error = %v", err)
}
if repo.batchCalls != 2 {
t.Fatalf("batchCalls = %d, want 2", repo.batchCalls)
}
if got.total != 120 {
t.Fatalf("total = %d, want 120", got.total)
}
if len(got.items) != 10 {
t.Fatalf("len(items) = %d, want 10", len(got.items))
}
}