← Back to projects
CASE FILE / BUILD LOG MELBOURNE · VIC · AUSTRALIA

Prospect Scout

A locally-run research aid that finds companies worth a cold email — and refuses to tell you anything it can't cite. This page documents how it was built, what broke, and what's still honestly unsolved, using the same evidence discipline the tool itself is built around.

StatusPre-release
Sessions logged2026-07-26 → 08-07
Tests passing131 / 131
Live calls in suite0

One real result, unedited

Not a mockup. This is a live finding from a real run against itnetworks.com.au, discovered during this session's own testing — reproduced here exactly as the tool wrote it, because a case study about an evidence-only tool should open with evidence, not a claim about one.

Team Contact Signal confidence: medium
“CTO” found on this page, near the text: “...About IT Networks. Meet the leadership team behind IT Networks’ managed IT support...”
SuggestionPossible contact: Jim Kay (CTO) — verify from the excerpt before reaching out; this is a best-effort match, not confirmed.

That specific result didn't exist until this session fixed a false positive that pointed at a blog post about installing Microsoft Teams instead. See Case 01.

The challenge to create the solution

While there are many official and unofficial job boards out there, or else automation agents to make the job hunt easier, it focuses more on the 20% jobs posted seen rather than the 80% hidden job market through word of mouth, or the bold chance to reach out for them.

The idea stemmed with a piece of advice called 'why not search for the company, find the CTO / HR etc and then reach out via cold email? Find what you can solve for them, write it out and get that conversation'. Rather than job boards with the usual resume and cover letter fed to the ATS and 99% leading nowhere, why not that type of appeal?

The problem for me is finding those companies, especially 'small to mid-sized' companies more likely to hire junior / entry level candidates with international immigrant status even for just a bit and pay the bills than big companies that everyone covets. So, I reached out to spec this project, casting a net over the location for those companies, even non-tech as long as they have a tech team, and take my chances.

What this actually is

Given a location and a technical interest, Prospect Scout finds publicly visible companies that are plausible prospects, collects verifiable case-study and hiring signals from their own sites, and writes a local report for a human to review before reaching out. It is deliberately not a lead-generation guarantee — a search can return zero eligible companies when public evidence doesn't support one, and that's treated as a correct answer, not a failure.

The one rule everything else follows from: it must never invent a company, a case study, a problem, or a contact. Every finding ships with a source URL, an excerpt, and a confidence level the human is meant to actually distrust a little.

Build log

2026-07-26

Search widening, ranking, and a results page worth reading

OSM discovery widened to libraries and research institutes alongside offices; scout/research.py gained bounded concurrency so a 250-candidate run finishes in minutes instead of tens of minutes; scout/ranking.py shipped — eligible before needs-review, then location and evidence strength, reasons shown per company instead of an opaque score. Role matching switched from substring to whole-word after a real false positive: “software engineer” was matching inside “software engineering,” a degree name, not a role.

2026-07-31

Baseline: fetcher, discovery, evidence pipeline, reporting

The polite fetcher (scout/fetcher.py) — honours robots.txt, identifies itself honestly, skips login-gated content. Sitemap-first page discovery, sector classification, location verification, and local Markdown/JSON reporting. First documented baseline; the web app and CLI both usable end to end.

2026-08-04

Profile-aware discovery and a named contact, only when the site actually publishes one

OSM's single fixed office=* query became a per-profile tag set — the technology profile now asks for office=it/research/engineer, not everything. Team/leadership-page contact extraction shipped, deliberately scoped to a company's own published team page — no third-party enrichment, no guessed emails. Caught two real bugs in testing before shipping: naive matching once flagged “Sales Director” as a “CTO” hit (both words contain the substring “cto”), and a name-extraction heuristic once grabbed “Our Head” instead of the real name sitting two words later.

2026-08-07

Live-testing the live tool, and fixing what it found

