Как не упустить критические данные при анализе базы лиц причастных к экстремистской деятельности

Алексей Коноваленко
rnds
Published in
16 min readOct 26, 2020

В нашей компании мы работаем с финансовыми организациями, что накладывает определенную ответственность, так как наши клиенты должны удовлетворять ряду критериев.

Для того чтобы убедиться, что клиент компании законопослушный гражданин, клиент проходит проверки по различным электронным базам данных, одна из которых — “Перечень организаций и физических лиц, в отношении которых имеются сведения об их участии в экстремистской деятельности”, именно о структуре этой базы данных мы сегодня и поговорим.

Структура базы данных

Электронная база данных “Перечень организаций и физических лиц, в отношении которых имеются сведения об их участии в экстремистской деятельности”, а проще “база террористов”, распространяется в виде файла в формате DBF.

Повествование о структуре этого файла будет разделено на три основные части.

В первой мы поговорим о том, какое представление о DBF файле можно получить, работая только с описанием из официального документа.

Во второй части мы взглянем на файл собственными глазами и обзорно опишем увиденные особенности, а также сравним насколько описание файла в официальном документе соответствует реальной структуре.

И наконец, в третьей части мы подвергнем файл наиболее подробному анализу, в том числе и автоматическому, с целью ответить на вопросы, возникшие по ходу двух предыдущих частей и понять, как же нужно работать с данными, чтобы ничего не упустить.

Что говорит документ?

Чтобы начать разговор о работе с данной базой, первым делом стоит обратиться к официальному документу, который описывает весь процесс обработки данных от момента их получения до непосредственной работы с файлом. Нас же будет интересовать раздел описывающий структуру DBF файла Структура электронной базы данных “Перечень организаций и физических лиц, в отношении которых имеются сведения об их участии в экстремистской деятельности”

Итак, резюмируя документ, можно сказать следующее:

  • одна запись может быть разделена на насколько строк в DBF файле (так, максимальный размер поля в файле 254 символа, записи, поля которых превышают это значение, нужно разделять на несколько строк);
  • файл содержит поля “NAMEU”, “DESCRIPT”, “AMR”, “ADRESS”, “MR”, “DIRECTOR”, “FOUNDER” и “TERRTYPE”, предназначенные для хранения длинных строк. В дальнейшем будем называть такие поля текстовыми. Если значение текстового поля превышает 254 символа, оно будет разделено на несколько строк;
  • принадлежность к одной записи определяется полем “NUMBER” (рис. 1.1.1);
  • порядок строк для одной записи определяется полем “ROW-ID” (рис. 1.1.2)
  • формат даты в поле “GR” так же “ДДММГГГГ”. Но как следует из моего понимания документации, гарантированно это только для случая когда запись принадлежит физическому лицу (рис. 1.1.3);
  • формат даты в полях “CB-DATE” и “CE-DATE” это “ДДММГГГГ”, то есть 1-е января 1970 года, будет записано как “01011970” (держим этот факт в голове) (рис. 1.1.4);
  • составные названия полей записываются через “-”. Это поля “CB-DATE”, “CE-DATE” и “ROW-ID”.
рис. 1.1.1 — Описание поля “NUMBER”.
рис. 1.1.2 — Описание поля “ROW-ID”.
рис. 1.1.3 — Описание поля “GR”.
рис. 1.1.4 — Описание полей “CB-DATE” и “CE-DATE”.

Что видят глаза?

Описание в официальном документе само собой не даёт полного представления о данных, с которыми придётся работать, и мы открываем наш DBF файл при помощи LibreOffice Calc, в качестве кодировки выбираем DOS/OS2–866. В этом разделе мы поговорим о том, какую информацию о файле мы можем извлечь просто взглянув на него, ещё не имея опыта автоматической обработки, какие вопросы это может вызвать и какие ошибки породить.

Стоит отметить, что DBF файл с данными об организациях и лицах причастных к экстремистской деятельности распространяется между финансовыми организациями, и в открытом доступе его нет, а бесплатный показ закрытых данных противоречит моим внутренним убеждениям:-) Поэтому, в дальнейшем, для обработки скриншотов, будут применены высокие технологии, позволяющие обезличить данные, но не в ущерб информативности.

