Spaces:
Running
Running
| """Ring size recommendation from calibrated finger width.""" | |
| from typing import Dict, List, Literal, Optional, Tuple | |
| # Ring model definitions: model name → {size: inner_diameter_mm} | |
| RING_MODELS: Dict[str, Dict[int, float]] = { | |
| "gen": { | |
| 6: 16.9, | |
| 7: 17.7, | |
| 8: 18.6, | |
| 9: 19.4, | |
| 10: 20.3, | |
| 11: 21.1, | |
| 12: 21.9, | |
| 13: 22.7, | |
| }, | |
| "air": { | |
| 6: 16.6, | |
| 7: 17.4, | |
| 8: 18.2, | |
| 9: 19.0, | |
| 10: 19.9, | |
| 11: 20.7, | |
| 12: 21.5, | |
| 13: 22.3, | |
| }, | |
| } | |
| VALID_RING_MODELS = list(RING_MODELS.keys()) | |
| DEFAULT_RING_MODEL = "gen" | |
| # Backwards-compatible alias | |
| RING_SIZE_CHART = RING_MODELS[DEFAULT_RING_MODEL] | |
| def _get_sorted_sizes(ring_model: str) -> List[Tuple[int, float]]: | |
| chart = RING_MODELS.get(ring_model, RING_MODELS[DEFAULT_RING_MODEL]) | |
| return sorted(chart.items(), key=lambda x: x[1]) | |
| def recommend_ring_size(diameter_cm: float, ring_model: str = DEFAULT_RING_MODEL) -> Optional[Dict]: | |
| """Recommend ring size from calibrated finger outer diameter. | |
| Returns dict with: | |
| - best_match: nearest ring size (int) | |
| - best_match_inner_mm: inner diameter of best match | |
| - range_min / range_max: recommended 2-size range | |
| - diameter_mm: input converted to mm | |
| - ring_model: which model chart was used | |
| Returns None if diameter is out of reasonable range. | |
| """ | |
| diameter_mm = diameter_cm * 10.0 | |
| if diameter_mm < 14.0 or diameter_mm > 26.0: | |
| return None | |
| sorted_sizes = _get_sorted_sizes(ring_model) | |
| # Find nearest size | |
| best_size, best_inner = min(sorted_sizes, key=lambda x: abs(x[1] - diameter_mm)) | |
| # Find second nearest size | |
| second_size, second_inner = min( | |
| (s for s in sorted_sizes if s[0] != best_size), | |
| key=lambda x: abs(x[1] - diameter_mm), | |
| ) | |
| range_min = min(best_size, second_size) | |
| range_max = max(best_size, second_size) | |
| return { | |
| "best_match": best_size, | |
| "best_match_inner_mm": best_inner, | |
| "range_min": range_min, | |
| "range_max": range_max, | |
| "diameter_mm": round(diameter_mm, 2), | |
| "ring_model": ring_model, | |
| } | |
| def aggregate_ring_sizes(per_finger_results: Dict[str, Dict]) -> Dict: | |
| """Aggregate ring size recommendations from multiple fingers. | |
| Args: | |
| per_finger_results: Dict mapping finger name to measurement result dict. | |
| Each value must have keys: | |
| - "finger_outer_diameter_cm": float or None | |
| - "confidence": float | |
| - "ring_size": dict from recommend_ring_size() or None | |
| - "fail_reason": str or None | |
| Returns: | |
| Dict with: | |
| - overall_best_size: int (consensus size if one exists in all | |
| fingers' ranges, otherwise confidence-weighted best size) | |
| - overall_range_min: int (min of all per-finger range_min) | |
| - overall_range_max: int (max of all per-finger range_max) | |
| - fingers_measured: int (total attempted) | |
| - fingers_succeeded: int (with valid measurement) | |
| - per_finger: dict of per-finger details | |
| - fail_reason: str or None (only if ALL fingers failed) | |
| """ | |
| fingers_measured = len(per_finger_results) | |
| # Build per_finger summary | |
| per_finger: Dict[str, Dict] = {} | |
| for name, result in per_finger_results.items(): | |
| failed = result.get("fail_reason") is not None or result.get("ring_size") is None | |
| rs = result.get("ring_size") | |
| per_finger[name] = { | |
| "diameter_cm": result.get("finger_outer_diameter_cm"), | |
| "confidence": result.get("confidence", 0.0), | |
| "best_match": rs["best_match"] if rs else None, | |
| "range": [rs["range_min"], rs["range_max"]] if rs else None, | |
| "status": "failed" if failed else "ok", | |
| "fail_reason": result.get("fail_reason"), | |
| } | |
| # Filter to succeeded fingers | |
| succeeded = { | |
| name: info for name, info in per_finger.items() if info["status"] == "ok" | |
| } | |
| if not succeeded: | |
| return { | |
| "fail_reason": "all_fingers_failed", | |
| "fingers_measured": fingers_measured, | |
| "fingers_succeeded": 0, | |
| "per_finger": per_finger, | |
| } | |
| # Confidence-weighted voting for best size | |
| vote_tally: Dict[int, float] = {} | |
| for info in succeeded.values(): | |
| size = info["best_match"] | |
| vote_tally[size] = vote_tally.get(size, 0.0) + info["confidence"] | |
| weighted_best_size = max(vote_tally, key=lambda s: vote_tally[s]) | |
| # Intersection-first override: if a size falls in every finger's range, prefer it | |
| all_ranges = [set(range(info["range"][0], info["range"][1] + 1)) | |
| for info in succeeded.values()] | |
| consensus_sizes = set.intersection(*all_ranges) if all_ranges else set() | |
| if consensus_sizes: | |
| # Pick the consensus size closest to the confidence-weighted winner | |
| overall_best_size = min(consensus_sizes, | |
| key=lambda s: abs(s - weighted_best_size)) | |
| else: | |
| overall_best_size = weighted_best_size | |
| # Aggregate range | |
| overall_range_min = min(info["range"][0] for info in succeeded.values()) | |
| overall_range_max = max(info["range"][1] for info in succeeded.values()) | |
| # Ensure range covers best size | |
| if overall_best_size < overall_range_min: | |
| overall_range_min = overall_best_size | |
| if overall_best_size > overall_range_max: | |
| overall_range_max = overall_best_size | |
| return { | |
| "overall_best_size": overall_best_size, | |
| "overall_range_min": overall_range_min, | |
| "overall_range_max": overall_range_max, | |
| "fingers_measured": fingers_measured, | |
| "fingers_succeeded": len(succeeded), | |
| "per_finger": per_finger, | |
| "fail_reason": None, | |
| } | |