Уведомления

Группа в Telegram: @pythonsu

#1 Дек. 29, 2008 08:26:56

baloo
От:
Зарегистрирован: 2008-05-13
Сообщения: 59
Репутация: +  0  -
Профиль   Отправить e-mail  

Моя первая программа. Нужна критика.

Ну вот, состряпал первую программу. Обработка текстового файла, дублирующиеся записи в базах данных разных страховых компаний. Поскольку эту сводку делает госслужба, ничего умнее, чем почти нетипизированный текстовый файл, предложить не могут.
Нужно было успеть до 30-го декабря. Поэтому амбициозный проект с предварительным выводом данных в красивый цветной грид уперся в долгое ковыряние с wx… В конце концов программа свернулась до маленького файлика )))
Задача: обработать записи с выявлением
Номера полиса
Даты страхования
ФИО
Даты рождения

Сравнение даты страхования в нашей компании и в чужой. Если в чужой дата более свежая - то эта дата становится датой остановки страхования у нас. (Dismissed) Если у нас дата больше - пропускаем блок.
Потом найденный данные загоняются в SQL-скрипт, который запускается и “чистит базу”.
Вот пример файла для обработки:

WIN         32010  МСК 
======================================================================
(33001) Г11/0161066 26.01.2003 - 01.04.2008 Абакумова Мария Тимофеевна 05.03.1980 2
07236 ЛЕВОКУКУЕВСКОЕ,ЛУГОВАЯ 11/ -
07236 6 МИНИСТЕРСТВО ЗДРАВООХРАНЕНИЯ СТАВРОПОЛЬСКОГО КРАЯ
(32010) 59-1184-004599 25.10.2007 - 31.12.2007 Абакумова Мария Тимофеевна 05.03.1980 1
07236 С. ЛЕВОКУЕВСКОЕ, УЛ. КОМАРОВА, Д. 1 КВ. 1
07402 1184 ОАО СТАВРОПОЛЬЭНЕРГО ФИЛИАЛ ЗАПАДНЫЕ ЭЛЕКТРИЧЕСКИЕ СЕТИ
======================================================================
(33001) 15/031513593534 10.10.2007 - 01.04.2008 Абдурахманов Шамиль Эльдарович 04.05.1966 2
07421 КРАЙ СТАВРОПОЛЬСКИЙ, Р-Н МИНЕРАЛОВОДСКИЙ, С КАНГЛЫ
07421 6 МИНИСТЕРСТВО ЗДРАВООХРАНЕНИЯ СТАВРОПОЛЬСКОГО КРАЯ
(32010) 32-3094-000001 20.07.2006 - 31.12.2007 Абдурахманов Шамиль Эльдарович 04.05.1966 1
07239 С.КАНГЛЫ, М.ДЖАМИЛЯ
07412 3094 ООО ПМК ИНОЗЕМЦЕВСКАЯ-1
======================================================================
(32010) 33-0218-000345 30.11.2007 - 31.12.2007 Абрамов Валерий Георгиевич 28.05.1959 1
07248 ПРЕДГОРНЫЙ Р-Н, С.БЛАГОДАРНОЕ, УЛ. ЛЕНИНА, Д. 52
07427 218 ПЯТИГОРСКИЙ ЗАВОД ОАО
(32011) 5.030567 18.04.2005 - 01.04.2008 Абрамов Валерий Георгиевич 28.05.1959 2
07248 С НОВОБЛАГОДАРНОЕ ЛЕНИНАБАДСКАЯ 52
07248 7000 МИНИСТЕРСТВО ЗДРАВООХРАНЕНИЯ
======================================================================
...и т д. Адреса и имена, понятно, левые.
вот сам Python-файл:
# -*- coding: cp1251 -*-
#from wxPython._wx import false
import re
#import datetime
import time

##example = "(32010) 25/012504398888 24.10.2008 - 31.12.2008 Аветисян Ромелла Леонидовна 13.04.1982 1" ##Персона
#Регулярное выражение - не путать скобки с escape-бэкслэш и скобки обособления группы
#Совпадение персона (застрахованный)
log_per = r"\((\d{5})\)\s+(\S+)\s+(\d{2}.\d{2}.\d{4})[-\s]+(\d{2}.\d{2}.\d{4})[\s]+([-а-я,А-Я]+)\s+([-а-я,А-Я]+)\s+([-а-я,А-Я]*)\s+(\d{2}.\d{2}.\d{4}).+$"
#Совпадение строка-разделитель дублей
log_flag = r"^.+===========.+$"


ln=0 ##Номер строки (будем пересчитывать строки в файле)
is_r=False ##Есть ли наша контора в группе
i=0 ##Счетчик дублирующихся записей в группе

