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 | |