DawnC commited on
Commit
77a3b9d
1 Parent(s): 9bb5d3f

Update recommendation_html_format.py

Browse files
Files changed (1) hide show
  1. 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) -> float:
59
- """
60
- - 確保100%時完全填滿
61
- - 更線性的視覺呈現
62
- - 保持合理的視覺比例
63
  """
64
- # 基礎寬度計算
65
- if score >= 1.0:
66
- return 100.0 # 確保100%時完全填滿
67
 
68
- # 一般情況的寬度計算
69
- if score > 0.9:
70
- # 高分區間線性延伸
71
- width = 90 + (score - 0.9) * 100
72
- elif score > 0.7:
73
- # 中高分區間稍微展開
74
- width = 70 + (score - 0.7) * 100
 
 
 
 
75
  else:
76
- # 基礎線性關係
77
- width = score * 100
78
-
79
- # 加入微小的隨機變化,使顯示更自然
80
- import random
81
- width += random.uniform(-0.5, 0.5)
 
 
 
 
 
82
 
83
- # 確保範圍合理
84
- return min(99.5, max(20, width)) if score < 1.0 else 100.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- 'space': _generate_progress_bar(scores['space']),
109
- 'exercise': _generate_progress_bar(scores['exercise']),
110
- 'grooming': _generate_progress_bar(scores['grooming']),
111
- 'experience': _generate_progress_bar(scores['experience']),
112
- 'noise': _generate_progress_bar(scores['noise'])
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">Space Compatibility:</span>
 
 
 
 
 
 
 
 
 
 
 
195
  <div class="progress-bar">
196
- <div class="progress" style="width: {progress_bars['space']}%"></div>
197
  </div>
198
- <span class="percentage">{display_scores['space'] if is_description_search else scores['space']*100:.1f}%</span>
199
  </div>
 
 
200
  <div class="score-item">
201
- <span class="label">Exercise Match:</span>
 
 
 
 
 
 
 
 
 
 
 
202
  <div class="progress-bar">
203
- <div class="progress" style="width: {progress_bars['exercise']}%"></div>
204
  </div>
205
- <span class="percentage">{display_scores['exercise'] if is_description_search else scores['exercise']*100:.1f}%</span>
206
  </div>
 
 
207
  <div class="score-item">
208
- <span class="label">Grooming Match:</span>
 
 
 
 
 
 
 
 
 
 
 
209
  <div class="progress-bar">
210
- <div class="progress" style="width: {progress_bars['grooming']}%"></div>
211
  </div>
212
- <span class="percentage">{display_scores['grooming'] if is_description_search else scores['grooming']*100:.1f}%</span>
213
  </div>
 
 
214
  <div class="score-item">
215
- <span class="label">Experience Match:</span>
 
 
 
 
 
 
 
 
 
 
 
216
  <div class="progress-bar">
217
- <div class="progress" style="width: {progress_bars['experience']}%"></div>
218
  </div>
219
- <span class="percentage">{display_scores['experience'] if is_description_search else scores['experience']*100:.1f}%</span>
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: {progress_bars['noise']}%"></div>
236
  </div>
237
- <span class="percentage">{display_scores['noise'] if is_description_search else scores['noise']*100:.1f}%</span>
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="width: {progress_bars.get('bonus', bonus_score*100)}%"></div>
259
  </div>
260
  <span class="percentage">{bonus_score*100:.1f}%</span>
261
  </div>
262
- ''' if bonus_score > 0 else ''}
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 []