Skip to content

carsonmulligan/sam-dot-gov-smart-contract-search

Repository files navigation

QuoteFast.io

Federal contract discovery platform. Searches SAM.gov and USASpending.gov APIs to find opportunities for government contractors.

Data Sources

Source Endpoint Data
SAM.gov Entity API api.sam.gov/entity-information/v3/entities Company registration, NAICS codes, UEI, business types
SAM.gov Opportunities API api.sam.gov/opportunities/v2/search Active solicitations, presolicitations
USASpending.gov API api.usaspending.gov/api/v2/search/spending_by_award Historical contract awards by recipient

Controllers

TrialSearchController (/try)

Anonymous search flow. Rate limited to 5 searches per IP per day.

Route Action Description
GET / index Landing page with search form
POST /try/search_sam search_sam Search SAM.gov for entity by name
POST /try/create_from_sam create_from_sam Create trial company from selected entity

Dataflow:

User enters company name
    → SamGovService.search_entity(name)
    → Returns array of matching entities with NAICS codes
    → User selects entity
    → Company.create(sam_entity_data: entity, is_trial: true)
    → UsaSpendingService.find_past_contracts(name, cage_code)
    → Company.update(past_contracts: results)
    → Session stores company_id for subsequent requests

CompaniesController (/companies)

Authenticated user company management and search execution.

Route Action Description
POST /companies/search_sam search_sam Search SAM.gov entity API
POST /companies/create_from_sam create_from_sam Create company from SAM entity
POST /companies/:id/generate_profile generate_profile Generate AI profile from past contracts
POST /companies/:id/generate_queries generate_queries Generate search queries from profile
POST /companies/:id/execute_searches execute_searches Run queries against SAM.gov
POST /companies/:id/score_all score_all AI score all opportunities YES/MAYBE/NO
POST /companies/:id/email_opportunities email_opportunities Send opportunities via email
POST /companies/:id/save_recurring_search save_recurring_search Enable weekly email digest

Full Search Dataflow:

1. Entity Selection
   POST /companies/search_sam {query: "Company Name"}
       → SamGovService.search_entity()
       → Returns: [{legalBusinessName, cageCode, ueiSAM, naicsCodes, businessTypes}]

2. Company Creation
   POST /companies/create_from_sam {entity: {...}}
       → Company.create(sam_entity_data: entity)
       → LoadPastContractsJob.perform_later(company)
           → UsaSpendingService.find_past_contracts(name, cage_code)
           → Company.update(past_contracts: awards)

3. Profile Generation (optional)
   POST /companies/:id/generate_profile
       → CompanyProfileService.generate_or_fetch_profile()
       → Uses Perplexity API to analyze past contracts
       → Company.update(ai_profile: {keywords, capabilities, target_agencies})

4. Query Generation
   POST /companies/:id/generate_queries
       → AiQueryGenerator.build_queries()
       → Returns: [{keywords, naics, notice_types, days_back, rationale}]

5. Search Execution
   POST /companies/:id/execute_searches {queries: [...]}
       → For each query (with 2s delay for rate limiting):
           → SamGovContractSearchService.search_opportunities_direct()
           → Dedup by solicitation_id
       → Save to contracts table
       → Returns: {opportunities_count, opportunities: [...]}

6. AI Scoring (optional)
   POST /companies/:id/score_all
       → OpportunityScoringService.score_all_opportunities()
       → GPT-4o-mini scores each contract against company profile
       → Contract.update(ai_score: YES|MAYBE|NO, ai_reasoning: "...")

ContractsController (/contracts)

Individual contract actions.

Route Action Description
PATCH /contracts/:id/yes yes Mark contract as user interested
PATCH /contracts/:id/no no Mark contract as not interested
POST /contracts/:id/email email Email single contract
DELETE /contracts/:id destroy Remove contract

PublicCompaniesController (/c/:token)

Public shareable company pages. No authentication required.

Route Action Description
GET /c/:token show Display public company page with YES/MAYBE contracts
POST /c/:token/send_preview send_preview Email opportunities to visitor

Services

SamGovService

Entity registration lookup.

service = SamGovService.new
entities = service.search_entity("Company Name")
# Returns: [{legalBusinessName, cageCode, ueiSAM, naicsCodes, businessTypes, expirationDate}]

SamGovContractSearchService

Opportunities search with caching.

service = SamGovContractSearchService.new
results = service.search_opportunities_direct(
  keywords: "software",
  naics: "541511",
  notice_types: ['p', 's'],  # presol, special notice
  days_back: 45,
  company_name: "Test Co"    # for cache key
)
# Returns: [{solicitation_id, title, agency, due_date, set_aside, url, type}]

Notice Types:

  • p = Presolicitation
  • s = Special Notice
  • k = Combined Synopsis/Solicitation
  • o = Solicitation
  • r = Sources Sought

UsaSpendingService

Historical contract awards.

service = UsaSpendingService.new
awards = service.find_past_contracts("Company Name", "CAGE123")
# Returns: [{title, agency, award_amount, award_date, naics_code}]

Database Schema

companies

Column Type Description
sam_entity_data JSON SAM.gov registration data
past_contracts JSON USASpending.gov awards
ai_profile JSON Generated company profile
saved_queries JSON Recurring search queries
recurring_email string Email for weekly digest
is_trial boolean Anonymous trial company
is_public boolean Publicly shareable
share_token string Public page URL token

contracts

Column Type Description
solicitation_id string SAM.gov notice ID
title text Opportunity title
agency string Federal agency (DOD, DHS, etc)
due_date date Response deadline
set_aside string Small business set-aside type
url string SAM.gov opportunity link
contract_type string Solicitation, Presol, etc
ai_score string YES, MAYBE, NO
ai_reasoning text GPT explanation
raw_json JSON Full SAM.gov response

SAM.gov API Constraints

  • Rate limit: ~4 requests before 429 error
  • Required delay: 2-3 seconds between requests
  • OR queries broken: "keyword1 OR keyword2" returns 0 results
  • Use single keywords: "software" works, "software development" may not
  • title param filters title only, q param searches title + description

Environment Variables

SAM_GOV_API_KEY=...           # Required - SAM.gov API key
OPENAI_API_KEY=...            # Optional - AI scoring
PERPLEXITY_API_KEY=...        # Optional - Profile generation
STRIPE_SECRET_KEY=...         # Optional - Payments

Background Jobs

Job Trigger Action
LoadPastContractsJob Company creation Fetch USASpending awards
ContractScanJob Manual scan Search SAM.gov opportunities
AiContractSearchJob AI search Full search + scoring pipeline
SendOpportunityNotificationsJob New results Email/Slack notifications
RecurringSearchJob Weekly cron Re-run saved queries

Routes Summary

# Anonymous
GET  /                          → trial_search#index
POST /try/search_sam            → trial_search#search_sam
POST /try/create_from_sam       → trial_search#create_from_sam

# Authenticated
GET  /companies                 → companies#index
POST /companies/search_sam      → companies#search_sam
POST /companies/create_from_sam → companies#create_from_sam
POST /companies/:id/execute_searches → companies#execute_searches
POST /companies/:id/score_all   → companies#score_all

# Public
GET  /c/:token                  → public_companies#show

Stack

  • Rails 8, SQLite, Solid Queue
  • Kamal deployment
  • Devise auth, Stripe payments
  • Tailwind CSS

About

LLM wrapper for search query creation and results filtering of SAM.gov Opportunities API

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors