// Package adapters registers the existing federal feeds as OpportunitySource implementations. Each // adapter is a thin wrapper over an existing client and normalizer (samgov, marketintel) and changes // none of their behavior or output; it only maps their result into the unified OpportunityRecord. The // pure conversion functions live here so they can be golden tested without any network or database. package adapters import ( "strings" "time" oppintel "github.com/missionworkforce/missionmatch/internal/modules/opportunity_intelligence" "github.com/missionworkforce/missionmatch/internal/platform/marketintel" "github.com/missionworkforce/missionmatch/internal/platform/samgov" ) // fromSAM converts a normalized SAM opportunity into a unified active solicitation record. SAM notices // carry no dollar value, so EstimatedValue stays nil (never fabricated). func fromSAM(n samgov.Normalized, fetchedAt time.Time) oppintel.OpportunityRecord { source := n.Source if source == "" { source = "SAM.gov" } return oppintel.OpportunityRecord{ SourceName: source, SourceRecordID: n.NoticeID, DedupeKey: dedupe(n.SolicitationNumber, n.NoticeID), Kind: oppintel.KindActiveSolicitation, Title: n.Title, Agency: n.Department, SubAgency: n.SubAgency, NAICS: n.NaicsCode, PSC: n.PSCCode, Place: oppintel.Place{City: n.POPCity, State: n.POPState, Zip: n.POPZip}, SetAside: n.SetAside, SetAsideCode: n.SetAsideCode, Posted: n.PostedDate, ResponseDeadline: n.ResponseDeadline, UILink: n.UILink, FetchedAt: fetchedAt, Raw: n.Raw, } } // fromAward converts a normalized federal award into a unified contract award record. The award amount // is a real figure from USASpending, so it carries value_source usaspending and the recipient is the // incumbent. Awards have no title, so the description stands in. func fromAward(a marketintel.Award, fetchedAt time.Time) oppintel.OpportunityRecord { rec := oppintel.OpportunityRecord{ SourceName: "USASpending", SourceRecordID: a.GeneratedInternalID, DedupeKey: dedupe(a.AwardID, a.GeneratedInternalID), Kind: oppintel.KindContractAward, Title: a.Description, Agency: a.AwardingAgency, SubAgency: a.AwardingSubAgency, NAICS: a.NAICSCode, EstimatedValue: a.AwardAmount, IncumbentName: a.RecipientName, IncumbentUEI: a.RecipientUEI, PopStart: a.POPStart, PopEnd: a.POPEnd, FetchedAt: fetchedAt, Raw: a.Raw, } if a.AwardAmount != nil { rec.ValueSource = "usaspending" } return rec } // dedupe builds a normalized cross source key, preferring the primary id (a solicitation or award id) // and falling back to the record id. It upper cases and keeps only letters and digits, dropping the // spaces and separators that differ between feeds, so the same underlying opportunity links across them // (a SAM solicitation FA8773 26 R 0001 and an award PIID FA8773-26-R-0001 reduce to the same key). func dedupe(primary, fallback string) string { src := strings.TrimSpace(primary) if src == "" { src = strings.TrimSpace(fallback) } var b strings.Builder for _, r := range strings.ToUpper(src) { if (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') { b.WriteRune(r) } } return b.String() }