overalSummary nil response
This commit is contained in:
+50
-48
@@ -66,30 +66,31 @@ type SearchResponse struct {
|
|||||||
|
|
||||||
// TenderResponse represents a tender in API responses
|
// TenderResponse represents a tender in API responses
|
||||||
type TenderResponse struct {
|
type TenderResponse struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
NoticePublicationID string `json:"notice_publication_id"`
|
NoticePublicationID string `json:"notice_publication_id"`
|
||||||
NoticeTypeCode string `json:"notice_type_code"`
|
RelatedNoticePublicationIDs []string `json:"related_notice_publication_ids,omitempty"`
|
||||||
Title string `json:"title"`
|
NoticeTypeCode string `json:"notice_type_code"`
|
||||||
Description string `json:"description"`
|
Title string `json:"title"`
|
||||||
ProcurementTypeCode string `json:"procurement_type_code"`
|
Description string `json:"description"`
|
||||||
ProcedureCode string `json:"procedure_code"`
|
ProcurementTypeCode string `json:"procurement_type_code"`
|
||||||
MainClassification string `json:"main_classification"`
|
ProcedureCode string `json:"procedure_code"`
|
||||||
EstimatedValue float64 `json:"estimated_value"`
|
MainClassification string `json:"main_classification"`
|
||||||
Currency string `json:"currency"`
|
EstimatedValue float64 `json:"estimated_value"`
|
||||||
Duration string `json:"duration"`
|
Currency string `json:"currency"`
|
||||||
DurationUnit string `json:"duration_unit"`
|
Duration string `json:"duration"`
|
||||||
PublicationDate int64 `json:"publication_date"`
|
DurationUnit string `json:"duration_unit"`
|
||||||
TenderDeadline int64 `json:"tender_deadline"`
|
PublicationDate int64 `json:"publication_date"`
|
||||||
SubmissionDeadline int64 `json:"submission_deadline"`
|
TenderDeadline int64 `json:"tender_deadline"`
|
||||||
ApplicationDeadline int64 `json:"application_deadline"`
|
SubmissionDeadline int64 `json:"submission_deadline"`
|
||||||
SubmissionURL string `json:"submission_url"`
|
ApplicationDeadline int64 `json:"application_deadline"`
|
||||||
CountryCode string `json:"country_code"`
|
SubmissionURL string `json:"submission_url"`
|
||||||
BuyerOrganization *OrganizationResponse `json:"buyer_organization"`
|
CountryCode string `json:"country_code"`
|
||||||
Status TenderStatus `json:"status"`
|
BuyerOrganization *OrganizationResponse `json:"buyer_organization"`
|
||||||
TenderID string `json:"tender_id"`
|
Status TenderStatus `json:"status"`
|
||||||
CreatedAt int64 `json:"created_at"`
|
TenderID string `json:"tender_id"`
|
||||||
OverallSummary string `json:"overall_summary,omitempty"` // AI-generated summary from the pipeline
|
CreatedAt int64 `json:"created_at"`
|
||||||
Language string `json:"language,omitempty"`
|
OverallSummary string `json:"overall_summary,omitempty"` // AI-generated summary from the pipeline
|
||||||
|
Language string `json:"language,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TenderDocumentResponse represents a scraped tender document available for download.
|
// TenderDocumentResponse represents a scraped tender document available for download.
|
||||||
@@ -135,29 +136,30 @@ func (t *Tender) ToResponseWithLanguage(language string) *TenderResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
response := &TenderResponse{
|
response := &TenderResponse{
|
||||||
ID: t.ID.Hex(),
|
ID: t.ID.Hex(),
|
||||||
NoticePublicationID: t.NoticePublicationID,
|
NoticePublicationID: t.NoticePublicationID,
|
||||||
NoticeTypeCode: t.NoticeTypeCode,
|
RelatedNoticePublicationIDs: t.RelatedNoticePublicationIDs,
|
||||||
Title: title,
|
NoticeTypeCode: t.NoticeTypeCode,
|
||||||
Description: description,
|
Title: title,
|
||||||
ProcurementTypeCode: t.ProcurementTypeCode,
|
Description: description,
|
||||||
ProcedureCode: t.ProcedureCode,
|
ProcurementTypeCode: t.ProcurementTypeCode,
|
||||||
MainClassification: t.MainClassification,
|
ProcedureCode: t.ProcedureCode,
|
||||||
EstimatedValue: t.EstimatedValue,
|
MainClassification: t.MainClassification,
|
||||||
Currency: t.Currency,
|
EstimatedValue: t.EstimatedValue,
|
||||||
Duration: t.Duration,
|
Currency: t.Currency,
|
||||||
DurationUnit: t.DurationUnit,
|
Duration: t.Duration,
|
||||||
PublicationDate: t.PublicationDate,
|
DurationUnit: t.DurationUnit,
|
||||||
TenderDeadline: t.TenderDeadline,
|
PublicationDate: t.PublicationDate,
|
||||||
SubmissionDeadline: t.SubmissionDeadline,
|
TenderDeadline: t.TenderDeadline,
|
||||||
ApplicationDeadline: t.ApplicationDeadline,
|
SubmissionDeadline: t.SubmissionDeadline,
|
||||||
CountryCode: t.CountryCode,
|
ApplicationDeadline: t.ApplicationDeadline,
|
||||||
SubmissionURL: t.SubmissionURL,
|
CountryCode: t.CountryCode,
|
||||||
BuyerOrganization: org,
|
SubmissionURL: t.SubmissionURL,
|
||||||
Status: t.Status,
|
BuyerOrganization: org,
|
||||||
TenderID: t.TenderID,
|
Status: t.Status,
|
||||||
CreatedAt: t.CreatedAt,
|
TenderID: t.TenderID,
|
||||||
Language: usedLanguage,
|
CreatedAt: t.CreatedAt,
|
||||||
|
Language: usedLanguage,
|
||||||
}
|
}
|
||||||
|
|
||||||
return response
|
return response
|
||||||
@@ -180,7 +182,7 @@ type AITranslateResponse struct {
|
|||||||
type AISummaryResponse struct {
|
type AISummaryResponse struct {
|
||||||
NoticeID string `json:"notice_id"`
|
NoticeID string `json:"notice_id"`
|
||||||
OverallSummary string `json:"overall_summary"`
|
OverallSummary string `json:"overall_summary"`
|
||||||
Source string `json:"source"` // "storage" or "on_demand"
|
Source string `json:"source"` // storage | pending | unavailable
|
||||||
}
|
}
|
||||||
|
|
||||||
// AISummarizeResponse represents the response for triggering on-demand AI summarization
|
// AISummarizeResponse represents the response for triggering on-demand AI summarization
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
"tm/pkg/logger"
|
"tm/pkg/logger"
|
||||||
|
orm "tm/pkg/mongo"
|
||||||
"tm/pkg/response"
|
"tm/pkg/response"
|
||||||
|
|
||||||
"github.com/asaskevich/govalidator"
|
"github.com/asaskevich/govalidator"
|
||||||
@@ -477,11 +478,14 @@ func (h *TenderHandler) GetAISummary(c echo.Context) error {
|
|||||||
|
|
||||||
summary, err := h.service.GetAISummary(c.Request().Context(), id)
|
summary, err := h.service.GetAISummary(c.Request().Context(), id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, orm.ErrDocumentNotFound) {
|
||||||
|
return response.NotFound(c, "Tender not found")
|
||||||
|
}
|
||||||
h.logger.Error("Failed to get AI summary", map[string]interface{}{
|
h.logger.Error("Failed to get AI summary", map[string]interface{}{
|
||||||
"tender_id": id,
|
"tender_id": id,
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
return response.NotFound(c, err.Error())
|
return response.InternalServerError(c, "Failed to retrieve AI summary")
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.Success(c, summary, "AI summary retrieved successfully")
|
return response.Success(c, summary, "AI summary retrieved successfully")
|
||||||
@@ -574,11 +578,14 @@ func (h *TenderHandler) GetPublicAISummary(c echo.Context) error {
|
|||||||
|
|
||||||
summary, err := h.service.GetAISummary(c.Request().Context(), id)
|
summary, err := h.service.GetAISummary(c.Request().Context(), id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, orm.ErrDocumentNotFound) {
|
||||||
|
return response.NotFound(c, "Tender not found")
|
||||||
|
}
|
||||||
h.logger.Error("Failed to get AI summary", map[string]interface{}{
|
h.logger.Error("Failed to get AI summary", map[string]interface{}{
|
||||||
"tender_id": id,
|
"tender_id": id,
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
return response.NotFound(c, err.Error())
|
return response.InternalServerError(c, "Failed to retrieve AI summary")
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.Success(c, summary, "AI summary retrieved successfully")
|
return response.Success(c, summary, "AI summary retrieved successfully")
|
||||||
|
|||||||
+18
-14
@@ -827,14 +827,14 @@ func (s *tenderService) CleanupOldData(ctx context.Context, olderThan time.Durat
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetAISummary retrieves the AI-generated summary for a tender.
|
// GetAISummary retrieves the AI-generated summary for a tender.
|
||||||
// It first tries to fetch from MinIO storage (async pipeline result).
|
// It loads from MinIO storage (async pipeline result). When no summary exists yet,
|
||||||
// If not found or not ready, it returns an appropriate error.
|
// the pipeline is still running, or storage is unavailable, it returns 200-compatible
|
||||||
|
// data with an empty overall_summary and a Source hint instead of an error.
|
||||||
func (s *tenderService) GetAISummary(ctx context.Context, id string) (*AISummaryResponse, error) {
|
func (s *tenderService) GetAISummary(ctx context.Context, id string) (*AISummaryResponse, error) {
|
||||||
if id == "" {
|
if id == "" {
|
||||||
return nil, fmt.Errorf("tender ID cannot be empty")
|
return nil, fmt.Errorf("tender ID cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get tender to find the notice_publication_id
|
|
||||||
t, err := s.repository.GetByID(ctx, id)
|
t, err := s.repository.GetByID(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to get tender for AI summary", map[string]interface{}{
|
s.logger.Error("Failed to get tender for AI summary", map[string]interface{}{
|
||||||
@@ -844,20 +844,24 @@ func (s *tenderService) GetAISummary(ctx context.Context, id string) (*AISummary
|
|||||||
return nil, fmt.Errorf("failed to get tender: %w", err)
|
return nil, fmt.Errorf("failed to get tender: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if t == nil {
|
noticeID := strings.TrimSpace(t.NoticePublicationID)
|
||||||
return nil, fmt.Errorf("tender not found")
|
emptyResp := func(source string) *AISummaryResponse {
|
||||||
|
return &AISummaryResponse{
|
||||||
|
NoticeID: noticeID,
|
||||||
|
OverallSummary: "",
|
||||||
|
Source: source,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
noticeID := t.NoticePublicationID
|
if noticeID == "" || strings.TrimSpace(t.ContractFolderID) == "" {
|
||||||
if noticeID == "" {
|
return emptyResp("unavailable"), nil
|
||||||
return nil, fmt.Errorf("tender has no notice publication ID")
|
|
||||||
}
|
|
||||||
if t.ContractFolderID == "" {
|
|
||||||
return nil, fmt.Errorf("tender has no contract folder id")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.aiSummarizerStorage == nil {
|
if s.aiSummarizerStorage == nil {
|
||||||
return nil, fmt.Errorf("AI summarizer storage is not configured")
|
s.logger.Debug("AI summarizer storage not configured; returning empty AI summary", map[string]interface{}{
|
||||||
|
"tender_id": id,
|
||||||
|
})
|
||||||
|
return emptyResp("unavailable"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
summary, usedNotice, err := s.lookupSummaryForTender(ctx, t)
|
summary, usedNotice, err := s.lookupSummaryForTender(ctx, t)
|
||||||
@@ -877,10 +881,10 @@ func (s *tenderService) GetAISummary(ctx context.Context, id string) (*AISummary
|
|||||||
})
|
})
|
||||||
|
|
||||||
if errors.Is(err, ai_summarizer.ErrSummaryNotReady) {
|
if errors.Is(err, ai_summarizer.ErrSummaryNotReady) {
|
||||||
return nil, fmt.Errorf("AI summary is not ready yet, the pipeline is still processing")
|
return emptyResp("pending"), nil
|
||||||
}
|
}
|
||||||
if errors.Is(err, ai_summarizer.ErrObjectNotFound) || errors.Is(err, ai_summarizer.ErrNoOverallSummary) {
|
if errors.Is(err, ai_summarizer.ErrObjectNotFound) || errors.Is(err, ai_summarizer.ErrNoOverallSummary) {
|
||||||
return nil, fmt.Errorf("AI summary not found — this tender has not been processed by the AI pipeline yet")
|
return emptyResp("unavailable"), nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("failed to get AI summary: %w", err)
|
return nil, fmt.Errorf("failed to get AI summary: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user