package ai_summarizer import ( "strings" "time" ) // SummarizeRequest represents the request payload sent to the AI service // POST /ai/summarize endpoint for on-demand summarization. type SummarizeRequest struct { 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 { 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. type DocumentSummary struct { DocumentName string `json:"document_name"` DocumentType string `json:"document_type"` Summary string `json:"summary"` SummaryModel string `json:"summary_model"` Error string `json:"error"` // empty string when no error } // StoredDocument represents a document object stored for a tender in MinIO. type StoredDocument struct { ObjectName string `json:"-"` DocumentName string `json:"document_name"` Size int64 `json:"size"` LastModified int64 `json:"last_modified"` DocumentType string `json:"document_type,omitempty"` } // TranslateRequest represents the request payload sent to the AI service // POST /ai/translate endpoint. type TranslateRequest struct { ContractFolderID string `json:"contract_folder_id"` NoticePublicationID string `json:"notice_publication_id"` Title string `json:"title"` Description string `json:"description"` Language string `json:"language"` } // TranslateResponse represents the translation payload from POST /ai/translate. type TranslateResponse struct { ContractFolderID string `json:"contract_folder_id"` NoticePublicationID string `json:"notice_publication_id"` Language string `json:"language"` TranslatedTitle string `json:"translated_title"` TranslatedDescription string `json:"translated_description"` } // PipelineTranslateRequest represents the request payload for POST /pipeline/translate. type PipelineTranslateRequest struct { Languages []string `json:"languages"` } // PipelineTranslateResponse represents the response from POST /pipeline/translate. type PipelineTranslateResponse struct { Status string `json:"status"` Languages []string `json:"languages"` } // StoredTranslation is one language entry inside tender.json translations map. type StoredTranslation struct { Title string `json:"title"` Description string `json:"description"` } // TranslationStatus tracks which languages the async pipeline has finished. type TranslationStatus struct { Done []string `json:"done"` DoneUpper []string `json:"Done"` } func (s TranslationStatus) languagesDone() []string { if len(s.Done) > 0 { return s.Done } return s.DoneUpper } func languageInDone(done []string, language string) bool { lang := strings.ToLower(strings.TrimSpace(language)) for _, item := range done { if strings.ToLower(strings.TrimSpace(item)) == lang { return true } } return false } // TenderJSON represents the structure of the /tender.json file // stored in the AI service's MinIO bucket. type TenderJSON struct { OverallSummary *string `json:"overall_summary"` OverallSummaryCamel *string `json:"overallSummary"` SummarizeStatus SummarizeStatus `json:"summarize_status"` SummarizeStatusCamel SummarizeStatus `json:"summarizeStatus"` Translations map[string]StoredTranslation `json:"translations"` TranslationStatus TranslationStatus `json:"translation_status"` TranslationStatusCamel TranslationStatus `json:"translationStatus"` } // resolved returns whether summarization is marked done and the best-effort // overall summary pointer (may be nil or empty when the pipeline finished // without text yet). func (t *TenderJSON) resolved() (done bool, overall *string) { done = t.SummarizeStatus.isDone() || t.SummarizeStatusCamel.isDone() overall = t.OverallSummary if overall == nil || strings.TrimSpace(*overall) == "" { overall = t.OverallSummaryCamel } return done, overall } // SummarizeStatus represents the summarization status inside tender.json. type SummarizeStatus struct { Done bool `json:"done"` // true when the pipeline has finished DoneUpper bool `json:"Done"` // some writers emit PascalCase } func (s SummarizeStatus) isDone() bool { return s.Done || s.DoneUpper } // translationsDone returns languages marked complete by the translation pipeline. func (t *TenderJSON) translationsDone() []string { done := t.TranslationStatus.languagesDone() if len(done) > 0 { return done } return t.TranslationStatusCamel.languagesDone() } // translationForLanguage returns stored translation text when the pipeline marked // the language done and non-empty title exists. func (t *TenderJSON) translationForLanguage(language string) (StoredTranslation, bool) { if t == nil || !languageInDone(t.translationsDone(), language) { return StoredTranslation{}, false } lang := strings.ToLower(strings.TrimSpace(language)) if t.Translations != nil { for key, entry := range t.Translations { if strings.ToLower(strings.TrimSpace(key)) != lang { continue } if strings.TrimSpace(entry.Title) == "" { continue } return entry, true } } return StoredTranslation{}, false }