The Opportunity Scoring Pipeline That Filters 200 Leads Down to 5 Worth Pursuing

Every freelancer and small agency knows the feeling: you open your inbox to 40 new job postings, 12 inbound inquiries, and a handful of marketplace notifications. By the time you've read through them all, triaged the duds, and identified the two or three worth pursuing, ninety minutes have evaporated. Multiply that by every day of the week and you're spending 7+ hours just deciding what to work on — before writing a single proposal.

At Ledd Consulting, we solved this with a scoring pipeline that runs every six hours, ingests every opportunity from every source we monitor, scores each one against a multi-signal model, and outputs a ranked queue of exactly the opportunities worth human attention. On a typical cycle, it processes around 200 raw leads and surfaces 3–7 that clear our threshold. The rest get logged, tracked, and ignored.

Here's the pattern, the code, and the production numbers.

The Pattern — Multi-Signal Opportunity Scoring with Threshold Funneling

A scoring pipeline that ingests heterogeneous lead data from multiple sources, applies weighted signal groups (skill fit, financial readiness, engagement quality, historical outcomes), classifies each lead into action tiers, and emits only the top tier for human review. Everything below the threshold gets archived automatically.

The Naive Approach (and Why It Fails)

Most freelancers and agencies try one of two things:

Manual triage. Open every listing, skim the description, make a gut call. This works when you have 5 leads a week. At 30+ per day across multiple platforms, it collapses. You start skipping listings, missing good fits buried under bad titles, and making increasingly worse decisions as fatigue accumulates.

Simple keyword filtering. Set up alerts for "AI agent" or "Node.js" and only look at those. Better, but crude. A job titled "AI Agent Developer Needed" with a $200 budget and a 3-day deadline is still junk. A job titled "Automation Consultant for Internal Tools" that pays $15K and matches your stack perfectly gets filtered out because it missed your keyword list.

Both approaches fail because they evaluate a single signal. Real opportunity quality is multi-dimensional — skill fit, budget alignment, timeline compatibility, engagement signals, and historical win rate all matter. The pattern we run evaluates all of them simultaneously.

Pattern Implementation — Full Code Walkthrough

Step 1: Define the Profile Model

The scorer needs to know what "good fit" means. We encode this as a structured profile with tiered skill classifications and rate boundaries:

const PROFILE = {
  skills: {
    expert: ['ai agents', 'agentic ai', 'claude api', 'llm integration',
             'automation', 'python', 'node.js', 'typescript', 'javascript',
             'api development'],
    proficient: ['react', 'next.js', 'web scraping', 'data pipelines',
                 'docker', 'devops', 'postgresql', 'mongodb', 'websockets'],
    familiar: ['c++', 'embedded systems', 'rust', 'go', 'kubernetes',
               'aws', 'gcp', 'machine learning'],
  },
  rate: { min: 15, preferred: 35, max: 50 },
  availability: 'full-time',
  preferredProjectLength: { min: 1, max: 90, idealDays: 14 },
};

The three-tier skill model (expert, proficient, familiar) is critical. A job requiring your expert skills scores higher than one requiring skills you're merely familiar with — even if both are technically matches. This single distinction eliminates a huge class of "technically qualified but strategically poor" opportunities.

Step 2: Gather from All Sources

Opportunities arrive from multiple channels. Our pipeline pulls from all of them in a single pass:

function gatherOpportunities() {
  const opportunities = [];

  // Source 1: Job scraper reports (last 5 report files)
  try {
    const files = fs.readdirSync(JOB_REPORTS_DIR)
      .filter(file => file.endsWith('.json'))
      .sort()
      .slice(-5);

    for (const file of files) {
      const data = readJson(path.join(JOB_REPORTS_DIR, file), []);
      const jobs = Array.isArray(data) ? data : data.jobs || data.results || [];
      jobs.forEach(job => opportunities.push({ ...job, _source: 'job-scraper' }));
    }
  } catch {}

  // Source 2: Freelance platform bid history
  try {
    const bids = readJson(path.join(WORKSPACE, 'bid-log.json'), []);
    (Array.isArray(bids) ? bids : [])
      .slice(-20)
      .forEach(bid => opportunities.push({ ...bid, _source: 'freelancer' }));
  } catch {}

  // Source 3: Proposal drafts still in queue
  try {
    const drafts = readJson(path.join(WORKSPACE, 'proposals/bid-queue.json'), []);
    (Array.isArray(drafts) ? drafts : [])
      .filter(draft =>
        !draft.submitted &&
        String(draft.status || '').toLowerCase() !== 'submitted')
      .forEach(draft => opportunities.push({ ...draft, _source: 'bid-drafter' }));
  } catch {}

  return opportunities;
}

