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