234 lines
7.8 KiB
Go
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)
|
|
}
|
|
}
|