Итак, приступим. Открыв файл, видим:

В полях для одной и той же записи присутствуют дубликаты. Существует одна запись (множество строк из DBF файла с одинаковым номером в поле “NUMBER”) и у разных частей этой записи может быть совершенно одинаковое значение поля “NAMEU”, а может и не быть (рис. 1.2.1).

рис. 1.2.1 — Дубликаты в поле “NANEU”.

Описанный выше случай выглядит довольно обычно, но бывают и более интересные. Взгляните на рисунок 1.2.2, эта запись примечательна тем, что не просто имеет дубликаты в поле “NAMEU”, а тем, что дубликаты чередуются с пустыми строками, причём эти части одной записи расположены в правильном порядке (от более ранней к более поздней), о чём свидетельствуют значения в поле “ROW-ID” (рис. 1.2.3).

рис. 1.2.2 — Дубликаты в поле “NAMEU” чередующиеся в пустыми значениями.
рис. 1.2.3 — Значения поля “ROW-ID”.

Отметим, что это проблема не только поля “NAMEU”, дубликаты встречаются и в других текстовых полях.

Некоторые поля зачастую пусты, а в части полей видеть данные пока не доводилось, например в поле “CE_DATE”. Просто обозначим это как факт с которым приходится жить.

Значения в полях могут меняться. Как видно на рисунке 1.2.4 не все поля заполнены одинаково, хотя и принадлежат одной записи, и идут в отсортированном порядке. И если поле “NAMEU” не вызывает беспокойства, то непостоянство значений в полях “TERROR” и “TU”, может стать причиной ошибки.

рис. 1.2.4 — Изменяющиеся значения полей “TERROR” и “TU” для записи номер 18.

Формат даты выглядит совсем не так. Открыв файл и взглянув на поля “GR” (рис. 1.2.5), “CB_DATE” и “CE_DATE”, содержащие в себе дату, мы видим совсем не то, что ожидали.

рис. 1.2.5— Формат даты в поле “GR” (отображающийся в файле).

Мы ожидали (согласно документации) формат “ДДММГГГГ”, а получили “ДД.MM.ГГ”. Полученный результат обусловлен настройками отображения даты в конкретном редакторе, вы увидите это посмотрев формат ячейки содержащий дату в LibreOffice Calc, но реальное значение ячейки, всё же способно удивить. На самом деле, формат даты, который в итоге придётся парсить нашему коду, выглядит вот так “ГГГГММДД”.

Строки не по порядку. При беглом осмотре файла может показаться, что строки принадлежащие одной записи идут по порядку, но это не так. Так например первые строки записи, с номером 1, могут быть размещены в самом начале, а последующие в его середине (рис. 1.2.6).

рис. 1.2.6 — Демонстрация того, что записи расположенные в начале документа могут быть продолжены в середине.

Следовательно, у нас нет возможности, читая файл последовательно, наткнувшись на запись с номером 1, вычитать все её строки подряд.

Именование полей в документе, не совпадает. В документе описаны поля “CB-DATE”, “CE-DATE” и “ROW-ID”, в самом же файле мы видим поля именованные через нижнее подчёркивание “CB_DATE”, “CE_DATE”, “ROW_ID”.

Чтобы убедиться в этом, воспользуемся пакетом godbf и напечатаем все поля DBF файла.

dbfTable, _ := godbf.NewFromFile(filePath, “866”)
fmt.Println(dbfTable.FieldNames())

Выполнив код выше, мы получим:

[NUMBER TERROR TU NAMEU DESCRIPT KODCR KODCN AMR ADRESS KD SD RG ND VD GR YR MR CB_DATE CE_DATE DIRECTOR FOUNDER ROW_ID TERRTYPE]

Как мы видим, в реальном файле эти поля действительно именуются не так как в документации. Это не так критично, но получать ошибку о том, что описанное в документе поле, не существует, было довольно неприятно.

Что на самом деле?

