Spaces:
Running
on
Zero
Running
on
Zero
from typing import Dict, List, Any, Optional | |
from dataclasses import dataclass | |
import json | |
import os | |
class DimensionConfig: | |
"""維度配置""" | |
name: str | |
base_weight: float | |
priority_multiplier: Dict[str, float] | |
compatibility_matrix: Dict[str, Dict[str, float]] | |
threshold_values: Dict[str, float] | |
description: str | |
class ConstraintConfig: | |
"""約束配置""" | |
name: str | |
condition_keywords: List[str] | |
elimination_threshold: float | |
penalty_factors: Dict[str, float] | |
exemption_conditions: List[str] | |
description: str | |
class ScoringProfile: | |
"""評分配置檔""" | |
profile_name: str | |
dimensions: List[DimensionConfig] | |
constraints: List[ConstraintConfig] | |
normalization_method: str | |
bias_correction_rules: Dict[str, Any] | |
ui_preferences: Dict[str, Any] | |
class DynamicScoringConfig: | |
"""動態評分配置管理器""" | |
def __init__(self, config_path: Optional[str] = None): | |
self.config_path = config_path or self._get_default_config_path() | |
self.current_profile = self._load_default_profile() | |
self.custom_profiles = {} | |
def _get_default_config_path(self) -> str: | |
"""獲取默認配置路徑""" | |
return os.path.join(os.path.dirname(__file__), 'scoring_configs') | |
def _load_default_profile(self) -> ScoringProfile: | |
"""載入預設評分配置""" | |
# 空間相容性維度配置 | |
space_dimension = DimensionConfig( | |
name="space_compatibility", | |
base_weight=0.30, | |
priority_multiplier={ | |
"apartment_living": 1.5, | |
"first_time_owner": 1.2, | |
"limited_space": 1.4 | |
}, | |
compatibility_matrix={ | |
"apartment": { | |
"toy": 0.95, "small": 0.90, "medium": 0.50, | |
"large": 0.15, "giant": 0.05 | |
}, | |
"house_small": { | |
"toy": 0.85, "small": 0.90, "medium": 0.85, | |
"large": 0.60, "giant": 0.30 | |
}, | |
"house_medium": { | |
"toy": 0.80, "small": 0.85, "medium": 0.95, | |
"large": 0.85, "giant": 0.60 | |
}, | |
"house_large": { | |
"toy": 0.75, "small": 0.80, "medium": 0.90, | |
"large": 0.95, "giant": 0.95 | |
} | |
}, | |
threshold_values={ | |
"elimination_threshold": 0.20, | |
"warning_threshold": 0.40, | |
"good_threshold": 0.70 | |
}, | |
description="Evaluates breed size compatibility with living space" | |
) | |
# 運動相容性維度配置 | |
exercise_dimension = DimensionConfig( | |
name="exercise_compatibility", | |
base_weight=0.25, | |
priority_multiplier={ | |
"low_activity": 1.6, | |
"high_activity": 1.3, | |
"time_limited": 1.4 | |
}, | |
compatibility_matrix={ | |
"low_user": { | |
"low": 1.0, "moderate": 0.70, "high": 0.30, "very_high": 0.10 | |
}, | |
"moderate_user": { | |
"low": 0.80, "moderate": 1.0, "high": 0.80, "very_high": 0.50 | |
}, | |
"high_user": { | |
"low": 0.60, "moderate": 0.85, "high": 1.0, "very_high": 0.95 | |
} | |
}, | |
threshold_values={ | |
"severe_mismatch": 0.25, | |
"moderate_mismatch": 0.50, | |
"good_match": 0.75 | |
}, | |
description="Matches user activity level with breed exercise needs" | |
) | |
# 噪音相容性維度配置 | |
noise_dimension = DimensionConfig( | |
name="noise_compatibility", | |
base_weight=0.15, | |
priority_multiplier={ | |
"apartment_living": 1.8, | |
"noise_sensitive": 2.0, | |
"quiet_preference": 1.5 | |
}, | |
compatibility_matrix={ | |
"low_tolerance": { | |
"quiet": 1.0, "moderate": 0.60, "high": 0.20, "very_high": 0.05 | |
}, | |
"moderate_tolerance": { | |
"quiet": 0.90, "moderate": 1.0, "high": 0.70, "very_high": 0.40 | |
}, | |
"high_tolerance": { | |
"quiet": 0.80, "moderate": 0.90, "high": 1.0, "very_high": 0.85 | |
} | |
}, | |
threshold_values={ | |
"unacceptable": 0.15, | |
"concerning": 0.40, | |
"acceptable": 0.70 | |
}, | |
description="Matches breed noise levels with user tolerance" | |
) | |
# 約束配置 | |
apartment_constraint = ConstraintConfig( | |
name="apartment_size_constraint", | |
condition_keywords=["apartment", "small space", "studio", "condo"], | |
elimination_threshold=0.15, | |
penalty_factors={ | |
"large_breed": 0.70, | |
"giant_breed": 0.85, | |
"high_exercise": 0.60 | |
}, | |
exemption_conditions=["experienced_owner", "large_apartment"], | |
description="Eliminates breeds unsuitable for apartment living" | |
) | |
exercise_constraint = ConstraintConfig( | |
name="exercise_mismatch_constraint", | |
condition_keywords=["don't exercise", "low activity", "minimal exercise"], | |
elimination_threshold=0.20, | |
penalty_factors={ | |
"very_high_exercise": 0.80, | |
"working_breed": 0.60, | |
"high_energy": 0.70 | |
}, | |
exemption_conditions=["dog_park_access", "active_family"], | |
description="Prevents high-energy breeds for low-activity users" | |
) | |
# 偏見修正規則 | |
bias_correction_rules = { | |
"size_bias": { | |
"enabled": True, | |
"detection_threshold": 0.70, # 70%以上大型犬觸發修正 | |
"correction_strength": 0.60, # 修正強度 | |
"target_distribution": { | |
"toy": 0.10, "small": 0.25, "medium": 0.40, | |
"large": 0.20, "giant": 0.05 | |
} | |
}, | |
"popularity_bias": { | |
"enabled": True, | |
"common_breeds_penalty": 0.05, | |
"rare_breeds_bonus": 0.03 | |
} | |
} | |
# UI偏好設定 | |
ui_preferences = { | |
"ranking_style": "gradient_badges", | |
"score_display": "percentage_with_bars", | |
"color_scheme": { | |
"excellent": "#22C55E", | |
"good": "#F59E0B", | |
"moderate": "#6B7280", | |
"poor": "#EF4444" | |
}, | |
"animation_enabled": True, | |
"detailed_breakdown": True | |
} | |
return ScoringProfile( | |
profile_name="comprehensive_default", | |
dimensions=[space_dimension, exercise_dimension, noise_dimension], | |
constraints=[apartment_constraint, exercise_constraint], | |
normalization_method="sigmoid_compression", | |
bias_correction_rules=bias_correction_rules, | |
ui_preferences=ui_preferences | |
) | |
def get_dimension_config(self, dimension_name: str) -> Optional[DimensionConfig]: | |
"""獲取維度配置""" | |
for dim in self.current_profile.dimensions: | |
if dim.name == dimension_name: | |
return dim | |
return None | |
def get_constraint_config(self, constraint_name: str) -> Optional[ConstraintConfig]: | |
"""獲取約束配置""" | |
for constraint in self.current_profile.constraints: | |
if constraint.name == constraint_name: | |
return constraint | |
return None | |
def calculate_dynamic_weights(self, user_context: Dict[str, Any]) -> Dict[str, float]: | |
"""根據用戶情境動態計算權重""" | |
weights = {} | |
total_weight = 0 | |
for dimension in self.current_profile.dimensions: | |
base_weight = dimension.base_weight | |
# 根據用戶情境調整權重 | |
for context_key, multiplier in dimension.priority_multiplier.items(): | |
if user_context.get(context_key, False): | |
base_weight *= multiplier | |
weights[dimension.name] = base_weight | |
total_weight += base_weight | |
# 正規化權重 | |
return {k: v / total_weight for k, v in weights.items()} | |
def get_compatibility_score(self, dimension_name: str, | |
user_category: str, breed_category: str) -> float: | |
"""獲取相容性分數""" | |
dimension_config = self.get_dimension_config(dimension_name) | |
if not dimension_config: | |
return 0.5 | |
matrix = dimension_config.compatibility_matrix | |
if user_category in matrix and breed_category in matrix[user_category]: | |
return matrix[user_category][breed_category] | |
return 0.5 # 預設值 | |
def should_eliminate_breed(self, constraint_name: str, | |
breed_info: Dict[str, Any], | |
user_input: str) -> tuple[bool, str]: | |
"""判斷是否應該淘汰品種""" | |
constraint_config = self.get_constraint_config(constraint_name) | |
if not constraint_config: | |
return False, "" | |
# 檢查觸發條件 | |
user_input_lower = user_input.lower() | |
triggered = any(keyword in user_input_lower | |
for keyword in constraint_config.condition_keywords) | |
if not triggered: | |
return False, "" | |
# 檢查豁免條件 | |
exempted = any(condition in user_input_lower | |
for condition in constraint_config.exemption_conditions) | |
if exempted: | |
return False, "Exempted due to special conditions" | |
# 應用淘汰邏輯(具體實現取決於約束類型) | |
return self._apply_elimination_logic(constraint_config, breed_info, user_input) | |
def _apply_elimination_logic(self, constraint_config: ConstraintConfig, | |
breed_info: Dict[str, Any], user_input: str) -> tuple[bool, str]: | |
"""應用淘汰邏輯""" | |
# 根據約束名稱決定具體邏輯 | |
if constraint_config.name == "apartment_size_constraint": | |
breed_size = breed_info.get('Size', '').lower() | |
if any(size in breed_size for size in ['large', 'giant']): | |
return True, f"Breed size ({breed_size}) unsuitable for apartment" | |
elif constraint_config.name == "exercise_mismatch_constraint": | |
exercise_needs = breed_info.get('Exercise Needs', '').lower() | |
if any(level in exercise_needs for level in ['very high', 'extreme']): | |
return True, f"Exercise needs ({exercise_needs}) exceed user capacity" | |
return False, "" | |
def get_bias_correction_settings(self) -> Dict[str, Any]: | |
"""獲取偏見修正設定""" | |
return self.current_profile.bias_correction_rules | |
def get_ui_preferences(self) -> Dict[str, Any]: | |
"""獲取UI偏好設定""" | |
return self.current_profile.ui_preferences | |
def save_custom_profile(self, profile: ScoringProfile, filename: str): | |
"""保存自定義配置檔""" | |
if not os.path.exists(self.config_path): | |
os.makedirs(self.config_path) | |
filepath = os.path.join(self.config_path, f"{filename}.json") | |
# 將配置檔案轉換為JSON格式 | |
profile_dict = { | |
"profile_name": profile.profile_name, | |
"dimensions": [self._dimension_to_dict(dim) for dim in profile.dimensions], | |
"constraints": [self._constraint_to_dict(cons) for cons in profile.constraints], | |
"normalization_method": profile.normalization_method, | |
"bias_correction_rules": profile.bias_correction_rules, | |
"ui_preferences": profile.ui_preferences | |
} | |
with open(filepath, 'w', encoding='utf-8') as f: | |
json.dump(profile_dict, f, indent=2, ensure_ascii=False) | |
def load_custom_profile(self, filename: str) -> Optional[ScoringProfile]: | |
"""載入自定義配置檔""" | |
filepath = os.path.join(self.config_path, f"{filename}.json") | |
if not os.path.exists(filepath): | |
return None | |
try: | |
with open(filepath, 'r', encoding='utf-8') as f: | |
profile_dict = json.load(f) | |
return self._dict_to_profile(profile_dict) | |
except Exception as e: | |
print(f"Error loading profile {filename}: {str(e)}") | |
return None | |
def _dimension_to_dict(self, dimension: DimensionConfig) -> Dict[str, Any]: | |
"""將維度配置轉換為字典""" | |
return { | |
"name": dimension.name, | |
"base_weight": dimension.base_weight, | |
"priority_multiplier": dimension.priority_multiplier, | |
"compatibility_matrix": dimension.compatibility_matrix, | |
"threshold_values": dimension.threshold_values, | |
"description": dimension.description | |
} | |
def _constraint_to_dict(self, constraint: ConstraintConfig) -> Dict[str, Any]: | |
"""將約束配置轉換為字典""" | |
return { | |
"name": constraint.name, | |
"condition_keywords": constraint.condition_keywords, | |
"elimination_threshold": constraint.elimination_threshold, | |
"penalty_factors": constraint.penalty_factors, | |
"exemption_conditions": constraint.exemption_conditions, | |
"description": constraint.description | |
} | |
def _dict_to_profile(self, profile_dict: Dict[str, Any]) -> ScoringProfile: | |
"""將字典轉換為評分配置檔""" | |
dimensions = [self._dict_to_dimension(dim) for dim in profile_dict["dimensions"]] | |
constraints = [self._dict_to_constraint(cons) for cons in profile_dict["constraints"]] | |
return ScoringProfile( | |
profile_name=profile_dict["profile_name"], | |
dimensions=dimensions, | |
constraints=constraints, | |
normalization_method=profile_dict["normalization_method"], | |
bias_correction_rules=profile_dict["bias_correction_rules"], | |
ui_preferences=profile_dict["ui_preferences"] | |
) | |
def _dict_to_dimension(self, dim_dict: Dict[str, Any]) -> DimensionConfig: | |
"""將字典轉換為維度配置""" | |
return DimensionConfig( | |
name=dim_dict["name"], | |
base_weight=dim_dict["base_weight"], | |
priority_multiplier=dim_dict["priority_multiplier"], | |
compatibility_matrix=dim_dict["compatibility_matrix"], | |
threshold_values=dim_dict["threshold_values"], | |
description=dim_dict["description"] | |
) | |
def _dict_to_constraint(self, cons_dict: Dict[str, Any]) -> ConstraintConfig: | |
"""將字典轉換為約束配置""" | |
return ConstraintConfig( | |
name=cons_dict["name"], | |
condition_keywords=cons_dict["condition_keywords"], | |
elimination_threshold=cons_dict["elimination_threshold"], | |
penalty_factors=cons_dict["penalty_factors"], | |
exemption_conditions=cons_dict["exemption_conditions"], | |
description=cons_dict["description"] | |
) | |
def get_scoring_config() -> DynamicScoringConfig: | |
"""獲取全局評分配置""" | |
return scoring_config | |
def update_scoring_config(new_config: DynamicScoringConfig): | |
"""更新全局評分配置""" | |
global scoring_config | |
scoring_config = new_config | |