Overview
Nucleate Hunter (dashboard title: Lead Radar) is a local Python tool that watches public discussions on Hacker News and Reddit and surfaces threads where Nucleate would genuinely help someone—not where a marketing bot should drop a link.
The problem is familiar to any solo developer shipping a niche tool: relevant conversations happen constantly, but they are scattered across subreddits, HN search results, and RSS feeds. Manually checking those channels does not scale, and blindly auto-posting does not fit how Watchlight Studio wants to participate in communities.
Nucleate Hunter automates the listening and triage layers: collect posts, score relevance, enrich threads with comments, classify opportunity type, optionally draft reply text with local Ollama, and alert me when something new lands on the board. Posting stays manual. The tool is opinionated about that.
This write-up documents what we built, why, and how the pieces fit together.
Why Not Just Search Reddit?
Three constraints shaped the design:
- No astroturfing — The tool must make it easier to be a useful community member, not to spam product mentions. Draft prompts default to answering the question first; Nucleate is named only when someone explicitly asks for tool recommendations and local transcription is clearly relevant.
- No mandatory cloud APIs — Reddit discovery runs through public RSS feeds and JSON endpoints. Hacker News uses Algolia’s public search API. Scoring is local regex and heuristics. Optional Ollama drafting stays on-machine.
- Actionable freshness — Collection windows and display windows are separate. Reddit threads are ingested for up to 72 hours but the Opportunities board focuses on what is still worth replying to. Pinned items persist past the freshness cutoff.
The goal is a personal radar, not a growth-hacking dashboard.
What the Operator Sees
A FastAPI dashboard on localhost:8765 with four views:
| Page | Role |
|---|---|
| Dashboard | Summary metrics and top leads |
| Opportunities | Full ranked board with filters (source, type, reply-ready, pinned) |
| Detail | Thread body, scoring breakdown, discussion highlights, Ollama drafts |
| Analytics | Opportunity type breakdown and collection run history |
Default board filters: score ≥ 2, hide Ignore types, sort pinned first, then reply-ready threads, then score. A Reply-ready only toggle surfaces threads where the OP or a comment scores as a high-priority engagement target (engagement score ≥ 8).
Pinned opportunities stay on the board until manually unpinned. Detail URLs remain reachable even after an item ages off the list.
Pipeline: Collect → Score → Enrich → Draft → Notify
Collection runs from a CLI (collect.py) or on an hourly loop when the dashboard is open (scheduler.py). Manual and scheduled runs share a lock so overlapping collects are skipped.
┌─────────────┐ ┌──────────────┐ ┌─────────────────┐
│ Collectors │ ──► │ Keyword score │ ──► │ Classify + store │
│ HN / RSS │ │ + exclusions │ │ (SQLite) │
└─────────────┘ └──────────────┘ └────────┬────────┘
│
┌─────────────────────────────┼─────────────────────────────┐
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌──────────────┐
│ Enrich │ │ Auto-draft │ │ Email alert │
│ comments │ │ (Ollama) │ │ (optional) │
└─────────────┘ └─────────────┘ └──────────────┘Collectors
Hacker News — Algolia search across configured queries (transcription, meeting notes, whisper ai, obsidian, etc.), stories and comments, filtered by max age.
RSS — feedparser over a configurable feed list. Reddit subreddit URLs in [rss.feeds] are tagged source=reddit automatically—no PRAW or OAuth required for the default path.
Reddit enrichment — High-scoring threads fetch comment trees via Reddit’s public .json endpoints, with shared rate limiting, RSS caching, 429 cooldowns, and stale-cache fallbacks so collection survives intermittent blocks.
Own-author filter — Posts from configured studio accounts are skipped as standalone items (they can still appear inside enriched threads).
Scoring (Layer 1)
Keyword scoring is weighted and tiered:
- High — Core speech/audio terms (
transcription,whisper,diarization,meeting notes, …) - Medium — Domain-adjacent terms (
obsidian,pkm,local ai, …) - Generic — Broad productivity/AI chatter, capped so catchall topics cannot rank high alone
Guards reduce false positives: bare transcription requires speech/audio context; items without any core signal are capped; exclusion patterns penalize off-topic matches; competitor mentions add a bonus.
classify_opportunity() maps the keyword score plus content signals into types:
Direct Lead— someone actively looking for what Nucleate doesCommunity Engagement Opportunity— relevant discussion, good place to contributeFeature Request Signal— user wants a capability Nucleate could addressResearch Opportunity— insight into user problemsSEO Topic Candidate— discussion that could inspire contentIgnore— below relevance threshold
Layers 2 and 3 (embedding similarity, Ollama relevance ranking) are stubbed in config weights but not wired yet. Final score today is effectively the keyword layer.
Engagement scoring (Layer 1b)
engagement_hints.py scores the OP and individual comments for reply-worthiness: recommendation asks, frustration signals, feature requests, pipeline-gap patterns (Whisper-only workflows missing diarization or summarization), privacy/local preferences, and competitor mentions.
Enriched threads store post_engagement_score, ranked engagement_targets, and Discussion Highlights on the detail page. This layer answers a different question than keyword score: is someone in this thread asking a question I can help with right now?
Ollama drafting (optional)
For eligible threads—fresh, keyword-qualified, reply-ready—local Ollama (qwen3:8b by default) drafts paste-ready replies. Product context lives in config/nucleate_context.md; behavior rules in drafting/prompts.py enforce rapport-first tone and strict mention policy.
Output is structured JSON: draft text, approach rationale, whether Nucleate was named, confidence, and caveats. Drafts can run automatically during collection or on demand from the detail page. TTL prevents stale drafts from lingering.
Notifications (optional)
SMTP email alerts fire for new items that pass the same eligibility SQL as the Opportunities board. Each item is notified at most once (notified_at deduplication). Failures log but do not interrupt collection.
Shared Eligibility Logic
notifications/eligibility.py centralizes the Opportunities board WHERE clause: minimum score, type filters, freshness windows per source, own-author exclusion, pinned/dismissed state, reply-ready predicates, and notification deduplication.
Collection freshness and display freshness are intentionally different—for example, HN ingest up to 7 days, board display up to 96 hours for HN, 72 hours for Reddit. One SQL module keeps the dashboard, email alerts, and future exporters consistent.
Stack and Layout
| Piece | Choice |
|---|---|
| Runtime | Python 3, FastAPI + Uvicorn |
| Storage | SQLite (data/lead_radar.db), WAL mode |
| Templates | Jinja2 + light static JS (pins, drafts) |
| LLM | Ollama HTTP API (optional) |
| Scheduling | asyncio loop in dashboard lifespan + external collect.py for cron/Task Scheduler |
nucleate-hunter/
├── main.py # FastAPI app, routes, dashboard
├── pipeline.py # Collection + scoring orchestration
├── collect.py # CLI runner
├── scheduler.py # In-app hourly collection loop
├── config/
│ ├── settings.toml # Sources, scoring, display, Ollama, SMTP
│ └── nucleate_context.md # Product brief for drafting
├── collectors/ # HN, RSS, Reddit/HN enrichment
├── scoring/ # Keyword scorer + classifier
├── drafting/ # Ollama reply generation
├── notifications/ # Email dispatch + shared eligibility
├── integrations/ollama.py
├── db/ # Schema + upsert helpers
└── dashboard/ # Templates + static assetsDesign Principles We Kept Coming Back To
Listen wider than you speak. The collectors cast a broad net; scoring and classification narrow it. Most items end up as Ignore or Research Opportunity—that is fine.
Separate discovery from display. Ingest generously, show only what is still actionable.
Drafts are starting points, not sends. Every reply is copied, edited, and posted by hand on HN or Reddit.
Community tone is platform-aware. HN drafts are concise and technical; Reddit drafts are slightly warmer. Neither reads like ad copy.
Secrets stay out of git. SMTP passwords load from config/smtp.password or LEADRADAR_SMTP_PASSWORD via scripts/with_secrets.py.
What Is Next
- Embedding similarity (Layer 2) — semantic match against Nucleate docs and positioning without brittle keyword lists
- Ollama relevance ranking (Layer 3) — separate from drafting; re-rank borderline threads
- More sources — additional RSS feeds, optional PRAW path if OAuth credentials are added later
Nucleate Hunter is internal tooling: it exists so community participation stays human, informed, and honest. If you are building something similar for your own product, start with eligibility rules and mention policy before you wire up collectors—the scoring and prompts matter more than the dashboard chrome.