В этом разделе мы взглянем на DBF файл ещё раз, но уже с позиции человека имеющего некий опыт работы с ним. Будем разбираться в том как на самом деле устроены данные внутри него и как правильно с ними работать. Посмотрим к каким заключениям можно прийти изучая файл вручную, а также проанализируем файл автоматически.

Итак, представьте, вы начали обрабатывать файл и превращать строки из файла в полноценную запись (структуру, объект, массив, хеш-таблицу), в нечто такое, что в последствии сможет стать кортежем в вашей базе данных.

Есть ли пробелы?

В процессе парсинга файла, нам придётся конкатенировать значения текстовых полей. Если размер поля превысит 254 символа, запись будет разделена на несколько строк и нам нужно понимать как собрать её воедино.

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

Первый.

Из исходного текста итеративно вырезаются первые 254 символа (они и становятся очередной строкой), до тех пор пока вырезанная строка не окажется меньше 254 символов.

При этом варианте обрезать пробелы в начале или на концах строк недопустимо, как недопустимо и вставлять пробелы между ними.

Второй.

Алгоритм итеративно берёт максимально возможное количество слов из исходного текста, так чтобы число символов в получившейся строке не превышало допустимые 254 символа, до тех пор пока в исходном тексте не закончатся слова. Результат каждой из итераций и будет очередной строкой.

Второй алгоритм исключает разбиение посередине слова, что теоретически позволяет обрезать пробелы по краям получившейся строки. Вопрос только в том, обрезает алгоритм пробелы или нет?

Какой же из вариантов верный? Попытаемся разобраться на практике.

Когда код для конкатенации строк писался впервые, общая концепция была примерно такой:

res = nameu1 + nameu2

И как итог, часть результатов были похожи на это:

Jonathan Ivan LeeJonathan Ivan Lee

Глядя на результат, кажется, что нужно добавить пробел между строками:

res = nameu1 + " " + nameu2

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

Branden George Bev erly Hopkins

Не один из подходов не даёт удовлетворительного результата, что же не так?

Если вы изучите данные в вашем DBF фале, вы скорее всего найдёте как строки которые, как вам будет казаться, правильно конкатенирировать через пробел, так и те, где дополнительный пробел явно лишний.

То, как выглядят строки в файле и предполагаемый результат конкатенации, можно выразить вот такими примерами:

str1 := "Luke, I"
str2 := "am your father"
fmt.Println(str1 + str2)
// Luke, Iam your father
str1 := "Luke, I"
str2 := " am your father"
fmt.Println(str1 + str2)
// Luke, I am your father
str1 := "Luke, I am yo"
str2 := "ur father"
fmt.Println(str1 + str2)
// Luke, I am your father

Просматривая файл при помощи LibreOffice Calc, мы должны понимать, что он вносит свои коррективы в отображение данных (например отображение даты) и будет лучше провести автоматический анализ файла, тем более, что мы всё ещё не в состоянии дать однозначный ответ на вопрос “Нужно ли добавлять пробел при конкатенации строк?”.

Давайте соберём немного статистики. В этом нам поможет написанная на языке Go утилита, с кодом которой можно ознакомиться здесь. Данная утилита поддерживает команду spaces, которая и проведёт анализ полей “NAMEU”, “DESCRIPT”, “AMR”, “ADRESS”, “MR”, “DIRECTOR”, “FOUNDER” и “TERRTYPE” на наличие пробелов в начале и конце строк.

Выполним команду spaces передав ей в качестве аргумента путь к директории содержащей файлы для анализа.

./dbf-research spaces /tmp/dbf_source

Для экономии места приведу вывод результатов только для одного файла.

Statistics for file /tmp/dbf_source/file1.dbf
NumRows: 10820
NumRecords: 7579
Nameu:
LeadingSpace: 0
TrailingSpace: 0
BorderingSpaces: 0
Descript:
LeadingSpace: 0
TrailingSpace: 0
BorderingSpaces: 0

Может показаться, что результаты вполне нормальные, но мы точно можем сказать, что как минимум пробелы слева там должны быть, так как при визуальном осмотре файла их можно обнаружить (рис. 1.3.1).

