← All projects

Competitor Analysis

Multi-layer competitive analysis of SaaS products, run locally.

Role
Solo
Year
2026
Status
live
The Competitor Analysis dashboard showing five run modes and the Step 1 configuration panel
Picking a run mode, then the configuration the run reads from.

Being confidently wrong was the problem. Not the hours.

An early scan of Bolt reported six features missing that Bolt's own support site documents. An audit of a later scan found roughly 94% of its "missing feature" flags were wrong. A table that says a rival can't do something it plainly can is worse than no table, because someone reads it out loud in a meeting.

So the engine got rebuilt around one rule. No claim without a receipt.

It's a Streamlit app that runs on my machine, in five modes. A full pipeline that finds the competitive set itself. A manual scan where I name the competitors. A peer matrix that treats every brand as an equal, with no privileged "us versus them". A scan seeded from whatever Google's AI Overview returns for a brand's positioning. And a single-brand read with no competitors at all. Each one crawls the brands' own sites and help centres, pulls capabilities and commercial signals off the pages, canonicalises the wording so two brands' phrasing for one feature lands on one row, then builds the matrices.

The rule everything hangs off

Every capability cell is tri-state: present, absent, or unknown. An absent is only allowed when a real search ran against that brand's own domain and came back with nothing. Out of budget, no backend, backend errored: none of those are findings. They stay unknown, and an unknown gets dropped from the deliverable rather than softened into "not documented".

Each gap runs a ladder, stopping at the first rung that resolves it. The pages already crawled, then a search restricted to the brand's own domain, then real Google results. Every dispatched cell carries a receipt naming which backend ran and what it decided.

The number I watch, and what it's worth today

There's a gold set of labelled cases. Every yes or no has to carry a URL and a verbatim quote, and the loader refuses a label without one. The headline metric is confidently_wrong: asserting present on a labelled No, or absent on a labelled Yes. The ceiling is zero and the build fails above it.

I ran it. It's at zero. It's also worth less than it sounds. The one real case in the set is a frozen Apollo vs Instantly scan, 73 labelled cells, and the scan marked every single one unknown. Nothing wrong, because nothing asserted. All 25 cells that audit confirmed as real capabilities are flagged as known recall debt, which takes them out of the recall denominator. The recall score reads 1.0 and a small seed case carries all of it. The configured floor is 0.5.

That's the honest state. Failure lands on unknown instead of a false no, which was the whole point. But shrugging isn't the same as knowing. Recall is the gap I'm pushing on, and it's a gap, not a decision.

Debugging without paying for it twice

A full scan takes about ninety minutes and burns API credit, so validating a one-line fix was absurd. Four analysis stages, capability collection through to the signals rollup, now replay offline against a scan already paid for, through the same functions the live pipeline calls, then get diffed against last time. There's also a smoke profile: four pages per brand, cheaper models, no paid search step. It announces itself as a test run in the log, so thin results never read as real.

Where it stops

One operator, one machine. No hosted mode, no accounts, nothing to sign into. Exports get hand-transcribed into my comparison site rather than published automatically. I want a person sitting between a scan and anything public.

The crawler is Screaming Frog. My own is wired in as a drop-in, still in beta, and it isn't what real runs use.

Stack: Python and Streamlit, because it's an internal tool where iteration speed beats polish. Tavily and DataForSEO for the search ladder, Ahrefs for keyword data in the full pipeline, DeepSeek, Gemini and Kimi for the text work, local Ollama models for embeddings and image reads so those cost nothing per run, and pandas with openpyxl for the spreadsheet exports.