Zirok05 commited on
Commit
e191b9b
·
verified ·
1 Parent(s): 502e367

Update app/simulation/visualization/animation.py

Browse files
app/simulation/visualization/animation.py CHANGED
@@ -1,7 +1,9 @@
1
- import plotly.graph_objects as go
2
- from plotly.subplots import make_subplots
 
 
3
  import numpy as np
4
-
5
 
6
  def minutes_to_time(minutes, start_time="00:00"):
7
  start_hour, start_min = map(int, start_time.split(':'))
@@ -10,148 +12,6 @@ def minutes_to_time(minutes, start_time="00:00"):
10
  minute = total_minutes % 60
11
  return f"{hour:02d}:{minute:02d}"
12
 
13
-
14
- def create_animation_frame_plotly(frame_data, specialists_count, second_model_name="XGBoost"):
15
- # Фиксированная ось X для графиков
16
- time_ticks = list(range(0, 1441, 180))
17
- time_labels = [minutes_to_time(t, "00:00") for t in time_ticks]
18
-
19
- fig = make_subplots(
20
- rows=3, cols=2,
21
- subplot_titles=('📈 Динамика входящего потока', '⚙️ Загрузка специалистов (%)',
22
- '👥 МОНИТОРИНГ РАБОТЫ СПЕЦИАЛИСТОВ', '',
23
- '📊 Сводная статистика обработки', '🎯 Оперативные показатели'),
24
- specs=[
25
- [{'type': 'scatter'}, {'type': 'scatter'}],
26
- [{'type': 'heatmap', 'colspan': 2}, None],
27
- [{'type': 'table'}, {'type': 'scatter'}]
28
- ],
29
- row_heights=[0.25, 0.40, 0.35],
30
- vertical_spacing=0.1,
31
- )
32
-
33
- # --- РЯД 1: ГРАФИКИ ---
34
- inflow_h = frame_data.get('inflow_history', [])
35
- load_h = frame_data.get('load_history', [])
36
-
37
- fig.add_trace(go.Scatter(y=inflow_h, fill='tozeroy', line=dict(color='#4361ee', width=2)), row=1, col=1)
38
- fig.add_trace(go.Scatter(y=[l * 100 for l in load_h], fill='tozeroy', line=dict(color='#4cc9f0', width=2)), row=1,
39
- col=2)
40
-
41
- for col in [1, 2]:
42
- fig.update_xaxes(range=[0, 1440], tickvals=time_ticks, ticktext=time_labels, row=1, col=col)
43
- fig.update_yaxes(rangemode="tozero", row=1, col=col)
44
-
45
- # --- РЯД 2: HEATMAP (Строго 20 ячеек в ширину) ---
46
- states = np.array(frame_data['specialist_states'])
47
- cols = 20
48
- rows = int(np.ceil(specialists_count / cols))
49
-
50
- # Создаем матрицу, заполненную None (или NaN), чтобы пустые места не красились
51
- z_matrix = np.full((rows, cols), np.nan)
52
- for i, val in enumerate(states):
53
- r, c = divmod(i, cols)
54
- # Мапим значения: 0 -> 0.1 (голубой), 1-3 -> 0.4 (зеленый) и т.д.
55
- if val == 0:
56
- z_matrix[r, c] = 0.1
57
- elif val <= 3:
58
- z_matrix[r, c] = 0.4
59
- elif val <= 7:
60
- z_matrix[r, c] = 0.7
61
- else:
62
- z_matrix[r, c] = 1.0
63
-
64
- # Настраиваем цвета: NaN будет прозрачным/фоновым
65
- colorscale = [
66
- [0.0, '#66ccff'], # Свободен (0)
67
- [0.4, '#4ade80'], # 1-3 мин
68
- [0.7, '#facc15'], # 4-7 мин
69
- [1.0, '#f87171'] # 8+ мин
70
- ]
71
-
72
- fig.add_trace(go.Heatmap(
73
- z=z_matrix, colorscale=colorscale, showscale=False,
74
- xgap=2, ygap=2, zmin=0, zmax=1, hoverinfo='none'
75
- ), row=2, col=1)
76
-
77
- # Легенда над хитмапом
78
- free = sum(1 for t in states if t <= 0)
79
- legend = (f"Свободно: <b>{free}</b> | <span style='color:#66ccff'>■</span> Свободен "
80
- f"<span style='color:#4ade80'>■</span> 1-3м <span style='color:#facc15'>■</span> 4-7м "
81
- f"<span style='color:#f87171'>■</span> 8м+")
82
- fig.add_annotation(text=legend, xref="paper", yref="paper", x=0.5, y=0.70, showarrow=False, font=dict(size=14))
83
-
84
- # --- РЯД 3: ТАБЛИЦА (Формальная) ---
85
- cum = frame_data['cumulative']
86
- fig.add_trace(go.Table(
87
- header=dict(values=['Параметр', 'Значение'], fill_color='#1e293b', font=dict(color='white', size=15),
88
- height=35),
89
- cells=dict(values=[
90
- ['✅ Авто-одобрено', '❌ Авто-отказы', '👤 На рассмотрении (Manual)', '<b>ИТОГО ОБРАБОТАНО</b>'],
91
- [cum['auto_approved'], cum['auto_declined'],
92
- cum['manual_processed'] + cum['business_manual_processed'], f"<b>{cum['total_processed']}</b>"]
93
- ], align='left', font=dict(size=14), height=35, fill_color='#f8f9fa')
94
- ), row=3, col=1)
95
-
96
- # --- ОПЕРАТИВНЫЕ ПОКАЗАТЕЛИ (Крупный заголовок) ---
97
- q_models = frame_data['queue'] # Очередь к спецам
98
- q_business = frame_data.get('business_queue', 0) # Бизнес-очередь
99
-
100
- # Расчет ожидания только для очереди моделей (как на левом графике)
101
- avg_w = frame_data.get('avg_wait', 0)
102
-
103
- status_card = (
104
- f"<span style='font-size:22px; font-weight:bold;'>МОНИТОРИН��</span><br><br>"
105
- f"<span style='background-color:#dcfce7; color:#166534; padding:8px; border-radius:5px;'>"
106
- f"<b>👤 ОЧЕРЕДЬ (СПЕЦ): {q_models}</b></span><br><br>"
107
- f"<span style='font-size:18px; color:#666;'>"
108
- f"⚙️ Бизнес-правила: {q_business}</span><br><br>"
109
- f"🕒 Время: <b>{frame_data['time_str']}</b><br>"
110
- f"⏳ Ожидание: <b>{avg_w:.1f} мин</b>"
111
- )
112
-
113
- fig.add_trace(go.Scatter(x=[0], y=[0], mode='text', text=[status_card], textfont=dict(size=16)), row=3, col=2)
114
-
115
- # Очистка осей
116
- fig.update_xaxes(visible=False, row=2, col=1);
117
- fig.update_yaxes(visible=False, row=2, col=1)
118
- fig.update_xaxes(visible=False, row=3, col=2);
119
- fig.update_yaxes(visible=False, row=3, col=2)
120
-
121
- # Фиксируем оси, чтобы график не "дышал" (это главная причина мерцания)
122
- fig.update_yaxes(range=[0, 60], row=1, col=1) # Замени 60 на твой макс. поток
123
- fig.update_yaxes(range=[0, 105], row=1, col=2) # Загрузка всегда до 100%
124
-
125
- fig.update_layout(
126
- height=950,
127
- margin=dict(t=80, b=40, l=50, r=50),
128
- template="plotly_white",
129
- showlegend=False,
130
- # ОТКЛЮЧАЕМ анимации переходов, которые создают эффект мигания
131
- transition_duration=0,
132
- hovermode=False
133
- )
134
-
135
- # Это заставит Plotly обновлять только данные, не перерисовывая всё полотно
136
- fig.layout.datarevision = frame_data['time']
137
- return fig
138
-
139
-
140
- from matplotlib.animation import FFMpegWriter
141
-
142
- import matplotlib.pyplot as plt
143
- import matplotlib.animation as animation
144
- import tempfile
145
- import numpy as np
146
-
147
- import matplotlib.pyplot as plt
148
- import matplotlib.animation as animation
149
- import tempfile
150
- import numpy as np
151
- import os
152
-
153
-
154
- # Внести изменения в функцию create_simulation_video в animation.py
155
  def create_simulation_video(frames, specialists_count, second_model_name, fps=24):
