RUDRESH.
← All work
01

Opportunity Intelligence Engine

A 27-node scheduled run that surfaces accounts where a buying signal just fired, scores fit and intent deterministically instead of asking a model to invent a number, and stops short of the send.

Built on my own time · Jul 2026
n8n · Docker · Groq · Llama · JavaScript · Apollo · Google Sheets · Slack

27nodes in the production workflow
4signal decay bands, 1.0× to 0×
6state tables, one sheet per concern
0messages sent automatically

The problem

GTM teams do not need more lists. They need to know which accounts have a reason to be contacted this week and why.

A static list of good-fit companies is stale the day you build it. A company that fit your ICP eighteen months ago and a company that just hired its first RevOps lead are not the same opportunity, and nothing in a normal prospecting database can tell them apart.

The system

Eight stages, running on a schedule in n8n, self-hosted in Docker.

Discovery. Three RSS sources merged, deduplicated and limited, plus an HTTP pull.

Classification. A model decides one thing only: is this an AI-GTM company as defined, or is it something adjacent — a research lab, a consumer AI product, an infrastructure vendor? Each rejection is written back with its reason.

Scoring. Deterministic. The model never returns a score.

Signal decay. Every signal carries a detected date and a freshness multiplier. Under 30 days it counts fully, 30 to 90 days at 0.6, 90 to 180 at 0.25, and past 180 days it counts for nothing.

Contact intelligence. Role targets, not scraped individuals: Head of Marketing, Head of Growth, Founder, VP Sales, Head of RevOps, each with a written rationale for why that role would care.

Suppression. Every surfaced contact gets a cooldown date. Nothing resurfaces inside it.

State. Google Sheets, one tab per concern: Companies, Signals, Contacts, Suppression, Partners, Runs. Every run writes a row to Runs.

Output. A Slack digest listing what qualified, what was rejected and why, and which contacts to look at.

The decisions

The model classifies. The code does the maths.

The tempting build asks the LLM for a score out of 100. It will happily give you one, and the number will be unreproducible, unexplainable and different next Tuesday for the same input.

So the split is: fuzzy judgment to the model, arithmetic to the code. Is this an AI-GTM company is a judgment call, and a model is good at it. What is this account worth this week is arithmetic over signals, weights and dates, and code is good at that.

The result is that every score can be recomputed by hand from the sheet. That is the difference between a system an operator trusts and one they quietly stop using.

Rejections are written down, with reasons

Most scoring systems discard what they reject. This one writes every rejection back with its reason.

That turns the reject pile into the most useful table in the system. If “at research labs” is rejecting thirty companies a week, either the definition is wrong or the source is wrong, and you can only see that if the rejections are recorded.

The engine stops at a recommendation

It discovers, classifies, scores, finds contacts and hands a human a digest. It sends nothing.

That boundary is not timidity. An automated send is irreversible and lands on a real person, and the cost of being wrong is a burned domain. Everything upstream of that is reversible, so everything upstream of that is automated.

Where this came from

I built this as the technical assessment for a GTM Engineer engagement. It won the engagement, and the four things I shipped once I was inside their stack are the case studies that follow.

See the same human-review boundary in the reply router. It runs in your browser, no sign-up, nothing sent anywhere.

Open it →

Evidence

Screenshots of the running system.

Nothing here is a mock-up. Click any image to open it full size.

The full workflow. Schedule trigger and three RSS reads on the left, scoring and filtering through the middle, contact discovery and suppression dropping down, digest and run log on the right.
The full workflow. Schedule trigger and three RSS reads on the left, scoring and filtering through the middle, contact discovery and suppression dropping down, digest and run log on the right.
Company state. Every row carries a status and, when rejected, a reason: not a company, at research labs, consumer or creative AI, infrastructure.
Company state. Every row carries a status and, when rejected, a reason: not a company, at research labs, consumer or creative AI, infrastructure.
Signals with a detected date, a base weight and a freshness multiplier, so the effective points can be recomputed rather than trusted.
Signals with a detected date, a base weight and a freshness multiplier, so the effective points can be recomputed rather than trusted.
Contact recommendations as role targets rather than scraped individuals, each with a rationale and a cooldown date.
Contact recommendations as role targets rather than scraped individuals, each with a rationale and a cooldown date.
Suppression. Every entry records why and until when, so nothing is quietly re-surfaced.
Suppression. Every entry records why and until when, so nothing is quietly re-surfaced.
The operator digest in Slack. Qualified accounts with their signals, rejected accounts with the reason, and the contacts to look at next.
The operator digest in Slack. Qualified accounts with their signals, rejected accounts with the reason, and the contacts to look at next.

Next

Reply classification and routing

A scheduled service that reads cold reply traffic, registers genuine positives against the event, and routes anything ambiguous to a human instead of guessing.