Spaces:
Running
on
Zero
Running
on
Zero
from dataclasses import dataclass | |
from typing import Dict, List, Any, Optional | |
import math | |
import random | |
import numpy as np | |
import traceback | |
from breed_health_info import breed_health_info | |
from breed_noise_info import breed_noise_info | |
from dog_database import get_dog_description | |
from dimension_score_calculator import DimensionScoreCalculator | |
from score_integration_manager import ScoreIntegrationManager, UserPreferences | |
from bonus_penalty_engine import BonusPenaltyEngine | |
class DimensionalScore: | |
"""維度分數結構""" | |
dimension_name: str | |
raw_score: float # 原始計算分數 (0.0-1.0) | |
weight: float # 維度權重 (0.0-1.0) | |
display_score: float # 顯示分數 (0.0-1.0) | |
explanation: str # 評分說明 | |
class UnifiedBreedScore: | |
"""統一品種評分結果""" | |
breed_name: str | |
overall_score: float # 總體分數 (0.0-1.0) | |
dimensional_scores: List[DimensionalScore] # 各維度分數 | |
bonus_factors: Dict[str, float] # 加分因素 | |
penalty_factors: Dict[str, float] # 扣分因素 | |
confidence_level: float # 推薦信心度 (0.0-1.0) | |
match_explanation: str # 匹配說明 | |
warnings: List[str] # 警告訊息 | |
# 初始化計算器實例 | |
_dimension_calculator = DimensionScoreCalculator() | |
_score_manager = ScoreIntegrationManager() | |
_bonus_engine = BonusPenaltyEngine() | |
def apply_size_filter(breed_score: float, user_preference: str, breed_size: str) -> float: | |
""" | |
強過濾機制,基於用戶的體型偏好過濾品種 | |
Parameters: | |
breed_score (float): 原始品種評分 | |
user_preference (str): 用戶偏好的體型 | |
breed_size (str): 品種的實際體型 | |
Returns: | |
float: 過濾後的評分,如果體型不符合會返回 0 | |
""" | |
return _score_manager.apply_size_filter(breed_score, user_preference, breed_size) | |
def calculate_breed_bonus(breed_info: dict, user_prefs: 'UserPreferences') -> float: | |
"""計算品種額外加分""" | |
return BonusPenaltyEngine.calculate_breed_bonus(breed_info, user_prefs) | |
def calculate_additional_factors(breed_info: dict, user_prefs: 'UserPreferences') -> dict: | |
""" | |
計算額外的評估因素,結合品種特性與使用者需求的全面評估系統 | |
""" | |
return BonusPenaltyEngine.calculate_additional_factors(breed_info, user_prefs) | |
def calculate_compatibility_score(breed_info: dict, user_prefs: UserPreferences) -> dict: | |
"""計算品種與使用者條件的相容性分數""" | |
try: | |
print(f"Processing breed: {breed_info.get('Breed', 'Unknown')}") | |
print(f"Breed info keys: {breed_info.keys()}") | |
if 'Size' not in breed_info: | |
print("Missing Size information") | |
raise KeyError("Size information missing") | |
if user_prefs.size_preference != "no_preference": | |
if breed_info['Size'].lower() != user_prefs.size_preference.lower(): | |
return { | |
'space': 0, | |
'exercise': 0, | |
'grooming': 0, | |
'experience': 0, | |
'health': 0, | |
'noise': 0, | |
'overall': 0, | |
'adaptability_bonus': 0 | |
} | |
# 計算所有基礎分數並整合到字典中 | |
scores = { | |
'space': _dimension_calculator.calculate_space_score( | |
breed_info['Size'], | |
user_prefs.living_space, | |
user_prefs.yard_access != 'no_yard', | |
breed_info.get('Exercise Needs', 'Moderate') | |
), | |
'exercise': _dimension_calculator.calculate_exercise_score( | |
breed_info.get('Exercise Needs', 'Moderate'), | |
user_prefs.exercise_time, | |
user_prefs.exercise_type, | |
breed_info['Size'], | |
user_prefs.living_space, | |
breed_info | |
), | |
'grooming': _dimension_calculator.calculate_grooming_score( | |
breed_info.get('Grooming Needs', 'Moderate'), | |
user_prefs.grooming_commitment.lower(), | |
breed_info['Size'] | |
), | |
'experience': _dimension_calculator.calculate_experience_score( | |
breed_info.get('Care Level', 'Moderate'), | |
user_prefs.experience_level, | |
breed_info.get('Temperament', '') | |
), | |
'health': _dimension_calculator.calculate_health_score( | |
breed_info.get('Breed', ''), | |
user_prefs.health_sensitivity | |
), | |
'noise': _dimension_calculator.calculate_noise_score( | |
breed_info.get('Breed', ''), | |
user_prefs.noise_tolerance, | |
user_prefs.living_space, | |
user_prefs.has_children, | |
user_prefs.children_age | |
) | |
} | |
final_score = _score_manager.calculate_breed_compatibility_score( | |
scores=scores, | |
user_prefs=user_prefs, | |
breed_info=breed_info | |
) | |
# 計算環境適應性加成 | |
adaptability_bonus = _score_manager.calculate_environmental_fit(breed_info, user_prefs) | |
if (breed_info.get('Exercise Needs') == "Very High" and | |
user_prefs.living_space == "apartment" and | |
user_prefs.exercise_time < 90): | |
final_score *= 0.85 # 高運動需求但條件不足的懲罰 | |
# 整合最終分數和加成 | |
combined_score = (final_score * 0.9) + (adaptability_bonus * 0.1) | |
# 體型過濾 | |
filtered_score = apply_size_filter( | |
breed_score=combined_score, | |
user_preference=user_prefs.size_preference, | |
breed_size=breed_info['Size'] | |
) | |
final_score = _bonus_engine.amplify_score_extreme(filtered_score) | |
# 更新並返回完整的評分結果 | |
scores.update({ | |
'overall': final_score, | |
'size': breed_info['Size'], | |
'adaptability_bonus': adaptability_bonus | |
}) | |
return scores | |
except Exception as e: | |
print(f"\n!!!!! Critical Error Occurred !!!!!") | |
print(f"Error Type: {type(e).__name__}") | |
print(f"Error Message: {str(e)}") | |
print(f"Full Error Traceback:") | |
print(traceback.format_exc()) | |
return {k: 0.6 for k in ['space', 'exercise', 'grooming', 'experience', 'health', 'noise', 'overall']} | |
def calculate_environmental_fit(breed_info: dict, user_prefs: UserPreferences) -> float: | |
"""計算品種與環境的適應性加成""" | |
return _score_manager.calculate_environmental_fit(breed_info, user_prefs) | |
def calculate_breed_compatibility_score(scores: dict, user_prefs: UserPreferences, breed_info: dict) -> float: | |
"""計算品種相容性總分""" | |
return _score_manager.calculate_breed_compatibility_score(scores, user_prefs, breed_info) | |
def amplify_score_extreme(score: float) -> float: | |
""" | |
優化分數分布,提供更有意義的評分範圍。 | |
純粹進行數學轉換,不依賴外部資訊。 | |
Parameters: | |
score: 原始評分(0-1之間的浮點數) | |
Returns: | |
float: 調整後的評分(0-1之間的浮點數) | |
""" | |
return _bonus_engine.amplify_score_extreme(score) | |
class UnifiedScoringSystem: | |
"""統一評分系統核心類""" | |
def __init__(self): | |
"""初始化評分系統""" | |
self.dimension_weights = { | |
'space_compatibility': 0.30, # Increased from 0.25 | |
'exercise_compatibility': 0.25, # Increased from 0.20 | |
'grooming_compatibility': 0.10, # Reduced from 0.15 | |
'experience_compatibility': 0.10, # Reduced from 0.15 | |
'noise_compatibility': 0.15, # Adjusted | |
'family_compatibility': 0.10 # Added | |
} | |
random.seed(42) # 確保一致性 | |
def calculate_space_compatibility(self, breed_info: Dict, user_prefs: UserPreferences) -> DimensionalScore: | |
"""計算空間適配性分數""" | |
breed_size = breed_info.get('Size', 'Medium').lower() | |
living_space = user_prefs.living_space | |
yard_access = user_prefs.yard_access | |
# 基礎空間評分邏輯 | |
space_score = 0.5 # 基礎分數 | |
explanation_parts = [] | |
# Enhanced size-space matrix with stricter penalties | |
size_space_matrix = { | |
'apartment': { | |
'toy': 0.95, 'small': 0.90, 'medium': 0.50, # Reduced medium score | |
'large': 0.15, 'giant': 0.05 # Severe penalties for large/giant | |
}, | |
'house_small': { | |
'toy': 0.85, 'small': 0.90, 'medium': 0.85, | |
'large': 0.60, 'giant': 0.30 # Still penalize giant breeds | |
}, | |
'house_medium': { # Added for medium houses | |
'toy': 0.80, 'small': 0.85, 'medium': 0.95, | |
'large': 0.85, 'giant': 0.60 # Giants still not ideal | |
}, | |
'house_large': { | |
'toy': 0.75, 'small': 0.80, 'medium': 0.90, | |
'large': 0.95, 'giant': 0.95 | |
} | |
} | |
# Determine actual living space category | |
if 'apartment' in living_space or 'small' in living_space: | |
space_category = 'apartment' | |
elif 'medium' in living_space: | |
space_category = 'house_medium' | |
elif 'large' in living_space: | |
space_category = 'house_large' | |
else: | |
space_category = 'house_small' | |
# Get base score from matrix | |
base_score = size_space_matrix[space_category].get( | |
self._normalize_size(breed_size), 0.5 | |
) | |
# Apply additional penalties for exercise needs in small spaces | |
if space_category == 'apartment': | |
exercise_needs = breed_info.get('Exercise Needs', '').lower() | |
if 'high' in exercise_needs: | |
base_score *= 0.7 # 30% additional penalty | |
if 'very high' in exercise_needs: | |
base_score *= 0.5 # 50% additional penalty | |
space_score = base_score | |
explanation_parts = [] | |
if base_score < 0.3: | |
explanation_parts.append(f"Poor match: {breed_size} dog in {space_category}") | |
elif base_score < 0.7: | |
explanation_parts.append(f"Moderate match: {breed_size} dog in {space_category}") | |
else: | |
explanation_parts.append(f"Good match: {breed_size} dog in {space_category}") | |
# 院子需求調整 | |
if yard_access == 'private_yard': | |
space_score = min(1.0, space_score + 0.1) | |
explanation_parts.append("Private yard bonus") | |
elif yard_access == 'no_yard' and breed_size in ['large', 'giant']: | |
space_score *= 0.7 | |
explanation_parts.append("Large dog without yard penalty") | |
# 運動需求考量 | |
exercise_needs = breed_info.get('Exercise Needs', 'Moderate').lower() | |
if exercise_needs in ['high', 'very high'] and living_space == 'apartment': | |
space_score *= 0.8 | |
explanation_parts.append("High exercise needs in apartment limitation") | |
explanation = "; ".join(explanation_parts) | |
return DimensionalScore( | |
dimension_name='space_compatibility', | |
raw_score=space_score, | |
weight=self.dimension_weights['space_compatibility'], | |
display_score=space_score, | |
explanation=explanation | |
) | |
def calculate_exercise_compatibility(self, breed_info: Dict, user_prefs: UserPreferences) -> DimensionalScore: | |
"""計算運動適配性分數""" | |
breed_exercise_needs = breed_info.get('Exercise Needs', 'Moderate').lower() | |
user_exercise_time = user_prefs.exercise_time | |
user_exercise_type = user_prefs.exercise_type | |
# 運動需求映射 | |
exercise_requirements = { | |
'low': {'min_time': 20, 'ideal_time': 30}, | |
'moderate': {'min_time': 45, 'ideal_time': 60}, | |
'high': {'min_time': 90, 'ideal_time': 120}, | |
'very high': {'min_time': 120, 'ideal_time': 180} | |
} | |
breed_req = exercise_requirements.get(breed_exercise_needs, exercise_requirements['moderate']) | |
# 基礎時間匹配度 | |
if user_exercise_time >= breed_req['ideal_time']: | |
time_score = 1.0 | |
time_explanation = "Sufficient exercise time" | |
elif user_exercise_time >= breed_req['min_time']: | |
time_score = 0.7 + 0.3 * (user_exercise_time - breed_req['min_time']) / (breed_req['ideal_time'] - breed_req['min_time']) | |
time_explanation = "Exercise time meets basic requirements" | |
else: | |
time_score = 0.3 * user_exercise_time / breed_req['min_time'] | |
time_explanation = "Insufficient exercise time" | |
# Enhanced compatibility matrix | |
breed_level = self._parse_exercise_level(breed_exercise_needs) | |
user_level = self._get_user_exercise_level(user_exercise_time) | |
compatibility_matrix = { | |
('low', 'low'): 1.0, | |
('low', 'moderate'): 0.85, | |
('low', 'high'): 0.40, # Stronger penalty | |
('low', 'very high'): 0.15, # Severe penalty | |
('moderate', 'low'): 0.70, | |
('moderate', 'moderate'): 1.0, | |
('moderate', 'high'): 0.85, | |
('moderate', 'very high'): 0.60, | |
('high', 'low'): 0.20, # Severe penalty | |
('high', 'moderate'): 0.65, | |
('high', 'high'): 1.0, | |
('high', 'very high'): 0.90, | |
} | |
base_score = compatibility_matrix.get((user_level, breed_level), 0.5) | |
# Check for exercise type compatibility | |
if hasattr(user_prefs, 'exercise_type'): | |
exercise_type_bonus = self._calculate_exercise_type_match( | |
breed_info, user_prefs.exercise_type | |
) | |
base_score = base_score * 0.8 + exercise_type_bonus * 0.2 | |
exercise_score = base_score | |
explanation = f"{user_level} user with {breed_level} exercise breed" | |
return DimensionalScore( | |
dimension_name='exercise_compatibility', | |
raw_score=exercise_score, | |
weight=self.dimension_weights['exercise_compatibility'], | |
display_score=exercise_score, | |
explanation=explanation | |
) | |
def _normalize_size(self, breed_size: str) -> str: | |
"""Normalize breed size string""" | |
breed_size = breed_size.lower() | |
if 'giant' in breed_size: | |
return 'giant' | |
elif 'large' in breed_size: | |
return 'large' | |
elif 'medium' in breed_size: | |
return 'medium' | |
elif 'small' in breed_size: | |
return 'small' | |
elif 'toy' in breed_size or 'tiny' in breed_size: | |
return 'toy' | |
else: | |
return 'medium' | |
def _parse_exercise_level(self, exercise_description: str) -> str: | |
"""Parse exercise level from description""" | |
exercise_lower = exercise_description.lower() | |
if any(term in exercise_lower for term in ['very high', 'extremely high', 'intense']): | |
return 'very high' | |
elif 'high' in exercise_lower: | |
return 'high' | |
elif any(term in exercise_lower for term in ['low', 'minimal']): | |
return 'low' | |
else: | |
return 'moderate' | |
def _get_user_exercise_level(self, minutes: int) -> str: | |
"""Convert exercise minutes to level""" | |
if minutes < 30: | |
return 'low' | |
elif minutes < 60: | |
return 'moderate' | |
else: | |
return 'high' | |
def _calculate_exercise_type_match(self, breed_info: Dict, user_type: str) -> float: | |
"""Calculate exercise type compatibility""" | |
breed_description = str(breed_info.get('Exercise Needs', '')).lower() | |
if user_type == 'active_training': | |
if any(term in breed_description for term in ['agility', 'working', 'herding']): | |
return 1.0 | |
elif 'sprint' in breed_description: | |
return 0.6 # Afghan Hound case | |
elif user_type == 'light_walks': | |
if any(term in breed_description for term in ['gentle', 'moderate', 'light']): | |
return 1.0 | |
elif any(term in breed_description for term in ['intense', 'vigorous']): | |
return 0.3 | |
return 0.7 # Default moderate match | |
def calculate_unified_breed_score(self, breed_name: str, user_prefs: UserPreferences) -> UnifiedBreedScore: | |
"""計算統一品種分數""" | |
# 獲取品種資訊 | |
try: | |
breed_info = get_dog_description(breed_name.replace(' ', '_')) | |
except ImportError: | |
breed_info = None | |
if not breed_info: | |
return self._get_default_breed_score(breed_name) | |
breed_info['breed_name'] = breed_name | |
# 計算各維度分數 (簡化版,包含主要維度) | |
dimensional_scores = [ | |
self.calculate_space_compatibility(breed_info, user_prefs), | |
self.calculate_exercise_compatibility(breed_info, user_prefs) | |
] | |
# 計算加權總分 | |
weighted_sum = sum(score.raw_score * score.weight for score in dimensional_scores) | |
total_weight = sum(score.weight for score in dimensional_scores) | |
base_overall_score = weighted_sum / total_weight if total_weight > 0 else 0.5 | |
# 計算加分和扣分因素 | |
bonus_factors = {} | |
penalty_factors = {} | |
# 應用加分扣分 | |
overall_score = max(0.0, min(1.0, base_overall_score)) | |
return UnifiedBreedScore( | |
breed_name=breed_name, | |
overall_score=overall_score, | |
dimensional_scores=dimensional_scores, | |
bonus_factors=bonus_factors, | |
penalty_factors=penalty_factors, | |
confidence_level=0.8, | |
match_explanation=f"Breed assessment for {breed_name} based on unified scoring system", | |
warnings=[] | |
) | |
def _get_default_breed_score(self, breed_name: str) -> UnifiedBreedScore: | |
"""獲取預設品種分數""" | |
default_dimensional_scores = [ | |
DimensionalScore('space_compatibility', 0.6, 0.25, 0.6, 'Insufficient information'), | |
DimensionalScore('exercise_compatibility', 0.6, 0.20, 0.6, 'Insufficient information') | |
] | |
return UnifiedBreedScore( | |
breed_name=breed_name, | |
overall_score=0.6, | |
dimensional_scores=default_dimensional_scores, | |
bonus_factors={}, | |
penalty_factors={}, | |
confidence_level=0.3, | |
match_explanation="Insufficient data available, recommend further research on this breed", | |
warnings=["Incomplete breed information, scores are for reference only"] | |
) | |
def calculate_unified_breed_scores(breed_list: List[str], user_prefs: UserPreferences) -> List[UnifiedBreedScore]: | |
"""計算多個品種的統一分數""" | |
scoring_system = UnifiedScoringSystem() | |
scores = [] | |
for breed in breed_list: | |
breed_score = scoring_system.calculate_unified_breed_score(breed, user_prefs) | |
scores.append(breed_score) | |
# 按總分排序 | |
scores.sort(key=lambda x: x.overall_score, reverse=True) | |
return scores | |