added AI auto-generated keywords to customer profile

This commit is contained in:
Mazyar
2026-01-04 13:44:45 +03:30
parent 3381e04e05
commit 8dcda0e031
9 changed files with 435 additions and 48 deletions
+1
View File
@@ -21,6 +21,7 @@ type SearchForm struct {
CountryCodes []string `query:"country_codes" valid:"optional"`
RegionCodes []string `query:"region_codes" valid:"optional"`
CompanyID *string `query:"company_id" valid:"optional"`
CustomerID *string `query:"customer_id" valid:"optional"`
Status []string `query:"status" valid:"optional"`
Classifications []string `query:"classifications" valid:"optional"`
MinEstimatedValue *float64 `query:"min_estimated_value" valid:"optional"`
+13 -2
View File
@@ -216,13 +216,19 @@ func (h *TenderHandler) GetPublicTenders(c echo.Context) error {
form.Status = []string{string(TenderStatusActive)}
// Get company ID from customer context for automatic matching
// Get company ID and customer ID from customer context for automatic matching
var companyID *string
if customerCompanyID, ok := c.Get("company_id").(string); ok {
companyID = &customerCompanyID
}
form.CompanyID = companyID
var customerID *string
if customerIDStr, ok := c.Get("customer_id").(string); ok {
customerID = &customerIDStr
}
form.CustomerID = customerID
result, err := h.service.Search(c.Request().Context(), form, response.NewPagination(c))
if err != nil {
return response.InternalServerError(c, "Failed to retrieve tenders")
@@ -277,9 +283,14 @@ func (h *TenderHandler) RecommendTenders(c echo.Context) error {
if customerCompanyID, ok := c.Get("company_id").(string); ok {
companyID = &customerCompanyID
}
form.CompanyID = companyID
var customerID *string
if customerIDStr, ok := c.Get("customer_id").(string); ok {
customerID = &customerIDStr
}
form.CustomerID = customerID
result, err := h.service.Recommend(c.Request().Context(), form, response.NewPagination(c))
if err != nil {
return response.InternalServerError(c, "Failed to retrieve tenders")
+86 -24
View File
@@ -3,8 +3,10 @@ package tender
import (
"context"
"fmt"
"strings"
"time"
"tm/internal/company"
"tm/internal/customer"
"tm/pkg/logger"
"tm/pkg/response"
)
@@ -26,17 +28,19 @@ type Service interface {
// tenderService implements TenderService interface
type tenderService struct {
repository TenderRepository
companyService company.Service
logger logger.Logger
repository TenderRepository
companyService company.Service
customerService customer.Service
logger logger.Logger
}
// NewService creates a new tender service
func NewService(repository TenderRepository, companyService company.Service, logger logger.Logger) Service {
func NewService(repository TenderRepository, companyService company.Service, customerService customer.Service, logger logger.Logger) Service {
return &tenderService{
repository: repository,
companyService: companyService,
logger: logger,
repository: repository,
companyService: companyService,
customerService: customerService,
logger: logger,
}
}
@@ -156,8 +160,8 @@ func (s *tenderService) GetDocumentSummaries(ctx context.Context, id string) ([]
}
s.logger.Info("Retrieved document summaries for tender", map[string]interface{}{
"tender_id": id,
"summaries_count": len(tender.DocumentSummaries),
"tender_id": id,
"summaries_count": len(tender.DocumentSummaries),
})
return tender.DocumentSummaries, nil
@@ -166,13 +170,41 @@ func (s *tenderService) GetDocumentSummaries(ctx context.Context, id string) ([]
// ListTenders retrieves tenders with pagination and filtering
func (s *tenderService) Search(ctx context.Context, form *SearchForm, pagination *response.Pagination) (*SearchResponse, error) {
s.logger.Info("Listing tenders", map[string]interface{}{
"limit": pagination.Limit,
"offset": pagination.Offset,
"company_id": form.CompanyID,
"from": form.DeadlineFrom,
"to": form.DeadlineTo,
"limit": pagination.Limit,
"offset": pagination.Offset,
"company_id": form.CompanyID,
"customer_id": form.CustomerID,
"from": form.DeadlineFrom,
"to": form.DeadlineTo,
})
// If customer ID is provided, get customer keywords and add to search
if form.CustomerID != nil && *form.CustomerID != "" {
customerResp, err := s.customerService.GetByID(ctx, *form.CustomerID)
if err == nil && customerResp != nil {
// Only use keywords if status is ready
if customerResp.KeywordsStatus == "ready" && len(customerResp.Keywords) > 0 {
// Combine customer keywords with existing search query
keywordsQuery := strings.Join(customerResp.Keywords, " ")
if form.Search != nil && *form.Search != "" {
combinedSearch := *form.Search + " " + keywordsQuery
form.Search = &combinedSearch
} else {
form.Search = &keywordsQuery
}
s.logger.Info("Using customer keywords for search", map[string]interface{}{
"customer_id": *form.CustomerID,
"keywords": customerResp.Keywords,
})
} else {
s.logger.Info("Customer keywords not ready, skipping keyword-based search", map[string]interface{}{
"customer_id": *form.CustomerID,
"keywords_status": customerResp.KeywordsStatus,
})
}
}
}
tenders, total, err := s.repository.Search(ctx, form, pagination)
if err != nil {
s.logger.Error("Failed to list tenders", map[string]interface{}{
@@ -240,20 +272,50 @@ func (s *tenderService) Search(ctx context.Context, form *SearchForm, pagination
// Recommend retrieves tenders with pagination and filtering
func (s *tenderService) Recommend(ctx context.Context, form *SearchForm, pagination *response.Pagination) (*SearchResponse, error) {
s.logger.Info("Recommend tenders", map[string]interface{}{
"company_id": form.CompanyID,
"company_id": form.CompanyID,
"customer_id": form.CustomerID,
})
company, err := s.companyService.GetByID(ctx, *form.CompanyID)
if err != nil {
s.logger.Error("Failed to get company", map[string]interface{}{
"company_id": form.CompanyID,
"error": err.Error(),
})
return nil, fmt.Errorf("failed to get company: %w", err)
form.Status = []string{string(TenderStatusActive)}
// Priority: Use customer keywords if available, otherwise use company CPV codes
if form.CustomerID != nil && *form.CustomerID != "" {
customerResp, err := s.customerService.GetByID(ctx, *form.CustomerID)
if err == nil && customerResp != nil {
// Only use keywords if status is ready
if customerResp.KeywordsStatus == "ready" && len(customerResp.Keywords) > 0 {
// Use customer keywords for recommendation
keywordsQuery := strings.Join(customerResp.Keywords, " ")
form.Search = &keywordsQuery
s.logger.Info("Using customer keywords for recommendation", map[string]interface{}{
"customer_id": *form.CustomerID,
"keywords": customerResp.Keywords,
})
} else {
s.logger.Info("Customer keywords not ready, falling back to company CPV codes", map[string]interface{}{
"customer_id": *form.CustomerID,
"keywords_status": customerResp.KeywordsStatus,
})
}
}
}
form.Status = []string{string(TenderStatusActive)}
form.Classifications = company.Tags.CPVCodes
// Fallback to company CPV codes if no customer keywords
if form.Search == nil || *form.Search == "" {
if form.CompanyID != nil && *form.CompanyID != "" {
company, err := s.companyService.GetByID(ctx, *form.CompanyID)
if err != nil {
s.logger.Error("Failed to get company", map[string]interface{}{
"company_id": form.CompanyID,
"error": err.Error(),
})
return nil, fmt.Errorf("failed to get company: %w", err)
}
if company.Tags != nil && len(company.Tags.CPVCodes) > 0 {
form.Classifications = company.Tags.CPVCodes
}
}
}
tenders, total, err := s.repository.Search(ctx, form, pagination)
if err != nil {