Files
tm_back/internal/document_scraper/tender_response.go
T
2026-05-13 01:07:43 +03:30

234 lines
7.8 KiB
Go

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)
}
}