1c3ad648c1
- 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.
301 lines
10 KiB
Go
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
|
|
}
|