Spaces:
Running
Running
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 []
|