156
  if not frames:
157
  return None
@@ -185,7 +45,7 @@ def create_simulation_video(frames, specialists_count, second_model_name, fps=24
185
  axes[0, 1].set_ylim(0, 110)
186
  axes[0, 1].set_title(f"ЗАГРУЖЕННОСТЬ СПЕЦИАЛИСТОВ %: {y_load[-1]:.1f}%", fontsize=12, fontweight='bold')
187
 
188
- # 3. HEATMAP И ЛЕГЕНДА (Возвращаем информативность)
189
  states = np.array(data['specialist_states'])
190
  cols = 20
191
  rows = int(np.ceil(specialists_count / cols))
@@ -210,7 +70,6 @@ def create_simulation_video(frames, specialists_count, second_model_name, fps=24
210
  q_mod_color = '#991b1b' if data['queue'] > 50 else '#166534'
211
  q_biz_color = '#991b1b' if data.get('business_queue', 0) > 50 else '#1e293b'
212
 
213
- # Две надписи очередей сверху
214
  ax_stat.text(0.25, 0.9, "ОЧЕРЕДЬ\n(МОДЕЛИ)", fontsize=10, ha='center', fontweight='bold')
215
  ax_stat.text(0.25, 0.78, f"{data['queue']}", fontsize=26, ha='center', fontweight='bold', color=q_mod_color)
216
 
@@ -218,7 +77,7 @@ def create_simulation_video(frames, specialists_count, second_model_name, fps=24
218
  ax_stat.text(0.75, 0.78, f"{data.get('business_queue', 0)}", fontsize=26, ha='center', fontweight='bold',
219
  color=q_biz_color)
220
 
221
- # Сводная таблица ниже
222
  cum = data['cumulative']
223
  stats_text = (
224
  f"Итоговые показатели к {data['time_str']}\n"
 
1
+ from matplotlib.animation import FFMpegWriter
2
+ import matplotlib.pyplot as plt
3
+ import matplotlib.animation as animation
4
+ import tempfile
5
  import numpy as np
6
+ import os
7
 
8
  def minutes_to_time(minutes, start_time="00:00"):
9
  start_hour, start_min = map(int, start_time.split(':'))
 
12
  minute = total_minutes % 60
13
  return f"{hour:02d}:{minute:02d}"
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  def create_simulation_video(frames, specialists_count, second_model_name, fps=24):
16
  if not frames:
17
  return None
 
45
  axes[0, 1].set_ylim(0, 110)
46
  axes[0, 1].set_title(f"ЗАГРУЖЕННОСТЬ СПЕЦИАЛИСТОВ %: {y_load[-1]:.1f}%", fontsize=12, fontweight='bold')
47
 
48
+ # 3. HEATMAP И ЛЕГЕНДА
49
  states = np.array(data['specialist_states'])
50
  cols = 20
51
  rows = int(np.ceil(specialists_count / cols))
 
70
  q_mod_color = '#991b1b' if data['queue'] > 50 else '#166534'
71
  q_biz_color = '#991b1b' if data.get('business_queue', 0) > 50 else '#1e293b'
72
 
 
73
  ax_stat.text(0.25, 0.9, "ОЧЕРЕДЬ\n(МОДЕЛИ)", fontsize=10, ha='center', fontweight='bold')
74
  ax_stat.text(0.25, 0.78, f"{data['queue']}", fontsize=26, ha='center', fontweight='bold', color=q_mod_color)
75
 
 
77
  ax_stat.text(0.75, 0.78, f"{data.get('business_queue', 0)}", fontsize=26, ha='center', fontweight='bold',
78
  color=q_biz_color)
79
 
80
+ # Сводная таблица
81
  cum = data['cumulative']
82
  stats_text = (
83
  f"Итоговые показатели к {data['time_str']}\n"