Artem Zmailov
Final cleanup: all heavy files moved to LFS
af457d2
Raw
History Blame
17.2 kB
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
def minutes_to_time(minutes, start_time="00:00"):
"""Преобразует минуты от старта в строку времени ЧЧ:ММ"""
start_hour, start_min = map(int, start_time.split(':'))
total_minutes = start_hour * 60 + start_min + minutes
hour = (total_minutes // 60) % 24
minute = total_minutes % 60
return f"{hour:02d}:{minute:02d}"
def plot_queue_dynamics(queue_history, business_queue_history=None, start_time="00:00"):
"""
Два отдельных графика для очередей с временной шкалой ЧЧ:ММ
"""
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
# Создаем метки времени для каждого часа
total_minutes = len(queue_history)
hours = range(0, total_minutes, 60) # каждый час
hour_labels = [minutes_to_time(m, start_time) for m in hours]
# График 1: Очередь моделей
ax1.plot(range(total_minutes), queue_history, 'b-', linewidth=1.5)
ax1.set_xticks(hours)
ax1.set_xticklabels(hour_labels, rotation=45)
ax1.set_xlabel('Время')
ax1.set_ylabel('Размер очереди')
ax1.set_title('Очередь моделей')
ax1.grid(True, alpha=0.3)
# График 2: Очередь бизнес-правил
if business_queue_history and len(business_queue_history) > 0:
ax2.plot(range(total_minutes), business_queue_history, 'orange', linewidth=1.5)
ax2.set_xticks(hours)
ax2.set_xticklabels(hour_labels, rotation=45)
ax2.set_xlabel('Время')
ax2.set_ylabel('Размер очереди')
ax2.set_title('Очередь бизнес-правил')
ax2.grid(True, alpha=0.3)
else:
ax2.text(0.5, 0.5, 'Нет данных', ha='center', va='center', transform=ax2.transAxes)
ax2.set_title('Очередь бизнес-правил')
ax2.set_xlabel('Время')
plt.tight_layout()
return plt
def plot_specialist_load(specialist_busy_history, specialists_count, start_time="00:00"):
"""График загрузки специалистов с временной шкалой ЧЧ:ММ"""
load_percent = [busy / specialists_count * 100 for busy in specialist_busy_history]
fig, ax = plt.subplots(figsize=(10, 4))
total_minutes = len(load_percent)
hours = range(0, total_minutes, 60) # каждый час
hour_labels = [minutes_to_time(m, start_time) for m in hours]
ax.plot(range(total_minutes), load_percent, 'g-', linewidth=1.5)
ax.axhline(y=100, color='r', linestyle='--', alpha=0.5, label='Максимум')
ax.axhline(y=80, color='b', linestyle='--', alpha=0.5, label='Цель 80%')
ax.set_xticks(hours)
ax.set_xticklabels(hour_labels, rotation=45)
ax.set_xlabel('Время')
ax.set_ylabel('Загрузка (%)')
ax.set_title('Загрузка специалистов')
ax.legend()
ax.grid(True, alpha=0.3)
ax.set_ylim(0, 110)
plt.tight_layout()
return plt
def plot_inflow(minute_counts, start_time="00:00"):
"""
График входящего потока заявок с заливкой под кривой
"""
fig, ax = plt.subplots(figsize=(14, 5))
total_minutes = len(minute_counts)
minutes = range(total_minutes)
# Заливка под кривой (area plot)
ax.fill_between(minutes, minute_counts, alpha=0.3, color='blue', label='Общий поток')
# Основной график (линия поверх заливки)
ax.plot(minutes, minute_counts, 'b-', linewidth=1.5, alpha=0.8)
# Скользящее среднее
window = 30
if total_minutes > window:
smoothed = np.convolve(minute_counts, np.ones(window) / window, mode='valid')
ax.plot(range(window - 1, total_minutes), smoothed,
'r-', linewidth=2.5, label=f'Среднее за 30 мин')
# Можно добавить заливку и для среднего (опционально)
# ax.fill_between(range(window - 1, total_minutes), smoothed, alpha=0.2, color='red')
# Метки времени
hours = range(0, total_minutes, 60)
hour_labels = [minutes_to_time(m, start_time) for m in hours]
ax.set_xticks(hours)
ax.set_xticklabels(hour_labels, rotation=45)
ax.set_xlabel('Время')
ax.set_ylabel('Количество заявок')
ax.set_title('Входящий поток заявок')
ax.legend()
ax.grid(True, alpha=0.3)
# Добавим горизонтальную линию среднего
mean_value = np.mean(minute_counts)
ax.axhline(y=mean_value, color='gray', linestyle='--', alpha=0.7,
label=f'Среднее: {mean_value:.1f}')
plt.tight_layout()
return plt
def minutes_to_time(minutes, start_time="00:00"):
"""Преобразует минуты от старта в строку времени ЧЧ:ММ"""
start_hour, start_min = map(int, start_time.split(':'))
total_minutes = start_hour * 60 + start_min + minutes
hour = (total_minutes // 60) % 24
minute = total_minutes % 60
return f"{hour:02d}:{minute:02d}"
def plot_detailed_decisions(batch_stats, second_model_name="XGBoost", start_time="00:00"):
"""
Набор графиков для каждого типа решений отдельно с временной шкалой ЧЧ:ММ
"""
if not batch_stats:
return None
fig, axes = plt.subplots(3, 2, figsize=(14, 10))
times = [stat['time'] for stat in batch_stats] # минуты
total_minutes = max(times) if times else 0
# Метки времени каждый час
hours = range(0, total_minutes + 60, 60)
hour_labels = [minutes_to_time(m, start_time) for m in hours]
# 1. Бизнес-правила (ручной разбор)
axes[0, 0].plot(times, [stat['business_manual'] for stat in batch_stats],
'r-', linewidth=1.5)
axes[0, 0].fill_between(times, 0, [stat['business_manual'] for stat in batch_stats],
alpha=0.2, color='red')
axes[0, 0].set_title('Ручной разбор: бизнес-правила', fontweight='bold')
axes[0, 0].set_xticks(hours)
axes[0, 0].set_xticklabels(hour_labels, rotation=45)
axes[0, 0].set_xlabel('Время')
axes[0, 0].set_ylabel('Заявок')
axes[0, 0].grid(True, alpha=0.3)
# 2. Бизнес-правила (авто отказ)
axes[0, 1].plot(times, [stat['business_auto'] for stat in batch_stats],
'darkred', linewidth=1.5)
axes[0, 1].fill_between(times, 0, [stat['business_auto'] for stat in batch_stats],
alpha=0.2, color='darkred')
axes[0, 1].set_title('Авто отказ: бизнес-правила', fontweight='bold')
axes[0, 1].set_xticks(hours)
axes[0, 1].set_xticklabels(hour_labels, rotation=45)
axes[0, 1].set_xlabel('Время')
axes[0, 1].set_ylabel('Заявок')
axes[0, 1].grid(True, alpha=0.3)
# 3. LR уверенные решения
axes[1, 0].plot(times, [stat['lr_confident'] for stat in batch_stats],
'blue', linewidth=1.5)
axes[1, 0].fill_between(times, 0, [stat['lr_confident'] for stat in batch_stats],
alpha=0.2, color='blue')
axes[1, 0].set_title('Уверенные решения: Logistic Regression', fontweight='bold')
axes[1, 0].set_xticks(hours)
axes[1, 0].set_xticklabels(hour_labels, rotation=45)
axes[1, 0].set_xlabel('Время')
axes[1, 0].set_ylabel('Заявок')
axes[1, 0].grid(True, alpha=0.3)
# 4. Вторая модель уверенные решения
axes[1, 1].plot(times, [stat['second_confident'] for stat in batch_stats],
'green', linewidth=1.5)
axes[1, 1].fill_between(times, 0, [stat['second_confident'] for stat in batch_stats],
alpha=0.2, color='green')
axes[1, 1].set_title(f'Уверенные решения: {second_model_name}', fontweight='bold')
axes[1, 1].set_xticks(hours)
axes[1, 1].set_xticklabels(hour_labels, rotation=45)
axes[1, 1].set_xlabel('Время')
axes[1, 1].set_ylabel('Заявок')
axes[1, 1].grid(True, alpha=0.3)
# 5. Ручной разбор от моделей
axes[2, 0].plot(times, [stat['second_uncertain'] for stat in batch_stats],
'orange', linewidth=1.5)
axes[2, 0].fill_between(times, 0, [stat['second_uncertain'] for stat in batch_stats],
alpha=0.2, color='orange')
axes[2, 0].set_title('Ручной разбор: модели неуверенны', fontweight='bold')
axes[2, 0].set_xticks(hours)
axes[2, 0].set_xticklabels(hour_labels, rotation=45)
axes[2, 0].set_xlabel('Время')
axes[2, 0].set_ylabel('Заявок')
axes[2, 0].grid(True, alpha=0.3)
# 6. Сравнительный график
axes[2, 1].plot(times, [stat['business_manual'] for stat in batch_stats],
'r-', linewidth=1.5, label='Бизнес-правила', alpha=0.7)
axes[2, 1].plot(times, [stat['second_uncertain'] for stat in batch_stats],
'orange', linewidth=1.5, label='Модели неуверенны', alpha=0.7)
axes[2, 1].set_title('Сравнение источников ручного разбора', fontweight='bold')
axes[2, 1].set_xticks(hours)
axes[2, 1].set_xticklabels(hour_labels, rotation=45)
axes[2, 1].set_xlabel('Время')
axes[2, 1].set_ylabel('Заявок')
axes[2, 1].legend()
axes[2, 1].grid(True, alpha=0.3)
plt.suptitle('Детальный анализ решений', fontsize=14, fontweight='bold')
plt.tight_layout()
return plt
def plot_parameters_history(pid_history, second_model_name="XGBoost", start_time="00:00"):
"""График изменения параметров регулятора"""
if pid_history is None or pid_history.empty:
return None
fig, axes = plt.subplots(3, 1, figsize=(12, 12))
total_minutes = len(pid_history)
times = range(total_minutes)
# Метки времени
hours = range(0, total_minutes, 60)
hour_labels = [minutes_to_time(m, start_time) for m in hours]
# 1. Отступы LR
axes[0].plot(times, pid_history['lr_low'], 'g-', linewidth=2, label='LR Low')
axes[0].plot(times, pid_history['lr_high'], 'r-', linewidth=2, label='LR High')
axes[0].set_ylabel('Отступ')
axes[0].set_title('Отступы Logistic Regression')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
axes[0].set_xticks(hours)
axes[0].set_xticklabels(hour_labels, rotation=45)
# 2. Отступы второй модели (с именем из параметра)
axes[1].plot(times, pid_history['second_low'], 'g-', linewidth=2, label=f'{second_model_name} Low')
axes[1].plot(times, pid_history['second_high'], 'r-', linewidth=2, label=f'{second_model_name} High')
axes[1].set_ylabel('Отступ')
axes[1].set_title(f'Отступы {second_model_name}')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
axes[1].set_xticks(hours)
axes[1].set_xticklabels(hour_labels, rotation=45)
# 3. Ошибка загрузки и выход регулятора
axes[2].plot(times, pid_history['error_load'], 'b-', label='Error load', alpha=0.7, linewidth=1.5)
axes[2].plot(times, pid_history['output'], 'r-', label='Output', linewidth=2, alpha=0.7)
axes[2].axhline(y=0, color='black', linestyle='-', linewidth=0.5)
axes[2].set_xlabel('Время')
axes[2].set_ylabel('Значение')
axes[2].set_title('Ошибка загрузки и выход регулятора')
axes[2].legend()
axes[2].grid(True, alpha=0.3)
axes[2].set_xticks(hours)
axes[2].set_xticklabels(hour_labels, rotation=45)
plt.tight_layout()
return plt
# def plot_summary(processor):
# """Сводный дашборд"""
# fig, axes = plt.subplots(2, 3, figsize=(15, 10))
#
# stats = processor.stats
#
# # 1. Динамика очередей
# axes[0, 0].plot(stats['queue_history'], 'b-', linewidth=1.5, label='Очередь моделей')
# if 'business_queue_history' in stats:
# axes[0, 0].plot(stats['business_queue_history'], 'orange', linewidth=1.5, label='Очередь бизнес-правил')
# axes[0, 0].set_title('Динамика очередей')
# axes[0, 0].set_xlabel('Минута')
# axes[0, 0].set_ylabel('Заявок')
# axes[0, 0].legend()
# axes[0, 0].grid(True, alpha=0.3)
#
# # 2. Загрузка специалистов (модели)
# load = [b / processor.specialists_count * 100 for b in stats['specialist_busy']]
# axes[0, 1].plot(load, 'g-', linewidth=1.5, label='Основные специалисты')
# axes[0, 1].axhline(y=100, color='r', linestyle='--', alpha=0.5, label='Максимум')
# if hasattr(processor, 'target_load'):
# axes[0, 1].axhline(y=processor.target_load * 100, color='b', linestyle='--',
# alpha=0.5, label=f'Цель {processor.target_load * 100:.0f}%')
# axes[0, 1].set_title('Загрузка специалистов (модели)')
# axes[0, 1].set_xlabel('Минута')
# axes[0, 1].set_ylabel('%')
# axes[0, 1].legend()
# axes[0, 1].grid(True, alpha=0.3)
#
# # 3. Загрузка экспертов
# if 'business_specialist_busy' in stats and stats['business_specialist_busy']:
# business_load = [b / processor.business_specialists_count * 100 for b in stats['business_specialist_busy']]
# axes[1, 0].plot(business_load, 'orange', linewidth=1.5, label='Эксперты')
# axes[1, 0].axhline(y=100, color='r', linestyle='--', alpha=0.5, label='Максимум')
# axes[1, 0].set_title('Загрузка экспертов (бизнес-правила)')
# axes[1, 0].set_xlabel('Минута')
# axes[1, 0].set_ylabel('%')
# axes[1, 0].legend()
# axes[1, 0].grid(True, alpha=0.3)
# else:
# axes[1, 0].text(0.5, 0.5, 'Нет данных по экспертам', ha='center', va='center')
# axes[1, 0].set_title('Загрузка экспертов')
#
# # 4. Распределение решений
# sizes = [
# stats['auto_approved'],
# stats['auto_declined'],
# stats['manual_processed'],
# stats.get('business_manual_processed', 0)
# ]
# labels = ['Одобрено авто', 'Отказ авто', 'Ручной (модели)', 'Ручной (бизнес)']
# colors = ['#2ecc71', '#e74c3c', '#3498db', '#f39c12']
#
# if sum(sizes) > 0:
# wedges, texts, autotexts = axes[1, 1].pie(sizes, labels=labels, autopct='%1.1f%%',
# colors=colors, startangle=90)
# for autotext in autotexts:
# autotext.set_color('white')
# autotext.set_fontweight('bold')
# axes[1, 1].set_title('Итоговые решения')
#
# # 5. Ключевые метрики (освободилось место)
# total = stats['total_processed']
# if total > 0:
# avg_wait = np.mean(stats['wait_times']) if stats['wait_times'] else 0
# avg_business_wait = np.mean(stats.get('business_wait_times', [0])) if stats.get('business_wait_times') else 0
#
# metrics_text = f"""
# Всего заявок: {total:,}
# Одобрено авто: {stats['auto_approved']:,} ({stats['auto_approved'] / total * 100:.1f}%)
# Отказ авто: {stats['auto_declined']:,} ({stats['auto_declined'] / total * 100:.1f}%)
#
# Ручной разбор (модели): {stats['manual_processed']:,} ({stats['manual_processed'] / total * 100:.1f}%)
# Ручной разбор (бизнес): {stats.get('business_manual_processed', 0):,}
#
# Среднее время ожидания (модели): {avg_wait:.1f} мин
# Среднее время ожидания (бизнес): {avg_business_wait:.1f} мин
#
# Средняя загрузка специалистов: {np.mean(load):.1f}%
# """
# else:
# metrics_text = "Нет данных"
#
# axes[0, 2].text(0.1, 0.5, metrics_text, transform=axes[0, 2].transAxes,
# fontsize=10, verticalalignment='center', fontfamily='monospace')
# axes[0, 2].axis('off')
# axes[0, 2].set_title('Ключевые метрики')
#
# # 6. Пустой график или можно что-то еще
# axes[1, 2].axis('off')
#
# plt.suptitle('Сводная статистика симуляции', fontsize=14, fontweight='bold')
# plt.tight_layout()
# return plt