supersam/scripts/check_autocall_window.py

101 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Детерминированная проверка окна обзвона автодозвона.
Проверяет, что автоматическая кампания НЕ ставит call-файлы вне рабочего окна
(часы и дни из панели «Автодозвон»), не звонит в выходные и звонит внутри окна.
Ничего не звонит: push_call_file, process_queue, backfill и выборка кандидатов
подменены, время — фейковое (модульная datetime). Запускать на .8 через обёртку
(она подставит DB_*):
bash /opt/supersam/scripts/sms_run_with_db.sh \
/opt/supersam/scripts/check_autocall_window.py [путь_к_проверяемому_файлу]
Без аргумента проверяется установленный /opt/supersam/scripts/autocall_scheduler.py
(можно проверять и файл-кандидат до установки). Зачем: инцидент 28.09.2026 —
окно проверялось только для ручной очереди, и робот позвонил клиенту в 21:03.
"""
import importlib.util, datetime as _dt, sys
from importlib.machinery import SourceFileLoader
PATH = sys.argv[1] if len(sys.argv) > 1 else "/opt/supersam/scripts/autocall_scheduler.py"
_loader = SourceFileLoader("acs", PATH)
spec = importlib.util.spec_from_loader("acs", _loader)
m = importlib.util.module_from_spec(spec)
_loader.exec_module(m)
MSK = _dt.timezone(_dt.timedelta(hours=3))
class FakeDT(_dt.datetime):
fixed = None
@classmethod
def now(cls, tz=None):
if tz is None:
return cls.fixed.astimezone(MSK).replace(tzinfo=None)
return cls.fixed.astimezone(tz)
m.datetime = FakeDT
FAKE_GROUP = {
"id": "00000000-0000-0000-0000-000000000000",
"customer_phone": "79000000000",
"customer_phone_normalized": "79000000000",
"town": "Симферополь",
"created_at": _dt.datetime(2026, 9, 28, 9, 0, tzinfo=_dt.timezone.utc),
"delivery_type": "pickup",
"attempt_count": 0,
"no_input_count": 0,
"sms_sent_at": _dt.datetime(2026, 9, 28, 8, 0, tzinfo=_dt.timezone.utc),
"notification_status": "first_sms_sent",
"delivery_status": "pending_confirmation",
"autocall_opt_out": False,
}
pushes = []
manual = []
def run_case(label, when_msk, expect_push, settings_patch=None):
FakeDT.fixed = when_msk.astimezone(MSK)
pushes.clear(); manual.clear()
def _fake_push(group, settings, attempt_no, is_test_call=False, **kw):
pushes.append(group["customer_phone"])
return True
m.push_call_file = _fake_push
m.process_queue = lambda conn, settings: (manual.append(1), 0)[1]
m.backfill_no_answer = lambda conn, settings: 0
m.get_groups_to_call = lambda conn, settings: [FAKE_GROUP]
m.trunk_free_slots = lambda settings: 1
m.trunk_capacity = lambda settings: 1
m.trunk_name = lambda settings: "goip5"
m.get_city_exclusion = lambda conn, town: None
m.get_exclusion_match = lambda conn, phone: None
if settings_patch:
orig = m.load_settings
def patched(conn):
s = orig(conn)
s.update(settings_patch)
return s
m.load_settings = patched
m.main()
ok = (len(pushes) > 0) == expect_push
print(f"{'PASS' if ok else 'FAIL'}: {label} → звонков {len(pushes)} (ожидали {'есть' if expect_push else 'нет'})")
return ok
results = []
results.append(run_case("понедельник 21:03 МСК (вне окна, инцидент)", _dt.datetime(2026, 9, 28, 21, 3, tzinfo=MSK), False))
results.append(run_case("понедельник 19:58 МСК (вне окна, инцидент)", _dt.datetime(2026, 9, 28, 19, 58, tzinfo=MSK), False))
results.append(run_case("понедельник 10:00 МСК (в окне)", _dt.datetime(2026, 9, 28, 10, 0, tzinfo=MSK), True))
results.append(run_case("понедельник 18:59 МСК (последняя минута окна)", _dt.datetime(2026, 9, 28, 18, 59, tzinfo=MSK), True))
results.append(run_case("воскресенье 12:00 МСК (нерабочий день)", _dt.datetime(2026, 9, 27, 12, 0, tzinfo=MSK), False))
results.append(run_case("суббота 12:00 МСК (рабочий день)", _dt.datetime(2026, 9, 26, 12, 0, tzinfo=MSK), True))
results.append(run_case("понедельник 09:00 МСК (граница окна)", _dt.datetime(2026, 9, 28, 9, 0, tzinfo=MSK), True))
print("\nИТОГ:", "ВСЕ PASS" if all(results) else "ЕСТЬ FAIL")
sys.exit(0 if all(results) else 1)