DawnC commited on
Commit
595e0a5
·
verified ·
1 Parent(s): 5e6e54f

Upload 6 files

Browse files

refactored description recommendation function and improved UI

breed_recommendation_enhanced.py CHANGED
@@ -30,6 +30,8 @@ def create_description_examples():
30
  gap: 15px;
31
  margin-top: 10px;
32
  '>
 
 
33
  <div style='
34
  background: white;
35
  padding: 12px;
@@ -37,12 +39,13 @@ def create_description_examples():
37
  border: 1px solid #e2e8f0;
38
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
39
  '>
40
- <strong style='color: #4299e1;'>🏠 Living Environment:</strong><br>
41
  <span style='color: #4a5568; font-size: 0.9em;'>
42
- "I live in an apartment and need a quiet, small dog that's good with children"
43
  </span>
44
  </div>
45
 
 
46
  <div style='
47
  background: white;
48
  padding: 12px;
@@ -50,12 +53,13 @@ def create_description_examples():
50
  border: 1px solid #e2e8f0;
51
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
52
  '>
53
- <strong style='color: #48bb78;'>🎾 Activity Preferences:</strong><br>
54
  <span style='color: #4a5568; font-size: 0.9em;'>
55
  "I want an active medium to large dog for hiking and outdoor activities"
56
  </span>
57
  </div>
58
 
 
59
  <div style='
60
  background: white;
61
  padding: 12px;
@@ -63,12 +67,13 @@ def create_description_examples():
63
  border: 1px solid #e2e8f0;
64
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
65
  '>
66
- <strong style='color: #ed8936;'>❤️ Breed Preferences:</strong><br>
67
  <span style='color: #4a5568; font-size: 0.9em;'>
68
- "I love Border Collies most, then Golden Retrievers, followed by Pugs"
69
  </span>
70
  </div>
71
 
 
72
  <div style='
73
  background: white;
74
  padding: 12px;
@@ -76,7 +81,7 @@ def create_description_examples():
76
  border: 1px solid #e2e8f0;
77
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
78
  '>
79
- <strong style='color: #9f7aea;'>👥 Family Situation:</strong><br>
80
  <span style='color: #4a5568; font-size: 0.9em;'>
81
  "Looking for a calm, low-maintenance companion dog for elderly person"
82
  </span>
@@ -98,7 +103,6 @@ def create_description_examples():
98
  </div>
99
  """
100
 
101
-
102
  def create_recommendation_tab(
103
  UserPreferences,
104
  get_breed_recommendations,
@@ -110,7 +114,7 @@ def create_recommendation_tab(
110
  with gr.TabItem("Breed Recommendation"):
111
  with gr.Tabs():
112
  # --------------------------
113
- # Tab 1: Find by Criteria
114
  # --------------------------
115
  with gr.Tab("Find by Criteria"):
116
  gr.HTML("""
@@ -334,7 +338,7 @@ def create_recommendation_tab(
334
  )
335
 
336
  # --------------------------
337
- # Tab 2: Find by Description
338
  # --------------------------
339
  with gr.Tab("Find by Description") as description_tab:
340
  gr.HTML("""
@@ -639,4 +643,4 @@ def create_recommendation_tab(
639
  'criteria_results': locals().get('criteria_results'),
640
  'description_results': locals().get('description_results'),
641
  'description_input': locals().get('description_input')
642
- }
 
30
  gap: 15px;
31
  margin-top: 10px;
32
  '>
33
+
34
+ <!-- 左上:冷色(藍) -->
35
  <div style='
36
  background: white;
37
  padding: 12px;
 
39
  border: 1px solid #e2e8f0;
40
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
41
  '>
42
+ <strong style='color: #4299e1;'>🏡 Active Lifestyle & Space:</strong><br>
43
  <span style='color: #4a5568; font-size: 0.9em;'>
44
+ "I live in a large house with a big backyard, and I love hiking and outdoor activities. I don't mind if the dog is noisy, as long as it's active and playful."
45
  </span>
46
  </div>
47
 
48
+ <!-- 右上:暖色(橘) -->
49
  <div style='
50
  background: white;
51
  padding: 12px;
 
53
  border: 1px solid #e2e8f0;
54
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
55
  '>
56
+ <strong style='color: #ed8936;'>🎾 Activity Preferences:</strong><br>
57
  <span style='color: #4a5568; font-size: 0.9em;'>
58
  "I want an active medium to large dog for hiking and outdoor activities"
59
  </span>
60
  </div>
61
 
62
+ <!-- 左下:冷色(紫) -->
63
  <div style='
64
  background: white;
65
  padding: 12px;
 
67
  border: 1px solid #e2e8f0;
68
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
69
  '>
70
+ <strong style='color: #805ad5;'>🚶 Balanced Daily Routine:</strong><br>
71
  <span style='color: #4a5568; font-size: 0.9em;'>
72
+ "I live in a medium-sized house, walk about 30 minutes every day, and I'm okay with a moderately vocal dog. Looking for a balanced companion."
73
  </span>
74
  </div>
75
 
76
+ <!-- 右下:暖色(琥珀橘) -->
77
  <div style='
78
  background: white;
79
  padding: 12px;
 
81
  border: 1px solid #e2e8f0;
82
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
83
  '>
84
+ <strong style='color: #276749;'>👥 Family Situation:</strong><br>
85
  <span style='color: #4a5568; font-size: 0.9em;'>
86
  "Looking for a calm, low-maintenance companion dog for elderly person"
87
  </span>
 
103
  </div>
104
  """
105
 
 
106
  def create_recommendation_tab(
107
  UserPreferences,
108
  get_breed_recommendations,
 
114
  with gr.TabItem("Breed Recommendation"):
115
  with gr.Tabs():
116
  # --------------------------
117
+ # Find by Criteria
118
  # --------------------------
119
  with gr.Tab("Find by Criteria"):
120
  gr.HTML("""
 
338
  )
339
 
340
  # --------------------------
341
+ # Find by Description
342
  # --------------------------
343
  with gr.Tab("Find by Description") as description_tab:
344
  gr.HTML("""
 
643
  'criteria_results': locals().get('criteria_results'),
644
  'description_results': locals().get('description_results'),
645
  'description_input': locals().get('description_input')
646
+ }
matching_score_calculator.py ADDED
@@ -0,0 +1,974 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import hashlib
3
+ import numpy as np
4
+ import sqlite3
5
+ import re
6
+ import traceback
7
+ from typing import List, Dict, Tuple, Optional, Any
8
+ from dataclasses import dataclass
9
+ from sentence_transformers import SentenceTransformer
10
+ import torch
11
+ from sklearn.metrics.pairwise import cosine_similarity
12
+ from dog_database import get_dog_description
13
+ from breed_health_info import breed_health_info
14
+ from breed_noise_info import breed_noise_info
15
+ from scoring_calculation_system import UserPreferences, calculate_compatibility_score, UnifiedScoringSystem, calculate_unified_breed_scores
16
+ from query_understanding import QueryUnderstandingEngine, analyze_user_query
17
+ from constraint_manager import ConstraintManager, apply_breed_constraints
18
+ from multi_head_scorer import MultiHeadScorer, score_breed_candidates, BreedScore
19
+ from score_calibrator import ScoreCalibrator, calibrate_breed_scores
20
+ from config_manager import get_config_manager, get_standardized_breed_data
21
+
22
+ class MatchingScoreCalculator:
23
+ """
24
+ 匹配評分計算器
25
+ 處理多維度匹配計算、約束條件過濾和評分校準
26
+ """
27
+
28
+ def __init__(self, breed_list: List[str]):
29
+ """初始化匹配評分計算器"""
30
+ self.breed_list = breed_list
31
+
32
+ def apply_size_distribution_correction(self, recommendations: List[Dict]) -> List[Dict]:
33
+ """應用尺寸分佈修正以防止大型品種偏差"""
34
+ if len(recommendations) < 10:
35
+ return recommendations
36
+
37
+ # 分析尺寸分佈
38
+ size_counts = {'toy': 0, 'small': 0, 'medium': 0, 'large': 0, 'giant': 0}
39
+
40
+ for rec in recommendations:
41
+ breed_info = get_dog_description(rec['breed'])
42
+ if breed_info:
43
+ size = self._normalize_breed_size(breed_info.get('Size', 'Medium'))
44
+ size_counts[size] += 1
45
+
46
+ total_recs = len(recommendations)
47
+ large_giant_ratio = (size_counts['large'] + size_counts['giant']) / total_recs
48
+
49
+ # 如果超過 70% 是大型/巨型品種,應用修正
50
+ if large_giant_ratio > 0.7:
51
+ corrected_recommendations = []
52
+ size_quotas = {'toy': 2, 'small': 4, 'medium': 6, 'large': 2, 'giant': 1}
53
+ current_counts = {'toy': 0, 'small': 0, 'medium': 0, 'large': 0, 'giant': 0}
54
+
55
+ # 第一輪:在配額內添加品種
56
+ for rec in recommendations:
57
+ breed_info = get_dog_description(rec['breed'])
58
+ if breed_info:
59
+ size = self._normalize_breed_size(breed_info.get('Size', 'Medium'))
60
+ if current_counts[size] < size_quotas[size]:
61
+ corrected_recommendations.append(rec)
62
+ current_counts[size] += 1
63
+
64
+ # 第二輪:用最佳剩餘候選品種填滿剩餘位置
65
+ remaining_slots = 15 - len(corrected_recommendations)
66
+ remaining_breeds = [rec for rec in recommendations if rec not in corrected_recommendations]
67
+
68
+ corrected_recommendations.extend(remaining_breeds[:remaining_slots])
69
+ return corrected_recommendations
70
+
71
+ return recommendations
72
+
73
+ def _normalize_breed_size(self, size: str) -> str:
74
+ """標準化品種尺寸到標準分類"""
75
+ if not isinstance(size, str):
76
+ return 'medium'
77
+
78
+ size_lower = size.lower()
79
+ if any(term in size_lower for term in ['toy', 'tiny']):
80
+ return 'toy'
81
+ elif 'small' in size_lower:
82
+ return 'small'
83
+ elif 'medium' in size_lower:
84
+ return 'medium'
85
+ elif 'large' in size_lower:
86
+ return 'large'
87
+ elif any(term in size_lower for term in ['giant', 'extra large']):
88
+ return 'giant'
89
+ else:
90
+ return 'medium'
91
+
92
+ def apply_hard_constraints(self, breed: str, user_input: str, breed_characteristics: Dict[str, Any]) -> float:
93
+ """增強硬約束,具有更嚴格的懲罰"""
94
+ penalty = 0.0
95
+ user_text_lower = user_input.lower()
96
+
97
+ # 獲取品種信息
98
+ breed_info = get_dog_description(breed)
99
+ if not breed_info:
100
+ return 0.0
101
+
102
+ breed_size = breed_info.get('Size', '').lower()
103
+ exercise_needs = breed_info.get('Exercise Needs', '').lower()
104
+
105
+ # 公寓居住約束 - 更嚴格
106
+ if any(term in user_text_lower for term in ['apartment', 'flat', 'studio', 'small space']):
107
+ if 'giant' in breed_size:
108
+ return -2.0 # 完全淘汰
109
+ elif 'large' in breed_size:
110
+ if any(term in exercise_needs for term in ['high', 'very high']):
111
+ return -2.0 # 完全淘汰
112
+ else:
113
+ penalty -= 0.5 # 仍有顯著懲罰
114
+ elif 'medium' in breed_size and 'very high' in exercise_needs:
115
+ penalty -= 0.6
116
+
117
+ # 運動不匹配約束
118
+ if "don't exercise much" in user_text_lower or "low exercise" in user_text_lower:
119
+ if any(term in exercise_needs for term in ['very high', 'extreme', 'intense']):
120
+ return -2.0 # 完全淘汰
121
+ elif 'high' in exercise_needs:
122
+ penalty -= 0.8
123
+
124
+ # 中等生活方式檢測
125
+ if any(term in user_text_lower for term in ['moderate', 'balanced', '30 minutes', 'half hour']):
126
+ # 懲罰極端情況
127
+ if 'giant' in breed_size:
128
+ penalty -= 0.7 # 對巨型犬的強懲罰
129
+ elif 'very high' in exercise_needs:
130
+ penalty -= 0.5
131
+
132
+ # 兒童安全(現有邏輯保持但增強)
133
+ if any(term in user_text_lower for term in ['child', 'kids', 'family', 'baby']):
134
+ good_with_children = breed_info.get('Good with Children', '').lower()
135
+ if good_with_children == 'no':
136
+ return -2.0 # 為了安全完全淘汰
137
+
138
+ return penalty
139
+
140
+ def calculate_lifestyle_bonus(self, breed_characteristics: Dict[str, Any],
141
+ lifestyle_keywords: Dict[str, List[str]]) -> float:
142
+ """增強生活方式匹配獎勵計算"""
143
+ bonus = 0.0
144
+ penalties = 0.0
145
+
146
+ # 增強尺寸匹配
147
+ breed_size = breed_characteristics.get('size', '').lower()
148
+ size_prefs = lifestyle_keywords.get('size_preference', [])
149
+ for pref in size_prefs:
150
+ if pref in breed_size:
151
+ bonus += 0.25 # 尺寸匹配的強獎勵
152
+ elif (pref == 'small' and 'large' in breed_size) or \
153
+ (pref == 'large' and 'small' in breed_size):
154
+ penalties += 0.15 # 尺寸不匹配的懲罰
155
+
156
+ # 增強活動水平匹配
157
+ breed_exercise = breed_characteristics.get('exercise_needs', '').lower()
158
+ activity_prefs = lifestyle_keywords.get('activity_level', [])
159
+
160
+ if 'high' in activity_prefs:
161
+ if 'high' in breed_exercise or 'very high' in breed_exercise:
162
+ bonus += 0.2
163
+ elif 'low' in breed_exercise:
164
+ penalties += 0.2
165
+ elif 'low' in activity_prefs:
166
+ if 'low' in breed_exercise:
167
+ bonus += 0.2
168
+ elif 'high' in breed_exercise or 'very high' in breed_exercise:
169
+ penalties += 0.25
170
+ elif 'moderate' in activity_prefs:
171
+ if 'moderate' in breed_exercise:
172
+ bonus += 0.15
173
+
174
+ # 增強家庭情況匹配
175
+ good_with_children = breed_characteristics.get('good_with_children', 'Yes')
176
+ family_prefs = lifestyle_keywords.get('family_situation', [])
177
+
178
+ if 'children' in family_prefs:
179
+ if good_with_children == 'Yes':
180
+ bonus += 0.15
181
+ else:
182
+ penalties += 0.3 # 對非兒童友好品種的強懲罰
183
+
184
+ # 增強居住空間匹配
185
+ living_prefs = lifestyle_keywords.get('living_space', [])
186
+ if 'apartment' in living_prefs:
187
+ if 'small' in breed_size:
188
+ bonus += 0.2
189
+ elif 'medium' in breed_size and 'low' in breed_exercise:
190
+ bonus += 0.1
191
+ elif 'large' in breed_size or 'giant' in breed_size:
192
+ penalties += 0.2 # 公寓中大型犬的懲罰
193
+
194
+ # 噪音偏好匹配
195
+ noise_prefs = lifestyle_keywords.get('noise_preference', [])
196
+ temperament = breed_characteristics.get('temperament', '').lower()
197
+
198
+ if 'low' in noise_prefs:
199
+ # 獎勵安靜品種
200
+ if any(term in temperament for term in ['gentle', 'calm', 'quiet']):
201
+ bonus += 0.1
202
+
203
+ # 照護水平匹配
204
+ grooming_needs = breed_characteristics.get('grooming_needs', '').lower()
205
+ care_prefs = lifestyle_keywords.get('care_level', [])
206
+
207
+ if 'low' in care_prefs and 'low' in grooming_needs:
208
+ bonus += 0.1
209
+ elif 'high' in care_prefs and 'high' in grooming_needs:
210
+ bonus += 0.1
211
+ elif 'low' in care_prefs and 'high' in grooming_needs:
212
+ penalties += 0.15
213
+
214
+ # 特殊需求匹配
215
+ special_needs = lifestyle_keywords.get('special_needs', [])
216
+
217
+ if 'guard' in special_needs:
218
+ if any(term in temperament for term in ['protective', 'alert', 'watchful']):
219
+ bonus += 0.1
220
+ elif 'companion' in special_needs:
221
+ if any(term in temperament for term in ['affectionate', 'gentle', 'loyal']):
222
+ bonus += 0.1
223
+
224
+ # 計算包含懲罰的最終獎勵
225
+ final_bonus = bonus - penalties
226
+ return max(-0.3, min(0.5, final_bonus)) # 允許負獎勵但限制範圍
227
+
228
+ def apply_intelligent_trait_matching(self, recommendations: List[Dict], user_input: str) -> List[Dict]:
229
+ """基於增強關鍵字提取和數據庫挖掘應用智能特徵匹配"""
230
+ try:
231
+ # 從用戶輸入提取增強關鍵字
232
+ extracted_keywords = self._extract_enhanced_lifestyle_keywords(user_input)
233
+
234
+ # 對每個推薦應用智能特徵匹配
235
+ enhanced_recommendations = []
236
+
237
+ for rec in recommendations:
238
+ breed_name = rec['breed'].replace(' ', '_')
239
+
240
+ # 獲取品種數據庫信息
241
+ breed_info = get_dog_description(breed_name) or {}
242
+
243
+ # 計算智能特徵獎勵
244
+ intelligence_bonus = 0.0
245
+ trait_match_details = {}
246
+
247
+ # 1. 智力匹配
248
+ if extracted_keywords.get('intelligence_preference'):
249
+ intelligence_pref = extracted_keywords['intelligence_preference'][0]
250
+ breed_desc = breed_info.get('Description', '').lower()
251
+
252
+ if intelligence_pref == 'high':
253
+ if any(word in breed_desc for word in ['intelligent', 'smart', 'clever', 'quick to learn', 'trainable']):
254
+ intelligence_bonus += 0.05
255
+ trait_match_details['intelligence_match'] = 'High intelligence match detected'
256
+ elif any(word in breed_desc for word in ['stubborn', 'independent', 'difficult']):
257
+ intelligence_bonus -= 0.02
258
+ trait_match_details['intelligence_warning'] = 'May be challenging to train'
259
+
260
+ elif intelligence_pref == 'independent':
261
+ if any(word in breed_desc for word in ['independent', 'stubborn', 'strong-willed']):
262
+ intelligence_bonus += 0.03
263
+ trait_match_details['independence_match'] = 'Independent nature match'
264
+
265
+ # 2. 美容偏好匹配
266
+ if extracted_keywords.get('grooming_preference'):
267
+ grooming_pref = extracted_keywords['grooming_preference'][0]
268
+ breed_grooming = breed_info.get('Grooming Needs', '').lower()
269
+
270
+ if grooming_pref == 'low' and 'low' in breed_grooming:
271
+ intelligence_bonus += 0.03
272
+ trait_match_details['grooming_match'] = 'Low maintenance grooming match'
273
+ elif grooming_pref == 'high' and 'high' in breed_grooming:
274
+ intelligence_bonus += 0.03
275
+ trait_match_details['grooming_match'] = 'High maintenance grooming match'
276
+ elif grooming_pref == 'low' and 'high' in breed_grooming:
277
+ intelligence_bonus -= 0.04
278
+ trait_match_details['grooming_mismatch'] = 'High grooming needs may not suit preferences'
279
+
280
+ # 3. 氣質偏好匹配
281
+ if extracted_keywords.get('temperament_preference'):
282
+ temp_prefs = extracted_keywords['temperament_preference']
283
+ breed_temperament = breed_info.get('Temperament', '').lower()
284
+ breed_desc = breed_info.get('Description', '').lower()
285
+
286
+ temp_text = (breed_temperament + ' ' + breed_desc).lower()
287
+
288
+ for temp_pref in temp_prefs:
289
+ if temp_pref == 'gentle' and any(word in temp_text for word in ['gentle', 'calm', 'peaceful', 'mild']):
290
+ intelligence_bonus += 0.04
291
+ trait_match_details['temperament_match'] = f'Gentle temperament match: {temp_pref}'
292
+ elif temp_pref == 'playful' and any(word in temp_text for word in ['playful', 'energetic', 'lively', 'fun']):
293
+ intelligence_bonus += 0.04
294
+ trait_match_details['temperament_match'] = f'Playful temperament match: {temp_pref}'
295
+ elif temp_pref == 'protective' and any(word in temp_text for word in ['protective', 'guard', 'alert', 'watchful']):
296
+ intelligence_bonus += 0.04
297
+ trait_match_details['temperament_match'] = f'Protective temperament match: {temp_pref}'
298
+ elif temp_pref == 'friendly' and any(word in temp_text for word in ['friendly', 'social', 'outgoing', 'people']):
299
+ intelligence_bonus += 0.04
300
+ trait_match_details['temperament_match'] = f'Friendly temperament match: {temp_pref}'
301
+
302
+ # 4. 經驗水平匹配
303
+ if extracted_keywords.get('experience_level'):
304
+ exp_level = extracted_keywords['experience_level'][0]
305
+ breed_desc = breed_info.get('Description', '').lower()
306
+
307
+ if exp_level == 'beginner':
308
+ # 為初學者偏愛易於處理的品種
309
+ if any(word in breed_desc for word in ['easy', 'gentle', 'good for beginners', 'family', 'calm']):
310
+ intelligence_bonus += 0.06
311
+ trait_match_details['beginner_friendly'] = 'Good choice for first-time owners'
312
+ elif any(word in breed_desc for word in ['challenging', 'dominant', 'requires experience', 'strong-willed']):
313
+ intelligence_bonus -= 0.08
314
+ trait_match_details['experience_warning'] = 'May be challenging for first-time owners'
315
+
316
+ elif exp_level == 'advanced':
317
+ # 高級用戶可以處理更具挑戰性的品種
318
+ if any(word in breed_desc for word in ['working', 'requires experience', 'intelligent', 'strong']):
319
+ intelligence_bonus += 0.03
320
+ trait_match_details['advanced_suitable'] = 'Good match for experienced owners'
321
+
322
+ # 5. 壽命偏好匹配
323
+ if extracted_keywords.get('lifespan_preference'):
324
+ lifespan_pref = extracted_keywords['lifespan_preference'][0]
325
+ breed_lifespan = breed_info.get('Lifespan', '10-12 years')
326
+
327
+ try:
328
+ import re
329
+ years = re.findall(r'\d+', breed_lifespan)
330
+ if years:
331
+ avg_years = sum(int(y) for y in years) / len(years)
332
+ if lifespan_pref == 'long' and avg_years >= 13:
333
+ intelligence_bonus += 0.02
334
+ trait_match_details['longevity_match'] = f'Long lifespan match: {breed_lifespan}'
335
+ elif lifespan_pref == 'healthy' and avg_years >= 12:
336
+ intelligence_bonus += 0.02
337
+ trait_match_details['health_match'] = f'Healthy lifespan: {breed_lifespan}'
338
+ except:
339
+ pass
340
+
341
+ # 將智力獎勵應用到總分
342
+ original_score = rec['overall_score']
343
+ enhanced_score = min(1.0, original_score + intelligence_bonus)
344
+
345
+ # 創建包含特徵匹配詳細信息的增強推薦
346
+ enhanced_rec = rec.copy()
347
+ enhanced_rec['overall_score'] = enhanced_score
348
+ enhanced_rec['intelligence_bonus'] = intelligence_bonus
349
+ enhanced_rec['trait_match_details'] = trait_match_details
350
+
351
+ # 如果發生顯著增強,添加詳細說明
352
+ if abs(intelligence_bonus) > 0.02:
353
+ enhancement_explanation = []
354
+ for detail_key, detail_value in trait_match_details.items():
355
+ enhancement_explanation.append(detail_value)
356
+
357
+ if enhancement_explanation:
358
+ current_explanation = enhanced_rec.get('explanation', '')
359
+ enhanced_explanation = current_explanation + f" Enhanced matching: {'; '.join(enhancement_explanation)}"
360
+ enhanced_rec['explanation'] = enhanced_explanation
361
+
362
+ enhanced_recommendations.append(enhanced_rec)
363
+
364
+ # 按增強總分重新排序
365
+ enhanced_recommendations.sort(key=lambda x: x['overall_score'], reverse=True)
366
+
367
+ # 更新排名
368
+ for i, rec in enumerate(enhanced_recommendations):
369
+ rec['rank'] = i + 1
370
+
371
+ print(f"Applied intelligent trait matching with average bonus: {sum(r['intelligence_bonus'] for r in enhanced_recommendations) / len(enhanced_recommendations):.3f}")
372
+
373
+ return enhanced_recommendations
374
+
375
+ except Exception as e:
376
+ print(f"Error in intelligent trait matching: {str(e)}")
377
+ # 如果特徵匹配失敗,返回原始推薦
378
+ return recommendations
379
+
380
+ def _extract_enhanced_lifestyle_keywords(self, user_input: str) -> Dict[str, List[str]]:
381
+ """提取增強的生活方式關鍵字(用於智能特徵匹配)"""
382
+ keywords = {
383
+ 'intelligence_preference': [],
384
+ 'grooming_preference': [],
385
+ 'temperament_preference': [],
386
+ 'experience_level': [],
387
+ 'lifespan_preference': []
388
+ }
389
+
390
+ text = user_input.lower()
391
+
392
+ # 智力偏好檢測
393
+ smart_terms = ['smart', 'intelligent', 'clever', 'bright', 'quick learner', 'easy to train', 'trainable', 'genius', 'brilliant']
394
+ independent_terms = ['independent', 'stubborn', 'strong-willed', 'less trainable', 'thinks for themselves']
395
+
396
+ if any(term in text for term in smart_terms):
397
+ keywords['intelligence_preference'].append('high')
398
+ if any(term in text for term in independent_terms):
399
+ keywords['intelligence_preference'].append('independent')
400
+
401
+ # 美容偏好檢測
402
+ low_grooming_terms = ['low grooming', 'minimal grooming', 'easy care', 'wash and wear', 'no grooming', 'simple coat']
403
+ high_grooming_terms = ['high grooming', 'professional grooming', 'lots of care', 'high maintenance coat', 'daily brushing', 'regular grooming']
404
+
405
+ if any(term in text for term in low_grooming_terms):
406
+ keywords['grooming_preference'].append('low')
407
+ if any(term in text for term in high_grooming_terms):
408
+ keywords['grooming_preference'].append('high')
409
+
410
+ # 氣質偏���檢測
411
+ gentle_terms = ['gentle', 'calm', 'peaceful', 'laid back', 'chill', 'mellow', 'docile']
412
+ playful_terms = ['playful', 'energetic', 'fun', 'active personality', 'lively', 'spirited', 'bouncy']
413
+ protective_terms = ['protective', 'guard', 'watchdog', 'alert', 'vigilant', 'defensive']
414
+ friendly_terms = ['friendly', 'social', 'outgoing', 'loves people', 'sociable', 'gregarious']
415
+
416
+ if any(term in text for term in gentle_terms):
417
+ keywords['temperament_preference'].append('gentle')
418
+ if any(term in text for term in playful_terms):
419
+ keywords['temperament_preference'].append('playful')
420
+ if any(term in text for term in protective_terms):
421
+ keywords['temperament_preference'].append('protective')
422
+ if any(term in text for term in friendly_terms):
423
+ keywords['temperament_preference'].append('friendly')
424
+
425
+ # 經驗水平檢測
426
+ beginner_terms = ['first time', 'beginner', 'new to dogs', 'never had', 'novice', 'inexperienced']
427
+ advanced_terms = ['experienced', 'advanced', 'dog expert', 'many dogs before', 'professional', 'seasoned']
428
+
429
+ if any(term in text for term in beginner_terms):
430
+ keywords['experience_level'].append('beginner')
431
+ if any(term in text for term in advanced_terms):
432
+ keywords['experience_level'].append('advanced')
433
+
434
+ # 壽命偏好檢測
435
+ long_lived_terms = ['long lived', 'long lifespan', 'live long', 'many years', '15+ years', 'longevity']
436
+ healthy_terms = ['healthy breed', 'few health issues', 'robust', 'hardy', 'strong constitution']
437
+
438
+ if any(term in text for term in long_lived_terms):
439
+ keywords['lifespan_preference'].append('long')
440
+ if any(term in text for term in healthy_terms):
441
+ keywords['lifespan_preference'].append('healthy')
442
+
443
+ return keywords
444
+
445
+ def calculate_enhanced_matching_score(self, breed: str, breed_info: dict, user_description: str, base_similarity: float) -> dict:
446
+ """計算增強的匹配分數,基於用戶描述和品種特性"""
447
+ try:
448
+ user_desc = user_description.lower()
449
+
450
+ # 分析用戶需求
451
+ space_requirements = self._analyze_space_requirements(user_desc)
452
+ exercise_requirements = self._analyze_exercise_requirements(user_desc)
453
+ noise_requirements = self._analyze_noise_requirements(user_desc)
454
+ size_requirements = self._analyze_size_requirements(user_desc)
455
+ family_requirements = self._analyze_family_requirements(user_desc)
456
+
457
+ # 獲取品種特性
458
+ breed_size = breed_info.get('Size', '').lower()
459
+ breed_exercise = breed_info.get('Exercise Needs', '').lower()
460
+ breed_noise = breed_noise_info.get(breed, {}).get('noise_level', 'moderate').lower()
461
+ breed_temperament = breed_info.get('Temperament', '').lower()
462
+ breed_good_with_children = breed_info.get('Good with Children', '').lower()
463
+
464
+ # 計算各維度匹配分數
465
+ dimension_scores = {}
466
+
467
+ # 空間匹配 (30% 權重)
468
+ space_score = self._calculate_space_compatibility(space_requirements, breed_size, breed_exercise)
469
+ dimension_scores['space'] = space_score
470
+
471
+ # 運動需求匹配 (25% 權重)
472
+ exercise_score = self._calculate_exercise_compatibility(exercise_requirements, breed_exercise)
473
+ dimension_scores['exercise'] = exercise_score
474
+
475
+ # 噪音匹配 (20% 權重)
476
+ noise_score = self._calculate_noise_compatibility(noise_requirements, breed_noise)
477
+ dimension_scores['noise'] = noise_score
478
+
479
+ # 體型匹配 (15% 權重)
480
+ size_score = self._calculate_size_compatibility(size_requirements, breed_size)
481
+ dimension_scores['grooming'] = min(0.9, base_similarity + 0.1) # 美容需求基於語意相似度
482
+
483
+ # 家庭相容性 (10% 權重)
484
+ family_score = self._calculate_family_compatibility(family_requirements, breed_good_with_children, breed_temperament)
485
+ dimension_scores['family'] = family_score
486
+ dimension_scores['experience'] = min(0.9, base_similarity + 0.05) # 經驗需求基於語意相似度
487
+
488
+ # 應用硬約束過濾
489
+ constraint_penalty = self._apply_hard_constraints_enhanced(user_desc, breed_info)
490
+
491
+ # 計算加權總分 - 精確化維度權重配置
492
+ # 根據指導建議重新平衡維度權重
493
+ weighted_score = (
494
+ space_score * 0.30 + # 空間相容性(降低5%)
495
+ exercise_score * 0.28 + # 運動需求匹配(降低2%)
496
+ noise_score * 0.18 + # 噪音控制(提升3%)
497
+ family_score * 0.12 + # 家庭相容性(提升2%)
498
+ size_score * 0.08 + # 體型匹配(降低2%)
499
+ min(0.9, base_similarity + 0.1) * 0.04 # 護理需求(新增獨立���重)
500
+ )
501
+
502
+ # 優化完美匹配獎勵機制 - 降低觸發門檻並增加層次
503
+ perfect_match_bonus = 0.0
504
+ if space_score >= 0.88 and exercise_score >= 0.88 and noise_score >= 0.85:
505
+ perfect_match_bonus = 0.08 # 卓越匹配獎勵
506
+ elif space_score >= 0.82 and exercise_score >= 0.82 and noise_score >= 0.75:
507
+ perfect_match_bonus = 0.04 # 優秀匹配獎勵
508
+ elif space_score >= 0.75 and exercise_score >= 0.75:
509
+ perfect_match_bonus = 0.02 # 良好匹配獎勵
510
+
511
+ # 結合語意相似度與維度匹配 - 調整為75%維度匹配 25%語義相似度
512
+ base_combined_score = (weighted_score * 0.75 + base_similarity * 0.25) + perfect_match_bonus
513
+
514
+ # 應用漸進式約束懲罰,但確保基礎分數保障
515
+ raw_final_score = base_combined_score + constraint_penalty
516
+
517
+ # 實施動態分數保障機制 - 提升至40-42%基礎分數
518
+ # 根據品種特性動態調整基礎分數
519
+ base_guaranteed_score = 0.42 # 提升基礎保障分數
520
+
521
+ # 特殊品種基礎分數調整
522
+ high_adaptability_breeds = ['French_Bulldog', 'Pug', 'Golden_Retriever', 'Labrador_Retriever']
523
+ if any(breed in breed for breed in high_adaptability_breeds):
524
+ base_guaranteed_score = 0.45 # 高適應性品種更高基礎分數
525
+
526
+ # 動態分數分佈優化
527
+ if raw_final_score >= base_guaranteed_score:
528
+ # 對於高分品種,實施適度壓縮避免過度集中
529
+ if raw_final_score > 0.85:
530
+ compression_factor = 0.92 # 輕度壓縮高分
531
+ final_score = 0.85 + (raw_final_score - 0.85) * compression_factor
532
+ else:
533
+ final_score = raw_final_score
534
+ final_score = min(0.93, final_score) # 降低最高分數限制
535
+ else:
536
+ # 對於低分品種,使用改進的保障機制
537
+ normalized_raw_score = max(0.15, raw_final_score)
538
+ # 基礎保障75% + 實際計算25%,保持一定區分度
539
+ final_score = base_guaranteed_score * 0.75 + normalized_raw_score * 0.25
540
+ final_score = max(base_guaranteed_score, min(0.93, final_score))
541
+
542
+ lifestyle_bonus = max(0.0, weighted_score - base_similarity)
543
+
544
+ return {
545
+ 'final_score': final_score,
546
+ 'weighted_score': weighted_score,
547
+ 'lifestyle_bonus': lifestyle_bonus,
548
+ 'dimension_scores': dimension_scores,
549
+ 'constraint_penalty': constraint_penalty
550
+ }
551
+
552
+ except Exception as e:
553
+ print(f"Error in enhanced matching calculation for {breed}: {str(e)}")
554
+ return {
555
+ 'final_score': base_similarity,
556
+ 'weighted_score': base_similarity,
557
+ 'lifestyle_bonus': 0.0,
558
+ 'dimension_scores': {
559
+ 'space': base_similarity * 0.9,
560
+ 'exercise': base_similarity * 0.85,
561
+ 'grooming': base_similarity * 0.8,
562
+ 'experience': base_similarity * 0.75,
563
+ 'noise': base_similarity * 0.7,
564
+ 'family': base_similarity * 0.65
565
+ },
566
+ 'constraint_penalty': 0.0
567
+ }
568
+
569
+ def _analyze_space_requirements(self, user_desc: str) -> dict:
570
+ """分析空間需求 - 增強中等活動量識別"""
571
+ requirements = {'type': 'unknown', 'size': 'medium', 'importance': 0.5}
572
+
573
+ if any(word in user_desc for word in ['apartment', 'small apartment', 'small space', 'condo', 'flat']):
574
+ requirements['type'] = 'apartment'
575
+ requirements['size'] = 'small'
576
+ requirements['importance'] = 0.95 # 提高重要性
577
+ elif any(word in user_desc for word in ['medium-sized house', 'medium house', 'townhouse']):
578
+ requirements['type'] = 'medium_house'
579
+ requirements['size'] = 'medium'
580
+ requirements['importance'] = 0.8 # 中等活動量用戶的特殊標記
581
+ elif any(word in user_desc for word in ['large house', 'big house', 'yard', 'garden', 'large space', 'backyard']):
582
+ requirements['type'] = 'house'
583
+ requirements['size'] = 'large'
584
+ requirements['importance'] = 0.7
585
+
586
+ return requirements
587
+
588
+ def _analyze_exercise_requirements(self, user_desc: str) -> dict:
589
+ """分析運動需求 - 增強中等活動量識別"""
590
+ requirements = {'level': 'moderate', 'importance': 0.5}
591
+
592
+ # 低運動量識別
593
+ if any(word in user_desc for word in ["don't exercise", "don't exercise much", "low exercise", "minimal", "lazy", "not active"]):
594
+ requirements['level'] = 'low'
595
+ requirements['importance'] = 0.95
596
+ # 中等運動量的精確識別
597
+ elif any(phrase in user_desc for phrase in ['30 minutes', 'half hour', 'moderate', 'balanced', 'walk about']):
598
+ if 'walk' in user_desc or 'daily' in user_desc:
599
+ requirements['level'] = 'moderate'
600
+ requirements['importance'] = 0.85 # 中等活動量的特殊標記
601
+ # 高運動量識別
602
+ elif any(word in user_desc for word in ['active', 'hiking', 'outdoor activities', 'running', 'outdoors', 'love hiking']):
603
+ requirements['level'] = 'high'
604
+ requirements['importance'] = 0.9
605
+
606
+ return requirements
607
+
608
+ def _analyze_noise_requirements(self, user_desc: str) -> dict:
609
+ """分析噪音需求"""
610
+ requirements = {'tolerance': 'medium', 'importance': 0.5}
611
+
612
+ if any(word in user_desc for word in ['quiet', 'no bark', "won't bark", "doesn't bark", 'silent', 'peaceful']):
613
+ requirements['tolerance'] = 'low'
614
+ requirements['importance'] = 0.9
615
+ elif any(word in user_desc for word in ['loud', 'barking ok', 'noise ok']):
616
+ requirements['tolerance'] = 'high'
617
+ requirements['importance'] = 0.7
618
+
619
+ return requirements
620
+
621
+ def _analyze_size_requirements(self, user_desc: str) -> dict:
622
+ """分析體型需求"""
623
+ requirements = {'preferred': 'any', 'importance': 0.5}
624
+
625
+ if any(word in user_desc for word in ['small', 'tiny', 'little', 'lap dog', 'compact']):
626
+ requirements['preferred'] = 'small'
627
+ requirements['importance'] = 0.8
628
+ elif any(word in user_desc for word in ['large', 'big', 'giant']):
629
+ requirements['preferred'] = 'large'
630
+ requirements['importance'] = 0.8
631
+
632
+ return requirements
633
+
634
+ def _analyze_family_requirements(self, user_desc: str) -> dict:
635
+ """分析家庭需求"""
636
+ requirements = {'children': False, 'importance': 0.3}
637
+
638
+ if any(word in user_desc for word in ['children', 'kids', 'family', 'child']):
639
+ requirements['children'] = True
640
+ requirements['importance'] = 0.8
641
+
642
+ return requirements
643
+
644
+ def _calculate_space_compatibility(self, space_req: dict, breed_size: str, breed_exercise: str) -> float:
645
+ """計算空間相容性分數 - 增強中等活動量處理"""
646
+ if space_req['type'] == 'apartment':
647
+ if 'small' in breed_size or 'toy' in breed_size:
648
+ base_score = 0.95
649
+ elif 'medium' in breed_size:
650
+ if 'low' in breed_exercise:
651
+ base_score = 0.75
652
+ else:
653
+ base_score = 0.45 # 降低中型犬在公寓的分數
654
+ elif 'large' in breed_size:
655
+ base_score = 0.05 # 大型犬極度不適合公寓
656
+ elif 'giant' in breed_size:
657
+ base_score = 0.01 # 超大型犬完全不適合公寓
658
+ else:
659
+ base_score = 0.7
660
+ elif space_req['type'] == 'medium_house':
661
+ # 中型房屋的特殊處理 - 適合中等活動量用戶
662
+ if 'small' in breed_size or 'toy' in breed_size:
663
+ base_score = 0.9
664
+ elif 'medium' in breed_size:
665
+ base_score = 0.95 # 中型犬在中型房屋很適合
666
+ elif 'large' in breed_size:
667
+ if 'moderate' in breed_exercise or 'low' in breed_exercise:
668
+ base_score = 0.8 # 低運動量大型犬還可以
669
+ else:
670
+ base_score = 0.6 # 高運動量大型犬不太適合
671
+ elif 'giant' in breed_size:
672
+ base_score = 0.3 # 超大型犬在中型房屋不太適合
673
+ else:
674
+ base_score = 0.85
675
+ else:
676
+ # 大型房屋的情況
677
+ if 'small' in breed_size or 'toy' in breed_size:
678
+ base_score = 0.85
679
+ elif 'medium' in breed_size:
680
+ base_score = 0.9
681
+ elif 'large' in breed_size or 'giant' in breed_size:
682
+ base_score = 0.95
683
+ else:
684
+ base_score = 0.8
685
+
686
+ return min(0.95, base_score)
687
+
688
+ def _calculate_exercise_compatibility(self, exercise_req: dict, breed_exercise: str) -> float:
689
+ """計算運動需求相容性分數 - 增強中等活動量處理"""
690
+ if exercise_req['level'] == 'low':
691
+ if 'low' in breed_exercise or 'minimal' in breed_exercise:
692
+ return 0.95
693
+ elif 'moderate' in breed_exercise:
694
+ return 0.5 # 降低不匹配分數
695
+ elif 'high' in breed_exercise:
696
+ return 0.1 # 進一步降低高運動需求的匹配
697
+ else:
698
+ return 0.7
699
+ elif exercise_req['level'] == 'high':
700
+ if 'high' in breed_exercise:
701
+ return 0.95
702
+ elif 'moderate' in breed_exercise:
703
+ return 0.8
704
+ elif 'low' in breed_exercise:
705
+ return 0.6
706
+ else:
707
+ return 0.7
708
+ else: # moderate - 中等活動量的精確處理
709
+ if 'moderate' in breed_exercise:
710
+ return 0.95 # 完美匹配
711
+ elif 'low' in breed_exercise:
712
+ return 0.85 # 低運動需求的品種對中等活動量用戶也不錯
713
+ elif 'high' in breed_exercise:
714
+ return 0.5 # 中等活動量用戶不太適合高運動需求品種
715
+ else:
716
+ return 0.75
717
+
718
+ return 0.6
719
+
720
+ def _calculate_noise_compatibility(self, noise_req: dict, breed_noise: str) -> float:
721
+ """計算噪音相容性分數,更好處理複合等級"""
722
+ breed_noise_lower = breed_noise.lower()
723
+
724
+ if noise_req['tolerance'] == 'low':
725
+ if 'low' in breed_noise_lower and 'moderate' not in breed_noise_lower:
726
+ return 0.95 # 純低噪音
727
+ elif 'low-moderate' in breed_noise_lower or 'low to moderate' in breed_noise_lower:
728
+ return 0.8 # 低到中等噪音,還可接受
729
+ elif breed_noise_lower in ['moderate']:
730
+ return 0.4 # 中等噪音有些問題
731
+ elif 'high' in breed_noise_lower:
732
+ return 0.1 # 高噪音不適合
733
+ else:
734
+ return 0.6 # 未知噪音水平,保守估計
735
+ elif noise_req['tolerance'] == 'high':
736
+ if 'high' in breed_noise_lower:
737
+ return 0.9
738
+ elif 'moderate' in breed_noise_lower:
739
+ return 0.85
740
+ elif 'low' in breed_noise_lower:
741
+ return 0.8 # 安靜犬對高容忍度的人也很好
742
+ else:
743
+ return 0.8
744
+ else: # moderate tolerance
745
+ if 'moderate' in breed_noise_lower:
746
+ return 0.9
747
+ elif 'low' in breed_noise_lower:
748
+ return 0.85
749
+ elif 'high' in breed_noise_lower:
750
+ return 0.6
751
+ else:
752
+ return 0.75
753
+
754
+ return 0.7
755
+
756
+ def _calculate_size_compatibility(self, size_req: dict, breed_size: str) -> float:
757
+ """計算體型相容性分數"""
758
+ if size_req['preferred'] == 'small':
759
+ if any(word in breed_size for word in ['small', 'toy', 'tiny']):
760
+ return 0.9
761
+ elif 'medium' in breed_size:
762
+ return 0.6
763
+ else:
764
+ return 0.3
765
+ elif size_req['preferred'] == 'large':
766
+ if any(word in breed_size for word in ['large', 'giant']):
767
+ return 0.9
768
+ elif 'medium' in breed_size:
769
+ return 0.7
770
+ else:
771
+ return 0.4
772
+
773
+ return 0.7 # 無特別偏好
774
+
775
+ def _calculate_family_compatibility(self, family_req: dict, good_with_children: str, temperament: str) -> float:
776
+ """計算家庭相容性分數"""
777
+ if family_req['children']:
778
+ if 'yes' in good_with_children.lower():
779
+ return 0.9
780
+ elif any(word in temperament for word in ['gentle', 'patient', 'friendly']):
781
+ return 0.8
782
+ elif 'no' in good_with_children.lower():
783
+ return 0.2
784
+ else:
785
+ return 0.6
786
+
787
+ return 0.7
788
+
789
+ def _apply_hard_constraints_enhanced(self, user_desc: str, breed_info: dict) -> float:
790
+ """應用品種特性感知的動態懲罰機制"""
791
+ penalty = 0.0
792
+
793
+ # 建立懲罰衰減係數和補償機制
794
+ penalty_decay_factor = 0.7
795
+ breed_adaptability_bonus = 0.0
796
+ breed_size = breed_info.get('Size', '').lower()
797
+ breed_exercise = breed_info.get('Exercise Needs', '').lower()
798
+ breed_name = breed_info.get('Breed', '').replace(' ', '_')
799
+
800
+ # 公寓空間約束 - 品種特性感知懲罰機制
801
+ if 'apartment' in user_desc or 'small apartment' in user_desc:
802
+ if 'giant' in breed_size:
803
+ base_penalty = -0.35 # 減少基礎懲罰
804
+ # 特定品種適應性補償
805
+ adaptable_giants = ['Mastiff', 'Great Dane'] # 相對安靜的巨型犬
806
+ if any(adapt_breed in breed_name for adapt_breed in adaptable_giants):
807
+ breed_adaptability_bonus += 0.08
808
+ penalty += base_penalty * penalty_decay_factor
809
+ elif 'large' in breed_size:
810
+ base_penalty = -0.25 # 減少大型犬懲罰
811
+ # 適合公寓的大型犬補償
812
+ apartment_friendly_large = ['Greyhound', 'Great_Dane']
813
+ if any(apt_breed in breed_name for apt_breed in apartment_friendly_large):
814
+ breed_adaptability_bonus += 0.06
815
+ penalty += base_penalty * penalty_decay_factor
816
+ elif 'medium' in breed_size and 'high' in breed_exercise:
817
+ penalty += -0.15 * penalty_decay_factor # 進一步減少懲罰
818
+
819
+ # 運動需求不匹配 - 品種特性感知懲罰機制
820
+ if any(phrase in user_desc for phrase in ["don't exercise", "not active", "low exercise", "don't exercise much"]):
821
+ if 'high' in breed_exercise:
822
+ base_penalty = -0.28 # 減少基礎懲罰
823
+ # 低維護高運動犬種補償
824
+ adaptable_high_energy = ['Greyhound', 'Whippet'] # 運動爆發型,平時安靜
825
+ if any(adapt_breed in breed_name for adapt_breed in adaptable_high_energy):
826
+ breed_adaptability_bonus += 0.10
827
+ penalty += base_penalty * penalty_decay_factor
828
+ elif 'moderate' in breed_exercise:
829
+ penalty += -0.08 * penalty_decay_factor # 進一步減少懲罰
830
+
831
+ # 噪音控制需求不匹配 - 品種特性感知懲罰機制
832
+ if any(phrase in user_desc for phrase in ['quiet', "won't bark", "doesn't bark", "silent"]):
833
+ breed_noise = breed_noise_info.get(breed_name, {}).get('noise_level', 'moderate').lower()
834
+ if 'high' in breed_noise:
835
+ base_penalty = -0.18 # 減少基礎懲罰
836
+ # 訓練性良好的高噪音品種補償
837
+ trainable_vocal_breeds = ['German_Shepherd', 'Golden_Retriever']
838
+ if any(train_breed in breed_name for train_breed in trainable_vocal_breeds):
839
+ breed_adaptability_bonus += 0.05
840
+ penalty += base_penalty * penalty_decay_factor
841
+ elif 'moderate' in breed_noise and 'low' not in breed_noise:
842
+ penalty += -0.05 * penalty_decay_factor
843
+
844
+ # 體型偏好不匹配 - 漸進式懲罰
845
+ if any(phrase in user_desc for phrase in ['small', 'tiny', 'little']):
846
+ if 'giant' in breed_size:
847
+ penalty -= 0.35 # 超大型犬懲罰
848
+ elif 'large' in breed_size:
849
+ penalty -= 0.20 # 大型犬懲罰
850
+
851
+ # 中等活動量用戶的特殊約束處理 - 漸進式懲罰
852
+ moderate_activity_terms = ['30 minutes', 'half hour', 'moderate', 'balanced', 'medium-sized house']
853
+ if any(term in user_desc for term in moderate_activity_terms):
854
+ # 超大型犬對中等活動量用戶的適度懲罰
855
+ giant_breeds = ['Saint Bernard', 'Tibetan Mastiff', 'Great Dane', 'Mastiff', 'Newfoundland']
856
+ if any(giant in breed_name for giant in giant_breeds) or 'giant' in breed_size:
857
+ penalty -= 0.35 # 適度懲罰,不完全排除
858
+
859
+ # 中型房屋 + 超大型犬的額外考量
860
+ if 'medium-sized house' in user_desc and any(giant in breed_name for giant in giant_breeds):
861
+ if not any(high_activity in user_desc for high_activity in ['hiking', 'running', 'active', 'outdoor activities']):
862
+ penalty -= 0.15 # 輕度額外懲罰
863
+
864
+ # 30分鐘散步對極高運動需求品種的懲罰
865
+ if any(term in user_desc for term in ['30 minutes', 'half hour']) and 'walk' in user_desc:
866
+ high_energy_breeds = ['Siberian Husky', 'Border Collie', 'Jack Russell Terrier', 'Weimaraner']
867
+ if any(he_breed in breed_name for he_breed in high_energy_breeds) and 'high' in breed_exercise:
868
+ penalty -= 0.25 # 適度懲罰極高運動需求品種
869
+
870
+ # 添加特殊品種適應性補償機制
871
+ # 對於邊界適配品種,給予適度補償
872
+ boundary_adaptable_breeds = {
873
+ 'Italian_Greyhound': 0.08, # 安靜、低維護的小型犬
874
+ 'Boston_Bull': 0.06, # 適應性強的小型犬
875
+ 'Havanese': 0.05, # 友好適應的小型犬
876
+ 'Silky_terrier': 0.04, # 安靜的玩具犬
877
+ 'Basset': 0.07 # 低能量但友好的中型犬
878
+ }
879
+
880
+ if breed_name in boundary_adaptable_breeds:
881
+ breed_adaptability_bonus += boundary_adaptable_breeds[breed_name]
882
+
883
+ # 應用品種適應性補償並設置懲罰上限
884
+ final_penalty = penalty + breed_adaptability_bonus
885
+ # 限制最大懲罰,避免單一約束主導評分
886
+ final_penalty = max(-0.4, final_penalty)
887
+
888
+ return final_penalty
889
+
890
+ def get_breed_characteristics_enhanced(self, breed: str) -> Dict[str, Any]:
891
+ """獲取品種特徵"""
892
+ breed_info = get_dog_description(breed)
893
+ if not breed_info:
894
+ return {}
895
+
896
+ characteristics = {
897
+ 'size': breed_info.get('Size', 'Unknown'),
898
+ 'temperament': breed_info.get('Temperament', ''),
899
+ 'exercise_needs': breed_info.get('Exercise Needs', 'Moderate'),
900
+ 'grooming_needs': breed_info.get('Grooming Needs', 'Moderate'),
901
+ 'good_with_children': breed_info.get('Good with Children', 'Unknown'),
902
+ 'lifespan': breed_info.get('Lifespan', '10-12 years'),
903
+ 'description': breed_info.get('Description', '')
904
+ }
905
+
906
+ # 添加噪音資訊
907
+ noise_info = breed_noise_info.get(breed, {})
908
+ characteristics['noise_level'] = noise_info.get('noise_level', 'moderate')
909
+
910
+ return characteristics
911
+
912
+ def get_breed_info_from_standardized(self, standardized_info) -> Dict[str, Any]:
913
+ """將標準化品種信息轉換為字典���式"""
914
+ try:
915
+ size_map = {1: 'Tiny', 2: 'Small', 3: 'Medium', 4: 'Large', 5: 'Giant'}
916
+ exercise_map = {1: 'Low', 2: 'Moderate', 3: 'High', 4: 'Very High'}
917
+ care_map = {1: 'Low', 2: 'Moderate', 3: 'High'}
918
+
919
+ return {
920
+ 'Size': size_map.get(standardized_info.size_category, 'Medium'),
921
+ 'Exercise Needs': exercise_map.get(standardized_info.exercise_level, 'Moderate'),
922
+ 'Grooming Needs': care_map.get(standardized_info.care_complexity, 'Moderate'),
923
+ 'Good with Children': 'Yes' if standardized_info.child_compatibility >= 0.8 else
924
+ 'No' if standardized_info.child_compatibility <= 0.2 else 'Unknown',
925
+ 'Temperament': 'Varies by individual',
926
+ 'Lifespan': '10-12 years',
927
+ 'Description': f'A {size_map.get(standardized_info.size_category, "medium")} sized breed'
928
+ }
929
+ except Exception as e:
930
+ print(f"Error converting standardized info: {str(e)}")
931
+ return {}
932
+
933
+ def get_fallback_recommendations(self, top_k: int = 15) -> List[Dict[str, Any]]:
934
+ """當增強系統失敗時獲取備用推薦"""
935
+ try:
936
+ safe_breeds = [
937
+ ('Labrador Retriever', 0.85),
938
+ ('Golden Retriever', 0.82),
939
+ ('Cavalier King Charles Spaniel', 0.80),
940
+ ('French Bulldog', 0.78),
941
+ ('Boston Terrier', 0.76),
942
+ ('Bichon Frise', 0.74),
943
+ ('Pug', 0.72),
944
+ ('Cocker Spaniel', 0.70)
945
+ ]
946
+
947
+ recommendations = []
948
+ for i, (breed, score) in enumerate(safe_breeds[:top_k]):
949
+ breed_info = get_dog_description(breed.replace(' ', '_')) or {}
950
+
951
+ recommendation = {
952
+ 'breed': breed,
953
+ 'rank': i + 1,
954
+ 'overall_score': score,
955
+ 'final_score': score,
956
+ 'semantic_score': score * 0.8,
957
+ 'comparative_bonus': 0.0,
958
+ 'lifestyle_bonus': 0.0,
959
+ 'size': breed_info.get('Size', 'Unknown'),
960
+ 'temperament': breed_info.get('Temperament', ''),
961
+ 'exercise_needs': breed_info.get('Exercise Needs', 'Moderate'),
962
+ 'grooming_needs': breed_info.get('Grooming Needs', 'Moderate'),
963
+ 'good_with_children': breed_info.get('Good with Children', 'Yes'),
964
+ 'lifespan': breed_info.get('Lifespan', '10-12 years'),
965
+ 'description': breed_info.get('Description', ''),
966
+ 'search_type': 'fallback'
967
+ }
968
+ recommendations.append(recommendation)
969
+
970
+ return recommendations
971
+
972
+ except Exception as e:
973
+ print(f"Error generating fallback recommendations: {str(e)}")
974
+ return []
recommendation_formatter.py CHANGED
@@ -28,6 +28,10 @@ def get_breed_recommendations(user_prefs: UserPreferences, top_n: int = 15) -> L
28
  breed = breed_tuple[0]
29
  base_breed = breed.split('(')[0].strip()
30
 
 
 
 
 
31
  if base_breed in seen_breeds:
32
  continue
33
  seen_breeds.add(base_breed)
@@ -127,7 +131,7 @@ def get_breed_recommendations(user_prefs: UserPreferences, top_n: int = 15) -> L
127
 
128
  print(f"Breeds after filtering: {len(recommendations)}")
129
 
130
- # 嚴格按照 final_score 排序
131
  recommendations.sort(key=lambda x: (round(-x['final_score'], 4), x['breed']))
132
 
133
  # 修正後的推薦選擇邏輯,移除有問題的分數比較
 
28
  breed = breed_tuple[0]
29
  base_breed = breed.split('(')[0].strip()
30
 
31
+ # 過濾掉野生動物品種
32
+ if base_breed == 'Dhole':
33
+ continue
34
+
35
  if base_breed in seen_breeds:
36
  continue
37
  seen_breeds.add(base_breed)
 
131
 
132
  print(f"Breeds after filtering: {len(recommendations)}")
133
 
134
+ # 按照 final_score 排序
135
  recommendations.sort(key=lambda x: (round(-x['final_score'], 4), x['breed']))
136
 
137
  # 修正後的推薦選擇邏輯,移除有問題的分數比較
semantic_breed_recommender.py CHANGED
The diff for this file is too large to render. See raw diff
 
semantic_vector_manager.py ADDED
@@ -0,0 +1,385 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import hashlib
3
+ import numpy as np
4
+ import sqlite3
5
+ import re
6
+ import traceback
7
+ from typing import List, Dict, Tuple, Optional, Any
8
+ from dataclasses import dataclass
9
+ from sentence_transformers import SentenceTransformer
10
+ import torch
11
+ from sklearn.metrics.pairwise import cosine_similarity
12
+ from dog_database import get_dog_description
13
+ from breed_health_info import breed_health_info
14
+ from breed_noise_info import breed_noise_info
15
+
16
+ @dataclass
17
+ class BreedDescriptionVector:
18
+ """品種描述向量的資料結構"""
19
+ breed_name: str
20
+ description_text: str
21
+ embedding: np.ndarray
22
+ characteristics: Dict[str, Any]
23
+
24
+ class SemanticVectorManager:
25
+ """
26
+ 語義向量管理器
27
+ 處理 SBERT 模型初始化、品種向量化建構和品種描述生成
28
+ """
29
+
30
+ def __init__(self):
31
+ """初始化語義向量管理器"""
32
+ self.model_name = 'all-MiniLM-L6-v2'
33
+ self.sbert_model = None
34
+ self._sbert_loading_attempted = False
35
+ self.breed_vectors = {}
36
+ self.breed_list = self._get_breed_list()
37
+ # 延遲SBERT模型載入直到需要時才在GPU環境中進行
38
+ print("SemanticVectorManager initialized (SBERT loading deferred)")
39
+
40
+ def _get_breed_list(self) -> List[str]:
41
+ """從資料庫獲取品種清單"""
42
+ try:
43
+ conn = sqlite3.connect('animal_detector.db')
44
+ cursor = conn.cursor()
45
+ cursor.execute("SELECT DISTINCT Breed FROM AnimalCatalog")
46
+ breeds = [row[0] for row in cursor.fetchall()]
47
+ cursor.close()
48
+ conn.close()
49
+ # 過濾掉野生動物品種
50
+ breeds = [breed for breed in breeds if breed != 'Dhole']
51
+ return breeds
52
+ except Exception as e:
53
+ print(f"Error getting breed list: {str(e)}")
54
+ return ['Labrador_Retriever', 'German_Shepherd', 'Golden_Retriever',
55
+ 'Bulldog', 'Poodle', 'Beagle', 'Rottweiler', 'Yorkshire_Terrier']
56
+
57
+ def _initialize_model(self):
58
+ """初始化 SBERT 模型,包含容錯機制 - 設計用於ZeroGPU相容性"""
59
+ if self.sbert_model is not None or self._sbert_loading_attempted:
60
+ return self.sbert_model
61
+
62
+ try:
63
+ print("Loading SBERT model in GPU context...")
64
+ # 如果主要模型失敗,嘗試不同的模型名稱
65
+ model_options = ['all-MiniLM-L6-v2', 'all-mpnet-base-v2', 'all-MiniLM-L12-v2']
66
+
67
+ for model_name in model_options:
68
+ try:
69
+ # 明確指定設備以處理ZeroGPU環境
70
+ import torch
71
+ device = 'cuda' if torch.cuda.is_available() else 'cpu'
72
+ self.sbert_model = SentenceTransformer(model_name, device=device)
73
+ self.model_name = model_name
74
+ print(f"SBERT model {model_name} loaded successfully on {device}")
75
+ return self.sbert_model
76
+ except Exception as model_e:
77
+ print(f"Failed to load {model_name}: {str(model_e)}")
78
+ continue
79
+
80
+ # 如果所有模型都失敗
81
+ print("All SBERT models failed to load. Using basic text matching fallback.")
82
+ self.sbert_model = None
83
+ return None
84
+
85
+ except Exception as e:
86
+ print(f"Failed to initialize any SBERT model: {str(e)}")
87
+ print(traceback.format_exc())
88
+ print("Will provide basic text-based recommendations without embeddings")
89
+ self.sbert_model = None
90
+ return None
91
+ finally:
92
+ self._sbert_loading_attempted = True
93
+
94
+ def _create_breed_description(self, breed: str) -> str:
95
+ """為品種創建包含所有關鍵特徵的全面自然語言描述"""
96
+ try:
97
+ # 獲取所有信息來源
98
+ breed_info = get_dog_description(breed) or {}
99
+ health_info = breed_health_info.get(breed, {}) if breed_health_info else {}
100
+ noise_info = breed_noise_info.get(breed, {}) if breed_noise_info else {}
101
+
102
+ breed_display_name = breed.replace('_', ' ')
103
+ description_parts = []
104
+
105
+ # 1. 基本尺寸和身體特徵
106
+ size = breed_info.get('Size', 'medium').lower()
107
+ description_parts.append(f"{breed_display_name} is a {size} sized dog breed")
108
+
109
+ # 2. 氣質和個性(匹配的關鍵因素)
110
+ temperament = breed_info.get('Temperament', '')
111
+ if temperament:
112
+ description_parts.append(f"with a {temperament.lower()} temperament")
113
+
114
+ # 3. 運動和活動水平(公寓居住的關鍵因素)
115
+ exercise_needs = breed_info.get('Exercise Needs', 'moderate').lower()
116
+ if 'high' in exercise_needs or 'very high' in exercise_needs:
117
+ description_parts.append("requiring high daily exercise and mental stimulation")
118
+ elif 'low' in exercise_needs or 'minimal' in exercise_needs:
119
+ description_parts.append("with minimal exercise requirements, suitable for apartment living")
120
+ else:
121
+ description_parts.append("with moderate exercise needs")
122
+
123
+ # 4. 噪音特徵(安靜需求的關鍵因素)
124
+ noise_level = noise_info.get('noise_level', 'moderate').lower()
125
+ if 'low' in noise_level or 'quiet' in noise_level:
126
+ description_parts.append("known for being quiet and rarely barking")
127
+ elif 'high' in noise_level or 'loud' in noise_level:
128
+ description_parts.append("tends to be vocal and bark frequently")
129
+ else:
130
+ description_parts.append("with moderate barking tendencies")
131
+
132
+ # 5. 居住空間相容性
133
+ if size in ['small', 'tiny']:
134
+ description_parts.append("excellent for small apartments and limited spaces")
135
+ elif size in ['large', 'giant']:
136
+ description_parts.append("requiring large living spaces and preferably a yard")
137
+ else:
138
+ description_parts.append("adaptable to various living situations")
139
+
140
+ # 6. 美容和維護
141
+ grooming_needs = breed_info.get('Grooming Needs', 'moderate').lower()
142
+ if 'high' in grooming_needs:
143
+ description_parts.append("requiring regular professional grooming")
144
+ elif 'low' in grooming_needs:
145
+ description_parts.append("with minimal grooming requirements")
146
+ else:
147
+ description_parts.append("with moderate grooming needs")
148
+
149
+ # 7. 家庭相容性
150
+ good_with_children = breed_info.get('Good with Children', 'Yes')
151
+ if good_with_children == 'Yes':
152
+ description_parts.append("excellent with children and families")
153
+ else:
154
+ description_parts.append("better suited for adult households")
155
+
156
+ # 8. 智力和可訓練性(從資料庫描述中提取)
157
+ intelligence_keywords = []
158
+ description_text = breed_info.get('Description', '').lower()
159
+
160
+ if description_text:
161
+ # 從描述中提取智力指標
162
+ if any(word in description_text for word in ['intelligent', 'smart', 'clever', 'quick to learn']):
163
+ intelligence_keywords.extend(['highly intelligent', 'trainable', 'quick learner'])
164
+ elif any(word in description_text for word in ['stubborn', 'independent', 'difficult to train']):
165
+ intelligence_keywords.extend(['independent minded', 'requires patience', 'challenging to train'])
166
+ else:
167
+ intelligence_keywords.extend(['moderate intelligence', 'trainable with consistency'])
168
+
169
+ # 從描述中提取工作/用途特徵
170
+ if any(word in description_text for word in ['working', 'herding', 'guard', 'hunting']):
171
+ intelligence_keywords.extend(['working breed', 'purpose-driven', 'task-oriented'])
172
+ elif any(word in description_text for word in ['companion', 'lap', 'toy', 'decorative']):
173
+ intelligence_keywords.extend(['companion breed', 'affectionate', 'people-focused'])
174
+
175
+ # 添加智力背景到描述中
176
+ if intelligence_keywords:
177
+ description_parts.append(f"characterized as {', '.join(intelligence_keywords[:2])}")
178
+
179
+ # 9. 特殊特徵和用途(使用資料庫挖掘進行增強)
180
+ if breed_info.get('Description'):
181
+ desc = breed_info.get('Description', '')[:150] # 增加到 150 字元以提供更多背景
182
+ if desc:
183
+ # 從描述中提取關鍵特徵以便更好的語義匹配
184
+ desc_lower = desc.lower()
185
+ key_traits = []
186
+
187
+ # 從描述中提取關鍵行為特徵
188
+ if 'friendly' in desc_lower:
189
+ key_traits.append('friendly')
190
+ if 'gentle' in desc_lower:
191
+ key_traits.append('gentle')
192
+ if 'energetic' in desc_lower or 'active' in desc_lower:
193
+ key_traits.append('energetic')
194
+ if 'calm' in desc_lower or 'peaceful' in desc_lower:
195
+ key_traits.append('calm')
196
+ if 'protective' in desc_lower or 'guard' in desc_lower:
197
+ key_traits.append('protective')
198
+
199
+ trait_text = f" and {', '.join(key_traits)}" if key_traits else ""
200
+ description_parts.append(f"Known for: {desc.lower()}{trait_text}")
201
+
202
+ # 10. 照護水平需求
203
+ try:
204
+ care_level = breed_info.get('Care Level', 'moderate')
205
+ if isinstance(care_level, str):
206
+ description_parts.append(f"requiring {care_level.lower()} overall care level")
207
+ else:
208
+ description_parts.append("requiring moderate overall care level")
209
+ except Exception as e:
210
+ print(f"Error processing care level for {breed}: {str(e)}")
211
+ description_parts.append("requiring moderate overall care level")
212
+
213
+ # 11. 壽命資訊
214
+ try:
215
+ lifespan = breed_info.get('Lifespan', '10-12 years')
216
+ if lifespan and isinstance(lifespan, str) and lifespan.strip():
217
+ description_parts.append(f"with a typical lifespan of {lifespan}")
218
+ else:
219
+ description_parts.append("with a typical lifespan of 10-12 years")
220
+ except Exception as e:
221
+ print(f"Error processing lifespan for {breed}: {str(e)}")
222
+ description_parts.append("with a typical lifespan of 10-12 years")
223
+
224
+ # 創建全面的描述
225
+ full_description = '. '.join(description_parts) + '.'
226
+
227
+ # 添加全面的關鍵字以便更好的語義匹配
228
+ keywords = []
229
+
230
+ # 基本品種名稱關鍵字
231
+ keywords.extend([word.lower() for word in breed_display_name.split()])
232
+
233
+ # 氣質關鍵字
234
+ if temperament:
235
+ keywords.extend([word.lower().strip(',') for word in temperament.split()])
236
+
237
+ # 基於尺寸的關鍵字
238
+ if 'small' in size or 'tiny' in size:
239
+ keywords.extend(['small', 'tiny', 'compact', 'little', 'apartment', 'indoor', 'lap'])
240
+ elif 'large' in size or 'giant' in size:
241
+ keywords.extend(['large', 'big', 'giant', 'huge', 'yard', 'space', 'outdoor'])
242
+ else:
243
+ keywords.extend(['medium', 'moderate', 'average', 'balanced'])
244
+
245
+ # 活動水平關鍵字
246
+ exercise_needs = breed_info.get('Exercise Needs', 'moderate').lower()
247
+ if 'high' in exercise_needs:
248
+ keywords.extend(['active', 'energetic', 'exercise', 'outdoor', 'hiking', 'running', 'athletic'])
249
+ elif 'low' in exercise_needs:
250
+ keywords.extend(['calm', 'low-energy', 'indoor', 'relaxed', 'couch', 'sedentary'])
251
+ else:
252
+ keywords.extend(['moderate', 'balanced', 'walks', 'regular'])
253
+
254
+ # 噪音水平關鍵字
255
+ noise_level = noise_info.get('noise_level', 'moderate').lower()
256
+ if 'quiet' in noise_level or 'low' in noise_level:
257
+ keywords.extend(['quiet', 'silent', 'calm', 'peaceful', 'low-noise'])
258
+ elif 'high' in noise_level or 'loud' in noise_level:
259
+ keywords.extend(['vocal', 'barking', 'loud', 'alert', 'watchdog'])
260
+
261
+ # 居住情況關鍵字
262
+ if size in ['small', 'tiny'] and 'low' in exercise_needs:
263
+ keywords.extend(['apartment', 'city', 'urban', 'small-space'])
264
+ if size in ['large', 'giant'] or 'high' in exercise_needs:
265
+ keywords.extend(['house', 'yard', 'suburban', 'rural', 'space'])
266
+
267
+ # 家庭關鍵字
268
+ good_with_children = breed_info.get('Good with Children', 'Yes')
269
+ if good_with_children == 'Yes':
270
+ keywords.extend(['family', 'children', 'kids', 'friendly', 'gentle'])
271
+
272
+ # 智力和可訓練性關鍵字(從資料庫描述挖掘)
273
+ if intelligence_keywords:
274
+ keywords.extend([word.lower() for phrase in intelligence_keywords for word in phrase.split()])
275
+
276
+ # 美容相關關鍵字(增強)
277
+ grooming_needs = breed_info.get('Grooming Needs', 'moderate').lower()
278
+ if 'high' in grooming_needs:
279
+ keywords.extend(['high-maintenance', 'professional-grooming', 'daily-brushing', 'coat-care'])
280
+ elif 'low' in grooming_needs:
281
+ keywords.extend(['low-maintenance', 'minimal-grooming', 'easy-care', 'wash-and-go'])
282
+ else:
283
+ keywords.extend(['moderate-grooming', 'weekly-brushing', 'regular-care'])
284
+
285
+ # 基於壽命的關鍵字
286
+ lifespan = breed_info.get('Lifespan', '10-12 years')
287
+ if lifespan and isinstance(lifespan, str):
288
+ try:
289
+ # 從壽命字符串中提取年數(例如 "10-12 years" 或 "12-15 years")
290
+ import re
291
+ years = re.findall(r'\d+', lifespan)
292
+ if years:
293
+ avg_years = sum(int(y) for y in years) / len(years)
294
+ if avg_years >= 14:
295
+ keywords.extend(['long-lived', 'longevity', 'durable', 'healthy-lifespan'])
296
+ elif avg_years <= 8:
297
+ keywords.extend(['shorter-lifespan', 'health-considerations', 'special-care'])
298
+ else:
299
+ keywords.extend(['average-lifespan', 'moderate-longevity'])
300
+ except:
301
+ keywords.extend(['average-lifespan'])
302
+
303
+ # 將關鍵字添加到描述中���便更好的語義匹配
304
+ unique_keywords = list(set(keywords))
305
+ keyword_text = ' '.join(unique_keywords)
306
+ full_description += f" Additional context: {keyword_text}"
307
+
308
+ return full_description
309
+
310
+ except Exception as e:
311
+ print(f"Error creating description for {breed}: {str(e)}")
312
+ return f"{breed.replace('_', ' ')} is a dog breed with unique characteristics."
313
+
314
+ def _build_breed_vectors(self):
315
+ """為所有品種建立向量表示 - 延遲調用當需要時"""
316
+ try:
317
+ print("Building breed vector database...")
318
+
319
+ # 初始化模型如果尚未完成
320
+ if self.sbert_model is None:
321
+ self._initialize_model()
322
+
323
+ # 如果模型不可用則跳過
324
+ if self.sbert_model is None:
325
+ print("SBERT model not available, skipping vector building")
326
+ return
327
+
328
+ for breed in self.breed_list:
329
+ description = self._create_breed_description(breed)
330
+
331
+ # 生成嵌入向量
332
+ embedding = self.sbert_model.encode(description, convert_to_tensor=False)
333
+
334
+ # 獲取品種特徵
335
+ breed_info = get_dog_description(breed)
336
+ characteristics = {
337
+ 'size': breed_info.get('Size', 'Medium') if breed_info else 'Medium',
338
+ 'exercise_needs': breed_info.get('Exercise Needs', 'Moderate') if breed_info else 'Moderate',
339
+ 'grooming_needs': breed_info.get('Grooming Needs', 'Moderate') if breed_info else 'Moderate',
340
+ 'good_with_children': breed_info.get('Good with Children', 'Yes') if breed_info else 'Yes',
341
+ 'temperament': breed_info.get('Temperament', '') if breed_info else ''
342
+ }
343
+
344
+ self.breed_vectors[breed] = BreedDescriptionVector(
345
+ breed_name=breed,
346
+ description_text=description,
347
+ embedding=embedding,
348
+ characteristics=characteristics
349
+ )
350
+
351
+ print(f"Successfully built {len(self.breed_vectors)} breed vectors")
352
+
353
+ except Exception as e:
354
+ print(f"Error building breed vectors: {str(e)}")
355
+ print(traceback.format_exc())
356
+ raise
357
+
358
+ def get_breed_vectors(self) -> Dict[str, BreedDescriptionVector]:
359
+ """獲取所有品種向量"""
360
+ # 確保向量已建構
361
+ if not self.breed_vectors:
362
+ self._build_breed_vectors()
363
+ return self.breed_vectors
364
+
365
+ def get_sbert_model(self) -> Optional[SentenceTransformer]:
366
+ """獲取 SBERT 模型"""
367
+ return self.sbert_model
368
+
369
+ def get_breed_list(self) -> List[str]:
370
+ """獲取品種清單"""
371
+ return self.breed_list
372
+
373
+ def is_model_available(self) -> bool:
374
+ """檢查 SBERT 模型是否可用"""
375
+ return self.sbert_model is not None
376
+
377
+ def encode_text(self, text: str) -> np.ndarray:
378
+ """使用 SBERT 模型編碼文本"""
379
+ # 初始化模型如果尚未完成
380
+ if self.sbert_model is None:
381
+ self._initialize_model()
382
+
383
+ if self.sbert_model is None:
384
+ raise RuntimeError("SBERT model not available")
385
+ return self.sbert_model.encode(text, convert_to_tensor=False)
user_query_analyzer.py ADDED
@@ -0,0 +1,511 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import hashlib
3
+ import numpy as np
4
+ import sqlite3
5
+ import re
6
+ import traceback
7
+ from typing import List, Dict, Tuple, Optional, Any
8
+ from dataclasses import dataclass
9
+ from sentence_transformers import SentenceTransformer
10
+ import torch
11
+ from sklearn.metrics.pairwise import cosine_similarity
12
+ from dog_database import get_dog_description
13
+ from breed_health_info import breed_health_info
14
+ from breed_noise_info import breed_noise_info
15
+ from scoring_calculation_system import UserPreferences, calculate_compatibility_score, UnifiedScoringSystem, calculate_unified_breed_scores
16
+ from query_understanding import QueryUnderstandingEngine, analyze_user_query
17
+ from constraint_manager import ConstraintManager, apply_breed_constraints
18
+ from multi_head_scorer import MultiHeadScorer, score_breed_candidates, BreedScore
19
+ from score_calibrator import ScoreCalibrator, calibrate_breed_scores
20
+ from config_manager import get_config_manager, get_standardized_breed_data
21
+
22
+ class UserQueryAnalyzer:
23
+ """
24
+ 用戶查詢分析器
25
+ 專門處理用戶輸入分析、生活方式關鍵字提取和偏好解析
26
+ """
27
+
28
+ def __init__(self, breed_list: List[str]):
29
+ """初始化用戶查詢分析器"""
30
+ self.breed_list = breed_list
31
+ self.comparative_keywords = {
32
+ 'most': 1.0, 'love': 1.0, 'prefer': 0.9, 'like': 0.8,
33
+ 'then': 0.7, 'second': 0.7, 'followed': 0.6,
34
+ 'third': 0.5, 'least': 0.3, 'dislike': 0.2
35
+ }
36
+ self.stop_words = {
37
+ 'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by',
38
+ 'from', 'up', 'about', 'into', 'through', 'during', 'before', 'after', 'above', 'below',
39
+ 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'do', 'does', 'did',
40
+ 'will', 'would', 'could', 'should', 'may', 'might', 'must', 'can', 'i', 'me', 'my', 'myself',
41
+ 'we', 'our', 'ours', 'ourselves', 'you', 'your', 'yours', 'yourself', 'yourselves', 'he',
42
+ 'him', 'his', 'himself', 'she', 'her', 'hers', 'herself', 'it', 'its', 'itself', 'they',
43
+ 'them', 'their', 'theirs', 'themselves'
44
+ }
45
+
46
+ def parse_comparative_preferences(self, user_input: str) -> Dict[str, float]:
47
+ """解析比較性偏好表達"""
48
+ breed_scores = {}
49
+
50
+ # 標準化輸入
51
+ text = user_input.lower()
52
+
53
+ # 找到品種名稱和偏好關鍵字
54
+ for breed in self.breed_list:
55
+ breed_display = breed.replace('_', ' ').lower()
56
+ breed_words = breed_display.split()
57
+
58
+ # 檢查是否提到此品種
59
+ breed_mentioned = False
60
+ for word in breed_words:
61
+ if word in text:
62
+ breed_mentioned = True
63
+ break
64
+
65
+ if breed_mentioned:
66
+ # 在附近找到偏好關鍵字
67
+ breed_score = 0.5 # 預設分數
68
+
69
+ # 在品種名稱 50 字符內尋找關鍵字
70
+ breed_pos = text.find(breed_words[0])
71
+ if breed_pos != -1:
72
+ # 檢查背景中的關鍵字
73
+ context_start = max(0, breed_pos - 50)
74
+ context_end = min(len(text), breed_pos + 50)
75
+ context = text[context_start:context_end]
76
+
77
+ for keyword, score in self.comparative_keywords.items():
78
+ if keyword in context:
79
+ breed_score = max(breed_score, score)
80
+
81
+ breed_scores[breed] = breed_score
82
+
83
+ return breed_scores
84
+
85
+ def extract_lifestyle_keywords(self, user_input: str) -> Dict[str, List[str]]:
86
+ """增強的生活方式關鍵字提取,具有更好的模式匹配"""
87
+ keywords = {
88
+ 'living_space': [],
89
+ 'activity_level': [],
90
+ 'family_situation': [],
91
+ 'noise_preference': [],
92
+ 'size_preference': [],
93
+ 'care_level': [],
94
+ 'special_needs': [],
95
+ 'intelligence_preference': [],
96
+ 'grooming_preference': [],
97
+ 'lifespan_preference': [],
98
+ 'temperament_preference': [],
99
+ 'experience_level': []
100
+ }
101
+
102
+ text = user_input.lower()
103
+
104
+ # 增強居住空間檢測
105
+ apartment_terms = ['apartment', 'flat', 'condo', 'small space', 'city living', 'urban', 'no yard', 'indoor']
106
+ house_terms = ['house', 'yard', 'garden', 'backyard', 'large space', 'suburban', 'rural', 'farm']
107
+
108
+ if any(term in text for term in apartment_terms):
109
+ keywords['living_space'].append('apartment')
110
+ if any(term in text for term in house_terms):
111
+ keywords['living_space'].append('house')
112
+
113
+ # 增強活動水平檢測
114
+ high_activity = ['active', 'energetic', 'exercise', 'hiking', 'running', 'outdoor', 'sports', 'jogging',
115
+ 'athletic', 'adventure', 'vigorous', 'high energy', 'workout']
116
+ low_activity = ['calm', 'lazy', 'indoor', 'low energy', 'couch', 'sedentary', 'relaxed',
117
+ 'peaceful', 'quiet lifestyle', 'minimal exercise']
118
+ moderate_activity = ['moderate', 'walk', 'daily walks', 'light exercise']
119
+
120
+ if any(term in text for term in high_activity):
121
+ keywords['activity_level'].append('high')
122
+ if any(term in text for term in low_activity):
123
+ keywords['activity_level'].append('low')
124
+ if any(term in text for term in moderate_activity):
125
+ keywords['activity_level'].append('moderate')
126
+
127
+ # 增強家庭情況檢測
128
+ children_terms = ['children', 'kids', 'family', 'child', 'toddler', 'baby', 'teenage', 'school age']
129
+ elderly_terms = ['elderly', 'senior', 'old', 'retirement', 'aged', 'mature']
130
+ single_terms = ['single', 'alone', 'individual', 'solo', 'myself']
131
+
132
+ if any(term in text for term in children_terms):
133
+ keywords['family_situation'].append('children')
134
+ if any(term in text for term in elderly_terms):
135
+ keywords['family_situation'].append('elderly')
136
+ if any(term in text for term in single_terms):
137
+ keywords['family_situation'].append('single')
138
+
139
+ # 增強噪音偏好檢測
140
+ quiet_terms = ['quiet', 'silent', 'noise-sensitive', 'peaceful', 'no barking', 'minimal noise',
141
+ 'soft-spoken', 'calm', 'tranquil']
142
+ noise_ok_terms = ['loud', 'barking ok', 'noise tolerant', 'vocal', 'doesn\'t matter']
143
+
144
+ if any(term in text for term in quiet_terms):
145
+ keywords['noise_preference'].append('low')
146
+ if any(term in text for term in noise_ok_terms):
147
+ keywords['noise_preference'].append('high')
148
+
149
+ # 增強體型偏好檢測
150
+ small_terms = ['small', 'tiny', 'little', 'compact', 'miniature', 'toy', 'lap dog']
151
+ large_terms = ['large', 'big', 'giant', 'huge', 'massive', 'great']
152
+ medium_terms = ['medium', 'moderate size', 'average', 'mid-sized']
153
+
154
+ if any(term in text for term in small_terms):
155
+ keywords['size_preference'].append('small')
156
+ if any(term in text for term in large_terms):
157
+ keywords['size_preference'].append('large')
158
+ if any(term in text for term in medium_terms):
159
+ keywords['size_preference'].append('medium')
160
+
161
+ # 增強照護水平檢測
162
+ low_care = ['low maintenance', 'easy care', 'simple', 'minimal grooming', 'wash and go']
163
+ high_care = ['high maintenance', 'grooming', 'care intensive', 'professional grooming', 'daily brushing']
164
+
165
+ if any(term in text for term in low_care):
166
+ keywords['care_level'].append('low')
167
+ if any(term in text for term in high_care):
168
+ keywords['care_level'].append('high')
169
+
170
+ # 智力偏好檢測(新增)
171
+ smart_terms = ['smart', 'intelligent', 'clever', 'bright', 'quick learner', 'easy to train', 'trainable', 'genius', 'brilliant']
172
+ independent_terms = ['independent', 'stubborn', 'strong-willed', 'less trainable', 'thinks for themselves']
173
+
174
+ if any(term in text for term in smart_terms):
175
+ keywords['intelligence_preference'].append('high')
176
+ if any(term in text for term in independent_terms):
177
+ keywords['intelligence_preference'].append('independent')
178
+
179
+ # 美容偏好檢測(新增)
180
+ low_grooming_terms = ['low grooming', 'minimal grooming', 'easy care', 'wash and wear', 'no grooming', 'simple coat']
181
+ high_grooming_terms = ['high grooming', 'professional grooming', 'lots of care', 'high maintenance coat', 'daily brushing', 'regular grooming']
182
+
183
+ if any(term in text for term in low_grooming_terms):
184
+ keywords['grooming_preference'].append('low')
185
+ if any(term in text for term in high_grooming_terms):
186
+ keywords['grooming_preference'].append('high')
187
+
188
+ # 壽命偏好檢測(新增)
189
+ long_lived_terms = ['long lived', 'long lifespan', 'live long', 'many years', '15+ years', 'longevity']
190
+ healthy_terms = ['healthy breed', 'few health issues', 'robust', 'hardy', 'strong constitution']
191
+
192
+ if any(term in text for term in long_lived_terms):
193
+ keywords['lifespan_preference'].append('long')
194
+ if any(term in text for term in healthy_terms):
195
+ keywords['lifespan_preference'].append('healthy')
196
+
197
+ # 氣質偏好檢測(新增)
198
+ gentle_terms = ['gentle', 'calm', 'peaceful', 'laid back', 'chill', 'mellow', 'docile']
199
+ playful_terms = ['playful', 'energetic', 'fun', 'active personality', 'lively', 'spirited', 'bouncy']
200
+ protective_terms = ['protective', 'guard', 'watchdog', 'alert', 'vigilant', 'defensive']
201
+ friendly_terms = ['friendly', 'social', 'outgoing', 'loves people', 'sociable', 'gregarious']
202
+
203
+ if any(term in text for term in gentle_terms):
204
+ keywords['temperament_preference'].append('gentle')
205
+ if any(term in text for term in playful_terms):
206
+ keywords['temperament_preference'].append('playful')
207
+ if any(term in text for term in protective_terms):
208
+ keywords['temperament_preference'].append('protective')
209
+ if any(term in text for term in friendly_terms):
210
+ keywords['temperament_preference'].append('friendly')
211
+
212
+ # 經驗水平檢測(新增)
213
+ beginner_terms = ['first time', 'beginner', 'new to dogs', 'never had', 'novice', 'inexperienced']
214
+ advanced_terms = ['experienced', 'advanced', 'dog expert', 'many dogs before', 'professional', 'seasoned']
215
+
216
+ if any(term in text for term in beginner_terms):
217
+ keywords['experience_level'].append('beginner')
218
+ if any(term in text for term in advanced_terms):
219
+ keywords['experience_level'].append('advanced')
220
+
221
+ # 增強特殊需求檢測
222
+ guard_terms = ['guard', 'protection', 'security', 'watchdog', 'protective', 'defender']
223
+ companion_terms = ['therapy', 'emotional support', 'companion', 'comfort', 'lap dog', 'cuddly']
224
+ hypoallergenic_terms = ['hypoallergenic', 'allergies', 'non-shedding', 'allergy-friendly', 'no shed']
225
+ multi_pet_terms = ['good with cats', 'cat friendly', 'multi-pet', 'other animals']
226
+
227
+ if any(term in text for term in guard_terms):
228
+ keywords['special_needs'].append('guard')
229
+ if any(term in text for term in companion_terms):
230
+ keywords['special_needs'].append('companion')
231
+ if any(term in text for term in hypoallergenic_terms):
232
+ keywords['special_needs'].append('hypoallergenic')
233
+ if any(term in text for term in multi_pet_terms):
234
+ keywords['special_needs'].append('multi_pet')
235
+
236
+ return keywords
237
+
238
+ def preprocess_text(self, text: str) -> str:
239
+ """預處理文本"""
240
+ # 轉換為小寫
241
+ text = text.lower()
242
+
243
+ # 移除特殊字符,保留字母、數字和基本標點
244
+ text = re.sub(r'[^\w\s\-\']', ' ', text)
245
+
246
+ # 標準化空格
247
+ text = ' '.join(text.split())
248
+
249
+ return text
250
+
251
+ def generate_search_keywords(self, text: str) -> List[str]:
252
+ """
253
+ 為語義搜索生成關鍵字
254
+
255
+ Args:
256
+ text: 輸入文本
257
+
258
+ Returns:
259
+ 關鍵字列表
260
+ """
261
+ text = self.preprocess_text(text)
262
+ keywords = []
263
+
264
+ try:
265
+ # 分詞並過濾停用詞
266
+ words = text.split()
267
+ for word in words:
268
+ if len(word) > 2 and word not in self.stop_words:
269
+ keywords.append(word)
270
+
271
+ # 提取重要短語
272
+ phrases = self._extract_phrases(text)
273
+ keywords.extend(phrases)
274
+
275
+ # 移除重複項
276
+ keywords = list(set(keywords))
277
+
278
+ return keywords
279
+
280
+ except Exception as e:
281
+ print(f"Error generating search keywords: {str(e)}")
282
+ return []
283
+
284
+ def _extract_phrases(self, text: str) -> List[str]:
285
+ """
286
+ 提取重要短語
287
+
288
+ Args:
289
+ text: 輸入文本
290
+
291
+ Returns:
292
+ 短語列表
293
+ """
294
+ phrases = []
295
+
296
+ # 定義重要短語模式
297
+ phrase_patterns = [
298
+ r'good with \w+',
299
+ r'apartment \w+',
300
+ r'family \w+',
301
+ r'exercise \w+',
302
+ r'grooming \w+',
303
+ r'noise \w+',
304
+ r'training \w+',
305
+ r'health \w+',
306
+ r'\w+ friendly',
307
+ r'\w+ tolerant',
308
+ r'\w+ maintenance',
309
+ r'\w+ energy',
310
+ r'\w+ barking',
311
+ r'\w+ shedding'
312
+ ]
313
+
314
+ for pattern in phrase_patterns:
315
+ matches = re.findall(pattern, text)
316
+ phrases.extend(matches)
317
+
318
+ return phrases
319
+
320
+ def analyze_sentiment(self, text: str) -> Dict[str, float]:
321
+ """
322
+ 分析文本情感
323
+
324
+ Args:
325
+ text: 輸入文本
326
+
327
+ Returns:
328
+ 情感分析結果
329
+ """
330
+ # 簡化的情感分析實現
331
+ positive_words = [
332
+ 'love', 'like', 'prefer', 'enjoy', 'want', 'need', 'looking for',
333
+ 'good', 'great', 'excellent', 'perfect', 'wonderful', 'amazing'
334
+ ]
335
+
336
+ negative_words = [
337
+ 'hate', 'dislike', 'avoid', 'don\'t want', 'no', 'not',
338
+ 'bad', 'terrible', 'awful', 'horrible', 'worst', 'never'
339
+ ]
340
+
341
+ words = text.lower().split()
342
+ positive_count = sum(1 for word in words if word in positive_words)
343
+ negative_count = sum(1 for word in words if word in negative_words)
344
+ total_words = len(words)
345
+
346
+ if total_words == 0:
347
+ return {'positive': 0.5, 'negative': 0.5, 'neutral': 0.0}
348
+
349
+ positive_score = positive_count / total_words
350
+ negative_score = negative_count / total_words
351
+ neutral_score = max(0, 1 - positive_score - negative_score)
352
+
353
+ return {
354
+ 'positive': positive_score,
355
+ 'negative': negative_score,
356
+ 'neutral': neutral_score
357
+ }
358
+
359
+ def parse_user_requirements(self, user_input: str) -> Dict[str, Any]:
360
+ """更準確地解析用戶需求"""
361
+ requirements = {
362
+ 'living_space': None,
363
+ 'exercise_level': None,
364
+ 'preferred_size': None,
365
+ 'noise_tolerance': None
366
+ }
367
+
368
+ input_lower = user_input.lower()
369
+
370
+ # 居住空間檢測
371
+ if 'apartment' in input_lower or 'small' in input_lower:
372
+ requirements['living_space'] = 'apartment'
373
+ elif 'large house' in input_lower or 'big' in input_lower:
374
+ requirements['living_space'] = 'large_house'
375
+ elif 'medium' in input_lower:
376
+ requirements['living_space'] = 'medium_house'
377
+
378
+ # 運動水平檢測
379
+ if "don't exercise" in input_lower or 'low exercise' in input_lower:
380
+ requirements['exercise_level'] = 'low'
381
+ elif any(term in input_lower for term in ['hiking', 'running', 'active']):
382
+ requirements['exercise_level'] = 'high'
383
+ elif '30 minutes' in input_lower or 'moderate' in input_lower:
384
+ requirements['exercise_level'] = 'moderate'
385
+
386
+ # 體型偏好檢測
387
+ if any(term in input_lower for term in ['small dog', 'tiny', 'toy']):
388
+ requirements['preferred_size'] = 'small'
389
+ elif any(term in input_lower for term in ['large dog', 'big dog']):
390
+ requirements['preferred_size'] = 'large'
391
+ elif 'medium' in input_lower:
392
+ requirements['preferred_size'] = 'medium'
393
+
394
+ return requirements
395
+
396
+ def analyze_user_description_enhanced(self, user_description: str) -> Dict[str, Any]:
397
+ """增強用戶描述分析"""
398
+ text = user_description.lower()
399
+ analysis = {
400
+ 'mentioned_breeds': [],
401
+ 'lifestyle_keywords': {},
402
+ 'preference_strength': {},
403
+ 'constraint_requirements': [],
404
+ 'user_context': {}
405
+ }
406
+
407
+ # 提取提及的品種
408
+ for breed in self.breed_list:
409
+ breed_display = breed.replace('_', ' ').lower()
410
+ if breed_display in text or any(word in text for word in breed_display.split()):
411
+ analysis['mentioned_breeds'].append(breed)
412
+ # 簡單偏好強度分析
413
+ if any(word in text for word in ['love', 'prefer', 'like', '喜歡', '最愛']):
414
+ analysis['preference_strength'][breed] = 0.8
415
+ else:
416
+ analysis['preference_strength'][breed] = 0.5
417
+
418
+ # 提取約束要求
419
+ if any(word in text for word in ['quiet', 'silent', 'no barking', '安靜']):
420
+ analysis['constraint_requirements'].append('low_noise')
421
+ if any(word in text for word in ['apartment', 'small space', '公寓']):
422
+ analysis['constraint_requirements'].append('apartment_suitable')
423
+ if any(word in text for word in ['children', 'kids', 'family', '小孩']):
424
+ analysis['constraint_requirements'].append('child_friendly')
425
+
426
+ # 提取用戶背景
427
+ analysis['user_context'] = {
428
+ 'has_children': any(word in text for word in ['children', 'kids', '小孩']),
429
+ 'living_space': 'apartment' if any(word in text for word in ['apartment', '公寓']) else 'house',
430
+ 'activity_level': 'high' if any(word in text for word in ['active', 'energetic', '活躍']) else 'moderate',
431
+ 'noise_sensitive': any(word in text for word in ['quiet', 'silent', '安靜']),
432
+ 'experience_level': 'beginner' if any(word in text for word in ['first time', 'beginner', '新手']) else 'intermediate'
433
+ }
434
+
435
+ return analysis
436
+
437
+ def create_user_preferences_from_analysis_enhanced(self, analysis: Dict[str, Any]) -> 'UserPreferences':
438
+ """從分析結果創建用戶偏好物件"""
439
+ context = analysis['user_context']
440
+
441
+ # 推斷居住空間類型
442
+ living_space = 'apartment' if context.get('living_space') == 'apartment' else 'house_small'
443
+
444
+ # 推斷院子權限
445
+ yard_access = 'no_yard' if living_space == 'apartment' else 'shared_yard'
446
+
447
+ # 推斷運動時間
448
+ activity_level = context.get('activity_level', 'moderate')
449
+ exercise_time_map = {'high': 120, 'moderate': 60, 'low': 30}
450
+ exercise_time = exercise_time_map.get(activity_level, 60)
451
+
452
+ # 推斷運動類型
453
+ exercise_type_map = {'high': 'active_training', 'moderate': 'moderate_activity', 'low': 'light_walks'}
454
+ exercise_type = exercise_type_map.get(activity_level, 'moderate_activity')
455
+
456
+ # 推斷噪音容忍度
457
+ noise_tolerance = 'low' if context.get('noise_sensitive', False) else 'medium'
458
+
459
+ return UserPreferences(
460
+ living_space=living_space,
461
+ yard_access=yard_access,
462
+ exercise_time=exercise_time,
463
+ exercise_type=exercise_type,
464
+ grooming_commitment='medium',
465
+ experience_level=context.get('experience_level', 'intermediate'),
466
+ time_availability='moderate',
467
+ has_children=context.get('has_children', False),
468
+ children_age='school_age' if context.get('has_children', False) else None,
469
+ noise_tolerance=noise_tolerance,
470
+ space_for_play=(living_space != 'apartment'),
471
+ other_pets=False,
472
+ climate='moderate',
473
+ health_sensitivity='medium',
474
+ barking_acceptance=noise_tolerance,
475
+ size_preference='no_preference'
476
+ )
477
+
478
+ def get_candidate_breeds_enhanced(self, analysis: Dict[str, Any]) -> List[str]:
479
+ """獲取候選品種列表"""
480
+ candidate_breeds = set()
481
+
482
+ # 如果提及特定品種,優先包含
483
+ if analysis['mentioned_breeds']:
484
+ candidate_breeds.update(analysis['mentioned_breeds'])
485
+
486
+ # 根據約束要求過濾品種
487
+ if 'apartment_suitable' in analysis['constraint_requirements']:
488
+ apartment_suitable = [
489
+ 'French_Bulldog', 'Cavalier_King_Charles_Spaniel', 'Boston_Terrier',
490
+ 'Pug', 'Bichon_Frise', 'Cocker_Spaniel', 'Yorkshire_Terrier', 'Shih_Tzu'
491
+ ]
492
+ candidate_breeds.update(breed for breed in apartment_suitable if breed in self.breed_list)
493
+
494
+ if 'child_friendly' in analysis['constraint_requirements']:
495
+ child_friendly = [
496
+ 'Labrador_Retriever', 'Golden_Retriever', 'Beagle', 'Cavalier_King_Charles_Spaniel',
497
+ 'Bichon_Frise', 'Poodle', 'Cocker_Spaniel'
498
+ ]
499
+ candidate_breeds.update(breed for breed in child_friendly if breed in self.breed_list)
500
+
501
+ # 如果候選品種不足,添加更多通用品種
502
+ if len(candidate_breeds) < 20:
503
+ general_breeds = [
504
+ 'Labrador_Retriever', 'German_Shepherd', 'Golden_Retriever', 'French_Bulldog',
505
+ 'Bulldog', 'Poodle', 'Beagle', 'Rottweiler', 'Yorkshire_Terrier', 'Boston_Terrier',
506
+ 'Border_Collie', 'Siberian_Husky', 'Cavalier_King_Charles_Spaniel', 'Boxer',
507
+ 'Bichon_Frise', 'Cocker_Spaniel', 'Shih_Tzu', 'Pug', 'Chihuahua'
508
+ ]
509
+ candidate_breeds.update(breed for breed in general_breeds if breed in self.breed_list)
510
+
511
+ return list(candidate_breeds)[:30] # 限制候選數量以提高效率