From 42d0a472265ef3394877640b780bf1c2b7045cf0 Mon Sep 17 00:00:00 2001 From: Mazyar Date: Sun, 17 May 2026 17:02:12 +0330 Subject: [PATCH] tender summary response fix --- cmd/worker/workers/summarizer.go | 32 ++++++------- internal/tender/form.go | 7 +-- internal/tender/service.go | 72 +++++++++++++++++------------ pkg/ai_summarizer/client.go | 17 ++++--- pkg/ai_summarizer/entities.go | 79 ++++++++++++++++++++++++++------ 5 files changed, 138 insertions(+), 69 deletions(-) diff --git a/cmd/worker/workers/summarizer.go b/cmd/worker/workers/summarizer.go index 8b3df6a..991d917 100644 --- a/cmd/worker/workers/summarizer.go +++ b/cmd/worker/workers/summarizer.go @@ -166,25 +166,25 @@ func (w *DocumentSummarizationWorker) summarizeDocumentViaAI(ctx context.Context return "", "", err.Error() } - noticeID := strings.TrimSpace(t.NoticePublicationID) - if noticeID == "" { + if strings.TrimSpace(t.NoticePublicationID) == "" { 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{ - NoticeID: noticeID, - DocumentURL: docURL, - Title: t.Title, - Description: t.Description, - Country: t.CountryCode, - Currency: t.Currency, - SubmissionDeadline: "", - AuthorityName: authorityNameFromTender(t), - } - if t.EstimatedValue > 0 { - v := t.EstimatedValue - req.EstimatedValue = &v - } + req := ai_summarizer.NewSummarizeRequest(ai_summarizer.SummarizeRequestInput{ + ContractFolderID: t.ContractFolderID, + NoticePublicationID: t.NoticePublicationID, + DocumentURL: docURL, + Title: t.Title, + Description: t.Description, + Country: t.CountryCode, + Currency: t.Currency, + SubmissionDeadline: t.SubmissionDeadline, + EstimatedValue: t.EstimatedValue, + AuthorityName: authorityNameFromTender(t), + }) resp, err := w.AIClient.FetchSummaryOnDemand(ctx, req) if err != nil { diff --git a/internal/tender/form.go b/internal/tender/form.go index d9508a2..edb4c89 100644 --- a/internal/tender/form.go +++ b/internal/tender/form.go @@ -535,9 +535,10 @@ type AISummaryResponse struct { // AISummarizeResponse represents the response for triggering on-demand AI summarization type AISummarizeResponse struct { - NoticeID string `json:"notice_id"` - Summaries []AIDocumentSummary `json:"summaries"` - OverallSummary *string `json:"overall_summary"` + ContractFolderID string `json:"contract_folder_id"` + NoticePublicationID string `json:"notice_publication_id"` + Summaries []AIDocumentSummary `json:"summaries"` + OverallSummary *string `json:"overall_summary"` } // AIDocumentSummary represents a single document summary from the AI service diff --git a/internal/tender/service.go b/internal/tender/service.go index 98a6900..280ea32 100644 --- a/internal/tender/service.go +++ b/internal/tender/service.go @@ -952,44 +952,45 @@ func (s *tenderService) TriggerAISummarize(ctx context.Context, id string) (*AIS return nil, fmt.Errorf("tender not found") } - noticeID := t.NoticePublicationID - if noticeID == "" { + if t.NoticePublicationID == "" { return nil, fmt.Errorf("tender has no notice publication ID") } - - // Build the AI summarizer request - 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 strings.TrimSpace(t.ContractFolderID) == "" { + return nil, fmt.Errorf("tender has no contract folder ID") } - if t.EstimatedValue > 0 { - ev := t.EstimatedValue - reqBody.EstimatedValue = &ev - } - if t.Currency != "" { - reqBody.Currency = t.Currency - } + authorityName := "" 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{}{ - "tender_id": id, - "notice_id": noticeID, + "tender_id": id, + "contract_folder_id": t.ContractFolderID, + "notice_publication_id": t.NoticePublicationID, }) // Call the AI service resp, err := s.aiSummarizerClient.FetchSummaryOnDemand(ctx, reqBody) if err != nil { s.logger.Error("On-demand AI summarization failed", map[string]interface{}{ - "tender_id": id, - "notice_id": noticeID, - "error": err.Error(), + "tender_id": id, + "contract_folder_id": t.ContractFolderID, + "notice_publication_id": t.NoticePublicationID, + "error": err.Error(), }) 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{}{ - "tender_id": id, - "notice_id": noticeID, - "summaries_count": len(summaries), + "tender_id": id, + "contract_folder_id": t.ContractFolderID, + "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{ - NoticeID: resp.NoticeID, - Summaries: summaries, - OverallSummary: resp.OverallSummary, + ContractFolderID: contractFolderID, + NoticePublicationID: noticePublicationID, + Summaries: summaries, + OverallSummary: resp.OverallSummary, }, nil } diff --git a/pkg/ai_summarizer/client.go b/pkg/ai_summarizer/client.go index 61329ee..590e715 100644 --- a/pkg/ai_summarizer/client.go +++ b/pkg/ai_summarizer/client.go @@ -53,8 +53,9 @@ func (c *Client) FetchSummaryOnDemand(ctx context.Context, reqBody SummarizeRequ for attempt := 0; attempt <= c.config.APIRetryCount; attempt++ { if attempt > 0 { c.logger.Warn("Retrying AI summarize request", map[string]interface{}{ - "notice_id": reqBody.NoticeID, - "attempt": attempt, + "contract_folder_id": reqBody.ContractFolderID, + "notice_publication_id": reqBody.NoticePublicationID, + "attempt": attempt, }) select { case <-ctx.Done(): @@ -67,9 +68,10 @@ func (c *Client) FetchSummaryOnDemand(ctx context.Context, reqBody SummarizeRequ if err != nil { lastErr = err c.logger.Error("AI summarize request failed", map[string]interface{}{ - "notice_id": reqBody.NoticeID, - "attempt": attempt, - "error": err.Error(), + "contract_folder_id": reqBody.ContractFolderID, + "notice_publication_id": reqBody.NoticePublicationID, + "attempt": attempt, + "error": err.Error(), }) 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{}{ - "notice_id": result.NoticeID, - "summaries_count": len(result.Summaries), + "contract_folder_id": result.ContractFolderID, + "notice_publication_id": result.ResolvedNoticePublicationID(), + "summaries_count": len(result.Summaries), }) return &result, nil diff --git a/pkg/ai_summarizer/entities.go b/pkg/ai_summarizer/entities.go index fcb0414..d85dac0 100644 --- a/pkg/ai_summarizer/entities.go +++ b/pkg/ai_summarizer/entities.go @@ -1,26 +1,79 @@ package ai_summarizer -import "strings" +import ( + "strings" + "time" +) // SummarizeRequest represents the request payload sent to the AI service // POST /ai/summarize endpoint for on-demand summarization. type SummarizeRequest struct { - NoticeID string `json:"notice_id"` // required - DocumentURL string `json:"document_url"` // required - Title string `json:"title"` // required - Description string `json:"description"` // required - Country string `json:"country,omitempty"` // optional - EstimatedValue *float64 `json:"estimated_value,omitempty"` // optional (pointer to distinguish 0 from absent) - Currency string `json:"currency,omitempty"` // optional - SubmissionDeadline string `json:"submission_deadline,omitempty"` // optional - AuthorityName string `json:"authority_name,omitempty"` // optional + ContractFolderID string `json:"contract_folder_id"` + NoticePublicationID string `json:"notice_publication_id"` + DocumentURL string `json:"document_url,omitempty"` + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty"` + Country string `json:"country,omitempty"` + EstimatedValue *float64 `json:"estimated_value,omitempty"` + Currency string `json:"currency,omitempty"` + 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. type SummarizeResponse struct { - NoticeID string `json:"notice_id"` - Summaries []DocumentSummary `json:"summaries"` - OverallSummary *string `json:"overall_summary"` // nil when not available + ContractFolderID string `json:"contract_folder_id"` + NoticePublicationID string `json:"notice_publication_id"` + 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.