from scipy.optimize import minimize_scalar from chatbot.agents.states.state import SwapState import numpy as np import logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) def calculate_top_options(state: SwapState): logger.info("---NODE: SCIPY RANKING (MATH FILTER)---") candidates = state.get("candidates", []) food_old = state["food_old"] if not candidates or not food_old: logger.warning("⚠️ Candidates hoặc Food_old rỗng, bỏ qua tính toán.") return {"top_candidates": []} # 1. Xác định "KPI" từ món cũ old_scale = float(food_old.get("portion_scale", 1.0)) target_vector = np.array([ float(food_old.get("kcal", 0)) * old_scale, float(food_old.get("protein", 0)) * old_scale, float(food_old.get("totalfat", 0)) * old_scale, float(food_old.get("carbs", 0)) * old_scale ]) weights = np.array([3.0, 2.0, 1.0, 1.0]) # Bound của món cũ bounds = food_old.get("solver_bounds", (0.5, 2.0)) # Hàm tính toán def calculate_score(candidate): try: base_vector = np.array([ float(candidate.get("kcal", 0)), float(candidate.get("protein", 0)), float(candidate.get("totalfat", 0)), float(candidate.get("carbs", 0)) ]) if np.sum(base_vector) == 0: return float('inf'), 1.0 def objective(x): current_vector = base_vector * x diff = (current_vector - target_vector) / (target_vector + 1e-5) loss = np.sum(weights * (diff ** 2)) return loss res = minimize_scalar(objective, bounds=bounds, method='bounded') if res.success: return res.fun, res.x else: return float('inf'), 1.0 except Exception as inner_e: logger.debug(f"Bỏ qua món {candidate.get('name')} do lỗi toán học: {inner_e}") return float('inf'), 1.0 # 3. Chấm điểm hàng loạt scored_candidates = [] for item in candidates: try: loss, scale = calculate_score(item) item_score = item.copy() item_score["optimization_loss"] = round(loss, 4) item_score["portion_scale"] = round(scale, 2) # Tính chỉ số hiển thị sau khi scale item_score["final_kcal"] = int(item["kcal"] * scale) item_score["final_protein"] = int(item["protein"] * scale) item_score["final_totalfat"] = int(item["totalfat"] * scale) item_score["final_carbs"] = int(item["carbs"] * scale) scored_candidates.append(item_score) except Exception as e: logger.warning(f"Lỗi khi xử lý món {item.get('name', 'N/A')}: {e}") continue # 4. Lấy Top 10 tốt nhất scored_candidates.sort(key=lambda x: x["optimization_loss"]) top_10 = scored_candidates[:10] logger.info(f"📊 Scipy đã lọc ra {len(top_10)} ứng viên tiềm năng.") for item in top_10: logger.info(f" - {item['name']} (Scale x{item['portion_scale']} | Loss: {item['optimization_loss']})") return {"top_candidates": top_10}