Skip to content

feat: Rework outfit recommendation slot rules to treat full body items better - #264

Merged
Anyesh merged 6 commits into
Anyesh:mainfrom
geekygirlsarah:213-full-body-afterthought
Oct 16, 2026
Merged

Anyesh merged 6 commits into
Anyesh:mainfrom
geekygirlsarah:213-full-body-afterthought

Conversation

@geekygirlsarah

@geekygirlsarah geekygirlsarah commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

…ith garment vocabulary, ensuring consistency and avoiding manual updates. Added tests for vocabulary-based role types and prompt rendering.

Description

Reworks the outfit silhouettes in both AI outfit prompts so separates and one-piece looks are equal peers, and derives the garment type lists from the ITEM_ROLE taxonomy so the prompts can't drift from what the tagger validates.

Previously recommendation.txt framed the formula as separates-first with OR pick ONE dress as a less-preferred fallback, and item_pairing.txt did the same. The slot enumeration was also a hand-written subset that omitted several real base_top types (e.g. top, polo, tank-top) and never named jumpsuit. For a wardrobe heavy in dresses, this wording biased the model toward separating.

Changes:

  • backend/app/utils/garment_vocabulary.py: add types_for_role(role) and render_outfit_prompt(template), which fills <<ROLE_TYPES>> tokens from ITEM_ROLE (mirrors the existing render_tagging_prompt pattern).
  • backend/app/prompts/recommendation.txt: replace the OR continuation with two equal-weight silhouette bullets (separates / one-piece) plus an optional legwear bullet; generalize the proportion guidance to cover one-piece waists.
  • backend/app/prompts/item_pairing.txt: same silhouette treatment, and state that a full-body garment fills both top and bottom slots.
  • backend/app/services/recommendation_service.py, backend/app/services/pairing_service.py: render the prompts at load time.
  • backend/tests/test_garment_vocabulary.py: tests asserting the role lists come from the vocabulary and that both prompts render and contain every role's types.

Related Issue

Resolves #213

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

python -m pytest tests/test_garment_vocabulary.py -q -k "types_for_role or outfit_prompt or type_lists or tagging_prompt"
# 5 passed

python -m pytest tests/test_recommendation_service.py -q -k "Prompt or prompt or TempRange or temp_range"
# 14 passed

uvx ruff check app/utils/garment_vocabulary.py app/services/recommendation_service.py app/services/pairing_service.py tests/test_garment_vocabulary.py
# All checks passed!

uvx ruff format --check <same files>
# 4 files already formatted

The DB-backed tests in test_recommendation_service.py were not run (they require Postgres via TEST_DATABASE_URL); only the prompt/template tests in that file were exercised.

Screenshots (if applicable)

N/A — prompt-only change, no UI.

Additional Notes

  • This is a prompt change only: no migration or schema work. The taxonomy in garment_vocabulary.json is untouched, so test_type_lists_agree continues to hold.
  • leggings is a pants subtype in clothing_analysis.txt, not a TYPE, so it is not a member of the derived bottom list (pants, jeans, shorts, skirt). It remains identifiable through subtype tagging.
  • An alternative from the issue was to keep the prompts fully literal and restate the lists. I went with token rendering at load time so the .txt files stay readable while the lists stay derived; happy to switch if reviewers prefer statically literal prompts.
  • Side note from the issue (unrelated, not addressed): recommendation_service.py reads outfit_data.get("layers", {}), but no prompt asks the model to emit a layers key, so that field is always empty.

Summary by Sourcery

Improve outfit recommendations and pairings by making one-piece garments first-class options and keeping prompt garment types aligned with the validated taxonomy.

New Features:

  • Derive outfit prompt garment lists directly from the garment vocabulary taxonomy.
  • Treat separates and one-piece outfits as equal recommendation and pairing silhouettes, including full-body garments covering both top and bottom roles.

Bug Fixes:

  • Preserve mandatory source items and reject pairings whose body-slot coverage becomes invalid after deduplication.

Enhancements:

  • Render recommendation and pairing prompts from vocabulary-backed templates at service load time.

Tests:

  • Add coverage for role-derived garment lists, complete prompt rendering, silhouette vocabulary inclusion, and source preservation during pairing generation.

…ith garment vocabulary, ensuring consistency and avoiding manual updates. Added tests for vocabulary-based role types and prompt rendering.
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for your first pull request to Wardrowbe! Please make sure you've read the CONTRIBUTING guide, especially the PR title format section. A maintainer will review this soon.

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

sourcery-ai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

The PR updates recommendation and pairing prompts to treat separates and one-piece outfits as equal silhouettes, while rendering every garment list from the validated ITEM_ROLE taxonomy to prevent prompt drift. It adds focused coverage for role derivation and prompt rendering; database-backed recommendation tests were not run because they require Postgres.

Sequence diagram for taxonomy-driven outfit prompt loading

sequenceDiagram
    participant Service as Recommendation_or_Pairing_Service
    participant Loader as load_prompt
    participant Renderer as render_outfit_prompt
    participant Vocabulary as ITEM_ROLE_Vocabulary
    Service->>Loader: load_prompt(template)
    Loader-->>Service: Prompt_template
    Service->>Renderer: render_outfit_prompt(Prompt_template)
    loop Each role in ROLES
        Renderer->>Vocabulary: types_for_role(role)
        Vocabulary-->>Renderer: Role_garment_types
        Renderer->>Renderer: Replace_ROLE_TYPES_token
    end
    Renderer-->>Service: Rendered_outfit_prompt
Loading

Flow diagram for equal-weight outfit silhouettes

