# Сравнение сигналов с CSV-эталоном Общее ядро `set_devices.signal_reference` и Qt-порт `set_devices.qt_ports.reference_comparison` сравнивают измеренную запись с эталоном. Они не импортируют приложение, не выбирают его каталоги и не требуют конкретного движка отрисовки. Ядро использует только стандартную библиотеку Python; Qt-порт поддерживает PySide6 и PySide2. ## Численный API `Comparison.analysis` содержит локальный параметрический отчёт: `summary`, `details`, `attention`, `findings`, `features`, `model_report`. Он рассчитывается `set_devices.signal_diagnostics` по тому же интервалу, временному сдвигу и допускам. Исходные метрики используют все общие отсчёты; объём выборки подбора моделей явно указан в `model_report`. Добавлено необязательное поле с пустым словарём по умолчанию: прежний конструктор `Comparison` с пятью аргументами сохраняет совместимость. Для одинаковых известных обозначений `Uce`, `Uge`, `Ic` анализ различает повторные полные переходы, усиленный звон Uce после выключения, отсутствие переключения, растянутые фронты и временной сдвиг. Общие причины включают смещение, шум и неопределённое изменение формы. Разные известные физические каналы, недостаточное число точек и неполное покрытие отражаются в выводе. На неизвестных каналах аппаратные гипотезы о снаббере и транзисторе не строятся. `set_devices.signal_fault_models` подбирает смещение, масштаб, дрейф, задержку, растяжение фронтов, постоянный уровень, кусочные прерывания и затухающий отклик RLC. `set_devices.signal_joint_models` добавляет локальную ВАХ `Uce=U0+Rdiff*Ic`, остаточный ток выключенного состояния и совпадение провалов управления/проводимости по трём каналам. `compare(..., circuit=..., channels=...)` принимает необязательные известные `inductance_h`, `capacitance_f`, `resistance_ohm` в СИ и словарь связанных каналов `dict(measured=..., reference=...)`. GUI формирует эти снимки сам. Однозначность физических имён и единиц проверяется до совместного анализа. Глобальные модели сравниваются по BIC общей обучающей выборки и RMSE на каждой третьей отложенной точке. JSON сохраняет всех конкурентов, число параметров, формулы, критерии и ограничения. BIC не интерпретируется как вероятность аппаратного отказа. Без известного элемента R/L/C не выдаются три произвольных номинала: доступны только LC и R/L последовательного эквивалента полюсов. `ComparisonAnalysis` в `qt_ports.reference_results` показывает одну строку и кнопку отчёта с таблицей моделей и подробным текстом по образцу Android GUI. Компонент используется и над исходным графиком, и в полной форме сравнения; изменение входов, чтение другого эталона и ошибки очищают прежний анализ. JSON-экспорт обоих видов включает `analysis` рядом с `metrics`. Ядро не использует сеть или обученную модель; пороги признаков и ограничения указаны в подробностях. `Trace(name, times, values)` содержит имя, строго возрастающие временные метки в миллисекундах и амплитуды в единицах канала. `trace_from_pairs` создаёт такой снимок из пар `(time_ms, value)`; необязательный `transform` применяет множитель или иное преобразование амплитуды. `read_reference(path, cancel=...)` потоково читает UTF-8 CSV с необязательным BOM. Первый столбец — `time_ms` либо `timestamp` с ISO-временем и явным часовым поясом. Остальные столбцы имеют непустые уникальные имена каналов. Поддерживаются запятая, точка с запятой и табуляция. Пустые ячейки и NaN сохраняются как пропуски; повторное время, бесконечности и повреждённые строки отклоняются. ```python from set_devices.signal_reference import compare, read_reference, trace_from_pairs # Ядро можно использовать без Qt и графика. measured = trace_from_pairs('U (V)', [(0., 2.), (1., 4.), (2., 6.)]) # Время задано в миллисекундах, амплитуда — в вольтах. reference = read_reference('reference.csv')['U (V)'] # Столбец выбирается по полному имени канала. result = compare(measured, reference, absolute_tolerance=.1, relative_tolerance_pct=2.) # Допуск равен 0,1 В плюс 2% модуля эталона. print(result.metrics['rmse']) # Показатель рассчитан по всем корректным точкам измерения в общем интервале. ``` `compare` возвращает `Comparison`: нормализованные кривые `measured` и `reference`, остаток `difference`, эталон на сетке остатка `expected` и словарь `metrics`. Статистика включает покрытие, пропуски, среднее смещение, MAE, RMSE, СКО без смещения, максимальную ошибку и выходы за допуск. Эталон линейно интерполируется на исходную сетку измерения без экстраполяции и соединения через NaN. Остаток равен измерению минус эталон. Допуск равен `absolute_tolerance + relative_tolerance_pct / 100 * abs(reference)`; равенство границе допуска не считается превышением. Экранная децимация не участвует в расчёте. По умолчанию начала записей совмещаются. `align_start=False` сохраняет различие абсолютных начал; положительный `shift_ms` сдвигает эталон вправо. `interval=(start_ms, stop_ms)` задаётся от начала измерения, обе границы включены. Callback `cancel()` прерывает подготовку, чтение и сравнение через `InterruptedError`. ## Наложение на существующий график Для добавления эталона в уже работающий график используйте `set_devices.qt_ports.reference_overlay.ReferenceOverlayPanel`. Это компактные параметры без canvas. Оригинальный график остаётся у потребителя и продолжает показывать измерение при загрузке и изменении эталона. Конструктор принимает `sources`, `parent`, `source_path` и необязательный `path_provider` с тем же смыслом, что в таблице ниже. `load_reference(path)` автоматически читает CSV, совмещает выбранные каналы и рассчитывает показатели в рабочем потоке. Отдельной кнопки запуска расчёта нет. Изменение канала, сдвига, совмещения начал или допусков обновляет результат автоматически. Сигнал `changed` сообщает потребителю, что `overlay()` вернёт актуальные спецификации и точки одной дополнительной кривой. Потребитель **добавляет** их к оригинальным рядам и общей легенде своего canvas. `RESULT_KEY` отличает эталон от измерений; оранжевая кривая имеет подпись `Эталон: <канал>`. Временные метки эталона уже находятся в исходной шкале измерения: начало оригинала не обнуляется. Потребитель применяет своё перебазирование короткой развёртки к обеим кривым одинаково. `set_sources(sources)` обновляет кадр прибора или преобразование амплитуд, сохраняя загруженный эталон. Сигнал `remove_requested` предлагает владельцу удалить дополнительный слой и параметры. `report()` и раскрываемая строка «Допуски и отчёт» предоставляют актуальную статистику и экспорт JSON. Если общей части времени нет, кривая остаётся доступна, а показатели заменяются объяснением отсутствия перекрытия. `close()` останавливает worker, не закрывая график потребителя. Истории, переданные в одном снимке `sources`, должны быть неизменяемыми; для нового кадра передайте новую историю через `set_sources`. Порт поддерживает PySide6 и PySide2 без импорта приложения. ### Подробные показатели рядом с исходным графиком `ReferenceOverlayPanel.results` — виджет `ComparisonResults` с полной таблицей всех 14 показателей: покрытие, пропуски, смещение, MAE, RMSE, СКО, максимум и его время, нормированная ошибка и выходы за допуск. Потребитель размещает его в существующей боковой области рядом с canvas; в SETGUI он открывается автоматически во вкладке «Отклонения и расчёты». Сигнал `results_requested` кнопки «Расчёты» позволяет вернуться к этой области после выбора обычной легенды. Флажок `results.delta_curve` добавляет в `overlay()` кривую с ключом `DIFFERENCE_KEY`. Её временные метки находятся в той же исходной шкале, что эталон и оригинал. Исходные кривые сохраняются, отдельный canvas не создаётся. Кнопка JSON рядом с таблицей экспортирует тот же актуальный результат. Изменение параметров и ошибки очищают устаревшие показатели. `comparison_rows(metrics)` из `qt_ports.reference_results` задаёт единые подписи, порядок и единицы как для этой таблицы, так и для прежнего диалога. ## Совместимые Qt-панель и отдельное окно `ReferenceComparisonPanel` — обычный `QWidget` для существующей страницы графика. Кнопка «К записи» испускает `back_requested`; потребитель сам скрывает панель и возвращает исходный вид. При повторном показе той же панели сохраняются эталон, параметры и рассчитанные показатели. `ReferenceComparisonDialog` — отдельный `QDialog` с тем же расчётом и настройками. Оба конструктора принимают: | Параметр | Контракт | | --- | --- | | `sources` | Последовательность пар `(source, points)`: `source.name` — имя, `source.display_value(value)` — преобразование амплитуды, `points` — полная неизменяемая история `(time_ms, value)` | | `parent` | Родительский QWidget; для встроенной панели передайте страницу графика | | `source_path` | Происхождение измерения для JSON-отчёта, по умолчанию пустая строка | | `plot_factory` | Обязательная фабрика без аргументов, возвращающая настроенный QWidget графика | | `series_factory` | Обязательная фабрика `factory(key, name, color=..., timestamps=..., values=...)`, создающая кривую потребителя | | `path_provider` | Необязательный callback `filename → начальный путь`; пустое имя используется для открытия эталона, `reference_comparison.json` — для отчёта | ```python from set_devices.qt_ports.reference_comparison import ReferenceComparisonPanel # Qt загружается только при импорте конкретного порта. panel = ReferenceComparisonPanel( # Один компонент обслуживает выбор каналов, параметры, фоновый расчёт и экспорт. sources=recording_channels, # Потребитель фиксирует полные истории и преобразование амплитуд до создания панели. parent=chart_page, # Сравнение размещается в существующей рабочей области. source_path=recording_filename, # Имя исходной записи попадёт в отчёт. plot_factory=create_comparison_canvas, # Адаптер создаёт график с единой шкалой Y для обеих кривых. series_factory=create_series, # Контейнеры кривых принадлежат используемому движку отрисовки. path_provider=recording_path, # Папки и правила хранения остаются политикой потребителя. ) # Все функции и объекты адаптера задаются приложением до этого вызова. chart_page.layout().addWidget(panel, 1) # Панель занимает свободное место страницы. panel.back_requested.connect(show_recording) # Обработчик скрывает сравнение и возвращает исходный график. ``` Canvas должен предоставлять изменяемый список `_series`, методы `reset_interaction()`, `_sync_bounds()`, стандартные `update()` и `close()`, флажки `axis_lock_x` и `axis_lock_y` с `setChecked()`, поле `axis_limits`, `visible_bounds.left/right` в миллисекундах и `viewport` типа `protocan.plot.Viewport`. `_sync_bounds()` задаёт конечный ненулевой диапазон X даже для пустого графика. Обе наложенные кривые используют **одну шкалу Y**; автоматическое разделение каналов на независимые оси отключает адаптер. Дополнительные кнопки canvas, подписи и правила снимков также настраиваются в `plot_factory`. Панель передаёт фабрике кривых полные массивы: их экранное сокращение остаётся ответственностью canvas. Временные поля интерфейса показывают микросекунды, ядро и JSON-отчёт используют миллисекунды. Явно различающиеся единицы в скобках имён каналов блокируют сравнение; автоматической конвертации нет. Истории и настройки источников нельзя менять, пока панель использует их снимок. При смене записи или преобразования канала закройте старую панель и создайте новую. `close()` встроенной панели отменяет и дожидается worker; уничтожение родителя также останавливает поток до удаления дочерних объектов. Отдельный диалог откладывает закрытие до завершения кооперативной отмены. `load_reference(path)` позволяет открыть эталон программно, `report()` возвращает актуальный словарь отчёта либо `None`. Изменение входных параметров очищает старые кривые и показатели до следующего расчёта. ## Проверки `python/tests/test_reference_comparison_port.py` проверяет внедрение стороннего canvas, преобразование амплитуд, отклонение и экспорт с внешним provider путей, а также API отдельного диалога. Его можно запустить из каталога `templates/python` командой `python -m unittest discover -s tests -p test_reference_comparison_port.py`. `python/tests/test_shared_library_boundary.py` проверяет независимость вычислительных модулей от GUI и Qt. Интеграционные проверки потребителя дополнительно покрывают полные истории, общий масштаб Y, отмену, смену записи, возврат к графику и полноэкранный режим.