package ted import ( "strconv" "strings" "time" "tm/internal/notice" ) // mapToTenderFromParsedDoc maps a parsed TED document to tender entity func (s *TEDScraper) mapToTenderFromParsedDoc(parsedDoc *ParsedDocument, sourceFileURL, sourceFileName string) *notice.Notice { switch parsedDoc.Type { case DocumentTypeContractNotice: if parsedDoc.ContractNotice == nil { s.logger.Error("ContractNotice is nil", map[string]interface{}{ "document_type": string(parsedDoc.Type), }) return nil } return s.mapToTender(parsedDoc.ContractNotice, sourceFileURL, sourceFileName) case DocumentTypeContractAwardNotice: if parsedDoc.ContractAwardNotice == nil { s.logger.Error("ContractAwardNotice is nil", map[string]interface{}{ "document_type": string(parsedDoc.Type), }) return nil } return s.mapContractAwardNoticeToTender(parsedDoc.ContractAwardNotice, sourceFileURL, sourceFileName) case DocumentTypePriorInformationNotice: if parsedDoc.PriorInformationNotice == nil { s.logger.Error("PriorInformationNotice is nil", map[string]interface{}{ "document_type": string(parsedDoc.Type), }) return nil } return s.mapGenericNoticeToTender(parsedDoc.PriorInformationNotice, sourceFileURL, sourceFileName) default: s.logger.Error("Unknown document type for mapping", map[string]interface{}{ "document_type": string(parsedDoc.Type), }) return nil } } // mapGenericNoticeToTender maps any TED document type to tender entity using the common interface func (s *TEDScraper) mapGenericNoticeToTender(doc TEDDocument, sourceFileURL, sourceFileName string) *notice.Notice { if doc == nil { s.logger.Error("Document is nil in mapGenericNoticeToTender", map[string]interface{}{}) return nil } now := time.Now().Unix() t := ¬ice.Notice{ ContractNoticeID: doc.GetID(), ContractFolderID: "", // Not available in generic interface NoticeTypeCode: doc.GetNoticeType(), NoticeSubTypeCode: "", // Not available in generic interface NoticeLanguageCode: doc.GetLanguage(), IssueDate: now, // TODO: Parse actual date from document IssueTime: now, // TODO: Parse actual time from document GazetteID: "", // Not available in generic interface NoticePublicationID: "", // Not available in generic interface PublicationDate: now, // TODO: Parse actual publication date // Basic tender information Title: "Generic Notice " + doc.GetNoticeType(), Description: "Processed from " + doc.GetNoticeType() + " document", Status: notice.TenderStatusActive, Source: notice.TenderSourceTEDScraper, SourceFileURL: sourceFileURL, SourceFileName: sourceFileName, ProcessingMetadata: notice.ProcessingMetadata{ ScrapedAt: now, ProcessedAt: now, ProcessingVersion: "1.0", }, } // Generate the tender ID after creating the tender t.TenderID = GenerateTenderID(t) return t } // mapToTender maps a TED ContractNotice to tender entity func (s *TEDScraper) mapToTender(cn *ContractNotice, sourceFileURL, sourceFileName string) *notice.Notice { now := time.Now().Unix() t := ¬ice.Notice{ ContractNoticeID: cn.ID, ContractFolderID: cn.ContractFolderID, NoticeTypeCode: cn.GetNoticeSubType(), NoticeSubTypeCode: cn.GetNoticeSubType(), NoticeLanguageCode: cn.NoticeLanguageCode, IssueDate: ParseDateToUnix(cn.IssueDate), IssueTime: ParseTimeToUnix(cn.IssueTime), GazetteID: cn.GetOJSID(), Title: cn.GetProcurementTitle(), Description: cn.GetProcurementDescription(), ProcurementTypeCode: cn.GetContractNature(), ProcedureCode: cn.GetProcedureCode(), MainClassification: cn.GetMainClassification(), PlaceOfPerformance: cn.GetPlaceOfPerformance(), DocumentURI: cn.GetDocumentURI(), Status: notice.TenderStatusActive, Source: notice.TenderSourceTEDScraper, SourceFileURL: sourceFileURL, SourceFileName: sourceFileName, ProcessingMetadata: notice.ProcessingMetadata{ ScrapedAt: now, ProcessedAt: now, ProcessingVersion: "1.0", }, } // Set publication information if pubInfo := cn.GetPublicationInfo(); pubInfo != nil { t.NoticePublicationID = pubInfo.NoticePublicationID t.PublicationDate = ParseDateToUnix(pubInfo.PublicationDate) t.TenderURL = GenerateTenderURL(pubInfo.NoticePublicationID) } // Set additional classifications if cn.ProcurementProject != nil && cn.ProcurementProject.AdditionalCommodityClassification != nil { for _, additionalClass := range cn.ProcurementProject.AdditionalCommodityClassification { if additionalClass.ItemClassificationCode != "" { t.AdditionalClassifications = append(t.AdditionalClassifications, additionalClass.ItemClassificationCode) } } } // Set estimated value and currency if amount, currency := cn.GetBudget(); amount != "" { if value, err := strconv.ParseFloat(amount, 64); err == nil { t.EstimatedValue = value t.Currency = currency } } // Set duration information if duration, unit := cn.GetDuration(); duration != "" { t.Duration = duration t.DurationUnit = unit } // Set tender deadline if deadline := cn.GetTenderDeadline(); deadline != "" { t.TenderDeadline = ParseDateToUnix(deadline) if deadlineTime, err := ParseDate(deadline); err == nil { applicationDeadline := CalculateApplicationDeadline(deadlineTime) t.ApplicationDeadline = applicationDeadline.Unix() } } if pubDate, err := ParseDate(cn.GetPublicationDate()); err == nil { submissionDeadline := CalculateSubmissionDeadline(pubDate) t.SubmissionDeadline = submissionDeadline.Unix() } // Set SubmissionURL from TenderingTerms if cn.ProcurementProjectLot != nil && cn.ProcurementProjectLot.TenderingTerms != nil { if tenderingTerms := cn.ProcurementProjectLot.TenderingTerms; tenderingTerms.TenderRecipientParty != nil { if endpointID := tenderingTerms.TenderRecipientParty.EndpointID; endpointID != "" { if strings.HasPrefix(endpointID, "http") { t.SubmissionURL = endpointID } else { t.SubmissionURL = "https://" + endpointID } } } } // Set location information if cn.ProcurementProject != nil && cn.ProcurementProject.RealizedLocation != nil { if addr := cn.ProcurementProject.RealizedLocation.Address; addr != nil { t.CityName = addr.CityName t.PostalCode = addr.PostalZone t.RegionCode = addr.CountrySubentityCode if addr.Country != nil { t.CountryCode = addr.Country.IdentificationCode } } } // Set buyer organization buyerID := cn.GetBuyerID() if buyerID != "" { if buyerOrg := cn.GetOrganizationByID(buyerID); buyerOrg != nil { t.BuyerOrganization = s.mapOrganization(buyerOrg, "buyer") } } // Set review organization reviewID := cn.GetReviewOrganizationID() if reviewID != "" { if reviewOrg := cn.GetOrganizationByID(reviewID); reviewOrg != nil { t.ReviewOrganization = s.mapOrganization(reviewOrg, "review") } } // Set all organizations if orgs := cn.GetOrganizations(); orgs != nil { for _, org := range orgs.Organization { mappedOrg := s.mapOrganization(&org, "") if mappedOrg != nil { t.Organizations = append(t.Organizations, *mappedOrg) } } } // Set selection criteria if criteria := cn.GetSelectionCriteria(); criteria != nil { for _, criterion := range criteria { t.SelectionCriteria = append(t.SelectionCriteria, notice.SelectionCriterion{ TypeCode: criterion.CriterionTypeCode, Description: criterion.Description, LanguageID: criterion.LanguageID, }) } } // Set official languages if languages := cn.GetOfficialLanguages(); languages != nil { t.OfficialLanguages = languages } // Generate unique tender ID t.TenderID = GenerateTenderID(t) return t } // mapContractAwardNoticeToTender maps a TED ContractAwardNotice to tender entity func (s *TEDScraper) mapContractAwardNoticeToTender(can *ContractAwardNotice, sourceFileURL, sourceFileName string) *notice.Notice { now := time.Now().Unix() t := ¬ice.Notice{ ContractNoticeID: can.ID, ContractFolderID: can.ContractFolderID, NoticeTypeCode: can.NoticeTypeCode, NoticeSubTypeCode: can.GetNoticeSubType(), NoticeLanguageCode: can.NoticeLanguageCode, IssueDate: ParseDateToUnix(can.IssueDate), IssueTime: ParseTimeToUnix(can.IssueTime), GazetteID: can.GetOJSID(), Title: can.GetProcurementTitle(), Description: can.GetProcurementDescription(), ProcurementTypeCode: can.GetContractNature(), ProcedureCode: can.GetProcedureCode(), MainClassification: can.GetMainClassification(), Status: notice.TenderStatusAwarded, // Award notices are for completed tenders Source: notice.TenderSourceTEDScraper, SourceFileURL: sourceFileURL, SourceFileName: sourceFileName, ProcessingMetadata: notice.ProcessingMetadata{ ScrapedAt: now, ProcessedAt: now, ProcessingVersion: "1.0", }, } // Set publication information if pubInfo := can.GetPublicationInfo(); pubInfo != nil { t.NoticePublicationID = pubInfo.NoticePublicationID t.PublicationDate = ParseDateToUnix(pubInfo.PublicationDate) t.TenderURL = GenerateTenderURL(pubInfo.NoticePublicationID) } // Set total award value if amount, currency := can.GetTotalAwardValue(); amount != "" { if parsedAmount, err := strconv.ParseFloat(amount, 64); err == nil { t.EstimatedValue = parsedAmount } t.Currency = currency } // Set location information from main procurement project if can.ProcurementProject != nil && can.ProcurementProject.RealizedLocation != nil { if addr := can.ProcurementProject.RealizedLocation.Address; addr != nil { t.CityName = addr.CityName t.PostalCode = addr.PostalZone t.RegionCode = addr.CountrySubentityCode if addr.Country != nil { t.CountryCode = addr.Country.IdentificationCode } } } // Set buyer organization buyerID := can.GetBuyerID() if buyerID != "" { if buyerOrg := can.GetOrganizationByID(buyerID); buyerOrg != nil { t.BuyerOrganization = s.mapOrganization(buyerOrg, "buyer") } } // Set all organizations if orgs := can.GetOrganizations(); orgs != nil { for _, org := range orgs.Organization { mappedOrg := s.mapOrganization(&org, "") if mappedOrg != nil { t.Organizations = append(t.Organizations, *mappedOrg) } } } // Generate unique tender ID t.TenderID = GenerateTenderID(t) return t } // mapOrganization maps a TED Organization to tender Organization func (s *TEDScraper) mapOrganization(tedOrg *Organization, role string) *notice.Organization { if tedOrg == nil || tedOrg.Company == nil { return nil } company := tedOrg.Company org := ¬ice.Organization{ Role: role, } // Set organization ID and name if company.PartyIdentification != nil { org.ID = company.PartyIdentification.ID } if company.PartyName != nil { org.Name = company.PartyName.Name } // Set company ID if company.PartyLegalEntity != nil { org.CompanyID = company.PartyLegalEntity.CompanyID } // Set website org.WebsiteURI = company.WebsiteURI // Set contact information if company.Contact != nil { org.ContactName = company.Contact.Name org.ContactTelephone = company.Contact.Telephone org.ContactEmail = company.Contact.ElectronicMail org.ContactFax = company.Contact.Telefax } // Set address if company.PostalAddress != nil { org.Address = notice.Address{ StreetName: company.PostalAddress.StreetName, CityName: company.PostalAddress.CityName, PostalZone: company.PostalAddress.PostalZone, CountrySubentityCode: company.PostalAddress.CountrySubentityCode, Department: company.PostalAddress.Department, } if company.PostalAddress.Country != nil { org.Address.CountryCode = company.PostalAddress.Country.IdentificationCode } } return org }