spc-lib 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
spc_lib/__init__.py ADDED
@@ -0,0 +1,17 @@
1
+ from .charts.variables import XBarRChart, XBarSChart, IMRChart
2
+ from .charts.attributes import PChart, CChart
3
+ from .charts.time_weighted import CUSUMChart, CUSUMVarianceChart, EWMAChart
4
+ from .visualization.plotly_engine import plot_control_chart, plot_rules_violations
5
+
6
+ __all__ = [
7
+ "XBarRChart",
8
+ "XBarSChart",
9
+ "IMRChart",
10
+ "PChart",
11
+ "CChart",
12
+ "CUSUMChart",
13
+ "CUSUMVarianceChart",
14
+ "EWMAChart",
15
+ "plot_control_chart",
16
+ "plot_rules_violations"
17
+ ]
@@ -0,0 +1,14 @@
1
+ from .variables import XBarRChart, XBarSChart, IMRChart
2
+ from .attributes import PChart, CChart
3
+ from .time_weighted import CUSUMChart, CUSUMVarianceChart, EWMAChart
4
+
5
+ __all__ = [
6
+ "XBarRChart",
7
+ "XBarSChart",
8
+ "IMRChart",
9
+ "PChart",
10
+ "CChart",
11
+ "CUSUMChart",
12
+ "CUSUMVarianceChart",
13
+ "EWMAChart"
14
+ ]
@@ -0,0 +1,147 @@
1
+ import numpy as np
2
+ from src.spc_lib.core.base_chart import BaseControlChart
3
+
4
+ class PChart(BaseControlChart):
5
+ """
6
+ p-карта (контрольная карта доли дефектных изделий).
7
+ """
8
+ def __init__(self, data, n_fixed=None, datetimes=None, target=None, usl=None, lsl=None):
9
+ super().__init__(data, datetimes, target, usl, lsl)
10
+ self.n_fixed = n_fixed
11
+ self.main_label = "p-карта: Доля дефектных единиц"
12
+ self.disp_label = None
13
+
14
+ self.p_values = None
15
+ self.n_values = None
16
+ self.p_bar = None
17
+ self.n_bar = None
18
+
19
+ def fit(self, baseline_mask=None, method='classic', use_average_n=False):
20
+ """
21
+ use_average_n: bool
22
+ Если True, для переменного размера выборки будут вычислены
23
+ константные (прямые) границы на основе среднего размера выборки (n_bar).
24
+ """
25
+ if baseline_mask is None:
26
+ baseline_mask = np.ones(self.n_subgroups, dtype=bool)
27
+
28
+ if self.n_fixed is not None:
29
+ if self.data.ndim == 2 and self.data.shape[1] == 1:
30
+ self.p_values = self.data.flatten()
31
+ else:
32
+ self.p_values = np.asarray(self.data).flatten()
33
+ self.n_values = np.full(len(self.p_values), self.n_fixed)
34
+ else:
35
+ if self.data.ndim == 2 and self.data.shape[1] == 2:
36
+ self.p_values = self.data[:, 0] / self.data[:, 1]
37
+ self.n_values = self.data[:, 1]
38
+ else:
39
+ raise ValueError("При переменном объеме выборки data должно быть 2D массивом: [x, n]")
40
+
41
+ base_p = self.p_values[baseline_mask]
42
+ base_n = self.n_values[baseline_mask]
43
+
44
+ if self.target is not None:
45
+ self.p_bar = self.target
46
+ else:
47
+ total_defects = np.sum(base_p * base_n)
48
+ total_n = np.sum(base_n)
49
+ self.p_bar = total_defects / total_n if total_n > 0 else np.mean(base_p)
50
+
51
+ self.n_bar = np.mean(base_n)
52
+
53
+ if method == 'classic':
54
+ self.cl_main = self.p_bar
55
+
56
+ # --- ИЗМЕНЕНИЕ ЗДЕСЬ ---
57
+ # Если n_fixed задан ИЛИ пользователь попросил усредненные границы
58
+ if self.n_fixed is not None or use_average_n:
59
+ n_eval = self.n_fixed if self.n_fixed is not None else self.n_bar
60
+
61
+ sigma_p = np.sqrt(self.p_bar * (1 - self.p_bar) / n_eval)
62
+ self.ucl_main = self.p_bar + 3 * sigma_p
63
+ self.lcl_main = max(0, self.p_bar - 3 * sigma_p)
64
+ else:
65
+ # Переменные (ступенчатые) границы
66
+ self.ucl_main = np.zeros(len(self.p_values))
67
+ self.lcl_main = np.zeros(len(self.p_values))
68
+
69
+ for i in range(len(self.p_values)):
70
+ n_i = self.n_values[i]
71
+ sigma_p_i = np.sqrt(self.p_bar * (1 - self.p_bar) / n_i)
72
+ self.ucl_main[i] = self.p_bar + 3 * sigma_p_i
73
+ self.lcl_main[i] = max(0, self.p_bar - 3 * sigma_p_i)
74
+
75
+ self.sigma_est = np.sqrt(self.p_bar * (1 - self.p_bar))
76
+
77
+ elif method == 'percentiles':
78
+ self.cl_main = np.median(base_p)
79
+ self.ucl_main = np.percentile(base_p, 99.865)
80
+ self.lcl_main = np.percentile(base_p, 0.135)
81
+ self.sigma_est = np.std(base_p, ddof=1)
82
+
83
+ else:
84
+ raise ValueError(f"Неизвестный метод: {method}")
85
+
86
+ self.stat_main = self.p_values
87
+ self.cl_disp = None
88
+ self.ucl_disp = None
89
+ self.lcl_disp = None
90
+
91
+ return self
92
+
93
+ class CChart(BaseControlChart):
94
+ """
95
+ c-карта (контрольная карта числа дефектов).
96
+
97
+ Используется для мониторинга количества дефектов на единицу продукции
98
+ при постоянном объеме выборки.
99
+ """
100
+
101
+ def __init__(self, data, datetimes=None, target=None, usl=None, lsl=None):
102
+ super().__init__(data, datetimes, target, usl, lsl)
103
+ self.main_label = "c-карта: Число дефектов"
104
+ self.disp_label = None
105
+
106
+ self.c_values = None
107
+ self.c_bar = None
108
+
109
+ def fit(self, baseline_mask=None, method='classic'):
110
+ if baseline_mask is None:
111
+ baseline_mask = np.ones(self.n_subgroups, dtype=bool)
112
+
113
+ if self.data.ndim == 2 and self.data.shape[1] == 1:
114
+ self.c_values = self.data.flatten()
115
+ else:
116
+ self.c_values = np.asarray(self.data).flatten()
117
+
118
+ base_c = self.c_values[baseline_mask]
119
+
120
+ if self.target is not None:
121
+ self.c_bar = self.target
122
+ else:
123
+ self.c_bar = np.mean(base_c)
124
+
125
+ if method == 'classic':
126
+ self.cl_main = self.c_bar
127
+ sigma_c = np.sqrt(self.c_bar)
128
+ self.ucl_main = self.c_bar + 3 * sigma_c
129
+ # LCL не может быть меньше 0
130
+ self.lcl_main = max(0, self.c_bar - 3 * sigma_c)
131
+ self.sigma_est = sigma_c
132
+
133
+ elif method == 'percentiles':
134
+ self.cl_main = np.median(base_c)
135
+ self.ucl_main = np.percentile(base_c, 99.865)
136
+ self.lcl_main = np.percentile(base_c, 0.135)
137
+ self.sigma_est = np.std(base_c, ddof=1)
138
+
139
+ else:
140
+ raise ValueError(f"Неизвестный метод: {method}")
141
+
142
+ self.stat_main = self.c_values
143
+ self.cl_disp = None
144
+ self.ucl_disp = None
145
+ self.lcl_disp = None
146
+
147
+ return self
@@ -0,0 +1,412 @@
1
+ import numpy as np
2
+ import warnings
3
+ from src.spc_lib.core.base_chart import BaseControlChart
4
+
5
+
6
+ class CUSUMChart(BaseControlChart):
7
+ """
8
+ Кумулятивная сумма (CUSUM) - контрольная карта для обнаружения
9
+ малых сдвигов процесса.
10
+
11
+ Параметры:
12
+ ----------
13
+ data : array-like
14
+ Данные подгрупп (2D массив) или индивидуальные значения (1D)
15
+ datetimes : array-like, optional
16
+ Временные метки
17
+ target : float, optional
18
+ Целевое значение процесса
19
+ usl, lsl : float, optional
20
+ Верхняя/нижняя границы спецификации
21
+ h : float, default=5
22
+ Параметр принятия решения (расстояние от нулевой линии до границы)
23
+ k : float, default=0.5
24
+ Параметр ссылочного значения (обычно 0.5 для стандартного CUSUM)
25
+ std_est : float, optional
26
+ Оценка стандартного отклонения. Если None, рассчитывается из данных.
27
+ """
28
+
29
+ def __init__(self, data, datetimes=None, target=None, usl=None, lsl=None,
30
+ h=5.0, k=0.5, std_est=None):
31
+ super().__init__(data, datetimes, target, usl, lsl)
32
+ self.h = h
33
+ self.k = k
34
+ self.std_est = std_est
35
+ self.main_label = "CUSUM: Кумулятивная сумма (верхняя/нижняя)"
36
+ self.disp_label = None
37
+
38
+ self.cusum_upper = None
39
+ self.cusum_lower = None
40
+ self.sigma_est = None
41
+
42
+ def fit(self, baseline_mask=None, method='classic'):
43
+ """
44
+ Методы:
45
+ - 'classic': стандартный CUSUM с параметрами h и k
46
+ - 'percentiles': эмпирические пределы на основе процентилей
47
+ - 'made': робастный CUSUM на основе MAD
48
+ """
49
+ if baseline_mask is None:
50
+ baseline_mask = np.ones(self.n_subgroups, dtype=bool)
51
+
52
+ if self.data.ndim == 2:
53
+ if self.subgroup_size == 1:
54
+ x = self.data.flatten()
55
+ n = 1
56
+ else:
57
+ x = np.mean(self.data, axis=1)
58
+ n = self.subgroup_size
59
+ else:
60
+ warnings.warn(
61
+ "Передан 1D массив. Рекомендуется использовать 2D формат: data.reshape(-1, 1)",
62
+ UserWarning
63
+ )
64
+ x = self.data
65
+ n = 1
66
+
67
+ base_x = x[baseline_mask]
68
+
69
+ if self.std_est is None:
70
+ if n > 1 and method == 'classic':
71
+ r = np.ptp(self.data[baseline_mask], axis=1)
72
+ d2 = {2: 1.128, 3: 1.693, 4: 2.059, 5: 2.326,
73
+ 6: 2.534, 7: 2.704, 8: 2.847, 9: 2.970, 10: 3.078}
74
+ sigma_est = np.mean(r) / d2.get(n, 1.128)
75
+ else:
76
+ sigma_est = np.std(base_x, ddof=1)
77
+ else:
78
+ sigma_est = self.std_est
79
+
80
+ if self.target is not None:
81
+ target = self.target
82
+ elif method == 'classic':
83
+ target = np.mean(base_x)
84
+ else:
85
+ target = np.median(base_x)
86
+
87
+ # Нормировка
88
+ if sigma_est > 0:
89
+ z = (x - target) / sigma_est
90
+ else:
91
+ z = x - target
92
+ warnings.warn("Сигма равна 0, используется ненормированная статистика", UserWarning)
93
+
94
+ self.cusum_upper = np.zeros(len(x))
95
+ self.cusum_lower = np.zeros(len(x))
96
+
97
+ if method == 'classic':
98
+ for i in range(1, len(x)):
99
+ self.cusum_upper[i] = max(0, self.cusum_upper[i-1] + z[i] - self.k)
100
+ self.cusum_lower[i] = max(0, self.cusum_lower[i-1] - z[i] - self.k)
101
+
102
+ self.cl_main = 0
103
+ self.ucl_main = self.h
104
+ self.lcl_main = self.h
105
+
106
+ elif method == 'percentiles':
107
+ for i in range(1, len(x)):
108
+ self.cusum_upper[i] = max(0, self.cusum_upper[i-1] + z[i])
109
+ self.cusum_lower[i] = max(0, self.cusum_lower[i-1] - z[i])
110
+
111
+ base_cusum_upper = self.cusum_upper[baseline_mask]
112
+ base_cusum_lower = self.cusum_lower[baseline_mask]
113
+
114
+ self.cl_main = 0
115
+ self.ucl_main = np.percentile(base_cusum_upper, 99.865)
116
+ self.lcl_main = np.percentile(base_cusum_lower, 99.865)
117
+
118
+ elif method == 'made':
119
+ mad = np.median(np.abs(base_x - np.median(base_x)))
120
+ robust_sigma = 1.4826 * mad if mad > 0 else 1e-10
121
+
122
+ z_robust = (x - target) / robust_sigma if robust_sigma > 0 else x - target
123
+
124
+ for i in range(1, len(x)):
125
+ self.cusum_upper[i] = max(0, self.cusum_upper[i-1] + z_robust[i] - self.k)
126
+ self.cusum_lower[i] = max(0, self.cusum_lower[i-1] - z_robust[i] - self.k)
127
+
128
+ self.cl_main = 0
129
+ self.ucl_main = self.h
130
+ self.lcl_main = self.h
131
+ sigma_est = robust_sigma
132
+
133
+ else:
134
+ raise ValueError(f"Неизвестный метод: {method}")
135
+
136
+ self.target = target
137
+ self.sigma_est = sigma_est
138
+ self.stat_main = x
139
+
140
+ return self
141
+
142
+ def get_cusum_stats(self):
143
+ """Возвращает верхнюю и нижнюю CUSUM статистики"""
144
+ return self.cusum_upper, self.cusum_lower
145
+
146
+
147
+ class EWMAChart(BaseControlChart):
148
+ """
149
+ Экспоненциально взвешенное скользящее среднее (EWMA)
150
+ - контрольная карта для обнаружения малых сдвигов процесса.
151
+
152
+ Параметры:
153
+ ----------
154
+ data : array-like
155
+ Данные подгрупп (2D массив) или индивидуальные значения (1D)
156
+ datetimes : array-like, optional
157
+ Временные метки
158
+ target : float, optional
159
+ Целевое значение процесса
160
+ usl, lsl : float, optional
161
+ Верхняя/нижняя границы спецификации
162
+ lambda_ : float, default=0.2
163
+ Весовой коэффициент (0 < lambda <= 1)
164
+ L : float, default=3
165
+ Коэффициент ширины контрольных границ
166
+ std_est : float, optional
167
+ Оценка стандартного отклонения. Если None, рассчитывается из данных.
168
+ """
169
+
170
+ def __init__(self, data, datetimes=None, target=None, usl=None, lsl=None,
171
+ lambda_=0.2, L=3, std_est=None):
172
+ super().__init__(data, datetimes, target, usl, lsl)
173
+ self.lambda_ = lambda_
174
+ self.L = L
175
+ self.std_est = std_est
176
+ self.main_label = "EWMA: Экспоненциально взвешенное скользящее среднее"
177
+ self.disp_label = None
178
+
179
+ self.ewma_values = None
180
+ self.ewma_sigma = None
181
+ self.sigma_est = None
182
+
183
+ def fit(self, baseline_mask=None, method='classic'):
184
+ """
185
+ Методы:
186
+ - 'classic': стандартная EWMA с фиксированными пределами
187
+ - 'percentiles': эмпирические пределы на основе процентилей
188
+ - 'made': робастная EWMA на основе MAD
189
+ """
190
+ if baseline_mask is None:
191
+ baseline_mask = np.ones(self.n_subgroups, dtype=bool)
192
+
193
+ # Извлечение данных
194
+ if self.data.ndim == 2:
195
+ if self.subgroup_size == 1:
196
+ x = self.data.flatten()
197
+ n = 1
198
+ else:
199
+ x = np.mean(self.data, axis=1)
200
+ n = self.subgroup_size
201
+ else:
202
+ warnings.warn(
203
+ "Передан 1D массив. Рекомендуется использовать 2D формат: data.reshape(-1, 1)",
204
+ UserWarning
205
+ )
206
+ x = self.data
207
+ n = 1
208
+
209
+ base_x = x[baseline_mask]
210
+
211
+ # Оценка стандартного отклонения
212
+ if self.std_est is None:
213
+ if n > 1 and method == 'classic':
214
+ r = np.ptp(self.data[baseline_mask], axis=1)
215
+ d2 = {2: 1.128, 3: 1.693, 4: 2.059, 5: 2.326,
216
+ 6: 2.534, 7: 2.704, 8: 2.847, 9: 2.970, 10: 3.078}
217
+ sigma_est = np.mean(r) / d2.get(n, 1.128)
218
+ else:
219
+ sigma_est = np.std(base_x, ddof=1)
220
+ else:
221
+ sigma_est = self.std_est
222
+
223
+ # Целевое значение
224
+ if self.target is not None:
225
+ target = self.target
226
+ elif method == 'classic':
227
+ target = np.mean(base_x)
228
+ else:
229
+ target = np.median(base_x)
230
+
231
+ lambda_ = self.lambda_
232
+ L = self.L
233
+
234
+ # Расчет EWMA
235
+ self.ewma_values = np.zeros(len(x))
236
+ self.ewma_values[0] = target
237
+
238
+ for i in range(1, len(x)):
239
+ self.ewma_values[i] = lambda_ * x[i] + (1 - lambda_) * self.ewma_values[i-1]
240
+
241
+ # Стандартное отклонение EWMA: σ * sqrt(λ/(2-λ) * (1 - (1-λ)^(2i)))
242
+ weights = np.zeros(len(x))
243
+ for i in range(1, len(x) + 1):
244
+ weights[i-1] = np.sqrt(lambda_ / (2 - lambda_) * (1 - (1 - lambda_)**(2*i)))
245
+
246
+ self.ewma_sigma = sigma_est * weights
247
+
248
+ # Расчет границ в зависимости от метода
249
+ if method == 'classic':
250
+ self.cl_main = target
251
+ self.ucl_main = target + L * self.ewma_sigma
252
+ self.lcl_main = target - L * self.ewma_sigma
253
+
254
+ elif method == 'percentiles':
255
+ base_ewma = self.ewma_values[baseline_mask]
256
+ self.cl_main = np.median(base_ewma)
257
+ self.ucl_main = np.percentile(base_ewma, 99.865)
258
+ self.lcl_main = np.percentile(base_ewma, 0.135)
259
+
260
+ elif method == 'made':
261
+ mad = np.median(np.abs(base_x - np.median(base_x)))
262
+ robust_sigma = 1.4826 * mad if mad > 0 else 1e-10
263
+
264
+ self.ewma_sigma = robust_sigma * weights
265
+ self.cl_main = target
266
+ self.ucl_main = target + L * self.ewma_sigma
267
+ self.lcl_main = target - L * self.ewma_sigma
268
+ sigma_est = robust_sigma
269
+
270
+ else:
271
+ raise ValueError(f"Неизвестный метод: {method}")
272
+
273
+ self.target = target
274
+ self.sigma_est = sigma_est
275
+ self.stat_main = self.ewma_values
276
+
277
+ return self
278
+
279
+ def get_ewma_values(self):
280
+ """Возвращает EWMA значения и их стандартные отклонения"""
281
+ return self.ewma_values, self.ewma_sigma
282
+
283
+ class CUSUMVarianceChart(BaseControlChart):
284
+ """
285
+ CUSUM карта для контроля дисперсии/стандартного отклонения.
286
+ Использует преобразование Хокинса для мониторинга вариабельности процесса.
287
+
288
+ Параметры:
289
+ ----------
290
+ data : array-like
291
+ Данные подгрупп (2D массив) или индивидуальные значения (1D)
292
+ datetimes : array-like, optional
293
+ Временные метки
294
+ target_mean : float, optional
295
+ Целевое среднее процесса. Если None - оценивается из данных.
296
+ target_std : float, optional
297
+ Целевое стандартное отклонение. Если None - оценивается из данных.
298
+ usl, lsl : float, optional
299
+ Верхняя/нижняя границы спецификации
300
+ h : float, default=5
301
+ Параметр принятия решения
302
+ k : float, default=0.5
303
+ Параметр ссылочного значения
304
+ """
305
+
306
+ def __init__(self, data, datetimes=None, target_mean=None, target_std=None,
307
+ usl=None, lsl=None, h=5.0, k=0.5):
308
+ super().__init__(data, datetimes, None, usl, lsl) # target передаем как None, используем target_mean
309
+ self.target_mean = target_mean
310
+ self.target_std = target_std
311
+ self.h = h
312
+ self.k = k
313
+ self.main_label = "CUSUM для дисперсии (верхняя/нижняя)"
314
+ self.disp_label = None
315
+
316
+ self.cusum_upper = None
317
+ self.cusum_lower = None
318
+ self.v_values = None # Преобразованные значения v_i
319
+ self.sigma_est = None
320
+
321
+ def fit(self, baseline_mask=None, method='classic'):
322
+ """
323
+ Методы:
324
+ - 'classic': стандартный CUSUM для дисперсии с преобразованием Хокинса
325
+ - 'percentiles': эмпирические пределы на основе процентилей
326
+ """
327
+ if baseline_mask is None:
328
+ baseline_mask = np.ones(self.n_subgroups, dtype=bool)
329
+
330
+ # Извлечение данных
331
+ if self.data.ndim == 2:
332
+ if self.subgroup_size == 1:
333
+ x = self.data.flatten()
334
+ n = 1
335
+ else:
336
+ x = np.mean(self.data, axis=1)
337
+ n = self.subgroup_size
338
+ else:
339
+ warnings.warn(
340
+ "Передан 1D массив. Рекомендуется использовать 2D формат: data.reshape(-1, 1)",
341
+ UserWarning
342
+ )
343
+ x = self.data
344
+ n = 1
345
+
346
+ base_x = x[baseline_mask]
347
+
348
+ # Оценка среднего и стандартного отклонения
349
+ if self.target_mean is not None:
350
+ mu = self.target_mean
351
+ elif method == 'classic':
352
+ mu = np.mean(base_x)
353
+ else:
354
+ mu = np.median(base_x)
355
+
356
+ if self.target_std is not None:
357
+ sigma = self.target_std
358
+ elif method == 'classic' and n > 1:
359
+ r = np.ptp(self.data[baseline_mask], axis=1)
360
+ d2 = {2: 1.128, 3: 1.693, 4: 2.059, 5: 2.326,
361
+ 6: 2.534, 7: 2.704, 8: 2.847, 9: 2.970, 10: 3.078}
362
+ sigma = np.mean(r) / d2.get(n, 1.128)
363
+ else:
364
+ sigma = np.std(base_x, ddof=1)
365
+
366
+ # Стандартизованные значения
367
+ if sigma > 0:
368
+ y = (x - mu) / sigma
369
+ else:
370
+ y = x - mu
371
+ warnings.warn("Сигма равна 0, используется ненормированная статистика", UserWarning)
372
+
373
+ # Преобразование Хокинса для мониторинга дисперсии
374
+ # v_i = (sqrt(|y_i|) - 0.822) / 0.349
375
+ # При нормальном распределении v_i ~ N(0,1)
376
+ self.v_values = (np.sqrt(np.abs(y)) - 0.822) / 0.349
377
+
378
+ self.cusum_upper = np.zeros(len(x))
379
+ self.cusum_lower = np.zeros(len(x))
380
+
381
+ if method == 'classic':
382
+ # Стандартный CUSUM для v_i
383
+ for i in range(1, len(x)):
384
+ self.cusum_upper[i] = max(0, self.cusum_upper[i-1] + self.v_values[i] - self.k)
385
+ self.cusum_lower[i] = max(0, self.cusum_lower[i-1] - self.k - self.v_values[i])
386
+
387
+ self.cl_main = 0
388
+ self.ucl_main = self.h
389
+ self.lcl_main = self.h
390
+
391
+ elif method == 'percentiles':
392
+ for i in range(1, len(x)):
393
+ self.cusum_upper[i] = max(0, self.cusum_upper[i-1] + self.v_values[i])
394
+ self.cusum_lower[i] = max(0, self.cusum_lower[i-1] - self.v_values[i])
395
+
396
+ base_cusum_upper = self.cusum_upper[baseline_mask]
397
+ base_cusum_lower = self.cusum_lower[baseline_mask]
398
+
399
+ self.cl_main = 0
400
+ self.ucl_main = np.percentile(base_cusum_upper, 99.865)
401
+ self.lcl_main = np.percentile(base_cusum_lower, 99.865)
402
+
403
+ else:
404
+ raise ValueError(f"Неизвестный метод: {method}")
405
+
406
+ self.target = mu # Для совместимости с базовым классом
407
+ self.target_mean = mu
408
+ self.target_std = sigma
409
+ self.sigma_est = sigma
410
+ self.stat_main = x # Исходные данные для справки
411
+
412
+ return self