tender summary response fix

This commit is contained in:
Mazyar
2026-05-17 17:02:12 +03:30
parent 0fbcc74377
commit 42d0a47226
5 changed files with 138 additions and 69 deletions
+16 -16
View File
@@ -166,25 +166,25 @@ func (w *DocumentSummarizationWorker) summarizeDocumentViaAI(ctx context.Context
return "", "", err.Error() return "", "", err.Error()
} }
noticeID := strings.TrimSpace(t.NoticePublicationID) if strings.TrimSpace(t.NoticePublicationID) == "" {
if noticeID == "" {
return "", "", "tender has no notice_publication_id for AI summarize request" return "", "", "tender has no notice_publication_id for AI summarize request"
} }
if strings.TrimSpace(t.ContractFolderID) == "" {
return "", "", "tender has no contract_folder_id for AI summarize request"
}
req := ai_summarizer.SummarizeRequest{ req := ai_summarizer.NewSummarizeRequest(ai_summarizer.SummarizeRequestInput{
NoticeID: noticeID, ContractFolderID: t.ContractFolderID,
DocumentURL: docURL, NoticePublicationID: t.NoticePublicationID,
Title: t.Title, DocumentURL: docURL,
Description: t.Description, Title: t.Title,
Country: t.CountryCode, Description: t.Description,
Currency: t.Currency, Country: t.CountryCode,
SubmissionDeadline: "", Currency: t.Currency,
AuthorityName: authorityNameFromTender(t), SubmissionDeadline: t.SubmissionDeadline,
} EstimatedValue: t.EstimatedValue,
if t.EstimatedValue > 0 { AuthorityName: authorityNameFromTender(t),
v := t.EstimatedValue })
req.EstimatedValue = &v
}
resp, err := w.AIClient.FetchSummaryOnDemand(ctx, req) resp, err := w.AIClient.FetchSummaryOnDemand(ctx, req)
if err != nil { if err != nil {
+4 -3
View File
@@ -535,9 +535,10 @@ type AISummaryResponse struct {
// AISummarizeResponse represents the response for triggering on-demand AI summarization // AISummarizeResponse represents the response for triggering on-demand AI summarization
type AISummarizeResponse struct { type AISummarizeResponse struct {
NoticeID string `json:"notice_id"` ContractFolderID string `json:"contract_folder_id"`
Summaries []AIDocumentSummary `json:"summaries"` NoticePublicationID string `json:"notice_publication_id"`
OverallSummary *string `json:"overall_summary"` Summaries []AIDocumentSummary `json:"summaries"`
OverallSummary *string `json:"overall_summary"`
} }
// AIDocumentSummary represents a single document summary from the AI service // AIDocumentSummary represents a single document summary from the AI service
+42 -30
View File
@@ -952,44 +952,45 @@ func (s *tenderService) TriggerAISummarize(ctx context.Context, id string) (*AIS
return nil, fmt.Errorf("tender not found") return nil, fmt.Errorf("tender not found")
} }
noticeID := t.NoticePublicationID if t.NoticePublicationID == "" {
if noticeID == "" {
return nil, fmt.Errorf("tender has no notice publication ID") return nil, fmt.Errorf("tender has no notice publication ID")
} }
if strings.TrimSpace(t.ContractFolderID) == "" {
// Build the AI summarizer request return nil, fmt.Errorf("tender has no contract folder ID")
reqBody := ai_summarizer.SummarizeRequest{
NoticeID: noticeID,
DocumentURL: t.DocumentURI,
Title: t.Title,
Description: t.Description,
Country: t.CountryCode,
SubmissionDeadline: time.UnixMilli(t.SubmissionDeadline).Format(time.RFC3339),
} }
if t.EstimatedValue > 0 { authorityName := ""
ev := t.EstimatedValue
reqBody.EstimatedValue = &ev
}
if t.Currency != "" {
reqBody.Currency = t.Currency
}
if t.BuyerOrganization != nil { if t.BuyerOrganization != nil {
reqBody.AuthorityName = t.BuyerOrganization.Name authorityName = t.BuyerOrganization.Name
} }
reqBody := ai_summarizer.NewSummarizeRequest(ai_summarizer.SummarizeRequestInput{
ContractFolderID: t.ContractFolderID,
NoticePublicationID: t.NoticePublicationID,
DocumentURL: t.DocumentURI,
Title: t.Title,
Description: t.Description,
Country: t.CountryCode,
Currency: t.Currency,
SubmissionDeadline: t.SubmissionDeadline,
EstimatedValue: t.EstimatedValue,
AuthorityName: authorityName,
})
s.logger.Info("Triggering on-demand AI summarization", map[string]interface{}{ s.logger.Info("Triggering on-demand AI summarization", map[string]interface{}{
"tender_id": id, "tender_id": id,
"notice_id": noticeID, "contract_folder_id": t.ContractFolderID,
"notice_publication_id": t.NoticePublicationID,
}) })
// Call the AI service // Call the AI service
resp, err := s.aiSummarizerClient.FetchSummaryOnDemand(ctx, reqBody) resp, err := s.aiSummarizerClient.FetchSummaryOnDemand(ctx, reqBody)
if err != nil { if err != nil {
s.logger.Error("On-demand AI summarization failed", map[string]interface{}{ s.logger.Error("On-demand AI summarization failed", map[string]interface{}{
"tender_id": id, "tender_id": id,
"notice_id": noticeID, "contract_folder_id": t.ContractFolderID,
"error": err.Error(), "notice_publication_id": t.NoticePublicationID,
"error": err.Error(),
}) })
return nil, fmt.Errorf("AI summarization request failed: %w", err) return nil, fmt.Errorf("AI summarization request failed: %w", err)
} }
@@ -1007,15 +1008,26 @@ func (s *tenderService) TriggerAISummarize(ctx context.Context, id string) (*AIS
} }
s.logger.Info("On-demand AI summarization completed", map[string]interface{}{ s.logger.Info("On-demand AI summarization completed", map[string]interface{}{
"tender_id": id, "tender_id": id,
"notice_id": noticeID, "contract_folder_id": t.ContractFolderID,
"summaries_count": len(summaries), "notice_publication_id": t.NoticePublicationID,
"summaries_count": len(summaries),
}) })
noticePublicationID := resp.ResolvedNoticePublicationID()
if noticePublicationID == "" {
noticePublicationID = t.NoticePublicationID
}
contractFolderID := strings.TrimSpace(resp.ContractFolderID)
if contractFolderID == "" {
contractFolderID = t.ContractFolderID
}
return &AISummarizeResponse{ return &AISummarizeResponse{
NoticeID: resp.NoticeID, ContractFolderID: contractFolderID,
Summaries: summaries, NoticePublicationID: noticePublicationID,
OverallSummary: resp.OverallSummary, Summaries: summaries,
OverallSummary: resp.OverallSummary,
}, nil }, nil
} }
+10 -7
View File
@@ -53,8 +53,9 @@ func (c *Client) FetchSummaryOnDemand(ctx context.Context, reqBody SummarizeRequ
for attempt := 0; attempt <= c.config.APIRetryCount; attempt++ { for attempt := 0; attempt <= c.config.APIRetryCount; attempt++ {
if attempt > 0 { if attempt > 0 {
c.logger.Warn("Retrying AI summarize request", map[string]interface{}{ c.logger.Warn("Retrying AI summarize request", map[string]interface{}{
"notice_id": reqBody.NoticeID, "contract_folder_id": reqBody.ContractFolderID,
"attempt": attempt, "notice_publication_id": reqBody.NoticePublicationID,
"attempt": attempt,
}) })
select { select {
case <-ctx.Done(): case <-ctx.Done():
@@ -67,9 +68,10 @@ func (c *Client) FetchSummaryOnDemand(ctx context.Context, reqBody SummarizeRequ
if err != nil { if err != nil {
lastErr = err lastErr = err
c.logger.Error("AI summarize request failed", map[string]interface{}{ c.logger.Error("AI summarize request failed", map[string]interface{}{
"notice_id": reqBody.NoticeID, "contract_folder_id": reqBody.ContractFolderID,
"attempt": attempt, "notice_publication_id": reqBody.NoticePublicationID,
"error": err.Error(), "attempt": attempt,
"error": err.Error(),
}) })
continue continue
} }
@@ -205,8 +207,9 @@ func (c *Client) doPost(ctx context.Context, url string, jsonBody []byte) (*Summ
} }
c.logger.Info("AI summarize request succeeded", map[string]interface{}{ c.logger.Info("AI summarize request succeeded", map[string]interface{}{
"notice_id": result.NoticeID, "contract_folder_id": result.ContractFolderID,
"summaries_count": len(result.Summaries), "notice_publication_id": result.ResolvedNoticePublicationID(),
"summaries_count": len(result.Summaries),
}) })
return &result, nil return &result, nil
+66 -13
View File
@@ -1,26 +1,79 @@
package ai_summarizer package ai_summarizer
import "strings" import (
"strings"
"time"
)
// SummarizeRequest represents the request payload sent to the AI service // SummarizeRequest represents the request payload sent to the AI service
// POST /ai/summarize endpoint for on-demand summarization. // POST /ai/summarize endpoint for on-demand summarization.
type SummarizeRequest struct { type SummarizeRequest struct {
NoticeID string `json:"notice_id"` // required ContractFolderID string `json:"contract_folder_id"`
DocumentURL string `json:"document_url"` // required NoticePublicationID string `json:"notice_publication_id"`
Title string `json:"title"` // required DocumentURL string `json:"document_url,omitempty"`
Description string `json:"description"` // required Title string `json:"title,omitempty"`
Country string `json:"country,omitempty"` // optional Description string `json:"description,omitempty"`
EstimatedValue *float64 `json:"estimated_value,omitempty"` // optional (pointer to distinguish 0 from absent) Country string `json:"country,omitempty"`
Currency string `json:"currency,omitempty"` // optional EstimatedValue *float64 `json:"estimated_value,omitempty"`
SubmissionDeadline string `json:"submission_deadline,omitempty"` // optional Currency string `json:"currency,omitempty"`
AuthorityName string `json:"authority_name,omitempty"` // optional SubmissionDeadline string `json:"submission_deadline,omitempty"`
AuthorityName string `json:"authority_name,omitempty"`
}
// SummarizeRequestInput collects fields used to build SummarizeRequest.
type SummarizeRequestInput struct {
ContractFolderID string
NoticePublicationID string
DocumentURL string
Title string
Description string
Country string
Currency string
SubmissionDeadline int64 // Unix seconds; omitted from request when zero
EstimatedValue float64
AuthorityName string
}
// NewSummarizeRequest builds a SummarizeRequest for POST /ai/summarize.
func NewSummarizeRequest(in SummarizeRequestInput) SummarizeRequest {
req := SummarizeRequest{
ContractFolderID: strings.TrimSpace(in.ContractFolderID),
NoticePublicationID: strings.TrimSpace(in.NoticePublicationID),
DocumentURL: strings.TrimSpace(in.DocumentURL),
Title: in.Title,
Description: in.Description,
Country: in.Country,
Currency: in.Currency,
AuthorityName: strings.TrimSpace(in.AuthorityName),
}
if in.EstimatedValue > 0 {
v := in.EstimatedValue
req.EstimatedValue = &v
}
if in.SubmissionDeadline > 0 {
req.SubmissionDeadline = time.Unix(in.SubmissionDeadline, 0).UTC().Format(time.RFC3339)
}
return req
} }
// SummarizeResponse represents the response from the AI summarization API. // SummarizeResponse represents the response from the AI summarization API.
type SummarizeResponse struct { type SummarizeResponse struct {
NoticeID string `json:"notice_id"` ContractFolderID string `json:"contract_folder_id"`
Summaries []DocumentSummary `json:"summaries"` NoticePublicationID string `json:"notice_publication_id"`
OverallSummary *string `json:"overall_summary"` // nil when not available NoticeID string `json:"notice_id"` // legacy field name
Summaries []DocumentSummary `json:"summaries"`
OverallSummary *string `json:"overall_summary"`
}
// ResolvedNoticePublicationID returns the notice publication ID from the response.
func (r *SummarizeResponse) ResolvedNoticePublicationID() string {
if r == nil {
return ""
}
if id := strings.TrimSpace(r.NoticePublicationID); id != "" {
return id
}
return strings.TrimSpace(r.NoticeID)
} }
// DocumentSummary represents a summary for a single document within the AI response. // DocumentSummary represents a summary for a single document within the AI response.