Skip to content

fix: outfit needs top - #262

Merged
Anyesh merged 16 commits into
mainfrom
fix/outfit-needs-top
Oct 18, 2026
Merged

Anyesh merged 16 commits into
mainfrom
fix/outfit-needs-top

Conversation

@Anyesh

@Anyesh Anyesh commented Oct 12, 2026 •

Copy link
Copy Markdown
Owner

Description

Related Issue

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Refactoring (no functional changes)
  • CI/CD or build changes

Checklist

  • I have read the CONTRIBUTING guide
  • My code follows the project's coding style
  • I have added tests that prove my fix/feature works
  • New and existing tests pass locally
  • I have updated documentation as needed
  • My changes don't introduce new warnings or errors

Testing

Test Environment

  • Docker Compose
  • Kubernetes
  • Local development

Tests Performed

Screenshots (if applicable)

Additional Notes

Summary by Sourcery

Ensure outfit recommendations are complete and wardrobe-compatible by validating covered regions and filling missing pieces with suitable items when possible.

Bug Fixes:

  • Ensure generated and cached outfit recommendations contain all wearable regions available in the wardrobe, including a required top.
  • Reject requested or AI-selected outfits that cannot be completed from the available wardrobe instead of saving incomplete or fabricated outfits.
  • Prefer complete AI suggestions while filling incomplete options with the best-fitting wardrobe items only as a fallback.

Enhancements:

  • Model worn-alone garments and outfit regions more accurately, including one-piece garments, suits, layers, footwear, and accessories.
  • Reuse fit scoring to rank fallback items and prioritize less recently worn items when fit scores tie.
  • Validate mandatory items, cached suggestions, duplicate body slots, and hot-weather legwear consistently before saving outfits.

Tests:

  • Add coverage for region detection, outfit completeness, wardrobe completion, fallback filling, fit ranking, cached suggestions, mandatory items, and invalid AI responses.

@github-actions github-actions Bot added the python Pull requests that update python code label Oct 12, 2026
@sourcery-ai

sourcery-ai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Makes outfit generation wardrobe-aware so available recommendations include required upper-body, lower-body, and footwear regions, while safely rejecting unusable AI output, completing viable partial outfits with ranked items, and validating cached suggestions.

Sequence diagram for wardrobe-aware outfit recommendation

sequenceDiagram
    participant User
    participant RecommendationService
    participant AIService
    participant ClothingUtils
    participant ItemScorer
    participant OutfitStore

    User->>RecommendationService: generate_recommendations()
    RecommendationService->>AIService: generate outfit suggestions
    AIService-->>RecommendationService: AI outfit options
    RecommendationService->>RecommendationService: resolve_outfit_items()
    RecommendationService->>ClothingUtils: missing_regions()
    alt complete outfit
        RecommendationService->>OutfitStore: _save_outfit()
    else partial outfit
        RecommendationService->>ItemScorer: rank_by_fit()
        ItemScorer-->>RecommendationService: ranked candidate IDs
        RecommendationService->>ClothingUtils: fill_missing_regions()
        RecommendationService->>OutfitStore: _save_outfit()
    else unusable outfit
        RecommendationService-->>User: AIRecommendationError
    end
Loading

Flow diagram for validating cached outfit suggestions

flowchart TD
    A[Load cached suggestion] --> B["picked_item_ids()"]
    B --> C{All items still available?}
    C -- No --> D[Skip cached suggestion]
    C -- Yes --> E["resolve_outfit_items()"]
    E --> F{Wearable and complete?}
    F -- No --> D
    F -- Yes --> G["_save_outfit()"]
Loading

File-Level Changes

Change Details Files
Define wardrobe-aware region coverage and fill missing outfit regions with compatible items.
  • Recognize upper, lower, and footwear coverage across one-piece garments, suits, layers, and worn-alone items.
  • Detect missing regions based on what the wardrobe can supply.
  • Select compatible ranked items while avoiding slot conflicts and preserving mandatory items.
