Python на страже кошелька



    Однажды забрел я на сайт мобильного оператора в поисках чего-нибудь интересного. Интересным оказалось только наличие большого количества разнообразных тарифных планов на любой вкус и цвет. Тут — низкий тариф внутри сети, там — тарификация по длительности звонка, а вот и фиксированная плата за звонок. В общем, маркетологи подзаработали на славу. На сайтах других операторов картина была похожая. Мне стало интересно, насколько мои расходы на мобильную связь изменились бы в зависимости от выбранного тарифа. Но одной только силы мысли оказалось недостаточно, чтобы проанализировать статистику звонков за последние месяцы и сопоставить их со всеми тарифами. Решив отложить это дело до лучших времен, нажимаю Alt+Tab и попадаю — правильно, в консоль с заголовком Python 2.7.5+ и манящим приглашением >>>

    Сразу уточню, что на местном рынке всего 3 основных оператора мобильной связи, поэтому составить таблицу наиболее привлекательных для меня тарифных планов не составило особого труда. Таковых оказалось аж целых 10:


    Следующим шагом было получение истории звонков за последние 3 месяца. Эта функция была доступна прямо с сайта, за что спасибо моему оператору. Скачав PDF, превратил его в текстовый файл с помощью PDFMiner (кстати, тоже написан на python). Результат получился неплохой, но все же пара ошибок в тексте была замечена, поэтому вместо танцов с бубном вокруг этой утилиты данные были переведены в текстовый файл с помощью банального copy-paste прямо из PDF файла. Этот файл и был скормлен нашему питону:

    #!/usr/bin/python
    
    import MySQLdb as mysql
    
    con = mysql.connect('localhost', 'mobile', 'mobile', 'mobile')
    cur = con.cursor()
     
    with open('calls.log') as f:
        for line in f:
            if 'MOC' in line:
                tokens = line.split()
                if len(tokens) == 11:
                    cur.execute("INSERT INTO calls (operator, amount) VALUES('" + tokens[1] + "','" + tokens[6] + "')")
    con.commit()
    

    Учитывая, что SMS я практически не использую, выборка была сделана только по исходящим звонкам (mobile-originated call, MOC). В результате лог звонков был сохранен в MySQL базе для дальнейших манипуляций.

    Дальнейшие манипуляции

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

    cur.execute("SELECT operator,plan,call_init,first_min_int,first_min_ext,min_int,min_ext FROM tariffs")
    rows = cur.fetchall()
     
    for row in rows:
        operator, plan, call_init, first_min_int, first_min_ext, min_int, min_ext = row
         
        # цена соединения
        cur.execute("SELECT COUNT(*) FROM calls") 
        total = call_init*cur.fetchone()[0] 
        
        # первая минута внутри сети 
        cur.execute("SELECT COALESCE(SUM(LEAST(amount,60)),0) FROM calls WHERE operator='" + operator + "'") 
        total += cur.fetchone()[0]/60*first_min_int
        
        # внутри сети, без учета первой минуты 
        cur.execute("SELECT COALESCE(SUM(amount-60),0) FROM calls WHERE operator='" + operator + "' AND amount > 60") 
        total += cur.fetchone()[0]/60*min_int
         
        # первая минута вне сети
        cur.execute("SELECT COALESCE(SUM(LEAST(amount,60)),0) FROM calls WHERE operator<>'" + operator + "'") 
        total += cur.fetchone()[0]/60*first_min_ext
         
        # вне сети, без учета первой минуты
        cur.execute("SELECT COALESCE(SUM(amount-60),0) FROM calls WHERE operator<>'" + operator + "' AND amount > 60") 
        total += cur.fetchone()[0]/60*min_ext
         
        print plan + " : " + str(round(total/100, 2)) + " у.е."
    

    Вот что имеем на выходе:

    Magti Standard : 47.1 y.e.
    Magti I Alternative : 56.5 y.e.
    Bani Standard : 29.72 y.e.
    Bani Zero+ : 26.67 y.e.
    Geocell 000 : 49.94 y.e.
    Geocell 1-10 : 35.86 y.e.
    Geocell 12 : 35.66 y.e.
    Beeline 007 : 37.69 y.e.
    Beeline Non Stop : 40.7 y.e.
    Bani Universal : 39.21 y.e.
    

    Эти же данные в виде диаграммы, для большей наглядности:


    Выводы

    Таким образом, если не рассматривать крайние значения, то среднеквадратичное отклонение получилось достаточно маленьким, в том числе и среди разных операторов связи. Однако, даже такой простой анализ может способствовать экономии более чем в 2 раза при выборе наиболее выгодного тарифного плана по сравнению с наименее выгодным, учитывая специфику конкретного пользователя.
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 6

      +2
      Я даже не знаю что сказать про весь этот код. Если это одноразовый код, который никогда не увидит мир — ок, задача решена.
        +2
        Я обычно делаю такие вещи в Excel. Но совсем давно, когда компьютеры еще были под MS DOS, я любил подобные задачи решать на самых разных языках (от Clipper и FoxBase до Delphi и Watcom C).

        Думаю, склонность к постановке и решению таких задачек хорошо характеризует человека :)
          0
          Пожалуй, соглашусь с одноразовостью кода, появилась идея — реализовал и поделился, вдруг кому интересно.
          0
          А зачем нужно было использовать MySQL? А потом делать такую пачку SELECT? Да ещё и в цикле.

          Полистайте PEP8, пожалуйста.
            +1
            Спасибо, обязательно полистаю. На самом деле питон я использую совсем недавно, поэтому за кривость кода не ручаюсь. Просто захотелось попробовать связку python+mysql в «деле».
              +1
              А, новичок! Смелей в поисковик и найдите себе Марка Лутца — это совсем для начинающих, так же Дэвид Бизли (буквально краткий справочник по stdlib), а сразу как начнёте там спокойно начинать плавать — тогда Dive into Python.

              Это самый простой язык, добро пожаловать. ;)

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

          Самое читаемое