Recent tech advances mean building digital products is easier than ever. And yet, the foundational challenge of picking meaningful and promising problems to solve remains. Here, I share my experiments with new product ideas. These experiments are deliberately NOT related to my ‘day job’ in the life sciences and are on personal interest topics. Rolling up the sleeves to directly build and experiment with product ideas is an increasingly important skill both professionally and personally. I’m delighted (and amused) to share the journey.
Warp & Weft: A Vintage Menswear Authentication Concept
Can curated reference data help an AI accurately date vintage menswear or just make it look like it should? Prototype concept is available here.
The Setting
As a sartorially inclined gentleman, I've long had an interest in vintage menswear. The fabrics and craftsmanship of bygone eras are truly fascinating to behold. As an ex-materials science product manager, I've long had an interest in the circular economy. Questions around material creation, their use, and recycling streams are phenomenally complicated to ponder.
These two domains, vintage menswear and the circular economy, directly collide at the humble charity shop.
Establishing provenance is critical for properly assessing vintage clothing value. However, dating and authenticating a garment is a seriously hard problem. A typical process goes like this: a Woolmark logo says "not before 1964," a care-symbol block says "not before the mid-70s," and a union label says "up to the 1990s." Since modern vision-language models are already shockingly good at this, I wanted to explore a deeper hypothesis. If I fed a model a curated table of datable marks and brand facts, does this measurably improve its dating, or is the model already good enough that the lookup is just theater?
Garment authentication is a huge market, but it's focused on luxury counterfeit detection. I wasn't interested in fake Louis Vuitton bags but rather "what is this, and when was it made?" for a cut-label chore coat with no brand to match against. This is the kind of question that underserved vintage menswear resellers face. Some apps treat dating as a byproduct of price with no scoring or abstention when evidence runs thin. A confidently wrong date is worse than an honest "I don't know." Dating and authentication as a first-class, properly-hedged claim, on exactly the pieces the luxury-reference models can't see, is where my experiment began.
Many "AI + your proprietary data" pitches assume that data earns its keep. I thus set off on a small, honest test of that assumption in the sphere of vintage menswear.
The Setup
I wandered charity shops in London looking for three specific vintage menswear areas: outerwear, tailoring, and workwear. I photographed the garments, their care tags, brand labels, and other identifying marks. I also browsed relevant eBay listings. With these photos, I created a gold set of authenticated garments by manually authenticating each piece. After 60 garments, I had enough to begin the technical analysis. Not all 60 fed into the scored runs. A frozen slice and holdout (see below) were a deliberately chosen subset, with the hardest, sparsest-evidence pieces parked.
I’m not a leather jacket guy but holy smokes this was a sexy jacket!
The experiment design was straightforward: one contender, measured against a baseline, on data the model couldn't peek at. The baseline dated each garment from photographs alone. The contender is the same model handed a curated and authenticated data set, a document of dating facts pulled in alongside the photos. Thus, any difference comes from that reference data, not the model. The primary metric was 'era correct to within a decade', scored as net garments flipped: pieces the reference data newly gets right, minus those it breaks.
Two sub-datasets kept me honest. I iterated on a frozen 25-garment slice, then scored the final run on a sealed 8-garment holdout I never tuned on, which was the real test of whether my changes generalised. The pass/fail bars were locked in advance on a kill/pivot/persevere gate, so a marginal result couldn't be counted as a win.
The one key design detail was having two independent evidence sources. Standardized marks (e.g., Woolmark, care symbols, union labels, registration numbers) give hard date bounds. Brand-archives (e.g., label-art era, a concession details) give a soft point estimate. Because the two are independent, I could attribute per garment which cue did the work. That attribution turned out to be the whole story.
Screenshot from my manually curated Gold Set used as the authentication reference.
The Result
The first run was a +2 net, no regressions on the frozen slice marginal pass below my "keep investing" bar. Only one of the two "wins" was real. An Aquascutum piece the baseline had misdated was recovered by a genuine brand-archive fact, on a garment from before standardized marks existed. That's the experiment working as designed: the archive reaching where the marks can't. The second "win" was the model committing to a decade it had hedged on before. The model gave slightly different answers each run, so that's noise, not the reference data earning its keep. Discounted honestly, the real signal was +1: a single garment.
The marks were accurate but redundant. Six pieces resolved on a correctly-fired mark, and every one was already dated right by the model's eye. Not wrong, just superfluous. On a slice where the base model is near ceiling, structured lookup has almost no headroom to work in. That's the finding I didn't expect and value most.
Then came the sealed holdout. It regressed! It was one worse, and that broken piece is the sharpest result in the project.
It was a pair of Levi Strauss Signature jeans, a mass-market sub-brand that didn't launch until 2003. The baseline dated them correctly to the 2000s. But they carry an old "WPL" registration number, and my reference table treated that number as a bounded 1941–1959 window. The resolver handed the model that window, told it to commit, and the model, against its own stated judgment, output the 1950s. A fifty-year error! Reading its reasoning back, it had flagged the contradiction itself and been overridden anyway.
The cause was a real domain fact: WPL numbers don't expire. They're a not-before mark, not a bounded window, so the same number turns up for decades. One wrong ceiling in my data turned a correct answer into a confident, badly wrong one.
The piece that ‘broke’ the project.
What I Actually Learned
Curated data doesn't simply "help or not help" but rather helps in a narrow seam: early pieces where no datable mark exists and an archival fact is the only signal. It sits redundant when the model already knows. And it actively regresses the model when a bound is wrong, overriding a correct hedge with false confidence. Curation quality isn't a detail of the system, it is the system. A concrete design rule would be needed: a hard lookup should surface a conflict with strong model evidence, never silently override it.
For this particular use case, the evaluation itself is the product. If I wanted to take this further, what would I do? For starters, a scaled-up gold set of more diverse pieces would be needed, focused on garments with no datable marks. Other, more sophisticated approaches could also be relevant: matching garments against a library of reference images (using techniques like CLIP or SigLIP embeddings), giving the model tools it can call on its own (agentic orchestration), or even infrared spectroscopy to anaylze fabric composition directly.
For fun, I created a mockup of the product concept. I call it 'Warp & Weft', from the two directions of fabric strands on a loom. You're very welcome to explore this prototype!
Tooling: a custom Python evaluation harness with pre-registered scoring, per-piece cue attribution, and contamination diagnostics. Dating references were hand-curated from standardized textile marks and brand-archive sources. Analysis was done using Claude Cowork, with Claude Opus 4.8 as the vision-language comparator, and Lovable as the prototype generator.
Mundus Ulterior: Ancient Rome and the Wider World
How can we understand, using firsthand accounts, the many ways that ancient Rome and the wider world were connected? Portal site available here; GitHub repo is here.
Fifty-four source records, eighteen languages, one curious Londoner.
The Setting
Ever since I was a kid, I've been fascinated by ancient Rome. Whether I admired or condemned them, the Romans are many things to me but never, ever boring. A social media phenomenon asked “how often do you think about the Roman Empire?” In my case, I think about ancient Rome almost every day since Roman London (Londinium) is literally under my feet!
I recently read William Dalrymple's The Golden Road, with its dense and intense accounts of ancient India's trade with Rome: Roman gold flowing east, pepper flowing west. Much of what we know about Rome's connections to the regions beyond comes from Greek and Latin writers. I began to wonder, what did everyone else say about Rome? I knew that Roman artefacts had turned up in places from Scandinavia to Sri Lanka and Vietnam. What might we learn from what the ancient sources from these regions say about Rome?
It turns out that answers do exist but are scattered across continents and languages, for example: an Old Tamil poem about a port where foreign ships arrived with gold and left with pepper, Literary Chinese court histories describing a distant empire called Da Qin, Sabaic inscriptions on south Arabian dams, and runes on a Danish scabbard. Despite searching, I couldn’t find a single resource connecting these non-Roman voices.
I then thought, could I build one myself? The project would need to answer specific questions straight from the sources. AI might be a useful tool here. But then again, AI answers about the ancient world that read beautifully but are based on drifted (or invented) quotes are useless. I decided to take up the challenge: could I build and validate a multilingual, multicultural proof-of-concept for the world beyond ancient Rome?
The Concept
I named this project ‘mundus ulterior’, which is Latin for ‘the world beyond.’ The premise deliberately made sourcing hard. I gathered 54 source records from seven regions (Arabia, East Asia, Persia, Scandinavia, South Asia, Southeast Asia, and Sub-Saharan Africa) between 27 BC and 1204 AD, in 18 languages counting English. Each record is a single source, often in its own words and script, and includes published translations when available. Sources range from inscriptions and literary texts to coins and museum catalog entries.
Ezana Stone, Aksum (Rod Waddington, CC BY-SA 2.0); Da Qin stele, Xi'an (David Castor, public domain); Sasanian ewer (The Met, public domain).
An early curation lesson: reading a source isn't the same as having permission to quote it, so each record's terms were read off the source itself and recorded word for word.
To connect questions to sources, each source is cut into passages, and each passage gets an embedding (a numeric representation of its meaning) from a multilingual model. An English question can then land on a Literary Chinese passage without sharing a single character. Originals and translations are stored separately, to test whether an original is findable on its own.
Before any answer was generated, I created a fixed set of reference questions, noting for each the source it should find and whether it should be answered or declined. Every check is a plain fact grounded in the original source records.
Building the Portal
Selecting a question brings up an answer that quotes its sources. Clicking any highlighted quote opens a side panel showing the exact characters it came from, down to the field and file. All 685 archived answers, across 33 questions, are replayed from storage and re-checked against the current sources. The map does the same for geography, drawing 63 places from published gazetteers and listing 90 more it can't definitively pin down.
Pepper, pearls, and ivory from Muziris, with receipts.
Why not create an “ancient chatbot” where site visitors type their own questions? Mainly cost: every live question would need a search and a model call. The harder problem: every answer here was scored against expectations set before it was generated, and a live answer would need its own scoring and presentation, beyond the scope of a proof-of-concept.
Visually, the site leans toward the reading room rather than the dashboard: warm paper and ink, quotes highlighted like a marker on a printout, and a masthead with a Roman eagle standard on one ridge and a Han banner on another. I wanted to create an inviting portal with a good design experience rather than a purely utilitarian one.
How Does It Work in Practice?
The core idea did indeed work! A total of 1,227 out of 1,289 archived quotations land on the passage they quote, though about half of those highlights stop a character or two short, usually just a final period, comma, or bracket. Seven questions were marked in advance as unanswerable from these sources, and every attempt declined them. That nearly got lost! A tidy-looking rule to hide unanswered questions would have deleted exactly those correct refusals. Measuring the rule before building it saved them.
What could be improved? Retrieval is cross-lingual, but quotation mostly isn't: 77% of quotations land on the published English translation, shown beside the original. Machine translation was built, then sat dormant: it fired on seven passages, all from one Greek text, and never again, because nearly every open source already had a published translation.
The sharpest lesson came from scaling. English sources were found for 8 of 9 test questions in every run, and padding the collection with plausible but irrelevant English changed nothing. Adding real sources did. On the same 44 test questions, sources in other languages found in the top six slipped from 24 of 35 to 21 of 35. The likely culprit: each new source brings English translation passages that crowd originals out of the top results. Old Tamil was especially stubborn: one key passage ranked 373rd, and the standard model, asked to write Old Tamil, repeatedly produced Devanagari, Malayalam, Sinhala, or Bengali instead. Why this happens remains an open question.
English held steady. Everything else slipped as the collection grew.
Here's the kicker: nothing reported results by language, so the overall score dipped only gently (73% to 66%) while the product's core promise quietly eroded. That's one of the project's most valuable lessons: segment your evaluation by the thing your product exists to do.
Stopping Here (For Now)
Mundus Ulterior was always intended as a proof-of-concept. A larger expansion was planned, with the bar for continuing set in advance. The results didn't clear it, so I dropped the expansion rather than postponing it.
Where could it go next? Quite a few directions, actually. Report results by language as the tests run, then add sources in the languages that struggle, once the crowding is understood and fixed. Roman and Byzantine sources could sit alongside, describing the same objects, routes, and embassies from the imperial end. Sources without published translations would finally wake up the translation checks. And the highest-value change isn't technical: specialists who read languages like Geʿez or have excavated these sites could correct things I can't possibly check.
You're very welcome to explore the portal at https://mundus-ulterior.pages.dev/. The full code is on GitHub at https://github.com/productofrob/mundus-ulterior for anyone who wants to take it further.
Tooling: claude-haiku-4-5 as the standard model, with claude-sonnet-5 as a stronger comparison; voyage-3.5 embeddings; PostgreSQL with pgvector; a LiteLLM gateway; a single hand-written HTML page; and Cloudflare Pages. Built in sessions with Claude Cowork over roughly 85 hours of active work.