""" SBIR/STTR Award Search Uses the SBIR.gov API to search for Small Business Innovation Research and Small Business Technology Transfer awards. API Documentation: https://www.sbir.gov/api Free, no API key required. """ import logging import requests from typing import List, Optional, Dict, Any from datetime import datetime from ..search.base import BaseSearcher, SearchResult logger = logging.getLogger(__name__) class SBIRSearcher(BaseSearcher): """ Search SBIR/STTR awards database. The SBIR program funds R&D at small businesses, making it excellent for finding emerging defense technologies. """ BASE_URL = "https://www.sbir.gov/api/awards.json" def __init__(self, timeout: int = 30): self.timeout = timeout @property def name(self) -> str: return "SBIR.gov" @property def source_type(self) -> str: return "sbir" def search( self, query: str, max_results: int = 20, agency: Optional[str] = None, year_start: Optional[int] = None, year_end: Optional[int] = None, phase: Optional[str] = None # "Phase I", "Phase II", "Phase III" ) -> List[SearchResult]: """ Search SBIR/STTR awards. Args: query: Keyword search max_results: Maximum results to return agency: Filter by agency (e.g., "DOD", "NASA", "DOE") year_start: Start year filter year_end: End year filter phase: Filter by phase Returns: List of SearchResult objects """ params = { "keyword": query, "rows": min(max_results, 100), # API limit } if agency: params["agency"] = agency if year_start: params["year_start"] = year_start if year_end: params["year_end"] = year_end if phase: params["phase"] = phase results = [] try: response = requests.get( self.BASE_URL, params=params, timeout=self.timeout ) response.raise_for_status() data = response.json() for rank, award in enumerate(data, 1): if rank > max_results: break # Estimate TRL based on phase trl_estimate = self._estimate_trl(award.get("phase", "")) # Build URL to award page award_id = award.get("award_id", "") url = f"https://www.sbir.gov/sbirsearch/detail/{award_id}" if award_id else "" results.append(SearchResult( title=award.get("award_title", ""), url=url, snippet=award.get("abstract", "")[:500] if award.get("abstract") else "", source=self.name, source_type=self.source_type, rank=rank, published_date=award.get("award_year"), organization=award.get("firm", ""), award_amount=self._parse_amount(award.get("award_amount")), trl_estimate=trl_estimate, award_id=award_id, raw_data=award )) logger.info(f"SBIR search for '{query}' returned {len(results)} results") except requests.exceptions.RequestException as e: logger.error(f"SBIR API request failed: {e}") except Exception as e: logger.error(f"SBIR search error: {e}") return results def _estimate_trl(self, phase: str) -> int: """Estimate TRL based on SBIR phase.""" phase = phase.lower() if "phase i" in phase and "ii" not in phase: return 3 # Proof of concept elif "phase ii" in phase and "iii" not in phase: return 5 # Prototype development elif "phase iii" in phase: return 7 # Production ready return 4 # Default mid-range def _parse_amount(self, amount) -> Optional[float]: """Parse award amount to float.""" if amount is None: return None try: if isinstance(amount, str): amount = amount.replace("$", "").replace(",", "") return float(amount) except (ValueError, TypeError): return None def search_defense(self, query: str, max_results: int = 20) -> List[SearchResult]: """Search specifically DOD SBIR awards.""" return self.search(query, max_results=max_results, agency="DOD") def search_space(self, query: str, max_results: int = 20) -> List[SearchResult]: """Search NASA and Space Force SBIR awards.""" # Search NASA nasa_results = self.search(query, max_results=max_results // 2, agency="NASA") # Search DOD (includes Space Force) dod_results = self.search( f"{query} space OR satellite OR orbital", max_results=max_results // 2, agency="DOD" ) return nasa_results + dod_results def get_award_details(self, award_id: str) -> Optional[Dict[str, Any]]: """Get detailed information about a specific award.""" try: response = requests.get( self.BASE_URL, params={"award_id": award_id}, timeout=self.timeout ) response.raise_for_status() data = response.json() return data[0] if data else None except Exception as e: logger.error(f"Failed to get award details: {e}") return None