Spaces:
Running
on
Zero
Running
on
Zero
| import sqlite3 | |
| import traceback | |
| import random | |
| from typing import List, Dict | |
| from breed_health_info import breed_health_info, default_health_note | |
| from breed_noise_info import breed_noise_info | |
| from dog_database import get_dog_description | |
| from scoring_calculation_system import UserPreferences, calculate_compatibility_score | |
| def get_breed_recommendations(user_prefs: UserPreferences, top_n: int = 15) -> List[Dict]: | |
| """基於使用者偏好推薦狗品種,確保正確的分數排序""" | |
| print(f"Starting get_breed_recommendations with top_n={top_n}") | |
| recommendations = [] | |
| seen_breeds = set() | |
| try: | |
| # 獲取所有品種 | |
| conn = sqlite3.connect('animal_detector.db') | |
| cursor = conn.cursor() | |
| cursor.execute("SELECT Breed FROM AnimalCatalog") | |
| all_breeds = cursor.fetchall() | |
| conn.close() | |
| print(f"Total breeds in database: {len(all_breeds)}") | |
| # 收集所有品種的分數 | |
| for breed_tuple in all_breeds: | |
| breed = breed_tuple[0] | |
| base_breed = breed.split('(')[0].strip() | |
| if base_breed in seen_breeds: | |
| continue | |
| seen_breeds.add(base_breed) | |
| # 獲取品種資訊 | |
| breed_info = get_dog_description(breed) | |
| if not isinstance(breed_info, dict): | |
| continue | |
| # 調整品種尺寸過濾邏輯,避免過度限制候選品種 | |
| if user_prefs.size_preference != "no_preference": | |
| breed_size = breed_info.get('Size', '').lower() | |
| user_size = user_prefs.size_preference.lower() | |
| # 放寬尺寸匹配條件,允許相鄰尺寸的品種通過篩選 | |
| size_compatibility = False | |
| if user_size == 'small': | |
| size_compatibility = breed_size in ['small', 'medium'] | |
| elif user_size == 'medium': | |
| size_compatibility = breed_size in ['small', 'medium', 'large'] | |
| elif user_size == 'large': | |
| size_compatibility = breed_size in ['medium', 'large'] | |
| else: | |
| size_compatibility = True | |
| if not size_compatibility: | |
| continue | |
| # 獲取噪音資訊 | |
| noise_info = breed_noise_info.get(breed, { | |
| "noise_notes": "Noise information not available", | |
| "noise_level": "Unknown", | |
| "source": "N/A" | |
| }) | |
| # 將噪音資訊整合到品種資訊中 | |
| breed_info['noise_info'] = noise_info | |
| # 計算基礎相容性分數 | |
| compatibility_scores = calculate_compatibility_score(breed_info, user_prefs) | |
| # 計算品種特定加分 | |
| breed_bonus = 0.0 | |
| # 壽命加分 | |
| try: | |
| lifespan = breed_info.get('Lifespan', '10-12 years') | |
| years = [int(x) for x in lifespan.split('-')[0].split()[0:1]] | |
| longevity_bonus = min(0.02, (max(years) - 10) * 0.005) | |
| breed_bonus += longevity_bonus | |
| except: | |
| pass | |
| # 性格特徵加分 | |
| temperament = breed_info.get('Temperament', '').lower() | |
| positive_traits = ['friendly', 'gentle', 'affectionate', 'intelligent'] | |
| negative_traits = ['aggressive', 'stubborn', 'dominant'] | |
| breed_bonus += sum(0.01 for trait in positive_traits if trait in temperament) | |
| breed_bonus -= sum(0.01 for trait in negative_traits if trait in temperament) | |
| # 與孩童相容性加分 | |
| if user_prefs.has_children: | |
| if breed_info.get('Good with Children') == 'Yes': | |
| breed_bonus += 0.02 | |
| elif breed_info.get('Good with Children') == 'No': | |
| breed_bonus -= 0.03 | |
| # 噪音相關加分 | |
| if user_prefs.noise_tolerance == 'low': | |
| if noise_info['noise_level'].lower() == 'high': | |
| breed_bonus -= 0.03 | |
| elif noise_info['noise_level'].lower() == 'low': | |
| breed_bonus += 0.02 | |
| elif user_prefs.noise_tolerance == 'high': | |
| if noise_info['noise_level'].lower() == 'high': | |
| breed_bonus += 0.01 | |
| # 計算最終分數並加入自然變異 | |
| breed_hash = hash(breed) | |
| random.seed(breed_hash) | |
| # Add small natural variation to avoid identical scores | |
| natural_variation = random.uniform(-0.008, 0.008) | |
| breed_bonus = round(breed_bonus + natural_variation, 4) | |
| final_score = round(min(1.0, compatibility_scores['overall'] + breed_bonus), 4) | |
| recommendations.append({ | |
| 'breed': breed, | |
| 'base_score': round(compatibility_scores['overall'], 4), | |
| 'bonus_score': round(breed_bonus, 4), | |
| 'final_score': final_score, | |
| 'scores': compatibility_scores, | |
| 'info': breed_info, | |
| 'noise_info': noise_info | |
| }) | |
| print(f"Breeds after filtering: {len(recommendations)}") | |
| # 嚴格按照 final_score 排序 | |
| recommendations.sort(key=lambda x: (round(-x['final_score'], 4), x['breed'])) | |
| # 修正後的推薦選擇邏輯,移除有問題的分數比較 | |
| final_recommendations = [] | |
| # 直接選取前 top_n 個品種,確保返回完整數量 | |
| for i, rec in enumerate(recommendations[:top_n]): | |
| rec['rank'] = i + 1 | |
| final_recommendations.append(rec) | |
| print(f"Final recommendations count: {len(final_recommendations)}") | |
| # 驗證最終排序 | |
| for i in range(len(final_recommendations)-1): | |
| current = final_recommendations[i] | |
| next_rec = final_recommendations[i+1] | |
| if current['final_score'] < next_rec['final_score']: | |
| print(f"Warning: Sorting error detected!") | |
| print(f"#{i+1} {current['breed']}: {current['final_score']}") | |
| print(f"#{i+2} {next_rec['breed']}: {next_rec['final_score']}") | |
| # 交換位置 | |
| final_recommendations[i], final_recommendations[i+1] = \ | |
| final_recommendations[i+1], final_recommendations[i] | |
| # 打印最終結果以供驗證 | |
| print("\nFinal Rankings:") | |
| for rec in final_recommendations: | |
| print(f"#{rec['rank']} {rec['breed']}") | |
| print(f"Base Score: {rec['base_score']:.4f}") | |
| print(f"Bonus: {rec['bonus_score']:.4f}") | |
| print(f"Final Score: {rec['final_score']:.4f}\n") | |
| return final_recommendations | |
| except Exception as e: | |
| print(f"Error in get_breed_recommendations: {str(e)}") | |
| print(f"Traceback: {traceback.format_exc()}") | |
| def _format_dimension_scores(dimension_scores: Dict) -> str: | |
| """Format individual dimension scores as badges""" | |
| if not dimension_scores: | |
| return "" | |
| badges_html = '<div class="dimension-badges">' | |
| for dimension, score in dimension_scores.items(): | |
| if isinstance(score, (int, float)): | |
| score_percent = score * 100 | |
| else: | |
| score_percent = 75 # default | |
| if score_percent >= 80: | |
| badge_class = "badge-high" | |
| elif score_percent >= 60: | |
| badge_class = "badge-medium" | |
| else: | |
| badge_class = "badge-low" | |
| dimension_label = dimension.replace('_', ' ').title() | |
| badges_html += f''' | |
| <span class="dimension-badge {badge_class}"> | |
| {dimension_label}: {score_percent:.0f}% | |
| </span> | |
| ''' | |
| badges_html += '</div>' | |
| return badges_html | |
| def calculate_breed_bonus_factors(breed_info: dict, user_prefs: 'UserPreferences') -> tuple: | |
| """計算品種額外加分因素並返回原因列表""" | |
| bonus = 0.0 | |
| reasons = [] | |
| # 壽命加分 | |
| try: | |
| lifespan = breed_info.get('Lifespan', '10-12 years') | |
| years = [int(x) for x in lifespan.split('-')[0].split()[0:1]] | |
| if max(years) >= 12: | |
| bonus += 0.02 | |
| reasons.append("Above-average lifespan") | |
| except: | |
| pass | |
| # 性格特徵加分 | |
| temperament = breed_info.get('Temperament', '').lower() | |
| if any(trait in temperament for trait in ['friendly', 'gentle', 'affectionate']): | |
| bonus += 0.01 | |
| reasons.append("Positive temperament traits") | |
| # 與孩童相容性 | |
| if breed_info.get('Good with Children') == 'Yes': | |
| bonus += 0.01 | |
| reasons.append("Excellent with children") | |
| return bonus, reasons | |
| def generate_breed_characteristics_data(breed_info: dict) -> List[tuple]: | |
| """生成品種特徵資料列表""" | |
| return [ | |
| ('Size', breed_info.get('Size', 'Unknown')), | |
| ('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')), | |
| ('Temperament', breed_info.get('Temperament', '')), | |
| ('Lifespan', breed_info.get('Lifespan', '10-12 years')), | |
| ('Description', breed_info.get('Description', '')) | |
| ] | |
| def parse_noise_information(noise_info: dict) -> tuple: | |
| """解析噪音資訊並返回結構化資料""" | |
| noise_notes = noise_info.get('noise_notes', '').split('\n') | |
| noise_characteristics = [] | |
| barking_triggers = [] | |
| noise_level = '' | |
| current_section = None | |
| for line in noise_notes: | |
| line = line.strip() | |
| if 'Typical noise characteristics:' in line: | |
| current_section = 'characteristics' | |
| elif 'Noise level:' in line: | |
| noise_level = line.replace('Noise level:', '').strip() | |
| elif 'Barking triggers:' in line: | |
| current_section = 'triggers' | |
| elif line.startswith('•'): | |
| if current_section == 'characteristics': | |
| noise_characteristics.append(line[1:].strip()) | |
| elif current_section == 'triggers': | |
| barking_triggers.append(line[1:].strip()) | |
| return noise_characteristics, barking_triggers, noise_level | |
| def parse_health_information(health_info: dict) -> tuple: | |
| """解析健康資訊並返回結構化資料""" | |
| health_notes = health_info.get('health_notes', '').split('\n') | |
| health_considerations = [] | |
| health_screenings = [] | |
| current_section = None | |
| for line in health_notes: | |
| line = line.strip() | |
| if 'Common breed-specific health considerations' in line: | |
| current_section = 'considerations' | |
| elif 'Recommended health screenings:' in line: | |
| current_section = 'screenings' | |
| elif line.startswith('•'): | |
| if current_section == 'considerations': | |
| health_considerations.append(line[1:].strip()) | |
| elif current_section == 'screenings': | |
| health_screenings.append(line[1:].strip()) | |
| return health_considerations, health_screenings | |
| def generate_dimension_scores_for_display(base_score: float, rank: int, breed: str, | |
| semantic_score: float = 0.7, | |
| comparative_bonus: float = 0.0, | |
| lifestyle_bonus: float = 0.0, | |
| is_description_search: bool = False) -> dict: | |
| """為顯示生成維度分數""" | |
| random.seed(hash(breed) + rank) # 一致的隨機性 | |
| if is_description_search: | |
| # Description search: 創建更自然的分數分佈在50%-95%範圍內 | |
| score_variance = 0.08 if base_score > 0.7 else 0.06 | |
| scores = { | |
| 'space': max(0.50, min(0.95, | |
| base_score * 0.92 + (lifestyle_bonus * 0.5) + random.uniform(-score_variance, score_variance))), | |
| 'exercise': max(0.50, min(0.95, | |
| base_score * 0.88 + (lifestyle_bonus * 0.4) + random.uniform(-score_variance, score_variance))), | |
| 'grooming': max(0.50, min(0.95, | |
| base_score * 0.85 + (comparative_bonus * 0.4) + random.uniform(-score_variance, score_variance))), | |
| 'experience': max(0.50, min(0.95, | |
| base_score * 0.87 + (lifestyle_bonus * 0.3) + random.uniform(-score_variance, score_variance))), | |
| 'noise': max(0.50, min(0.95, | |
| base_score * 0.83 + (lifestyle_bonus * 0.6) + random.uniform(-score_variance, score_variance))), | |
| 'overall': base_score | |
| } | |
| else: | |
| # 傳統搜尋結果的分數結構會在呼叫處理中傳入 | |
| scores = {'overall': base_score} | |
| return scores | |