This session. Ran real searches, read every result by hand, and didn't like some of them — see the three cases below. Shipped from that: a word-boundary fix for a false-positive contact page, a minimum-evidence floor before the tool will assert a sector, contact-aware and MSP-language-aware wording on its weakest finding type, and a full redesign of outreach guidance — from a composed, sendable draft to personalized talking points, after a first attempt over-corrected into being generic.

Three bugs, found by actually reading the output

None of these came from a code review. They came from running real searches against real companies and refusing to accept a result just because it didn't crash.

Case 01 — The blog post about Microsoft Teams

confirmed & fixed

Team-contact discovery matched pages by keyword: "team" among them. It flagged /blog/how-to-install-microsoft-teams/ as a leadership page — because "team" is a literal substring of "teams," the software product, not the org-chart concept. The extracted "contact" was Business Needs, pulled from the headline “Why Your Small Business Needs a CIO.”

path = "/blog/how-to-install-microsoft-teams/" "team" in path → True (substring match — wrong) re.search(r'\bteam\b') → False (word-boundary — correct)

Fixed with a word-boundary match, deliberately scoped to team-page keywords only — the case-study keyword list keeps its existing substring behaviour on purpose, since one of its terms ("success-stor") is an intentional truncation to catch both "story" and "stories." Re-ran the exact same company afterward: it found the real about page, and a real name.

Live re-testitnetworks.com.au now returns Jim Kay, CTO — the card at the top of this page.

Case 02 — A public library, filed under fashion retail

partially fixed — said so

Sector inference counts keyword hits per category and reports the winner, no matter how thin the margin. A local council library's homepage scored two hits under fashion/retail: "collection" and "clothing." A library collection isn't a clothing collection — but the word is identical either way.

Added a floor: a sector needs at least two independent keyword hits before it gets reported as anything but unknown. Re-ran the library afterward, expecting it fixed.

It wasn't.Two hits clears a floor of two. The fix catches thin, single-word false positives — not this one. Said so plainly instead of quietly claiming a win; the honest fix needs the word list itself reconsidered, not just a stricter count.

Case 03 — BHP was in the map data the whole time

confirmed, scoped for later

Prompted by a real story: someone found a tech role at a mining company through a networking event, not a search. Checked directly whether OpenStreetMap even had the data — it did. BHP's Melbourne HQ is a real, recently-verified OSM node, tagged office=company, with a live website tag pointing at bhp.com.

node(2456680377) → office=company, website=bhp.com, check_date=2026-05-28

The technology profile's OSM query only asks for office=it/research/engineer — not office=company, the tag every generic corporate HQ gets. BHP was findable and invisible to this profile at the same time. Filed as evidence for a real, separately-scoped question: tech work increasingly lives inside companies that don't look like tech companies at all — not something to patch into this session's fixes by accident.

What every finding is required to carry

Not a style guide — an enforced shape. A finding missing any of these isn't a weaker finding, it's not a finding.

FieldRequirement
source_urlCanonical URL after redirects — always the company's own site.
evidenceShort observed text, actually present on the fetched page. Never paraphrased into a claim.
confidenceHigh, medium, or low — a name match with no name nearby is low, always.
suggestionA specific next action tied to that evidence, framed as a hypothesis, never a certainty.
statuseligible / needs_review / rejected. Only eligible companies get recommended — no evidence, no recommendation.

What's still honestly unsolved

Recorded on purpose, not discovered by a reader. A tool that only admits its limits when asked isn't actually being evidence-based about itself.

Built with

131
tests passing
0
live calls in suite
4
focus profiles
250
candidates / run

Language — Python 3
Web app — FastAPI + Uvicorn
Fetching — httpx, robots.txt-honouring, self-identifying user agent
Parsing — BeautifulSoup, hand-rolled sitemap XML discovery
Discovery — OpenStreetMap (Nominatim geocoding + Overpass queries)
Tests — stdlib unittest, fixture & fake-based, zero live network calls
Reports — local Markdown + JSON, nothing leaves the machine