File size: 23,790 Bytes
6c558fc
8ada85f
 
 
a099bbc
8ada85f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
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]) -> str:
    """將推薦結果格式化為HTML"""
    html_content = "<div class='recommendations-container'>"

    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)

        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'<li>{item}</li>' for item in noise_characteristics])
        barking_triggers_html = '\n'.join([f'<li>{item}</li>' 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'<li>{item}</li>' for item in health_considerations])
        health_screenings_html = '\n'.join([f'<li>{item}</li>' 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"""
        <div class="dog-info-card recommendation-card">
            <div class="breed-info">
                <h2 class="section-title">
                    <span class="icon">🏆</span> #{rank} {breed.replace('_', ' ')}
                    <span class="score-badge">
                        Overall Match: {final_score*100:.1f}%
                    </span>
                </h2>
                <div class="compatibility-scores">
                    <div class="score-item">
                        <span class="label">Space Compatibility:</span>
                        <div class="progress-bar">
                            <div class="progress" style="width: {scores['space']*100}%"></div>
                        </div>
                        <span class="percentage">{scores['space']*100:.1f}%</span>
                    </div>
                    <div class="score-item">
                        <span class="label">Exercise Match:</span>
                        <div class="progress-bar">
                            <div class="progress" style="width: {scores['exercise']*100}%"></div>
                        </div>
                        <span class="percentage">{scores['exercise']*100:.1f}%</span>
                    </div>
                    <div class="score-item">
                        <span class="label">Grooming Match:</span>
                        <div class="progress-bar">
                            <div class="progress" style="width: {scores['grooming']*100}%"></div>
                        </div>
                        <span class="percentage">{scores['grooming']*100:.1f}%</span>
                    </div>
                    <div class="score-item">
                        <span class="label">Experience Match:</span>
                        <div class="progress-bar">
                            <div class="progress" style="width: {scores['experience']*100}%"></div>
                        </div>
                        <span class="percentage">{scores['experience']*100:.1f}%</span>
                    </div>
                    <div class="score-item">
                        <span class="label">
                            Noise Compatibility:
                            <span class="tooltip">
                                <span class="tooltip-icon">ⓘ</span>
                                <span class="tooltip-text">
                                    <strong>Noise Compatibility Score:</strong><br>
                                    • Based on your noise tolerance preference<br>
                                    • Considers breed's typical noise level<br>
                                    • Accounts for living environment
                                </span>
                            </span>
                        </span>
                        <div class="progress-bar">
                            <div class="progress" style="width: {scores['noise']*100}%"></div>
                        </div>
                        <span class="percentage">{scores['noise']*100:.1f}%</span>
                    </div>
                    {f'''
                    <div class="score-item bonus-score">
                        <span class="label">
                            Breed Bonus:
                            <span class="tooltip">
                                <span class="tooltip-icon">ⓘ</span>
                                <span class="tooltip-text">
                                    <strong>Breed Bonus Points:</strong><br>
{('<br>• '.join(bonus_reasons)) if bonus_reasons else 'No additional bonus points'}<br>
                                    <br>
                                    <strong>Bonus Factors Include:</strong><br>
                                    • Friendly temperament<br>
                                    • Child compatibility<br>
                                    • Longer lifespan<br>
                                    • Living space adaptability
                                </span>
                            </span>
                        </span>
                        <div class="progress-bar">
                            <div class="progress" style="width: {bonus_score*100}%"></div>
                        </div>
                        <span class="percentage">{bonus_score*100:.1f}%</span>
                    </div>
                    ''' if bonus_score > 0 else ''}
                </div>
                <div class="breed-details-section">
                    <h3 class="subsection-title">
                        <span class="icon">📋</span> Breed Details
                    </h3>
                    <div class="details-grid">
                        <div class="detail-item">
                            <span class="tooltip">
                                <span class="icon">📏</span>
                                <span class="label">Size:</span>
                                <span class="tooltip-icon">ⓘ</span>
                                <span class="tooltip-text">
                                    <strong>Size Categories:</strong><br>
                                    • Small: Under 20 pounds<br>
                                    • Medium: 20-60 pounds<br>
                                    • Large: Over 60 pounds
                                </span>
                                <span class="value">{info['Size']}</span>
                            </span>
                        </div>
                        <div class="detail-item">
                            <span class="tooltip">
                                <span class="icon">🏃</span>
                                <span class="label">Exercise Needs:</span>
                                <span class="tooltip-icon">ⓘ</span>
                                <span class="tooltip-text">
                                    <strong>Exercise Needs:</strong><br>
                                    • Low: Short walks<br>
                                    • Moderate: 1-2 hours daily<br>
                                    • High: 2+ hours daily<br>
                                    • Very High: Constant activity
                                </span>
                                <span class="value">{info['Exercise Needs']}</span>
                            </span>
                        </div>
                        <div class="detail-item">
                            <span class="tooltip">
                                <span class="icon">👨‍👩‍👧‍👦</span>
                                <span class="label">Good with Children:</span>
                                <span class="tooltip-icon">ⓘ</span>
                                <span class="tooltip-text">
                                    <strong>Child Compatibility:</strong><br>
                                    • Yes: Excellent with kids<br>
                                    • Moderate: Good with older children<br>
                                    • No: Better for adult households
                                </span>
                                <span class="value">{info['Good with Children']}</span>
                            </span>
                        </div>
                        <div class="detail-item">
                            <span class="tooltip">
                                <span class="icon">⏳</span>
                                <span class="label">Lifespan:</span>
                                <span class="tooltip-icon">ⓘ</span>
                                <span class="tooltip-text">
                                    <strong>Average Lifespan:</strong><br>
                                    • Short: 6-8 years<br>
                                    • Average: 10-15 years<br>
                                    • Long: 12-20 years<br>
                                    • Varies by size: Larger breeds typically have shorter lifespans
                                </span>
                            </span>
                            <span class="value">{info['Lifespan']}</span>
                        </div>
                    </div>
                </div>
                <div class="description-section">
                    <h3 class="subsection-title">
                        <span class="icon">📝</span> Description
                    </h3>
                    <p class="description-text">{info.get('Description', '')}</p>
                </div>
                <div class="noise-section">
                    <h3 class="section-header">
                        <span class="icon">🔊</span> Noise Behavior
                    </h3>
                    <div class="noise-info">
                        <div class="noise-details">
                            <h4 class="section-header">Typical noise characteristics:</h4>
                            <div class="characteristics-list">
                                <div class="list-item">Moderate to high barker</div>
                                <div class="list-item">Alert watch dog</div>
                                <div class="list-item">Attention-seeking barks</div>
                                <div class="list-item">Social vocalizations</div>
                            </div>

                            <div class="noise-level-display">
                                <h4 class="section-header">Noise level:</h4>
                                <div class="level-indicator">
                                    <span class="level-text">Moderate-High</span>
                                    <div class="level-bars">
                                        <span class="bar"></span>
                                        <span class="bar"></span>
                                        <span class="bar"></span>
                                    </div>
                                </div>
                            </div>

                            <h4 class="section-header">Barking triggers:</h4>
                            <div class="triggers-list">
                                <div class="list-item">Separation anxiety</div>
                                <div class="list-item">Attention needs</div>
                                <div class="list-item">Strange noises</div>
                                <div class="list-item">Excitement</div>
                            </div>
                        </div>
                        <div class="noise-disclaimer">
                            <p class="disclaimer-text source-text">Source: Compiled from various breed behavior resources, 2024</p>
                            <p class="disclaimer-text">Individual dogs may vary in their vocalization patterns.</p>
                            <p class="disclaimer-text">Training can significantly influence barking behavior.</p>
                            <p class="disclaimer-text">Environmental factors may affect noise levels.</p>
                        </div>
                    </div>
                </div>

                <div class="health-section">
                    <h3 class="section-header">
                        <span class="icon">🏥</span> Health Insights
                        <span class="tooltip">
                            <span class="tooltip-icon">ⓘ</span>
                            <span class="tooltip-text">
                                Health information is compiled from multiple sources including veterinary resources, breed guides, and international canine health databases.
                                Each dog is unique and may vary from these general guidelines.
                            </span>
                        </span>
                    </h3>
                    <div class="health-info">
                        <div class="health-details">
                            <div class="health-block">
                                <h4 class="section-header">Common breed-specific health considerations:</h4>
                                <div class="health-grid">
                                    <div class="health-item">Patellar luxation</div>
                                    <div class="health-item">Progressive retinal atrophy</div>
                                    <div class="health-item">Von Willebrand's disease</div>
                                    <div class="health-item">Open fontanel</div>
                                </div>
                            </div>

                            <div class="health-block">
                                <h4 class="section-header">Recommended health screenings:</h4>
                                <div class="health-grid">
                                    <div class="health-item screening">Patella evaluation</div>
                                    <div class="health-item screening">Eye examination</div>
                                    <div class="health-item screening">Blood clotting tests</div>
                                    <div class="health-item screening">Skull development monitoring</div>
                                </div>
                            </div>
                        </div>
                        <div class="health-disclaimer">
                            <p class="disclaimer-text source-text">Source: Compiled from various veterinary and breed information resources, 2024</p>
                            <p class="disclaimer-text">This information is for reference only and based on breed tendencies.</p>
                            <p class="disclaimer-text">Each dog is unique and may not develop any or all of these conditions.</p>
                            <p class="disclaimer-text">Always consult with qualified veterinarians for professional advice.</p>
                        </div>
                    </div>
                </div>

                <div class="action-section">
                    <a href="https://www.akc.org/dog-breeds/{breed.lower().replace('_', '-')}/"
                       target="_blank"
                       class="akc-button">
                        <span class="icon">🌐</span>
                        Learn more about {breed.replace('_', ' ')} on AKC website
                    </a>
                </div>
            </div>
        </div>
        """

    html_content += "</div>"
    return html_content

def get_breed_recommendations(user_prefs: UserPreferences, top_n: int = 10) -> 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

            # 獲取噪音資訊
            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'] ))  # 負號使其降序排列,並確保4位小數

        # 選擇前N名並確保正確排序
        final_recommendations = []
        last_score = None
        rank = 1

        for rec in recommendations:
            if len(final_recommendations) >= top_n:
                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 []