Files
tm_back/cmd/worker/workers/notice.go
T
n.nakhostin 1c3ad648c1 Implement Worker Service and Refactor Tender Initialization
- Introduced a new worker service with a dedicated main entry point for handling background tasks, including MongoDB connection management and notification service initialization.
- Added a bootstrap package to manage application configuration, logging, and service initialization for the worker.
- Implemented a NoticeWorker to process unprocessed notices and create corresponding tender entities, enhancing the integration of notice management within the tender system.
- Refactored the tender service to remove the Ollama SDK dependency, streamlining the service initialization in the main application.
- Enhanced the notice repository with a method to retrieve unprocessed notices, improving data handling capabilities.
2025-10-05 16:17:41 +03:30

301 lines
10 KiB
Go

package workers
import (
"context"
"fmt"
"tm/internal/notice"
"tm/internal/tender"
"tm/pkg/logger"
"tm/pkg/mongo"
"tm/pkg/notification"
"tm/pkg/ollama"
)
type NoticeWorker struct {
Mongo *mongo.ConnectionManager
Logger logger.Logger
Notify notification.SDK
Ollama *ollama.SDK
NoticeRepo notice.Repository
TenderRepo tender.TenderRepository
}
func NewNoticeWorker(mongo *mongo.ConnectionManager, logger logger.Logger, notify notification.SDK, noticeRepo notice.Repository, tenderRepo tender.TenderRepository, ollama *ollama.SDK) *NoticeWorker {
return &NoticeWorker{
Mongo: mongo,
Logger: logger,
Notify: notify,
Ollama: ollama,
NoticeRepo: noticeRepo,
TenderRepo: tenderRepo,
}
}
func (w *NoticeWorker) Run() {
w.Logger.Info("Notice worker started", map[string]interface{}{})
limit := 10
skip := 0
for {
notices, _, err := w.NoticeRepo.GetUnProcessedNotices(context.Background(), limit, skip)
if err != nil {
w.Logger.Error("Failed to get notices", map[string]interface{}{
"error": err.Error(),
})
continue
}
if len(notices) == 0 {
break
}
for _, n := range notices {
w.Logger.Info("Notice", map[string]interface{}{
"notice": n.ID.Hex(),
})
// exists, err := w.TenderRepo.GetByNoticePublicationID(context.Background(), n.NoticePublicationID)
// if err != nil {
// w.Logger.Error("Failed to get tender", map[string]interface{}{
// "error": err.Error(),
// })
// continue
// }
t, err := w.ToTender(&n)
if err != nil {
w.Logger.Error("Failed to create tender", map[string]interface{}{
"error": err.Error(),
})
continue
}
err = w.TenderRepo.Create(context.Background(), t)
if err != nil {
w.Logger.Error("Failed to create tender", map[string]interface{}{
"error": err.Error(),
})
}
}
skip += limit
}
}
func (w *NoticeWorker) translateAI(msg string) (string, error) {
builder := w.Ollama.NewChatBuilder().SetStream(false).SetMaxTokens(200000)
chat, err := builder.AddMessage(ollama.MessageRoleUser, fmt.Sprintf("Translate the following text into fluent, natural English while keeping the original meaning and tone. Do not summarize, shorten, or add extra explanations. Just provide the translation without other additional values. %s", msg)).Build()
if err != nil {
return "", err
}
resp, err := w.Ollama.Chat(context.Background(), chat)
if err != nil {
return "", err
}
return resp.Message.Content, nil
}
func (w *NoticeWorker) ToTender(n *notice.Notice) (*tender.Tender, error) {
// Buyer Organization
buyerOrg := &tender.Organization{}
if n.BuyerOrganization != nil {
buyerOrg = &tender.Organization{
Name: n.BuyerOrganization.Name,
CompanyID: n.BuyerOrganization.CompanyID,
WebsiteURI: n.BuyerOrganization.WebsiteURI,
ContactName: n.BuyerOrganization.ContactName,
ContactTelephone: n.BuyerOrganization.ContactTelephone,
ContactEmail: n.BuyerOrganization.ContactEmail,
ContactFax: n.BuyerOrganization.ContactFax,
Role: n.BuyerOrganization.Role,
Address: tender.Address{
StreetName: n.BuyerOrganization.Address.StreetName,
CityName: n.BuyerOrganization.Address.CityName,
PostalZone: n.BuyerOrganization.Address.PostalZone,
CountrySubentityCode: n.BuyerOrganization.Address.CountrySubentityCode,
Department: n.BuyerOrganization.Address.Department,
Region: n.BuyerOrganization.Address.Region,
CountryCode: n.BuyerOrganization.Address.CountryCode,
},
}
}
// Review Organization
reviewOrg := &tender.Organization{}
if n.ReviewOrganization != nil {
reviewOrg = &tender.Organization{
Name: n.ReviewOrganization.Name,
CompanyID: n.ReviewOrganization.CompanyID,
WebsiteURI: n.ReviewOrganization.WebsiteURI,
ContactName: n.ReviewOrganization.ContactName,
ContactTelephone: n.ReviewOrganization.ContactTelephone,
ContactEmail: n.ReviewOrganization.ContactEmail,
ContactFax: n.ReviewOrganization.ContactFax,
Role: n.ReviewOrganization.Role,
Address: tender.Address{
StreetName: n.ReviewOrganization.Address.StreetName,
CityName: n.ReviewOrganization.Address.CityName,
PostalZone: n.ReviewOrganization.Address.PostalZone,
CountrySubentityCode: n.ReviewOrganization.Address.CountrySubentityCode,
Department: n.ReviewOrganization.Address.Department,
Region: n.ReviewOrganization.Address.Region,
CountryCode: n.ReviewOrganization.Address.CountryCode,
},
}
}
// Organizations
organizations := make([]tender.Organization, len(n.Organizations))
for i, org := range n.Organizations {
organizations[i] = tender.Organization{
Name: org.Name,
CompanyID: org.CompanyID,
WebsiteURI: org.WebsiteURI,
ContactName: org.ContactName,
ContactTelephone: org.ContactTelephone,
ContactEmail: org.ContactEmail,
ContactFax: org.ContactFax,
Role: org.Role,
Address: tender.Address{
StreetName: org.Address.StreetName,
CityName: org.Address.CityName,
PostalZone: org.Address.PostalZone,
CountrySubentityCode: org.Address.CountrySubentityCode,
Department: org.Address.Department,
Region: org.Address.Region,
CountryCode: org.Address.CountryCode,
},
}
}
// Selection Criteria
selectionCriteria := make([]tender.SelectionCriterion, len(n.SelectionCriteria))
for i, criterion := range n.SelectionCriteria {
selectionCriteria[i] = tender.SelectionCriterion{
TypeCode: criterion.TypeCode,
Description: criterion.Description,
LanguageID: criterion.LanguageID,
}
}
// Winning Tenderer
winningTenderer := &tender.Organization{}
if n.WinningTenderer != nil {
winningTenderer = &tender.Organization{
Name: n.WinningTenderer.Name,
CompanyID: n.WinningTenderer.CompanyID,
WebsiteURI: n.WinningTenderer.WebsiteURI,
ContactName: n.WinningTenderer.ContactName,
ContactTelephone: n.WinningTenderer.ContactTelephone,
ContactEmail: n.WinningTenderer.ContactEmail,
ContactFax: n.WinningTenderer.ContactFax,
Role: n.WinningTenderer.Role,
Address: tender.Address{
StreetName: n.WinningTenderer.Address.StreetName,
CityName: n.WinningTenderer.Address.CityName,
PostalZone: n.WinningTenderer.Address.PostalZone,
CountrySubentityCode: n.WinningTenderer.Address.CountrySubentityCode,
Department: n.WinningTenderer.Address.Department,
Region: n.WinningTenderer.Address.Region,
CountryCode: n.WinningTenderer.Address.CountryCode,
},
}
}
// Modifications
modifications := make([]tender.TenderModification, len(n.Modifications))
for i, modification := range n.Modifications {
modifications[i] = tender.TenderModification{
ModificationDate: modification.ModificationDate,
ModificationReason: modification.ModificationReason,
Description: modification.Description,
LanguageID: modification.LanguageID,
}
}
// Awarded Entities
awardedEntities := make([]tender.Awarded, len(n.AwardedEntities))
for i, awarded := range n.AwardedEntities {
awardedEntities[i] = tender.Awarded{
Name: awarded.Name,
Address: awarded.Address,
Country: awarded.Country,
Amount: awarded.Amount,
Currency: awarded.Currency,
Share: awarded.Share,
AwardDate: awarded.AwardDate,
ContractID: awarded.ContractID,
TenderID: awarded.TenderID,
LotID: awarded.LotID,
CompanyID: awarded.CompanyID,
OrganizationID: awarded.OrganizationID,
}
}
title, err := w.translateAI(n.Title)
if err != nil {
return nil, fmt.Errorf("failed to translate title: %w", err)
}
description, err := w.translateAI(n.Description)
if err != nil {
return nil, fmt.Errorf("failed to translate description: %w", err)
}
return &tender.Tender{
NoticePublicationID: n.NoticePublicationID,
Title: title,
Description: description,
ProcurementTypeCode: n.ProcurementTypeCode,
ProcedureCode: n.ProcedureCode,
EstimatedValue: n.EstimatedValue,
Currency: n.Currency,
TenderDeadline: n.TenderDeadline,
SubmissionURL: n.SubmissionURL,
BuyerOrganization: buyerOrg,
ReviewOrganization: reviewOrg,
Organizations: organizations,
SelectionCriteria: selectionCriteria,
Status: tender.TenderStatus(n.Status),
Source: tender.TenderSource(n.Source),
SourceFileURL: n.SourceFileURL,
SourceFileName: n.SourceFileName,
TenderID: n.TenderID,
ContractNoticeID: n.ContractNoticeID,
ContractFolderID: n.ContractFolderID,
NoticeTypeCode: n.NoticeTypeCode,
NoticeSubTypeCode: n.NoticeSubTypeCode,
NoticeLanguageCode: n.NoticeLanguageCode,
IssueDate: n.IssueDate,
IssueTime: n.IssueTime,
PublicationDate: n.PublicationDate,
SubmissionDeadline: n.SubmissionDeadline,
ApplicationDeadline: n.ApplicationDeadline,
GazetteID: n.GazetteID,
Duration: n.Duration,
DurationUnit: n.DurationUnit,
PlaceOfPerformance: n.PlaceOfPerformance,
CountryCode: n.CountryCode,
RegionCode: n.RegionCode,
CityName: n.CityName,
PostalCode: n.PostalCode,
DocumentURI: n.DocumentURI,
TenderURL: n.TenderURL,
MainClassification: n.MainClassification,
AdditionalClassifications: n.AdditionalClassifications,
OfficialLanguages: n.OfficialLanguages,
CancellationReason: n.CancellationReason,
CancellationDate: n.CancellationDate,
AwardDate: n.AwardDate,
AwardedValue: n.AwardedValue,
ContractNumber: n.ContractNumber,
SuspensionReason: n.SuspensionReason,
SuspensionDate: n.SuspensionDate,
WinningTenderer: winningTenderer,
Modifications: modifications,
AwardedEntities: awardedEntities,
}, nil
}