package document_scraper import ( "context" "fmt" "time" "tm/internal/tender" "tm/pkg/logger" "tm/pkg/response" ) var documentScraperCountryCodes = []string{"SWE", "FIN", "NOR"} func isDocumentScraperCountry(countryCode string) bool { for _, code := range documentScraperCountryCodes { if countryCode == code { return true } } return false } // Service defines business logic for the document scraper API type Service interface { // ListPendingTenders retrieves tenders that need document scraping ListPendingTenders(ctx context.Context, limit, offset int) (*DocumentScraperListResponse, *response.Meta, error) // GetTenderByNoticeID retrieves a specific tender by its notice publication ID GetTenderByNoticeID(ctx context.Context, noticeID string) (*DocumentScraperTenderResponse, error) } type service struct { tenderRepo tender.TenderRepository logger logger.Logger } // NewService creates a new document scraper service func NewService(tenderRepo tender.TenderRepository, logger logger.Logger) Service { return &service{ tenderRepo: tenderRepo, logger: logger, } } // ListPendingTenders retrieves tenders that have not yet been scraped for documents. // Only returns tenders from Sweden, Finland, and Norway with active deadlines (deadline not yet reached). func (s *service) ListPendingTenders(ctx context.Context, limit, offset int) (*DocumentScraperListResponse, *response.Meta, error) { s.logger.Info("Listing pending tenders for document scraper", map[string]interface{}{ "limit": limit, "offset": offset, }) now := time.Now().Unix() tenders, hasMore, err := s.tenderRepo.GetPendingDocumentScrapeTenders(ctx, documentScraperCountryCodes, now, limit, offset) if err != nil { s.logger.Error("Failed to list pending tenders", map[string]interface{}{ "error": err.Error(), }) return nil, nil, fmt.Errorf("failed to list pending tenders: %w", err) } result := &DocumentScraperListResponse{ Tenders: make([]DocumentScraperTenderResponse, len(tenders)), } for i := range tenders { result.Tenders[i] = *BuildDocumentScraperTenderResponse(&tenders[i]) } meta := &response.Meta{ Total: len(tenders), Limit: limit, Offset: offset, HasMore: hasMore, } if meta.Limit > 0 { meta.Page = (offset / meta.Limit) + 1 } s.logger.Info("Pending tenders retrieved successfully", map[string]interface{}{ "count": len(tenders), "has_more": hasMore, }) return result, meta, nil } // GetTenderByNoticeID retrieves a specific tender by its notice publication ID. // Only returns tenders from Sweden, Finland, and Norway with active deadlines (deadline not yet reached). func (s *service) GetTenderByNoticeID(ctx context.Context, noticeID string) (*DocumentScraperTenderResponse, error) { s.logger.Info("Getting tender by notice ID for document scraper", map[string]interface{}{ "notice_id": noticeID, }) t, err := s.tenderRepo.GetByNoticePublicationID(ctx, noticeID) if err != nil { s.logger.Error("Failed to get tender by notice ID", map[string]interface{}{ "notice_id": noticeID, "error": err.Error(), }) return nil, fmt.Errorf("failed to get tender: %w", err) } if t == nil { return nil, fmt.Errorf("tender not found") } now := time.Now().Unix() if !isDocumentScraperCountry(t.CountryCode) || t.TenderDeadline <= now { s.logger.Info("Tender filtered out: unsupported country or deadline passed", map[string]interface{}{ "notice_id": noticeID, "country_code": t.CountryCode, "tender_deadline": t.TenderDeadline, "now": now, }) return nil, fmt.Errorf("tender not found") } result := BuildDocumentScraperTenderResponse(t) s.logger.Info("Tender retrieved successfully for document scraper", map[string]interface{}{ "notice_id": noticeID, "tender_id": t.ID.Hex(), }) return result, nil }