Ali Mohsin commited on
Commit
7669ee7
Β·
1 Parent(s): 1f07471

Update category mappings and enhance outfit templates for better filtering and accuracy

Browse files
Files changed (1) hide show
  1. inference.py +112 -43
inference.py CHANGED
@@ -132,7 +132,7 @@ class InferenceService:
132
  2: "shoes", # shoes, sneakers, boots, footwear
133
  3: "jacket", # jacket, blazer, coat, outerwear
134
  4: "dress", # dress, gown
135
- 5: "pants", # skirt, shorts (map to pants for outfit logic)
136
  6: "shirt", # sweater, hoodie, pullover (map to shirt)
137
  7: "accessory", # watch, ring, necklace, jewelry
138
  8: "accessory", # bag, purse, handbag
@@ -140,7 +140,7 @@ class InferenceService:
140
  10: "accessory", # belt, accessory
141
  11: "kameez", # kameez, kurta, traditional Pakistani shirt
142
  12: "shalwar", # shalwar, traditional Pakistani pants
143
- 13: "peshawari" # Peshawari chappal, traditional Pakistani sandals
144
  }
145
 
146
  predicted_category = category_map.get(indices[0].item(), "other")
@@ -160,20 +160,46 @@ class InferenceService:
160
 
161
  filename_lower = filename.lower()
162
 
163
- # Upper body items
164
- if any(kw in filename_lower for kw in ["shirt", "top", "blouse", "tank", "hoodie", "sweater", "jacket", "blazer", "coat"]):
165
- return "shirt"
166
-
167
- # Bottom items
168
- if any(kw in filename_lower for kw in ["pant", "jean", "short", "skirt", "trouser", "legging", "jogger"]):
 
 
 
 
 
 
 
 
 
 
169
  return "pants"
170
-
171
- # Shoes
172
- if any(kw in filename_lower for kw in ["shoe", "boot", "sneaker", "sandal", "heel", "loafer", "oxford"]):
 
 
 
 
 
 
173
  return "shoes"
 
 
 
 
 
 
 
 
 
 
174
 
175
  # Accessories
176
- if any(kw in filename_lower for kw in ["watch", "ring", "necklace", "bracelet", "bag", "hat", "belt", "scarf"]):
177
  return "accessory"
178
 
179
  # Default fallback