for line in open("32010.txt"):
ln = ln + 1
pr = re.findall(log_per, line) ##Проверяем - строка это застрахованный?

if len(pr)>0: ##Если это застрахованный
i = i+1 ##Строка дублирования
if pr[0][0] == "32010": ##Если это НАШ застрахованный
pr_r = pr ##Строка РОСНО
is_r = True ##РОСНО есть в этой группе
r_id = time.strptime(pr[0][2], "%d.%m.%Y") ##Дата страхования в нашей конторе
#print(pr[0][0], r_id)

else:
a_id = time.strptime(pr[0][2], "%d.%m.%Y") ##Дата страхования чужая
sa_id = pr[0][2]
#print(pr[0][0], a_id)
if ((i > 1) and (is_r == True)): ##Если не первый в группе и мы уже найдены
if (a_id > r_id): ##Если чужая дата выгоднее - больше
r_fail = True ##Тогда failure!!!
else:
r_fail = False

pr = re.findall(log_flag, line) ##Проверяем - не закончилась ли группа
if len(pr)>0: ##Если группа закончилась
##Считаем итоги, еще не обнулили
if ((i > 1) and (is_r == True)):
if (r_fail == True):
print("UPDATE INSURED SET DISMISSED = '%s' WHERE FAM = '%s' AND IM = '%s' AND OT = '%s' AND DR = '%s' AND DISMISSED IS NULL;") % (sa_id, pr_r[0][4], pr_r[0][5], pr_r[0][6], pr_r[0][7])
i = 0 ##Обнуляем счетчики дублирования
is_r = False ##Обнуляем флаг нашей конторы
Я понимаю, что написано на Delphi, с использованием Питоновских слов. Наверняка есть какие-нибудь способы или записать циклы проще или логические if-else.
Кстати, изначально флагом конца группы являлась не строка “==============…”, а отслеживалась смена фамилии-имени отчества:
if fio != pr[0][4]: ##То-есть, речь пошла о другом человеке 
fio = pr[0][4] ##Запоминаем этого человека, обнуляем счетчик дублей, обнуляем наш флаг (есть ли мы, т.е. 32010, в этой группе и т.д.)
Так вот: почему-то условие неравенства соблюдалось даже тогда, когда ФИО были равны… Странно.



Офлайн

#2 Дек. 29, 2008 09:06:12

bw
От:
Зарегистрирован: 2007-09-26
Сообщения: 938
Репутация: +  20  -
Профиль   Адрес электронной почты  

Моя первая программа. Нужна критика.

А какой вопрос :-) ?

..bw



Офлайн

#3 Дек. 29, 2008 09:16:01

pythonwin
От:
Зарегистрирован: 2006-07-18
Сообщения: 1294
Репутация: +  0  -
Профиль   Отправить e-mail  

Моя первая программа. Нужна критика.

baloo, опиши, пожалуйста, в чем проблема или тебе нужно чтобы мы оценили программу?
И пожалуйста, сформулируй заголовок темы более осмыслено и с привязкой к самомой теме



Офлайн

#4 Дек. 29, 2008 11:07:38

PooH
От:
Зарегистрирован: 2006-12-05
Сообщения: 1948
Репутация: +  72  -
Профиль   Отправить e-mail  

Моя первая программа. Нужна критика.

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

# -*- coding: cp1251 -*-
import re
import time

RE_RECORD = re.compile(r"\((\d{5})\)\s+(\S+)\s+(\d{2}.\d{2}.\d{4})[-\s]+(\d{2}.\d{2}.\d{4})\s(\S+)\s+(\S+)\s+(\S+)\s+(\d{2}.\d{2}.\d{4})")

def iter_records(stream):
'''итератор по записям попадающим под маску
возврашает code, (f.i.o), insurance date, birth date'''
for s in stream:
mt = RE_RECORD.match(s)
if mt:
yield mt.group(1), (mt.group(5), mt.group(6), mt.group(7)), \
time.strptime(mt.group(4), "%d.%m.%Y"), mt.group(8)

def iter_group(stream):
'''итератор по групам записей о персоне
возвращает (f.i.o), ls, birth_date
где ls список дат страхования в формате (код конторы, дата окончания страхования)'''
last, ls = None, []
for code, fio, insurance, birth in stream:
if last and last != fio:
yield fio, ls, birth
ls = [(code, insurance)]
else:
ls.append((code, insurance))
last = fio
yield fio, ls, birth

def select_date(ls):
'''возвращает дату для коррекции записи или None если коррекция не нужна
'''
our = [x[1] for x in ls if x[0] == '32010']
our = (our and our[0]) or None
alien = [x[1] for x in ls if x[0] != '32010' and x[1] > our]
if alien:
return max(alien)

