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
from recommendation_formatter import (
get_breed_recommendations,
_format_dimension_scores,
calculate_breed_bonus_factors,
generate_breed_characteristics_data,
parse_noise_information,
parse_health_information,
generate_dimension_scores_for_display
)
from recommendation_html_formatter import RecommendationHTMLFormatter
def format_recommendation_html(recommendations: List[Dict], is_description_search: bool = False) -> str:
"""統一推薦結果HTML格式化,確保視覺與數值邏輯一致"""
# 創建HTML格式器實例
formatter = RecommendationHTMLFormatter()
# 獲取對應的CSS樣式
html_content = formatter.get_css_styles(is_description_search) + "
"
for rec in recommendations:
breed = rec['breed']
rank = rec.get('rank', 0)
breed_name_for_db = breed.replace(' ', '_')
breed_info_from_db = get_dog_description(breed_name_for_db)
if is_description_search:
# Handle semantic search results structure - use scores directly from semantic recommender
overall_score = rec.get('overall_score', 0.7)
final_score = rec.get('final_score', overall_score) # Use final_score if available
semantic_score = rec.get('semantic_score', 0.7)
comparative_bonus = rec.get('comparative_bonus', 0.0)
lifestyle_bonus = rec.get('lifestyle_bonus', 0.0)
# Use the actual calculated scores without re-computation
base_score = final_score
# Generate dimension scores using the formatter helper
scores = generate_dimension_scores_for_display(
base_score, rank, breed, semantic_score,
comparative_bonus, lifestyle_bonus, is_description_search
)
bonus_score = max(0.0, comparative_bonus + random.uniform(-0.02, 0.02))
info = generate_breed_characteristics_data(breed_info_from_db or {})
info = dict(info) # Convert to dict for compatibility
# Add any missing fields from rec
if not breed_info_from_db:
for key in ['Size', 'Exercise Needs', 'Grooming Needs', 'Good with Children', 'Temperament', 'Lifespan', 'Description']:
if key not in info:
info[key] = rec.get(key.lower().replace(' ', '_'), 'Unknown' if key != 'Description' else '')
# Display scores as percentages with one decimal place
display_scores = {
'space': round(scores['space'] * 100, 1),
'exercise': round(scores['exercise'] * 100, 1),
'grooming': round(scores['grooming'] * 100, 1),
'experience': round(scores['experience'] * 100, 1),
'noise': round(scores['noise'] * 100, 1),
}
else:
# Handle traditional search results structure
scores = rec['scores']
info = rec['info']
final_score = rec.get('final_score', scores['overall'])
bonus_score = rec.get('bonus_score', 0)
# Convert traditional scores to percentage display format with one decimal
display_scores = {
'space': round(scores.get('space', 0) * 100, 1),
'exercise': round(scores.get('exercise', 0) * 100, 1),
'grooming': round(scores.get('grooming', 0) * 100, 1),
'experience': round(scores.get('experience', 0) * 100, 1),
'noise': round(scores.get('noise', 0) * 100, 1),
}
progress_bars = {}
for metric in ['space', 'exercise', 'grooming', 'experience', 'noise']:
if metric in scores:
# 使用顯示分數(百分比)來計算進度條
display_score = display_scores[metric]
bar_data = formatter.generate_progress_bar(display_score, metric, is_percentage_display=True, is_description_search=is_description_search)
progress_bars[metric] = {
'style': f"width: {bar_data['width']}%; background-color: {bar_data['color']};"
}
# bonus
if bonus_score > 0:
# bonus_score 通常是 0-1 範圍,需要轉換為百分比顯示
bonus_percentage = bonus_score * 100
bonus_data = formatter.generate_progress_bar(bonus_percentage, 'bonus', is_percentage_display=True, is_description_search=is_description_search)
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_characteristics, barking_triggers, noise_level = parse_noise_information(noise_info)
health_considerations, health_screenings = parse_health_information(health_info)
# 計算獎勵因素
_, bonus_reasons = calculate_breed_bonus_factors(info, None) # User prefs not needed for display
# 生成品種卡片標題
html_content += formatter.generate_breed_card_header(breed, rank, final_score, is_description_search)
# 品種詳細資訊區域 - 使用格式器方法簡化
tooltip_html = formatter.generate_tooltips_section()
html_content += f"""
Space Compatibility:{tooltip_html}
{display_scores['space']:.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']:.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']:.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']:.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']:.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 ''}
"""
# 使用格式器生成詳細區段
html_content += formatter.generate_detailed_sections_html(
breed, info, noise_characteristics, barking_triggers, noise_level,
health_considerations, health_screenings
)
html_content += """
"""
# 結束 HTML 內容
html_content += ""
return html_content
def format_unified_recommendation_html(recommendations: List[Dict], is_description_search: bool = False) -> str:
"""統一推薦HTML格式化主函數,確保視覺呈現與數值計算完全一致"""
# 創建HTML格式器實例
formatter = RecommendationHTMLFormatter()
if not recommendations:
return '''
🐕
No Recommendations Available
Please try adjusting your preferences or description, and we'll help you find the most suitable breeds.
'''
# 使用格式器的統一CSS樣式
html_content = formatter.unified_css + ""
for rec in recommendations:
breed = rec['breed']
rank = rec.get('rank', 0)
# 統一分數處理
overall_score = rec.get('overall_score', rec.get('final_score', 0.7))
scores = rec.get('scores', {})
# 如果沒有維度分數,基於總分生成一致的維度分數
if not scores:
scores = generate_dimension_scores_for_display(
overall_score, rank, breed, is_description_search=is_description_search
)
# 獲取品種資訊
breed_name_for_db = breed.replace(' ', '_')
breed_info = get_dog_description(breed_name_for_db) or {}
# 維度標籤
dimension_labels = {
'space': '🏠 Space Compatibility',
'exercise': '🏃 Exercise Requirements',
'grooming': '✂️ Grooming Needs',
'experience': '🎓 Experience Level',
'noise': '🔊 Noise Control',
'family': '👨👩👧👦 Family Compatibility'
}
# 維度提示氣泡內容
tooltip_content = {
'space': '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.',
'exercise': 'Exercise Requirements Score:
• Based on your daily exercise time and activity type
• Compares your activity level to the breed\'s exercise needs
• Higher score means your routine aligns well with the breed\'s energy requirements.',
'grooming': 'Grooming Needs 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.',
'experience': 'Experience Level 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.',
'noise': 'Noise Control Score:
• Based on your noise tolerance preference
• Considers breed\'s typical noise level and barking tendencies
• Accounts for living environment and sensitivity to noise.',
'family': 'Family Compatibility Score:
• Evaluates how well the breed fits with your family situation
• Considers children, other pets, and family dynamics
• Higher score means better family compatibility.'
}
# 生成維度分數HTML
dimension_html = ""
for dim, label in dimension_labels.items():
score = scores.get(dim, overall_score * 0.9)
percentage = formatter.format_unified_percentage(score)
progress_bar = formatter.generate_unified_progress_bar(score)
# 為 Find by Description 添加提示氣泡
tooltip_html = ''
if is_description_search:
tooltip_html = f'
i{tooltip_content.get(dim, "")}'
dimension_html += f'''
{label} {tooltip_html}
{percentage}
{progress_bar}
'''
# 生成品種資訊HTML
characteristics = generate_breed_characteristics_data(breed_info)
info_html = ""
for label, value in characteristics:
if label != 'Description': # Skip description as it's shown separately
info_html += f'''
'''
# 生成單個品種卡片HTML
overall_percentage = formatter.format_unified_percentage(overall_score)
overall_progress_bar = formatter.generate_unified_progress_bar(overall_score)
brand_card_html = f'''
{overall_progress_bar}
{dimension_html}
{info_html}
'''
html_content += brand_card_html
html_content += "
"
return html_content