рис. 1.3.1 — Ведущий пробел в поле “NAMEU”.

Самое простое, что можно предположить, это то, что в коде допущена ошибка, но нет. Тогда подозрения пали на пакет, используемый для чтения DBF фалов. Давайте посмотрим как же реализована функция для получения значений из полей DBF файла (рис. 1.3.2).

рис. 1.3.2— Метод FieldValue структуры DbfTable из пакета godbf.

Причина полученных ранее результатов, находится в строке 451, представленного файла, на рисунке 1.3.2. Если убрать из этой строки удаление пробелов (TrimSpaces), результат команды spaces резко измениться. Но это точно не к лучшему, так как теперь пробелы видятся даже в тех полях которые мы считали пустыми (при визуальном осмотре анализируемого файла, поля действительно выглядят как пустые).

Давайте посмотрим на результат работы команды spaces после того как мы модифицировали пакет godbf и убрали удаление пробелов.

Statistics for file /tmp/dbf_source/file1.dbf
NumRows: 10820
NumRecords: 7579
Nameu:
LeadingSpace: 1
TrailingSpace: 9779
BorderingSpaces: 894
Descript:
LeadingSpace: 13
TrailingSpace: 2931
BorderingSpaces: 7472

Количество пробелов резко возросло, что не удивительно. Начинать глубокое исследование этого явления мы не будем, но приведём вот такой пример:
Если убрать из пакета godbf удаление пробелов для получаемого значения и после этого попытаться получить значение поля “NUMBER” в котором записан номер “1”, то фактически мы получим следующую строку, состоящую из пяти байт “1____” (через “_” условно обозначены пробелы). Как можно видеть из документации, для поля “NUMBER” (рис. 1.1.1) как раз выделяется пять байт.

Полностью отказываться от удаления пробелов неразумно, но исходя из того, что байты поля заполняются слева направо, возможно будет удачным решением заменить удаление пробелов на обоих концах строки (значение поля), на удаление только пробелов справа.

Заменим строку 451 из файла dbftable.go, из пакета godbf на такую:

value = strings.TrimRight(s, " ")

После чего запустим команду spaces снова.

Statistics for file /tmp/dbf_source/file1.dbf
NumRows: 10820
NumRecords: 7579
Nameu:
LeadingSpace: 5
TrailingSpace: 0
BorderingSpaces: 0
Descript:
LeadingSpace: 58
TrailingSpace: 0
BorderingSpaces: 0

Теперь наш результат выглядит куда приличнее, а пакет godbf по-прежнему работоспособен. Для большей уверенности, конечно стоит изучить этот вопрос глубже, но это уже за рамками этой статьи.

Изначальная реализация пакета godbf делает конкатенацию значений с ведущим пробелом, заведомо неверной. Хоть процент таких записей около 0.043%, и поле “NAMEU” содержит имя записанное многократно и в разных формах, что снижает ущерб, допускать такие ситуации всё же не стоит, тем более что это не сложно.

А что насчёт значений с пробелом в конце? Как понять, что он был, если при чтении данных пробелы справа обрезаются? При осмотре файла мы встречали строки, которым явно не хватало пробела для корректной склейки, но в начале строки, выступающей как второе слагаемое, его не было. Придётся искать пробел в первой строке.

Предположим, что наши строки выглядят вот так:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse lacinia, risus nec vulputate lacinia, turpis dui sodales ligula, sit amet congue tortor libero in dui. Nullam quis ipsum placerat, dapibus augue non, pulvinar nisl. Cras sodales nam toDonec dolor sit viverra. Lorem ipsum dolor sit amet, consectetur tincidunt.

Если посчитать количество символов в первой строке, то окажется, что оно равно 253-м. Напомним, что для текстовых полей выделяется 254 байта. Куда же пропал ещё один байт? А это и есть тот самый пробел, которого нам так не хватало, но LibreOffice Calc и пакет godbf его обрезали.

Зафиксируем это как правило. Строка имеет завершающий пробел если:

  • длина строки равна 253-м символам;
  • строка не является последней строкой записи;
  • строка, следующая за текущей не пуста.

