Spaces:
Sleeping
Sleeping
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # PakFit Engine v1 | |
| # Weighted, garment-aware fit scoring algorithm for Pakistani Eastern wear | |
| # Author: [Your Name] | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # ββ Measurement weights per garment type βββββββββββββββββββββββββββββββββββββ | |
| # Based on J. and Almirah published size charts | |
| # Weights reflect how hard each measurement is to fix after stitching | |
| WEIGHTS = { | |
| "Kameez": { | |
| "chest": 0.35, # Most important β cannot fix after stitching | |
| "shoulder": 0.20, # Defines silhouette β very hard to fix | |
| "sleeve": 0.18, # Short sleeves cannot be extended | |
| "collar": 0.15, # Visible immediately β hard to adjust | |
| "length": 0.12, # Tailor can shorten cheaply | |
| }, | |
| "Kurta": { | |
| "chest": 0.35, | |
| "shoulder": 0.20, | |
| "sleeve": 0.18, | |
| "collar": 0.15, | |
| "length": 0.12, | |
| }, | |
| "Waistcoat": { | |
| "chest": 0.42, # No sleeves β chest weight increased | |
| "shoulder": 0.28, # No sleeves β shoulder weight increased | |
| "length": 0.30, # Length carries remaining weight | |
| }, | |
| "Shalwar": { | |
| "waist": 0.60, # Primary measurement for Shalwar | |
| "length": 0.40, # Confirmed from J. and Almirah charts | |
| # No chest, hip, or thigh β brands do not publish these | |
| }, | |
| } | |
| # ββ Tolerance thresholds (inches) ββββββββββββββββββββββββββββββββββββββββββββ | |
| # Maximum difference at which a measurement still scores above zero | |
| TOLERANCES = { | |
| "chest": 6.0, | |
| "shoulder": 3.0, | |
| "sleeve": 2.5, | |
| "collar": 2.0, | |
| "length": 4.0, | |
| "waist": 5.0, | |
| } | |
| # ββ Directional sleeve penalty ββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Short sleeves are worse than long β cannot extend after stitching | |
| SLEEVE_SHORT_THRESHOLD = 0.75 # inches shorter than buyer arm | |
| SLEEVE_SHORT_PENALTY = 8.0 # points deducted from FitScore | |
| # ββ Shalwar length formula ββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Calculated from buyer height β buyer never needs to measure this | |
| SHALWAR_LENGTH_RATIO = 0.595 # height_cm Γ 0.595 = ideal length in inches | |
| SHALWAR_STYLE_ADJUSTMENTS = { | |
| "traditional": 1.0, # Falls to ankle β add 1 inch | |
| "churidar": 2.0, # Bunches at ankle β add 2 inches | |
| "trouser": 0.0, # Exact ankle length | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # CORE FUNCTIONS | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def calculate_shalwar_length(height_cm, style="traditional"): | |
| """ | |
| Calculate ideal Shalwar length from buyer height. | |
| Buyer never needs to measure Shalwar length separately. | |
| """ | |
| height_inches = height_cm / 2.54 | |
| base_length = height_inches * SHALWAR_LENGTH_RATIO | |
| adjustment = SHALWAR_STYLE_ADJUSTMENTS.get(style.lower(), 1.0) | |
| return round(base_length + adjustment, 1) | |
| def match_score(body_val, garment_val, measurement): | |
| """ | |
| Compute how closely a garment measurement matches a buyer body measurement. | |
| Returns a score between 0 and 1. | |
| 1.0 = perfect match | |
| 0.0 = difference equals or exceeds tolerance threshold | |
| No ease is added β Pakistani brand charts already include manufacturing ease. | |
| """ | |
| tolerance = TOLERANCES.get(measurement, 4.0) | |
| difference = abs(body_val - garment_val) | |
| score = max(0.0, 1.0 - (difference / tolerance)) | |
| return score | |
| def apply_directional_penalty(buyer, garment_row, base_score): | |
| """ | |
| Apply directional sleeve penalty. | |
| Short sleeves on a Kameez cannot be extended β penalise more heavily. | |
| Long sleeves can be rolled up β no penalty. | |
| """ | |
| penalty = 0.0 | |
| buyer_sleeve = buyer.get("sleeve", None) | |
| garment_sleeve = garment_row.get("sleeve", None) | |
| if buyer_sleeve and garment_sleeve: | |
| shortfall = buyer_sleeve - garment_sleeve | |
| if shortfall > SLEEVE_SHORT_THRESHOLD: | |
| penalty += SLEEVE_SHORT_PENALTY | |
| return base_score - penalty | |
| def fit_score(buyer, garment_row, garment_type): | |
| """ | |
| Compute FitScore for one size option. | |
| Compares buyer body measurements to garment published measurements. | |
| Returns score from 0 to 100. | |
| """ | |
| weights = WEIGHTS.get(garment_type, WEIGHTS["Kameez"]) | |
| raw_score = 0.0 | |
| for measurement, weight in weights.items(): | |
| buyer_val = buyer.get(measurement) | |
| garment_val = garment_row.get(measurement) | |
| # Skip if either value is missing | |
| if buyer_val is None or garment_val is None: | |
| continue | |
| raw_score += weight * match_score(buyer_val, garment_val, measurement) | |
| # Convert to 0-100 scale | |
| score_100 = raw_score * 100.0 | |
| # Apply directional sleeve penalty for Kameez and Kurta | |
| if garment_type in ["Kameez", "Kurta"]: | |
| score_100 = apply_directional_penalty(buyer, garment_row, score_100) | |
| return round(max(0.0, score_100), 1) | |
| def apply_fit_preference_tiebreaker(results, fit_pref, garment_type): | |
| """ | |
| Fit preference tiebreaker β only applied when two sizes score within 5 points. | |
| Fit preference does NOT add inches to measurements. | |
| It only determines which direction to lean when sizes are nearly equal. | |
| slim β prefer smaller garment | |
| loose β prefer larger garment | |
| regular β prefer closest absolute match (default) | |
| """ | |
| if len(results) < 2: | |
| return results | |
| top_score = results[0]["score"] | |
| second_score = results[1]["score"] | |
| # Only apply tiebreaker if scores are within 5 points | |
| if abs(top_score - second_score) > 5.0: | |
| return results | |
| # Primary measurement to use for tiebreaker | |
| primary = "chest" if garment_type != "Shalwar" else "waist" | |
| if fit_pref == "slim": | |
| # Prefer smaller garment | |
| results.sort(key=lambda x: x["row"].get(primary, 99)) | |
| elif fit_pref == "loose": | |
| # Prefer larger garment | |
| results.sort(key=lambda x: -x["row"].get(primary, 0)) | |
| # regular = keep current order (closest absolute match already at top) | |
| return results | |
| def recommend_size(buyer_measurements, size_chart, garment_type, fit_pref="regular"): | |
| """ | |
| Main recommendation function. | |
| Takes buyer body measurements and brand size chart data. | |
| Returns ranked list of sizes with FitScores. | |
| buyer_measurements: dict with keys like chest, waist, shoulder, sleeve, collar, length | |
| size_chart: list of dicts, each representing one size row from the brand chart | |
| garment_type: "Kameez", "Shalwar", "Waistcoat", "Kurta" | |
| fit_pref: "slim", "regular", or "loose" | |
| """ | |
| if not size_chart: | |
| return [] | |
| results = [] | |
| for row in size_chart: | |
| score = fit_score(buyer_measurements, row, garment_type) | |
| results.append({ | |
| "size": row.get("size_label", "Unknown"), | |
| "score": score, | |
| "confidence": f"{score:.0f}%", | |
| "row": row, | |
| }) | |
| # Sort by score β highest first | |
| results.sort(key=lambda x: -x["score"]) | |
| # Apply fit preference tiebreaker | |
| results = apply_fit_preference_tiebreaker(results, fit_pref, garment_type) | |
| return results | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # QUICK TEST β run this file directly to verify the engine works | |
| # python3 engine.py | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if __name__ == "__main__": | |
| print("Testing PakFit Engine v1...\n") | |
| # Real J. Kameez size chart data | |
| j_kameez = [ | |
| {"size_label": "XS", "chest": 22, "shoulder": 17, "sleeve": 23, "collar": 14.5, "length": 39.5}, | |
| {"size_label": "S", "chest": 23, "shoulder": 17.5, "sleeve": 23.5, "collar": 15, "length": 40.75}, | |
| {"size_label": "M", "chest": 24, "shoulder": 18.5, "sleeve": 24.25, "collar": 16, "length": 42.25}, | |
| {"size_label": "L", "chest": 25, "shoulder": 19.5, "sleeve": 25, "collar": 17, "length": 44}, | |
| {"size_label": "XL", "chest": 27, "shoulder": 20.5, "sleeve": 25.5, "collar": 18, "length": 45.25}, | |
| ] | |
| # Test buyer with 24-inch chest (garment measurement) | |
| buyer = { | |
| "chest": 24, | |
| "shoulder": 18.5, | |
| "sleeve": 24, | |
| "collar": 16, | |
| "length": 42, | |
| } | |
| print("Buyer measurements:", buyer) | |
| print("Garment type: Kameez") | |
| print("Fit preference: regular\n") | |
| results = recommend_size(buyer, j_kameez, "Kameez", "regular") | |
| print("Results (ranked by FitScore):") | |
| for r in results: | |
| print(f" Size {r['size']:>3} β FitScore: {r['score']:>5.1f}%") | |
| print(f"\nRecommendation: Size {results[0]['size']} β {results[0]['confidence']} confidence") | |
| # Test Shalwar length calculation | |
| print("\n--- Shalwar length test ---") | |
| height_cm = 175 | |
| length = calculate_shalwar_length(height_cm, "traditional") | |
| print(f"Buyer height: {height_cm}cm β Ideal Shalwar length: {length} inches") | |
| print("\nEngine test complete.") | |