Two design choices matter here. First, every opportunity gets tagged with _source so downstream scoring can weight sources differently (a warm referral from your CRM scores differently than a cold marketplace listing). Second, the try/catch blocks are intentionally empty — if one source is down or empty, the pipeline still runs with whatever is available. Partial data is always better than a crashed pipeline.

Step 3: Multi-Signal Scoring

This is the core of the pattern. Each opportunity is scored across multiple independent signal groups, each contributing to a composite score. Here's the model we use for lead qualification:

const SCORING_RULES = {
  // Financial readiness signals (0-40 points)
  financial: {
    pre_approved: 15,
    has_downpayment: 10,
    knows_budget: 10,
    working_with_lender: 5,
  },
  // Motivation signals (0-30 points)
  motivation: {
    timeline_under_3mo: 15,
    timeline_3_to_6mo: 10,
    timeline_over_6mo: 3,
    relocating: 10,
    investment: 5,
    first_time_buyer: 5,
  },
  // Engagement signals (0-30 points)
  engagement: {
    responded_quickly: 10,
    answered_all_questions: 10,
    provided_phone: 5,
    requested_showing: 5,
  }
};

The weight distribution tells you everything about our priorities: financial readiness commands 40% of the total score. A lead who is pre-approved and knows their budget scores 25 points before we even look at motivation or engagement. This is deliberate — the strongest predictor of a deal closing is whether the money is actually available.

The scoring function applies these rules additively:

function scoreLead(lead) {
  let score = 0;
  const reasons = [];

  // Financial
  if (lead.pre_approved) {
    score += SCORING_RULES.financial.pre_approved;
    reasons.push('Pre-approved for mortgage');
  }
  if (lead.has_downpayment) {
    score += SCORING_RULES.financial.has_downpayment;
    reasons.push('Has downpayment ready');
  }
  if (lead.budget_range) {
    score += SCORING_RULES.financial.knows_budget;
    reasons.push('Budget: ' + lead.budget_range);
  }

  // Motivation
  if (lead.timeline === 'under_3mo') {
    score += SCORING_RULES.motivation.timeline_under_3mo;
    reasons.push('Timeline: under 3 months');
  } else if (lead.timeline === '3_to_6mo') {
    score += SCORING_RULES.motivation.timeline_3_to_6mo;
    reasons.push('Timeline: 3-6 months');
  }

  // Engagement
  if (lead.response_time_min && lead.response_time_min <= 60) {
    score += SCORING_RULES.engagement.responded_quickly;
    reasons.push('Fast responder');
  }
  if (lead.questions_answered >= 5) {
    score += SCORING_RULES.engagement.answered_all_questions;
    reasons.push('Answered all questions');
  }

  // Classification
  let classification;
  if (score >= 60) classification = 'HOT';
  else if (score >= 35) classification = 'WARM';
  else classification = 'COLD';

  return { score, max_score: 100, classification, reasons };
}

The reasons array is just as important as the score itself. When a human reviews the top 5, they see why each lead ranked high — "Pre-approved for mortgage, timeline under 3 months, fast responder" tells you everything you need in one glance.

Step 4: Threshold Classification and Notification

Once scored, opportunities get sorted, deduped, and funneled through a threshold gate. Only those above the configured hot-opportunity score trigger a notification:

const scored = opportunities.map(opp => scoreOpportunity(opp, {
  config,
  historicalSignal,
  profile: PROFILE,
}));

scored.sort((a, b) => (b._finalScore || 0) - (a._finalScore || 0));
const unique = dedupeOpportunities(scored);

// Persist the full ranked list (top 50)
fs.writeFileSync(OUTPUT_FILE, JSON.stringify(unique.slice(0, 50), null, 2));

// Only the HOT leads trigger human notification
const hotOpps = top10.filter(
  item => item._finalScore >= config.hotOpportunityScore
);
await notifyHotOpportunities(hotOpps, config);

The notification itself routes through our internal notification service with priority levels:

