Merge branch 'develop' into TM-616
This commit is contained in:
+3
-1
@@ -167,9 +167,11 @@ func main() {
|
|||||||
// Initialize AI Summarizer service
|
// Initialize AI Summarizer service
|
||||||
var aiSummarizerClient tender.AISummarizerClient
|
var aiSummarizerClient tender.AISummarizerClient
|
||||||
var aiSummarizerStorage tender.AISummarizerStorage
|
var aiSummarizerStorage tender.AISummarizerStorage
|
||||||
|
var aiRecommendationClient company.AIRecommendationClient
|
||||||
|
|
||||||
if c := bootstrap.InitAISummarizerClient(conf.AISummarizer, mongoManager, logger); c != nil {
|
if c := bootstrap.InitAISummarizerClient(conf.AISummarizer, mongoManager, logger); c != nil {
|
||||||
aiSummarizerClient = c
|
aiSummarizerClient = c
|
||||||
|
aiRecommendationClient = c
|
||||||
}
|
}
|
||||||
if s := bootstrap.InitAISummarizerStorage(conf.AISummarizer, logger); s != nil {
|
if s := bootstrap.InitAISummarizerStorage(conf.AISummarizer, logger); s != nil {
|
||||||
aiSummarizerStorage = s
|
aiSummarizerStorage = s
|
||||||
@@ -210,7 +212,7 @@ func main() {
|
|||||||
auditLogger := audit.NewLogger(logger)
|
auditLogger := audit.NewLogger(logger)
|
||||||
userService := user.NewService(userRepository, logger, userAuthService, userValidator, notificationSDK, redisClient, auditLogger)
|
userService := user.NewService(userRepository, logger, userAuthService, userValidator, notificationSDK, redisClient, auditLogger)
|
||||||
categoryService := company_category.NewService(categoryRepository, logger)
|
categoryService := company_category.NewService(categoryRepository, logger)
|
||||||
companyService := company.NewService(companyRepository, categoryService, fileStoreService, logger)
|
companyService := company.NewService(companyRepository, categoryService, fileStoreService, aiRecommendationClient, logger)
|
||||||
customerService := customer.NewService(customerRepository, logger, customerAuthService, companyService, redisClient, notificationSDK, customerValidator, auditLogger)
|
customerService := customer.NewService(customerRepository, logger, customerAuthService, companyService, redisClient, notificationSDK, customerValidator, auditLogger)
|
||||||
tenderService := tender.NewService(tenderRepository, companyService, customerService, logger, aiSummarizerClient, aiSummarizerStorage, conf.AISummarizer.DefaultLanguage)
|
tenderService := tender.NewService(tenderRepository, companyService, customerService, logger, aiSummarizerClient, aiSummarizerStorage, conf.AISummarizer.DefaultLanguage)
|
||||||
feedbackService := feedback.NewService(feedbackRepo, tenderService, companyService, logger)
|
feedbackService := feedback.NewService(feedbackRepo, tenderService, companyService, logger)
|
||||||
|
|||||||
@@ -291,6 +291,14 @@ func RegisterPublicRoutes(e *echo.Echo, customerHandler *customer.Handler, tende
|
|||||||
companiesGP.GET("", companyHandler.MyCompany)
|
companiesGP.GET("", companyHandler.MyCompany)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AI tender recommendation routes
|
||||||
|
recommendationGP := v1.Group("")
|
||||||
|
recommendationGP.Use(customerHandler.AuthMiddleware())
|
||||||
|
{
|
||||||
|
recommendationGP.POST("/onboarding", companyHandler.StartOnboarding)
|
||||||
|
recommendationGP.POST("/recommend", companyHandler.RecommendTenders)
|
||||||
|
}
|
||||||
|
|
||||||
// Public Inquiry Routes
|
// Public Inquiry Routes
|
||||||
inquiryGP := v1.Group("/inquiries")
|
inquiryGP := v1.Group("/inquiries")
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -185,6 +185,18 @@ func (c *CompanyTags) ToResponse(categories []*CategoryResponse) *CompanyTagsRes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnboardingResponse is returned after starting AI company onboarding.
|
||||||
|
type OnboardingResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecommendedTenderResponse is one ranked tender recommendation from the AI service.
|
||||||
|
type RecommendedTenderResponse struct {
|
||||||
|
Rank string `json:"rank"`
|
||||||
|
TenderID string `json:"tender_id"`
|
||||||
|
Analysis string `json:"analysis"`
|
||||||
|
}
|
||||||
|
|
||||||
// CompanyProfileResponse represents the company profile data sent in API responses
|
// CompanyProfileResponse represents the company profile data sent in API responses
|
||||||
type CompanyProfileResponse struct {
|
type CompanyProfileResponse struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package company
|
package company
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
|
|
||||||
"tm/internal/user"
|
"tm/internal/user"
|
||||||
@@ -53,6 +54,72 @@ func (h *Handler) MyCompany(c echo.Context) error {
|
|||||||
return response.Success(c, company, "Company retrieved successfully")
|
return response.Success(c, company, "Company retrieved successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StartOnboarding starts AI onboarding for the authenticated customer's company (Mobile App)
|
||||||
|
// @Summary Start AI company onboarding
|
||||||
|
// @Description Send company profile, uploaded documents, and website URL to the AI team to start tender recommendation onboarding
|
||||||
|
// @Tags Company
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} response.APIResponse{data=OnboardingResponse}
|
||||||
|
// @Failure 401 {object} response.APIResponse "Unauthorized - User not authenticated"
|
||||||
|
// @Failure 404 {object} response.APIResponse "Company not found"
|
||||||
|
// @Failure 503 {object} response.APIResponse "AI recommendation service unavailable"
|
||||||
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Router /api/v1/onboarding [post]
|
||||||
|
func (h *Handler) StartOnboarding(c echo.Context) error {
|
||||||
|
companyID, err := user.GetCompanyIDFromContext(c)
|
||||||
|
if err != nil {
|
||||||
|
return response.Unauthorized(c, "User not authenticated")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.service.StartAIOnboarding(c.Request().Context(), companyID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, ErrAIRecommendationNotConfigured) {
|
||||||
|
return response.ServiceUnavailable(c, "AI recommendation service is not configured")
|
||||||
|
}
|
||||||
|
if err.Error() == "company not found" {
|
||||||
|
return response.NotFound(c, "Company not found")
|
||||||
|
}
|
||||||
|
return response.InternalServerError(c, "Failed to start AI onboarding")
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Success(c, result, "AI onboarding started successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecommendTenders returns AI-ranked tender recommendations for the authenticated customer's company (Mobile App)
|
||||||
|
// @Summary Get AI tender recommendations
|
||||||
|
// @Description Retrieve ranked tender recommendations from the AI team for the authenticated customer's company
|
||||||
|
// @Tags Company
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} response.APIResponse{data=[]RecommendedTenderResponse}
|
||||||
|
// @Failure 401 {object} response.APIResponse "Unauthorized - User not authenticated"
|
||||||
|
// @Failure 404 {object} response.APIResponse "Company not found"
|
||||||
|
// @Failure 503 {object} response.APIResponse "AI recommendation service unavailable"
|
||||||
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Router /api/v1/recommend [post]
|
||||||
|
func (h *Handler) RecommendTenders(c echo.Context) error {
|
||||||
|
companyID, err := user.GetCompanyIDFromContext(c)
|
||||||
|
if err != nil {
|
||||||
|
return response.Unauthorized(c, "User not authenticated")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.service.GetAIRecommendations(c.Request().Context(), companyID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, ErrAIRecommendationNotConfigured) {
|
||||||
|
return response.ServiceUnavailable(c, "AI recommendation service is not configured")
|
||||||
|
}
|
||||||
|
if err.Error() == "company not found" {
|
||||||
|
return response.NotFound(c, "Company not found")
|
||||||
|
}
|
||||||
|
return response.InternalServerError(c, "Failed to retrieve AI recommendations")
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Success(c, result, "AI recommendations retrieved successfully")
|
||||||
|
}
|
||||||
|
|
||||||
// **************************** ADMIN ENDPOINTS ****************************
|
// **************************** ADMIN ENDPOINTS ****************************
|
||||||
|
|
||||||
// Create creates a new company (Web Panel)
|
// Create creates a new company (Web Panel)
|
||||||
|
|||||||
@@ -0,0 +1,186 @@
|
|||||||
|
package company
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"tm/pkg/ai_summarizer"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrAIRecommendationNotConfigured = errors.New("AI recommendation service is not configured")
|
||||||
|
|
||||||
|
// AIRecommendationClient defines the interface for company tender recommendation AI operations.
|
||||||
|
type AIRecommendationClient interface {
|
||||||
|
StartOnboarding(ctx context.Context, reqBody ai_summarizer.OnboardingRequest) (*ai_summarizer.OnboardingResponse, error)
|
||||||
|
FetchRecommendations(ctx context.Context, reqBody ai_summarizer.RecommendRequest) ([]ai_summarizer.RecommendedTender, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type onboardingDocumentPayload struct {
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
ContentType string `json:"content_type"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartAIOnboarding sends company profile data to the AI team to start recommendation onboarding.
|
||||||
|
func (s *companyService) StartAIOnboarding(ctx context.Context, companyID string) (*OnboardingResponse, error) {
|
||||||
|
if s.aiRecommendationClient == nil {
|
||||||
|
return nil, ErrAIRecommendationNotConfigured
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(companyID) == "" {
|
||||||
|
return nil, errors.New("company ID is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
company, err := s.repository.GetByID(ctx, companyID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to load company for AI onboarding", map[string]interface{}{
|
||||||
|
"company_id": companyID,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return nil, errors.New("company not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
documents, err := s.buildOnboardingDocuments(company.DocumentFileIDs)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to prepare company documents for AI onboarding", map[string]interface{}{
|
||||||
|
"company_id": companyID,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return nil, fmt.Errorf("failed to prepare company documents: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
websiteURL := ""
|
||||||
|
if company.Website != nil {
|
||||||
|
websiteURL = strings.TrimSpace(*company.Website)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("Starting AI company onboarding", map[string]interface{}{
|
||||||
|
"company_id": companyID,
|
||||||
|
"documents_count": len(company.DocumentFileIDs),
|
||||||
|
"has_website_url": websiteURL != "",
|
||||||
|
})
|
||||||
|
|
||||||
|
result, err := s.aiRecommendationClient.StartOnboarding(ctx, ai_summarizer.OnboardingRequest{
|
||||||
|
CompanyID: companyID,
|
||||||
|
Documents: documents,
|
||||||
|
WebsiteURL: websiteURL,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("AI onboarding request failed", map[string]interface{}{
|
||||||
|
"company_id": companyID,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return nil, fmt.Errorf("failed to start AI onboarding: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &OnboardingResponse{Status: result.Status}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAIRecommendations retrieves ranked tender recommendations from the AI team.
|
||||||
|
func (s *companyService) GetAIRecommendations(ctx context.Context, companyID string) ([]RecommendedTenderResponse, error) {
|
||||||
|
if s.aiRecommendationClient == nil {
|
||||||
|
return nil, ErrAIRecommendationNotConfigured
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(companyID) == "" {
|
||||||
|
return nil, errors.New("company ID is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := s.repository.GetByID(ctx, companyID); err != nil {
|
||||||
|
s.logger.Error("Failed to load company for AI recommendations", map[string]interface{}{
|
||||||
|
"company_id": companyID,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return nil, errors.New("company not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("Fetching AI tender recommendations", map[string]interface{}{
|
||||||
|
"company_id": companyID,
|
||||||
|
})
|
||||||
|
|
||||||
|
items, err := s.aiRecommendationClient.FetchRecommendations(ctx, ai_summarizer.RecommendRequest{
|
||||||
|
CompanyID: companyID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("AI recommend request failed", map[string]interface{}{
|
||||||
|
"company_id": companyID,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return nil, fmt.Errorf("failed to fetch AI recommendations: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := make([]RecommendedTenderResponse, 0, len(items))
|
||||||
|
for _, item := range items {
|
||||||
|
responses = append(responses, RecommendedTenderResponse{
|
||||||
|
Rank: item.Rank,
|
||||||
|
TenderID: item.TenderID,
|
||||||
|
Analysis: item.Analysis,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *companyService) buildOnboardingDocuments(fileIDs []string) (string, error) {
|
||||||
|
if len(fileIDs) == 0 {
|
||||||
|
return "[]", nil
|
||||||
|
}
|
||||||
|
if s.fileStore == nil {
|
||||||
|
return "", errors.New("file storage is not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
payloads := make([]onboardingDocumentPayload, 0, len(fileIDs))
|
||||||
|
for _, fileID := range fileIDs {
|
||||||
|
if strings.TrimSpace(fileID) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, info, err := s.fileStore.Download(fileID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Warn("Skipping company document for AI onboarding", map[string]interface{}{
|
||||||
|
"file_id": fileID,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
content, readErr := io.ReadAll(reader)
|
||||||
|
closeErr := reader.Close()
|
||||||
|
if readErr != nil {
|
||||||
|
return "", fmt.Errorf("failed to read document %s: %w", fileID, readErr)
|
||||||
|
}
|
||||||
|
if closeErr != nil {
|
||||||
|
s.logger.Warn("Failed to close company document reader", map[string]interface{}{
|
||||||
|
"file_id": fileID,
|
||||||
|
"error": closeErr.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := fileID
|
||||||
|
contentType := "application/octet-stream"
|
||||||
|
if info != nil {
|
||||||
|
if info.Filename != "" {
|
||||||
|
filename = info.Filename
|
||||||
|
}
|
||||||
|
if info.ContentType != "" {
|
||||||
|
contentType = info.ContentType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
payloads = append(payloads, onboardingDocumentPayload{
|
||||||
|
Filename: filename,
|
||||||
|
ContentType: contentType,
|
||||||
|
Content: base64.StdEncoding.EncodeToString(content),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded, err := json.Marshal(payloads)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to encode company documents: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(encoded), nil
|
||||||
|
}
|
||||||
@@ -34,6 +34,12 @@ type Service interface {
|
|||||||
|
|
||||||
// UploadDocuments uploads files and attaches them to a company
|
// UploadDocuments uploads files and attaches them to a company
|
||||||
UploadDocuments(ctx context.Context, companyID, uploadedBy string, files []*multipart.FileHeader) (*UploadDocumentsResponse, error)
|
UploadDocuments(ctx context.Context, companyID, uploadedBy string, files []*multipart.FileHeader) (*UploadDocumentsResponse, error)
|
||||||
|
|
||||||
|
// StartAIOnboarding sends company data to the AI team for recommendation onboarding
|
||||||
|
StartAIOnboarding(ctx context.Context, companyID string) (*OnboardingResponse, error)
|
||||||
|
|
||||||
|
// GetAIRecommendations returns ranked tender recommendations from the AI team
|
||||||
|
GetAIRecommendations(ctx context.Context, companyID string) ([]RecommendedTenderResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// companyService implements the Service interface
|
// companyService implements the Service interface
|
||||||
@@ -41,15 +47,23 @@ type companyService struct {
|
|||||||
repository Repository
|
repository Repository
|
||||||
categoryService company_category.Service
|
categoryService company_category.Service
|
||||||
fileStore filestore.FileStoreService
|
fileStore filestore.FileStoreService
|
||||||
|
aiRecommendationClient AIRecommendationClient
|
||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new company service
|
// NewService creates a new company service
|
||||||
func NewService(repository Repository, categoryService company_category.Service, fileStore filestore.FileStoreService, logger logger.Logger) Service {
|
func NewService(
|
||||||
|
repository Repository,
|
||||||
|
categoryService company_category.Service,
|
||||||
|
fileStore filestore.FileStoreService,
|
||||||
|
aiRecommendationClient AIRecommendationClient,
|
||||||
|
logger logger.Logger,
|
||||||
|
) Service {
|
||||||
return &companyService{
|
return &companyService{
|
||||||
repository: repository,
|
repository: repository,
|
||||||
categoryService: categoryService,
|
categoryService: categoryService,
|
||||||
fileStore: fileStore,
|
fileStore: fileStore,
|
||||||
|
aiRecommendationClient: aiRecommendationClient,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -209,6 +209,72 @@ func (c *Client) TriggerPipelineSummarize(ctx context.Context) (*PipelineActionR
|
|||||||
return c.triggerPipelineAction(ctx, "/pipeline/summarize", "pipeline summarize")
|
return c.triggerPipelineAction(ctx, "/pipeline/summarize", "pipeline summarize")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StartOnboarding calls POST /onboarding to start company profile indexing for recommendations.
|
||||||
|
func (c *Client) StartOnboarding(ctx context.Context, reqBody OnboardingRequest) (*OnboardingResponse, error) {
|
||||||
|
jsonBody, err := json.Marshal(reqBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ai_summarizer: failed to marshal onboarding request body: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
url := c.config.APIBaseURL + "/onboarding"
|
||||||
|
httpResp, bodyBytes, err := c.doRawPost(ctx, url, jsonBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer httpResp.Body.Close()
|
||||||
|
|
||||||
|
if httpResp.StatusCode >= 400 {
|
||||||
|
return nil, fmt.Errorf("%w: status %d, body: %s", ErrAPINonSuccess, httpResp.StatusCode, string(bodyBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
var result OnboardingResponse
|
||||||
|
if len(bodyBytes) > 0 {
|
||||||
|
if err := json.Unmarshal(bodyBytes, &result); err != nil {
|
||||||
|
return nil, fmt.Errorf("ai_summarizer: failed to decode onboarding response: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.logger.Info("AI onboarding request succeeded", map[string]interface{}{
|
||||||
|
"company_id": reqBody.CompanyID,
|
||||||
|
"status": result.Status,
|
||||||
|
})
|
||||||
|
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchRecommendations calls POST /recommend to retrieve ranked tenders for a company.
|
||||||
|
func (c *Client) FetchRecommendations(ctx context.Context, reqBody RecommendRequest) ([]RecommendedTender, error) {
|
||||||
|
jsonBody, err := json.Marshal(reqBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ai_summarizer: failed to marshal recommend request body: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
url := c.config.APIBaseURL + "/recommend"
|
||||||
|
httpResp, bodyBytes, err := c.doRawPost(ctx, url, jsonBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer httpResp.Body.Close()
|
||||||
|
|
||||||
|
if httpResp.StatusCode >= 400 {
|
||||||
|
return nil, fmt.Errorf("%w: status %d, body: %s", ErrAPINonSuccess, httpResp.StatusCode, string(bodyBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
var result []RecommendedTender
|
||||||
|
if len(bodyBytes) > 0 {
|
||||||
|
if err := json.Unmarshal(bodyBytes, &result); err != nil {
|
||||||
|
return nil, fmt.Errorf("ai_summarizer: failed to decode recommend response: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.logger.Info("AI recommend request succeeded", map[string]interface{}{
|
||||||
|
"company_id": reqBody.CompanyID,
|
||||||
|
"count": len(result),
|
||||||
|
})
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) triggerPipelineAction(ctx context.Context, path, label string) (*PipelineActionResponse, error) {
|
func (c *Client) triggerPipelineAction(ctx context.Context, path, label string) (*PipelineActionResponse, error) {
|
||||||
url := c.config.APIBaseURL + path
|
url := c.config.APIBaseURL + path
|
||||||
httpResp, bodyBytes, err := c.doRawPost(ctx, url, nil)
|
httpResp, bodyBytes, err := c.doRawPost(ctx, url, nil)
|
||||||
|
|||||||
@@ -138,6 +138,30 @@ type PipelineActionResponse struct {
|
|||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnboardingRequest is the payload for POST /onboarding.
|
||||||
|
type OnboardingRequest struct {
|
||||||
|
CompanyID string `json:"company_id"`
|
||||||
|
Documents string `json:"documents"`
|
||||||
|
WebsiteURL string `json:"website_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnboardingResponse is returned by POST /onboarding.
|
||||||
|
type OnboardingResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecommendRequest is the payload for POST /recommend.
|
||||||
|
type RecommendRequest struct {
|
||||||
|
CompanyID string `json:"company_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecommendedTender is one ranked tender from POST /recommend.
|
||||||
|
type RecommendedTender struct {
|
||||||
|
Rank string `json:"rank"`
|
||||||
|
TenderID string `json:"tender_id"`
|
||||||
|
Analysis string `json:"analysis"`
|
||||||
|
}
|
||||||
|
|
||||||
// StoredTranslation is one language entry inside tender.json translations map.
|
// StoredTranslation is one language entry inside tender.json translations map.
|
||||||
type StoredTranslation struct {
|
type StoredTranslation struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
|
|||||||
Reference in New Issue
Block a user