package document_scraper import ( "strings" "time" "tm/internal/tender" ) const ( enrichmentMainCPVDescription = "main_classification_description" vatBasisUnspecified = "unspecified" ) // BuildDocumentScraperTenderResponse maps a stored tender to the scraper API contract. func BuildDocumentScraperTenderResponse(t *tender.Tender) *DocumentScraperTenderResponse { if t == nil { return &DocumentScraperTenderResponse{ Lots: []DocumentScraperLotResponse{}, Winners: []DocumentScraperWinnerResponse{}, } } documentURL := strings.TrimSpace(t.DocumentURI) if documentURL == "" { documentURL = strings.TrimSpace(t.TenderURL) } mainDesc := firstNonEmpty( strings.TrimSpace(t.ProcessingMetadata.EnrichmentData[enrichmentMainCPVDescription]), strings.TrimSpace(t.MainClassificationDescription), ) resp := &DocumentScraperTenderResponse{ ContractFolderID: t.ContractFolderID, NoticePublicationID: t.NoticePublicationID, DocumentURL: documentURL, Title: t.Title, Description: t.Description, NoticeIdentifier: strings.TrimSpace(t.NoticeIdentifier), NoticeVersion: strings.TrimSpace(t.NoticeVersion), FormType: strings.TrimSpace(t.FormType), NoticeType: strings.TrimSpace(t.NoticeTypeCode), NoticeSubtype: strings.TrimSpace(t.NoticeSubTypeCode), PublicationDate: t.PublicationDate, NoticeDispatchDateWithTimezone: formatNoticeDispatchRFC3339(t), ProcurementTypeCode: strings.TrimSpace(t.ProcurementTypeCode), MainClassification: strings.TrimSpace(t.MainClassification), MainClassificationDescription: mainDesc, MainClassificationDisplay: classificationDisplay(strings.TrimSpace(t.MainClassification), mainDesc), AdditionalClassifications: append([]string(nil), t.AdditionalClassifications...), EstimatedValue: t.EstimatedValue, EstimatedValueCurrency: strings.TrimSpace(t.Currency), EstimatedValueVATBasis: vatBasisUnspecified, Status: string(t.Status), Buyer: buildBuyer(t), Lots: buildLots(t, mainDesc), Winners: buildWinners(t), } return resp } func formatNoticeDispatchRFC3339(t *tender.Tender) string { sec := t.NoticeDispatchAt if sec == 0 { sec = t.ESenderDispatchAt } if sec == 0 { return "" } return time.Unix(sec, 0).UTC().Format(time.RFC3339) } func buildBuyer(t *tender.Tender) DocumentScraperBuyerResponse { if t.BuyerOrganization == nil { return DocumentScraperBuyerResponse{} } b := t.BuyerOrganization return DocumentScraperBuyerResponse{ BuyerName: strings.TrimSpace(b.Name), BuyerEmail: strings.TrimSpace(b.ContactEmail), BuyerCountry: strings.TrimSpace(b.Address.CountryCode), BuyerRegionNuts: strings.TrimSpace(b.Address.CountrySubentityCode), BuyerCity: strings.TrimSpace(b.Address.CityName), BuyerProfileURL: strings.TrimSpace(firstNonEmpty(t.BuyerProfileURL, b.WebsiteURI)), } } func firstNonEmpty(values ...string) string { for _, v := range values { if s := strings.TrimSpace(v); s != "" { return s } } return "" } func classificationDisplay(code, description string) string { code = strings.TrimSpace(code) description = strings.TrimSpace(description) if code != "" && description != "" { return code + " - " + description } if code != "" { return code } return description } func buildLots(t *tender.Tender, globalMainDesc string) []DocumentScraperLotResponse { if len(t.ProcurementLots) == 0 { return []DocumentScraperLotResponse{} } out := make([]DocumentScraperLotResponse, 0, len(t.ProcurementLots)) for _, lot := range t.ProcurementLots { lotCode := strings.TrimSpace(lot.MainClassification) perLotDesc := strings.TrimSpace(lot.MainClassificationDescription) if perLotDesc == "" && lotCode != "" && lotCode == strings.TrimSpace(t.MainClassification) { perLotDesc = globalMainDesc } out = append(out, DocumentScraperLotResponse{ LotID: strings.TrimSpace(lot.LotID), LotTitle: strings.TrimSpace(lot.Title), LotDescription: strings.TrimSpace(lot.Description), LotMainNature: strings.TrimSpace(lot.MainNatureOfContract), LotMainClassificationCode: lotCode, LotMainClassificationDescription: perLotDesc, LotMainClassificationDisplay: classificationDisplay(lotCode, perLotDesc), AdditionalClassifications: append([]string(nil), lot.AdditionalClassifications...), LotDuration: strings.TrimSpace(lot.Duration), LotEstimatedValueExclVAT: lot.EstimatedValue, LotEstimatedValueCurrency: strings.TrimSpace(lot.Currency), }) } return out } func buildWinners(t *tender.Tender) []DocumentScraperWinnerResponse { if len(t.AwardedEntities) > 0 { out := make([]DocumentScraperWinnerResponse, 0, len(t.AwardedEntities)) for _, a := range t.AwardedEntities { w := DocumentScraperWinnerResponse{ OfficialName: strings.TrimSpace(a.Name), RegistrationNumber: strings.TrimSpace(a.CompanyID), PostalAddress: strings.TrimSpace(a.Address), Country: strings.TrimSpace(a.Country), LotID: strings.TrimSpace(a.LotID), Town: "", Postcode: "", CountrySubdivisionNuts: "", ContactPoint: "", Email: "", Telephone: "", } if org := orgByID(t, a.OrganizationID); org != nil { mergeWinnerFromOrganization(&w, org) } out = append(out, w) } return out } if t.WinningTenderer == nil { return []DocumentScraperWinnerResponse{} } w := t.WinningTenderer if strings.TrimSpace(w.Name) == "" { return []DocumentScraperWinnerResponse{} } addr := w.Address return []DocumentScraperWinnerResponse{{ OfficialName: strings.TrimSpace(w.Name), RegistrationNumber: strings.TrimSpace(w.CompanyID), PostalAddress: strings.TrimSpace(addr.StreetName), Town: strings.TrimSpace(addr.CityName), Postcode: strings.TrimSpace(addr.PostalZone), CountrySubdivisionNuts: strings.TrimSpace(addr.CountrySubentityCode), Country: strings.TrimSpace(addr.CountryCode), ContactPoint: strings.TrimSpace(w.ContactName), Email: strings.TrimSpace(w.ContactEmail), Telephone: strings.TrimSpace(w.ContactTelephone), }} } func orgByID(t *tender.Tender, id string) *tender.Organization { id = strings.TrimSpace(id) if id == "" || t == nil { return nil } for i := range t.Organizations { if strings.TrimSpace(t.Organizations[i].ID) == id { return &t.Organizations[i] } } return nil } func mergeWinnerFromOrganization(w *DocumentScraperWinnerResponse, org *tender.Organization) { if w == nil || org == nil { return } if w.OfficialName == "" { w.OfficialName = strings.TrimSpace(org.Name) } if w.RegistrationNumber == "" { w.RegistrationNumber = strings.TrimSpace(org.CompanyID) } if w.PostalAddress == "" { w.PostalAddress = strings.TrimSpace(org.Address.StreetName) } if w.Town == "" { w.Town = strings.TrimSpace(org.Address.CityName) } if w.Postcode == "" { w.Postcode = strings.TrimSpace(org.Address.PostalZone) } if w.CountrySubdivisionNuts == "" { w.CountrySubdivisionNuts = strings.TrimSpace(org.Address.CountrySubentityCode) } if w.Country == "" { w.Country = strings.TrimSpace(org.Address.CountryCode) } if w.ContactPoint == "" { w.ContactPoint = strings.TrimSpace(org.ContactName) } if w.Email == "" { w.Email = strings.TrimSpace(org.ContactEmail) } if w.Telephone == "" { w.Telephone = strings.TrimSpace(org.ContactTelephone) } }