Spaces:
Sleeping
Sleeping
| """ | |
| PriceOye Phone Recommendation AI | |
| ================================= | |
| Scoring Engine β Professional ML Benchmark System | |
| Benchmarks (hidden from user, used internally only): | |
| - Android: Samsung Galaxy S26 Ultra | |
| - iOS: iPhone 17 Pro Max | |
| Each phone is scored across 10 categories Γ multiple sub-dimensions. | |
| Final score = weighted sum of category averages per user priority profile. | |
| """ | |
| from dataclasses import dataclass, field | |
| from typing import Optional | |
| import math | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # DATA STRUCTURES | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| class SubDimension: | |
| score: float # 0.0 β 10.0 | |
| note: str # Human-readable justification | |
| class PhoneDimensions: | |
| """All benchmark sub-dimensions for a phone.""" | |
| # Camera (9 sub-dims) | |
| camera_main_sensor: SubDimension | |
| camera_aperture: SubDimension | |
| camera_optical_zoom: SubDimension | |
| camera_ultrawide: SubDimension | |
| camera_video: SubDimension | |
| camera_night_mode: SubDimension | |
| camera_front: SubDimension | |
| camera_lens_quality: SubDimension | |
| camera_ois: SubDimension | |
| # Performance (5 sub-dims) | |
| perf_cpu: SubDimension | |
| perf_gpu: SubDimension | |
| perf_ram_type: SubDimension | |
| perf_thermal: SubDimension | |
| perf_ai_chip: SubDimension | |
| # Display (6 sub-dims) | |
| disp_resolution: SubDimension | |
| disp_brightness: SubDimension | |
| disp_color_accuracy: SubDimension | |
| disp_refresh_rate: SubDimension | |
| disp_technology: SubDimension | |
| disp_touch_sampling: SubDimension | |
| # Battery (4 sub-dims) | |
| batt_capacity: SubDimension | |
| batt_real_world_sot: SubDimension | |
| batt_efficiency: SubDimension | |
| batt_wireless: SubDimension | |
| # Charging (4 sub-dims) | |
| charg_wired_speed: SubDimension | |
| charg_wireless_speed: SubDimension | |
| charg_reverse: SubDimension | |
| charg_inbox_charger: SubDimension | |
| # RAM (3 sub-dims) | |
| ram_capacity: SubDimension | |
| ram_type: SubDimension | |
| ram_os_management: SubDimension | |
| # Storage (3 sub-dims) | |
| stor_capacity: SubDimension | |
| stor_speed: SubDimension | |
| stor_expandable: SubDimension | |
| # Build (4 sub-dims) | |
| build_frame: SubDimension | |
| build_ip_rating: SubDimension | |
| build_front_glass: SubDimension | |
| build_form_factor: SubDimension | |
| # Software (4 sub-dims) | |
| soft_update_policy: SubDimension | |
| soft_bloatware: SubDimension | |
| soft_ai_features: SubDimension | |
| soft_ecosystem: SubDimension | |
| # Audio (3 sub-dims) | |
| audio_speakers: SubDimension | |
| audio_headphone_jack: SubDimension | |
| audio_bt_codecs: SubDimension | |
| class Phone: | |
| id: str | |
| name: str | |
| brand: str | |
| os: str # 'android' | 'ios' | |
| price_pkr: int | |
| price_label: str | |
| priceoye_url: str | |
| whatmobile_url: str | |
| emoji: str | |
| tags: list[str] | |
| highlights: dict[str, str] | |
| dims: PhoneDimensions | |
| available_on_priceoye: bool = True | |
| class UserPreferences: | |
| budget: Optional[int] = None | |
| os_preference: Optional[str] = None # 'android' | 'ios' | 'any' | |
| priority: Optional[str] = None # see WEIGHT_PROFILES keys | |
| brand_preference: Optional[str] = None # e.g. 'Samsung', 'Apple' | |
| brand_avoid: list[str] = field(default_factory=list) | |
| session_memory: dict = field(default_factory=dict) # cross-turn memory | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # CATEGORY AVERAGES | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| CATEGORY_GROUPS = { | |
| "camera": [ | |
| "camera_main_sensor", "camera_aperture", "camera_optical_zoom", | |
| "camera_ultrawide", "camera_video", "camera_night_mode", | |
| "camera_front", "camera_lens_quality", "camera_ois", | |
| ], | |
| "performance": [ | |
| "perf_cpu", "perf_gpu", "perf_ram_type", | |
| "perf_thermal", "perf_ai_chip", | |
| ], | |
| "display": [ | |
| "disp_resolution", "disp_brightness", "disp_color_accuracy", | |
| "disp_refresh_rate", "disp_technology", "disp_touch_sampling", | |
| ], | |
| "battery": [ | |
| "batt_capacity", "batt_real_world_sot", | |
| "batt_efficiency", "batt_wireless", | |
| ], | |
| "charging": [ | |
| "charg_wired_speed", "charg_wireless_speed", | |
| "charg_reverse", "charg_inbox_charger", | |
| ], | |
| "ram": ["ram_capacity", "ram_type", "ram_os_management"], | |
| "storage": ["stor_capacity", "stor_speed", "stor_expandable"], | |
| "build": [ | |
| "build_frame", "build_ip_rating", | |
| "build_front_glass", "build_form_factor", | |
| ], | |
| "software": [ | |
| "soft_update_policy", "soft_bloatware", | |
| "soft_ai_features", "soft_ecosystem", | |
| ], | |
| "audio": [ | |
| "audio_speakers", "audio_headphone_jack", "audio_bt_codecs", | |
| ], | |
| } | |
| def get_category_scores(phone: Phone) -> dict[str, float]: | |
| """Return average score per category.""" | |
| scores = {} | |
| for cat, fields in CATEGORY_GROUPS.items(): | |
| vals = [getattr(phone.dims, f).score for f in fields] | |
| scores[cat] = round(sum(vals) / len(vals), 2) | |
| return scores | |
| def get_sub_scores(phone: Phone, category: str) -> list[dict]: | |
| """Return detailed sub-dimension breakdown for a category.""" | |
| fields = CATEGORY_GROUPS.get(category, []) | |
| result = [] | |
| for f in fields: | |
| dim: SubDimension = getattr(phone.dims, f) | |
| label = f.replace(f.split("_")[0] + "_", "").replace("_", " ").title() | |
| result.append({ | |
| "label": label, | |
| "score": dim.score, | |
| "note": dim.note, | |
| }) | |
| return result | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # WEIGHT PROFILES | |
| # Must sum to 1.0 per profile | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| WEIGHT_PROFILES: dict[str, dict[str, float]] = { | |
| "photography": { | |
| "camera": 0.45, "display": 0.15, "storage": 0.10, | |
| "performance": 0.10, "battery": 0.06, "build": 0.05, | |
| "software": 0.04, "charging": 0.02, "ram": 0.02, "audio": 0.01, | |
| }, | |
| "gaming": { | |
| "performance": 0.30, "display": 0.20, "ram": 0.15, | |
| "battery": 0.12, "charging": 0.08, "audio": 0.05, | |
| "build": 0.05, "camera": 0.03, "software": 0.01, "storage": 0.01, | |
| }, | |
| "battery": { | |
| "battery": 0.30, "charging": 0.25, "performance": 0.15, | |
| "display": 0.10, "ram": 0.07, "camera": 0.06, | |
| "build": 0.04, "software": 0.02, "storage": 0.01, "audio": 0.00, | |
| }, | |
| "value": { | |
| "camera": 0.18, "performance": 0.18, "battery": 0.15, | |
| "display": 0.12, "charging": 0.10, "storage": 0.10, | |
| "build": 0.07, "software": 0.05, "ram": 0.03, "audio": 0.02, | |
| }, | |
| "business": { | |
| "software": 0.25, "performance": 0.20, "display": 0.15, | |
| "build": 0.12, "camera": 0.10, "battery": 0.08, | |
| "ram": 0.05, "charging": 0.03, "storage": 0.01, "audio": 0.01, | |
| }, | |
| "balanced": { | |
| "camera": 0.18, "performance": 0.17, "battery": 0.14, | |
| "display": 0.13, "charging": 0.10, "build": 0.09, | |
| "software": 0.08, "ram": 0.05, "storage": 0.04, "audio": 0.02, | |
| }, | |
| "ios": { | |
| "software": 0.25, "camera": 0.22, "performance": 0.18, | |
| "build": 0.12, "display": 0.10, "battery": 0.07, | |
| "ram": 0.03, "charging": 0.02, "storage": 0.01, "audio": 0.00, | |
| }, | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # CORE SCORING FUNCTION | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| IPHONE_MIN_PRICE = 280000 # Minimum realistic iPhone price in Pakistan (PKR) | |
| def score_phone( | |
| phone: Phone, | |
| prefs: UserPreferences, | |
| phone_db: list["Phone"], | |
| ) -> float: | |
| """ | |
| Returns a float score 0β10 for this phone against user preferences. | |
| Returns -1 if the phone is ineligible. | |
| """ | |
| if not phone.available_on_priceoye: | |
| return -1.0 | |
| # OS filter | |
| if prefs.os_preference == "ios" and phone.os != "ios": | |
| return -1.0 | |
| if prefs.os_preference == "android" and phone.os != "android": | |
| return -1.0 | |
| # Budget filter β auto-exclude iPhones if budget too low | |
| if prefs.budget: | |
| if phone.os == "ios" and prefs.budget < IPHONE_MIN_PRICE: | |
| return -1.0 | |
| if phone.price_pkr > prefs.budget * 1.12: # 12% tolerance | |
| return -1.0 | |
| # Brand preference boost | |
| brand_boost = 0.0 | |
| if prefs.brand_preference and phone.brand.lower() == prefs.brand_preference.lower(): | |
| brand_boost = 0.3 | |
| if phone.brand in prefs.brand_avoid: | |
| return -1.0 | |
| # Get weights | |
| weights = WEIGHT_PROFILES.get(prefs.priority or "balanced", WEIGHT_PROFILES["balanced"]) | |
| # Compute weighted score | |
| cat_scores = get_category_scores(phone) | |
| total = sum(cat_scores[cat] * w for cat, w in weights.items()) | |
| # Savings bonus (mild β specs should dominate) | |
| if prefs.budget and prefs.budget > 0: | |
| savings_ratio = max(0.0, 1.0 - phone.price_pkr / prefs.budget) | |
| total += savings_ratio * 0.15 | |
| return round(min(10.0, total + brand_boost), 2) | |
| def recommend( | |
| prefs: UserPreferences, | |
| phone_db: list["Phone"], | |
| count: int = 1, | |
| ) -> list[tuple["Phone", float]]: | |
| """ | |
| Returns top-N phones scored for the given preferences. | |
| Each item is (phone, final_score). | |
| """ | |
| scored = [] | |
| for phone in phone_db: | |
| s = score_phone(phone, prefs, phone_db) | |
| if s > 0: | |
| scored.append((phone, s)) | |
| scored.sort(key=lambda x: x[1], reverse=True) | |
| return scored[:count] | |
| def find_closest_alternative( | |
| target_phone_name: str, | |
| prefs: UserPreferences, | |
| phone_db: list["Phone"], | |
| ) -> Optional["Phone"]: | |
| """ | |
| If a benchmarked phone is not on PriceOye, find the closest | |
| available alternative based on category score similarity. | |
| """ | |
| # Try to find target in DB | |
| target = next( | |
| (p for p in phone_db if target_phone_name.lower() in p.name.lower()), None | |
| ) | |
| if target and target.available_on_priceoye: | |
| return target | |
| if not target: | |
| # Fall back to top recommendation | |
| results = recommend(prefs, phone_db, count=1) | |
| return results[0][0] if results else None | |
| # Find most similar by category scores | |
| target_cats = get_category_scores(target) | |
| best_match = None | |
| best_dist = float("inf") | |
| for phone in phone_db: | |
| if not phone.available_on_priceoye: | |
| continue | |
| if phone.id == target.id: | |
| continue | |
| if prefs.os_preference and phone.os != prefs.os_preference: | |
| continue | |
| phone_cats = get_category_scores(phone) | |
| dist = math.sqrt( | |
| sum((target_cats[c] - phone_cats[c]) ** 2 for c in target_cats) | |
| ) | |
| if dist < best_dist: | |
| best_dist = dist | |
| best_match = phone | |
| return best_match | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # PRIORITY REASON GENERATOR | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| def get_priority_reason(phone: Phone, priority: str) -> str: | |
| cats = get_category_scores(phone) | |
| reasons = { | |
| "photography": ( | |
| f"Camera score {cats['camera']:.1f}/10 β " | |
| f"{phone.dims.camera_lens_quality.note}" | |
| ), | |
| "gaming": ( | |
| f"Gaming performance {cats['performance']:.1f}/10 β " | |
| f"{phone.dims.perf_cpu.note}" | |
| ), | |
| "battery": ( | |
| f"Battery {cats['battery']:.1f}/10 Β· " | |
| f"Charging {cats['charging']:.1f}/10 β " | |
| f"{phone.dims.batt_real_world_sot.note}" | |
| ), | |
| "value": ( | |
| f"{phone.price_label} mein best value β " | |
| f"Camera {cats['camera']:.1f} Β· Performance {cats['performance']:.1f}" | |
| ), | |
| "business": ( | |
| f"Software {cats['software']:.1f}/10 Β· " | |
| f"Build {cats['build']:.1f}/10 β " | |
| f"{phone.dims.soft_update_policy.note}" | |
| ), | |
| "balanced": ( | |
| f"Koi bhi weak point nahi β " | |
| f"Camera {cats['camera']:.1f} Β· " | |
| f"Performance {cats['performance']:.1f} Β· " | |
| f"Battery {cats['battery']:.1f}" | |
| ), | |
| "ios": ( | |
| f"iOS ecosystem {cats['software']:.1f}/10 β " | |
| f"{phone.dims.soft_update_policy.note}" | |
| ), | |
| } | |
| return reasons.get(priority, f"Tamam categories mein strong performance: {sum(cats.values())/len(cats):.1f}/10 average") | |