Ceradon SAM Opportunity Bot is a hardened, offline-first Python service that queries the official SAM.gov Get Opportunities Public API, normalizes and scores contract opportunities for Ceradon Systems, deduplicates results locally in SQLite, and sends a daily email digest. It is designed for Raspberry Pi / Debian-class hosts in low-connectivity environments.
- API-only: No scraping, strictly uses the official SAM.gov public API.
- Offline-first: Local SQLite storage and rotating logs, no cloud dependencies.
- Deterministic scoring: Transparent keyword and metadata boosts, no ML.
- Edge friendly: Minimal dependencies, predictable runtime behavior.
This project is structured for uv (fast, offline-friendly dependency management). You can still use pip or Poetry, but uv is recommended for Raspberry Pi deployments.
- Python 3.9+
- A SAM.gov public API key
- Google Workspace / Gmail App Password for SMTP
- Create an account at https://sam.gov
- Request a public API key in the API key management section.
- Export the key as an environment variable:
export SAM_API_KEY="your_key_here"
- Enable 2-Step Verification on your Google account.
- Create an App Password for "Mail".
- Export the app password:
export SMTP_PASS="your_app_password"
Using uv:
uv venv
source .venv/bin/activate
uv pip install -e .Using pip:
python3 -m venv .venv
source .venv/bin/activate
pip install -e .Copy the example config and edit as needed:
cp config/config.example.yaml config/config.yamlmake run-onceceradon-sam-bot run --config config/config.yaml --once
ceradon-sam-bot run --config config/config.yaml --daemon --interval-minutes 1440
ceradon-sam-bot backfill --config config/config.yaml --days 60
ceradon-sam-bot export --format csv --since-days 30
ceradon-sam-bot explain --notice-id <id>Required:
SAM_API_KEYSMTP_PASS
Optional (defaults shown):
SMTP_HOST(smtp.gmail.com)SMTP_PORT(587)SMTP_USER(noah@ceradonsystems.com)EMAIL_TO(noah@ceradonsystems.com)EMAIL_FROM(noah@ceradonsystems.com)BOT_DATA_DIR(/var/lib/ceradon-sam-bot)SAM_API_KEY_IN_QUERY(false) to use query parameter auth instead of header
- SQLite database:
${BOT_DATA_DIR}/ceradon_sam_bot.sqlite - Logs:
${BOT_DATA_DIR}/logs/ceradon_sam_bot.log
30 6 * * * /usr/bin/env SAM_API_KEY=... SMTP_PASS=... BOT_DATA_DIR=/var/lib/ceradon-sam-bot \
/usr/bin/ceradon-sam-bot run --config /path/to/config.yaml --once >> /var/lib/ceradon-sam-bot/cron.log 2>&1- NAICS filters: Update
filters.naics_includeto expand or tighten scope. - Notice types: Keep
filters.exclude_notice_typesstrict to avoid award notices. - Keyword weights: Favor positive weights for R&D and sensing terms; keep negative weights high for construction and commodity reselling.
- Threshold: Increase
scoring.include_in_digest_scoreto reduce digest size.
docker compose up --build- SAM API key exported (
SAM_API_KEY) - SMTP app password exported (
SMTP_PASS) -
config.yamlcreated from example -
make run-oncecompletes successfully - Daily cron scheduled