import torch import re import numpy as np from typing import List, Dict, Tuple, Optional from dataclasses import dataclass from breed_health_info import breed_health_info from breed_noise_info import breed_noise_info from dog_database import dog_data from scoring_calculation_system import UserPreferences from sentence_transformers import SentenceTransformer, util class SmartBreedMatcher: def __init__(self, dog_data: List[Tuple]): self.dog_data = dog_data self.model = SentenceTransformer('all-mpnet-base-v2') self._embedding_cache = {} self._clear_cache() def _clear_cache(self): self._embedding_cache = {} def _get_cached_embedding(self, text: str) -> torch.Tensor: if text not in self._embedding_cache: self._embedding_cache[text] = self.model.encode(text) return self._embedding_cache[text] def _categorize_breeds(self) -> Dict: """自動將狗品種分類""" categories = { 'working_dogs': [], 'herding_dogs': [], 'hunting_dogs': [], 'companion_dogs': [], 'guard_dogs': [] } for breed_info in self.dog_data: description = breed_info[9].lower() temperament = breed_info[4].lower() # 根據描述和性格特徵自動分類 if any(word in description for word in ['herding', 'shepherd', 'cattle', 'flock']): categories['herding_dogs'].append(breed_info[1]) elif any(word in description for word in ['hunting', 'hunt', 'retriever', 'pointer']): categories['hunting_dogs'].append(breed_info[1]) elif any(word in description for word in ['companion', 'toy', 'family', 'lap']): categories['companion_dogs'].append(breed_info[1]) elif any(word in description for word in ['guard', 'protection', 'watchdog']): categories['guard_dogs'].append(breed_info[1]) elif any(word in description for word in ['working', 'draft', 'cart']): categories['working_dogs'].append(breed_info[1]) return categories def find_similar_breeds(self, breed_name: str, top_n: int = 5) -> List[Tuple[str, float]]: """ 找出與指定品種最相似的其他品種 Args: breed_name: 目標品種名稱 top_n: 返回的相似品種數量 Returns: List[Tuple[str, float]]: 相似品種列表,包含品種名稱和相似度分數 """ try: target_breed = next((breed for breed in self.dog_data if breed[1] == breed_name), None) if not target_breed: return [] # 獲取完整的目標品種特徵 target_features = { 'breed_name': target_breed[1], 'size': target_breed[2], 'temperament': target_breed[4], 'exercise': target_breed[7], 'grooming': target_breed[8], 'description': target_breed[9], 'good_with_children': target_breed[6] # 添加這個特徵 } similarities = [] for breed in self.dog_data: if breed[1] != breed_name: breed_features = { 'breed_name': breed[1], 'size': breed[2], 'temperament': breed[4], 'exercise': breed[7], 'grooming': breed[8], 'description': breed[9], 'good_with_children': breed[6] # 添加這個特徵 } try: similarity_score = self._calculate_breed_similarity(target_features, breed_features) # 確保分數在有效範圍內 similarity_score = min(1.0, max(0.0, similarity_score)) similarities.append((breed[1], similarity_score)) except Exception as e: print(f"Error calculating similarity for {breed[1]}: {str(e)}") continue # 根據相似度排序並返回前N個 return sorted(similarities, key=lambda x: x[1], reverse=True)[:top_n] except Exception as e: print(f"Error in find_similar_breeds: {str(e)}") return [] def _calculate_breed_similarity(self, breed1_features: Dict, breed2_features: Dict, weights: Dict[str, float]) -> float: try: # 1. 基礎相似度計算 size_similarity = self._calculate_size_similarity_enhanced( breed1_features.get('size', 'Medium'), breed2_features.get('size', 'Medium'), breed2_features.get('description', '') ) exercise_similarity = self._calculate_exercise_similarity_enhanced( breed1_features.get('exercise', 'Moderate'), breed2_features.get('exercise', 'Moderate') ) # 性格相似度 temp1_embedding = self._get_cached_embedding(breed1_features.get('temperament', '')) temp2_embedding = self._get_cached_embedding(breed2_features.get('temperament', '')) temperament_similarity = float(util.pytorch_cos_sim(temp1_embedding, temp2_embedding)) # 其他相似度 grooming_similarity = self._calculate_grooming_similarity( breed1_features.get('breed_name', ''), breed2_features.get('breed_name', '') ) health_similarity = self._calculate_health_score_similarity( breed1_features.get('breed_name', ''), breed2_features.get('breed_name', '') ) noise_similarity = self._calculate_noise_similarity( breed1_features.get('breed_name', ''), breed2_features.get('breed_name', '') ) # 2. 關鍵特徵評分 feature_scores = {} for feature, similarity in { 'size': size_similarity, 'exercise': exercise_similarity, 'temperament': temperament_similarity, 'grooming': grooming_similarity, 'health': health_similarity, 'noise': noise_similarity }.items(): # 根據權重調整每個特徵分數 importance = weights.get(feature, 0.1) if importance > 0.3: # 高權重特徵 if similarity < 0.5: # 若關鍵特徵匹配度低 feature_scores[feature] = similarity * 0.5 # 大幅降低分數 else: feature_scores[feature] = similarity * 1.2 # 提高匹配度好的分數 else: # 一般特徵 feature_scores[feature] = similarity # 3. 計算最終相似度 weighted_sum = 0 weight_sum = 0 for feature, score in feature_scores.items(): feature_weight = weights.get(feature, 0.1) weighted_sum += score * feature_weight weight_sum += feature_weight final_similarity = weighted_sum / weight_sum if weight_sum > 0 else 0.5 return min(1.0, max(0.2, final_similarity)) # 設定最低分數為0.2 except Exception as e: print(f"Error in calculate_breed_similarity: {str(e)}") return 0.5 def get_breed_characteristics_score(self, breed_features: Dict, description: str) -> float: score = 1.0 description_lower = description.lower() breed_score_multipliers = [] # 運動需求評估 exercise_needs = breed_features.get('exercise', 'Moderate') exercise_keywords = ['active', 'running', 'energetic', 'athletic'] if any(keyword in description_lower for keyword in exercise_keywords): multipliers = { 'Very High': 1.5, 'High': 1.3, 'Moderate': 0.7, 'Low': 0.4 } breed_score_multipliers.append(multipliers.get(exercise_needs, 1.0)) # 體型評估 size = breed_features.get('size', 'Medium') if 'apartment' in description_lower: size_multipliers = { 'Giant': 0.3, 'Large': 0.6, 'Medium-Large': 0.8, 'Medium': 1.4, 'Small': 1.0, 'Tiny': 0.9 } breed_score_multipliers.append(size_multipliers.get(size, 1.0)) elif 'house' in description_lower: size_multipliers = { 'Giant': 0.8, 'Large': 1.2, 'Medium-Large': 1.3, 'Medium': 1.2, 'Small': 0.9, 'Tiny': 0.7 } breed_score_multipliers.append(size_multipliers.get(size, 1.0)) # 家庭適應性評估 if any(keyword in description_lower for keyword in ['family', 'children', 'kids']): good_with_children = breed_features.get('good_with_children', False) breed_score_multipliers.append(1.3 if good_with_children else 0.6) # 噪音評估 if 'quiet' in description_lower: noise_level = breed_features.get('noise_level', 'Moderate') noise_multipliers = { 'Low': 1.3, 'Moderate': 0.9, 'High': 0.5 } breed_score_multipliers.append(noise_multipliers.get(noise_level, 1.0)) # 應用所有乘數 for multiplier in breed_score_multipliers: score *= multiplier # 確保分數在合理範圍內 return min(1.5, max(0.3, score)) def _calculate_size_similarity_enhanced(self, size1: str, size2: str, description: str) -> float: """ 增強版尺寸相似度計算 """ try: # 更細緻的尺寸映射 size_map = { 'Tiny': 0, 'Small': 1, 'Small-Medium': 2, 'Medium': 3, 'Medium-Large': 4, 'Large': 5, 'Giant': 6 } # 標準化並獲取數值 value1 = size_map.get(self._normalize_size(size1), 3) value2 = size_map.get(self._normalize_size(size2), 3) # 基礎相似度計算 base_similarity = 1.0 - (abs(value1 - value2) / 6.0) # 環境適應性調整 if 'apartment' in description.lower(): if size2 in ['Large', 'Giant']: base_similarity *= 0.7 # 大型犬在公寓降低相似度 elif size2 in ['Medium', 'Medium-Large']: base_similarity *= 1.2 # 中型犬更適合 elif size2 in ['Small', 'Tiny']: base_similarity *= 0.8 # 過小的狗也不是最佳選擇 return min(1.0, base_similarity) except Exception as e: print(f"Error in calculate_size_similarity_enhanced: {str(e)}") return 0.5 def _normalize_size(self, size: str) -> str: """ 標準化犬種尺寸分類 Args: size: 原始尺寸描述 Returns: str: 標準化後的尺寸類別 """ try: size = size.lower() if 'tiny' in size: return 'Tiny' elif 'small' in size and 'medium' in size: return 'Small-Medium' elif 'small' in size: return 'Small' elif 'medium' in size and 'large' in size: return 'Medium-Large' elif 'medium' in size: return 'Medium' elif 'giant' in size: return 'Giant' elif 'large' in size: return 'Large' return 'Medium' # 默認為 Medium except Exception as e: print(f"Error in normalize_size: {str(e)}") return 'Medium' def _calculate_exercise_similarity_enhanced(self, exercise1: str, exercise2: str) -> float: try: exercise_values = { 'Very High': 4, 'High': 3, 'Moderate': 2, 'Low': 1 } value1 = exercise_values.get(exercise1, 2) value2 = exercise_values.get(exercise2, 2) # 計算差異 diff = abs(value1 - value2) if diff == 0: return 1.0 elif diff == 1: return 0.7 elif diff == 2: return 0.4 else: return 0.2 except Exception as e: print(f"Error in calculate_exercise_similarity_enhanced: {str(e)}") return 0.5 def _calculate_grooming_similarity(self, breed1: str, breed2: str) -> float: """ 計算美容需求相似度 Args: breed1: 第一個品種名稱 breed2: 第二個品種名稱 Returns: float: 相似度分數 (0-1) """ try: grooming_map = { 'Low': 1, 'Moderate': 2, 'High': 3 } # 從dog_data中獲取美容需求 breed1_info = next((dog for dog in self.dog_data if dog[1] == breed1), None) breed2_info = next((dog for dog in self.dog_data if dog[1] == breed2), None) if not breed1_info or not breed2_info: return 0.5 # 數據缺失時返回中等相似度 grooming1 = breed1_info[8] # Grooming_Needs index grooming2 = breed2_info[8] # 獲取數值,默認為 Moderate value1 = grooming_map.get(grooming1, 2) value2 = grooming_map.get(grooming2, 2) # 基礎相似度計算 base_similarity = 1.0 - (abs(value1 - value2) / 2.0) # 美容需求調整 if grooming2 == 'Moderate': base_similarity *= 1.1 # 中等美容需求略微加分 elif grooming2 == 'High': base_similarity *= 0.9 # 高美容需求略微降分 return min(1.0, base_similarity) except Exception as e: print(f"Error in calculate_grooming_similarity: {str(e)}") return 0.5 def _calculate_health_score_similarity(self, breed1: str, breed2: str) -> float: """ 計算兩個品種的健康評分相似度 """ try: score1 = self._calculate_health_score(breed1) score2 = self._calculate_health_score(breed2) return 1.0 - abs(score1 - score2) except Exception as e: print(f"Error in calculate_health_score_similarity: {str(e)}") return 0.5 def _calculate_health_score(self, breed_name: str) -> float: """ 計算品種的健康評分 Args: breed_name: 品種名稱 Returns: float: 健康評分 (0-1) """ try: if breed_name not in breed_health_info: return 0.5 health_notes = breed_health_info[breed_name]['health_notes'].lower() # 嚴重健康問題 severe_conditions = [ 'cancer', 'cardiomyopathy', 'epilepsy', 'dysplasia', 'bloat', 'progressive', 'syndrome' ] # 中等健康問題 moderate_conditions = [ 'allergies', 'infections', 'thyroid', 'luxation', 'skin problems', 'ear' ] # 計算問題數量 severe_count = sum(1 for condition in severe_conditions if condition in health_notes) moderate_count = sum(1 for condition in moderate_conditions if condition in health_notes) # 基礎健康評分 health_score = 1.0 health_score -= (severe_count * 0.15) # 嚴重問題扣分更多 health_score -= (moderate_count * 0.05) # 中等問題扣分較少 # 確保評分在合理範圍內 return max(0.3, min(1.0, health_score)) except Exception as e: print(f"Error in calculate_health_score: {str(e)}") return 0.5 def _calculate_noise_similarity(self, breed1: str, breed2: str) -> float: """計算兩個品種的噪音相似度""" noise_levels = { 'Low': 1, 'Moderate': 2, 'High': 3, 'Unknown': 2 # 默認為中等 } noise1 = breed_noise_info.get(breed1, {}).get('noise_level', 'Unknown') noise2 = breed_noise_info.get(breed2, {}).get('noise_level', 'Unknown') # 獲取數值級別 level1 = noise_levels.get(noise1, 2) level2 = noise_levels.get(noise2, 2) # 計算差異並歸一化 difference = abs(level1 - level2) similarity = 1.0 - (difference / 2) # 最大差異是2,所以除以2來歸一化 return similarity # bonus score zone def _calculate_size_bonus(self, size: str, living_space: str) -> float: """ 計算尺寸匹配的獎勵分數 Args: size: 品種尺寸 living_space: 居住空間類型 Returns: float: 獎勵分數 (-0.25 到 0.15) """ try: if living_space == "apartment": size_scores = { 'Tiny': -0.15, 'Small': 0.10, 'Medium': 0.15, 'Large': 0.10, 'Giant': -0.30 } else: # house size_scores = { 'Tiny': -0.10, 'Small': 0.05, 'Medium': 0.15, 'Large': 0.15, 'Giant': -0.15 } return size_scores.get(size, 0.0) except Exception as e: print(f"Error in calculate_size_bonus: {str(e)}") return 0.0 def _calculate_exercise_bonus(self, exercise_needs: str, exercise_time: int) -> float: """ 計算運動需求匹配的獎勵分數 Args: exercise_needs: 品種運動需求 exercise_time: 用戶可提供的運動時間(分鐘) Returns: float: 獎勵分數 (-0.20 到 0.20) """ try: if exercise_time >= 120: # 高運動量需求 exercise_scores = { 'Low': -0.30, 'Moderate': -0.10, 'High': 0.15, 'Very High': 0.30 } elif exercise_time >= 60: # 中等運動量需求 exercise_scores = { 'Low': -0.05, 'Moderate': 0.15, 'High': 0.05, 'Very High': -0.10 } else: # 低運動量需求 exercise_scores = { 'Low': 0.15, 'Moderate': 0.05, 'High': -0.15, 'Very High': -0.20 } return exercise_scores.get(exercise_needs, 0.0) except Exception as e: print(f"Error in calculate_exercise_bonus: {str(e)}") return 0.0 def _calculate_grooming_bonus(self, grooming: str, commitment: str) -> float: """ 計算美容需求匹配的獎勵分數 Args: grooming: 品種美容需求 commitment: 用戶美容投入程度 Returns: float: 獎勵分數 (-0.15 到 0.10) """ try: if commitment == "high": grooming_scores = { 'Low': -0.05, 'Moderate': 0.05, 'High': 0.10 } else: # medium or low commitment grooming_scores = { 'Low': 0.10, 'Moderate': 0.05, 'High': -0.20 } return grooming_scores.get(grooming, 0.0) except Exception as e: print(f"Error in calculate_grooming_bonus: {str(e)}") return 0.0 def _calculate_family_bonus(self, breed_info: Dict) -> float: """ 計算家庭適應性的獎勵分數 Args: breed_info: 品種信息字典 Returns: float: 獎勵分數 (0 到 0.20) """ try: bonus = 0.0 temperament = breed_info.get('Temperament', '').lower() good_with_children = breed_info.get('Good_With_Children', False) if good_with_children: bonus += 0.20 if any(trait in temperament for trait in ['gentle', 'patient', 'friendly']): bonus += 0.10 return min(0.20, bonus) except Exception as e: print(f"Error in calculate_family_bonus: {str(e)}") return 0.0 def _detect_scenario(self, description: str) -> Dict[str, float]: """ 檢測場景並返回對應權重 """ # 基礎場景定義 scenarios = { 'athletic': { 'keywords': ['active', 'exercise', 'running', 'athletic', 'energetic', 'sports'], 'weights': { 'exercise': 0.40, 'size': 0.25, 'temperament': 0.20, 'health': 0.15 } }, 'apartment': { 'keywords': ['apartment', 'flat', 'condo'], 'weights': { 'size': 0.35, 'noise': 0.30, 'exercise': 0.20, 'temperament': 0.15 } }, 'family': { 'keywords': ['family', 'children', 'kids', 'friendly'], 'weights': { 'temperament': 0.35, 'safety': 0.30, 'noise': 0.20, 'exercise': 0.15 } }, 'novice': { 'keywords': ['first time', 'beginner', 'new owner', 'inexperienced'], 'weights': { 'trainability': 0.35, 'temperament': 0.30, 'care_level': 0.20, 'health': 0.15 } } } # 檢測匹配的場景 matched_scenarios = [] for scenario, config in scenarios.items(): if any(keyword in description.lower() for keyword in config['keywords']): matched_scenarios.append(scenario) # 默認權重 default_weights = { 'exercise': 0.20, 'size': 0.20, 'temperament': 0.20, 'health': 0.15, 'noise': 0.10, 'grooming': 0.10, 'trainability': 0.05 } # 如果沒有匹配場景,返回默認權重 if not matched_scenarios: return default_weights # 合併匹配場景的權重 final_weights = default_weights.copy() for scenario in matched_scenarios: scenario_weights = scenarios[scenario]['weights'] for feature, weight in scenario_weights.items(): if feature in final_weights: final_weights[feature] = max(final_weights[feature], weight) return final_weights def _calculate_final_scores(self, breed_name: str, base_scores: Dict, smart_score: float, is_preferred: bool, similarity_score: float = 0.0, characteristics_score: float = 1.0, weights: Dict[str, float] = None) -> Dict: try: # 使用傳入的權重或默認權重 if weights is None: weights = { 'base': 0.35, 'smart': 0.35, 'bonus': 0.15, 'characteristics': 0.15 } # 確保 base_scores 包含所有必要的鍵 base_scores = { 'overall': base_scores.get('overall', smart_score), 'size': base_scores.get('size', 0.0), 'exercise': base_scores.get('exercise', 0.0), 'temperament': base_scores.get('temperament', 0.0), 'grooming': base_scores.get('grooming', 0.0), 'health': base_scores.get('health', 0.0), 'noise': base_scores.get('noise', 0.0) } # 計算基礎分數 base_score = base_scores['overall'] # 計算獎勵分數 bonus_score = 0.0 if is_preferred: bonus_score = 0.95 elif similarity_score > 0: bonus_score = min(0.8, similarity_score) * 0.95 # 特徵匹配度調整 if characteristics_score < 0.5: base_score *= 0.7 # 降低基礎分數 smart_score *= 0.7 # 降低智能匹配分數 # 計算最終分數 final_score = ( base_score * weights.get('base', 0.35) + smart_score * weights.get('smart', 0.35) + bonus_score * weights.get('bonus', 0.15) + characteristics_score * weights.get('characteristics', 0.15) ) # 確保分數在合理範圍內 final_score = min(1.0, max(0.3, final_score)) return { 'final_score': round(final_score, 4), 'base_score': round(base_score, 4), 'smart_score': round(smart_score, 4), 'bonus_score': round(bonus_score, 4), 'characteristics_score': round(characteristics_score, 4), 'detailed_scores': base_scores } except Exception as e: print(f"Error in calculate_final_scores: {str(e)}") return { 'final_score': 0.5, 'base_score': 0.5, 'smart_score': 0.5, 'bonus_score': 0.0, 'characteristics_score': 0.5, 'detailed_scores': { 'overall': 0.5, 'size': 0.5, 'exercise': 0.5, 'temperament': 0.5, 'grooming': 0.5, 'health': 0.5, 'noise': 0.5 } } def _general_matching(self, description: str, weights: Dict[str, float], top_n: int = 10) -> List[Dict]: """基本的品種匹配邏輯,考慮描述、性格、噪音和健康因素""" try: matches = [] desc_embedding = self._get_cached_embedding(description) for breed in self.dog_data: breed_name = breed[1] breed_features = self._extract_breed_features(breed) breed_description = breed[9] temperament = breed[4] breed_desc_embedding = self._get_cached_embedding(breed_description) breed_temp_embedding = self._get_cached_embedding(temperament) desc_similarity = float(util.pytorch_cos_sim(desc_embedding, breed_desc_embedding)) temp_similarity = float(util.pytorch_cos_sim(desc_embedding, breed_temp_embedding)) noise_similarity = self._calculate_noise_similarity(breed_name, breed_name) health_score = self._calculate_health_score(breed_name) health_similarity = 1.0 - abs(health_score - 0.8) # 使用傳入的權重 final_score = ( desc_similarity * weights.get('description', 0.35) + temp_similarity * weights.get('temperament', 0.25) + noise_similarity * weights.get('noise', 0.2) + health_similarity * weights.get('health', 0.2) ) # 計算特徵分數 characteristics_score = self.get_breed_characteristics_score(breed_features, description) # 構建完整的 scores 字典 scores = { 'overall': final_score, 'size': breed_features.get('size_score', 0.0), 'exercise': breed_features.get('exercise_score', 0.0), 'temperament': temp_similarity, 'grooming': breed_features.get('grooming_score', 0.0), 'health': health_score, 'noise': noise_similarity } matches.append({ 'breed': breed_name, 'scores': scores, 'final_score': final_score, 'base_score': final_score, 'characteristics_score': characteristics_score, 'bonus_score': 0.0, 'is_preferred': False, 'similarity': final_score, 'health_score': health_score, 'reason': "Matched based on description and characteristics" }) return sorted(matches, key=lambda x: (-x['characteristics_score'], -x['final_score']))[:top_n] except Exception as e: print(f"Error in _general_matching: {str(e)}") return [] def _detect_breed_preference(self, description: str) -> Optional[str]: """檢測用戶是否提到特定品種""" description_lower = f" {description.lower()} " for breed_info in self.dog_data: breed_name = breed_info[1] normalized_breed = breed_name.lower().replace('_', ' ') pattern = rf"\b{re.escape(normalized_breed)}\b" if re.search(pattern, description_lower): return breed_name return None def _extract_breed_features(self, breed_info: Tuple) -> Dict: """ 從品種信息中提取特徵 Args: breed_info: 品種信息元組 Returns: Dict: 包含品種特徵的字典 """ try: return { 'breed_name': breed_info[1], 'size': breed_info[2], 'temperament': breed_info[4], 'exercise': breed_info[7], 'grooming': breed_info[8], 'description': breed_info[9], 'good_with_children': breed_info[6] } except Exception as e: print(f"Error in extract_breed_features: {str(e)}") return { 'breed_name': '', 'size': 'Medium', 'temperament': '', 'exercise': 'Moderate', 'grooming': 'Moderate', 'description': '', 'good_with_children': False } def match_user_preference(self, description: str, top_n: int = 10) -> List[Dict]: try: # 獲取場景權重 weights = self._detect_scenario(description) matches = [] preferred_breed = self._detect_breed_preference(description) # 處理用戶明確提到的品種 if preferred_breed: breed_info = next((breed for breed in self.dog_data if breed[1] == preferred_breed), None) if breed_info: breed_features = self._extract_breed_features(breed_info) base_similarity = self._calculate_breed_similarity(breed_features, breed_features, weights) # 計算特徵分數 characteristics_score = self.get_breed_characteristics_score(breed_features, description) # 計算最終分數 scores = self._calculate_final_scores( preferred_breed, {'overall': base_similarity}, smart_score=base_similarity, is_preferred=True, similarity_score=1.0, characteristics_score=characteristics_score, weights=weights ) matches.append({ 'breed': preferred_breed, 'scores': scores['detailed_scores'], 'final_score': scores['final_score'], 'base_score': scores['base_score'], 'bonus_score': scores['bonus_score'], 'characteristics_score': characteristics_score, 'is_preferred': True, 'priority': 1, 'health_score': self._calculate_health_score(preferred_breed), 'reason': "Directly matched your preferred breed" }) # 尋找相似品種 similar_breeds = self.find_similar_breeds(preferred_breed, top_n=top_n-1) for breed_name, similarity in similar_breeds: if breed_name != preferred_breed: breed_info = next((breed for breed in self.dog_data if breed[1] == breed_name), None) if breed_info: breed_features = self._extract_breed_features(breed_info) characteristics_score = self.get_breed_characteristics_score(breed_features, description) scores = self._calculate_final_scores( breed_name, {'overall': similarity}, smart_score=similarity, is_preferred=False, similarity_score=similarity, characteristics_score=characteristics_score, weights=weights ) if scores['final_score'] >= 0.4: # 設定最低分數門檻 matches.append({ 'breed': breed_name, 'scores': scores['detailed_scores'], 'final_score': scores['final_score'], 'base_score': scores['base_score'], 'bonus_score': scores['bonus_score'], 'characteristics_score': characteristics_score, 'is_preferred': False, 'priority': 2, 'health_score': self._calculate_health_score(breed_name), 'reason': f"Similar to {preferred_breed}" }) # 如果沒有找到偏好品種或需要更多匹配 if len(matches) < top_n: general_matches = self._general_matching(description, weights, top_n - len(matches)) for match in general_matches: if match['breed'] not in [m['breed'] for m in matches]: match['priority'] = 3 if match['final_score'] >= 0.4: # 分數門檻 matches.append(match) # 最終排序 matches.sort(key=lambda x: ( -x.get('characteristics_score', 0), # 首先考慮特徵匹配度 -x.get('final_score', 0), # 然後是總分 -x.get('base_score', 0), # 最後是基礎分數 x.get('breed', '') # 字母順序 )) # 取前N個結果 final_matches = matches[:top_n] # 更新排名 for i, match in enumerate(final_matches, 1): match['rank'] = i return final_matches except Exception as e: print(f"Error in match_user_preference: {str(e)}") return []