PawMatchAI / dynamic_scoring_config.py
DawnC's picture
Upload 18 files
1e4c9bc verified
from typing import Dict, List, Any, Optional
from dataclasses import dataclass
import json
import os
@dataclass
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
@dataclass
class ConstraintConfig:
"""約束配置"""
name: str
condition_keywords: List[str]
elimination_threshold: float
penalty_factors: Dict[str, float]
exemption_conditions: List[str]
description: str
@dataclass
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