DEV Community

Cover image for DressCode: Your AI Stylist for Tomorrow
Saad Alkentar
Saad Alkentar

Posted on

DressCode: Your AI Stylist for Tomorrow

Gemma 4 Challenge: Write about Gemma 4 Submission

This is a submission for the Gemma 4 Challenge: Write About Gemma 4

Getting dressed is often stressful. What should you wear when the weather keeps changing?
DressCode makes it simple. Powered by Google’s Gemma 4 AI, this app helps you choose the right clothes for the weather tomorrow.
Just upload photos of your clothes. The AI will describe each item, recognize the colors, and know if it’s for cold, warm, spring, or winter weather.
Tell the app the date and time of your event. It checks the weather forecast and suggests complete outfits that match in style and color.
DressCode saves you time and helps you look good every day.
Smart. Simple. Always dressed right.

To build DressCode, we rely on two main capabilities from Google’s Gemma 4 AI:

If in a hurry, look for the core code here
and the backend code here

1. Smart Clothing Analysis

When users upload photos of their clothes, Gemma 4 analyzes the images and extracts as many details as possible to sort outfits, details like:

  • Identifies each piece of clothing (shirt, pants, jacket, dress, etc.)
  • Describes important details: color, pattern, style, material, and type
  • Determines weather suitability (warm, cold, spring, winter, etc.)

This allows the app to understand the user’s wardrobe without manual input.

2. Weather-Based Outfit Generation

Gemma 4 then uses the second key function:

  • Checks the weather forecast for the selected date and time (day or night)
  • Understands the season and weather conditions
  • Picks the best matching clothes from the user’s collection
  • Creates complete, well-coordinated outfits that match in color and style

By combining powerful image understanding with smart reasoning, Gemma 4 turns your personal closet into an intelligent, weather-aware stylist.

This is the core technology behind DressCode — making outfit decisions fast, easy, and always appropriate for the weather.

Setup AI Studio project

In order to use Gemma, we need an API key. and in order to get the key, we need to create a project

AI Studio project

and after creating and naming the project, we can get the api key from the project.

Setup the environment

Using UV for the virtual environment, we only need the project file and sync the requirements

project.toml

[project]
name = "dresscode"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
    "google>=3.0.0",
    "google-genai>=2.4.0",
    "python-dotenv>=1.2.2",
    "requests>=2.34.2",
]
Enter fullscreen mode Exit fullscreen mode
uv sync
Enter fullscreen mode Exit fullscreen mode

Smart Clothing Analysis

One of the most important features in DressCode is Smart Clothing Analysis. When a user uploads a photo of their clothes, the app needs to understand every item clearly and return the information in a clean, organized way.
To achieve this, we use Gemma 4 with a structured prompt and JSON schema. Here’s how it works:
Gemma 4 analyzes the image and identifies every distinct clothing item and accessory. It then returns the results as a well-organized JSON object.
The model extracts key details for each item, including:

  • Item name
  • Category (top, bottom, dress, shoes, etc.)
  • Colors (as hex codes)
  • Dominant color
  • Warmth level
  • Season suitability
  • Style, material, pattern, formality, and more

By using response_mime_type="application/json" and a strict Pydantic schema, we force Gemma 4 to return clean, consistent, and ready-to-use data. This makes it easy for the app to save each clothing item into the database without extra processing.
This organized JSON output is the foundation of DressCode’s intelligent wardrobe system. It allows the AI to truly understand the user’s clothes before suggesting perfect outfits.

We need to set up the GEMINI_API_KEY as an environment variable. Let's create a .env file and set it.

GEMINI_API_KEY=AIz...
Enter fullscreen mode Exit fullscreen mode

Now for the clothes analysis script

from google import genai
from dotenv import load_dotenv
from google.genai import types
from pydantic import BaseModel, Field
from typing import List

load_dotenv()

# 1. Define the structure for a single clothing piece
class ClothingItem(BaseModel):
    name: str = Field(description="Name or short description of the piece")
    category: str = Field(description="Must be one of: Top, Bottom, Outerwear, Footwear")
    color_palette: str = Field(description="Dominant colors, e.g., Navy Blue, Warm Beige")
    seasonality: str = Field(description="e.g., Heavy Winter, Light Spring, Hot Summer")
    style_vibe: str = Field(description="e.g., Formal, Casual, Streetwear, Athletic")
    location_in_image: str = Field(description="where it is relative to the scene, e.g. [200, 123, 1000, 1000]'")

# 2. Define the container for the final list
class ClosetAnalysis(BaseModel):
    clothes: List[ClothingItem]


client = genai.Client()

image_path = "strikkeopskrift-p.jpg" 

