diff --git a/antifroud/check_fraud.py b/antifroud/check_fraud.py index 190358da..b6f3fabf 100644 --- a/antifroud/check_fraud.py +++ b/antifroud/check_fraud.py @@ -1,9 +1,11 @@ +import json from datetime import timedelta from urllib.parse import parse_qs from django.utils import timezone from django.db.models import Q +from django.db import connection from hotels.models import Reservation, Hotel -from .models import UserActivityLog, ViolationLog +from .models import UserActivityLog, ViolationLog, RoomDiscrepancy from touchh.utils.log import CustomLogger # Настройка логирования logger = CustomLogger(__name__).get_logger() @@ -18,9 +20,7 @@ class ReservationChecker: """ Инициализация времени проверки и списка нарушений. """ - self.start_time = timezone.now() - timedelta(days=30) - self.end_time = timezone.now() - self.violations = [] + self.checkin_diff_hours = 3 def log_info(self, message): logger.info(message) @@ -31,102 +31,71 @@ class ReservationChecker: def log_error(self, message): logger.error(message) - def fetch_user_logs(self): - try: - self.log_info("Начинается извлечение логов активности пользователей.") - user_logs = UserActivityLog.objects.filter(created__range=(self.start_time, self.end_time)) - self.log_info(f"Найдено {user_logs.count()} логов активности для анализа.") - return user_logs - except Exception as e: - self.log_error(f"Ошибка при извлечении логов активности: {e}") - return UserActivityLog.objects.none() - - def fetch_reservations(self): - try: - self.log_info("Начинается извлечение бронирований.") - reservations = Reservation.objects.filter( - Q(check_in__lte=self.end_time) & Q(check_out__gte=self.start_time) - ) - self.log_info(f"Найдено {reservations.count()} бронирований для анализа.") - return reservations - except Exception as e: - self.log_error(f"Ошибка при извлечении бронирований: {e}") - return Reservation.objects.none() - - def find_violations(self): - self.log_info("Начинается анализ несоответствий.") - user_logs = self.fetch_user_logs() - reservations = self.fetch_reservations() - - log_lookup = {} - for log in user_logs: - params = parse_qs(log.url_parameters or "") - hotel_id = params.get("utm_content", [None])[0] - room_number = params.get("utm_term", [None])[0] - if hotel_id and room_number: - key = (hotel_id, room_number) - log_lookup.setdefault(key, []).append(log) - - for reservation in reservations: - key = (reservation.hotel.hotel_id, reservation.room_number) - logs = log_lookup.get(key, []) - - if reservation.status == "заселен" and not logs: - self.record_violation( - hotel=reservation.hotel, - room_number=reservation.room_number, - violation_type="no_qr_scan", - details=f"Бронирование для номера {reservation.room_number} в отеле '{reservation.hotel.name}' " - f"не имеет записи сканирования QR." - ) - - for log in logs: - if log.created < reservation.check_in: - self.record_violation( - hotel=reservation.hotel, - room_number=reservation.room_number, - violation_type="early_check_in", - details=f"Раннее заселение: сканирование QR {log.created} раньше времени заезда " - f"{reservation.check_in} для номера {reservation.room_number}." - ) - - for (hotel_id, room_number), logs in log_lookup.items(): - matching_reservations = reservations.filter( - hotel__hotel_id=hotel_id, - room_number=room_number - ) - if not matching_reservations.exists(): - for log in logs: - self.record_violation( - hotel=Hotel.objects.filter(hotel_id=hotel_id).first(), - room_number=room_number, - violation_type="no_reservation", - details=f"Сканирование QR {log.created} для номера {room_number} в отеле с ID '{hotel_id}' " - f"не соответствует ни одному бронированию." - ) - - def record_violation(self, hotel, room_number, violation_type, details): - if hotel: - self.violations.append(ViolationLog( - hotel=hotel, - room_number=room_number, - violation_type=violation_type, - violation_details=details - )) - self.log_warning(f"Зафиксировано нарушение: {details}") - - def save_violations(self): - if self.violations: - ViolationLog.objects.bulk_create(self.violations) - self.log_info(f"Создано {len(self.violations)} записей в ViolationLog.") - else: - self.log_info("Нарушений не обнаружено.") - def run_check(self): - self.log_info(f"Запуск проверки с {self.start_time} по {self.end_time}.") + self.log_info(f"Запуск проверки.") try: - self.find_violations() - self.save_violations() + hotels_map = {} + hotels = Hotel.objects.all() + for hotel in hotels: + hotels_map[hotel.hotel_id] = hotel + + user_logs = UserActivityLog.objects.filter(fraud_checked=False) + reservations = Reservation.objects.filter(fraud_checked=False).select_related('hotel') + missing = list(reservations) + + violations = [] + check_in_diff = timedelta(hours=self.checkin_diff_hours) + + for user_log in user_logs: + try: + params = json.loads(user_log.url_parameters.replace("'", '"')) + hotel_id = params['utm_content'] + room = params['utm_term'] + reserv = next((x for x in reservations + if x.hotel.hotel_id == hotel_id and x.room_number == room + and user_log.date_time >= x.check_in - check_in_diff and user_log.date_time < x.check_out + ), None) + v_type = None + if reserv: + if reserv in missing: + missing.remove(reserv) + if user_log.date_time < reserv.check_in: + v_type = 'early' + if user_log.date_time > reserv.check_in + check_in_diff: + v_type = 'late' + else: + v_type = 'no_booking' + + if v_type: + violations.append(RoomDiscrepancy( + hotel=hotels_map[hotel_id], + room_number=room, + discrepancy_type=v_type, + booking_id=reserv.reservation_id if reserv else None, + check_in_date_expected=reserv.check_in if reserv else None, + check_in_date_actual=user_log.date_time, + )) + + user_log.fraud_checked = True + except Exception as e: + logger.error(e) + + for miss_reserv in missing: + violations.append(RoomDiscrepancy( + hotel=miss_reserv.hotel, + room_number=miss_reserv.room_number, + discrepancy_type='missed', + booking_id=miss_reserv.reservation_id, + check_in_date_expected=miss_reserv.check_in, + )) + for reserv in reservations: + reserv.fraud_checked = True + + RoomDiscrepancy.objects.bulk_create(violations) + UserActivityLog.objects.bulk_update(user_logs, ['fraud_checked'], 1000) + Reservation.objects.bulk_update(reservations, ['fraud_checked'], 1000) + + except Exception as e: self.log_error(f"Ошибка при выполнении проверки: {e}") self.log_info("Проверка завершена.") @@ -139,4 +108,4 @@ def run_reservation_check(): checker.run_check() except Exception as e: logger.error(f"Ошибка при запуске проверки: {e}") - logger.info("run_reservation_check завершена.") \ No newline at end of file + logger.info("run_reservation_check завершена.") diff --git a/antifroud/migrations/0005_roomdiscrepancy_fraud_checked_and_more.py b/antifroud/migrations/0005_roomdiscrepancy_fraud_checked_and_more.py new file mode 100644 index 00000000..7c458466 --- /dev/null +++ b/antifroud/migrations/0005_roomdiscrepancy_fraud_checked_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.1.4 on 2025-02-01 09:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('antifroud', '0004_alter_roomdiscrepancy_discrepancy_type'), + ] + + operations = [ + migrations.AddField( + model_name='roomdiscrepancy', + name='fraud_checked', + field=models.BooleanField(default=False, verbose_name='Проверено на несоответствия'), + ), + migrations.AddField( + model_name='useractivitylog', + name='fraud_checked', + field=models.BooleanField(default=False, verbose_name='Проверено на несоответствия'), + ), + ] diff --git a/antifroud/migrations/0006_alter_roomdiscrepancy_fraud_checked_and_more.py b/antifroud/migrations/0006_alter_roomdiscrepancy_fraud_checked_and_more.py new file mode 100644 index 00000000..8b8486a7 --- /dev/null +++ b/antifroud/migrations/0006_alter_roomdiscrepancy_fraud_checked_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.1.4 on 2025-02-01 09:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('antifroud', '0005_roomdiscrepancy_fraud_checked_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='roomdiscrepancy', + name='fraud_checked', + field=models.BooleanField(db_index=True, default=False, verbose_name='Проверено на несоответствия'), + ), + migrations.AlterField( + model_name='useractivitylog', + name='fraud_checked', + field=models.BooleanField(db_index=True, default=False, verbose_name='Проверено на несоответствия'), + ), + ] diff --git a/antifroud/migrations/0007_remove_roomdiscrepancy_fraud_checked.py b/antifroud/migrations/0007_remove_roomdiscrepancy_fraud_checked.py new file mode 100644 index 00000000..db4f7a53 --- /dev/null +++ b/antifroud/migrations/0007_remove_roomdiscrepancy_fraud_checked.py @@ -0,0 +1,17 @@ +# Generated by Django 5.1.4 on 2025-02-01 09:48 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('antifroud', '0006_alter_roomdiscrepancy_fraud_checked_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='roomdiscrepancy', + name='fraud_checked', + ), + ] diff --git a/antifroud/models.py b/antifroud/models.py index d0f996a0..ffefc37f 100644 --- a/antifroud/models.py +++ b/antifroud/models.py @@ -31,6 +31,7 @@ class UserActivityLog(models.Model): honeypot = models.BooleanField(verbose_name="Метка honeypot", blank=True, null=True) reply = models.BooleanField(verbose_name="Ответ пользователя", blank=True, null=True) page_url = models.URLField(blank=True, null=True, verbose_name="URL страницы") + fraud_checked = models.BooleanField(default=False, verbose_name="Проверено на несоответствия", db_index=True) @property def formatted_timestamp(self): diff --git a/hotels/migrations/0004_reservation_fraud_checked.py b/hotels/migrations/0004_reservation_fraud_checked.py new file mode 100644 index 00000000..cddb349b --- /dev/null +++ b/hotels/migrations/0004_reservation_fraud_checked.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.4 on 2025-02-01 09:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hotels', '0003_rename_external_id_hotel_external_id_pms_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='reservation', + name='fraud_checked', + field=models.BooleanField(db_index=True, default=False, verbose_name='Проверено на несоответствия'), + ), + ] diff --git a/hotels/models.py b/hotels/models.py index d2d9c9d6..1bf8e3b4 100644 --- a/hotels/models.py +++ b/hotels/models.py @@ -116,6 +116,7 @@ class Reservation(models.Model): status = models.CharField(max_length=50, verbose_name="Статус") price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name="Цена") discount = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name="Скидка") + fraud_checked = models.BooleanField(default=False, verbose_name="Проверено на несоответствия", db_index=True) def clean(self): if self.check_out and self.check_in and self.check_out <= self.check_in: diff --git a/tmp/tests.py b/tmp/tests.py new file mode 100644 index 00000000..f80ad1dc --- /dev/null +++ b/tmp/tests.py @@ -0,0 +1,18 @@ +import os +import django +import unittest +from antifroud.check_fraud import run_reservation_check + +# Установка переменной окружения для Django settings +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "your_project.settings") +django.setup() + +class FraudCheckTests(unittest.TestCase): + def test_run_reservation_check(self): + """Тест проверки бронирования на мошенничество""" + result = run_reservation_check() + self.assertIsNotNone(result) # Проверяем, что результат не None + self.assertIsInstance(result, dict) # Проверяем, что возвращается словарь + +if __name__ == "__main__": + unittest.main()