Zirok05's picture
Update app/simulation/core/processor.py
a69fc0b verified
Raw
History Blame Contribute Delete
14.5 kB
import numpy as np
import pandas as pd
from app.models.escalation import escalation_decision
from app.models.escalation import check_business_rules
def processing_time_function(lr_proba, second_proba, threshold=0.5, base_time=5,
lr_weight=1.0, second_weight=1.5):
"""
Генерирует время обработки для заявок, попавших в ручной разбор
"""
total_weight = lr_weight + second_weight
proba = (lr_proba * lr_weight + second_proba * second_weight) / total_weight
margin = abs(proba - threshold)
max_margin = max(threshold, 1 - threshold)
uncertainty = 1 - (margin / max_margin)
mean_time = base_time * (1 + 3 * uncertainty)
processing_time = np.random.exponential(scale=mean_time)
return max(1, processing_time)
class ApplicationProcessor:
def __init__(self, lr_model, second_model, second_model_name,
specialists_count=5, # основные специалисты (модели)
business_specialists_count=2, # эксперты (бизнес-правила)
base_processing_time=5, # время рассмотрения анкет от моделей
business_processing_time=10, # время рассмотрения анкет от бизнес-правил
lr_weight=1.0, second_weight=1.5):
self.lr_model = lr_model
self.second_model = second_model
self.second_model_name = second_model_name
self.specialists_count = specialists_count
self.business_specialists_count = business_specialists_count
self.base_processing_time = base_processing_time
self.business_processing_time = business_processing_time
self.lr_weight = lr_weight
self.second_weight = second_weight
self.specialists = [0] * specialists_count
self.business_specialists = [0] * business_specialists_count # отдельный пул
self.manual_queue = [] # очередь от моделей
self.business_queue = [] # очередь от бизнес-правил
self.stats = {
'total_processed': 0,
'auto_approved': 0,
'auto_declined': 0,
'manual_sent': 0,
'manual_processed': 0,
'business_manual_sent': 0,
'business_manual_processed': 0,
'queue_history': [],
'business_queue_history': [],
'wait_times': [],
'business_wait_times': [],
'specialist_busy': [],
'business_specialist_busy': [],
'business_rules_manual': 0,
'business_rules_auto': 0
}
self.batch_stats = []
def process_batch(self, applications_batch, preprocessor, scaler,
threshold, lr_margins, second_margins, current_time):
"""
Обрабатывает батч заявок за текущую минуту
"""
minute_results = {
'new_apps': len(applications_batch),
'auto_decisions': [],
'new_manual': 0,
'new_business_manual': 0,
'processed_manual': 0,
'processed_business_manual': 0,
'queue_size': 0,
'business_queue_size': 0,
'specialists_busy': sum(1 for s in self.specialists if s > 0),
'business_specialists_busy': sum(1 for s in self.business_specialists if s > 0),
'business_rules': 0
}
# 1. Уменьшаем время работы специалистов
self.specialists = [max(0, s - 1) for s in self.specialists]
self.business_specialists = [max(0, s - 1) for s in self.business_specialists]
if not applications_batch:
minute_results['queue_size'] = len(self.manual_queue)
minute_results['business_queue_size'] = len(self.business_queue)
self.stats['queue_history'].append(len(self.manual_queue))
self.stats['business_queue_history'].append(len(self.business_queue))
self.stats['specialist_busy'].append(minute_results['specialists_busy'])
self.stats['business_specialist_busy'].append(minute_results['business_specialists_busy'])
return minute_results
df = pd.DataFrame(applications_batch)
# 2. Применяем бизнес-правила ко всем заявкам
manual_mask, auto_reject_mask, messages, auto_decisions = check_business_rules(df)
# 3. Сохраняем статистику по бизнес-правилам
business_manual_count = manual_mask.sum()
business_auto_count = auto_reject_mask.sum()
# Инициализируем
n = len(applications_batch)
model_indices = []
# 4. Обрабатываем результаты бизнес-правил
for idx in range(n):
if manual_mask[idx]:
# Ручной разбор по бизнес-правилам - в отдельную очередь
self.business_queue.append({
'app': applications_batch[idx],
'arrival_time': current_time,
'reason': 'business_rules',
'message': messages[idx],
'lr_proba': None,
'second_proba': None
})
minute_results['new_business_manual'] += 1
minute_results['business_rules'] += 1
self.stats['business_rules_manual'] += 1
self.stats['business_manual_sent'] += 1
elif auto_reject_mask[idx]:
# Автоматический отказ по бизнес-правилам
decision = {
'final_decision': auto_decisions[idx], # всегда 1
'model_used': 'Business Rules',
'probability': 1.0,
'needs_review': False,
'message': messages[idx]
}
minute_results['auto_decisions'].append(decision)
self.stats['auto_declined'] += 1
self.stats['business_rules_auto'] += 1
self.stats['total_processed'] += 1
else:
# Заявка идет в модели
model_indices.append(idx)
# Инициализируем переменные для статистики моделей
lr_confident_count = 0
second_confident_count = 0
second_uncertain_count = 0
# 5. Батчевая обработка моделей
if model_indices:
# Берём только заявки, которые прошли бизнес-правила
df_models = df.iloc[model_indices].copy()
# Формируем DataFrame для моделей
model_df = pd.DataFrame({
'RevolvingUtilizationOfUnsecuredLines': df_models['RevolvingUtilizationOfUnsecuredLines'],
'age': df_models['age'],
'NumberOfTime30-59DaysPastDueNotWorse': df_models['NumberOfTime30-59DaysPastDueNotWorse'],
'DebtRatio': df_models['DebtRatio'].fillna(0),
'MonthlyIncome': df_models['MonthlyIncome'].fillna(0),
'NumberOfOpenCreditLinesAndLoans': df_models['NumberOfOpenCreditLinesAndLoans'],
'NumberOfTimes90DaysLate': df_models['NumberOfTimes90DaysLate'],
'NumberRealEstateLoansOrLines': df_models['NumberRealEstateLoansOrLines'],
'NumberOfTime60-89DaysPastDueNotWorse': df_models['NumberOfTime60-89DaysPastDueNotWorse'],
'NumberOfDependents': df_models['NumberOfDependents'].fillna(0)
})
# Вызываем escalation_decision для всего батча
batch_decisions, batch_manual_mask, stats = escalation_decision(
model_df,
self.lr_model,
self.second_model,
self.second_model_name,
threshold=threshold,
lr_margins=lr_margins,
second_margins=second_margins,
preprocessor=preprocessor,
scaler=scaler
)
# Сохраняем статистику из escalation_decision
lr_confident_count = stats['lr_confident']
second_confident_count = stats['second_confident']
second_uncertain_count = stats['second_uncertain']
# Распределяем результаты по исходным индексам
for local_idx, orig_idx in enumerate(model_indices):
decision = batch_decisions[local_idx]
if decision['needs_review']:
self.manual_queue.append({
'app': applications_batch[orig_idx],
'arrival_time': current_time,
'reason': 'model_uncertainty',
'decision': decision,
'lr_proba': decision.get('lr_proba'),
'second_proba': decision.get('second_proba')
})
minute_results['new_manual'] += 1
self.stats['manual_sent'] += 1
else:
minute_results['auto_decisions'].append(decision)
if decision['final_decision'] == 0:
self.stats['auto_approved'] += 1
else:
self.stats['auto_declined'] += 1
self.stats['total_processed'] += 1
# Сохраняем общую статистику батча
self.batch_stats.append({
'time': current_time,
'business_manual': business_manual_count,
'business_auto': business_auto_count,
'lr_confident': lr_confident_count,
'second_confident': second_confident_count,
'second_uncertain': second_uncertain_count,
'total_in_batch': len(applications_batch),
'new_manual': minute_results['new_manual'],
'new_business_manual': minute_results['new_business_manual'],
'auto_total': len(minute_results['auto_decisions'])
})
# 6. Распределяем заявки из бизнес-очереди по свободным экспертам
for i in range(self.business_specialists_count):
if self.business_specialists[i] <= 0 and self.business_queue:
next_app = self.business_queue.pop(0)
wait_time = current_time - next_app['arrival_time']
self.stats['business_wait_times'].append(wait_time)
# Эксперты обрабатывают бизнес-правила
proc_time = self.business_processing_time
self.business_specialists[i] = proc_time
minute_results['processed_business_manual'] += 1
self.stats['business_manual_processed'] += 1
# 7. Распределяем заявки из основной очереди по свободным специалистам
for i in range(self.specialists_count):
if self.specialists[i] <= 0 and self.manual_queue:
next_app = self.manual_queue.pop(0)
wait_time = current_time - next_app['arrival_time']
self.stats['wait_times'].append(wait_time)
if next_app['reason'] == 'business_rules':
proc_time = self.business_processing_time
else:
# Используем функцию processing_time_function
proc_time = processing_time_function(
lr_proba=next_app.get('lr_proba', 0.5),
second_proba=next_app.get('second_proba', 0.5),
threshold=threshold,
base_time=self.base_processing_time,
lr_weight=self.lr_weight,
second_weight=self.second_weight
)
self.specialists[i] = proc_time
minute_results['processed_manual'] += 1
self.stats['manual_processed'] += 1
minute_results['queue_size'] = len(self.manual_queue)
minute_results['business_queue_size'] = len(self.business_queue)
self.stats['queue_history'].append(len(self.manual_queue))
self.stats['business_queue_history'].append(len(self.business_queue))
self.stats['specialist_busy'].append(minute_results['specialists_busy'])
self.stats['business_specialist_busy'].append(minute_results['business_specialists_busy'])
return minute_results
def load_test_dataset(self, filepath):
df = pd.read_csv(filepath)
if 'SeriousDlqin2yrs' in df.columns:
df = df.drop(columns=['SeriousDlqin2yrs'])
return df.to_dict('records')
def get_queue_stats(self):
if self.stats['wait_times']:
avg_wait = np.mean(self.stats['wait_times'])
max_wait = np.max(self.stats['wait_times'])
else:
avg_wait = max_wait = 0
if self.stats['business_wait_times']:
avg_business_wait = np.mean(self.stats['business_wait_times'])
max_business_wait = np.max(self.stats['business_wait_times'])
else:
avg_business_wait = max_business_wait = 0
return {
'current_queue': len(self.manual_queue),
'current_business_queue': len(self.business_queue),
'avg_wait_minutes': avg_wait,
'max_wait_minutes': max_wait,
'avg_business_wait_minutes': avg_business_wait,
'max_business_wait_minutes': max_business_wait,
'queue_history': self.stats['queue_history'],
'business_queue_history': self.stats['business_queue_history'],
'specialist_busy': self.stats['specialist_busy'],
'business_specialist_busy': self.stats['business_specialist_busy'],
'business_rules_split': {
'manual': self.stats['business_rules_manual'],
'auto': self.stats['business_rules_auto']
}
}