feat: 添加 JD-R 理论分析模块与 SHAP 可解释性分析功能
- 后端新增 JD-R(工作要求-资源)理论维度数据生成,包含工作要求、工作资源、
个人资源、中介变量共 16 个新特征列
- 新增 JD-R 分析服务与 API(维度统计、倦怠投入分析、双路径中介分析、
分组轮廓、风险分布)
- 新增 SHAP 可解释性分析模块(全局重要性、局部解释、特征交互、依赖图)
- 预测服务增加风险分类模型加载与概率预测能力
- 前端新增 JD-R 分析页面(JDRAnalysis.vue),含雷达图、散点图、路径分析等可视化
- 预测页面增加风险概率展示与 SHAP 特征解释
- 路由与导航菜单同步更新
This commit is contained in:
@@ -32,6 +32,8 @@ MODEL_INFO = {
|
||||
class PredictService:
|
||||
def __init__(self):
|
||||
self.models = {}
|
||||
self.classifiers = {}
|
||||
self.classification_metrics = {}
|
||||
self.scaler = None
|
||||
self.feature_names = None
|
||||
self.selected_features = None
|
||||
@@ -94,6 +96,21 @@ class PredictService:
|
||||
if valid_metrics:
|
||||
self.default_model = max(valid_metrics.items(), key=lambda item: item[1]['r2'])[0]
|
||||
|
||||
# 加载风险分类模型
|
||||
for name in ['random_forest', 'gradient_boosting', 'lightgbm', 'xgboost']:
|
||||
path = os.path.join(config.MODELS_DIR, f'risk_{name}_classifier.pkl')
|
||||
if os.path.exists(path):
|
||||
try:
|
||||
self.classifiers[name] = joblib.load(path)
|
||||
except Exception:
|
||||
pass
|
||||
cls_metrics_path = os.path.join(config.MODELS_DIR, 'classification_metrics.pkl')
|
||||
if os.path.exists(cls_metrics_path):
|
||||
try:
|
||||
self.classification_metrics = joblib.load(cls_metrics_path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def get_available_models(self):
|
||||
self._ensure_models_loaded()
|
||||
models = []
|
||||
@@ -131,10 +148,15 @@ class PredictService:
|
||||
|
||||
risk_level, risk_label = self._get_risk_level(predicted_hours)
|
||||
confidence = max(0.5, self.model_metrics.get(model_type, {}).get('r2', 0.82))
|
||||
|
||||
# 风险分类概率
|
||||
risk_probability = self._get_risk_probability(features, model_type)
|
||||
|
||||
return {
|
||||
'predicted_hours': round(predicted_hours, 2),
|
||||
'risk_level': risk_level,
|
||||
'risk_label': risk_label,
|
||||
'risk_probability': risk_probability,
|
||||
'confidence': round(confidence, 2),
|
||||
'model_used': model_type,
|
||||
'model_name_cn': MODEL_INFO.get(model_type, {}).get('name_cn', model_type),
|
||||
@@ -198,11 +220,65 @@ class PredictService:
|
||||
'predicted_hours': round(max(0.5, base_hours), 2),
|
||||
'risk_level': risk_level,
|
||||
'risk_label': risk_label,
|
||||
'risk_probability': {'low': 0.0, 'medium': 1.0, 'high': 0.0},
|
||||
'confidence': 0.72,
|
||||
'model_used': 'default',
|
||||
'model_name_cn': '默认规则',
|
||||
}
|
||||
|
||||
def _get_risk_probability(self, features, model_type):
|
||||
"""获取分类器预测的风险概率"""
|
||||
classifier = self.classifiers.get(model_type)
|
||||
if classifier is None:
|
||||
classifier = self.classifiers.get('random_forest')
|
||||
if classifier is None:
|
||||
return {'low': 0.0, 'medium': 1.0, 'high': 0.0}
|
||||
try:
|
||||
proba = classifier.predict_proba([features])[0]
|
||||
classes = list(classifier.classes_)
|
||||
result = {'low': 0.0, 'medium': 0.0, 'high': 0.0}
|
||||
label_map = {0: 'low', 1: 'medium', 2: 'high'}
|
||||
for idx, cls in enumerate(classes):
|
||||
if cls in label_map:
|
||||
result[label_map[cls]] = round(float(proba[idx]), 4)
|
||||
return result
|
||||
except Exception:
|
||||
return {'low': 0.0, 'medium': 1.0, 'high': 0.0}
|
||||
|
||||
def predict_risk_classification(self, data, model_type=None):
|
||||
"""使用分类模型直接预测风险等级"""
|
||||
self._ensure_models_loaded()
|
||||
model_type = model_type or self.default_model
|
||||
classifier = self.classifiers.get(model_type)
|
||||
if classifier is None:
|
||||
classifier = self.classifiers.get('random_forest')
|
||||
if classifier is None or self.scaler is None:
|
||||
return None
|
||||
|
||||
features = self._prepare_features(data)
|
||||
try:
|
||||
pred_class = int(classifier.predict([features])[0])
|
||||
proba = classifier.predict_proba([features])[0]
|
||||
label_map = {0: 'low', 1: 'medium', 2: 'high'}
|
||||
risk_labels_map = {'low': '低风险', 'medium': '中风险', 'high': '高风险'}
|
||||
risk_level = label_map.get(pred_class, 'medium')
|
||||
|
||||
classes = list(classifier.classes_)
|
||||
probabilities = {'low': 0.0, 'medium': 0.0, 'high': 0.0}
|
||||
for idx, cls in enumerate(classes):
|
||||
if cls in label_map:
|
||||
probabilities[label_map[cls]] = round(float(proba[idx]), 4)
|
||||
|
||||
return {
|
||||
'risk_level': risk_level,
|
||||
'risk_label': risk_labels_map[risk_level],
|
||||
'risk_probability': probabilities,
|
||||
'model_used': model_type,
|
||||
'classification_metrics': self.classification_metrics.get(model_type, {}),
|
||||
}
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def get_model_info(self):
|
||||
self._ensure_models_loaded()
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user