@@ -434,80 +460,85 @@ class InferenceService:
434
  outfit_templates = {
435
  "casual": {
436
  "style": "relaxed, comfortable, everyday",
437
- "preferred_categories": ["tshirt", "jean", "sneaker", "hoodie", "sweatpant", "shirt", "pants", "shoes"],
 
438
  "color_palette": ["neutral", "denim", "white", "black", "gray"],
439
  "accessory_limit": 2,
440
  "weather_modifiers": {
441
- "hot": {"preferred_categories": ["tank", "short", "sandal", "light shirt"]},
442
- "cold": {"preferred_categories": ["hoodie", "sweater", "jacket", "boot"]},
443
- "rain": {"preferred_categories": ["jacket", "boot", "waterproof"]}
444
  },
445
  "occasion_modifiers": {
446
- "business": {"preferred_categories": ["shirt", "pants", "shoes"], "accessory_limit": 3},
447
- "formal": {"preferred_categories": ["shirt", "pants", "shoes"], "accessory_limit": 4}
448
  }
449
  },
450
  "smart_casual": {
451
  "style": "polished but relaxed, business casual",
452
- "preferred_categories": ["shirt", "chino", "loafer", "blazer", "polo", "pants", "shoes"],
 
453
  "color_palette": ["navy", "white", "khaki", "brown", "gray"],
454
  "accessory_limit": 3,
455
  "weather_modifiers": {
456
- "hot": {"preferred_categories": ["polo", "light shirt", "loafer"]},
457
- "cold": {"preferred_categories": ["blazer", "sweater", "boot"]},
458
- "rain": {"preferred_categories": ["blazer", "boot", "umbrella"]}
459
  },
460
  "occasion_modifiers": {
461
- "business": {"preferred_categories": ["shirt", "pants", "shoes"], "accessory_limit": 4},
462
- "formal": {"preferred_categories": ["shirt", "pants", "shoes"], "accessory_limit": 4}
463
  }
464
  },
465
  "formal": {
466
  "style": "professional, elegant, sophisticated",
467
- "preferred_categories": ["blazer", "jacket", "suit jacket", "dress shirt", "dress pant", "oxford", "suit", "shirt", "pants", "shoes"],
 
468
  "color_palette": ["navy", "black", "white", "gray", "charcoal"],
469
  "accessory_limit": 4,
470
  "requires_outerwear": True, # Flag to indicate formal outfits should include jackets
471
  "weather_modifiers": {
472
  "hot": {"preferred_categories": ["light shirt", "light pant", "oxford"], "requires_outerwear": False},
473
- "cold": {"preferred_categories": ["blazer", "suit", "boot"], "requires_outerwear": True},
474
- "rain": {"preferred_categories": ["blazer", "boot", "umbrella"], "requires_outerwear": True}
475
  },
476
  "occasion_modifiers": {
477
- "business": {"preferred_categories": ["shirt", "pants", "shoes"], "accessory_limit": 4, "requires_outerwear": True},
478
- "casual": {"preferred_categories": ["shirt", "pants", "shoes"], "accessory_limit": 3, "requires_outerwear": False}
479
  }
480
  },
481
  "sporty": {
482
  "style": "athletic, active, performance",
483
- "preferred_categories": ["athletic shirt", "jogger", "running shoe", "tank", "legging", "shirt", "pants", "shoes"],
 
484
  "color_palette": ["bright", "neon", "white", "black", "primary colors"],
485
  "accessory_limit": 1,
486
  "weather_modifiers": {
487
- "hot": {"preferred_categories": ["tank", "short", "running shoe"]},
488
  "cold": {"preferred_categories": ["hoodie", "legging", "running shoe"]},
489
  "rain": {"preferred_categories": ["jacket", "running shoe", "cap"]}
490
  },
491
  "occasion_modifiers": {
492
- "business": {"preferred_categories": ["shirt", "pants", "shoes"], "accessory_limit": 2},
493
- "formal": {"preferred_categories": ["shirt", "pants", "shoes"], "accessory_limit": 3}
494
  }
495
  },
496
  "traditional": {
497
  "style": "Pakistani traditional, cultural, ethnic",
498
- "preferred_categories": ["kameez", "kurta", "shalwar", "peshawari", "chappal", "traditional", "ethnic"],
 
499
  "color_palette": ["white", "black", "navy", "maroon", "gold", "green", "traditional colors"],
500
  "accessory_limit": 3,
501
  "requires_traditional": True, # Flag for traditional outfit combinations
502
  "weather_modifiers": {
503
- "hot": {"preferred_categories": ["light kameez", "cotton shalwar", "peshawari chappal"]},
504
- "cold": {"preferred_categories": ["warm kameez", "thick shalwar", "traditional boots"]},
505
  "rain": {"preferred_categories": ["waterproof kameez", "shalwar", "traditional boots"]}
506
  },
507
  "occasion_modifiers": {
508
- "business": {"preferred_categories": ["formal kameez", "shalwar", "peshawari"], "accessory_limit": 2},
509
- "formal": {"preferred_categories": ["elegant kameez", "shalwar", "peshawari"], "accessory_limit": 3},
510
- "casual": {"preferred_categories": ["casual kameez", "shalwar", "chappal"], "accessory_limit": 2}
511
  }
512
  }
513
  }
@@ -545,10 +576,16 @@ class InferenceService:
545
  if constraints.get("requires_outerwear"):
546
  template["requires_outerwear"] = constraints["requires_outerwear"]
547
 
 
 
 
 
548
  # Apply weather modifications
549
  if weather != "any" and weather in template.get("weather_modifiers", {}):
550
  weather_mod = template["weather_modifiers"][weather]
551
  template["preferred_categories"].extend(weather_mod.get("preferred_categories", []))
 
 
552
  if "accessory_limit" in weather_mod:
553
  template["accessory_limit"] = weather_mod["accessory_limit"]
554
 
@@ -556,11 +593,14 @@ class InferenceService:
556
  if occasion in template.get("occasion_modifiers", {}):
