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', '')}
Moderate to high barker
Alert watch dog
Attention-seeking barks
Social vocalizations
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.
Patellar luxation
Progressive retinal atrophy
Von Willebrand's disease
Open fontanel
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 []