Spaces:
Running
on
Zero
Running
on
Zero
Update scoring_calculation_system.py
Browse files- scoring_calculation_system.py +405 -192
scoring_calculation_system.py
CHANGED
@@ -4,6 +4,7 @@ from breed_noise_info import breed_noise_info
|
|
4 |
|
5 |
@dataclass
|
6 |
class UserPreferences:
|
|
|
7 |
"""使用者偏好設定的資料結構"""
|
8 |
living_space: str # "apartment", "house_small", "house_large"
|
9 |
exercise_time: int # minutes per day
|
@@ -14,277 +15,489 @@ class UserPreferences:
|
|
14 |
space_for_play: bool
|
15 |
other_pets: bool
|
16 |
climate: str # "cold", "moderate", "hot"
|
17 |
-
health_sensitivity: str = "medium"
|
18 |
barking_acceptance: str = None
|
|
|
|
|
|
|
|
|
19 |
|
20 |
def __post_init__(self):
|
21 |
"""在初始化後運行,用於設置派生值"""
|
22 |
if self.barking_acceptance is None:
|
23 |
self.barking_acceptance = self.noise_tolerance
|
24 |
|
25 |
-
@staticmethod
|
26 |
-
def calculate_breed_bonus(breed_info: dict, user_prefs: 'UserPreferences') -> float:
|
27 |
-
"""計算品種額外加分"""
|
28 |
-
bonus = 0.0
|
29 |
-
|
30 |
-
# 壽命加分
|
31 |
-
try:
|
32 |
-
lifespan = breed_info.get('Lifespan', '10-12 years')
|
33 |
-
years = [int(x) for x in lifespan.split('-')[0].split()[0:1]]
|
34 |
-
longevity_bonus = min(0.05, (max(years) - 10) * 0.01)
|
35 |
-
bonus += longevity_bonus
|
36 |
-
except:
|
37 |
-
pass
|
38 |
-
|
39 |
-
# 性格特徵加分
|
40 |
-
temperament = breed_info.get('Temperament', '').lower()
|
41 |
-
if user_prefs.has_children:
|
42 |
-
if 'gentle' in temperament or 'patient' in temperament:
|
43 |
-
bonus += 0.03
|
44 |
-
|
45 |
-
# 適應性加分
|
46 |
-
if breed_info.get('Size') == "Small" and user_prefs.living_space == "apartment":
|
47 |
-
bonus += 0.02
|
48 |
-
|
49 |
-
return bonus
|
50 |
-
|
51 |
-
@staticmethod
|
52 |
-
def calculate_additional_factors(breed_info: dict, user_prefs: 'UserPreferences') -> dict:
|
53 |
-
"""計算額外的排序因素"""
|
54 |
-
factors = {
|
55 |
-
'versatility': 0.0,
|
56 |
-
'health_score': 0.0,
|
57 |
-
'adaptability': 0.0
|
58 |
-
}
|
59 |
-
|
60 |
-
# 計算多功能性分數
|
61 |
-
temperament = breed_info.get('Temperament', '').lower()
|
62 |
-
versatile_traits = ['intelligent', 'adaptable', 'versatile', 'trainable']
|
63 |
-
factors['versatility'] = sum(trait in temperament for trait in versatile_traits) / len(versatile_traits)
|
64 |
|
65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
66 |
lifespan = breed_info.get('Lifespan', '10-12 years')
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
83 |
|
84 |
|
85 |
def calculate_compatibility_score(breed_info: dict, user_prefs: UserPreferences) -> dict:
|
86 |
-
"""
|
87 |
-
scores = {}
|
88 |
try:
|
89 |
-
|
90 |
-
|
|
|
91 |
base_scores = {
|
92 |
"Small": {"apartment": 0.95, "house_small": 1.0, "house_large": 0.90},
|
93 |
-
"Medium": {"apartment": 0.
|
94 |
-
"Large": {"apartment": 0.
|
95 |
"Giant": {"apartment": 0.15, "house_small": 0.55, "house_large": 1.0}
|
96 |
}
|
97 |
-
|
|
|
98 |
base_score = base_scores.get(size, base_scores["Medium"])[living_space]
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
if
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
|
|
|
|
|
|
|
|
|
|
109 |
adjustments += 0.05
|
|
|
|
|
110 |
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
def calculate_exercise_score(breed_exercise_needs, user_exercise_time):
|
115 |
exercise_needs = {
|
116 |
-
'VERY HIGH': 120,
|
117 |
-
'HIGH': 90,
|
118 |
-
'MODERATE': 60,
|
119 |
-
'LOW': 30,
|
120 |
-
'VARIES': 60
|
121 |
}
|
122 |
-
|
123 |
-
breed_need = exercise_needs.get(
|
124 |
-
|
125 |
-
|
126 |
-
if
|
|
|
|
|
127 |
return 1.0
|
128 |
-
elif
|
129 |
-
return 0.
|
130 |
-
elif difference <= 0.4:
|
131 |
-
return 0.85
|
132 |
-
elif difference <= 0.6:
|
133 |
-
return 0.70
|
134 |
-
elif difference <= 0.8:
|
135 |
-
return 0.50
|
136 |
else:
|
137 |
-
return 0.
|
138 |
|
139 |
-
|
140 |
-
|
|
|
141 |
base_scores = {
|
142 |
"High": {"low": 0.3, "medium": 0.7, "high": 1.0},
|
143 |
"Moderate": {"low": 0.5, "medium": 0.9, "high": 1.0},
|
144 |
-
"Low": {"low": 1.0, "medium": 0.95, "high": 0.
|
145 |
}
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
154 |
return base_score
|
155 |
|
156 |
-
|
157 |
-
|
|
|
158 |
base_scores = {
|
159 |
-
"High": {"beginner": 0.
|
160 |
-
"Moderate": {"beginner": 0.
|
161 |
-
"Low": {"beginner": 0.
|
162 |
}
|
163 |
-
|
164 |
score = base_scores.get(care_level, base_scores["Moderate"])[user_experience]
|
165 |
-
|
|
|
166 |
temperament_lower = temperament.lower()
|
|
|
167 |
if user_experience == "beginner":
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
174 |
|
175 |
def calculate_health_score(breed_name: str) -> float:
|
|
|
176 |
if breed_name not in breed_health_info:
|
177 |
return 0.5
|
178 |
|
179 |
health_notes = breed_health_info[breed_name]['health_notes'].lower()
|
180 |
-
|
181 |
-
#
|
182 |
severe_conditions = [
|
183 |
-
'
|
184 |
-
'
|
|
|
|
|
|
|
|
|
|
|
185 |
]
|
186 |
-
|
187 |
-
#
|
188 |
moderate_conditions = [
|
189 |
-
'allergies',
|
190 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
191 |
]
|
192 |
|
|
|
|
|
|
|
|
|
193 |
severe_count = sum(1 for condition in severe_conditions if condition in health_notes)
|
194 |
moderate_count = sum(1 for condition in moderate_conditions if condition in health_notes)
|
195 |
-
|
196 |
-
|
197 |
-
health_score -= (severe_count * 0.
|
198 |
-
health_score -= (moderate_count * 0.
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
|
|
|
|
|
|
203 |
health_score *= 0.9
|
|
|
|
|
|
|
|
|
204 |
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
|
209 |
-
return max(0.
|
210 |
|
211 |
def calculate_noise_score(breed_name: str, user_noise_tolerance: str) -> float:
|
|
|
212 |
if breed_name not in breed_noise_info:
|
213 |
return 0.5
|
214 |
|
215 |
noise_info = breed_noise_info[breed_name]
|
216 |
noise_level = noise_info['noise_level'].lower()
|
217 |
-
|
218 |
|
219 |
# 基礎噪音分數矩陣
|
220 |
-
|
221 |
-
'low': {'low': 1.0, 'medium': 0.
|
222 |
-
'medium': {'low': 0.7, 'medium': 1.0, 'high': 0.
|
223 |
-
'high': {'low': 0.4, 'medium': 0.7, 'high': 1.0}
|
|
|
224 |
}
|
225 |
|
226 |
-
#
|
227 |
-
base_score =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
228 |
|
229 |
-
#
|
230 |
special_adjustments = 0
|
231 |
-
if
|
|
|
|
|
232 |
special_adjustments -= 0.1
|
233 |
-
if user_prefs.living_space == 'apartment':
|
234 |
-
if noise_level == 'high':
|
235 |
-
special_adjustments -= 0.15
|
236 |
-
elif noise_level == 'medium':
|
237 |
-
special_adjustments -= 0.05
|
238 |
|
239 |
-
final_score = base_score + special_adjustments
|
240 |
-
|
|
|
241 |
|
242 |
# 計算所有基礎分數
|
243 |
scores = {
|
244 |
-
'space': calculate_space_score(
|
245 |
-
|
246 |
-
|
247 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
248 |
'health': calculate_health_score(breed_info.get('Breed', '')),
|
249 |
'noise': calculate_noise_score(breed_info.get('Breed', ''), user_prefs.noise_tolerance)
|
250 |
}
|
251 |
|
252 |
-
#
|
253 |
weights = {
|
254 |
-
'space': 0.
|
255 |
-
'exercise': 0.
|
256 |
-
'grooming': 0.
|
257 |
-
'experience': 0.
|
258 |
-
'health': 0.
|
259 |
-
'noise': 0.
|
260 |
}
|
261 |
|
262 |
-
#
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
adjustments += 0.02
|
273 |
-
|
274 |
-
# 2. 氣候相容性
|
275 |
-
if user_prefs.climate in breed_info.get('Suitable Climate', '').split(','):
|
276 |
-
adjustments += 0.02
|
277 |
-
|
278 |
-
# 3. 其他寵物相容性
|
279 |
-
if user_prefs.other_pets and breed_info.get('Good with Other Pets') == 'Yes':
|
280 |
-
adjustments += 0.02
|
281 |
-
|
282 |
-
final_score = min(1.0, max(0, base_score + adjustments))
|
283 |
-
scores['overall'] = round(final_score, 4)
|
284 |
|
285 |
# 四捨五入所有分數
|
286 |
-
for
|
287 |
-
|
288 |
|
289 |
return scores
|
290 |
|
|
|
4 |
|
5 |
@dataclass
|
6 |
class UserPreferences:
|
7 |
+
|
8 |
"""使用者偏好設定的資料結構"""
|
9 |
living_space: str # "apartment", "house_small", "house_large"
|
10 |
exercise_time: int # minutes per day
|
|
|
15 |
space_for_play: bool
|
16 |
other_pets: bool
|
17 |
climate: str # "cold", "moderate", "hot"
|
18 |
+
health_sensitivity: str = "medium"
|
19 |
barking_acceptance: str = None
|
20 |
+
time_away: str = "moderate" # 離家時間,影響某些品種的適合度
|
21 |
+
physical_activity: str = "moderate" # 活動類型,影響運動需求匹配
|
22 |
+
training_commitment: str = "moderate" # 訓練投入,影響經驗需求
|
23 |
+
children_age: str = None
|
24 |
|
25 |
def __post_init__(self):
|
26 |
"""在初始化後運行,用於設置派生值"""
|
27 |
if self.barking_acceptance is None:
|
28 |
self.barking_acceptance = self.noise_tolerance
|
29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
|
31 |
+
@staticmethod
|
32 |
+
def calculate_breed_bonus(breed_info: dict, user_prefs: 'UserPreferences') -> float:
|
33 |
+
"""計算品種額外加分"""
|
34 |
+
bonus = 0.0
|
35 |
+
temperament = breed_info.get('Temperament', '').lower()
|
36 |
+
|
37 |
+
# 1. 壽命加分(最高0.05)
|
38 |
+
try:
|
39 |
lifespan = breed_info.get('Lifespan', '10-12 years')
|
40 |
+
years = [int(x) for x in lifespan.split('-')[0].split()[0:1]]
|
41 |
+
longevity_bonus = min(0.05, (max(years) - 10) * 0.01)
|
42 |
+
bonus += longevity_bonus
|
43 |
+
except:
|
44 |
+
pass
|
45 |
+
|
46 |
+
# 2. 性格特徵加分(最高0.15)
|
47 |
+
positive_traits = {
|
48 |
+
'friendly': 0.05,
|
49 |
+
'gentle': 0.05,
|
50 |
+
'patient': 0.05,
|
51 |
+
'intelligent': 0.04,
|
52 |
+
'adaptable': 0.04,
|
53 |
+
'affectionate': 0.04,
|
54 |
+
'easy-going': 0.03,
|
55 |
+
'calm': 0.03
|
56 |
+
}
|
57 |
+
|
58 |
+
negative_traits = {
|
59 |
+
'aggressive': -0.08,
|
60 |
+
'stubborn': -0.06,
|
61 |
+
'dominant': -0.06,
|
62 |
+
'aloof': -0.04,
|
63 |
+
'nervous': -0.05,
|
64 |
+
'protective': -0.04
|
65 |
+
}
|
66 |
+
|
67 |
+
personality_score = sum(value for trait, value in positive_traits.items() if trait in temperament)
|
68 |
+
personality_score += sum(value for trait, value in negative_traits.items() if trait in temperament)
|
69 |
+
bonus += max(-0.15, min(0.15, personality_score))
|
70 |
+
|
71 |
+
# 3. 適應性加分(最高0.1)
|
72 |
+
adaptability_bonus = 0.0
|
73 |
+
if breed_info.get('Size') == "Small" and user_prefs.living_space == "apartment":
|
74 |
+
adaptability_bonus += 0.05
|
75 |
+
if 'adaptable' in temperament or 'versatile' in temperament:
|
76 |
+
adaptability_bonus += 0.05
|
77 |
+
bonus += min(0.1, adaptability_bonus)
|
78 |
+
|
79 |
+
# 4. 家庭相容性(最高0.1)
|
80 |
+
if user_prefs.has_children:
|
81 |
+
family_traits = {
|
82 |
+
'good with children': 0.06,
|
83 |
+
'patient': 0.05,
|
84 |
+
'gentle': 0.05,
|
85 |
+
'tolerant': 0.04,
|
86 |
+
'playful': 0.03
|
87 |
+
}
|
88 |
+
unfriendly_traits = {
|
89 |
+
'aggressive': -0.08,
|
90 |
+
'nervous': -0.07,
|
91 |
+
'protective': -0.06,
|
92 |
+
'territorial': -0.05
|
93 |
+
}
|
94 |
+
|
95 |
+
# 年齡評估這樣能更細緻
|
96 |
+
age_adjustments = {
|
97 |
+
'toddler': {'bonus_mult': 0.7, 'penalty_mult': 1.3},
|
98 |
+
'school_age': {'bonus_mult': 1.0, 'penalty_mult': 1.0},
|
99 |
+
'teenager': {'bonus_mult': 1.2, 'penalty_mult': 0.8}
|
100 |
+
}
|
101 |
+
|
102 |
+
adj = age_adjustments.get(user_prefs.children_age,
|
103 |
+
{'bonus_mult': 1.0, 'penalty_mult': 1.0})
|
104 |
+
|
105 |
+
family_bonus = sum(value for trait, value in family_traits.items()
|
106 |
+
if trait in temperament) * adj['bonus_mult']
|
107 |
+
family_penalty = sum(value for trait, value in unfriendly_traits.items()
|
108 |
+
if trait in temperament) * adj['penalty_mult']
|
109 |
+
|
110 |
+
bonus += min(0.15, max(-0.2, family_bonus + family_penalty))
|
111 |
+
|
112 |
+
|
113 |
+
# 5. 專門技能加分(最高0.1)
|
114 |
+
skill_bonus = 0.0
|
115 |
+
special_abilities = {
|
116 |
+
'working': 0.03,
|
117 |
+
'herding': 0.03,
|
118 |
+
'hunting': 0.03,
|
119 |
+
'tracking': 0.03,
|
120 |
+
'agility': 0.02
|
121 |
+
}
|
122 |
+
for ability, value in special_abilities.items():
|
123 |
+
if ability in temperament.lower():
|
124 |
+
skill_bonus += value
|
125 |
+
bonus += min(0.1, skill_bonus)
|
126 |
+
|
127 |
+
return min(0.5, max(-0.25, bonus))
|
128 |
+
|
129 |
+
|
130 |
+
@staticmethod
|
131 |
+
def calculate_additional_factors(breed_info: dict, user_prefs: 'UserPreferences') -> dict:
|
132 |
+
"""計算額外的評估因素"""
|
133 |
+
factors = {
|
134 |
+
'versatility': 0.0, # 多功能性
|
135 |
+
'trainability': 0.0, # 可訓練度
|
136 |
+
'energy_level': 0.0, # 能量水平
|
137 |
+
'grooming_needs': 0.0, # 美容需求
|
138 |
+
'social_needs': 0.0, # 社交需求
|
139 |
+
'weather_adaptability': 0.0 # 氣候適應性
|
140 |
+
}
|
141 |
+
|
142 |
+
temperament = breed_info.get('Temperament', '').lower()
|
143 |
+
size = breed_info.get('Size', 'Medium')
|
144 |
+
|
145 |
+
# 1. 多功能性評估
|
146 |
+
versatile_traits = ['intelligent', 'adaptable', 'trainable', 'athletic']
|
147 |
+
working_roles = ['working', 'herding', 'hunting', 'sporting', 'companion']
|
148 |
+
|
149 |
+
trait_score = sum(0.2 for trait in versatile_traits if trait in temperament)
|
150 |
+
role_score = sum(0.2 for role in working_roles if role in breed_info.get('Description', '').lower())
|
151 |
+
|
152 |
+
factors['versatility'] = min(1.0, trait_score + role_score)
|
153 |
+
|
154 |
+
# 2. 可訓練度評估
|
155 |
+
trainable_traits = {
|
156 |
+
'intelligent': 0.3,
|
157 |
+
'eager to please': 0.3,
|
158 |
+
'trainable': 0.2,
|
159 |
+
'quick learner': 0.2
|
160 |
+
}
|
161 |
+
factors['trainability'] = min(1.0, sum(value for trait, value in trainable_traits.items()
|
162 |
+
if trait in temperament))
|
163 |
+
|
164 |
+
# 3. 能量水平評估
|
165 |
+
exercise_needs = breed_info.get('Exercise Needs', 'MODERATE').upper()
|
166 |
+
energy_levels = {
|
167 |
+
'VERY HIGH': 1.0,
|
168 |
+
'HIGH': 0.8,
|
169 |
+
'MODERATE': 0.6,
|
170 |
+
'LOW': 0.4,
|
171 |
+
'VARIES': 0.6
|
172 |
+
}
|
173 |
+
factors['energy_level'] = energy_levels.get(exercise_needs, 0.6)
|
174 |
+
|
175 |
+
# 4. 美容需求評估
|
176 |
+
grooming_needs = breed_info.get('Grooming Needs', 'MODERATE').upper()
|
177 |
+
grooming_levels = {
|
178 |
+
'HIGH': 1.0,
|
179 |
+
'MODERATE': 0.6,
|
180 |
+
'LOW': 0.3
|
181 |
+
}
|
182 |
+
coat_penalty = 0.2 if any(term in breed_info.get('Description', '').lower()
|
183 |
+
for term in ['long coat', 'double coat']) else 0
|
184 |
+
factors['grooming_needs'] = min(1.0, grooming_levels.get(grooming_needs, 0.6) + coat_penalty)
|
185 |
+
|
186 |
+
# 5. 社交需求評估
|
187 |
+
social_traits = ['friendly', 'social', 'affectionate', 'people-oriented']
|
188 |
+
antisocial_traits = ['independent', 'aloof', 'reserved']
|
189 |
+
|
190 |
+
social_score = sum(0.25 for trait in social_traits if trait in temperament)
|
191 |
+
antisocial_score = sum(-0.2 for trait in antisocial_traits if trait in temperament)
|
192 |
+
factors['social_needs'] = min(1.0, max(0.0, social_score + antisocial_score))
|
193 |
+
|
194 |
+
# 6. 氣候適應性評估
|
195 |
+
climate_terms = {
|
196 |
+
'cold': ['thick coat', 'winter', 'cold climate'],
|
197 |
+
'hot': ['short coat', 'warm climate', 'heat tolerant'],
|
198 |
+
'moderate': ['adaptable', 'all climate']
|
199 |
+
}
|
200 |
+
|
201 |
+
climate_matches = sum(1 for term in climate_terms[user_prefs.climate]
|
202 |
+
if term in breed_info.get('Description', '').lower())
|
203 |
+
factors['weather_adaptability'] = min(1.0, climate_matches * 0.3 + 0.4) # 基礎分0.4
|
204 |
+
|
205 |
+
return factors
|
206 |
|
207 |
|
208 |
def calculate_compatibility_score(breed_info: dict, user_prefs: UserPreferences) -> dict:
|
209 |
+
"""計算品種與使用者條件的相容性分數的優化版本"""
|
|
|
210 |
try:
|
211 |
+
def calculate_space_score(size: str, living_space: str, has_yard: bool, exercise_needs: str) -> float:
|
212 |
+
"""空間分數計算"""
|
213 |
+
# 基礎空間需求矩陣
|
214 |
base_scores = {
|
215 |
"Small": {"apartment": 0.95, "house_small": 1.0, "house_large": 0.90},
|
216 |
+
"Medium": {"apartment": 0.60, "house_small": 0.90, "house_large": 1.0},
|
217 |
+
"Large": {"apartment": 0.30, "house_small": 0.75, "house_large": 1.0},
|
218 |
"Giant": {"apartment": 0.15, "house_small": 0.55, "house_large": 1.0}
|
219 |
}
|
220 |
+
|
221 |
+
# 取得基礎分數
|
222 |
base_score = base_scores.get(size, base_scores["Medium"])[living_space]
|
223 |
+
|
224 |
+
# 運動需求調整
|
225 |
+
exercise_adjustments = {
|
226 |
+
"Very High": -0.15 if living_space == "apartment" else 0,
|
227 |
+
"High": -0.10 if living_space == "apartment" else 0,
|
228 |
+
"Moderate": 0,
|
229 |
+
"Low": 0.05 if living_space == "apartment" else 0
|
230 |
+
}
|
231 |
+
|
232 |
+
adjustments = exercise_adjustments.get(exercise_needs.strip(), 0)
|
233 |
+
|
234 |
+
# 院子獎勵
|
235 |
+
if has_yard and size in ["Large", "Giant"]:
|
236 |
+
adjustments += 0.10
|
237 |
+
elif has_yard:
|
238 |
adjustments += 0.05
|
239 |
+
|
240 |
+
return min(1.0, max(0.1, base_score + adjustments))
|
241 |
|
242 |
+
def calculate_exercise_score(breed_needs: str, user_time: int) -> float:
|
243 |
+
"""運動需求計算"""
|
244 |
+
# 更精確的運動需求定義
|
|
|
245 |
exercise_needs = {
|
246 |
+
'VERY HIGH': {'min': 120, 'ideal': 150, 'max': 180},
|
247 |
+
'HIGH': {'min': 90, 'ideal': 120, 'max': 150},
|
248 |
+
'MODERATE': {'min': 45, 'ideal': 60, 'max': 90},
|
249 |
+
'LOW': {'min': 20, 'ideal': 30, 'max': 45},
|
250 |
+
'VARIES': {'min': 30, 'ideal': 60, 'max': 90}
|
251 |
}
|
252 |
+
|
253 |
+
breed_need = exercise_needs.get(breed_needs.strip().upper(), exercise_needs['MODERATE'])
|
254 |
+
|
255 |
+
# 計算匹配度
|
256 |
+
if user_time >= breed_need['ideal']:
|
257 |
+
if user_time > breed_need['max']:
|
258 |
+
return 0.9 # 稍微降分,因為可能過度運動
|
259 |
return 1.0
|
260 |
+
elif user_time >= breed_need['min']:
|
261 |
+
return 0.8 + (user_time - breed_need['min']) / (breed_need['ideal'] - breed_need['min']) * 0.2
|
|
|
|
|
|
|
|
|
|
|
|
|
262 |
else:
|
263 |
+
return max(0.3, 0.8 * (user_time / breed_need['min']))
|
264 |
|
265 |
+
def calculate_grooming_score(breed_needs: str, user_commitment: str, breed_size: str) -> float:
|
266 |
+
"""美容需求計算"""
|
267 |
+
# 基礎分數矩陣
|
268 |
base_scores = {
|
269 |
"High": {"low": 0.3, "medium": 0.7, "high": 1.0},
|
270 |
"Moderate": {"low": 0.5, "medium": 0.9, "high": 1.0},
|
271 |
+
"Low": {"low": 1.0, "medium": 0.95, "high": 0.8}
|
272 |
}
|
273 |
+
|
274 |
+
# 取得基礎分數
|
275 |
+
base_score = base_scores.get(breed_needs, base_scores["Moderate"])[user_commitment]
|
276 |
+
|
277 |
+
# 體型影響調整
|
278 |
+
size_adjustments = {
|
279 |
+
"Large": {"low": -0.2, "medium": -0.1, "high": 0},
|
280 |
+
"Giant": {"low": -0.3, "medium": -0.15, "high": 0},
|
281 |
+
}
|
282 |
+
|
283 |
+
if breed_size in size_adjustments:
|
284 |
+
adjustment = size_adjustments[breed_size].get(user_commitment, 0)
|
285 |
+
base_score = max(0.2, base_score + adjustment)
|
286 |
+
|
287 |
return base_score
|
288 |
|
289 |
+
def calculate_experience_score(care_level: str, user_experience: str, temperament: str) -> float:
|
290 |
+
"""飼養經驗需求計算"""
|
291 |
+
# 降低初學者的基礎分數
|
292 |
base_scores = {
|
293 |
+
"High": {"beginner": 0.15, "intermediate": 0.70, "advanced": 1.0},
|
294 |
+
"Moderate": {"beginner": 0.40, "intermediate": 0.85, "advanced": 1.0},
|
295 |
+
"Low": {"beginner": 0.75, "intermediate": 0.95, "advanced": 1.0}
|
296 |
}
|
297 |
+
|
298 |
score = base_scores.get(care_level, base_scores["Moderate"])[user_experience]
|
299 |
+
|
300 |
+
# 擴展性格特徵評估
|
301 |
temperament_lower = temperament.lower()
|
302 |
+
|
303 |
if user_experience == "beginner":
|
304 |
+
# 增加更多特徵評估
|
305 |
+
difficult_traits = {
|
306 |
+
'stubborn': -0.12,
|
307 |
+
'independent': -0.10,
|
308 |
+
'dominant': -0.10,
|
309 |
+
'strong-willed': -0.08,
|
310 |
+
'protective': -0.06,
|
311 |
+
'energetic': -0.05
|
312 |
+
}
|
313 |
+
|
314 |
+
easy_traits = {
|
315 |
+
'gentle': 0.06,
|
316 |
+
'friendly': 0.06,
|
317 |
+
'eager to please': 0.06,
|
318 |
+
'patient': 0.05,
|
319 |
+
'adaptable': 0.05,
|
320 |
+
'calm': 0.04
|
321 |
+
}
|
322 |
+
|
323 |
+
# 更精確的特徵影響計算
|
324 |
+
temperament_adjustments = sum(value for trait, value in easy_traits.items() if trait in temperament_lower)
|
325 |
+
temperament_adjustments += sum(value for trait, value in difficult_traits.items() if trait in temperament_lower)
|
326 |
+
|
327 |
+
# 品種特定調整
|
328 |
+
if "terrier" in breed_info['Description'].lower():
|
329 |
+
temperament_adjustments -= 0.1 # 梗類犬對新手不友善
|
330 |
+
|
331 |
+
final_score = max(0.2, min(1.0, score + temperament_adjustments))
|
332 |
+
return final_score
|
333 |
|
334 |
def calculate_health_score(breed_name: str) -> float:
|
335 |
+
"""計算品種健康分數"""
|
336 |
if breed_name not in breed_health_info:
|
337 |
return 0.5
|
338 |
|
339 |
health_notes = breed_health_info[breed_name]['health_notes'].lower()
|
340 |
+
|
341 |
+
# 嚴重健康問題(降低0.15分)
|
342 |
severe_conditions = [
|
343 |
+
'hip dysplasia',
|
344 |
+
'heart disease',
|
345 |
+
'progressive retinal atrophy',
|
346 |
+
'bloat',
|
347 |
+
'epilepsy',
|
348 |
+
'degenerative myelopathy',
|
349 |
+
'von willebrand disease'
|
350 |
]
|
351 |
+
|
352 |
+
# 中度健康問題(降低0.1分)
|
353 |
moderate_conditions = [
|
354 |
+
'allergies',
|
355 |
+
'eye problems',
|
356 |
+
'joint problems',
|
357 |
+
'hypothyroidism',
|
358 |
+
'ear infections',
|
359 |
+
'skin issues'
|
360 |
+
]
|
361 |
+
|
362 |
+
# 輕微健康問題(降低0.05分)
|
363 |
+
minor_conditions = [
|
364 |
+
'dental issues',
|
365 |
+
'weight gain tendency',
|
366 |
+
'minor allergies',
|
367 |
+
'seasonal allergies'
|
368 |
]
|
369 |
|
370 |
+
# 計算基礎健康分數
|
371 |
+
health_score = 1.0
|
372 |
+
|
373 |
+
# 根據問題嚴重程度扣分
|
374 |
severe_count = sum(1 for condition in severe_conditions if condition in health_notes)
|
375 |
moderate_count = sum(1 for condition in moderate_conditions if condition in health_notes)
|
376 |
+
minor_count = sum(1 for condition in minor_conditions if condition in health_notes)
|
377 |
+
|
378 |
+
health_score -= (severe_count * 0.15)
|
379 |
+
health_score -= (moderate_count * 0.1)
|
380 |
+
health_score -= (minor_count * 0.05)
|
381 |
+
|
382 |
+
# 壽命影響
|
383 |
+
try:
|
384 |
+
lifespan = breed_health_info[breed_name].get('average_lifespan', '10-12')
|
385 |
+
years = float(lifespan.split('-')[0])
|
386 |
+
if years < 8:
|
387 |
health_score *= 0.9
|
388 |
+
elif years > 13:
|
389 |
+
health_score *= 1.1
|
390 |
+
except:
|
391 |
+
pass
|
392 |
|
393 |
+
# 特殊健康優勢
|
394 |
+
if 'generally healthy' in health_notes or 'hardy breed' in health_notes:
|
395 |
+
health_score *= 1.1
|
396 |
|
397 |
+
return max(0.2, min(1.0, health_score))
|
398 |
|
399 |
def calculate_noise_score(breed_name: str, user_noise_tolerance: str) -> float:
|
400 |
+
"""計算品種噪音分數"""
|
401 |
if breed_name not in breed_noise_info:
|
402 |
return 0.5
|
403 |
|
404 |
noise_info = breed_noise_info[breed_name]
|
405 |
noise_level = noise_info['noise_level'].lower()
|
406 |
+
noise_notes = noise_info['noise_notes'].lower()
|
407 |
|
408 |
# 基礎噪音分數矩陣
|
409 |
+
base_scores = {
|
410 |
+
'low': {'low': 1.0, 'medium': 0.9, 'high': 0.8},
|
411 |
+
'medium': {'low': 0.7, 'medium': 1.0, 'high': 0.9},
|
412 |
+
'high': {'low': 0.4, 'medium': 0.7, 'high': 1.0},
|
413 |
+
'varies': {'low': 0.6, 'medium': 0.8, 'high': 0.9}
|
414 |
}
|
415 |
|
416 |
+
# 獲取基礎分數
|
417 |
+
base_score = base_scores.get(noise_level, {'low': 0.7, 'medium': 0.8, 'high': 0.6})[user_noise_tolerance]
|
418 |
+
|
419 |
+
# 吠叫原因評估
|
420 |
+
barking_reasons_penalty = 0
|
421 |
+
problematic_triggers = [
|
422 |
+
('separation anxiety', -0.15),
|
423 |
+
('excessive barking', -0.12),
|
424 |
+
('territorial', -0.08),
|
425 |
+
('alert barking', -0.05),
|
426 |
+
('attention seeking', -0.05)
|
427 |
+
]
|
428 |
+
|
429 |
+
for trigger, penalty in problematic_triggers:
|
430 |
+
if trigger in noise_notes:
|
431 |
+
barking_reasons_penalty += penalty
|
432 |
+
|
433 |
+
# 可訓練性補償
|
434 |
+
trainability_bonus = 0
|
435 |
+
if 'responds well to training' in noise_notes:
|
436 |
+
trainability_bonus = 0.1
|
437 |
+
elif 'can be trained' in noise_notes:
|
438 |
+
trainability_bonus = 0.05
|
439 |
|
440 |
+
# 特殊情況
|
441 |
special_adjustments = 0
|
442 |
+
if 'rarely barks' in noise_notes:
|
443 |
+
special_adjustments += 0.1
|
444 |
+
if 'howls' in noise_notes and user_noise_tolerance == 'low':
|
445 |
special_adjustments -= 0.1
|
|
|
|
|
|
|
|
|
|
|
446 |
|
447 |
+
final_score = base_score + barking_reasons_penalty + trainability_bonus + special_adjustments
|
448 |
+
|
449 |
+
return max(0.2, min(1.0, final_score))
|
450 |
|
451 |
# 計算所有基礎分數
|
452 |
scores = {
|
453 |
+
'space': calculate_space_score(
|
454 |
+
breed_info['Size'],
|
455 |
+
user_prefs.living_space,
|
456 |
+
user_prefs.space_for_play,
|
457 |
+
breed_info.get('Exercise Needs', 'Moderate')
|
458 |
+
),
|
459 |
+
'exercise': calculate_exercise_score(
|
460 |
+
breed_info.get('Exercise Needs', 'Moderate'),
|
461 |
+
user_prefs.exercise_time
|
462 |
+
),
|
463 |
+
'grooming': calculate_grooming_score(
|
464 |
+
breed_info.get('Grooming Needs', 'Moderate'),
|
465 |
+
user_prefs.grooming_commitment.lower(),
|
466 |
+
breed_info['Size']
|
467 |
+
),
|
468 |
+
'experience': calculate_experience_score(
|
469 |
+
breed_info.get('Care Level', 'Moderate'),
|
470 |
+
user_prefs.experience_level,
|
471 |
+
breed_info.get('Temperament', '')
|
472 |
+
),
|
473 |
'health': calculate_health_score(breed_info.get('Breed', '')),
|
474 |
'noise': calculate_noise_score(breed_info.get('Breed', ''), user_prefs.noise_tolerance)
|
475 |
}
|
476 |
|
477 |
+
# 優化權重配置
|
478 |
weights = {
|
479 |
+
'space': 0.28,
|
480 |
+
'exercise': 0.18,
|
481 |
+
'grooming': 0.12,
|
482 |
+
'experience': 0.22,
|
483 |
+
'health': 0.12,
|
484 |
+
'noise': 0.08
|
485 |
}
|
486 |
|
487 |
+
# 計算加權總分
|
488 |
+
weighted_score = sum(score * weights[category] for category, score in scores.items())
|
489 |
+
|
490 |
+
# 擴大分數差異
|
491 |
+
def amplify_score(score):
|
492 |
+
# 使用指數函數擴大差異
|
493 |
+
amplified = pow((score - 0.5) * 2, 3) / 8 + score
|
494 |
+
return max(0.65, min(0.95, amplified)) # 限制在65%-95%範圍內
|
495 |
+
|
496 |
+
final_score = amplify_score(weighted_score)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
497 |
|
498 |
# 四捨五入所有分數
|
499 |
+
scores = {k: round(v, 4) for k, v in scores.items()}
|
500 |
+
scores['overall'] = round(final_score, 4)
|
501 |
|
502 |
return scores
|
503 |
|