# ───────────────────────────────────────────────────────────────────────────── # 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.")