PawMatchAI / smart_breed_matcher.py
DawnC's picture
Upload 3 files
d161865
raw
history blame
36.7 kB
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 []