async function notifyHotOpportunities(hotOpps, config) {
  if (!hotOpps.length) return;

  const maxScore = hotOpps.reduce(
    (max, item) => Math.max(max, item._finalScore || 0), 0
  );

  await fetch('http://127.0.0.1:8080/notify', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      source: 'opportunity-scorer',
      priority: maxScore >= 9.2 ? 'critical' : 'high',
      category: 'revenue',
      score: maxScore,
      subject: `${hotOpps.length} hot opportunities (${config.hotOpportunityScore}+/10)`,
      body: hotOpps
        .map(item =>
          `${item._finalScore}/10 [${item._route}] ${item.title}\n` +
          `${item._explanation?.summary || ''}\n${item.url || ''}`.trim()
        )
        .join('\n\n'),
    }),
  });
}

A 9.2+ score gets critical priority (immediate push notification). Everything else in the hot range gets high (batched digest). Everything below the threshold gets logged and forgotten.

Step 5: Historical Signal Feedback

The secret weapon is the historical signal — a feedback loop from past outcomes that adjusts scoring over time:

const replayEntries = loadReplayEntries();
const historicalSignal = buildHistoricalSignal(replayEntries, config);

if (historicalSignal) {
  log(`Historical signal loaded (${historicalSignal.positiveCount} positive outcomes, ` +
      `${historicalSignal.negativeCount} hard failures, ` +
      `${historicalSignal.tokenCount} tokens)`);
}

Each scored opportunity carries a historicalFit sub-score derived from how similar past opportunities performed. If jobs with "blockchain" in the title have a 0% close rate in your history, future blockchain jobs get penalized automatically — even if the skill match looks decent on paper.

In Production

This pipeline runs on a systemd timer every 6 hours across our 25-service infrastructure. Real numbers from the past 30 days:

  • Average ingest per cycle: ~200 raw opportunities across 3 sources
  • Average output above hot threshold: 3–7 opportunities
  • Reduction ratio: 97% of leads filtered automatically
  • Historical signal: 40+ positive outcomes tracked, improving scoring accuracy each cycle
  • Time saved: from ~90 minutes of manual triage to ~5 minutes reviewing a pre-ranked list
  • Top score observed: 9.4/10 (closed deal within 48 hours)

Edge cases we encountered: duplicate opportunities appearing across sources required the dedupeOpportunities pass. Early versions would surface the same job three times — once from the scraper, once from the bid queue, once from a draft proposal. The deduper matches on title similarity and URL, keeping whichever copy has the highest score.

We also discovered that the scoring weights needed recalibration after the first month. Our initial model weighted skill match too heavily, consistently surfacing technically interesting projects with $300 budgets. Shifting financial signals to 40% of the total score immediately improved the quality of surfaced opportunities.

Variations

For SaaS companies scoring trial-to-paid leads: Replace the financial readiness signals with product usage signals — features activated, API calls made, team members invited. The three-tier classification (HOT/WARM/COLD) maps directly to sales outreach cadence.

For agencies with multiple service lines: Extend the profile model with separate skill profiles per service line and score each opportunity against all of them. The highest-scoring match determines both the score and the recommended team.

For high-volume marketplaces (1000+ leads/day): Add a pre-filter stage that rejects obviously disqualified leads (budget below minimum, required skills outside your entire profile) before the full scoring pass. This keeps the scoring pipeline fast even at scale.

Conclusion

The opportunity scoring pipeline is fundamentally a narrowing pattern: wide input, aggressive multi-signal filtering, tight output. The key insight is that each signal group (financial, motivation, engagement, historical) eliminates a different class of bad leads. Financial signals catch the tire-kickers. Motivation signals catch the "someday" crowd. Engagement signals catch the ghosts. Historical signals catch the patterns you've already learned the hard way. Stack all four and the 200 leads that looked overwhelming become 5 that look like real money.

The entire implementation is pure Node.js, runs on a single VPS, and costs exactly nothing beyond the compute you're already paying for. Every 6 hours, it tells you exactly where to spend your time. That's the whole value proposition.

Need help building AI agent systems or designing multi-agent architectures? Ledd Consulting specializes in autonomous workflow design and agent orchestration for enterprise teams.

Read more

Intelligence Brief — Saturday, April 11, 2026

MetalTorque Daily Brief — 2026-04-11 Cross-Swarm Connections The Audit Trail Is the Attack Surface — Everywhere. Three swarms converged on the same structural conclusion from radically different entry points. Agentic Design found that peer-preservation corrupts agent-generated logs, confidence inflation poisons self-reported metrics, and context contamination makes audit-time behavior diverge from production behavior.

By Ledd Consulting