Spaces:
Running
on
Zero
Running
on
Zero
import random | |
import hashlib | |
import numpy as np | |
import sqlite3 | |
import re | |
import traceback | |
from typing import List, Dict, Tuple, Optional, Any | |
from dataclasses import dataclass | |
from sentence_transformers import SentenceTransformer | |
import torch | |
from sklearn.metrics.pairwise import cosine_similarity | |
from dog_database import get_dog_description | |
from breed_health_info import breed_health_info | |
from breed_noise_info import breed_noise_info | |
from scoring_calculation_system import UserPreferences, calculate_compatibility_score, UnifiedScoringSystem, calculate_unified_breed_scores | |
from query_understanding import QueryUnderstandingEngine, analyze_user_query | |
from constraint_manager import ConstraintManager, apply_breed_constraints | |
from multi_head_scorer import MultiHeadScorer, score_breed_candidates, BreedScore | |
from score_calibrator import ScoreCalibrator, calibrate_breed_scores | |
from config_manager import get_config_manager, get_standardized_breed_data | |
class MatchingScoreCalculator: | |
""" | |
匹配評分計算器 | |
處理多維度匹配計算、約束條件過濾和評分校準 | |
""" | |
def __init__(self, breed_list: List[str]): | |
"""初始化匹配評分計算器""" | |
self.breed_list = breed_list | |
def apply_size_distribution_correction(self, recommendations: List[Dict]) -> List[Dict]: | |
"""應用尺寸分佈修正以防止大型品種偏差""" | |
if len(recommendations) < 10: | |
return recommendations | |
# 分析尺寸分佈 | |
size_counts = {'toy': 0, 'small': 0, 'medium': 0, 'large': 0, 'giant': 0} | |
for rec in recommendations: | |
breed_info = get_dog_description(rec['breed']) | |
if breed_info: | |
size = self._normalize_breed_size(breed_info.get('Size', 'Medium')) | |
size_counts[size] += 1 | |
total_recs = len(recommendations) | |
large_giant_ratio = (size_counts['large'] + size_counts['giant']) / total_recs | |
# 如果超過 70% 是大型/巨型品種,應用修正 | |
if large_giant_ratio > 0.7: | |
corrected_recommendations = [] | |
size_quotas = {'toy': 2, 'small': 4, 'medium': 6, 'large': 2, 'giant': 1} | |
current_counts = {'toy': 0, 'small': 0, 'medium': 0, 'large': 0, 'giant': 0} | |
# 第一輪:在配額內添加品種 | |
for rec in recommendations: | |
breed_info = get_dog_description(rec['breed']) | |
if breed_info: | |
size = self._normalize_breed_size(breed_info.get('Size', 'Medium')) | |
if current_counts[size] < size_quotas[size]: | |
corrected_recommendations.append(rec) | |
current_counts[size] += 1 | |
# 第二輪:用最佳剩餘候選品種填滿剩餘位置 | |
remaining_slots = 15 - len(corrected_recommendations) | |
remaining_breeds = [rec for rec in recommendations if rec not in corrected_recommendations] | |
corrected_recommendations.extend(remaining_breeds[:remaining_slots]) | |
return corrected_recommendations | |
return recommendations | |
def _normalize_breed_size(self, size: str) -> str: | |
"""標準化品種尺寸到標準分類""" | |
if not isinstance(size, str): | |
return 'medium' | |
size_lower = size.lower() | |
if any(term in size_lower for term in ['toy', 'tiny']): | |
return 'toy' | |
elif 'small' in size_lower: | |
return 'small' | |
elif 'medium' in size_lower: | |
return 'medium' | |
elif 'large' in size_lower: | |
return 'large' | |
elif any(term in size_lower for term in ['giant', 'extra large']): | |
return 'giant' | |
else: | |
return 'medium' | |
def apply_hard_constraints(self, breed: str, user_input: str, breed_characteristics: Dict[str, Any]) -> float: | |
"""增強硬約束,具有更嚴格的懲罰""" | |
penalty = 0.0 | |
user_text_lower = user_input.lower() | |
# 獲取品種信息 | |
breed_info = get_dog_description(breed) | |
if not breed_info: | |
return 0.0 | |
breed_size = breed_info.get('Size', '').lower() | |
exercise_needs = breed_info.get('Exercise Needs', '').lower() | |
# 公寓居住約束 - 更嚴格 | |
if any(term in user_text_lower for term in ['apartment', 'flat', 'studio', 'small space']): | |
if 'giant' in breed_size: | |
return -2.0 # 完全淘汰 | |
elif 'large' in breed_size: | |
if any(term in exercise_needs for term in ['high', 'very high']): | |
return -2.0 # 完全淘汰 | |
else: | |
penalty -= 0.5 # 仍有顯著懲罰 | |
elif 'medium' in breed_size and 'very high' in exercise_needs: | |
penalty -= 0.6 | |
# 運動不匹配約束 | |
if "don't exercise much" in user_text_lower or "low exercise" in user_text_lower: | |
if any(term in exercise_needs for term in ['very high', 'extreme', 'intense']): | |
return -2.0 # 完全淘汰 | |
elif 'high' in exercise_needs: | |
penalty -= 0.8 | |
# 中等生活方式檢測 | |
if any(term in user_text_lower for term in ['moderate', 'balanced', '30 minutes', 'half hour']): | |
# 懲罰極端情況 | |
if 'giant' in breed_size: | |
penalty -= 0.7 # 對巨型犬的強懲罰 | |
elif 'very high' in exercise_needs: | |
penalty -= 0.5 | |
# 兒童安全(現有邏輯保持但增強) | |
if any(term in user_text_lower for term in ['child', 'kids', 'family', 'baby']): | |
good_with_children = breed_info.get('Good with Children', '').lower() | |
if good_with_children == 'no': | |
return -2.0 # 為了安全完全淘汰 | |
return penalty | |
def calculate_lifestyle_bonus(self, breed_characteristics: Dict[str, Any], | |
lifestyle_keywords: Dict[str, List[str]]) -> float: | |
"""增強生活方式匹配獎勵計算""" | |
bonus = 0.0 | |
penalties = 0.0 | |
# 增強尺寸匹配 | |
breed_size = breed_characteristics.get('size', '').lower() | |
size_prefs = lifestyle_keywords.get('size_preference', []) | |
for pref in size_prefs: | |
if pref in breed_size: | |
bonus += 0.25 # 尺寸匹配的強獎勵 | |
elif (pref == 'small' and 'large' in breed_size) or \ | |
(pref == 'large' and 'small' in breed_size): | |
penalties += 0.15 # 尺寸不匹配的懲罰 | |
# 增強活動水平匹配 | |
breed_exercise = breed_characteristics.get('exercise_needs', '').lower() | |
activity_prefs = lifestyle_keywords.get('activity_level', []) | |
if 'high' in activity_prefs: | |
if 'high' in breed_exercise or 'very high' in breed_exercise: | |
bonus += 0.2 | |
elif 'low' in breed_exercise: | |
penalties += 0.2 | |
elif 'low' in activity_prefs: | |
if 'low' in breed_exercise: | |
bonus += 0.2 | |
elif 'high' in breed_exercise or 'very high' in breed_exercise: | |
penalties += 0.25 | |
elif 'moderate' in activity_prefs: | |
if 'moderate' in breed_exercise: | |
bonus += 0.15 | |
# 增強家庭情況匹配 | |
good_with_children = breed_characteristics.get('good_with_children', 'Yes') | |
family_prefs = lifestyle_keywords.get('family_situation', []) | |
if 'children' in family_prefs: | |
if good_with_children == 'Yes': | |
bonus += 0.15 | |
else: | |
penalties += 0.3 # 對非兒童友好品種的強懲罰 | |
# 增強居住空間匹配 | |
living_prefs = lifestyle_keywords.get('living_space', []) | |
if 'apartment' in living_prefs: | |
if 'small' in breed_size: | |
bonus += 0.2 | |
elif 'medium' in breed_size and 'low' in breed_exercise: | |
bonus += 0.1 | |
elif 'large' in breed_size or 'giant' in breed_size: | |
penalties += 0.2 # 公寓中大型犬的懲罰 | |
# 噪音偏好匹配 | |
noise_prefs = lifestyle_keywords.get('noise_preference', []) | |
temperament = breed_characteristics.get('temperament', '').lower() | |
if 'low' in noise_prefs: | |
# 獎勵安靜品種 | |
if any(term in temperament for term in ['gentle', 'calm', 'quiet']): | |
bonus += 0.1 | |
# 照護水平匹配 | |
grooming_needs = breed_characteristics.get('grooming_needs', '').lower() | |
care_prefs = lifestyle_keywords.get('care_level', []) | |
if 'low' in care_prefs and 'low' in grooming_needs: | |
bonus += 0.1 | |
elif 'high' in care_prefs and 'high' in grooming_needs: | |
bonus += 0.1 | |
elif 'low' in care_prefs and 'high' in grooming_needs: | |
penalties += 0.15 | |
# 特殊需求匹配 | |
special_needs = lifestyle_keywords.get('special_needs', []) | |
if 'guard' in special_needs: | |
if any(term in temperament for term in ['protective', 'alert', 'watchful']): | |
bonus += 0.1 | |
elif 'companion' in special_needs: | |
if any(term in temperament for term in ['affectionate', 'gentle', 'loyal']): | |
bonus += 0.1 | |
# 計算包含懲罰的最終獎勵 | |
final_bonus = bonus - penalties | |
return max(-0.3, min(0.5, final_bonus)) # 允許負獎勵但限制範圍 | |
def apply_intelligent_trait_matching(self, recommendations: List[Dict], user_input: str) -> List[Dict]: | |
"""基於增強關鍵字提取和數據庫挖掘應用智能特徵匹配""" | |
try: | |
# 從用戶輸入提取增強關鍵字 | |
extracted_keywords = self._extract_enhanced_lifestyle_keywords(user_input) | |
# 對每個推薦應用智能特徵匹配 | |
enhanced_recommendations = [] | |
for rec in recommendations: | |
breed_name = rec['breed'].replace(' ', '_') | |
# 獲取品種數據庫信息 | |
breed_info = get_dog_description(breed_name) or {} | |
# 計算智能特徵獎勵 | |
intelligence_bonus = 0.0 | |
trait_match_details = {} | |
# 1. 智力匹配 | |
if extracted_keywords.get('intelligence_preference'): | |
intelligence_pref = extracted_keywords['intelligence_preference'][0] | |
breed_desc = breed_info.get('Description', '').lower() | |
if intelligence_pref == 'high': | |
if any(word in breed_desc for word in ['intelligent', 'smart', 'clever', 'quick to learn', 'trainable']): | |
intelligence_bonus += 0.05 | |
trait_match_details['intelligence_match'] = 'High intelligence match detected' | |
elif any(word in breed_desc for word in ['stubborn', 'independent', 'difficult']): | |
intelligence_bonus -= 0.02 | |
trait_match_details['intelligence_warning'] = 'May be challenging to train' | |
elif intelligence_pref == 'independent': | |
if any(word in breed_desc for word in ['independent', 'stubborn', 'strong-willed']): | |
intelligence_bonus += 0.03 | |
trait_match_details['independence_match'] = 'Independent nature match' | |
# 2. 美容偏好匹配 | |
if extracted_keywords.get('grooming_preference'): | |
grooming_pref = extracted_keywords['grooming_preference'][0] | |
breed_grooming = breed_info.get('Grooming Needs', '').lower() | |
if grooming_pref == 'low' and 'low' in breed_grooming: | |
intelligence_bonus += 0.03 | |
trait_match_details['grooming_match'] = 'Low maintenance grooming match' | |
elif grooming_pref == 'high' and 'high' in breed_grooming: | |
intelligence_bonus += 0.03 | |
trait_match_details['grooming_match'] = 'High maintenance grooming match' | |
elif grooming_pref == 'low' and 'high' in breed_grooming: | |
intelligence_bonus -= 0.04 | |
trait_match_details['grooming_mismatch'] = 'High grooming needs may not suit preferences' | |
# 3. 氣質偏好匹配 | |
if extracted_keywords.get('temperament_preference'): | |
temp_prefs = extracted_keywords['temperament_preference'] | |
breed_temperament = breed_info.get('Temperament', '').lower() | |
breed_desc = breed_info.get('Description', '').lower() | |
temp_text = (breed_temperament + ' ' + breed_desc).lower() | |
for temp_pref in temp_prefs: | |
if temp_pref == 'gentle' and any(word in temp_text for word in ['gentle', 'calm', 'peaceful', 'mild']): | |
intelligence_bonus += 0.04 | |
trait_match_details['temperament_match'] = f'Gentle temperament match: {temp_pref}' | |
elif temp_pref == 'playful' and any(word in temp_text for word in ['playful', 'energetic', 'lively', 'fun']): | |
intelligence_bonus += 0.04 | |
trait_match_details['temperament_match'] = f'Playful temperament match: {temp_pref}' | |
elif temp_pref == 'protective' and any(word in temp_text for word in ['protective', 'guard', 'alert', 'watchful']): | |
intelligence_bonus += 0.04 | |
trait_match_details['temperament_match'] = f'Protective temperament match: {temp_pref}' | |
elif temp_pref == 'friendly' and any(word in temp_text for word in ['friendly', 'social', 'outgoing', 'people']): | |
intelligence_bonus += 0.04 | |
trait_match_details['temperament_match'] = f'Friendly temperament match: {temp_pref}' | |
# 4. 經驗水平匹配 | |
if extracted_keywords.get('experience_level'): | |
exp_level = extracted_keywords['experience_level'][0] | |
breed_desc = breed_info.get('Description', '').lower() | |
if exp_level == 'beginner': | |
# 為初學者偏愛易於處理的品種 | |
if any(word in breed_desc for word in ['easy', 'gentle', 'good for beginners', 'family', 'calm']): | |
intelligence_bonus += 0.06 | |
trait_match_details['beginner_friendly'] = 'Good choice for first-time owners' | |
elif any(word in breed_desc for word in ['challenging', 'dominant', 'requires experience', 'strong-willed']): | |
intelligence_bonus -= 0.08 | |
trait_match_details['experience_warning'] = 'May be challenging for first-time owners' | |
elif exp_level == 'advanced': | |
# 高級用戶可以處理更具挑戰性的品種 | |
if any(word in breed_desc for word in ['working', 'requires experience', 'intelligent', 'strong']): | |
intelligence_bonus += 0.03 | |
trait_match_details['advanced_suitable'] = 'Good match for experienced owners' | |
# 5. 壽命偏好匹配 | |
if extracted_keywords.get('lifespan_preference'): | |
lifespan_pref = extracted_keywords['lifespan_preference'][0] | |
breed_lifespan = breed_info.get('Lifespan', '10-12 years') | |
try: | |
import re | |
years = re.findall(r'\d+', breed_lifespan) | |
if years: | |
avg_years = sum(int(y) for y in years) / len(years) | |
if lifespan_pref == 'long' and avg_years >= 13: | |
intelligence_bonus += 0.02 | |
trait_match_details['longevity_match'] = f'Long lifespan match: {breed_lifespan}' | |
elif lifespan_pref == 'healthy' and avg_years >= 12: | |
intelligence_bonus += 0.02 | |
trait_match_details['health_match'] = f'Healthy lifespan: {breed_lifespan}' | |
except: | |
pass | |
# 將智力獎勵應用到總分 | |
original_score = rec['overall_score'] | |
enhanced_score = min(1.0, original_score + intelligence_bonus) | |
# 創建包含特徵匹配詳細信息的增強推薦 | |
enhanced_rec = rec.copy() | |
enhanced_rec['overall_score'] = enhanced_score | |
enhanced_rec['intelligence_bonus'] = intelligence_bonus | |
enhanced_rec['trait_match_details'] = trait_match_details | |
# 如果發生顯著增強,添加詳細說明 | |
if abs(intelligence_bonus) > 0.02: | |
enhancement_explanation = [] | |
for detail_key, detail_value in trait_match_details.items(): | |
enhancement_explanation.append(detail_value) | |
if enhancement_explanation: | |
current_explanation = enhanced_rec.get('explanation', '') | |
enhanced_explanation = current_explanation + f" Enhanced matching: {'; '.join(enhancement_explanation)}" | |
enhanced_rec['explanation'] = enhanced_explanation | |
enhanced_recommendations.append(enhanced_rec) | |
# 按增強總分重新排序 | |
enhanced_recommendations.sort(key=lambda x: x['overall_score'], reverse=True) | |
# 更新排名 | |
for i, rec in enumerate(enhanced_recommendations): | |
rec['rank'] = i + 1 | |
print(f"Applied intelligent trait matching with average bonus: {sum(r['intelligence_bonus'] for r in enhanced_recommendations) / len(enhanced_recommendations):.3f}") | |
return enhanced_recommendations | |
except Exception as e: | |
print(f"Error in intelligent trait matching: {str(e)}") | |
# 如果特徵匹配失敗,返回原始推薦 | |
return recommendations | |
def _extract_enhanced_lifestyle_keywords(self, user_input: str) -> Dict[str, List[str]]: | |
"""提取增強的生活方式關鍵字(用於智能特徵匹配)""" | |
keywords = { | |
'intelligence_preference': [], | |
'grooming_preference': [], | |
'temperament_preference': [], | |
'experience_level': [], | |
'lifespan_preference': [] | |
} | |
text = user_input.lower() | |
# 智力偏好檢測 | |
smart_terms = ['smart', 'intelligent', 'clever', 'bright', 'quick learner', 'easy to train', 'trainable', 'genius', 'brilliant'] | |
independent_terms = ['independent', 'stubborn', 'strong-willed', 'less trainable', 'thinks for themselves'] | |
if any(term in text for term in smart_terms): | |
keywords['intelligence_preference'].append('high') | |
if any(term in text for term in independent_terms): | |
keywords['intelligence_preference'].append('independent') | |
# 美容偏好檢測 | |
low_grooming_terms = ['low grooming', 'minimal grooming', 'easy care', 'wash and wear', 'no grooming', 'simple coat'] | |
high_grooming_terms = ['high grooming', 'professional grooming', 'lots of care', 'high maintenance coat', 'daily brushing', 'regular grooming'] | |
if any(term in text for term in low_grooming_terms): | |
keywords['grooming_preference'].append('low') | |
if any(term in text for term in high_grooming_terms): | |
keywords['grooming_preference'].append('high') | |
# 氣質偏好檢測 | |
gentle_terms = ['gentle', 'calm', 'peaceful', 'laid back', 'chill', 'mellow', 'docile'] | |
playful_terms = ['playful', 'energetic', 'fun', 'active personality', 'lively', 'spirited', 'bouncy'] | |
protective_terms = ['protective', 'guard', 'watchdog', 'alert', 'vigilant', 'defensive'] | |
friendly_terms = ['friendly', 'social', 'outgoing', 'loves people', 'sociable', 'gregarious'] | |
if any(term in text for term in gentle_terms): | |
keywords['temperament_preference'].append('gentle') | |
if any(term in text for term in playful_terms): | |
keywords['temperament_preference'].append('playful') | |
if any(term in text for term in protective_terms): | |
keywords['temperament_preference'].append('protective') | |
if any(term in text for term in friendly_terms): | |
keywords['temperament_preference'].append('friendly') | |
# 經驗水平檢測 | |
beginner_terms = ['first time', 'beginner', 'new to dogs', 'never had', 'novice', 'inexperienced'] | |
advanced_terms = ['experienced', 'advanced', 'dog expert', 'many dogs before', 'professional', 'seasoned'] | |
if any(term in text for term in beginner_terms): | |
keywords['experience_level'].append('beginner') | |
if any(term in text for term in advanced_terms): | |
keywords['experience_level'].append('advanced') | |
# 壽命偏好檢測 | |
long_lived_terms = ['long lived', 'long lifespan', 'live long', 'many years', '15+ years', 'longevity'] | |
healthy_terms = ['healthy breed', 'few health issues', 'robust', 'hardy', 'strong constitution'] | |
if any(term in text for term in long_lived_terms): | |
keywords['lifespan_preference'].append('long') | |
if any(term in text for term in healthy_terms): | |
keywords['lifespan_preference'].append('healthy') | |
return keywords | |
def calculate_enhanced_matching_score(self, breed: str, breed_info: dict, user_description: str, base_similarity: float) -> dict: | |
"""計算增強的匹配分數,基於用戶描述和品種特性""" | |
try: | |
user_desc = user_description.lower() | |
# 分析用戶需求 | |
space_requirements = self._analyze_space_requirements(user_desc) | |
exercise_requirements = self._analyze_exercise_requirements(user_desc) | |
noise_requirements = self._analyze_noise_requirements(user_desc) | |
size_requirements = self._analyze_size_requirements(user_desc) | |
family_requirements = self._analyze_family_requirements(user_desc) | |
# 獲取品種特性 | |
breed_size = breed_info.get('Size', '').lower() | |
breed_exercise = breed_info.get('Exercise Needs', '').lower() | |
breed_noise = breed_noise_info.get(breed, {}).get('noise_level', 'moderate').lower() | |
breed_temperament = breed_info.get('Temperament', '').lower() | |
breed_good_with_children = breed_info.get('Good with Children', '').lower() | |
# 計算各維度匹配分數 | |
dimension_scores = {} | |
# 空間匹配 (30% 權重) | |
space_score = self._calculate_space_compatibility(space_requirements, breed_size, breed_exercise) | |
dimension_scores['space'] = space_score | |
# 運動需求匹配 (25% 權重) | |
exercise_score = self._calculate_exercise_compatibility(exercise_requirements, breed_exercise) | |
dimension_scores['exercise'] = exercise_score | |
# 噪音匹配 (20% 權重) | |
noise_score = self._calculate_noise_compatibility(noise_requirements, breed_noise) | |
dimension_scores['noise'] = noise_score | |
# 體型匹配 (15% 權重) | |
size_score = self._calculate_size_compatibility(size_requirements, breed_size) | |
dimension_scores['grooming'] = min(0.9, base_similarity + 0.1) # 美容需求基於語意相似度 | |
# 家庭相容性 (10% 權重) | |
family_score = self._calculate_family_compatibility(family_requirements, breed_good_with_children, breed_temperament) | |
dimension_scores['family'] = family_score | |
dimension_scores['experience'] = min(0.9, base_similarity + 0.05) # 經驗需求基於語意相似度 | |
# 應用硬約束過濾 | |
constraint_penalty = self._apply_hard_constraints_enhanced(user_desc, breed_info) | |
# 計算加權總分 - 精確化維度權重配置 | |
# 根據指導建議重新平衡維度權重 | |
weighted_score = ( | |
space_score * 0.30 + # 空間相容性(降低5%) | |
exercise_score * 0.28 + # 運動需求匹配(降低2%) | |
noise_score * 0.18 + # 噪音控制(提升3%) | |
family_score * 0.12 + # 家庭相容性(提升2%) | |
size_score * 0.08 + # 體型匹配(降低2%) | |
min(0.9, base_similarity + 0.1) * 0.04 # 護理需求(新增獨立權重) | |
) | |
# 優化完美匹配獎勵機制 - 降低觸發門檻並增加層次 | |
perfect_match_bonus = 0.0 | |
if space_score >= 0.88 and exercise_score >= 0.88 and noise_score >= 0.85: | |
perfect_match_bonus = 0.08 # 卓越匹配獎勵 | |
elif space_score >= 0.82 and exercise_score >= 0.82 and noise_score >= 0.75: | |
perfect_match_bonus = 0.04 # 優秀匹配獎勵 | |
elif space_score >= 0.75 and exercise_score >= 0.75: | |
perfect_match_bonus = 0.02 # 良好匹配獎勵 | |
# 結合語意相似度與維度匹配 - 調整為75%維度匹配 25%語義相似度 | |
base_combined_score = (weighted_score * 0.75 + base_similarity * 0.25) + perfect_match_bonus | |
# 應用漸進式約束懲罰,但確保基礎分數保障 | |
raw_final_score = base_combined_score + constraint_penalty | |
# 實施動態分數保障機制 - 提升至40-42%基礎分數 | |
# 根據品種特性動態調整基礎分數 | |
base_guaranteed_score = 0.42 # 提升基礎保障分數 | |
# 特殊品種基礎分數調整 | |
high_adaptability_breeds = ['French_Bulldog', 'Pug', 'Golden_Retriever', 'Labrador_Retriever'] | |
if any(breed in breed for breed in high_adaptability_breeds): | |
base_guaranteed_score = 0.45 # 高適應性品種更高基礎分數 | |
# 動態分數分佈優化 | |
if raw_final_score >= base_guaranteed_score: | |
# 對於高分品種,實施適度壓縮避免過度集中 | |
if raw_final_score > 0.85: | |
compression_factor = 0.92 # 輕度壓縮高分 | |
final_score = 0.85 + (raw_final_score - 0.85) * compression_factor | |
else: | |
final_score = raw_final_score | |
final_score = min(0.93, final_score) # 降低最高分數限制 | |
else: | |
# 對於低分品種,使用改進的保障機制 | |
normalized_raw_score = max(0.15, raw_final_score) | |
# 基礎保障75% + 實際計算25%,保持一定區分度 | |
final_score = base_guaranteed_score * 0.75 + normalized_raw_score * 0.25 | |
final_score = max(base_guaranteed_score, min(0.93, final_score)) | |
lifestyle_bonus = max(0.0, weighted_score - base_similarity) | |
return { | |
'final_score': final_score, | |
'weighted_score': weighted_score, | |
'lifestyle_bonus': lifestyle_bonus, | |
'dimension_scores': dimension_scores, | |
'constraint_penalty': constraint_penalty | |
} | |
except Exception as e: | |
print(f"Error in enhanced matching calculation for {breed}: {str(e)}") | |
return { | |
'final_score': base_similarity, | |
'weighted_score': base_similarity, | |
'lifestyle_bonus': 0.0, | |
'dimension_scores': { | |
'space': base_similarity * 0.9, | |
'exercise': base_similarity * 0.85, | |
'grooming': base_similarity * 0.8, | |
'experience': base_similarity * 0.75, | |
'noise': base_similarity * 0.7, | |
'family': base_similarity * 0.65 | |
}, | |
'constraint_penalty': 0.0 | |
} | |
def _analyze_space_requirements(self, user_desc: str) -> dict: | |
"""分析空間需求 - 增強中等活動量識別""" | |
requirements = {'type': 'unknown', 'size': 'medium', 'importance': 0.5} | |
if any(word in user_desc for word in ['apartment', 'small apartment', 'small space', 'condo', 'flat']): | |
requirements['type'] = 'apartment' | |
requirements['size'] = 'small' | |
requirements['importance'] = 0.95 # 提高重要性 | |
elif any(word in user_desc for word in ['medium-sized house', 'medium house', 'townhouse']): | |
requirements['type'] = 'medium_house' | |
requirements['size'] = 'medium' | |
requirements['importance'] = 0.8 # 中等活動量用戶的特殊標記 | |
elif any(word in user_desc for word in ['large house', 'big house', 'yard', 'garden', 'large space', 'backyard']): | |
requirements['type'] = 'house' | |
requirements['size'] = 'large' | |
requirements['importance'] = 0.7 | |
return requirements | |
def _analyze_exercise_requirements(self, user_desc: str) -> dict: | |
"""分析運動需求 - 增強中等活動量識別""" | |
requirements = {'level': 'moderate', 'importance': 0.5} | |
# 低運動量識別 | |
if any(word in user_desc for word in ["don't exercise", "don't exercise much", "low exercise", "minimal", "lazy", "not active"]): | |
requirements['level'] = 'low' | |
requirements['importance'] = 0.95 | |
# 中等運動量的精確識別 | |
elif any(phrase in user_desc for phrase in ['30 minutes', 'half hour', 'moderate', 'balanced', 'walk about']): | |
if 'walk' in user_desc or 'daily' in user_desc: | |
requirements['level'] = 'moderate' | |
requirements['importance'] = 0.85 # 中等活動量的特殊標記 | |
# 高運動量識別 | |
elif any(word in user_desc for word in ['active', 'hiking', 'outdoor activities', 'running', 'outdoors', 'love hiking']): | |
requirements['level'] = 'high' | |
requirements['importance'] = 0.9 | |
return requirements | |
def _analyze_noise_requirements(self, user_desc: str) -> dict: | |
"""分析噪音需求""" | |
requirements = {'tolerance': 'medium', 'importance': 0.5} | |
if any(word in user_desc for word in ['quiet', 'no bark', "won't bark", "doesn't bark", 'silent', 'peaceful']): | |
requirements['tolerance'] = 'low' | |
requirements['importance'] = 0.9 | |
elif any(word in user_desc for word in ['loud', 'barking ok', 'noise ok']): | |
requirements['tolerance'] = 'high' | |
requirements['importance'] = 0.7 | |
return requirements | |
def _analyze_size_requirements(self, user_desc: str) -> dict: | |
"""分析體型需求""" | |
requirements = {'preferred': 'any', 'importance': 0.5} | |
if any(word in user_desc for word in ['small', 'tiny', 'little', 'lap dog', 'compact']): | |
requirements['preferred'] = 'small' | |
requirements['importance'] = 0.8 | |
elif any(word in user_desc for word in ['large', 'big', 'giant']): | |
requirements['preferred'] = 'large' | |
requirements['importance'] = 0.8 | |
return requirements | |
def _analyze_family_requirements(self, user_desc: str) -> dict: | |
"""分析家庭需求""" | |
requirements = {'children': False, 'importance': 0.3} | |
if any(word in user_desc for word in ['children', 'kids', 'family', 'child']): | |
requirements['children'] = True | |
requirements['importance'] = 0.8 | |
return requirements | |
def _calculate_space_compatibility(self, space_req: dict, breed_size: str, breed_exercise: str) -> float: | |
"""計算空間相容性分數 - 增強中等活動量處理""" | |
if space_req['type'] == 'apartment': | |
if 'small' in breed_size or 'toy' in breed_size: | |
base_score = 0.95 | |
elif 'medium' in breed_size: | |
if 'low' in breed_exercise: | |
base_score = 0.75 | |
else: | |
base_score = 0.45 # 降低中型犬在公寓的分數 | |
elif 'large' in breed_size: | |
base_score = 0.05 # 大型犬極度不適合公寓 | |
elif 'giant' in breed_size: | |
base_score = 0.01 # 超大型犬完全不適合公寓 | |
else: | |
base_score = 0.7 | |
elif space_req['type'] == 'medium_house': | |
# 中型房屋的特殊處理 - 適合中等活動量用戶 | |
if 'small' in breed_size or 'toy' in breed_size: | |
base_score = 0.9 | |
elif 'medium' in breed_size: | |
base_score = 0.95 # 中型犬在中型房屋很適合 | |
elif 'large' in breed_size: | |
if 'moderate' in breed_exercise or 'low' in breed_exercise: | |
base_score = 0.8 # 低運動量大型犬還可以 | |
else: | |
base_score = 0.6 # 高運動量大型犬不太適合 | |
elif 'giant' in breed_size: | |
base_score = 0.3 # 超大型犬在中型房屋不太適合 | |
else: | |
base_score = 0.85 | |
else: | |
# 大型房屋的情況 | |
if 'small' in breed_size or 'toy' in breed_size: | |
base_score = 0.85 | |
elif 'medium' in breed_size: | |
base_score = 0.9 | |
elif 'large' in breed_size or 'giant' in breed_size: | |
base_score = 0.95 | |
else: | |
base_score = 0.8 | |
return min(0.95, base_score) | |
def _calculate_exercise_compatibility(self, exercise_req: dict, breed_exercise: str) -> float: | |
"""計算運動需求相容性分數 - 增強中等活動量處理""" | |
if exercise_req['level'] == 'low': | |
if 'low' in breed_exercise or 'minimal' in breed_exercise: | |
return 0.95 | |
elif 'moderate' in breed_exercise: | |
return 0.5 # 降低不匹配分數 | |
elif 'high' in breed_exercise: | |
return 0.1 # 進一步降低高運動需求的匹配 | |
else: | |
return 0.7 | |
elif exercise_req['level'] == 'high': | |
if 'high' in breed_exercise: | |
return 0.95 | |
elif 'moderate' in breed_exercise: | |
return 0.8 | |
elif 'low' in breed_exercise: | |
return 0.6 | |
else: | |
return 0.7 | |
else: # moderate - 中等活動量的精確處理 | |
if 'moderate' in breed_exercise: | |
return 0.95 # 完美匹配 | |
elif 'low' in breed_exercise: | |
return 0.85 # 低運動需求的品種對中等活動量用戶也不錯 | |
elif 'high' in breed_exercise: | |
return 0.5 # 中等活動量用戶不太適合高運動需求品種 | |
else: | |
return 0.75 | |
return 0.6 | |
def _calculate_noise_compatibility(self, noise_req: dict, breed_noise: str) -> float: | |
"""計算噪音相容性分數,更好處理複合等級""" | |
breed_noise_lower = breed_noise.lower() | |
if noise_req['tolerance'] == 'low': | |
if 'low' in breed_noise_lower and 'moderate' not in breed_noise_lower: | |
return 0.95 # 純低噪音 | |
elif 'low-moderate' in breed_noise_lower or 'low to moderate' in breed_noise_lower: | |
return 0.8 # 低到中等噪音,還可接受 | |
elif breed_noise_lower in ['moderate']: | |
return 0.4 # 中等噪音有些問題 | |
elif 'high' in breed_noise_lower: | |
return 0.1 # 高噪音不適合 | |
else: | |
return 0.6 # 未知噪音水平,保守估計 | |
elif noise_req['tolerance'] == 'high': | |
if 'high' in breed_noise_lower: | |
return 0.9 | |
elif 'moderate' in breed_noise_lower: | |
return 0.85 | |
elif 'low' in breed_noise_lower: | |
return 0.8 # 安靜犬對高容忍度的人也很好 | |
else: | |
return 0.8 | |
else: # moderate tolerance | |
if 'moderate' in breed_noise_lower: | |
return 0.9 | |
elif 'low' in breed_noise_lower: | |
return 0.85 | |
elif 'high' in breed_noise_lower: | |
return 0.6 | |
else: | |
return 0.75 | |
return 0.7 | |
def _calculate_size_compatibility(self, size_req: dict, breed_size: str) -> float: | |
"""計算體型相容性分數""" | |
if size_req['preferred'] == 'small': | |
if any(word in breed_size for word in ['small', 'toy', 'tiny']): | |
return 0.9 | |
elif 'medium' in breed_size: | |
return 0.6 | |
else: | |
return 0.3 | |
elif size_req['preferred'] == 'large': | |
if any(word in breed_size for word in ['large', 'giant']): | |
return 0.9 | |
elif 'medium' in breed_size: | |
return 0.7 | |
else: | |
return 0.4 | |
return 0.7 # 無特別偏好 | |
def _calculate_family_compatibility(self, family_req: dict, good_with_children: str, temperament: str) -> float: | |
"""計算家庭相容性分數""" | |
if family_req['children']: | |
if 'yes' in good_with_children.lower(): | |
return 0.9 | |
elif any(word in temperament for word in ['gentle', 'patient', 'friendly']): | |
return 0.8 | |
elif 'no' in good_with_children.lower(): | |
return 0.2 | |
else: | |
return 0.6 | |
return 0.7 | |
def _apply_hard_constraints_enhanced(self, user_desc: str, breed_info: dict) -> float: | |
"""應用品種特性感知的動態懲罰機制""" | |
penalty = 0.0 | |
# 建立懲罰衰減係數和補償機制 | |
penalty_decay_factor = 0.7 | |
breed_adaptability_bonus = 0.0 | |
breed_size = breed_info.get('Size', '').lower() | |
breed_exercise = breed_info.get('Exercise Needs', '').lower() | |
breed_name = breed_info.get('Breed', '').replace(' ', '_') | |
# 公寓空間約束 - 品種特性感知懲罰機制 | |
if 'apartment' in user_desc or 'small apartment' in user_desc: | |
if 'giant' in breed_size: | |
base_penalty = -0.35 # 減少基礎懲罰 | |
# 特定品種適應性補償 | |
adaptable_giants = ['Mastiff', 'Great Dane'] # 相對安靜的巨型犬 | |
if any(adapt_breed in breed_name for adapt_breed in adaptable_giants): | |
breed_adaptability_bonus += 0.08 | |
penalty += base_penalty * penalty_decay_factor | |
elif 'large' in breed_size: | |
base_penalty = -0.25 # 減少大型犬懲罰 | |
# 適合公寓的大型犬補償 | |
apartment_friendly_large = ['Greyhound', 'Great_Dane'] | |
if any(apt_breed in breed_name for apt_breed in apartment_friendly_large): | |
breed_adaptability_bonus += 0.06 | |
penalty += base_penalty * penalty_decay_factor | |
elif 'medium' in breed_size and 'high' in breed_exercise: | |
penalty += -0.15 * penalty_decay_factor # 進一步減少懲罰 | |
# 運動需求不匹配 - 品種特性感知懲罰機制 | |
if any(phrase in user_desc for phrase in ["don't exercise", "not active", "low exercise", "don't exercise much"]): | |
if 'high' in breed_exercise: | |
base_penalty = -0.28 # 減少基礎懲罰 | |
# 低維護高運動犬種補償 | |
adaptable_high_energy = ['Greyhound', 'Whippet'] # 運動爆發型,平時安靜 | |
if any(adapt_breed in breed_name for adapt_breed in adaptable_high_energy): | |
breed_adaptability_bonus += 0.10 | |
penalty += base_penalty * penalty_decay_factor | |
elif 'moderate' in breed_exercise: | |
penalty += -0.08 * penalty_decay_factor # 進一步減少懲罰 | |
# 噪音控制需求不匹配 - 品種特性感知懲罰機制 | |
if any(phrase in user_desc for phrase in ['quiet', "won't bark", "doesn't bark", "silent"]): | |
breed_noise = breed_noise_info.get(breed_name, {}).get('noise_level', 'moderate').lower() | |
if 'high' in breed_noise: | |
base_penalty = -0.18 # 減少基礎懲罰 | |
# 訓練性良好的高噪音品種補償 | |
trainable_vocal_breeds = ['German_Shepherd', 'Golden_Retriever'] | |
if any(train_breed in breed_name for train_breed in trainable_vocal_breeds): | |
breed_adaptability_bonus += 0.05 | |
penalty += base_penalty * penalty_decay_factor | |
elif 'moderate' in breed_noise and 'low' not in breed_noise: | |
penalty += -0.05 * penalty_decay_factor | |
# 體型偏好不匹配 - 漸進式懲罰 | |
if any(phrase in user_desc for phrase in ['small', 'tiny', 'little']): | |
if 'giant' in breed_size: | |
penalty -= 0.35 # 超大型犬懲罰 | |
elif 'large' in breed_size: | |
penalty -= 0.20 # 大型犬懲罰 | |
# 中等活動量用戶的特殊約束處理 - 漸進式懲罰 | |
moderate_activity_terms = ['30 minutes', 'half hour', 'moderate', 'balanced', 'medium-sized house'] | |
if any(term in user_desc for term in moderate_activity_terms): | |
# 超大型犬對中等活動量用戶的適度懲罰 | |
giant_breeds = ['Saint Bernard', 'Tibetan Mastiff', 'Great Dane', 'Mastiff', 'Newfoundland'] | |
if any(giant in breed_name for giant in giant_breeds) or 'giant' in breed_size: | |
penalty -= 0.35 # 適度懲罰,不完全排除 | |
# 中型房屋 + 超大型犬的額外考量 | |
if 'medium-sized house' in user_desc and any(giant in breed_name for giant in giant_breeds): | |
if not any(high_activity in user_desc for high_activity in ['hiking', 'running', 'active', 'outdoor activities']): | |
penalty -= 0.15 # 輕度額外懲罰 | |
# 30分鐘散步對極高運動需求品種的懲罰 | |
if any(term in user_desc for term in ['30 minutes', 'half hour']) and 'walk' in user_desc: | |
high_energy_breeds = ['Siberian Husky', 'Border Collie', 'Jack Russell Terrier', 'Weimaraner'] | |
if any(he_breed in breed_name for he_breed in high_energy_breeds) and 'high' in breed_exercise: | |
penalty -= 0.25 # 適度懲罰極高運動需求品種 | |
# 添加特殊品種適應性補償機制 | |
# 對於邊界適配品種,給予適度補償 | |
boundary_adaptable_breeds = { | |
'Italian_Greyhound': 0.08, # 安靜、低維護的小型犬 | |
'Boston_Bull': 0.06, # 適應性強的小型犬 | |
'Havanese': 0.05, # 友好適應的小型犬 | |
'Silky_terrier': 0.04, # 安靜的玩具犬 | |
'Basset': 0.07 # 低能量但友好的中型犬 | |
} | |
if breed_name in boundary_adaptable_breeds: | |
breed_adaptability_bonus += boundary_adaptable_breeds[breed_name] | |
# 應用品種適應性補償並設置懲罰上限 | |
final_penalty = penalty + breed_adaptability_bonus | |
# 限制最大懲罰,避免單一約束主導評分 | |
final_penalty = max(-0.4, final_penalty) | |
return final_penalty | |
def get_breed_characteristics_enhanced(self, breed: str) -> Dict[str, Any]: | |
"""獲取品種特徵""" | |
breed_info = get_dog_description(breed) | |
if not breed_info: | |
return {} | |
characteristics = { | |
'size': breed_info.get('Size', 'Unknown'), | |
'temperament': breed_info.get('Temperament', ''), | |
'exercise_needs': breed_info.get('Exercise Needs', 'Moderate'), | |
'grooming_needs': breed_info.get('Grooming Needs', 'Moderate'), | |
'good_with_children': breed_info.get('Good with Children', 'Unknown'), | |
'lifespan': breed_info.get('Lifespan', '10-12 years'), | |
'description': breed_info.get('Description', '') | |
} | |
# 添加噪音資訊 | |
noise_info = breed_noise_info.get(breed, {}) | |
characteristics['noise_level'] = noise_info.get('noise_level', 'moderate') | |
return characteristics | |
def get_breed_info_from_standardized(self, standardized_info) -> Dict[str, Any]: | |
"""將標準化品種信息轉換為字典格式""" | |
try: | |
size_map = {1: 'Tiny', 2: 'Small', 3: 'Medium', 4: 'Large', 5: 'Giant'} | |
exercise_map = {1: 'Low', 2: 'Moderate', 3: 'High', 4: 'Very High'} | |
care_map = {1: 'Low', 2: 'Moderate', 3: 'High'} | |
return { | |
'Size': size_map.get(standardized_info.size_category, 'Medium'), | |
'Exercise Needs': exercise_map.get(standardized_info.exercise_level, 'Moderate'), | |
'Grooming Needs': care_map.get(standardized_info.care_complexity, 'Moderate'), | |
'Good with Children': 'Yes' if standardized_info.child_compatibility >= 0.8 else | |
'No' if standardized_info.child_compatibility <= 0.2 else 'Unknown', | |
'Temperament': 'Varies by individual', | |
'Lifespan': '10-12 years', | |
'Description': f'A {size_map.get(standardized_info.size_category, "medium")} sized breed' | |
} | |
except Exception as e: | |
print(f"Error converting standardized info: {str(e)}") | |
return {} | |
def get_fallback_recommendations(self, top_k: int = 15) -> List[Dict[str, Any]]: | |
"""當增強系統失敗時獲取備用推薦""" | |
try: | |
safe_breeds = [ | |
('Labrador Retriever', 0.85), | |
('Golden Retriever', 0.82), | |
('Cavalier King Charles Spaniel', 0.80), | |
('French Bulldog', 0.78), | |
('Boston Terrier', 0.76), | |
('Bichon Frise', 0.74), | |
('Pug', 0.72), | |
('Cocker Spaniel', 0.70) | |
] | |
recommendations = [] | |
for i, (breed, score) in enumerate(safe_breeds[:top_k]): | |
breed_info = get_dog_description(breed.replace(' ', '_')) or {} | |
recommendation = { | |
'breed': breed, | |
'rank': i + 1, | |
'overall_score': score, | |
'final_score': score, | |
'semantic_score': score * 0.8, | |
'comparative_bonus': 0.0, | |
'lifestyle_bonus': 0.0, | |
'size': breed_info.get('Size', 'Unknown'), | |
'temperament': breed_info.get('Temperament', ''), | |
'exercise_needs': breed_info.get('Exercise Needs', 'Moderate'), | |
'grooming_needs': breed_info.get('Grooming Needs', 'Moderate'), | |
'good_with_children': breed_info.get('Good with Children', 'Yes'), | |
'lifespan': breed_info.get('Lifespan', '10-12 years'), | |
'description': breed_info.get('Description', ''), | |
'search_type': 'fallback' | |
} | |
recommendations.append(recommendation) | |
return recommendations | |
except Exception as e: | |
print(f"Error generating fallback recommendations: {str(e)}") | |
return [] | |