for fio, ls, birth in iter_group(iter_records(open("32010.txt"))):
dt = select_date(ls)
if dt:
print """UPDATE INSURED
SET DISMISSED = '%s'
WHERE FAM = '%s' AND IM = '%s' AND OT = '%s' AND DR = '%s'
AND DISMISSED IS NULL;""" % (time.strftime("%d.%m.%Y", dt), fio[0], fio[1], fio[2], birth)
т.есть: iter_records - фильтрует файл, его выход уходит в iter_group, который группирует данные по персоне, и остается только проити по всем персонам и проверить дату select_date



Вот здесь один из первых отарков съел лаборанта. Это был такой умный отарк, что понимал даже теорию относительности. Он разговаривал с лаборантом, а потом бросился на него и загрыз…

Отредактировано (Дек. 29, 2008 11:22:17)

Офлайн

#5 Дек. 29, 2008 14:38:52

pythonwin
От:
Зарегистрирован: 2006-07-18
Сообщения: 1294
Репутация: +  0  -
Профиль   Отправить e-mail  

Моя первая программа. Нужна критика.

PooH +1 задачу и +1 за телепатию



Офлайн

#6 Дек. 29, 2008 15:48:01

Ferroman
От:
Зарегистрирован: 2006-11-16
Сообщения: 2759
Репутация: +  1  -
Профиль   Отправить e-mail  

Моя первая программа. Нужна критика.

PooH
Отлично, только тестов не хватает :)
baloo
Задачу решил - это отлично. Единственное, что можно посоветовать - не пиши монолитный код. Старайся его разбивать на структуры мельче, тогда сопровождать код намного легче. Пример PooH отличная иллюстрация.
А то вдруг формат изначальных данных немного изменится.
И ещё одно - в регэкспах лучше делать именованные группы, и потом по именам их вызывать. Код легче читать, и меньше шансов запутаться с цифрами индексов.

Офлайн

#7 Дек. 30, 2008 10:22:10

baloo
От:
Зарегистрирован: 2008-05-13
Сообщения: 59
Репутация: +  0  -
Профиль   Отправить e-mail  

Моя первая программа. Нужна критика.

Спасибо, вариант Pooh'a рассмотрю сегодня обязательно. Вопрос состоял в том, что я хотел критики и тыканья носом. Поскольку я Дельфи-прогр. и, вполне понятно, что все прелести Pythona мог профукать.



Офлайн

#8 Дек. 30, 2008 12:02:03

ZAN
От:
Зарегистрирован: 2007-06-10
Сообщения: 403
Репутация: +  10  -
Профиль   Отправить e-mail  

Моя первая программа. Нужна критика.

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

Ferroman
И ещё одно - в регэкспах лучше делать именованные группы, и потом по именам их вызывать
Кроме того, регулярное выражение можно логически разбить на подстроки (re.compile с флагом re.VERBOSE)
Как я понимаю, информация, заключенная в разделительные полосы - это единица информации о клиенте:
====================================
...
====================================
Если это так, то обработку лучше всего свести к итерациям по объектам-записям, где вся необходимая информация будет занесена в соответствующие атрибуты.



Отредактировано (Дек. 30, 2008 12:03:11)

Офлайн

#9 Дек. 30, 2008 13:42:22

baloo
От:
Зарегистрирован: 2008-05-13
Сообщения: 59
Репутация: +  0  -
Профиль   Отправить e-mail  

Моя первая программа. Нужна критика.

Кстати, имею небольшой вопрос по рег. выражениям. Пытался за один раз выхватывать не только основные данные о клиенте, но и адрес (след строка после клиента), и страхователя (третья строка).
Делал строку regexp c включением знака конца строки и начала новой. Не работает. Дошло, что мы должны перебирать не line, а рассматривать текст целиком. не подскажете, как это оформить?

2ZAN: да, эти линии - разделители групп двойников.



Офлайн

#10 Дек. 30, 2008 14:54:57

Ed
От:
Зарегистрирован: 2008-12-13
Сообщения: 1032
Репутация: +  13  -
Профиль   Отправить e-mail  

Моя первая программа. Нужна критика.

Для работы за пределами строки используйте флаг MULTILINE.
Вот, например:

In [1]: import re
In [2]: line = 'line1.1 1\ncontinue: 2.1'
In [3]: re.findall('^line(.+) (.+)\n^continue: (.+)', line, re.MULTILINE)
Out[3]: [('1.1', '1', '2.1')]



Отредактировано (Дек. 30, 2008 14:57:43)

Офлайн

Board footer

Модераторировать

Powered by DjangoBB

Lo-Fi Version