557
  occasion_mod = template["occasion_modifiers"][occasion]
558
  template["preferred_categories"].extend(occasion_mod.get("preferred_categories", []))
 
 
559
  if "accessory_limit" in occasion_mod:
560
  template["accessory_limit"] = occasion_mod["accessory_limit"]
561
 
562
  # Remove duplicates and add context info
563
  template["preferred_categories"] = list(set(template["preferred_categories"]))
 
564
  template["context"] = {
565
  "occasion": occasion,
566
  "weather": weather,
@@ -572,6 +612,7 @@ class InferenceService:
572
 
573
  print(f"πŸ” DEBUG: Using template '{template_name}' with context: occasion={occasion}, weather={weather}")
574
  print(f"πŸ” DEBUG: Template categories: {template['preferred_categories']}")
 
575
  print(f"πŸ” DEBUG: Accessory limit: {template['accessory_limit']}")
576
 
577
  # Enhanced category-aware pools with diversity checks
@@ -765,7 +806,7 @@ class InferenceService:
765
  def get_category_type(cat: str) -> str:
766
  """Map category to outfit slot type with comprehensive taxonomy"""
767
  cat_lower = cat.lower().strip()
768
- print(f"πŸ” DEBUG: Mapping category '{cat}' -> '{cat_lower}'")
769
 
770
  # Direct mapping for CLIP-detected categories
771
  if cat_lower == "shirt":
@@ -784,6 +825,16 @@ class InferenceService:
784
  return "bottom" # Shalwar is bottom wear
785
  elif cat_lower == "peshawari":
786
  return "shoe" # Peshawari chappal is footwear
 
 
 
 
 
 
 
 
 
 
787
 
788
  # Upper body items (tops, innerwear)
789
  upper_keywords = [
@@ -1135,8 +1186,15 @@ class InferenceService:
1135
  return False
1136
 
1137
  categories = [get_category_type(cat_str(i)) for i in subset]
 
1138
  category_counts = {}
1139
 
 
 
 
 
 
 
1140
  for cat in categories:
1141
  category_counts[cat] = category_counts.get(cat, 0) + 1
1142
 
@@ -1159,6 +1217,7 @@ class InferenceService:
1159
  def calculate_outfit_penalty(subset: List[int], base_score: float) -> float:
1160
  """Calculate sophisticated penalty-adjusted score with advanced fashion reasoning"""
1161
  categories = [get_category_type(cat_str(i)) for i in subset]
 
1162
  category_counts = {}
1163
 
1164
  for cat in categories:
@@ -1177,9 +1236,15 @@ class InferenceService:
1177
  penalty += -1000.0
1178
 
1179
  # Duplicate core categories: -∞ penalty (fashion rule violation)
 
1180
  core_categories = {"upper", "bottom", "shoe", "outerwear"}
 
 
1181
  for cat in core_categories:
1182
- if category_counts.get(cat, 0) > 1:
 
 
 
1183
  penalty += -1000.0
1184
 
1185
  # 2. Context-specific critical violations
@@ -1231,6 +1296,8 @@ class InferenceService:
1231
  bonus += 0.4 # Complete formal set bonus
1232
  if style_score > 0.7:
1233
  bonus += 0.3 # High style coherence bonus
 
 
1234
 
1235
  # Business outfit bonuses
1236
  elif occasion == "business":
@@ -1250,13 +1317,15 @@ class InferenceService:
1250
 
1251
  # 8. Traditional Pakistani outfit bonuses
1252
  if outfit_style == "traditional":
1253
- traditional_items = [cat for cat in categories if any(traditional in cat.lower() for traditional in ["kameez", "kurta", "shalwar", "peshawari", "chappal"])]
1254
  if len(traditional_items) >= 2:
1255
  bonus += 0.7 # Strong cultural appropriateness bonus
1256
- if len(traditional_items) == 3:
1257
  bonus += 0.4 # Complete traditional set bonus
1258
  if style_score > 0.6:
1259
  bonus += 0.3 # Traditional style coherence
 
 
1260
 
1261
  # 9. Fashion rule compliance bonuses
1262
  # Perfect category distribution
 
132
  2: "shoes", # shoes, sneakers, boots, footwear
133
  3: "jacket", # jacket, blazer, coat, outerwear
134
  4: "dress", # dress, gown
135
+ 5: "shorts", # skirt, shorts (Updated from pants to shorts for better filtering)
136
  6: "shirt", # sweater, hoodie, pullover (map to shirt)
137
  7: "accessory", # watch, ring, necklace, jewelry
138
  8: "accessory", # bag, purse, handbag
 
140
  10: "accessory", # belt, accessory
141
  11: "kameez", # kameez, kurta, traditional Pakistani shirt
142
  12: "shalwar", # shalwar, traditional Pakistani pants
143
+ 13: "sandals" # Peshawari chappal, traditional Pakistani sandals (Updated to sandals)
144
  }
145
 
146
  predicted_category = category_map.get(indices[0].item(), "other")
 
160
 
161
  filename_lower = filename.lower()
162
 
163
+ # Traditional Pakistani Wear
164
+ if any(kw in filename_lower for kw in ["kameez", "kurta", "kurti"]):
165
+ return "kameez"
166
+ if any(kw in filename_lower for kw in ["shalwar", "salwar", "pyjama", "pajama"]):
167
+ return "shalwar"
168
+ if any(kw in filename_lower for kw in ["peshawari", "chappal", "khussa", "kolhapuri"]):
169
+ return "sandals"
170
+
171
+ # Specific Bottoms
172
+ if any(kw in filename_lower for kw in ["short", "shorts", "bermuda"]):
173
+ return "shorts"
174
+ if any(kw in filename_lower for kw in ["jean", "jeans", "denim"]):
175
+ return "jeans"
176
+ if any(kw in filename_lower for kw in ["skirt", "miniskirt"]):
177
+ return "skirt"
178
+ if any(kw in filename_lower for kw in ["pant", "trouser", "slack", "chino", "legging", "jogger"]):
179
  return "pants"
180
+
181
+ # Specific Footwear
182
+ if any(kw in filename_lower for kw in ["sandal", "flip flop", "slide", "slipper"]):
183
+ return "sandals"
184
+ if any(kw in filename_lower for kw in ["sneaker", "trainer", "runner", "athletic shoe"]):
185
+ return "sneakers"
186
+ if any(kw in filename_lower for kw in ["boot", "bootie"]):
187
+ return "boots"
188
+ if any(kw in filename_lower for kw in ["shoe", "heel", "loafer", "oxford", "pump", "flat"]):
189
  return "shoes"
190
+
191
+ # Specific Tops/Outerwear
192
+ if any(kw in filename_lower for kw in ["waistcoat", "vest"]):
193
+ return "waistcoat"
194
+ if any(kw in filename_lower for kw in ["blazer", "suit jacket", "coat", "jacket"]):
195
+ return "jacket"
196
+ if any(kw in filename_lower for kw in ["hoodie", "sweatshirt"]):
197
+ return "hoodie"
198
+ if any(kw in filename_lower for kw in ["shirt", "top", "blouse", "tank", "tee", "t-shirt", "polo", "sweater", "cardigan"]):
199
+ return "shirt"
200
 
201
  # Accessories
202
+ if any(kw in filename_lower for kw in ["watch", "ring", "necklace", "bracelet", "bag", "hat", "belt", "scarf", "tie", "pocket square"]):
203
  return "accessory"
204
 
205
  # Default fallback
 
460
  outfit_templates = {
461
  "casual": {
462
  "style": "relaxed, comfortable, everyday",
463
+ "preferred_categories": ["tshirt", "jean", "sneaker", "hoodie", "sweatpant", "shirt", "pants", "shoes", "shorts", "jeans", "sneakers"],
464
+ "excluded_categories": ["waistcoat", "suit jacket", "dress pant", "oxford"],
465
  "color_palette": ["neutral", "denim", "white", "black", "gray"],
466
  "accessory_limit": 2,
467
  "weather_modifiers": {
468
+ "hot": {"preferred_categories": ["tank", "shorts", "sandals", "light shirt"], "excluded_categories": ["hoodie", "sweater", "jacket", "boots"]},
469
+ "cold": {"preferred_categories": ["hoodie", "sweater", "jacket", "boots"], "excluded_categories": ["shorts", "sandals", "tank"]},
470
+ "rain": {"preferred_categories": ["jacket", "boots", "waterproof"], "excluded_categories": ["sandals", "suede"]}
471
  },
472
  "occasion_modifiers": {
473
+ "business": {"preferred_categories": ["shirt", "pants", "shoes", "blazer"], "excluded_categories": ["shorts", "sandals", "tank", "sweatpant", "hoodie", "legging"], "accessory_limit": 3},
474
+ "formal": {"preferred_categories": ["shirt", "pants", "shoes", "blazer"], "excluded_categories": ["shorts", "sandals", "sneakers", "jeans", "tshirt"], "accessory_limit": 4}
475
  }
476
  },
477
  "smart_casual": {
478
  "style": "polished but relaxed, business casual",
479
+ "preferred_categories": ["shirt", "chino", "loafer", "blazer", "polo", "pants", "shoes", "jeans", "boots"],
480
+ "excluded_categories": ["shorts", "sandals", "tank", "sweatpant", "hoodie", "athletic"],
481
  "color_palette": ["navy", "white", "khaki", "brown", "gray"],
482
  "accessory_limit": 3,
483
  "weather_modifiers": {
484
+ "hot": {"preferred_categories": ["polo", "light shirt", "loafer"], "excluded_categories": ["boots", "heavy jacket"]},
485
+ "cold": {"preferred_categories": ["blazer", "sweater", "boots"], "excluded_categories": ["loafer"]},
486
+ "rain": {"preferred_categories": ["blazer", "boots", "umbrella"], "excluded_categories": ["suede"]}
487
  },
488
  "occasion_modifiers": {
489
+ "business": {"preferred_categories": ["shirt", "pants", "shoes", "blazer"], "excluded_categories": ["jeans", "sneakers"], "accessory_limit": 4},
490
+ "formal": {"preferred_categories": ["shirt", "pants", "shoes", "blazer", "suit"], "excluded_categories": ["jeans", "sneakers", "polo"], "accessory_limit": 4}
491
  }
492
  },
493
  "formal": {
494
  "style": "professional, elegant, sophisticated",
495
+ "preferred_categories": ["blazer", "jacket", "suit jacket", "dress shirt", "dress pant", "oxford", "suit", "shirt", "pants", "shoes", "waistcoat"],
496
+ "excluded_categories": ["shorts", "sandals", "sneakers", "jeans", "tshirt", "hoodie", "sweatpant", "tank", "legging"],
497
  "color_palette": ["navy", "black", "white", "gray", "charcoal"],
498
  "accessory_limit": 4,
499
  "requires_outerwear": True, # Flag to indicate formal outfits should include jackets
500
  "weather_modifiers": {
501
  "hot": {"preferred_categories": ["light shirt", "light pant", "oxford"], "requires_outerwear": False},
502
+ "cold": {"preferred_categories": ["blazer", "suit", "boots", "waistcoat"], "requires_outerwear": True},
503
+ "rain": {"preferred_categories": ["blazer", "boots", "umbrella"], "requires_outerwear": True}
504
  },
505
  "occasion_modifiers": {
506
+ "business": {"preferred_categories": ["shirt", "pants", "shoes", "waistcoat"], "accessory_limit": 4, "requires_outerwear": True},
507
+ "casual": {"preferred_categories": ["shirt", "pants", "shoes", "blazer"], "accessory_limit": 3, "requires_outerwear": False}
508
  }
509
  },
510
  "sporty": {
511
  "style": "athletic, active, performance",
512
+ "preferred_categories": ["athletic shirt", "jogger", "running shoe", "tank", "legging", "shirt", "pants", "shoes", "sneakers", "shorts", "hoodie"],
513
+ "excluded_categories": ["blazer", "suit", "dress pant", "oxford", "loafer", "waistcoat", "jeans", "sandals"],
514
  "color_palette": ["bright", "neon", "white", "black", "primary colors"],
515
  "accessory_limit": 1,
516
  "weather_modifiers": {
517
+ "hot": {"preferred_categories": ["tank", "shorts", "running shoe"]},
518
  "cold": {"preferred_categories": ["hoodie", "legging", "running shoe"]},
519
  "rain": {"preferred_categories": ["jacket", "running shoe", "cap"]}
520
  },
521
  "occasion_modifiers": {
522
+ "business": {"preferred_categories": ["shirt", "pants", "shoes"], "excluded_categories": ["tank", "shorts", "legging"], "accessory_limit": 2},
523
+ "formal": {"preferred_categories": ["shirt", "pants", "shoes"], "excluded_categories": ["tank", "shorts", "legging", "hoodie"], "accessory_limit": 3}
524
  }
525
  },
526
  "traditional": {
527
  "style": "Pakistani traditional, cultural, ethnic",
528
+ "preferred_categories": ["kameez", "kurta", "shalwar", "peshawari", "chappal", "traditional", "ethnic", "waistcoat", "sandals"],
529
+ "excluded_categories": ["shorts", "jeans", "sneakers", "hoodie", "tank", "suit", "tie"],
530
  "color_palette": ["white", "black", "navy", "maroon", "gold", "green", "traditional colors"],
531
  "accessory_limit": 3,
532
  "requires_traditional": True, # Flag for traditional outfit combinations
533
  "weather_modifiers": {
534
+ "hot": {"preferred_categories": ["light kameez", "cotton shalwar", "peshawari chappal", "sandals"]},
535
+ "cold": {"preferred_categories": ["warm kameez", "thick shalwar", "traditional boots", "waistcoat", "shawl"]},
536
  "rain": {"preferred_categories": ["waterproof kameez", "shalwar", "traditional boots"]}
537
  },
538
  "occasion_modifiers": {
539
+ "business": {"preferred_categories": ["formal kameez", "shalwar", "peshawari", "waistcoat"], "accessory_limit": 2},
540
+ "formal": {"preferred_categories": ["elegant kameez", "shalwar", "peshawari", "waistcoat"], "accessory_limit": 3},
541
+ "casual": {"preferred_categories": ["casual kameez", "shalwar", "chappal", "sandals"], "accessory_limit": 2}
542
  }
543
  }
544
  }
 
576
  if constraints.get("requires_outerwear"):
577
  template["requires_outerwear"] = constraints["requires_outerwear"]
578
 
579
+ # Initialize excluded categories if not present
580
+ if "excluded_categories" not in template:
581
+ template["excluded_categories"] = []
582
+
583
  # Apply weather modifications
584
  if weather != "any" and weather in template.get("weather_modifiers", {}):
585
  weather_mod = template["weather_modifiers"][weather]
586
  template["preferred_categories"].extend(weather_mod.get("preferred_categories", []))
587
+ if "excluded_categories" in weather_mod:
588
+ template["excluded_categories"].extend(weather_mod["excluded_categories"])
589
  if "accessory_limit" in weather_mod:
590
  template["accessory_limit"] = weather_mod["accessory_limit"]
591
 
 
593
  if occasion in template.get("occasion_modifiers", {}):
594
  occasion_mod = template["occasion_modifiers"][occasion]
595
  template["preferred_categories"].extend(occasion_mod.get("preferred_categories", []))
596
+ if "excluded_categories" in occasion_mod:
597
+ template["excluded_categories"].extend(occasion_mod["excluded_categories"])
598
  if "accessory_limit" in occasion_mod:
599
  template["accessory_limit"] = occasion_mod["accessory_limit"]
600
 
601
  # Remove duplicates and add context info
602
  template["preferred_categories"] = list(set(template["preferred_categories"]))
603
+ template["excluded_categories"] = list(set(template["excluded_categories"]))
604
  template["context"] = {
605
  "occasion": occasion,
606
  "weather": weather,
 
612
 
613
  print(f"πŸ” DEBUG: Using template '{template_name}' with context: occasion={occasion}, weather={weather}")
614
  print(f"πŸ” DEBUG: Template categories: {template['preferred_categories']}")
615
+ print(f"πŸ” DEBUG: Excluded categories: {template['excluded_categories']}")
616
  print(f"πŸ” DEBUG: Accessory limit: {template['accessory_limit']}")
617
 
618
  # Enhanced category-aware pools with diversity checks
 
806
  def get_category_type(cat: str) -> str:
807
  """Map category to outfit slot type with comprehensive taxonomy"""
808
  cat_lower = cat.lower().strip()
809
+ # print(f"πŸ” DEBUG: Mapping category '{cat}' -> '{cat_lower}'")
810
 
811
  # Direct mapping for CLIP-detected categories
812
  if cat_lower == "shirt":
 
825
  return "bottom" # Shalwar is bottom wear
826
  elif cat_lower == "peshawari":
827
  return "shoe" # Peshawari chappal is footwear
828
+ elif cat_lower == "shorts":
829
+ return "bottom"
830
+ elif cat_lower == "sandals":
831
+ return "shoe"
832
+ elif cat_lower == "sneakers":
833
+ return "shoe"
834
+ elif cat_lower == "jeans":
835
+ return "bottom"
836
+ elif cat_lower == "waistcoat":
837
+ return "outerwear"
838
 
839
  # Upper body items (tops, innerwear)
840
  upper_keywords = [
 
1186
  return False
1187
 
1188
  categories = [get_category_type(cat_str(i)) for i in subset]
1189
+ raw_categories = [cat_str(i) for i in subset]
1190
  category_counts = {}
1191
 
1192
+ # Check for excluded categories
1193
+ excluded = template.get("excluded_categories", [])
1194
+ for cat in raw_categories:
1195
+ if any(ex in cat for ex in excluded):
1196
+ return False
1197
+
1198
  for cat in categories:
1199
  category_counts[cat] = category_counts.get(cat, 0) + 1
1200
 
 
1217
  def calculate_outfit_penalty(subset: List[int], base_score: float) -> float:
1218
  """Calculate sophisticated penalty-adjusted score with advanced fashion reasoning"""
1219
  categories = [get_category_type(cat_str(i)) for i in subset]
1220
+ raw_categories = [cat_str(i) for i in subset]
1221
  category_counts = {}
1222
 
1223
  for cat in categories:
 
1236
  penalty += -1000.0
1237
 
1238
  # Duplicate core categories: -∞ penalty (fashion rule violation)
1239
+ # EXCEPTION: Allow multiple outerwear if one is a waistcoat (3-piece suit)
1240
  core_categories = {"upper", "bottom", "shoe", "outerwear"}
1241
+ has_waistcoat = any("waistcoat" in c for c in raw_categories)
1242
+
1243
  for cat in core_categories:
1244
+ count = category_counts.get(cat, 0)
1245
+ if cat == "outerwear" and has_waistcoat and count <= 2:
1246
+ continue # Allow waistcoat + jacket
1247
+ if count > 1:
1248
  penalty += -1000.0
1249
 
1250
  # 2. Context-specific critical violations
 
1296
  bonus += 0.4 # Complete formal set bonus
1297
  if style_score > 0.7:
1298
  bonus += 0.3 # High style coherence bonus
1299
+ if has_waistcoat and category_counts.get("outerwear", 0) == 2:
1300
+ bonus += 0.5 # 3-piece suit bonus
1301
 
1302
  # Business outfit bonuses
1303
  elif occasion == "business":
 
1317
 
1318
  # 8. Traditional Pakistani outfit bonuses
1319
  if outfit_style == "traditional":
1320
+ traditional_items = [cat for cat in raw_categories if any(traditional in cat for traditional in ["kameez", "kurta", "shalwar", "peshawari", "chappal", "waistcoat"])]
1321
  if len(traditional_items) >= 2:
1322
  bonus += 0.7 # Strong cultural appropriateness bonus
1323
+ if len(traditional_items) >= 3:
1324
  bonus += 0.4 # Complete traditional set bonus
1325
  if style_score > 0.6:
1326
  bonus += 0.3 # Traditional style coherence
1327
+ if has_waistcoat:
1328
+ bonus += 0.3 # Waistcoat with traditional wear
1329
 
1330
  # 9. Fashion rule compliance bonuses
1331
  # Perfect category distribution