417 lines
16 KiB
Python
417 lines
16 KiB
Python
import json
|
||
import sys
|
||
from datetime import datetime
|
||
from collections import defaultdict
|
||
|
||
|
||
def zagruzit_tranzaktsii(filepath):
|
||
transaktsii = []
|
||
|
||
try:
|
||
with open(filepath, 'r', encoding='utf-8') as f:
|
||
for num, line in enumerate(f, 1):
|
||
line = line.strip()
|
||
if not line:
|
||
continue
|
||
|
||
parts = line.split('|')
|
||
|
||
if len(parts) != 7:
|
||
print(f'Ошибка в строке {num}: ожидается 7 полей, найдено {len(parts)}. Строка: {line[:50]}...', file=sys.stderr)
|
||
continue
|
||
|
||
try:
|
||
transaktsiya = {
|
||
'transaction_id': parts[0].strip(),
|
||
'user_id': int(parts[1].strip()),
|
||
'amount': float(parts[2].strip()),
|
||
'category': parts[3].strip(),
|
||
'timestamp': parts[4].strip(),
|
||
'is_fraud': parts[5].strip().lower() == 'true',
|
||
'card_last_digits': parts[6].strip()
|
||
}
|
||
transaktsii.append(transaktsiya)
|
||
except ValueError as e:
|
||
print(f'Ошибка в строке {num}: неверный тип данных - {e}', file=sys.stderr)
|
||
continue
|
||
|
||
except FileNotFoundError:
|
||
print(f'Файл {filepath} не найден', file=sys.stderr)
|
||
return []
|
||
except Exception as e:
|
||
print(f'Ошибка при чтении файла: {e}', file=sys.stderr)
|
||
return []
|
||
|
||
return transaktsii
|
||
|
||
|
||
def filtr_po_kategorii(transaktsii, kategorii):
|
||
resultat = []
|
||
kategorii_nizhnij = [kat.lower() for kat in kategorii]
|
||
|
||
for t in transaktsii:
|
||
if t.get('category', '').lower() in kategorii_nizhnij:
|
||
resultat.append(t)
|
||
return resultat
|
||
|
||
|
||
def filtr_po_summe(transaktsii, min_summa, max_summa):
|
||
otfiltrovannye = []
|
||
for t in transaktsii:
|
||
summa = t.get('amount', 0)
|
||
if min_summa <= summa <= max_summa:
|
||
otfiltrovannye.append(t)
|
||
return otfiltrovannye
|
||
|
||
|
||
def filtr_moshennicheskih(transaktsii):
|
||
moshenniki = []
|
||
for t in transaktsii:
|
||
if t.get('is_fraud') == True:
|
||
moshenniki.append(t)
|
||
|
||
#сорт по убыванию суммы
|
||
moshenniki.sort(key=lambda x: x.get('amount', 0), reverse=True)
|
||
return moshenniki
|
||
|
||
|
||
def rassch_traty_po_kategoriyam(transaktsii, id_polzovatelya):
|
||
traty = {}
|
||
|
||
for t in transaktsii:
|
||
if t.get('user_id') == id_polzovatelya and t.get('is_fraud') == False:
|
||
kat = t.get('category', 'Другое')
|
||
summa = t.get('amount', 0)
|
||
|
||
if kat in traty:
|
||
traty[kat] += summa
|
||
else:
|
||
traty[kat] = summa
|
||
|
||
return traty
|
||
|
||
|
||
def top_kategorii(transaktsii, n):
|
||
summy_kategorij = {}
|
||
|
||
for t in transaktsii:
|
||
kat = t.get('category', 'Другое')
|
||
summa = t.get('amount', 0)
|
||
|
||
if kat in summy_kategorij:
|
||
summy_kategorij[kat] += summa
|
||
else:
|
||
summy_kategorij[kat] = summa
|
||
|
||
#список пар сортировк
|
||
elementy = list(summy_kategorij.items())
|
||
elementy.sort(key=lambda x: x[1], reverse=True)
|
||
|
||
return elementy[:n]
|
||
|
||
|
||
def obnaruzhit_podozritelnye(transaktsii, id_polzovatelya, porog):
|
||
traty_polzovatelya = []
|
||
for t in transaktsii:
|
||
if t.get('user_id') == id_polzovatelya and t.get('is_fraud') == False:
|
||
traty_polzovatelya.append(t)
|
||
|
||
if len(traty_polzovatelya) < 3:
|
||
return []
|
||
|
||
vsego = 0
|
||
for t in traty_polzovatelya:
|
||
vsego += t.get('amount', 0)
|
||
srednyaya = vsego / len(traty_polzovatelya)
|
||
|
||
porogovoe_znachenie = srednyaya * porog
|
||
podozritelnye = []
|
||
for t in traty_polzovatelya:
|
||
if t.get('amount', 0) > porogovoe_znachenie:
|
||
podozritelnye.append(t)
|
||
|
||
# убыван суммы
|
||
podozritelnye.sort(key=lambda x: x.get('amount', 0), reverse=True)
|
||
return podozritelnye
|
||
|
||
|
||
def gruppirovat_po_dnyam(transaktsii):
|
||
dni = {}
|
||
|
||
for t in transaktsii:
|
||
|
||
ts = t.get('timestamp', '')
|
||
if ts:
|
||
data = ts.split()[0]
|
||
else:
|
||
data = '1970-01-01'
|
||
|
||
summa = t.get('amount', 0)
|
||
|
||
if data not in dni:
|
||
dni[data] = {
|
||
'total_amount': 0.0,
|
||
'transaction_count': 0,
|
||
'avg_amount': 0.0
|
||
}
|
||
|
||
dni[data]['total_amount'] += summa
|
||
dni[data]['transaction_count'] += 1
|
||
|
||
# среднее дня
|
||
for data in dni:
|
||
kolvo = dni[data]['transaction_count']
|
||
if kolvo > 0:
|
||
dni[data]['avg_amount'] = dni[data]['total_amount'] / kolvo
|
||
|
||
return dni
|
||
|
||
|
||
def gruppirovat_po_polzovatelyam(transaktsii):
|
||
polzovateli = {}
|
||
|
||
for t in transaktsii:
|
||
uid = t.get('user_id')
|
||
if uid is None:
|
||
continue
|
||
|
||
if uid not in polzovateli:
|
||
polzovateli[uid] = []
|
||
|
||
polzovateli[uid].append(t)
|
||
|
||
# сорт по времени
|
||
for uid in polzovateli:
|
||
polzovateli[uid].sort(key=lambda x: x.get('timestamp', '1970-01-01 00:00:00'))
|
||
|
||
return polzovateli
|
||
|
||
|
||
def sozdat_otchet(transaktsii, output_file):
|
||
try:
|
||
vsego_tranzaktsij = len(transaktsii)
|
||
vsego_summa = 0
|
||
moshen_tranzaktsij = 0
|
||
moshen_summa = 0
|
||
|
||
for t in transaktsii:
|
||
summa = t.get('amount', 0)
|
||
vsego_summa += summa
|
||
|
||
if t.get('is_fraud'):
|
||
moshen_tranzaktsij += 1
|
||
moshen_summa += summa
|
||
|
||
obych_tranzaktsij = vsego_tranzaktsij - moshen_tranzaktsij
|
||
obych_summa = vsego_summa - moshen_summa
|
||
|
||
top_kategorij = top_kategorii(transaktsii, 5)
|
||
|
||
kolvo_po_polzovatelyam = {}
|
||
for t in transaktsii:
|
||
uid = t.get('user_id')
|
||
if uid:
|
||
kolvo_po_polzovatelyam[uid] = kolvo_po_polzovatelyam.get(uid, 0) + 1
|
||
|
||
top_polzovateli = sorted(kolvo_po_polzovatelyam.items(), key=lambda x: x[1], reverse=True)[:3]
|
||
|
||
unikalnye_polzovateli = len(set([t.get('user_id') for t in transaktsii if t.get('user_id')]))
|
||
|
||
po_dnyam = gruppirovat_po_dnyam(transaktsii)
|
||
pervye_7_dnej = dict(list(po_dnyam.items())[:7])
|
||
|
||
otchet = []
|
||
otchet.append('=' * 60)
|
||
otchet.append('ФИНАНСОВЫЙ ОТЧЕТ')
|
||
otchet.append('=' * 60)
|
||
otchet.append('')
|
||
otchet.append(f'Всего транзакций: {vsego_tranzaktsij}')
|
||
otchet.append(f'Общая сумма: {vsego_summa:,.2f} руб.')
|
||
otchet.append('')
|
||
otchet.append('Обычные транзакции:')
|
||
otchet.append(f' Количество: {obych_tranzaktsij}')
|
||
otchet.append(f' Сумма: {obych_summa:,.2f} руб.')
|
||
otchet.append('')
|
||
otchet.append('Мошеннические транзакции:')
|
||
otchet.append(f' Количество: {moshen_tranzaktsij}')
|
||
otchet.append(f' Сумма: {moshen_summa:,.2f} руб.')
|
||
otchet.append('')
|
||
otchet.append('ТОП-5 КАТЕГОРИЙ ПО ТРАТАМ:')
|
||
|
||
for i, (kat, summa) in enumerate(top_kategorij, 1):
|
||
otchet.append(f' {i}. {kat}: {summa:,.2f} руб.')
|
||
|
||
otchet.append('')
|
||
otchet.append('ТОП-3 ПОЛЬЗОВАТЕЛЯ ПО КОЛИЧЕСТВУ ТРАНЗАКЦИЙ:')
|
||
|
||
for i, (uid, kol) in enumerate(top_polzovateli, 1):
|
||
otchet.append(f' {i}. Пользователь {uid}: {kol} транзакций')
|
||
|
||
otchet.append('')
|
||
otchet.append(f'Уникальных пользователей: {unikalnye_polzovateli}')
|
||
otchet.append('')
|
||
otchet.append('СТАТИСТИКА ПО ДНЯМ (первые 7 дней):')
|
||
|
||
for data in sorted(pervye_7_dnej.keys()):
|
||
stat = pervye_7_dnej[data]
|
||
otchet.append(f' {data}:')
|
||
otchet.append(f' Сумма: {stat["total_amount"]:,.2f} руб.')
|
||
otchet.append(f' Кол-во: {stat["transaction_count"]}')
|
||
otchet.append(f' Средний чек: {stat["avg_amount"]:,.2f} руб.')
|
||
|
||
otchet.append('')
|
||
otchet.append('=' * 60)
|
||
|
||
# сохран
|
||
with open(output_file, 'w', encoding='utf-8') as f:
|
||
f.write('\n'.join(otchet))
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f'Ошибка при сохранении отчета: {e}', file=sys.stderr)
|
||
return False
|
||
|
||
|
||
def main():
|
||
print('Запуск программы анализа транзакций...')
|
||
print('-' * 50)
|
||
|
||
transaktsii = zagruzit_tranzaktsii('data.txt')
|
||
|
||
if not transaktsii:
|
||
print('Не удалось загрузить транзакции. Программа завершена.')
|
||
print('\nФайл data.txt должен содержать строки в формате:')
|
||
print('ID_транзакции|ID_пользователя|Сумма|Категория|Время|is_fraud|Последние_4_цифры')
|
||
print('Пример:')
|
||
print('tx001|1|1500.50|Продукты|2024-01-15 10:30:00|False|1234')
|
||
return
|
||
|
||
print(f'Загружено транзакций: {len(transaktsii)}')
|
||
|
||
kategorii = ['Продукты', 'Кафе', 'Транспорт']
|
||
otfiltrovannye_po_kat = filtr_po_kategorii(transaktsii, kategorii)
|
||
print(f'После фильтрации по категориям {kategorii}: {len(otfiltrovannye_po_kat)} транзакций')
|
||
|
||
otfiltrovannye_po_summe = filtr_po_summe(otfiltrovannye_po_kat, 500, 5000)
|
||
print(f'После фильтрации по сумме (500-5000 руб.): {len(otfiltrovannye_po_summe)} транзакций')
|
||
|
||
moshenniki = filtr_moshennicheskih(transaktsii)
|
||
summa_moshennikov = sum(t.get('amount', 0) for t in moshenniki)
|
||
|
||
print(f'\nМошеннических транзакций: {len(moshenniki)}')
|
||
print(f'Общая сумма мошеннических транзакций: {summa_moshennikov:,.2f} руб.')
|
||
|
||
if moshenniki:
|
||
print('\nТоп-3 мошеннических транзакции по сумме:')
|
||
for i, t in enumerate(moshenniki[:3], 1):
|
||
print(f' {i}. ID: {t["transaction_id"]}, Сумма: {t["amount"]} руб., Категория: {t["category"]}')
|
||
|
||
if transaktsii:
|
||
primernyj_polzovatel = transaktsii[0].get('user_id')
|
||
|
||
raskhody = rassch_traty_po_kategoriyam(transaktsii, primernyj_polzovatel)
|
||
print(f'\nРасходы пользователя {primernyj_polzovatel} по категориям:')
|
||
if raskhody:
|
||
for kat, summa in raskhody.items():
|
||
print(f' {kat}: {summa:,.2f} руб.')
|
||
else:
|
||
print(' Нет расходов (или все транзакции мошеннические)')
|
||
|
||
podozritelnye = obnaruzhit_podozritelnye(transaktsii, primernyj_polzovatel, 1.5)
|
||
print(f'\nПодозрительно крупные транзакции пользователя {primernyj_polzovatel} (среднее * 1.5):')
|
||
if podozritelnye:
|
||
for t in podozritelnye:
|
||
print(f' {t["timestamp"]}: {t["amount"]} руб. - {t["category"]}')
|
||
else:
|
||
print(' Не найдено')
|
||
|
||
print('\n' + '=' * 50)
|
||
print('АГРЕГИРОВАННАЯ СТАТИСТИКА')
|
||
print('=' * 50)
|
||
|
||
top_kat = top_kategorii(transaktsii, 5)
|
||
print('\nТоп-5 категорий по сумме трат:')
|
||
for i, (kat, summa) in enumerate(top_kat, 1):
|
||
print(f' {i}. {kat}: {summa:,.2f} руб.')
|
||
|
||
po_dnyam = gruppirovat_po_dnyam(transaktsii)
|
||
print('\nСтатистика за первые 3 дня:')
|
||
for i, (data, stat) in enumerate(list(po_dnyam.items())[:3]):
|
||
print(f' {data}:')
|
||
print(f' Сумма: {stat["total_amount"]:,.2f} руб.')
|
||
print(f' Кол-во: {stat["transaction_count"]}')
|
||
print(f' Средний чек: {stat["avg_amount"]:,.2f} руб.')
|
||
|
||
polzovateli_s_tranzaktsiyami = gruppirovat_po_polzovatelyam(transaktsii)
|
||
kolvo_po_polz = [(uid, len(txs)) for uid, txs in polzovateli_s_tranzaktsiyami.items()]
|
||
kolvo_po_polz.sort(key=lambda x: x[1], reverse=True)
|
||
|
||
print('\nТоп-5 пользователей по количеству транзакций:')
|
||
for i, (uid, kol) in enumerate(kolvo_po_polz[:5], 1):
|
||
print(f' {i}. Пользователь {uid}: {kol} транзакций')
|
||
|
||
print('\n' + '=' * 50)
|
||
uspekh = sozdat_otchet(transaktsii, 'financial_report.txt')
|
||
|
||
if uspekh:
|
||
print('Отчет успешно сохранен в файл financial_report.txt')
|
||
else:
|
||
print('Ошибка при сохранении отчета')
|
||
|
||
print('\n' + '=' * 50)
|
||
print('ДОПОЛНИТЕЛЬНЫЙ АНАЛИЗ')
|
||
print('=' * 50)
|
||
|
||
moshenniki_po_polz = defaultdict(float)
|
||
for t in moshenniki:
|
||
moshenniki_po_polz[t.get('user_id')] += t.get('amount', 0)
|
||
|
||
if moshenniki_po_polz:
|
||
top_moshennik = max(moshenniki_po_polz.items(), key=lambda x: x[1])
|
||
print(f'\nПользователь с максимальной суммой мошеннических транзакций:')
|
||
print(f' ID: {top_moshennik[0]}, Сумма: {top_moshennik[1]:,.2f} руб.')
|
||
|
||
if po_dnyam:
|
||
samyy_nagruzhennyy_den = max(po_dnyam.items(), key=lambda x: x[1]['transaction_count'])
|
||
print(f'\nДень с наибольшим количеством транзакций:')
|
||
print(f' {samyy_nagruzhennyy_den[0]}: {samyy_nagruzhennyy_den[1]["transaction_count"]} транзакций')
|
||
|
||
statistika_po_dnyam_nedeli = defaultdict(lambda: {'vsego': 0, 'kolvo': 0})
|
||
for t in transaktsii:
|
||
ts = t.get('timestamp')
|
||
if ts:
|
||
try:
|
||
dt = datetime.strptime(ts.split()[0], '%Y-%m-%d')
|
||
den_nedeli = dt.strftime('%A')
|
||
summa = t.get('amount', 0)
|
||
statistika_po_dnyam_nedeli[den_nedeli]['vsego'] += summa
|
||
statistika_po_dnyam_nedeli[den_nedeli]['kolvo'] += 1
|
||
except:
|
||
pass
|
||
|
||
if statistika_po_dnyam_nedeli:
|
||
print('\nСредний чек по дням недели:')
|
||
poryadok_dnej = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
|
||
russkie_dni = {
|
||
'Monday': 'Понедельник',
|
||
'Tuesday': 'Вторник',
|
||
'Wednesday': 'Среда',
|
||
'Thursday': 'Четверг',
|
||
'Friday': 'Пятница',
|
||
'Saturday': 'Суббота',
|
||
'Sunday': 'Воскресенье'
|
||
}
|
||
|
||
for eng_den in poryadok_dnej:
|
||
if eng_den in statistika_po_dnyam_nedeli:
|
||
stat = statistika_po_dnyam_nedeli[eng_den]
|
||
sredniy = stat['vsego'] / stat['kolvo'] if stat['kolvo'] > 0 else 0
|
||
print(f' {russkie_dni[eng_den]}: {sredniy:,.2f} руб. ({stat["kolvo"]} транзакций)')
|
||
|
||
print('\n' + '=' * 50)
|
||
print('Программа завершена!')
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main() |