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"""
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) -> float:
"""
- 確保100%時完全填滿
- 更線性的視覺呈現
- 保持合理的視覺比例
"""
# 基礎寬度計算
if score >= 1.0:
return 100.0 # 確保100%時完全填滿
# 一般情況的寬度計算
if score > 0.9:
# 高分區間線性延伸
width = 90 + (score - 0.9) * 100
elif score > 0.7:
# 中高分區間稍微展開
width = 70 + (score - 0.7) * 100
else:
# 基礎線性關係
width = score * 100
# 加入微小的隨機變化,使顯示更自然
import random
width += random.uniform(-0.5, 0.5)
# 確保範圍合理
return min(99.5, max(20, width)) if score < 1.0 else 100.0
html_content = "
"
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 = {
'space': _generate_progress_bar(scores['space']),
'exercise': _generate_progress_bar(scores['exercise']),
'grooming': _generate_progress_bar(scores['grooming']),
'experience': _generate_progress_bar(scores['experience']),
'noise': _generate_progress_bar(scores['noise'])
}
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:
{display_scores['space'] if is_description_search else scores['space']*100:.1f}%
Exercise Match:
{display_scores['exercise'] if is_description_search else scores['exercise']*100:.1f}%
Grooming Match:
{display_scores['grooming'] if is_description_search else scores['grooming']*100:.1f}%
Experience Match:
{display_scores['experience'] if is_description_search else scores['experience']*100:.1f}%
Noise Compatibility:
ⓘ
Noise Compatibility Score:
• Based on your noise tolerance preference
• Considers breed's typical noise level
• Accounts for living environment
{display_scores['noise'] if is_description_search else scores['noise']*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 []