ТранскрибаторТранскрибатор
Практическая инструкция

Сравнение двух CSV-расшифровок по ID: как найти добавленные, удалённые и изменённые реплики

Отчёт Python по стабильному replica_id. Отдельный контроль текста, говорящего и таймкода без ложных изменений из-за перестановки строк.

Транскрибатор
ЗАПИСЬПЛАН РАБОТЫПроверьте стабильность IDСравните поля двух версийРазберите изменения позаписиПроверить результатОТ СЛОВ К РЕЗУЛЬТАТУ
Работа с реальной записьюНаглядный учебный примерШаблон для копирования
1Проверьте стабильностьID2Сравните поля двухверсий3Разберите изменения позаписи
Стабильный ключ связывает одну реплику между версиями; значения сравниваются отдельно от расположения строк.

Когда сравнение по строкам недостаточно

После редактуры таблица может быть пересортирована, и обычный текстовый diff покажет перемещения как множество правок. Если у каждой реплики есть стабильный ID, сравнение по ключу отвечает на другой вопрос: какие записи появились, исчезли и какие поля изменились.

Используйте before.csv и after.csv одного интервью с колонками replica_id, speaker, timecode, text. Значение ID не должно меняться при исправлении текста. Не создавайте ключ заново по текущему порядковому номеру: после вставки строки это разрушит соответствие версий.

Текстовую основу можно подготовить через Транскрибатор. Согласованные идентификаторы и CSV-версии здесь формируются отдельно; не предполагается наличие встроенного отчёта сравнения по ID.

Проверьте ключи до сопоставления

Сначала установите, что оба файла относятся к одной временной шкале и одному набору участников. Одинаковый ID в разных интервью не означает одинаковую реплику. Для нескольких интервью нужен составной ключ, например interview_id вместе с replica_id.

Скрипт ниже отклоняет пустые и повторные ID и несогласованный заголовок. Он также останавливается при лишнем или недостающем поле. Без этих проверок запись словаря по повторному ключу могла бы незаметно заменить предыдущую реплику.

Не удаляйте пробелы из ID автоматически: это может объединить два разных значения. Если ключи действительно испорчены, сначала согласуйте исправление и сохраните таблицу соответствия. Документируйте изменение правила отдельно от сравнения текста.

Запустите отчёт по трём полям

Код использует стандартную библиотеку Python без pandas. Положите два согласованных файла рядом со скриптом. Чтение utf-8-sig допускает начальную метку BOM; newline="" сохраняет корректный разбор многострочных полей.

Объединение ключей даёт полный список записей. Для общего ID отдельно сравниваются speaker, timecode и text. Один ID может дать несколько строк changed, если исправлены несколько полей. Количество строк отчёта поэтому не равно числу затронутых реплик.

Для added и removed пример выводит текст, а поле field остаётся пустым. Полные значения других полей этих записей сверяйте в исходных CSV. Результат changes.csv создаётся в режиме x и не перезаписывает прежний отчёт.

Отделите техническую разницу от смысла

Пример сравнивает строки точно. Пробел, регистр, перенос или другое представление таймкода также считаются изменением. Это полезно для контроля передачи, но не означает смысловую правку. Не нормализуйте значения перед первым отчётом, иначе можно скрыть важную деталь.

Если нужно проверить общий порядок таймкодов, используйте отдельную сортировку по времени. Сравнение ключей не выявляет перестановку реплик при неизменных полях: порядок здесь намеренно не является частью сравнения.

При смене ключа старая запись попадёт в removed, новая — в added. Из отчёта нельзя автоматически заключить, что участник произнёс новую фразу или что две записи нужно склеить. Сверьте историю разметки и запись.

Разберите спорные изменения

Проверьте смены говорящего, цифры, отрицания и уточнения по аудио. Исправление «не согласен» на «согласен» технически выглядит как замена текста, но требует внимательной проверки смысла. Не принимайте after.csv за правильную версию только из-за более поздней даты.

Для сравнения непрерывного TXT полезен отчёт difflib. Для структурированных реплик сохраните changes.csv, две версии и решения по каждому спорному ID.

Учебный тест подтвердил три состояния: added, removed и changed, а также отсутствие изменений при перестановке неизменных записей. Это проверка механики, а не точности реального интервью. Для больших таблиц нужен отдельный процесс, поскольку обе версии загружаются в память.

Учебный пример

Учебный пример изменений по ID

Условные записи иллюстрируют значения статуса.

IDРазницаСтатус
r01Текст изменился, ID сохранёнchanged / text
r02Есть только в первой версииremoved
r03Есть только во второй версииadded
r04Строка переместилась, поля прежниеНе включается в отчёт
КлючОдна реплика междуверсиямиПроверить уникальностьПоляТекст, время, говорящийСравнить точноРешениеПричина измененияСверить с аудио
Сравнение по ID показывает различия значений и не выдаёт перестановку строк за новую реплику.

Не объединяйте added и removed только по сходству текста: сначала установите историю ID и источник записи.

Скрипт отчёта двух CSV-версий

Рабочая заготовкаВыделите и скопируйте
import csv
fields = ["replica_id", "speaker", "timecode", "text"]
def read(path):
    result = {}
    with open(path, encoding="utf-8-sig", newline="") as f:
        reader = csv.DictReader(f)
        if reader.fieldnames != fields:
            raise ValueError("Заголовки: " + path)
        for row in reader:
            if None in row or any(v is None for v in row.values()):
                raise ValueError("Число полей: " + path)
            key = row["replica_id"]
            if not key or key in result:
                raise ValueError("Пустой или повторный ID: " + path)
            result[key] = row
    return result
old, new = read("before.csv"), read("after.csv")
changes = []
for key in sorted(set(old) | set(new)):
    if key not in old:
        changes.append([key, "added", "", "", new[key]["text"]])
    elif key not in new:
        changes.append([key, "removed", "", old[key]["text"], ""])
    else:
        for field in fields[1:]:
            if old[key][field] != new[key][field]:
                changes.append([key, "changed", field,
                                old[key][field], new[key][field]])
with open("changes.csv", "x", encoding="utf-8", newline="") as f:
    writer = csv.writer(f)
    writer.writerow(["replica_id", "status", "field", "before", "after"])
    writer.writerows(changes)
print("Строк отчёта:", len(changes))

Частые вопросы

Перестановка строк попадёт в отчёт?

Нет, если ID и сравниваемые поля не изменились. Порядок проверяется отдельно.

Почему один ID встречается несколько раз?

Каждое изменённое поле даёт отдельную строку changed.

Поздняя версия обязательно правильная?

Нет. Спорные изменения проверяйте по записи и решениям редактора.

Можно сравнить разные интервью?

Только с согласованным составным ключом; пример рассчитан на одно интервью.