Вооружившись этими знаниями, мы можем переписать сбор статистики по пробелам и посмотреть результаты.

Statistics for file /tmp/dbf_source/file1.dbf
NumRows: 10820
NumRecords: 7579
Nameu:
LeadingSpace: 5
TrailingSpace: 9
BorderingSpaces: 0
Descript:
LeadingSpace: 57
TrailingSpace: 81
BorderingSpaces: 1

Фуууух! Отлично! Похоже мы всё-таки научились верно анализировать пробелы. Звучит достаточно тривиально, но путь был непростым.

Для достижения удовлетворительного результата, нам понадобилось сделать всего две вещи:

  • изменить удаление пробелов по краям строки на удаление пробелов справа;
  • определять строки для которых предполагался завершающий пробел, так как это описано выше, и восполнять этот пробел самостоятельно.

Что насчёт дубликатов?

В одном из разделов выше мы упоминали о наличии дубликатов, в уже полюбившихся нам полях. Мы видели их собственными глазами, знаем, что их нужно иметь в виду при написании кода для парсинга, но раз уж у нас есть такая возможность, то давайте приведём немного цифр.

Для анализа дубликатов в нашей утилите имеется команда dup, ей и воспользуемся.

./dbf-research dup /tmp/dbf_source --short

Флаг “short” нужен для сокращённого вывода результатов. Без него, будут выведены данные о количестве дубликатов в каждой конкретной записи.

Приведём результат для одного из файлов

Statistics for file /tmp/dbf_source/file4.dbf
NumRows: 14600
NumRecords: 10039
Nameu:
Count: 2822
Descript:
Count: 2645
Amr:
Count: 1806
Address:
Count: 1025
Mr:
Count: 2319
Director:
Count: 0
Founder:
Count: 0
Terrtype:
Count: 316

Как мы видим, случаев дублирование значений в полях, достаточно много. Имейте это ввиду при работе с файлом.

Отмечу, что дубликатом считается ситуация, когда в рамках одной записи (с одинаковым значением поля “NUMBER”), значения в конкретном поле повторяются.

Что с форматом даты?

Как вы возможно помните, поля “GR”, “CB-DATE” и “CE-DATE” содержат дату, как нас заверяет документация в формате “ДДММГГГГ”. Но для поля “GR” формат даты указан только для случая, когда запись относится к физическому лицу (рис. 1.1.3). Не говоря уже о том, что как было сказано в одном из предыдущих разделов, реальное значение присутствующее в файле соответствует вот такому формату “ГГГГММДД”.

Убедимся в этом, написав вот такой код:

Код, демонстрирующий реальный формат данных в полях “GR”, “CB_DATE” и ”CE_DATE”.

Запустив его, мы получим следующее:

GR: 19901229
CB_DATE: 20151224
CE_DATE:
GR: 19880922
CB_DATE: 20141113
CE_DATE:
GR: 19870619
CB_DATE: 20160712
CE_DATE:

Как вы можете видеть, дата записана в формате “ГГГГММДД”. Конечно же мы могли что-то упустить и дата в формате “ДДММГГГГ” всё же встречается.

Чтобы исключить ошибку, проанализируем даты автоматически и сосчитаем, сколько раз встретится тот или иной формат.

Для анализа файла используется язык Go, поэтому шаблоны для парсинга даты будут описаны в принятом для Go формате. Приведём небольшой пример. Если мы хотим превратить строку “01.05.2020” в объект, мы должны описать для него следующий шаблон “02.01.2006”.

t, err := time.Parse(“02.01.2006”, “01.05.2020”)

Подробнее, о том как в Go задаётся формат даты, можно почитать здесь.

Воспользуемся командой date из нашей утилиты для анализа файла.

./dbf-research date /tmp/dbf_source/file4.dbf

И вот каким будет результат:

Statistics for file /tmp/dbf_source/file4.dbf
NumRows: 14600
NumRecords: 10039
Gr:
02012006: 49
20060102: 12828
Empty: 1772
Other: 0
CB_DATE:
02012006: 1143
20060102: 13419
Empty: 1181
Other: 0
CE_DATE:
02012006: 0
20060102: 0
Empty: 14600
Other: 0