with open(image_path, "rb") as f:
    image_bytes = f.read()

response = client.models.generate_content(
    model="gemma-4-26b-a4b-it",
    contents=[
        types.Part.from_bytes(
            data=image_bytes,
            mime_type="image/jpeg",
        ), 
        "Analyze the clothing pieces visible in this image and catalog them strictly according to the schema.",
        ],
        config=types.GenerateContentConfig(
            response_mime_type="application/json",
            response_schema=ClosetAnalysis,
        ),
)

print(response.text)
Enter fullscreen mode Exit fullscreen mode

This script will generate a response following the schema provided, but for more details, I would update the analysis prompt to

DRESS_VISION_PROMPT = (
    "Analyze every distinct clothing item visible in this image and catalog "
    "each one as a separate entry in the `items` array, strictly according "
    "to the schema. Include ALL visible garments and wearable items: tops, "
    "bottoms, outerwear, dresses, shoes, socks, hats, bags, and accessories. "
    "Do not skip smaller or partial items—if shoes, socks, a hat, jewelry, "
    "a belt, scarf, tie, watch, or sunglasses are visible, each gets its own "
    "entry with the correct category: "
    "shoes for any footwear; socks for socks or visible hosiery; hat for "
    "hats, caps, and beanies; accessory for jewelry, belts, scarves, ties, "
    "watches, sunglasses, gloves, and similar add-ons; bag for handbags and "
    "backpacks. "
    "Do not merge multiple garments into one entry. "
    "If only one garment is visible, return a single-item array. "
    "Colors must be lowercase 7-character hex strings starting with '#'."
)
Enter fullscreen mode Exit fullscreen mode

And I would update the JSON response to allow multiple pieces of clothing.

schemas.py

from datetime import datetime, timezone
from typing import Optional
from enum import Enum

from pydantic import BaseModel, Field, field_validator


class Category(str, Enum):
    top = "top"
    bottom = "bottom"
    outerwear = "outerwear"
    shoes = "shoes"
    accessory = "accessory"
    dress = "dress"
    underwear = "underwear"
    bag = "bag"
    hat = "hat"
    socks = "socks"
    other = "other"


class WarmthLevel(str, Enum):
    light = "light"
    medium = "medium"
    heavy = "heavy"


class Season(str, Enum):
    summer = "summer"
    winter = "winter"
    spring = "spring"
    fall = "fall"
    all_season = "all_season"


class Layering(str, Enum):
    base = "base"
    mid = "mid"
    outer = "outer"


class Pattern(str, Enum):
    solid = "solid"
    striped = "striped"
    plaid = "plaid"
    floral = "floral"
    graphic = "graphic"
    other = "other"


class Formality(str, Enum):
    casual = "casual"
    smart_casual = "smart_casual"
    business = "business"
    formal = "formal"


class Brightness(str, Enum):
    light = "light"
    dark = "dark"
    mixed = "mixed"


class DressStatus(str, Enum):
    draft = "draft"
    ready = "ready"
    needs_review = "needs_review"


class DressVisionMultiResult(BaseModel):
    """JSON schema sent to Gemma when cataloging all garments in one image."""

    items: list["DressVisionResult"] = Field(
        min_length=1,
        description=(
            "One entry per distinct garment or wearable visible in the image "
            "(e.g. top, bottom, shoes, socks, hat, accessory in an outfit photo)"
        ),
    )


class DressVisionResult(BaseModel):
    """Vision metadata for a single clothing item."""

    item_name: str = Field(description="Short human-readable name of the garment")
    category: Category = Field(
        description="One of: top, bottom, outerwear, shoes, socks, accessory, "
        "dress, underwear, bag, hat, other"
    )
    colors: list[str] = Field(
        default_factory=list,
        description="All visible colors as #rrggbb hex strings",
    )
    dominant_color: str = Field(description="Primary color as #rrggbb")
    warmth_level: WarmthLevel = Field(
        description="One of: light, medium, heavy"
    )
    season_suitability: list[Season] = Field(
        default_factory=list,
        description="One or more of: summer, winter, spring, fall, all_season",
    )
    style: list[str] = Field(
        default_factory=list,
        description="Short style tags, e.g. casual, formal, streetwear",
    )
    description: str = Field(
        description="Concise visual description (1-3 sentences)"
    )
    layering: Layering = Field(description="One of: base, mid, outer")
    pattern: Optional[Pattern] = Field(
        default=None,
        description="One of: solid, striped, plaid, floral, graphic, other",
    )
    material: Optional[str] = Field(
        default=None, description="Best guess of fabric, e.g. cotton, wool, denim"
    )
    formality: Optional[Formality] = Field(
        default=None,
        description="One of: casual, smart_casual, business, formal",
    )
    brightness: Optional[Brightness] = Field(
        default=None, description="One of: light, dark, mixed"
    )
    water_resistant: bool = Field(default=False)
    occasion_tags: list[str] = Field(
        default_factory=list,
        description="Short occasion tags, e.g. work, party, outdoor, gym",
    )
    confidence: Optional[float] = Field(
        default=None, ge=0.0, le=1.0, description="Model confidence from 0 to 1"
    )


