STRONG = 3 MEDIUM = 2 WEAK = 1 HAIR_TYPE_WEIGHT = 1.4 FEMALE_DIMS = {"layers", "updo", "curtain_fringe"} def _base_scores(gender): scores = { "volume_top": 0, "volume_sides": 0, "longer_hair": 0, "fringe": 0, "clean_lines": 0, "textured_top": 0, "soft_texture": 0, } if gender == "Woman": scores.update({"layers": 0, "updo": 0, "curtain_fringe": 0}) else: scores["short_sides"] = 0 return scores def apply_rules(traits, gender="Man"): scores = _base_scores(gender) is_female = gender == "Woman" if traits.get("face_length") == "long": scores["volume_sides"] += STRONG scores["fringe"] += STRONG scores["volume_top"] -= MEDIUM scores["longer_hair"] -= MEDIUM if not is_female else WEAK scores["clean_lines"] -= WEAK if is_female: scores["curtain_fringe"] += MEDIUM scores["layers"] += MEDIUM scores["updo"] -= MEDIUM elif traits.get("face_length") == "short": scores["volume_top"] += STRONG scores["longer_hair"] += MEDIUM if not is_female else STRONG scores["fringe"] -= MEDIUM scores["volume_sides"] -= WEAK if is_female: scores["updo"] += MEDIUM if traits.get("jaw") == "wide": scores["soft_texture"] += STRONG scores["longer_hair"] += WEAK if not is_female else MEDIUM scores["clean_lines"] -= MEDIUM if is_female: scores["layers"] += MEDIUM scores["curtain_fringe"] += MEDIUM scores["volume_sides"] -= MEDIUM else: scores["volume_top"] += MEDIUM scores["short_sides"] -= STRONG elif traits.get("jaw") == "narrow": scores["volume_sides"] += MEDIUM if not is_female else STRONG scores["clean_lines"] += WEAK scores["soft_texture"] -= WEAK if is_female: scores["layers"] += MEDIUM else: scores["short_sides"] += STRONG if traits.get("jaw_height") == "high": scores["longer_hair"] += MEDIUM scores["volume_top"] -= MEDIUM if is_female: scores["layers"] += WEAK else: scores["soft_texture"] += WEAK scores["short_sides"] -= WEAK elif traits.get("jaw_height") == "low": scores["volume_top"] += MEDIUM scores["fringe"] += WEAK scores["longer_hair"] -= WEAK if traits.get("eyes") == "wide": scores["clean_lines"] += MEDIUM scores["volume_top"] += MEDIUM if not is_female else WEAK scores["volume_sides"] -= WEAK if is_female: scores["curtain_fringe"] -= WEAK elif traits.get("eyes") == "close": scores["volume_sides"] += MEDIUM scores["fringe"] -= MEDIUM if is_female: scores["curtain_fringe"] += STRONG else: scores["soft_texture"] += WEAK scores["clean_lines"] -= WEAK if traits.get("eye_openness") == "narrow": scores["volume_top"] += MEDIUM scores["fringe"] -= STRONG if is_female: scores["curtain_fringe"] -= MEDIUM else: scores["clean_lines"] += WEAK elif traits.get("eye_openness") == "open": scores["fringe"] += MEDIUM scores["textured_top"] += WEAK if is_female: scores["curtain_fringe"] += MEDIUM if traits.get("lips") == "wide": scores["soft_texture"] += MEDIUM scores["clean_lines"] -= WEAK if is_female: scores["layers"] += WEAK else: scores["volume_top"] += WEAK elif traits.get("lips") == "narrow": scores["clean_lines"] += MEDIUM if is_female: scores["updo"] += WEAK else: scores["short_sides"] += WEAK scores["soft_texture"] -= WEAK if traits.get("nose") == "upper-dominant": scores["fringe"] += MEDIUM if not is_female else STRONG scores["volume_top"] -= STRONG if is_female: scores["curtain_fringe"] += MEDIUM else: scores["volume_sides"] += WEAK elif traits.get("nose") == "lower-dominant": scores["volume_top"] += STRONG scores["fringe"] -= MEDIUM if is_female: scores["updo"] += WEAK else: scores["longer_hair"] += WEAK if traits.get("lower_face") == "long": scores["longer_hair"] += MEDIUM scores["soft_texture"] += MEDIUM if is_female: scores["layers"] += WEAK else: scores["short_sides"] -= WEAK elif traits.get("lower_face") == "short": scores["clean_lines"] += MEDIUM scores["longer_hair"] -= WEAK if not is_female: scores["short_sides"] += MEDIUM if traits.get("chin") == "prominent": scores["longer_hair"] += MEDIUM if not is_female else STRONG scores["soft_texture"] += MEDIUM if not is_female else STRONG scores["clean_lines"] -= WEAK if not is_female else MEDIUM if is_female: scores["layers"] += MEDIUM else: scores["textured_top"] += STRONG scores["short_sides"] -= WEAK elif traits.get("chin") == "recessed": scores["volume_top"] += STRONG scores["clean_lines"] += WEAK scores["longer_hair"] -= WEAK if is_female: scores["updo"] += WEAK else: scores["short_sides"] += MEDIUM scores["textured_top"] -= MEDIUM if traits.get("facial_thirds") == "lower_dominant": scores["volume_top"] += STRONG scores["fringe"] -= MEDIUM scores["longer_hair"] -= WEAK if is_female: scores["updo"] += MEDIUM elif traits.get("facial_thirds") == "middle_dominant": scores["fringe"] += MEDIUM scores["volume_top"] -= MEDIUM if is_female: scores["curtain_fringe"] += MEDIUM else: scores["volume_sides"] += WEAK if traits.get("forehead") == "high": scores["fringe"] += STRONG scores["volume_top"] -= MEDIUM if is_female: scores["curtain_fringe"] += MEDIUM scores["updo"] -= MEDIUM elif traits.get("forehead") == "low": scores["fringe"] -= STRONG if is_female: scores["updo"] += MEDIUM scores["curtain_fringe"] -= MEDIUM else: scores["volume_top"] += WEAK scores["textured_top"] += WEAK if traits.get("thirds_balance") == "imbalanced": scores["soft_texture"] += MEDIUM scores["clean_lines"] -= MEDIUM if is_female: scores["layers"] += MEDIUM if traits.get("hair_type") == "curly": scores["soft_texture"] += MEDIUM * HAIR_TYPE_WEIGHT scores["textured_top"] += MEDIUM * HAIR_TYPE_WEIGHT scores["clean_lines"] -= WEAK * HAIR_TYPE_WEIGHT scores["longer_hair"] -= WEAK * HAIR_TYPE_WEIGHT elif traits.get("hair_type") in ("straight", "wavy"): scores["clean_lines"] += WEAK * HAIR_TYPE_WEIGHT scores["longer_hair"] += WEAK * HAIR_TYPE_WEIGHT elif traits.get("hair_type") == "coily": scores["soft_texture"] += STRONG * HAIR_TYPE_WEIGHT scores["textured_top"] += STRONG * HAIR_TYPE_WEIGHT scores["volume_top"] += MEDIUM * HAIR_TYPE_WEIGHT scores["clean_lines"] -= MEDIUM * HAIR_TYPE_WEIGHT scores["longer_hair"] -= MEDIUM * HAIR_TYPE_WEIGHT if traits.get("hairline") == "receding": scores["textured_top"] += WEAK scores["fringe"] -= STRONG scores["volume_top"] -= WEAK if not is_female: scores["short_sides"] += WEAK elif traits.get("hairline") == "uneven": scores["fringe"] -= MEDIUM scores["soft_texture"] += WEAK scores = _apply_interaction_rules(scores, traits, gender) scores = _apply_symmetry_modulation(scores, traits, gender) scores = _apply_hard_constraints(scores, traits) scores = clamp_scores(scores) return scores def _apply_interaction_rules(scores, traits, gender): is_female = gender == "Woman" if traits.get("face_length") == "long" and traits.get("forehead") == "high": scores["fringe"] += MEDIUM if not is_female else WEAK scores["volume_sides"] += WEAK scores["volume_top"] -= STRONG if not is_female else MEDIUM if is_female: scores["curtain_fringe"] += WEAK scores["updo"] -= WEAK else: scores["longer_hair"] -= MEDIUM scores["clean_lines"] -= WEAK if traits.get("face_length") == "long" and traits.get("jaw") == "narrow": scores["volume_sides"] += WEAK if not is_female else MEDIUM if is_female: scores["layers"] += MEDIUM scores["curtain_fringe"] += WEAK scores["volume_top"] -= WEAK scores["updo"] -= WEAK else: scores["short_sides"] -= WEAK if traits.get("face_length") == "short" and traits.get("jaw") == "narrow": scores["volume_top"] += MEDIUM scores["clean_lines"] += WEAK scores["fringe"] -= MEDIUM scores["volume_sides"] -= WEAK if is_female: scores["updo"] += MEDIUM else: scores["short_sides"] += MEDIUM if traits.get("jaw") == "wide" and traits.get("chin") == "prominent": scores["soft_texture"] += STRONG scores["textured_top"] += MEDIUM scores["longer_hair"] += WEAK if not is_female else MEDIUM scores["short_sides"] -= MEDIUM scores["clean_lines"] -= MEDIUM if is_female: scores["layers"] += MEDIUM scores["curtain_fringe"] += WEAK scores["updo"] -= WEAK if traits.get("jaw") == "narrow" and traits.get("chin") == "recessed": scores["clean_lines"] += MEDIUM if not is_female else WEAK scores["volume_top"] += WEAK scores["soft_texture"] -= WEAK if is_female: scores["longer_hair"] -= WEAK if traits.get("face_length") != "long" and traits.get("forehead") != "high": scores["updo"] += WEAK else: scores["short_sides"] += MEDIUM if traits.get("eyes") == "close" and traits.get("forehead") == "low": scores["volume_sides"] += MEDIUM scores["volume_top"] += WEAK scores["soft_texture"] += WEAK scores["fringe"] -= STRONG if is_female: scores["curtain_fringe"] += MEDIUM if traits.get("eyes") == "wide" and traits.get("forehead") == "high": scores["fringe"] += MEDIUM if not is_female else WEAK scores["volume_top"] -= MEDIUM if not is_female else WEAK if traits.get("eye_openness") == "narrow" and traits.get("face_length") == "long": scores["volume_sides"] += WEAK if not is_female else MEDIUM scores["fringe"] -= MEDIUM if is_female: scores["layers"] += WEAK scores["curtain_fringe"] -= WEAK if traits.get("eye_openness") == "narrow" and traits.get("forehead") == "high": scores["fringe"] += MEDIUM scores["volume_top"] -= WEAK if is_female: scores["curtain_fringe"] += WEAK scores["soft_texture"] += WEAK else: scores["textured_top"] += WEAK if (traits.get("facial_thirds") == "lower_dominant" and traits.get("chin") == "prominent"): scores["soft_texture"] += MEDIUM scores["clean_lines"] -= WEAK if is_female: scores["layers"] += MEDIUM else: scores["textured_top"] += MEDIUM if traits.get("face_length") != "long": scores["volume_top"] += MEDIUM if is_female: scores["updo"] += WEAK else: scores["volume_sides"] += MEDIUM if is_female: scores["curtain_fringe"] += WEAK if (traits.get("facial_thirds") == "middle_dominant" and traits.get("face_length") == "long"): scores["volume_top"] -= WEAK scores["volume_sides"] += WEAK if is_female: scores["fringe"] += WEAK scores["curtain_fringe"] += WEAK else: scores["fringe"] += WEAK if traits.get("symmetry") == "low" and traits.get("jaw") == "wide": scores["soft_texture"] += WEAK scores["clean_lines"] -= WEAK if is_female: scores["layers"] += MEDIUM scores["updo"] -= WEAK else: scores["textured_top"] += MEDIUM scores["short_sides"] -= WEAK return scores def _apply_symmetry_modulation(scores, traits, gender): is_female = gender == "Woman" TEXTURE_KEYS = ["soft_texture", "volume_sides", "fringe", "textured_top"] CLEAN_KEYS = ["clean_lines"] if is_female: TEXTURE_KEYS = ["soft_texture", "volume_sides", "fringe", "curtain_fringe", "layers"] CLEAN_KEYS = ["clean_lines", "updo"] else: CLEAN_KEYS = ["clean_lines", "short_sides"] if traits.get("symmetry") == "low": for k in TEXTURE_KEYS: scores[k] = scores[k] * 1.3 + 1 for k in CLEAN_KEYS: scores[k] = scores[k] * 0.7 elif traits.get("symmetry") == "high": for k in CLEAN_KEYS: scores[k] = scores[k] * 1.2 scores["clean_lines"] += WEAK for k in TEXTURE_KEYS: scores[k] = scores[k] * 0.9 return scores def _apply_hard_constraints(scores, traits): if traits.get("hairline") == "receding": scores["fringe"] = min(scores["fringe"], -2) return scores def clamp_scores(scores, min_score=-5, max_score=10): if scores is None: raise ValueError("clamp_scores received None") return { k: max(min_score, min(max_score, v)) for k, v in scores.items() }