Выходит даты в формате “ДДММГГГГ” всё-таки существую? Нет. А отклонениям в статистике есть объяснение.

При анализе строк, мы считаем, что строка подходит под формат, если функция парсинга даты не вернула ошибку.

_, err := time.Parse(layout, dateStr)
if err != nil {
// layout is fit
}

В этом и кроется разгадка. Строки, которые были распознаны как подходящие под формат “02012006”, не вызвали ошибки при парсинге, но полученный в результате объект, не эквивалентен дате в исходной строке.

Проще говоря, вот код:

Код, демонстрирующий парсинг даты по не верному шаблону, который не выдаст ошибку.

Вот результат:

20100923 -> 0923-10-20 00:00:00 +0000 UTC
20101220 -> 1220-10-20 00:00:00 +0000 UTC
20110701 -> 0701-11-20 00:00:00 +0000 UTC
20070316 -> 0316-07-20 00:00:00 +0000 UTC
20111101 -> 1101-11-20 00:00:00 +0000 UTC
20050228 -> 0228-05-20 00:00:00 +0000 UTC
20100625 -> 0625-10-20 00:00:00 +0000 UTC
20040121 -> 0121-04-20 00:00:00 +0000 UTC
20110912 -> 0912-11-20 00:00:00 +0000 UTC

Итог прост. Используйте только формат “ГГГГММДД”, так как формат “ДДММГГГГ” приведёт к ошибкам.

Как изменяется значение ROW_ID?

Данный вопрос не является критически важным, так как если при парсинге файла вы производите сортировку по полю “ROW_ID” в порядке возрастания, размещение строк не скажется на вашем результате.

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

Код, демонстрирующий изменение значений поля ROW_ID с каждой новой строкой файла.

Представленный код должен вывести общее количество строк в файле, количество строк идущих по порядку, согласно значению поля “ROW_ID”, а так же в процессе чтения файла будет выводиться информация о строках, значения “ROW_ID” в которых идут не по порядку. Под порядком мы понимаем ситуацию, когда каждое следующее значение “ROW_ID”, больше предыдущего ровно на единицу.

Запустим наш код и продемонстрируем результаты для нескольких файлов:

Number of rows: 10820
Rows in the correct order: 10820
Number of rows: 12291
Rows in the correct order: 12291
Number of rows: 13410
Rows in the correct order: 13410
Number of rows: 14600
Rows in the correct order: 14600

Как мы можем видеть, во всех исследованных файлах, значения в поле “ROW_ID” идут строго по порядку. Но так как этот порядок не гарантирован документацией, возможно не стоит отказываться от шага сортировки.

Может ли первая строка записи содержать неверные данные?

Мы разобрались как работать с текстовыми полями, но как быть с остальными? Для ясности, давайте назовём поля, чьи значения не разбиваются на несколько строк, статическими.

На рисунке 1.3.3, вы можете видеть, что значения полей “TERROR” и “TU” не одинаковы от строки к строке. Однако значения из первой по порядку строки, нам подходят, а это значит, что мы можем использовать первую строку, как источник данных для статических полей. Пока будем придерживаться этой мысли.

рис. 1.3.3 — Изменяющиеся значения полей “TERROR” и “TU” в рамках одной записи.

Раз уж мы собираемся использовать значения из первой по порядку строки для инициализации статических полей записи, мы должны быть уверенны, что первая строка всегда содержит корректные значения.

Из всех полей описанных в официальном документе, только 3 имеют строгий диапазон значений. Это поля “TERROR”, “TU” и “KD” (рис. 1.3.4 и рис. 1.3.5).

рис. 1.3.4 — Описание полей “TERROR” и “TU”.
рис. 1.3.5— Описание поля “KD”.

Эти поля мы и будем использовать как индикатор корректности строки. Если в первой по порядку строке записи, хотя бы одно значение из описанных выше полей отличается от задокументированных, такая строка считается невалидной.