class DressCatalogItem(BaseModel):
    """Slim wardrobe item shape sent to Gemma for outfit selection."""

    id: int
    item_name: Optional[str] = None
    category: Optional[Category] = None
    colors: list[str] = Field(default_factory=list)
    dominant_color: Optional[str] = None
    warmth_level: Optional[WarmthLevel] = None
    season_suitability: list[Season] = Field(default_factory=list)
    style: list[str] = Field(default_factory=list)
    formality: Optional[Formality] = None
    brightness: Optional[Brightness] = None
    layering: Optional[Layering] = None
    pattern: Optional[Pattern] = None
    material: Optional[str] = None
    water_resistant: bool = False
    occasion_tags: list[str] = Field(default_factory=list)
    description: Optional[str] = None

Enter fullscreen mode Exit fullscreen mode

so the new process images script will look more like

main.py

from google import genai
from dotenv import load_dotenv
from google.genai import types
from pydantic import BaseModel, Field
from typing import List
from enum import Enum
from schemas import DressVisionMultiResult
from scripts import DRESS_VISION_PROMPT

load_dotenv()

client = genai.Client()

image_path = "outfit.jpg" 

with open(image_path, "rb") as f:
    image_bytes = f.read()

response = client.models.generate_content(
    model="gemma-4-26b-a4b-it",
    contents=[
        types.Part.from_bytes(
            data=image_bytes,
            mime_type="image/jpeg",
        ), 
        DRESS_VISION_PROMPT,
        ],
        config=types.GenerateContentConfig(
            response_mime_type="application/json",
            response_schema=DressVisionMultiResult,
        ),
)


print(response.text)
Enter fullscreen mode Exit fullscreen mode

and you may get an output like

Sample outfit image

