I think an outfit is a response to an occasion. Not a random assembly of clothes - a set of decisions about a specific moment. That's why I built a wardrobe app years ago.

It holds over 400 items, each with its history: where I bought it, what the tailor changed, what it does and doesn't go with. Beautifully organised, and completely silent. An archive with no archivist.

Faced with the daily question of "what should I wear?", I still had to do all the work. The app could show me the pieces, but the intuition connecting them to an occasion was all in my head. I wanted to see how much of my own taste I could write down precisely enough for the app to offer a second opinion.

The Archivist vs. The Accountant

My existing system treated every outfit like a museum piece. It required a name, notes, and a rating: a workflow suited to archiving a carefully constructed, compliment-worthy ensemble.

But not every outfit is a work of art. Sometimes an outfit is just... what you wore. A t-shirt and jeans for a Tuesday at home still belong in a record of what I actually wear. Giving them a title and a review felt like a chore, so those ordinary days often went unlogged.

The app needed to be two things: an Archivist for the outfits worth remembering, and an Accountant for the ones that just happened.

"Log Today's Wear" became the Accountant: select the items, log them, move on. It updates each garment's wear count and last-worn date without requiring a named outfit. That lets me see what I keep reaching for and what I've been ignoring.

Those records don't automatically teach the recommender my taste. This is a rules-based tool, not a model learning from my mornings. The quick log supports item analytics; the recommendation rules still need to be written down.

An Occasion Before an Outfit

I spent a few days jotting ideas in the notebook I carry around and making UI mockups in Procreate, away from development. The "Outfit of the Day" page needed to start with two questions: what's the temperature range, and where am I going?

Each occasion has a preset for how I'd dress: a formality range, how expressive I want to be, how much I'll be moving around. "Chamber Music" and "Jazz Club" are both evening events with live music. I still want different suggestions. These are my defaults, not dress codes handed down by the venues.

The Outfit of the Day screen. A temperature range of 10 to 25 degrees Celsius sits at the top, then an occasion preset set to Office, which has expanded into four constraint sliders: formality between Casual and Smart Casual, expression between Minimal and Subtle, activity level at Moderate Movement, and comfort tolerance at Neutral. A preferred-tag chip reads Textured.
Picking "Office" supplies numeric ranges for the scorer. The preset represents how I'd dress for work, and I can adjust it for the day.

Translating Intuition into Logic (with an AI Consultant)

Here's the hard part: how do you turn the feeling of a good outfit into a score_outfit() function? I used an AI assistant for this, not to write the rules, but to keep asking me why until I could state them precisely.

Temperature Coverage: An outfit comfortable at 10-20C (yes, I use Celsius, sue me) leaves the hottest part of a 15-25C day uncovered. I wanted the scorer to favor outfits that cover the whole day's range, rather than ones that are comfortable for only part of it.

I was struggling with how to penalize formality mismatches. I asked the assistant, 'How can I mathematically model that being slightly underdressed is okay, but being very underdressed is a disaster?' It suggested exploring non-linear penalties, which led me directly to the idea of the exponential 'Formality Cliff' function.

The Formality Cliff: For saved outfits, I chose a steep penalty: one step outside the requested formality range multiplies the formality score by 0.1; two steps, by 0.01. It's my way of telling the tool not to let a favorite outfit win simply because I like it, regardless of where I'm going. The factor of ten is a preference I encoded, not a measurement of how badly dressed someone is.

The Birthday Party Problem

An early version of the recommender had a glaring flaw: it was boring. For any given occasion, it would almost always suggest my single highest-rated "staple" outfit. This is logical, but it's not how people dress.

I imagined a common scenario: I'm going to a friend's birthday party. I want to wear something fun and that I feel great in, but I certainly don't want to wear the exact same thing I wore to the last three parties, even if it is my favorite. The system needed to understand the human desire for variety.

For saved outfits, I added a recency penalty. First the engine keeps the candidates that clear the minimum suitability score. It then sorts those by their recorded last-worn dates and multiplies the three most recent candidates' scores by 0.5, 0.75, and 0.9, respectively. Older candidates keep their scores. A favorite can still win, but it has to overcome that handicap.

This reads the date on the saved outfit, not the dates on its individual garments. Logging a shirt and jeans through the Accountant doesn't update a saved combination's date; that record needs its own wear update. Wearing the same shirt in two different outfits isn't the same as repeating the outfit.

Building Outfits That Don't Exist Yet

The last step was getting it to assemble something new, item by item, rather than recalling something I'd already worn. Which meant writing down what makes an outfit cohere:

  • Color Harmony: My default is one main color plus neutrals, so I gave the generator a penalty for multiple non-neutral colors among items marked as single-colored. It compares their stored colors with my neutral palette in CIELAB, a color space that helps approximate differences as we see them. An off-white doesn't have to match a particular white hex code exactly. This encodes a combination I tend to like; it doesn't declare colorful outfits incoherent.

  • Layering: I also gave it a preference for removable outerwear on cool days with a large temperature swing, and for a sweater when it's consistently cool. If it chooses knitwear, it adds an undergarment when one is available.

  • The Gauntlet: The generator makes up to twenty candidate outfits. It samples pieces using their suitability scores, then compares the combinations for color and formality coherence and competing statement pieces. The winner goes on screen. The "re-roll" button runs the tournament again.

Generation doesn't use wear history, and it isn't the saved-outfit scorer with new clothes fed into it. It is a separate way to propose combinations from my catalog, guided by the attributes and preferences I've supplied.

Two recommended outfits side by side. On the left, labelled Top Pick From Saved Outfits: a rust corduroy overshirt, white tee, dark jeans, brown brogues, no-show socks, a ring and a chain. On the right, labelled Randomly Generated: a white oxford shirt, black trousers, black Chelsea boots and black socks.
The same request, answered two ways. On the left, a saved outfit ranked for suitability and recency. On the right, a generated combination assembled from individual pieces and scored for coherence. The two paths use different scoring rules.

Four Weeks Later

I wasn't trying to automate getting dressed. That's one of the better parts of the morning and I'd like to keep it.

What I wanted was something to argue with - a second opinion that has actually read all 400 entries, including the ones I've forgotten. That's what it does now. Most of its suggestions I reject. Every so often it puts two things together that I wouldn't have, and it's right.

The hardest part wasn't the code. It was being forced to say out loud, precisely enough to compile, why one outfit works and another doesn't. Turns out I only half knew.