package awards import ( "context" "errors" "time" "github.com/jackc/pgx/v5/pgtype" "github.com/missionworkforce/missionmatch/internal/platform/database/dbgen" "github.com/missionworkforce/missionmatch/internal/platform/tenant" ) // ErrNoDatabase is returned when no pool is configured. var ErrNoDatabase = errors.New("awards: no database configured") const ( defaultIncumbentLimit = 5 maxLimit = 50 ) // Service reads the cached awards catalog. type Service struct{ db *tenant.DB } // NewService builds the service. db may be nil (then calls return ErrNoDatabase). func NewService(db *tenant.DB) *Service { return &Service{db: db} } // Incumbents returns the largest comparable awards for the given NAICS codes, excluding Galapagos own // awards. Returns an empty slice when no NAICS is supplied. func (s *Service) Incumbents(ctx context.Context, naics []string, limit int) ([]Award, error) { if s.db == nil { return nil, ErrNoDatabase } if len(naics) == 0 { return []Award{}, nil } leaf, release, err := s.db.Acquire(ctx) if err != nil { return nil, err } defer release() if leaf.Pool == nil { return nil, ErrNoDatabase } rows, err := dbgen.New(leaf.Pool).ListAwardsByNaics(ctx, dbgen.ListAwardsByNaicsParams{ Naics: naics, Lim: int32(clampLimit(limit, defaultIncumbentLimit)), }) if err != nil { return nil, err } return toViews(rows), nil } // Galapagos returns Galapagos own past performance, largest first. func (s *Service) Galapagos(ctx context.Context, limit int) ([]Award, error) { if s.db == nil { return nil, ErrNoDatabase } leaf, release, err := s.db.Acquire(ctx) if err != nil { return nil, err } defer release() if leaf.Pool == nil { return nil, ErrNoDatabase } rows, err := dbgen.New(leaf.Pool).ListGalapagosAwards(ctx, int32(clampLimit(limit, 10))) if err != nil { return nil, err } return toViews(rows), nil } func clampLimit(v, def int) int { if v <= 0 { return def } if v > maxLimit { return maxLimit } return v } func toViews(rows []dbgen.UsaspendingAward) []Award { out := make([]Award, 0, len(rows)) for _, r := range rows { out = append(out, toView(r)) } return out } func toView(r dbgen.UsaspendingAward) Award { amount := "" if r.AwardAmount != nil { amount = r.AwardAmount.StringFixed(2) } return Award{ AwardUID: r.GeneratedInternalID, AwardID: r.AwardID, RecipientName: r.RecipientName, RecipientUEI: r.RecipientUei, AwardAmount: amount, AwardingAgency: r.AwardingAgency, AwardingSubAgency: r.AwardingSubAgency, POPStartDate: dateStr(r.PopStartDate), POPEndDate: dateStr(r.PopEndDate), NaicsCode: r.NaicsCode, NaicsDescription: r.NaicsDescription, Description: r.Description, IsGalapagos: r.IsGalapagos, Source: r.Source, SyncedAt: r.SyncedAt.Format(time.RFC3339), } } func dateStr(d pgtype.Date) string { if !d.Valid { return "" } return d.Time.Format("2006-01-02") }