Enhance tender repository and service with AI procedure reference handling
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
- Introduced `ProcedureReference` struct to encapsulate AI procedure coordinates for better data management. - Added `GetByProcedureReferences` method in the `TenderRepository` to retrieve multiple tenders based on AI procedure references in a single query. - Updated the `resolveRecommendedTenders` method in the service layer to utilize the new repository method, improving efficiency in fetching recommended tenders. - Enhanced error handling and logging for the new repository method to ensure robust operation. This update improves the handling of AI procedure references, streamlining tender retrieval processes and enhancing overall system performance.
This commit is contained in:
@@ -19,6 +19,13 @@ func FormatAIProcedureRef(contractFolderID, noticePublicationID string) string {
|
||||
return aiProcedurePrefix + contractFolderID + "/" + noticePublicationID
|
||||
}
|
||||
|
||||
// ProcedureReference identifies a tender using AI procedure coordinates.
|
||||
type ProcedureReference struct {
|
||||
Ref string
|
||||
ContractFolderID string
|
||||
NoticePublicationID string
|
||||
}
|
||||
|
||||
// ParseAIProcedureRef parses PROC_{contract_folder_id}/{notice_publication_id}.
|
||||
func ParseAIProcedureRef(ref string) (contractFolderID, noticePublicationID string, ok bool) {
|
||||
ref = strings.TrimSpace(ref)
|
||||
@@ -45,6 +52,21 @@ func ParseAIProcedureRef(ref string) (contractFolderID, noticePublicationID stri
|
||||
return contractFolderID, noticePublicationID, true
|
||||
}
|
||||
|
||||
func tenderMatchesProcedureRef(t Tender, contractFolderID, noticePublicationID string) bool {
|
||||
if strings.TrimSpace(t.ContractFolderID) != strings.TrimSpace(contractFolderID) {
|
||||
return false
|
||||
}
|
||||
if t.NoticePublicationID == noticePublicationID {
|
||||
return true
|
||||
}
|
||||
for _, related := range t.RelatedNoticePublicationIDs {
|
||||
if related == noticePublicationID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsMongoObjectIDRef reports whether ref is a 24-char hex MongoDB ObjectId.
|
||||
func IsMongoObjectIDRef(ref string) bool {
|
||||
ref = strings.TrimSpace(ref)
|
||||
|
||||
@@ -22,6 +22,7 @@ type TenderRepository interface {
|
||||
GetByContractNoticeID(ctx context.Context, contractNoticeID string) (*Tender, error)
|
||||
GetByNoticePublicationID(ctx context.Context, noticePublicationID string) (*Tender, error)
|
||||
GetByProcedureReference(ctx context.Context, contractFolderID, noticePublicationID string) (*Tender, error)
|
||||
GetByProcedureReferences(ctx context.Context, refs []ProcedureReference) (map[string]Tender, error)
|
||||
GetByContractFolderID(ctx context.Context, contractFolderID string) (*Tender, error)
|
||||
// GetLatestByContractFolderIDs returns the most recently updated tender per distinct contract_folder_id.
|
||||
GetLatestByContractFolderIDs(ctx context.Context, contractFolderIDs []string) (map[string]*Tender, error)
|
||||
@@ -333,6 +334,60 @@ func (r *tenderRepository) GetByProcedureReference(ctx context.Context, contract
|
||||
return &result.Items[0], nil
|
||||
}
|
||||
|
||||
// GetByProcedureReferences loads tenders for multiple AI procedure refs in one query.
|
||||
func (r *tenderRepository) GetByProcedureReferences(ctx context.Context, refs []ProcedureReference) (map[string]Tender, error) {
|
||||
out := make(map[string]Tender)
|
||||
if len(refs) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
orClauses := make([]bson.M, 0, len(refs))
|
||||
for _, ref := range refs {
|
||||
folder := strings.TrimSpace(ref.ContractFolderID)
|
||||
notice := strings.TrimSpace(ref.NoticePublicationID)
|
||||
if folder == "" || notice == "" {
|
||||
continue
|
||||
}
|
||||
orClauses = append(orClauses, bson.M{
|
||||
"contract_folder_id": folder,
|
||||
"$or": []bson.M{
|
||||
{"notice_publication_id": notice},
|
||||
{"related_notice_publication_ids": notice},
|
||||
},
|
||||
})
|
||||
}
|
||||
if len(orClauses) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
filter := bson.M{"$or": orClauses}
|
||||
pagination := orm.Pagination{
|
||||
Limit: len(orClauses) * 2,
|
||||
SortField: "updated_at",
|
||||
SortOrder: -1,
|
||||
}
|
||||
|
||||
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to get tenders by procedure references", map[string]interface{}{
|
||||
"count": len(orClauses),
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, ref := range refs {
|
||||
for i := range result.Items {
|
||||
if tenderMatchesProcedureRef(result.Items[i], ref.ContractFolderID, ref.NoticePublicationID) {
|
||||
out[ref.Ref] = result.Items[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// GetByContractFolderID retrieves a tender by its procedure-level identifier (ContractFolderID).
|
||||
// TED groups multiple notice publications (e.g. competition notice + result notice) under the same
|
||||
// ContractFolderID, so this lookup is the authoritative way to find the existing tender for a procedure
|
||||
|
||||
+16
-13
@@ -1672,6 +1672,7 @@ func (s *tenderService) enrichDocumentsScrapedFilter(ctx context.Context, form *
|
||||
|
||||
func (s *tenderService) resolveRecommendedTenders(ctx context.Context, recommendations []company.RecommendedTenderResponse) (map[string]Tender, error) {
|
||||
out := make(map[string]Tender)
|
||||
procRefs := make([]ProcedureReference, 0, len(recommendations))
|
||||
mongoIDs := make([]string, 0)
|
||||
|
||||
for _, rec := range recommendations {
|
||||
@@ -1681,19 +1682,11 @@ func (s *tenderService) resolveRecommendedTenders(ctx context.Context, recommend
|
||||
}
|
||||
|
||||
if folder, notice, ok := ParseAIProcedureRef(ref); ok {
|
||||
tender, err := s.repository.GetByProcedureReference(ctx, folder, notice)
|
||||
if err != nil {
|
||||
if errors.Is(err, orm.ErrDocumentNotFound) {
|
||||
s.logger.Warn("Recommended tender not found by procedure reference", map[string]interface{}{
|
||||
"tender_ref": ref,
|
||||
"contract_folder_id": folder,
|
||||
"notice_publication_id": notice,
|
||||
})
|
||||
continue
|
||||
}
|
||||
return nil, fmt.Errorf("failed to load recommended tender %s: %w", ref, err)
|
||||
}
|
||||
out[ref] = *tender
|
||||
procRefs = append(procRefs, ProcedureReference{
|
||||
Ref: ref,
|
||||
ContractFolderID: folder,
|
||||
NoticePublicationID: notice,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1707,6 +1700,16 @@ func (s *tenderService) resolveRecommendedTenders(ctx context.Context, recommend
|
||||
})
|
||||
}
|
||||
|
||||
if len(procRefs) > 0 {
|
||||
byProcedure, err := s.repository.GetByProcedureReferences(ctx, procRefs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load recommended tenders by procedure reference: %w", err)
|
||||
}
|
||||
for ref, tender := range byProcedure {
|
||||
out[ref] = tender
|
||||
}
|
||||
}
|
||||
|
||||
if len(mongoIDs) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user