Pull to refresh

Задача о минутах

Reading time4 min
Views1.1K
Жизнь, как говорится, лучший драматург. Так и задачи жизнь ставит не менее интересные, чем авторы занимательных книг по математике.
Буквально 2 дня назад одна, казалось бы, рутинная, неинтересная и лёгкая задача заставила меня понервничать и засомневаться в собственной адекватности восприятия реальности.

Итак, краткая вводная: я работаю в области IP-телефонии, через наши узлы проходит множество голосового транзитного трафика на различные направления, каждый оператор в этой области ведёт свой собственный подсчёт звонков, при выставлении счетов часто бывают значительные расхождения в показаниях, тогда возникает т.н. диспут, и начинается сверка по направлениям и дням.
Запись о каждом звонке хранится в так называемом CDR-формате (Call Detail Record: ru.wikipedia.org/wiki/Call_Detail_Record). Формат строго не регламенирован, каждый волен хранить там то, что ему нужно. Однако, у каждого звонка есть уникальный идентификатор, т.н. GID (Global Call ID), который на всех промежуточных узлах записывается в неизменном виде, именно он позволяет однозначно указать на какой либо звонок, т.к. все остальные данные звонка могут не совпадать (время установления и завершения звонка почти всегда различаются из-за неточности используемых часов, длительность звонка из-за задержек в передаче и округления часто отличается на одну-две секунды [кстати, на больших объемах это огромные деньги, мы ради интереса считали, сколько денег из воздуха может сделать оператор сотовой связи просто добавляя одну секунду к каждому разговору: миллионы долларов в месяц] и т.д.)
В этот раз мне пришлось анализировать данные по одному направлению 992 (Таджикистан). Мне были предоставлены CDR-записи оригинатора звонков (того, кто звонил) в формате CSV (comma separated values, хотя обычно используют точку с запятой :) за первые 5 дней, я выгрузил свои данные за этот же период и начал сравнивать.
Так как все данные почти однозначно отличаются, анализ производится по CallID. Оригинатор утверждал, что он отправили на нас меньше минут, чем мы указали в выставленном счёте. Соответственно, моей задачей были найти недостающие у оригинатора звонки в своих данных (если такие имеются), посчитать их длительность и предоставить их оригинатору.

Далее идёт небольшой скриптик, написанный на коленке и чуть обработанный для этой публикации (COMPANY1 — компания-оригинатор звонка, COMPANY2 — компания-терминатор звонка, то есть мы):
===========================================================
#!/bin/bash

# Сортируем файлы по полю CallID. Оставляем только уникальные значения
sort -u -f -k7 -t';' COMPANY1.csv >COMPANY1-sorted.csv
sort -u -f -k6 -t';' COMPANY2.csv >COMPANY2-sorted.csv

# Подсчитываем суммы минут в обоих файлах
echo "Сумма минут в файле COMPANY1:"
SEC=0
while read sec; do
SEC=$(($SEC+$sec))
done < <(cut -f6 -d';' COMPANY1-sorted.csv)
echo $(($SEC/60))

echo "Сумма минут в файле COMPANY2:"
SEC=0
while read sec; do
SEC=$(($SEC+$sec))
done < <(cut -f5 -d';' COMPANY2-sorted.csv)
echo $(($SEC/60))

# "Вырезаем" уникальное поле для поиска различий
cut -f7 -d';' COMPANY1-sorted.csv >COMPANY1-callid.csv
cut -f6 -d';' COMPANY2-sorted.csv >COMPANY2-callid.csv
# Находим и выводим CallID из второго файла, отсутствующие в первом файле и на их основе формируем
# файл, используя join на данные из второго же файла
# То есть, мы находим во втором файле звонки, которых однозначно нет в первом файле, т.к. CallID
# уникален в пределах каждого файла, и берем запись каждого звонка на основе этого CallID во втором
# же файле, то есть нашем, где этот звонок есть, после чего записываем их в файл
comm -1 -3 COMPANY1-callid.csv COMPANY2-callid.csv | join --check-order -i -1 1 -2 6 -t';' - COMPANY2-sorted.csv >COMPANY1-COMPANY2-absent.csv

echo "Сумма минут звонков, отсутствующих в файле COMPANY1:"
SEC=0
while read sec; do
SEC=$(($SEC+$sec))
done < <(cut -f6 -d';' COMPANY1-COMPANY2-absent.csv)
echo $(($SEC/60))

===========================================================
Запускаем:

Сумма минут в файле COMPANY1:
21291
Сумма минут в файле COMPANY2:
24789
Сумма минут звонков, отсутствующих в файле COMPANY1:
2731

Вуаля.
Однако, постойте! В файле компании-оригинатора 21291 минута, сумма недостающих минут, найденная на основе данных нашего файла равне 2731 минуту, значит в сумме они должны равняться 24789 минутам, как в нашем файле. А 21291+2731=24022, куда пропали 767 минут?
И вот тут-то я и начал потихоньку сходить с ума. Скрипт был перепроверен от и до, использовались различные промежуточные подсчёты, чтобы убедиться, что не было ошибок и количество записей совпадает, до утилиты comm была испробована привычная diff, я даже проверил исходные файлы в нужных колонках на isdigit() и т.д. и т.п.
Ничего не помогало! Количество минут магическим образом не совпадало, хотя на первый взгляд всё правильно. И только на следующий день один умный человек (Сережа Власов, vsu@ALTLinux Team) помог мне найти эти минуты. Попробуйте и вы их найти :-)
Для чистоты эксперимента выкладываю сам скрипт и два исходных файла (убрав из них названия и IP) у себя на ftp:
ftp://82.208.44.206/pub/minutes_task.zip

UPD: habracut почему-то отказывается воспринимать текст как в параметре text, получается только «Читать дальше» :(
UPD2: Попросили описать формат CSV (Comma Separated Values): это простой текстовый файл, где значения разделены запятой (comma), а в данном случае точкой с запятой. Причем порядок полей не регламентирован, поэтому только глазами можно определить, где и что. Вот пример:
XXX.XXX.XXX.11;COMPANY1;992927233890;2008-06-01 19:31:35;;9;780a890bddce4248b839040046464636;16;
Вот шестое по счету поле — длительность звонка в секундах. Седьмое — тот самый CallID
Tags:
Hubs:
Total votes 37: ↑23 and ↓14+9
Comments42

Articles