Spaces:
Running
Running
File size: 3,926 Bytes
5ec46b5 d6218c1 5ec46b5 d6218c1 5ec46b5 d6218c1 5ec46b5 d6218c1 5ec46b5 d6218c1 5ec46b5 d6218c1 5ec46b5 d6218c1 5ec46b5 d6218c1 5ec46b5 d6218c1 5ec46b5 d6218c1 5ec46b5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | """AI-powered ring size explanation using OpenAI.
Size selection is handled by deterministic logic in ring_size.py.
This module only generates a human-friendly explanation for the recommendation.
"""
import os
import logging
from typing import Dict, Optional
from src.ring_size import RING_MODELS, DEFAULT_RING_MODEL
logger = logging.getLogger(__name__)
_MODEL_LABELS = {"gen": "Gen1/Gen2", "air": "Air"}
def _build_size_table_text(ring_model: str = DEFAULT_RING_MODEL) -> str:
chart = RING_MODELS.get(ring_model, RING_MODELS[DEFAULT_RING_MODEL])
return "\n".join(
f" Size {size}: inner diameter {diameter_mm:.1f} mm"
for size, diameter_mm in sorted(chart.items())
)
_SYSTEM_PROMPT_TEMPLATE = """You are a sizing explanation assistant for Femometer Smart Ring ({model_label}).
You are given measured finger widths and a pre-computed ring size recommendation.
Your ONLY job is to explain WHY the recommended size is a good fit, in 1-2 concise sentences.
Do NOT suggest a different size. The size decision has already been made by the system.
Guidelines:
- Mention which finger(s) would fit best at this size
- Include specific diameter when first referencing a size, e.g. "Size 8 (18.6mm)"
- Priority context: index finger fit is slightly preferred over middle, then ring
- Keep it concise and actionable
Ring Size Chart ({model_label}):
{size_table}
Respond in plain text (1-2 sentences). Do NOT use JSON or markdown.
"""
def ai_explain_recommendation(
finger_widths: Dict[str, Optional[float]],
recommended_size: int,
range_min: int,
range_max: int,
ring_model: str = DEFAULT_RING_MODEL,
) -> Optional[str]:
"""Call OpenAI to explain an already-computed ring size recommendation.
Args:
finger_widths: Dict mapping finger name to diameter in cm (or None if failed).
Example: {"index": 1.93, "middle": 1.84, "ring": 1.93}
recommended_size: The deterministic best-match size from ring_size.py.
range_min: Lower bound of recommended size range.
range_max: Upper bound of recommended size range.
ring_model: Which ring model chart to reference.
Returns:
A plain-text explanation string, or None if the API call fails.
"""
api_key = os.environ.get("OPENAI_API_KEY")
if not api_key:
logger.warning("OPENAI_API_KEY not set, skipping AI explanation")
return None
model_label = _MODEL_LABELS.get(ring_model, ring_model)
system_prompt = _SYSTEM_PROMPT_TEMPLATE.format(
model_label=model_label,
size_table=_build_size_table_text(ring_model),
)
# Build user message with measurements and pre-computed recommendation
lines = ["Measured finger outer diameters:"]
for finger, width in finger_widths.items():
if width is not None:
lines.append(f" {finger.capitalize()}: {width:.2f} cm ({width * 10:.1f} mm)")
else:
lines.append(f" {finger.capitalize()}: measurement failed")
lines.append("")
lines.append(f"Recommended size: {recommended_size} (range {range_min}\u2013{range_max})")
lines.append("")
lines.append("Explain why this size is a good fit.")
user_msg = "\n".join(lines)
try:
import openai
client = openai.OpenAI(api_key=api_key)
response = client.chat.completions.create(
model="gpt-5.4",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_msg},
],
temperature=0.3,
max_completion_tokens=200,
)
content = response.choices[0].message.content.strip()
if not content:
logger.warning("AI returned empty explanation")
return None
return content
except Exception as e:
logger.error("AI explanation failed: %s", e)
return None
|