{
    "items":
    [
        {
            "item_name": "light blue blazer",
            "category": "outerwear",
            "colors":
            [
                "#a5c7f7"
            ],
            "dominant_color": "#a5c7f7",
            "warmth_level": "medium",
            "season_suitability":
            [
                "summer",
                "spring"
            ],
            "style":
            [
                "smart_casual"
            ],
            "description": "A light blue single-breasted blazer with a pocket square.",
            "layering": "outer",
            "pattern": "solid",
            "material": "linen",
            "formality": "smart_casual",
            "brightness": "light",
            "water_resistant": false,
            "occasion_tags":
            [
                "work",
                "outdoor"
            ],
            "confidence": 0.95
        },
        {
            "item_name": "white dress shirt",
            "category": "top",
            "colors":
            [
                "#ffffff"
            ],
            "dominant_color": "#ffffff",
            "warmth_level": "light",
            "season_suitability":
            [
                "summer",
                "spring"
            ],
            "style":
            [
                "formal"
            ],
            "description": "A crisp white button-down dress shirt.",
            "layering": "base",
            "pattern": "solid",
            "material": "cotton",
            "formality": "smart_casual",
            "brightness": "light",
            "water_resistant": false,
            "occasion_tags":
            [
                "work",
                "party"
            ],
            "confidence": 0.98
        },
        {
            "item_name": "white trousers",
            "category": "bottom",
            "colors":
            [
                "#ffffff"
            ],
            "dominant_color": "#ffffff",
            "warmth_level": "light",
            "season_suitability":
            [
                "summer",
                "spring"
            ],
            "style":
            [
                "smart_casual"
            ],
            "description": "A pair of slim-fit white trousers.",
            "layering": "base",
            "pattern": "solid",
            "material": "cotton",
            "formality": "smart_casual",
            "brightness": "light",
            "water_resistant": false,
            "occasion_tags":
            [
                "work",
                "outdoor"
            ],
            "confidence": 0.95
        },
        {
            "item_name": "dark brown leather belt",
            "category": "accessory",
            "colors":
            [
                "#3b2b1e"
            ],
            "dominant_color": "#3b2b1e",
            "warmth_level": "light",
            "season_suitability":
            [
                "all_season"
            ],
            "style":
            [
                "classic"
            ],
            "description": "A dark brown leather belt with a silver-tone buckle.",
            "layering": "base",
            "pattern": "solid",
            "material": "leather",
            "formality": "smart_casual",
            "brightness": "dark",
            "water_resistant": false,
            "occasion_tags":
            [
                "work"
            ],
            "confidence": 0.9
        },
        {
            "item_name": "sunglasses",
            "category": "accessory",
            "colors":
            [
                "#1a1a1a"
            ],
            "dominant_color": "#1a1a1a",
            "warmth_level": "light",
            "season_suitability":
            [
                "summer"
            ],
            "style":
            [
                "casual"
            ],
            "description": "Dark sunglasses with black frames.",
            "layering": "base",
            "pattern": "solid",
            "material": null,
            "formality": "casual",
            "brightness": "dark",
            "water_resistant": false,
            "occasion_tags":
            [
                "outdoor"
            ],
            "confidence": 0.95
        },
        {
            "item_name": "watch",
            "category": "accessory",
            "colors":
            [
                "#000000"
            ],
            "dominant_color": "#000000",
            "warmth_level": "light",
            "season_suitability":
            [
                "all_season"
            ],
            "style":
            [
                "classic"
            ],
            "description": "A black wristwatch with a dark face.",
            "layering": "base",
            "pattern": "solid",
            "material": null,
            "formality": "smart_casual",
            "brightness": "dark",
            "water_resistant": false,
            "occasion_tags":
            [
                "work"
            ],
            "confidence": 0.85
        },
        {
            "item_name": "dark brown loafers",
            "category": "shoes",
            "colors":
            [
                "#3d2b1f"
            ],
            "dominant_color": "#3d2b1f",
            "warmth_level": "light",
            "season_suitability":
            [
                "all_season"
            ],
            "style":
            [
                "casual",
                "smart_casual"
            ],
            "description": "Dark brown leather loafers.",
            "layering": "base",
            "pattern": "solid",
            "material": "leather",
            "formality": "smart_casual",
            "brightness": "dark",
            "water_resistant": false,
            "occasion_tags":
            [
                "outdoor",
                "work"
            ],
            "confidence": 0.92
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Weather prediction

After understanding the user’s clothes, DressCode uses Gemma 4’s function calling capability to handle the second key task. When the user selects a date, time, and city for their event or trip, the model makes a structured function call to fetch accurate weather information.
Using function calling, Gemma 4 requests real-time weather data for the chosen city and date. It receives details such as temperature, weather conditions (sunny, rainy, cloudy, etc.), and day/night timing. The model then analyzes this data together with the user’s clothing database to intelligently select and combine items.
This allows Gemma 4 to consider warmth level, season suitability, and weather conditions before generating complete outfit recommendations that match in color, style, and appropriateness.
By combining powerful image analysis with function calling for live weather, DressCode delivers smart and practical outfit suggestions every time.

import os
import requests
from google import genai
from google.genai import types
from dotenv import load_dotenv

load_dotenv()

def fetch_weather_from_api(location: str) -> dict:
    """Hits Open-Meteo API to get real weather data."""
    try:
        # First, geocode the city name to get latitude and longitude
        geo_url = f"https://geocoding-api.open-meteo.com/v1/search?name={location}&count=1&language=en&format=json"
        geo_res = requests.get(geo_url).json()

        if not geo_res.get("results"):
            return {"error": f"Could not find location: {location}"}

        lat = geo_res["results"][0]["latitude"]
        lon = geo_res["results"][0]["longitude"]

        # Second, get the weather using the coordinates
        weather_url = f"https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}&current=temperature_2m,relative_humidity_2m,precipitation&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_max&timezone=auto"
        weather_data = requests.get(weather_url).json()

        return {
            "location": location,
            "current_temp_celsius": weather_data["current"]["temperature_2m"],
            "current_humidity": weather_data["current"]["relative_humidity_2m"],
            "current_precipitation_mm": weather_data["current"]["precipitation"],
            "today_max_temp": weather_data["daily"]["temperature_2m_max"][0],
            "today_min_temp": weather_data["daily"]["temperature_2m_min"][0],
            "rain_chance_percentage": weather_data["daily"]["precipitation_probability_max"][0]
        }
    except Exception as e:
        return {"error": f"Failed to get weather data: {str(e)}"}

# Map function names to our actual python functions
AVAILABLE_TOOLS = {
    "get_weather": fetch_weather_from_api
}

get_weather_declaration = {
    "name": "get_weather",
    "description": "Get current weather for a given location.",
    "parameters": {
        "type": "object",
        "properties": {
            "location": {
                "type": "string",
                "description": "City name, e.g. 'Berlin', 'San Francisco'",
            },
        },
        "required": ["location"],
    },
}

client = genai.Client()
tools = types.Tool(function_declarations=[get_weather_declaration])
config = types.GenerateContentConfig(tools=[tools])


user_prompt = "Should I bring an umbrella to Berlin today?"
print(f"User: {user_prompt}\n")

# Turn 1: Ask the model
response = client.models.generate_content(
    model="gemma-4-26b-a4b-it",
    contents=user_prompt,
    config=config,
)

# If the model decided it needs to use our tool
if response.function_calls:
    for fc in response.function_calls:
        print(f"🤖 Model requests function: {fc.name} with args {fc.args}")

        # Execute the actual Python tool
        tool_to_call = AVAILABLE_TOOLS[fc.name]
        # Extract location safely from tool arguments
        tool_output = tool_to_call(location=fc.args["location"])
        print(f"🔌 Tool output fetched: {tool_output}\n")

        # Turn 2: Send the data back to the model so it can construct its answer
        final_response = client.models.generate_content(
            model="gemma-4-26b-a4b-it",
            contents=[
                # Include the original prompt context
                user_prompt, 
                # Include the tool request object the model generated
                response.candidates[0].content, 
                # Provide the real world answer back matching the function ID
                types.Part.from_function_response(
                    name=fc.name,
                    response={"result": tool_output}
                )
            ],
            config=config # Keep tools configuration attached
        )

        print(f"🤖 final AI Answer: {final_response.text}")
else:
    print(f"🤖 AI Answer: {response.text}")
Enter fullscreen mode Exit fullscreen mode

Sometimes, when trying to call any query gemma we face this error

google.genai.errors.ServerError: 500 INTERNAL. {'error': {'code': 500, 'message': 'Internal error encountered.', 'status': 'INTERNAL'}}
Enter fullscreen mode Exit fullscreen mode

I guess it is due to high demand that the servers sometimes fail. Just trying again usually works.

User: Should I bring an umbrella to Berlin today?                                 

🤖 Model requests function: get_weather with args {'location': 'Berlin'}
🔌 Tool output fetched: {'location': 'Berlin', 'current_temp_celsius': 13.8, 'current_humidity': 94, 'current_precipitation_mm': 0.0, 'today_max_temp': 19.5, 'today_min_temp': 13.8, 'rain_chance_percentage': 83}

🤖 final AI Answer: Yes, you should definitely bring an umbrella. There is an 83% chance of rain in Berlin today.
Enter fullscreen mode Exit fullscreen mode

Now, since we can get the weather prediction accurately, we need to write the outfit suggestion prompt

Outfit suggestion, put it all together

I've written this prompt for outfit suggestion

OUTFIT_SUGGESTION_PROMPT = """You are a personal stylist. Pick complete outfits from the user's wardrobe for a specific event.

Workflow:
1. Call the `get_weather` function for the event city, passing the event date in ISO YYYY-MM-DD format, to retrieve the forecast (temperature, precipitation, wind, sunrise, sunset).
2. Consider the event_type, the date (season), the start_time / end_time (whether it falls during the day, night, or spans both around sunrise/sunset), and the weather.
3. Pick pieces ONLY from the provided wardrobe catalog using their integer `id`. Never invent items.
4. Build AT LEAST 2 distinct complete outfits. A complete outfit covers, at minimum, a top, a bottom, and shoes when matching items exist in the catalog (a single `dress` category item replaces top+bottom). Add an outerwear piece when the forecast is cold, wet, or windy. Add accessories or bag/hat when appropriate.
5. Match formality to the event_type:
   - business / formal -> formality "business" or "formal"
   - smart_casual / date_night -> "smart_casual" or higher
   - casual / outdoor / sports / party -> "casual" or "smart_casual" as fits
6. Match warmth to the weather. Prefer water-resistant items if rain is likely. Prefer the right season_suitability for the event date.
7. Ensure COLOR HARMONY across the pieces in each outfit. Use the hex colors in the catalog to reason about complementary, analogous, monochromatic, or neutral-anchor palettes. Briefly state the harmony you chose in `color_harmony`.
8. Provide a short `weather_summary` of the conditions you optimized for and a per-outfit `reasoning`.

Return ONLY the structured JSON matching the response schema.
"""
Enter fullscreen mode Exit fullscreen mode

And for the weather prediction, we need to get it for a certain date, so I've updated it to

def fetch_weather_from_api(location: str, date: str = None) -> dict:
    """Hits Open-Meteo API to get real weather data, optionally for a specific date."""
    try:
        # 1. Geocode the city name to get latitude and longitude
        geo_url = f"https://geocoding-api.open-meteo.com/v1/search?name={location}&count=1&language=en&format=json"
        geo_res = requests.get(geo_url).json()

        if not geo_res.get("results"):
            return {"error": f"Could not find location: {location}"}

        lat = geo_res["results"][0]["latitude"]
        lon = geo_res["results"][0]["longitude"]

        # 2. Build the weather URL based on whether a date was provided
        # Open-Meteo takes start_date and end_date in YYYY-MM-DD format for specific ranges
        base_url = f"https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}&timezone=auto"

        if date:
            weather_url = f"{base_url}&start_date={date}&end_date={date}&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_max"
        else:
            # Fallback to current/today's forecast if no date is provided
            weather_url = f"{base_url}&current=temperature_2m,relative_humidity_2m,precipitation&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_max"

        weather_data = requests.get(weather_url).json()

        # If there's an API error response from open-meteo
        if "error" in weather_data:
            return {"error": weather_data["reason"]}

        # 3. Format response dynamically depending on what parameters were sent
        response = {
            "location": location,
            "date": date if date else "today",
            "today_max_temp": weather_data["daily"]["temperature_2m_max"][0],
            "today_min_temp": weather_data["daily"]["temperature_2m_min"][0],
            "rain_chance_percentage": weather_data["daily"]["precipitation_probability_max"][0]
        }

        # Only add current conditions if we didn't look up a specific forecast date
        if "current" in weather_data:
            response.update({
                "current_temp_celsius": weather_data["current"]["temperature_2m"],
                "current_humidity": weather_data["current"]["relative_humidity_2m"],
                "current_precipitation_mm": weather_data["current"]["precipitation"],
            })

        return response

    except Exception as e:
        return {"error": f"Failed to get weather data: {str(e)}"}

# Map function names to our actual python functions
AVAILABLE_TOOLS = {
    "get_weather": fetch_weather_from_api
}

# Updated declaration schema to include the date parameter
get_weather_declaration = {
    "name": "get_weather",
    "description": "Get current or predicted weather for a given location and optional date.",
    "parameters": {
        "type": "object",
        "properties": {
            "location": {
                "type": "string",
                "description": "City name, e.g. 'Berlin', 'San Francisco'",
            },
            "date": {
                "type": "string",
                "description": "The specific date for the weather prediction in YYYY-MM-DD format. Optional: default to today if not provided.",
            }
        },
        "required": ["location"],
    },
}
Enter fullscreen mode Exit fullscreen mode

Now the user is expected to provide the event details and a list of clothes (catalog). The event will be something like

event_payload = {
    "event_type": "casual",
    "event_date": "2026-06-01",
    "start_time": None,
    "end_time": None,
    "city": "Berlin",
    "season": "Spring",
    "title": "Walk in the park",
    "notes": "",
}
Enter fullscreen mode Exit fullscreen mode

and the catalog will be a list of clothes from analyzing the images

catalog = [
        {
            "item_name": "light blue blazer",
            "category": "outerwear",
            "colors":
            [
                "#a5c7f7"
            ],
            "dominant_color": "#a5c7f7",
            ...
Enter fullscreen mode Exit fullscreen mode

The final user prompt would look like

user_prompt = (
            f"{OUTFIT_SUGGESTION_PROMPT}\n\n"
            f"Event:\n{json.dumps(event_payload, indent=2)}\n\n"
            f"Wardrobe catalog ({len(catalog)} items):\n"
            f"{json.dumps([c for c in catalog], indent=2)}"
        )
Enter fullscreen mode Exit fullscreen mode

Will it work? let's try it out

{
    "weather_summary": "A warm and sunny spring day with a high of 29.9°C and a low of 16.4°C. Low chance of rain (16%), making it perfect for light, breathable fabrics and outdoor activities.",
    "color_harmony": "I utilized a palette of crisp whites and light blues anchored by deep brown leather tones to create a refreshing, airy, and sophisticated summer-ready look.",
    "outfits":
    [
        {
            "outfit_id": 1,
            "items":
            [
                1,
                2,
                4,
                5,
                6
            ],
            "reasoning": "This outfit is optimized for the warm peak temperatures. The all-white base (shirt and trousers) reflects sunlight to keep you cool during your walk. I've added sunglasses and a watch to keep the look effortless and casual for a park setting."
        },
        {
            "outfit_id": 2,
            "items":
            [
                0,
                1,
                2,
                3,
                6
            ],
            "reasoning": "For a slightly more elevated 'smart-casual' approach, I've layered the light blue linen blazer. The linen material is highly breathable for the 29°C weather, and the light blue complements the white base perfectly. The dark brown belt and loafers provide a classic, grounded finish."
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Interesting choices, especially since the closet we use is almost empty.

In short!

DressCode is a complete pipeline that intelligently recommends outfits based on your actual wardrobe and upcoming weather conditions. Built with a focus on simplicity and efficiency, the system uses Gemma 4 as its core AI engine and runs inside a UV virtual environment.
Based on our experiments in this article, the core proof of concept was built on those steps

  • Wardrobe Ingestion
    Users place photos of their clothes in a folder called catalog. The application scans this folder and uses Gemma 4 to analyze each image. For every photo (e.g., red_jacket.jpg), it generates a detailed JSON description file (red_jacket.json) containing the item’s name, colors, warmth level, season suitability, style tags, category, and rich visual description. If a matching JSON file already exists, the image is skipped to avoid redundant AI calls.

  • Event Definition
    The user defines their plans by creating a simple event.json file containing the event title, date, time range, location, and occasion type.

  • Weather Integration
    The system automatically fetches accurate weather forecast data for the specified date and location.

  • Intelligent Outfit Generation
    DressCode reads all the clothing descriptions from the catalog folder, filters out unsuitable items (wrong season, inadequate warmth, or mismatched style), and feeds the relevant clothing data along with the weather information and event details to Gemma 4. The model then generates well-coordinated outfit suggestions, taking into account color harmony, layering needs, day/night conditions, and the specific occasion.

A sample of the system outfit suggestions

outfit 1

outfit 2

{
  "event": {
    "event_type": "casual",
    "event_date": "2026-06-01",
    "start_time": null,
    "end_time": null,
    "city": "Berlin",
    "season": "summer",
    "title": "Walk in the park",
    "notes": ""
  },
  "season": "summer",
  "filtered_item_count": 11,
  "weather_summary": "Warm summer day with a high of 29.9\u00b0C and low of 16.4\u00b0C. Very low chance of rain and light winds.",
  "outfits": [
    {
      "name": "Sunny Park Casual",
      "color_harmony": "A light, airy palette using beige (#e6e1d3) and light blue (#8eb0d4) with navy accents (#001a33) for a cohesive, summery feel.",
      "reasoning": "Perfect for a warm summer walk in the park. The linen-blend trousers and short-sleeve shirt are breathable for the 30\u00b0C high, while the sun hat provides protection.",
      "pieces": [
        {
          "dress_id": 3,
          "category": "top",
          "role": "top",
          "source_image": "2ce384fd55b44d16bcc9bb3c12c9bf4a.webp",
          "item": {
            "id": 3,
            "item_name": "short sleeve shirt",
            "category": "top",
            "colors": [
              "#e6e1d3"
            ],
            "dominant_color": "#e6e1d3",
            "warmth_level": "light",
            "season_suitability": [
              "summer",
              "spring"
            ],
            "style": [
              "casual"
            ],
            "formality": "casual",
            "brightness": "light",
            "layering": "base",
            "pattern": "solid",
            "material": "linen",
            "water_resistant": false,
            "occasion_tags": [
              "daily"
            ],
            "description": "A light beige, short sleeve button-down shirt made of a lightweight fabric."
          }
        },
        {
          "dress_id": 14,
          "category": "bottom",
          "role": "bottom",
          "source_image": "spc2402.webp",
          "item": {
            "id": 14,
            "item_name": "light blue pleated trousers",
            "category": "bottom",
            "colors": [
              "#8eb0d4"
            ],
            "dominant_color": "#8eb0d4",
            "warmth_level": "medium",
            "season_suitability": [
              "spring",
              "summer",
              "fall"
            ],
            "style": [
              "casual",
              "relaxed"
            ],
            "formality": "smart_casual",
            "brightness": "light",
            "layering": "base",
            "pattern": "solid",
            "material": "linen blend",
            "water_resistant": false,
            "occasion_tags": [
              "daily wear",
              "summer outings"
            ],
            "description": "A pair of light blue trousers with a pleated front and an elasticated drawstring waist. The fabric appears to be a lightweight linen or cotton blend."
          }
        },
        {
          "dress_id": 7,
          "category": "shoes",
          "role": "shoes",
          "source_image": "Buy-Best-Quality-IMPORTED-Full-Blue-Shoes-for-Men-NB02-at-Most-Affordable-Price-by-shopse.pk-in-Pakistan-1-1200x900.jpg",
          "item": {
            "id": 7,
            "item_name": "navy blue lightning bolt sneakers",
            "category": "shoes",
            "colors": [
              "#1e3a5f",
              "#ffffff"
            ],
            "dominant_color": "#1e3a5f",
            "warmth_level": "light",
            "season_suitability": [
              "all_season"
            ],
            "style": [
              "streetwear",
              "casual"
            ],
            "formality": "casual",
            "brightness": "mixed",
            "layering": "base",
            "pattern": "graphic",
            "material": "synthetic",
            "water_resistant": false,
            "occasion_tags": [
              "lifestyle",
              "streetwear"
            ],
            "description": "Navy blue sneakers with a large white lightning bolt graphic on the side and a thick platform sole."
          }
        },
        {
          "dress_id": 10,
          "category": "hat",
          "role": "accessory",
          "source_image": "damenhut-elegant-blau-strohhut-sommer-s2024tiffany-11.282x0-portrait.jpg",
          "item": {
            "id": 10,
            "item_name": "wide brim sun hat",
            "category": "hat",
            "colors": [
              "#001a33"
            ],
            "dominant_color": "#001a33",
            "warmth_level": "light",
            "season_suitability": [
              "summer",
              "spring"
            ],
            "style": [
              "elegant",
              "classic"
            ],
            "formality": "formal",
            "brightness": "dark",
            "layering": "outer",
            "pattern": "solid",
            "material": "straw",
            "water_resistant": false,
            "occasion_tags": [
              "outdoor",
              "garden party"
            ],
            "description": "A large navy blue wide brim hat with a decorative band and a bow on the side."
          }
        }
      ]
    },
    {
      "name": "Relaxed Summer Blue",
      "color_harmony": "Monochromatic-leaning blue tones with neutral grey (#808080) as an anchor.",
      "reasoning": "A very casual, comfortable option for walking. The graphic tee and sweat shorts are ideal for high temperatures, and the sneakers are perfect for a park stroll.",
      "pieces": [
        {
          "dress_id": 1,
          "category": "top",
          "role": "top",
          "source_image": "0d93ccd6fd86dd88dbf1127957054b4f__384.webp",
          "item": {
            "id": 1,
            "item_name": "graphic t-shirt",
            "category": "top",
            "colors": [
              "#a1c4fd"
            ],
            "dominant_color": "#a1c4fd",
            "warmth_level": "light",
            "season_suitability": [
              "summer",
              "spring"
            ],
            "style": [
              "casual",
              "streetwear"
            ],
            "formality": "casual",
            "brightness": "light",
            "layering": "base",
            "pattern": "graphic",
            "material": "cotton",
            "water_resistant": false,
            "occasion_tags": [
              "birthday",
              "casual"
            ],
            "description": "A light blue short-sleeve t-shirt featuring a large black graphic text that says 'IT TOOK ME 30 Years to look this GOOD'."
          }
        },
        {
          "dress_id": 4,
          "category": "bottom",
          "role": "bottom",
          "source_image": "4_97683d58-4c20-474b-b247-2b952b726a65.webp",
          "item": {
            "id": 4,
            "item_name": "grey sweat shorts",
            "category": "bottom",
            "colors": [
              "#808080",
              "#ffffff"
            ],
            "dominant_color": "#808080",
            "warmth_level": "light",
            "season_suitability": [
              "summer",
              "spring"
            ],
            "style": [
              "casual",
              "streetwear"
            ],
            "formality": "casual",
            "brightness": "mixed",
            "layering": "base",
            "pattern": "solid",
            "material": "cotton",
            "water_resistant": false,
            "occasion_tags": [
              "lounging",
              "outdoor",
              "gym"
            ],
            "description": "A pair of grey marl sweat shorts with an elasticated waistband and white drawstrings."
          }
        },
        {
          "dress_id": 7,
          "category": "shoes",
          "role": "shoes",
          "source_image": "Buy-Best-Quality-IMPORTED-Full-Blue-Shoes-for-Men-NB02-at-Most-Affordable-Price-by-shopse.pk-in-Pakistan-1-1200x900.jpg",
          "item": {
            "id": 7,
            "item_name": "navy blue lightning bolt sneakers",
            "category": "shoes",
            "colors": [
              "#1e3a5f",
              "#ffffff"
            ],
            "dominant_color": "#1e3a5f",
            "warmth_level": "light",
            "season_suitability": [
              "all_season"
            ],
            "style": [
              "streetwear",
              "casual"
            ],
            "formality": "casual",
            "brightness": "mixed",
            "layering": "base",
            "pattern": "graphic",
            "material": "synthetic",
            "water_resistant": false,
            "occasion_tags": [
              "lifestyle",
              "streetwear"
            ],
            "description": "Navy blue sneakers with a large white lightning bolt graphic on the side and a thick platform sole."
          }
        }
      ]
    }
  ],
  "generated_at": "2026-05-22T14:54:07.319053+00:00"
}
Enter fullscreen mode Exit fullscreen mode

Did you like the idea? Would you like to build the complete app?
Contact me here and show your support at
the core code repo
and the backend code repo

Top comments (0)