← Back to work

Full-stack engineer (solo build) · 2026

BrandLens

A brand-visibility tracker that fans category prompts across OpenAI, Anthropic, and Perplexity, then scores how often and how favourably a brand is mentioned versus its competitors, per model.

Sends the same buyer-intent prompts to several LLMs and turns their answers into a per-model visibility score for a brand against its competitors, with the raw citations behind every number.

Source ↗
Cover image for BrandLens

The problem

People are starting to shop by asking an assistant instead of a search engine: "what are the best design tools right now?" The model names a handful of brands, and that list is becoming a channel companies care about, except most have no idea how they show up in it or how they stack up against competitors. I wanted to turn that vague worry into a number you can actually look at. BrandLens is a scoped slice of that idea, built solo and presented honestly as a portfolio project rather than a product.

Approach

You give it a brand, a few competitors, and a set of category prompts. It sends every prompt to each enabled model, reads the answers for who got mentioned, how early, and in what tone, and rolls that into a visibility score per model plus an overall average. The design goal was for every number to be traceable: the dashboard shows the raw model answers with each mention highlighted, so a score is never a black box you have to trust.

Architecture

How I built it

npm workspaces split it into a shared types package, the server, and the web app, so the API and the dashboard agree on the shape of a scan at compile time. The scoring, fan-out, and API are covered by Vitest, with the provider calls mocked so the tests and the CI run need no API keys. Every push runs GitHub Actions (lint, typecheck, tests, and the web build). I built it with Claude Code directing the work, reviewing each slice before it landed.

What I'd do differently

The scoring is deliberately simple string matching on whole words, not an NLP model: it is a strong, explainable first pass, but messier phrasing (a brand referred to indirectly, or by a sub-product) would benefit from an LLM-assisted extraction step, which is the natural next layer. There is no persistence beyond memory yet and no auth, both isolated so they can be added without touching the core, and scheduled re-scans with alerting when visibility drops are named in the README as where this goes next rather than built.