Spaces:
Running
on
Zero
Running
on
Zero
Update recommendation_html_format.py
Browse files- recommendation_html_format.py +152 -56
recommendation_html_format.py
CHANGED
|
@@ -8,6 +8,30 @@ from scoring_calculation_system import UserPreferences, calculate_compatibility
|
|
| 8 |
|
| 9 |
def format_recommendation_html(recommendations: List[Dict], is_description_search: bool = False) -> str:
|
| 10 |
"""將推薦結果格式化為HTML"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
def _convert_to_display_score(score: float, score_type: str = None) -> int:
|
| 12 |
"""
|
| 13 |
更改為生成更明顯差異的顯示分數
|
|
@@ -55,35 +79,51 @@ def format_recommendation_html(recommendations: List[Dict], is_description_searc
|
|
| 55 |
return 70
|
| 56 |
|
| 57 |
|
| 58 |
-
def _generate_progress_bar(score: float) ->
|
| 59 |
-
"""
|
| 60 |
-
- 確保100%時完全填滿
|
| 61 |
-
- 更線性的視覺呈現
|
| 62 |
-
- 保持合理的視覺比例
|
| 63 |
"""
|
| 64 |
-
|
| 65 |
-
if score >= 1.0:
|
| 66 |
-
return 100.0 # 確保100%時完全填滿
|
| 67 |
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
else:
|
| 76 |
-
#
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
|
| 83 |
-
#
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
-
html_content = "<div class='recommendations-container'>"
|
| 87 |
|
| 88 |
for rec in recommendations:
|
| 89 |
breed = rec['breed']
|
|
@@ -104,13 +144,20 @@ def format_recommendation_html(recommendations: List[Dict], is_description_searc
|
|
| 104 |
else:
|
| 105 |
display_scores = scores # 圖片識別使用原始分數
|
| 106 |
|
| 107 |
-
progress_bars = {
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
|
| 115 |
health_info = breed_health_info.get(breed, {"health_notes": default_health_note})
|
| 116 |
noise_info = breed_noise_info.get(breed, {
|
|
@@ -190,34 +237,87 @@ def format_recommendation_html(recommendations: List[Dict], is_description_searc
|
|
| 190 |
</span>
|
| 191 |
</h2>
|
| 192 |
<div class="compatibility-scores">
|
|
|
|
| 193 |
<div class="score-item">
|
| 194 |
-
<span class="label">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
<div class="progress-bar">
|
| 196 |
-
<div class="progress" style="width:
|
| 197 |
</div>
|
| 198 |
-
<span class="percentage">{display_scores['space'] if is_description_search else scores
|
| 199 |
</div>
|
|
|
|
|
|
|
| 200 |
<div class="score-item">
|
| 201 |
-
<span class="label">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
<div class="progress-bar">
|
| 203 |
-
<div class="progress" style="width:
|
| 204 |
</div>
|
| 205 |
-
<span class="percentage">{display_scores['exercise'] if is_description_search else scores
|
| 206 |
</div>
|
|
|
|
|
|
|
| 207 |
<div class="score-item">
|
| 208 |
-
<span class="label">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
<div class="progress-bar">
|
| 210 |
-
<div class="progress" style="width:
|
| 211 |
</div>
|
| 212 |
-
<span class="percentage">{display_scores['grooming'] if is_description_search else scores
|
| 213 |
</div>
|
|
|
|
|
|
|
| 214 |
<div class="score-item">
|
| 215 |
-
<span class="label">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
<div class="progress-bar">
|
| 217 |
-
<div class="progress" style="width:
|
| 218 |
</div>
|
| 219 |
-
<span class="percentage">{display_scores['experience'] if is_description_search else scores
|
| 220 |
</div>
|
|
|
|
|
|
|
| 221 |
<div class="score-item">
|
| 222 |
<span class="label">
|
| 223 |
Noise Compatibility:
|
|
@@ -226,16 +326,17 @@ def format_recommendation_html(recommendations: List[Dict], is_description_searc
|
|
| 226 |
<span class="tooltip-text">
|
| 227 |
<strong>Noise Compatibility Score:</strong><br>
|
| 228 |
• Based on your noise tolerance preference<br>
|
| 229 |
-
• Considers breed's typical noise level<br>
|
| 230 |
-
• Accounts for living environment
|
| 231 |
</span>
|
| 232 |
</span>
|
| 233 |
</span>
|
| 234 |
<div class="progress-bar">
|
| 235 |
-
<div class="progress" style="width:
|
| 236 |
</div>
|
| 237 |
-
<span class="percentage">{display_scores['noise'] if is_description_search else scores
|
| 238 |
</div>
|
|
|
|
| 239 |
{f'''
|
| 240 |
<div class="score-item bonus-score">
|
| 241 |
<span class="label">
|
|
@@ -255,11 +356,11 @@ def format_recommendation_html(recommendations: List[Dict], is_description_searc
|
|
| 255 |
</span>
|
| 256 |
</span>
|
| 257 |
<div class="progress-bar">
|
| 258 |
-
<div class="progress" style="
|
| 259 |
</div>
|
| 260 |
<span class="percentage">{bonus_score*100:.1f}%</span>
|
| 261 |
</div>
|
| 262 |
-
|
| 263 |
</div>
|
| 264 |
<div class="breed-details-section">
|
| 265 |
<h3 class="subsection-title">
|
|
@@ -354,7 +455,6 @@ def format_recommendation_html(recommendations: List[Dict], is_description_searc
|
|
| 354 |
<div class="list-item">Attention-seeking barks</div>
|
| 355 |
<div class="list-item">Social vocalizations</div>
|
| 356 |
</div>
|
| 357 |
-
|
| 358 |
<div class="noise-level-display">
|
| 359 |
<h4 class="section-header">Noise level:</h4>
|
| 360 |
<div class="level-indicator">
|
|
@@ -366,7 +466,6 @@ def format_recommendation_html(recommendations: List[Dict], is_description_searc
|
|
| 366 |
</div>
|
| 367 |
</div>
|
| 368 |
</div>
|
| 369 |
-
|
| 370 |
<h4 class="section-header">Barking triggers:</h4>
|
| 371 |
<div class="triggers-list">
|
| 372 |
<div class="list-item">Separation anxiety</div>
|
|
@@ -383,7 +482,6 @@ def format_recommendation_html(recommendations: List[Dict], is_description_searc
|
|
| 383 |
</div>
|
| 384 |
</div>
|
| 385 |
</div>
|
| 386 |
-
|
| 387 |
<div class="health-section">
|
| 388 |
<h3 class="section-header">
|
| 389 |
<span class="icon">🏥</span> Health Insights
|
|
@@ -406,7 +504,6 @@ def format_recommendation_html(recommendations: List[Dict], is_description_searc
|
|
| 406 |
<div class="health-item">Open fontanel</div>
|
| 407 |
</div>
|
| 408 |
</div>
|
| 409 |
-
|
| 410 |
<div class="health-block">
|
| 411 |
<h4 class="section-header">Recommended health screenings:</h4>
|
| 412 |
<div class="health-grid">
|
|
@@ -425,7 +522,6 @@ def format_recommendation_html(recommendations: List[Dict], is_description_searc
|
|
| 425 |
</div>
|
| 426 |
</div>
|
| 427 |
</div>
|
| 428 |
-
|
| 429 |
<div class="action-section">
|
| 430 |
<a href="https://www.akc.org/dog-breeds/{breed.lower().replace('_', '-')}/"
|
| 431 |
target="_blank"
|
|
@@ -538,7 +634,7 @@ def get_breed_recommendations(user_prefs: UserPreferences, top_n: int = 15) -> L
|
|
| 538 |
'info': breed_info,
|
| 539 |
'noise_info': noise_info # 添加噪音資訊到推薦結果
|
| 540 |
})
|
| 541 |
-
|
| 542 |
# 嚴格按照 final_score 排序
|
| 543 |
recommendations.sort(key=lambda x: (round(-x['final_score'], 4), x['breed'] )) # 負號降序排列
|
| 544 |
|
|
@@ -590,4 +686,4 @@ def get_breed_recommendations(user_prefs: UserPreferences, top_n: int = 15) -> L
|
|
| 590 |
except Exception as e:
|
| 591 |
print(f"Error in get_breed_recommendations: {str(e)}")
|
| 592 |
print(f"Traceback: {traceback.format_exc()}")
|
| 593 |
-
return []
|
|
|
|
| 8 |
|
| 9 |
def format_recommendation_html(recommendations: List[Dict], is_description_search: bool = False) -> str:
|
| 10 |
"""將推薦結果格式化為HTML"""
|
| 11 |
+
|
| 12 |
+
html_content = """
|
| 13 |
+
<style>
|
| 14 |
+
.progress {
|
| 15 |
+
transition: all 0.3s ease-in-out;
|
| 16 |
+
border-radius: 4px;
|
| 17 |
+
height: 12px;
|
| 18 |
+
}
|
| 19 |
+
.progress-bar {
|
| 20 |
+
background-color: #f5f5f5;
|
| 21 |
+
border-radius: 4px;
|
| 22 |
+
overflow: hidden;
|
| 23 |
+
position: relative;
|
| 24 |
+
}
|
| 25 |
+
.score-item {
|
| 26 |
+
margin: 10px 0;
|
| 27 |
+
}
|
| 28 |
+
.percentage {
|
| 29 |
+
margin-left: 8px;
|
| 30 |
+
font-weight: 500;
|
| 31 |
+
}
|
| 32 |
+
</style>
|
| 33 |
+
<div class='recommendations-container'>"""
|
| 34 |
+
|
| 35 |
def _convert_to_display_score(score: float, score_type: str = None) -> int:
|
| 36 |
"""
|
| 37 |
更改為生成更明顯差異的顯示分數
|
|
|
|
| 79 |
return 70
|
| 80 |
|
| 81 |
|
| 82 |
+
def _generate_progress_bar(score: float, score_type: str = None) -> dict:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
"""
|
| 84 |
+
生成進度條的寬度和顏色
|
|
|
|
|
|
|
| 85 |
|
| 86 |
+
Parameters:
|
| 87 |
+
score: 原始分數 (0-1 之間的浮點數)
|
| 88 |
+
score_type: 分數類型,用於特殊處理某些類型的分數
|
| 89 |
+
|
| 90 |
+
Returns:
|
| 91 |
+
dict: 包含寬度和顏色的字典
|
| 92 |
+
"""
|
| 93 |
+
# 計算寬度
|
| 94 |
+
if score_type == 'bonus':
|
| 95 |
+
# Breed Bonus 特殊的計算方式
|
| 96 |
+
width = min(100, max(5, 10 + (score * 300)))
|
| 97 |
else:
|
| 98 |
+
# 一般分數的計算
|
| 99 |
+
if score >= 0.9:
|
| 100 |
+
width = 90 + (score - 0.9) * 100
|
| 101 |
+
elif score >= 0.7:
|
| 102 |
+
width = 70 + (score - 0.7) * 100
|
| 103 |
+
elif score >= 0.5:
|
| 104 |
+
width = 40 + (score - 0.5) * 150
|
| 105 |
+
elif score >= 0.3:
|
| 106 |
+
width = 20 + (score - 0.3) * 100
|
| 107 |
+
else:
|
| 108 |
+
width = max(5, score * 66.7)
|
| 109 |
|
| 110 |
+
# 根據分數決定顏色
|
| 111 |
+
if score >= 0.9:
|
| 112 |
+
color = '#68b36b' # 高分段柔和綠
|
| 113 |
+
elif score >= 0.7:
|
| 114 |
+
color = '#9bcf74' # 中高分段略黃綠
|
| 115 |
+
elif score >= 0.5:
|
| 116 |
+
color = '#d4d880' # 中等分段黃綠
|
| 117 |
+
elif score >= 0.3:
|
| 118 |
+
color = '#e3b583' # 偏低分段柔和橘
|
| 119 |
+
else:
|
| 120 |
+
color = '#e9a098' # 低分段暖紅粉
|
| 121 |
+
|
| 122 |
+
return {
|
| 123 |
+
'width': width,
|
| 124 |
+
'color': color
|
| 125 |
+
}
|
| 126 |
|
|
|
|
| 127 |
|
| 128 |
for rec in recommendations:
|
| 129 |
breed = rec['breed']
|
|
|
|
| 144 |
else:
|
| 145 |
display_scores = scores # 圖片識別使用原始分數
|
| 146 |
|
| 147 |
+
progress_bars = {}
|
| 148 |
+
for metric in ['space', 'exercise', 'grooming', 'experience', 'noise']:
|
| 149 |
+
if metric in scores:
|
| 150 |
+
bar_data = _generate_progress_bar(scores[metric], metric)
|
| 151 |
+
progress_bars[metric] = {
|
| 152 |
+
'style': f"width: {bar_data['width']}%; background-color: {bar_data['color']};"
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
# bonus
|
| 156 |
+
if bonus_score > 0:
|
| 157 |
+
bonus_data = _generate_progress_bar(bonus_score, 'bonus')
|
| 158 |
+
progress_bars['bonus'] = {
|
| 159 |
+
'style': f"width: {bonus_data['width']}%; background-color: {bonus_data['color']};"
|
| 160 |
+
}
|
| 161 |
|
| 162 |
health_info = breed_health_info.get(breed, {"health_notes": default_health_note})
|
| 163 |
noise_info = breed_noise_info.get(breed, {
|
|
|
|
| 237 |
</span>
|
| 238 |
</h2>
|
| 239 |
<div class="compatibility-scores">
|
| 240 |
+
<!-- 空間相容性評分 -->
|
| 241 |
<div class="score-item">
|
| 242 |
+
<span class="label">
|
| 243 |
+
Space Compatibility:
|
| 244 |
+
<span class="tooltip">
|
| 245 |
+
<span class="tooltip-icon">ⓘ</span>
|
| 246 |
+
<span class="tooltip-text">
|
| 247 |
+
<strong>Space Compatibility Score:</strong><br>
|
| 248 |
+
• Evaluates how well the breed adapts to your living environment<br>
|
| 249 |
+
• Considers if your home (apartment/house) and yard access suit the breed’s size<br>
|
| 250 |
+
• Higher score means the breed fits well in your available space.
|
| 251 |
+
</span>
|
| 252 |
+
</span>
|
| 253 |
+
</span>
|
| 254 |
<div class="progress-bar">
|
| 255 |
+
<div class="progress" style="{progress_bars.get('space', {'style': 'width: 0%; background-color: #e74c3c;'})['style']}"></div>
|
| 256 |
</div>
|
| 257 |
+
<span class="percentage">{display_scores['space'] if is_description_search else scores.get('space', 0)*100:.1f}%</span>
|
| 258 |
</div>
|
| 259 |
+
|
| 260 |
+
<!-- 運動匹配度評分 -->
|
| 261 |
<div class="score-item">
|
| 262 |
+
<span class="label">
|
| 263 |
+
Exercise Match:
|
| 264 |
+
<span class="tooltip">
|
| 265 |
+
<span class="tooltip-icon">ⓘ</span>
|
| 266 |
+
<span class="tooltip-text">
|
| 267 |
+
<strong>Exercise Match Score:</strong><br>
|
| 268 |
+
• Based on your daily exercise time and type<br>
|
| 269 |
+
• Compares your activity level to the breed’s exercise needs<br>
|
| 270 |
+
• Higher score means your routine aligns well with the breed’s energy requirements.
|
| 271 |
+
</span>
|
| 272 |
+
</span>
|
| 273 |
+
</span>
|
| 274 |
<div class="progress-bar">
|
| 275 |
+
<div class="progress" style="{progress_bars.get('exercise', {'style': 'width: 0%; background-color: #e74c3c;'})['style']}"></div>
|
| 276 |
</div>
|
| 277 |
+
<span class="percentage">{display_scores['exercise'] if is_description_search else scores.get('exercise', 0)*100:.1f}%</span>
|
| 278 |
</div>
|
| 279 |
+
|
| 280 |
+
<!-- 美容需求匹配度評分 -->
|
| 281 |
<div class="score-item">
|
| 282 |
+
<span class="label">
|
| 283 |
+
Grooming Match:
|
| 284 |
+
<span class="tooltip">
|
| 285 |
+
<span class="tooltip-icon">ⓘ</span>
|
| 286 |
+
<span class="tooltip-text">
|
| 287 |
+
<strong>Grooming Match Score:</strong><br>
|
| 288 |
+
• Evaluates breed’s grooming needs (coat care, trimming, brushing)<br>
|
| 289 |
+
• Compares these requirements with your grooming commitment level<br>
|
| 290 |
+
• Higher score means the breed’s grooming needs fit your willingness and capability.
|
| 291 |
+
</span>
|
| 292 |
+
</span>
|
| 293 |
+
</span>
|
| 294 |
<div class="progress-bar">
|
| 295 |
+
<div class="progress" style="{progress_bars.get('grooming', {'style': 'width: 0%; background-color: #e74c3c;'})['style']}"></div>
|
| 296 |
</div>
|
| 297 |
+
<span class="percentage">{display_scores['grooming'] if is_description_search else scores.get('grooming', 0)*100:.1f}%</span>
|
| 298 |
</div>
|
| 299 |
+
|
| 300 |
+
<!-- 經驗需求匹配度評分 -->
|
| 301 |
<div class="score-item">
|
| 302 |
+
<span class="label">
|
| 303 |
+
Experience Match:
|
| 304 |
+
<span class="tooltip">
|
| 305 |
+
<span class="tooltip-icon">ⓘ</span>
|
| 306 |
+
<span class="tooltip-text">
|
| 307 |
+
<strong>Experience Match Score:</strong><br>
|
| 308 |
+
• Based on your dog-owning experience level<br>
|
| 309 |
+
• Considers breed’s training complexity, temperament, and handling difficulty<br>
|
| 310 |
+
• Higher score means the breed is more suitable for your experience level.
|
| 311 |
+
</span>
|
| 312 |
+
</span>
|
| 313 |
+
</span>
|
| 314 |
<div class="progress-bar">
|
| 315 |
+
<div class="progress" style="{progress_bars.get('experience', {'style': 'width: 0%; background-color: #e74c3c;'})['style']}"></div>
|
| 316 |
</div>
|
| 317 |
+
<span class="percentage">{display_scores['experience'] if is_description_search else scores.get('experience', 0)*100:.1f}%</span>
|
| 318 |
</div>
|
| 319 |
+
|
| 320 |
+
<!-- 噪音相容性評分 -->
|
| 321 |
<div class="score-item">
|
| 322 |
<span class="label">
|
| 323 |
Noise Compatibility:
|
|
|
|
| 326 |
<span class="tooltip-text">
|
| 327 |
<strong>Noise Compatibility Score:</strong><br>
|
| 328 |
• Based on your noise tolerance preference<br>
|
| 329 |
+
• Considers breed's typical noise level and barking tendencies<br>
|
| 330 |
+
• Accounts for living environment and sensitivity to noise.
|
| 331 |
</span>
|
| 332 |
</span>
|
| 333 |
</span>
|
| 334 |
<div class="progress-bar">
|
| 335 |
+
<div class="progress" style="{progress_bars.get('noise', {'style': 'width: 0%; background-color: #e74c3c;'})['style']}"></div>
|
| 336 |
</div>
|
| 337 |
+
<span class="percentage">{display_scores['noise'] if is_description_search else scores.get('noise', 0)*100:.1f}%</span>
|
| 338 |
</div>
|
| 339 |
+
|
| 340 |
{f'''
|
| 341 |
<div class="score-item bonus-score">
|
| 342 |
<span class="label">
|
|
|
|
| 356 |
</span>
|
| 357 |
</span>
|
| 358 |
<div class="progress-bar">
|
| 359 |
+
<div class="progress" style="{progress_bars['bonus']['style']}"></div>
|
| 360 |
</div>
|
| 361 |
<span class="percentage">{bonus_score*100:.1f}%</span>
|
| 362 |
</div>
|
| 363 |
+
''' if bonus_score > 0 else ''}
|
| 364 |
</div>
|
| 365 |
<div class="breed-details-section">
|
| 366 |
<h3 class="subsection-title">
|
|
|
|
| 455 |
<div class="list-item">Attention-seeking barks</div>
|
| 456 |
<div class="list-item">Social vocalizations</div>
|
| 457 |
</div>
|
|
|
|
| 458 |
<div class="noise-level-display">
|
| 459 |
<h4 class="section-header">Noise level:</h4>
|
| 460 |
<div class="level-indicator">
|
|
|
|
| 466 |
</div>
|
| 467 |
</div>
|
| 468 |
</div>
|
|
|
|
| 469 |
<h4 class="section-header">Barking triggers:</h4>
|
| 470 |
<div class="triggers-list">
|
| 471 |
<div class="list-item">Separation anxiety</div>
|
|
|
|
| 482 |
</div>
|
| 483 |
</div>
|
| 484 |
</div>
|
|
|
|
| 485 |
<div class="health-section">
|
| 486 |
<h3 class="section-header">
|
| 487 |
<span class="icon">🏥</span> Health Insights
|
|
|
|
| 504 |
<div class="health-item">Open fontanel</div>
|
| 505 |
</div>
|
| 506 |
</div>
|
|
|
|
| 507 |
<div class="health-block">
|
| 508 |
<h4 class="section-header">Recommended health screenings:</h4>
|
| 509 |
<div class="health-grid">
|
|
|
|
| 522 |
</div>
|
| 523 |
</div>
|
| 524 |
</div>
|
|
|
|
| 525 |
<div class="action-section">
|
| 526 |
<a href="https://www.akc.org/dog-breeds/{breed.lower().replace('_', '-')}/"
|
| 527 |
target="_blank"
|
|
|
|
| 634 |
'info': breed_info,
|
| 635 |
'noise_info': noise_info # 添加噪音資訊到推薦結果
|
| 636 |
})
|
| 637 |
+
|
| 638 |
# 嚴格按照 final_score 排序
|
| 639 |
recommendations.sort(key=lambda x: (round(-x['final_score'], 4), x['breed'] )) # 負號降序排列
|
| 640 |
|
|
|
|
| 686 |
except Exception as e:
|
| 687 |
print(f"Error in get_breed_recommendations: {str(e)}")
|
| 688 |
print(f"Traceback: {traceback.format_exc()}")
|
| 689 |
+
return []
|