flowchart LR
    Prompt[Outfit recommendation or pairing prompt] --> Silhouettes{Equal-weight silhouettes}
    Silhouettes --> Separates[Separates: top and bottom roles]
    Silhouettes --> OnePiece[One-piece: full-body garment fills top and bottom roles]
    Separates --> Legwear[Optional legwear]
    OnePiece --> Legwear
Loading

File-Level Changes

Change Details Files
Derive outfit prompt garment lists from the shared role taxonomy at load time.
  • Added role-based type lookup from the garment vocabulary.
  • Added token rendering for all outfit prompt role placeholders.
  • Updated recommendation and pairing services to render templates when loaded.
  • Added tests confirming taxonomy-derived lists and complete token substitution.
backend/app/utils/garment_vocabulary.py
backend/app/services/recommendation_service.py
backend/app/services/pairing_service.py
backend/tests/test_garment_vocabulary.py
Reframe outfit silhouettes so full-body garments are equal alternatives to separates.
  • Replaced separates-first fallback wording with equal separates and one-piece options.
  • Added optional legwear guidance and generalized waist/proportion guidance.
  • Clarified that a full-body garment fills both top and bottom slots.
  • Included all vocabulary-defined garment types, including jumpsuits and expanded base-top types.
backend/app/prompts/recommendation.txt
backend/app/prompts/item_pairing.txt

Assessment against linked issues

Issue Objective Addressed Explanation
#213 Give separates and one-piece outfits equal standing in both recommendation and item-pairing prompts, explicitly including dresses and jumpsuits as one-piece options. ✅
#213 Derive the garment type lists used in both prompts from the ITEM_ROLE vocabulary, covering the relevant top, bottom, one-piece, footwear, and legwear roles rather than relying on narrower hand-written lists. ✅
#213 Allow optional legwear or underlayers alongside each outfit silhouette. ✅

Possibly linked issues


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

@geekygirlsarah
geekygirlsarah marked this pull request as ready for review October 13, 2026 06:59

@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 2 issues

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

## Individual Comments

### Comment 1
<location path="backend/app/services/pairing_service.py" line_range="21" />
<code_context>
 logger = logging.getLogger(__name__)

-PAIRING_PROMPT_TEMPLATE = load_prompt("item_pairing")
+PAIRING_PROMPT_TEMPLATE = render_outfit_prompt(load_prompt("item_pairing"))

 # A pairing is an internally-generated row, or an externally-authored one carrying the
</code_context>
<issue_to_address>
**Pairings omit their source item**

When the source item is a top or bottom and the model selects a full-body garment, `generate_pairings` adds the source to `valid_ids`, but `deduplicate_by_body_slot` processes the multi-slot garment first and removes the source, so the service can persist and return a pairing without its required source item.

Ensure deduplication preserves the source item when resolving body-slot conflicts.
</issue_to_address>

### Comment 2
<location path="backend/tests/test_garment_vocabulary.py" line_range="79-87" />
<code_context>
     assert prompt_types == set(DEFAULT_WASH_INTERVALS)


+def test_types_for_role_is_derived_from_the_vocabulary():
+    assert types_for_role("base_top") == [
+        "shirt", "t-shirt", "top", "sweater", "polo", "blouse", "tank-top",
+    ]  # fmt: skip
+    assert types_for_role("full_body") == ["dress", "jumpsuit"]
+    assert set(types_for_role("bottom")) == {"pants", "jeans", "shorts", "skirt"}
+    assert set(types_for_role("footwear")) == {"shoes", "sneakers", "boots", "sandals"}
+    assert types_for_role("legwear") == ["tights"]
+    assert all(ITEM_ROLE[garment] == "base_top" for garment in types_for_role("base_top"))
+
+
</code_context>
<issue_to_address>
**New garment roles go unlisted**

When a garment is added to `ITEM_ROLE` for one of the tested roles while `types_for_role` keeps returning its old hard-coded list, `test_types_for_role_is_derived_from_the_vocabulary` checks fixed expected lists at lines 80–86, so an implementation that returns those hard-coded lists still passes after a new garment is added to a role. The assertion at line 87 only checks the listed base-top garments’ roles, not that every taxonomy entry is returned.

Compare each role’s result against the corresponding entries derived from `ITEM_ROLE`, so the test fails when a taxonomy addition is omitted.
</issue_to_address>

Sourcery assessment

Approval pending. 2 findings to address first.

Blocking findings: backend/app/services/pairing_service.py:21, backend/tests/test_garment_vocabulary.py:87


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

Comment thread backend/app/services/pairing_service.py
Comment thread backend/tests/test_garment_vocabulary.py Outdated
Sarah Withee and others added 5 commits October 13, 2026 03:20
test_types_for_role_is_derived_from_the_vocabulary pinned fixed per-role lists, so a hard-coded types_for_role would still pass after a garment is added to a role. Compare each role against the entries derived from ITEM_ROLE and assert the per-role lists cover the taxonomy.
@Anyesh
Anyesh merged commit cfbb5a9 into Anyesh:main Oct 16, 2026
11 checks passed
@Anyesh

Anyesh commented Oct 16, 2026

Copy link
Copy Markdown
Owner

Thanks a bunch for your help @geekygirlsarah! 🙏🏽

@geekygirlsarah
geekygirlsarah deleted the 213-full-body-afterthought branch October 16, 2026 22:23
TheBlackSide243 added a commit to TheBlackSide243/wardrowbe that referenced this pull request Oct 18, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

[Feature]: Outfit slot rules treat dress and jumpsuit as a fallback rather than a peer option

2 participants