import sqlite3 import traceback 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 format_recommendation_html(recommendations: List[Dict], is_description_search: bool = False) -> str: """將推薦結果格式化為HTML""" html_content = """
""" def _convert_to_display_score(score: float, score_type: str = None) -> int: """ 更改為生成更明顯差異的顯示分數 """ try: # 基礎分數轉換(保持相對關係但擴大差異) if score_type == 'bonus': # Breed Bonus 使用不同的轉換邏輯 base_score = 35 + (score * 60) # 35-95 範圍,差異更大 else: # 其他類型的分數轉換 if score <= 0.3: base_score = 40 + (score * 45) # 40-53.5 範圍 elif score <= 0.6: base_score = 55 + ((score - 0.3) * 55) # 55-71.5 範圍 elif score <= 0.8: base_score = 72 + ((score - 0.6) * 60) # 72-84 範圍 else: base_score = 85 + ((score - 0.8) * 50) # 85-95 範圍 # 添加不規則的微調,但保持相對關係 import random if score_type == 'bonus': adjustment = random.uniform(-2, 2) else: # 根據分數範圍決定調整幅度 if score > 0.8: adjustment = random.uniform(-3, 3) elif score > 0.6: adjustment = random.uniform(-4, 4) else: adjustment = random.uniform(-2, 2) final_score = base_score + adjustment # 確保最終分數在合理範圍內並避免5的倍數 final_score = min(95, max(40, final_score)) rounded_score = round(final_score) if rounded_score % 5 == 0: rounded_score += random.choice([-1, 1]) return rounded_score except Exception as e: print(f"Error in convert_to_display_score: {str(e)}") return 70 def _generate_progress_bar(score: float, score_type: str = None) -> dict: """ 生成進度條的寬度和顏色 Parameters: score: 原始分數 (0-1 之間的浮點數) score_type: 分數類型,用於特殊處理某些類型的分數 Returns: dict: 包含寬度和顏色的字典 """ # 計算寬度 if score_type == 'bonus': # Breed Bonus 特殊的計算方式 width = min(100, max(5, 10 + (score * 300))) else: # 一般分數的計算 if score >= 0.9: width = 90 + (score - 0.9) * 100 elif score >= 0.7: width = 70 + (score - 0.7) * 100 elif score >= 0.5: width = 40 + (score - 0.5) * 150 elif score >= 0.3: width = 20 + (score - 0.3) * 100 else: width = max(5, score * 66.7) # 根據分數決定顏色 if score >= 0.9: color = '#68b36b' # 高分段柔和綠 elif score >= 0.7: color = '#9bcf74' # 中高分段略黃綠 elif score >= 0.5: color = '#d4d880' # 中等分段黃綠 elif score >= 0.3: color = '#e3b583' # 偏低分段柔和橘 else: color = '#e9a098' # 低分段暖紅粉 return { 'width': width, 'color': color } for rec in recommendations: breed = rec['breed'] scores = rec['scores'] info = rec['info'] rank = rec.get('rank', 0) final_score = rec.get('final_score', scores['overall']) bonus_score = rec.get('bonus_score', 0) if is_description_search: display_scores = { 'space': _convert_to_display_score(scores['space'], 'space'), 'exercise': _convert_to_display_score(scores['exercise'], 'exercise'), 'grooming': _convert_to_display_score(scores['grooming'], 'grooming'), 'experience': _convert_to_display_score(scores['experience'], 'experience'), 'noise': _convert_to_display_score(scores['noise'], 'noise') } else: display_scores = scores # 圖片識別使用原始分數 progress_bars = {} for metric in ['space', 'exercise', 'grooming', 'experience', 'noise']: if metric in scores: bar_data = _generate_progress_bar(scores[metric], metric) progress_bars[metric] = { 'style': f"width: {bar_data['width']}%; background-color: {bar_data['color']};" } # bonus if bonus_score > 0: bonus_data = _generate_progress_bar(bonus_score, 'bonus') progress_bars['bonus'] = { 'style': f"width: {bonus_data['width']}%; background-color: {bonus_data['color']};" } health_info = breed_health_info.get(breed, {"health_notes": default_health_note}) noise_info = breed_noise_info.get(breed, { "noise_notes": "Noise information not available", "noise_level": "Unknown", "source": "N/A" }) # 解析噪音資訊 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()) # 生成特徵和觸發因素的HTML noise_characteristics_html = '\n'.join([f'
  • {item}
  • ' for item in noise_characteristics]) barking_triggers_html = '\n'.join([f'
  • {item}
  • ' for item in barking_triggers]) # 處理健康資訊 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()) health_considerations_html = '\n'.join([f'
  • {item}
  • ' for item in health_considerations]) health_screenings_html = '\n'.join([f'
  • {item}
  • ' for item in health_screenings]) # 獎勵原因計算 bonus_reasons = [] temperament = info.get('Temperament', '').lower() if any(trait in temperament for trait in ['friendly', 'gentle', 'affectionate']): bonus_reasons.append("Positive temperament traits") if info.get('Good with Children') == 'Yes': bonus_reasons.append("Excellent with children") try: lifespan = info.get('Lifespan', '10-12 years') years = int(lifespan.split('-')[0]) if years >= 12: bonus_reasons.append("Above-average lifespan") except: pass html_content += f"""

    🏆 #{rank} {breed.replace('_', ' ')} Overall Match: {final_score*100:.1f}%

    Space Compatibility: Space Compatibility Score:
    • Evaluates how well the breed adapts to your living environment
    • Considers if your home (apartment/house) and yard access suit the breed’s size
    • Higher score means the breed fits well in your available space.
    {display_scores['space'] if is_description_search else scores.get('space', 0)*100:.1f}%
    Exercise Match: Exercise Match Score:
    • Based on your daily exercise time and type
    • Compares your activity level to the breed’s exercise needs
    • Higher score means your routine aligns well with the breed’s energy requirements.
    {display_scores['exercise'] if is_description_search else scores.get('exercise', 0)*100:.1f}%
    Grooming Match: Grooming Match Score:
    • Evaluates breed’s grooming needs (coat care, trimming, brushing)
    • Compares these requirements with your grooming commitment level
    • Higher score means the breed’s grooming needs fit your willingness and capability.
    {display_scores['grooming'] if is_description_search else scores.get('grooming', 0)*100:.1f}%
    Experience Match: Experience Match Score:
    • Based on your dog-owning experience level
    • Considers breed’s training complexity, temperament, and handling difficulty
    • Higher score means the breed is more suitable for your experience level.
    {display_scores['experience'] if is_description_search else scores.get('experience', 0)*100:.1f}%
    Noise Compatibility: Noise Compatibility Score:
    • Based on your noise tolerance preference
    • Considers breed's typical noise level and barking tendencies
    • Accounts for living environment and sensitivity to noise.
    {display_scores['noise'] if is_description_search else scores.get('noise', 0)*100:.1f}%
    {f'''
    Breed Bonus: Breed Bonus Points:
    • {('
    • '.join(bonus_reasons)) if bonus_reasons else 'No additional bonus points'}

    Bonus Factors Include:
    • Friendly temperament
    • Child compatibility
    • Longer lifespan
    • Living space adaptability
    {bonus_score*100:.1f}%
    ''' if bonus_score > 0 else ''}

    📋 Breed Details

    📏 Size: Size Categories:
    • Small: Under 20 pounds
    • Medium: 20-60 pounds
    • Large: Over 60 pounds
    {info['Size']}
    🏃 Exercise Needs: Exercise Needs:
    • Low: Short walks
    • Moderate: 1-2 hours daily
    • High: 2+ hours daily
    • Very High: Constant activity
    {info['Exercise Needs']}
    👨‍👩‍👧‍👦 Good with Children: Child Compatibility:
    • Yes: Excellent with kids
    • Moderate: Good with older children
    • No: Better for adult households
    {info['Good with Children']}
    Lifespan: Average Lifespan:
    • Short: 6-8 years
    • Average: 10-15 years
    • Long: 12-20 years
    • Varies by size: Larger breeds typically have shorter lifespans
    {info['Lifespan']}

    📝 Description

    {info.get('Description', '')}

    🔊 Noise Behavior Noise Behavior:
    • Typical vocalization patterns
    • Common triggers and frequency
    • Based on breed characteristics

    Typical noise characteristics:

    Moderate to high barker
    Alert watch dog
    Attention-seeking barks
    Social vocalizations

    Noise level:

    Moderate-High

    Barking triggers:

    Separation anxiety
    Attention needs
    Strange noises
    Excitement

    Source: Compiled from various breed behavior resources, 2024

    Individual dogs may vary in their vocalization patterns.

    Training can significantly influence barking behavior.

    Environmental factors may affect noise levels.

    🏥 Health Insights Health information is compiled from multiple sources including veterinary resources, breed guides, and international canine health databases. Each dog is unique and may vary from these general guidelines.

    Common breed-specific health considerations:

    Patellar luxation
    Progressive retinal atrophy
    Von Willebrand's disease
    Open fontanel

    Recommended health screenings:

    Patella evaluation
    Eye examination
    Blood clotting tests
    Skull development monitoring

    Source: Compiled from various veterinary and breed information resources, 2024

    This information is for reference only and based on breed tendencies.

    Each dog is unique and may not develop any or all of these conditions.

    Always consult with qualified veterinarians for professional advice.

    """ html_content += "
    " return html_content def get_breed_recommendations(user_prefs: UserPreferences, top_n: int = 15) -> List[Dict]: """基於使用者偏好推薦狗品種,確保正確的分數排序""" print("Starting get_breed_recommendations") 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() # 收集所有品種的分數 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() if breed_size != user_size: 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_bonus = round(breed_bonus, 4) final_score = round(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 # 添加噪音資訊到推薦結果 }) # 嚴格按照 final_score 排序 recommendations.sort(key=lambda x: (round(-x['final_score'], 4), x['breed'] )) # 負號降序排列 # 選擇前N名並確保正確排序 final_recommendations = [] last_score = None rank = 1 available_breeds = len(recommendations) max_to_return = min(available_breeds, top_n) # 不會超過實際可用品種數 for rec in recommendations: if len(final_recommendations) >= max_to_return: break current_score = rec['final_score'] if last_score is not None and current_score > last_score: continue rec['rank'] = rank final_recommendations.append(rec) last_score = current_score rank += 1 # 驗證最終排序 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()}") return []