Запустим следующую команду:

./dbf-research first-row /tmp/dbf_source/

И вот что мы увидим:

Statistics for file /tmp/dbf_source/file1.dbf
NumRows: 10820
NumRecords: 7579
NotCorrect: 709
NotCorrectTerror: 0
NotCorrectTu: 0
NotCorrectKd: 709
Statistics for file /tmp/dbf_source/file2.dbf
NumRows: 12291
NumRecords: 8086
NotCorrect: 699
NotCorrectTerror: 0
NotCorrectTu: 0
NotCorrectKd: 699
Statistics for file /tmp/dbf_source/file3.dbf
NumRows: 13410
NumRecords: 8723
NotCorrect: 652
NotCorrectTerror: 0
NotCorrectTu: 0
NotCorrectKd: 652
Statistics for file /tmp/dbf_source/file4.dbf
NumRows: 14600
NumRecords: 10039
NotCorrect: 731
NotCorrectTerror: 0
NotCorrectTu: 0
NotCorrectKd: 731

Полученные результаты говорят нам, что поля “TERROR” и “TU” в первой строке записи, всегда имеют корректные значения, а вот поле “KD” неприятно удивило. Суть ситуации отражена на рисунке 1.3.6, на примере записи с номером 2.

рис. 1.3.6 — Демонстрация проблемы в поле “KD”, для записи номер 2.

Значение поля “ROW_ID” изменяется с шагом 1, следовательно, первая встреченная в файле строка, является первой по порядку.

Допустимыми значениями для поля “KD” являются “0”, “01”, “02”, “03”, “04”.

Из вышесказанного можно сделать вывод, что мы не можем быть уверены, что в первой строке записи получим корректные значения статических полей. Как минимум для поля “KD” мы в этом убедились.

Итог

История существования описанного в статье файла, по официальному стандарту началась в 2005 году, а как данная база жила до этого, мы не имеем ни малейшего понятия. Возможно до 2005 года стандарт был другим, или отсутствовал вовсе. Быть может изначально база существовала на бумажном носителе. Как в неё вносятся записи? А как удаляются? Ответы на эти вопросы нам не известны.

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

  • перед тем, как собирать поля записи воедино, отыщите все строки принадлежащие ей и отсортируйте по полю ROW_ID;
  • конкатенация значений из полей “NAMEU”, “DESCRIPT”, “AMR”, “ADRESS”, “MR”, “DIRECTOR”, “FOUNDER” и “TERRTYPE” должна осуществляться без дополнительного пробела между слагаемыми, если строка выступающая в качестве первого слагаемого, не подразумевает пробела в конце строки;
  • ситуацию, в которой строка выступающая в роли первого слагаемого нуждается в добавлении пробела в конец строки, можно определить по следующим признакам: строка содержит ровно 253 символа (на один меньше максимально допустимого значения); следующая за ней строка не пустая.
  • отслеживайте ситуации, когда в следующей по порядку строке присутствует дубликат — значение уже добавленное в результирующую строку, это сделает итоговую запись более пригодной для обработки;
  • строки, обозначающие дату в полях “GR”, “CB_DATE” и “CE_DATE” имеют формат “ГГГГММДД”, зеркальный тому, что описан в документации;
  • не пытайтесь предусмотреть сразу два формата даты “ГГГГММДД” и “ДДММГГГГ”, это приведёт к некорректному парсингу и полученная дата будет совсем не той, что ожидалась;
  • первая по порядку строка записи не всегда содержит в поле “KD” значение, которое стоит использовать для формирования итогового результата. Так например, для первой строки это поле будет пустым, а вторая (или любая последующая) строка будет содержать валидное значение. Проверяйте значение этого поля в каждой строке, до тех пор пока не встретите валидное значение;
  • не доверяйте документам, статьям и гадальным картам, перепроверяйте всё сами.

Конечно, можно было бы и дальше подвергать файл анализу, выявлять закономерности, собираться статистку, но наиболее важные моменты, мы всё-таки разобрали. Так что давайте на этом остановимся, во всяком случае пока.

--

--