backend/app/utils/clothing.py
backend/app/utils/garment_vocabulary.py
backend/app/data/garment_vocabulary.json
Harden recommendation materialization for incomplete, invalid, and cached AI suggestions.
  • Centralize AI item parsing, mandatory-item enforcement, deduplication, and hot-weather legwear removal.
  • Reject unusable options, fill incomplete single or multi-outfit responses, and prefer the least-changed completable partial.
  • Validate cached suggestions for current availability and completeness before serving them.
  • Persist only complete leftover suggestions in the cache.
backend/app/services/recommendation_service.py
Add fit-based fallback ranking for wardrobe items.
  • Rank candidates by weather, occasion, explicit and learned preferences, then least-recently-worn status.
backend/app/services/item_scorer.py
Update pairing silhouette validation to use wardrobe region coverage.
  • Preserve source-item coverage checks using the new region model, including one-piece and suit behavior.
backend/app/services/pairing_service.py
Expand regression coverage for outfit completeness and fallback behavior.
  • Test missing-region detection and compatible filling across garment types.
  • Test ranking, mandatory items, legwear handling, invalid AI picks, incomplete AI responses, cached suggestions, and option selection.
backend/tests/test_clothing_utils.py
backend/tests/test_item_scorer.py
backend/tests/test_recommendation_service.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@Anyesh
Anyesh force-pushed the fix/outfit-needs-top branch from 46c06be to e66e73d Compare October 16, 2026 10:44
@Anyesh
Anyesh marked this pull request as ready for review October 17, 2026 04:55

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 4 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="backend/app/services/recommendation_service.py" line_range="1012-1017" />
<code_context>
+                if not has_wearable_piece(item_ids, item_type_map):
+                    raise AIRecommendationError("AI did not select any valid items")
+                if missing_regions(item_ids, item_type_map):
+                    item_ids = fill_missing_regions(
+                        item_ids,
+                        rank_by_fit(candidates, weather, occasion, preferences, learned_prefs),
+                        item_type_map,
+                    )
+                outfit = await self._save_outfit(
                     outfit_data,
+                    item_ids,
</code_context>
<issue_to_address>
**Incomplete outfits are saved**

When fallback candidates all conflict with occupied slots for a missing region, both recommendation paths save the unchanged IDs without rechecking `missing_regions`, so users receive outfits missing a region even though changing a conflicting pick could cover it.

After `fill_missing_regions`, reject or repair any selection that still has missing regions before saving it.

Also at `backend/app/services/recommendation_service.py:181-190`, `backend/app/services/recommendation_service.py:1054`, `backend/app/services/recommendation_service.py:1059`.
</issue_to_address>

### Comment 2
<location path="backend/app/utils/clothing.py" line_range="101-104" />
<code_context>
+    return set().union(*(_regions(item_type_map.get(iid, "")) for iid in item_ids))
+
+
+def has_wearable_piece(item_ids: list[UUID], item_type_map: dict[UUID, str]) -> bool:
+    # A wardrobe with nothing that dresses any region (only layers, say) still gets outfits.
+    if not covered_regions(list(item_type_map), item_type_map):
+        return bool(item_ids)
+    return bool(covered_regions(item_ids, item_type_map))
+
</code_context>
<issue_to_address>
**Accessory-only outfits pass validation**

When the candidate wardrobe contains only regionless items and the AI selects one, `has_wearable_piece` treats any nonempty selection as wearable when the wardrobe covers no region, so the service saves selections such as a bag or hat as outfits.

Require a selected wearable item rather than treating any nonempty selection as wearable when the wardrobe has no region-covering items.
</issue_to_address>

### Comment 3
<location path="backend/app/services/item_scorer.py" line_range="268-281" />
<code_context>


+def rank_by_fit(
+    items: list[ClothingItem],
+    weather: WeatherData,
+    occasion: str,
+    preferences: UserPreference | None,
+    learned_prefs: dict | None,
+) -> list[UUID]:
+    # score_items leaves small wardrobes unscored, so filling a missing piece needs its own
+    # ranking or it would fall back to database order. Most items tie on fit in mild
+    # weather, so the least recently worn goes first to avoid adding the same piece every time.
+    def key(item: ClothingItem) -> tuple[float, date]:
+        fit = (
+            _weather_score(item, weather, preferences)
+            * _formality_score(item, occasion)
+            * _preference_score(item, preferences, learned_prefs)
+        )
+        return -fit, item.last_worn_at or date.min
</code_context>
<issue_to_address>
**Season-mismatched items rank higher**

When fallback candidates differ in seasonal suitability while tying or scoring similarly on weather, formality, and preferences, `rank_by_fit` omits `_season_score`, although `score_items` includes it, so a season-mismatched candidate outranks a season-appropriate one and is added to the outfit.

Include `_season_score` in `rank_by_fit`'s fit calculation, matching `score_items`.
</issue_to_address>

### Comment 4
<location path="backend/app/services/recommendation_service.py" line_range="1012" />
<code_context>
+                if not has_wearable_piece(item_ids, item_type_map):
+                    raise AIRecommendationError("AI did not select any valid items")
+                if missing_regions(item_ids, item_type_map):
+                    item_ids = fill_missing_regions(
+                        item_ids,
+                        rank_by_fit(candidates, weather, occasion, preferences, learned_prefs),
</code_context>
<issue_to_address>
**Fill-ins contradict outfit explanation**

When an incomplete AI answer is filled with wardrobe items, when fallback filling adds garments, `rank_by_fit` can select candidates outside the `TOP_N` items shown to the AI, while `_save_outfit` persists the AI’s original headline, styling notes, and raw response unchanged. Users receive an outfit whose explanation describes different pieces.

Record the fallback additions in the saved outfit and provide reasoning that does not imply the AI selected or evaluated those items.

Also at `backend/app/services/recommendation_service.py:1053`.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 4 findings to address first, and if the region-filling logic chooses an unsuitable item, that item is saved as part of the generated outfit and may remain after the code is reverted. The impact is bounded to generated outfit records and can be corrected by editing or removing them.

Blocking findings: backend/app/services/recommendation_service.py:1017, backend/app/utils/clothing.py:104, backend/app/services/item_scorer.py:281, backend/app/services/recommendation_service.py:1012


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread backend/app/services/recommendation_service.py Outdated
Comment thread backend/app/utils/clothing.py Outdated
Comment thread backend/app/services/item_scorer.py Outdated
if not has_wearable_piece(item_ids, item_type_map):
raise AIRecommendationError("AI did not select any valid items")
if missing_regions(item_ids, item_type_map):
item_ids = fill_missing_regions(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Fill-ins contradict outfit explanation

When an incomplete AI answer is filled with wardrobe items, when fallback filling adds garments, rank_by_fit can select candidates outside the TOP_N items shown to the AI, while _save_outfit persists the AI’s original headline, styling notes, and raw response unchanged. Users receive an outfit whose explanation describes different pieces.

Record the fallback additions in the saved outfit and provide reasoning that does not imply the AI selected or evaluated those items.

Also at backend/app/services/recommendation_service.py:1053.

Prompt for AI agents
In `backend/app/services/recommendation_service.py` at line 1012:

**Fill-ins contradict outfit explanation**

When an incomplete AI answer is filled with wardrobe items, when fallback filling adds garments, `rank_by_fit` can select candidates outside the `TOP_N` items shown to the AI, while `_save_outfit` persists the AI’s original headline, styling notes, and raw response unchanged. Users receive an outfit whose explanation describes different pieces.

Record the fallback additions in the saved outfit and provide reasoning that does not imply the AI selected or evaluated those items.

Also at `backend/app/services/recommendation_service.py:1053`.

@Anyesh
Anyesh merged commit 464841a into main Oct 18, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Pull requests that update python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant