package adapters import ( "context" "errors" "log" "time" oppintel "github.com/missionworkforce/missionmatch/internal/modules/opportunity_intelligence" "github.com/missionworkforce/missionmatch/internal/platform/marketintel" ) // USASpendingSource adapts the existing awards Provider to the OpportunitySource protocol with no change // to its behavior. It paginates one award market query and yields one contract award record per result. // A 429 stops the pull gracefully. The free USASpending provider is the default and stays the fallback // once the SAM Contract Awards API adapter is added in Workstream B. type USASpendingSource struct { prov marketintel.Provider query marketintel.AwardQuery pageCap int now func() time.Time } // NewUSASpendingSource builds the adapter over an existing Provider and a market query. A pageCap of // zero defaults to 5 pages, matching the standalone awards-sync. func NewUSASpendingSource(prov marketintel.Provider, query marketintel.AwardQuery, pageCap int) *USASpendingSource { if pageCap <= 0 { pageCap = 5 } return &USASpendingSource{prov: prov, query: query, pageCap: pageCap, now: time.Now} } // Name is the provenance label, taken from the underlying provider (for example USASpending). func (u *USASpendingSource) Name() string { return u.prov.Name() } // Fetch paginates the award market query and yields each award as a contract award record. func (u *USASpendingSource) Fetch(ctx context.Context, yield func(oppintel.OpportunityRecord) error) error { now := u.now().UTC() q := u.query for page := 1; page <= u.pageCap; page++ { q.Page = page awards, hasNext, err := u.prov.FetchAwards(ctx, q) if err != nil { if errors.Is(err, marketintel.ErrThrottled) { log.Printf("awards source throttled on page %d, stopping the pull", page) return oppintel.ErrThrottled } return err } for _, a := range awards { if yerr := yield(fromAward(a, now)); yerr != nil { return yerr } } if !hasNext { break } } return nil }