File size: 5,311 Bytes
e191b9b bd19734 e191b9b bd19734 e191b9b bd19734 e191b9b bd19734 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | from matplotlib.animation import FFMpegWriter
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import tempfile
import numpy as np
import os
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 create_simulation_video(frames, specialists_count, second_model_name, fps=24):
if not frames:
return None
# Настройка стиля
plt.style.use('seaborn-v0_8-whitegrid')
fig, axes = plt.subplots(2, 2, figsize=(16, 10), facecolor='#f8f9fa')
plt.subplots_adjust(hspace=0.4, wspace=0.25)
plt.close()
def update(i):
data = frames[i]
for ax in axes.flatten():
ax.clear()
ax.set_facecolor('white')
# 1. ДИНАМИКА ПОТОКА (Локализация)
y_inflow = data['inflow_history']
axes[0, 0].fill_between(range(len(y_inflow)), y_inflow, color='#4361ee', alpha=0.3)
axes[0, 0].plot(range(len(y_inflow)), y_inflow, color='#4361ee', linewidth=2)
axes[0, 0].set_xlim(0, 1440) # Фиксация оси времени
axes[0, 0].set_title("ДИНАМИКА ПОТОКА (заявок/мин)", fontsize=12, fontweight='bold')
axes[0, 0].set_xlabel("Минуты симуляции")
# 2. ЗАГРУЗКА СИСТЕМЫ
y_load = [v * 100 for v in data['load_history']]
axes[0, 1].fill_between(range(len(y_load)), y_load, color='#4cc9f0', alpha=0.3)
axes[0, 1].plot(range(len(y_load)), y_load, color='#4cc9f0', linewidth=2)
axes[0, 1].axhline(y=80, color='#f72585', linestyle='--', alpha=0.6)
axes[0, 1].set_xlim(0, 1440)
axes[0, 1].set_ylim(0, 110)
axes[0, 1].set_title(f"ЗАГРУЖЕННОСТЬ СПЕЦИАЛИСТОВ %: {y_load[-1]:.1f}%", fontsize=12, fontweight='bold')
# 3. HEATMAP И ЛЕГЕНДА
states = np.array(data['specialist_states'])
cols = 20
rows = int(np.ceil(specialists_count / cols))
z = np.zeros((rows, cols))
for idx, val in enumerate(states[:rows * cols]):
z[idx // cols, idx % cols] = val
im = axes[1, 0].imshow(z, cmap='RdYlGn_r', aspect='auto', vmin=0, vmax=10)
axes[1, 0].set_title(f"МОНИТОРИНГ: {specialists_count} СПЕЦИАЛИСТОВ", fontsize=12, fontweight='bold')
axes[1, 0].axis('off')
# Добавляем текстовую легенду под хитмапом
legend_text = "Цвета: Зеленый (Свободен) → Желтый (3-5 мин) → Красный (8+ мин)"
axes[1, 0].text(0.5, -0.1, legend_text, ha='center', transform=axes[1, 0].transAxes, fontsize=10)
# --- 4. РАЗДЕЛЕННЫЕ ОЧЕРЕДИ И СТАТИСТИКА ---
ax_stat = axes[1, 1]
ax_stat.clear()
ax_stat.axis('off')
# Цвета для очередей (краснеют, если очередь > 50)
q_mod_color = '#991b1b' if data['queue'] > 50 else '#166534'
q_biz_color = '#991b1b' if data.get('business_queue', 0) > 50 else '#1e293b'
ax_stat.text(0.25, 0.9, "ОЧЕРЕДЬ\n(МОДЕЛИ)", fontsize=10, ha='center', fontweight='bold')
ax_stat.text(0.25, 0.78, f"{data['queue']}", fontsize=26, ha='center', fontweight='bold', color=q_mod_color)
ax_stat.text(0.75, 0.9, "ОЧЕРЕДЬ\n(БИЗНЕС ПРАВИЛА)", fontsize=10, ha='center', fontweight='bold')
ax_stat.text(0.75, 0.78, f"{data.get('business_queue', 0)}", fontsize=26, ha='center', fontweight='bold',
color=q_biz_color)
# Сводная таблица
cum = data['cumulative']
stats_text = (
f"Итоговые показатели к {data['time_str']}\n"
f"--------------------------------------\n"
f"ОБРАБОТАНО ВСЕГО: {cum['total_processed']}\n"
f"Авто-одобрено: {cum['auto_approved']}\n"
f"Авто-отказы: {cum['auto_declined']}\n"
f"Ручной разбор (модель): {cum['manual_processed']}\n"
f"Ручной разбор (бизнес правила): {cum['business_manual_processed']}\n"
f"--------------------------------------\n"
f"Используемая модель: {second_model_name}"
)
ax_stat.text(0.5, 0.3, stats_text, fontsize=10, fontfamily='monospace',
ha='center', va='center', transform=ax_stat.transAxes,
bbox=dict(facecolor='#f8f9fa', alpha=1, boxstyle='round,pad=1', edgecolor='#dee2e6'))
return axes.flatten()
ani = animation.FuncAnimation(fig, update, frames=len(frames), interval=1000 / fps)
tmp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4')
writer = animation.FFMpegWriter(fps=fps, bitrate=2000, extra_args=['-vcodec', 'libx264', '-pix_fmt', 'yuv420p'])
ani.save(tmp_file.name, writer=writer)
return tmp_file.name |