Skip to content

Commit 9a05389

Browse files
committed
chore(studies): Досктринги функций проверки обучений
1 parent 8fce562 commit 9a05389

File tree

1 file changed

+147
-50
lines changed

1 file changed

+147
-50
lines changed

tgbot/services/schedulers/studies.py

Lines changed: 147 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
1-
"""Studies scheduler for managing study session notifications.
2-
3-
Handles notifications for participants when there's less than a week before study dates.
1+
"""Планировщик уведомлений об обучениях.
2+
3+
Модуль отвечает за управление уведомлениями участников обучений.
4+
Автоматически отправляет напоминания за 2 часа и за 1 час до начала обучения.
5+
6+
Основная функциональность:
7+
- Парсинг Excel-файла с расписанием обучений
8+
- Отслеживание приближающихся обучений
9+
- Автоматическая рассылка уведомлений участникам
10+
- Логирование результатов отправки
11+
12+
Компоненты:
13+
- StudiesScheduler: Основной класс планировщика
14+
- check_upcoming_studies: Проверка приближающихся обучений
15+
- send_study_notifications: Рассылка уведомлений участникам
16+
- create_study_notification_message: Формирование текста уведомления
417
"""
518

619
import logging
@@ -23,21 +36,49 @@
2336

2437

2538
class StudiesScheduler(BaseScheduler):
26-
"""Studies scheduler for managing study session notifications
39+
"""Планировщик уведомлений об обучениях.
40+
41+
Управляет автоматической отправкой уведомлений участникам обучений
42+
за определенное время до начала сессии. Наследует базовый функционал
43+
от BaseScheduler и добавляет специфическую логику для работы с обучениями.
44+
45+
Основные возможности:
46+
- Автоматическая проверка приближающихся обучений каждые 30 минут
47+
- Отправка уведомлений за 2 часа и за 1 час до начала обучения
48+
- Парсинг данных об обучениях из Excel-файла
49+
- Поиск участников в базе данных и отправка персональных уведомлений
50+
- Логирование всех операций с детализацией по участникам
2751
28-
Manages notifications for study participants when there's less than a week
29-
before the study date.
52+
Attributes:
53+
studies_parser: Экземпляр StudiesScheduleParser для обработки файла обучений
54+
55+
Note:
56+
Класс автоматически запускается при инициализации планировщика
57+
и работает в фоновом режиме, проверяя наличие файла uploads/Обучения.xlsx
3058
"""
3159

3260
def __init__(self):
3361
super().__init__("Обучения")
3462
self.studies_parser = StudiesScheduleParser()
3563

3664
def setup_jobs(self, scheduler: AsyncIOScheduler, session_pool, bot: Bot):
37-
"""Setup all studies-related jobs"""
65+
"""Настраивает задачи планировщика для уведомлений об обучениях.
66+
67+
Регистрирует в планировщике задачу автоматической проверки
68+
приближающихся обучений. Задача выполняется каждые 30 минут
69+
и проверяет необходимость отправки уведомлений.
70+
71+
Args:
72+
scheduler: Планировщик задач APScheduler для регистрации заданий
73+
session_pool: Пул соединений с базой данных для операций с участниками
74+
bot: Экземпляр бота для отправки уведомлений пользователям
75+
76+
Note:
77+
Метод переопределяет базовую реализацию BaseScheduler
78+
и добавляет специфичную для обучений логику планирования.
79+
"""
3880
self.logger.info("Настройка задач уведомлений об обучениях...")
3981

40-
# Проверка приближающихся обучений
4182
scheduler.add_job(
4283
func=self._check_upcoming_studies_job,
4384
args=[session_pool, bot],
@@ -48,7 +89,23 @@ def setup_jobs(self, scheduler: AsyncIOScheduler, session_pool, bot: Bot):
4889
)
4990

5091
async def _check_upcoming_studies_job(self, session_pool, bot: Bot):
51-
"""Wrapper for checking upcoming studies"""
92+
"""Обертка для проверки приближающихся обучений.
93+
94+
Выполняет проверку приближающихся обучений с логированием начала
95+
и завершения работы. Обрабатывает исключения и записывает результаты
96+
выполнения задачи.
97+
98+
Args:
99+
session_pool: Пул соединений с базой данных
100+
bot: Экземпляр бота для отправки уведомлений
101+
102+
Returns:
103+
dict: Результат выполнения проверки или None при ошибке
104+
105+
Note:
106+
Метод является оберткой вокруг основной функции check_upcoming_studies
107+
и добавляет логирование согласно стандартам BaseScheduler.
108+
"""
52109
self._log_job_execution_start("Проверка предстоящих обучений")
53110
try:
54111
result = await check_upcoming_studies(session_pool, bot)
@@ -61,17 +118,33 @@ async def _check_upcoming_studies_job(self, session_pool, bot: Bot):
61118

62119

63120
async def check_upcoming_studies(session_pool, bot: Bot):
64-
"""Check for upcoming studies and notify participants if less than a week away
121+
"""Проверяет приближающиеся обучения и отправляет уведомления участникам.
122+
123+
Основная функция для проверки обучений из Excel-файла и отправки
124+
уведомлений участникам за 2 часа и за 1 час до начала обучения.
125+
Работает с окном в 10 минут для каждого типа уведомления.
65126
66127
Args:
67-
session_pool: Database session pool
68-
bot: Bot instance for sending notifications
128+
session_pool: Пул соединений с базой данных для поиска участников
129+
bot: Экземпляр бота Telegram для отправки уведомлений
69130
70131
Returns:
71-
Dict with notification results
132+
dict: Словарь с результатами выполнения:
133+
- status: "success" или "error"
134+
- message: Сообщение о результате (при ошибке)
135+
- sessions: Количество обнаруженных обучений (при успехе)
136+
- notifications: Общее количество отправленных уведомлений
137+
- results: Детализация по сессиям
138+
139+
Raises:
140+
Exception: При критических ошибках парсинга файла или отправки уведомлений
141+
142+
Note:
143+
- Ожидает файл по пути uploads/Обучения.xlsx
144+
- Отправляет уведомления в окне ±10 минут от целевого времени
145+
- Логирует все операции для мониторинга работы системы
72146
"""
73147
try:
74-
# Get all studies from the file
75148
studies_parser = StudiesScheduleParser()
76149
file_path = Path("uploads/Обучения.xlsx")
77150

@@ -85,21 +158,17 @@ async def check_upcoming_studies(session_pool, bot: Bot):
85158
logger.info("[Обучения] No study sessions found in file")
86159
return {"status": "success", "message": "No study sessions found"}
87160

88-
# Filter sessions for notifications: 2 hours before OR 1 hour before
89161
now = datetime.now()
90162

91163
upcoming_sessions = []
92164
for session in all_sessions:
93-
# Calculate time difference
94165
time_diff = session.date - now
95166

96-
# Check if it's exactly 2 hours before (within 10 minutes window)
97167
two_hours_before = timedelta(hours=2)
98168
if abs(time_diff - two_hours_before) <= timedelta(minutes=10):
99169
upcoming_sessions.append(session)
100170
continue
101171

102-
# Check if it's exactly 1 hour before (within 10 minutes window)
103172
one_hour_before = timedelta(hours=1)
104173
if abs(time_diff - one_hour_before) <= timedelta(minutes=10):
105174
upcoming_sessions.append(session)
@@ -114,12 +183,10 @@ async def check_upcoming_studies(session_pool, bot: Bot):
114183
f"[Обучения] Найдено {len(upcoming_sessions)} приближающихся обучений"
115184
)
116185

117-
# Send notifications to participants
118186
notification_results = await send_study_notifications(
119187
upcoming_sessions, session_pool, bot
120188
)
121189

122-
# Log summary
123190
total_notifications = sum(notification_results.values())
124191
logger.info(
125192
f"[Обучения] Отправлено {total_notifications} уведомлений для {len(upcoming_sessions)} обучений"
@@ -140,15 +207,28 @@ async def check_upcoming_studies(session_pool, bot: Bot):
140207
async def send_study_notifications(
141208
sessions: List[StudySession], session_pool, bot: Bot
142209
) -> dict:
143-
"""Send notifications to study participants
210+
"""Отправляет уведомления участникам обучений.
211+
212+
Обрабатывает список сессий обучений и отправляет персональные
213+
уведомления всем участникам, найденным в базе данных.
214+
Избегает дублирования участников и отправляет уведомления
215+
только реальным участникам (не руководителям).
144216
145217
Args:
146-
sessions: List of upcoming study sessions
147-
session_pool: Database session pool
148-
bot: Bot instance
218+
sessions: Список сессий обучений для обработки
219+
session_pool: Пул соединений с базой данных для поиска участников
220+
bot: Экземпляр бота Telegram для отправки сообщений
149221
150222
Returns:
151-
Dict with notification results per session
223+
dict: Словарь с результатами отправки по каждой сессии,
224+
где ключ - уникальный идентификатор сессии (дата_название),
225+
значение - количество успешно отправленных уведомлений
226+
227+
Note:
228+
- Извлекает участников только из поля ФИО (исключая руководителей из РГ)
229+
- Использует set для исключения дублирования участников
230+
- Логирует подробную информацию о каждом этапе отправки
231+
- Пропускает участников без user_id в базе данных
152232
"""
153233
notification_results = {}
154234

@@ -159,12 +239,8 @@ async def send_study_notifications(
159239
session_key = f"{session_obj.date.strftime('%d.%m.%Y')}_{session_obj.title}"
160240
notifications_sent = 0
161241

162-
# Get unique participant names (avoid duplicates)
163-
# Only extract names from the ФИО field (column 2) - these are the actual participants
164-
# The РГ field (column 3) contains heads/supervisors who should NOT be notified
165242
participant_names: Set[str] = set()
166243
for area, name, rg, attendance, reason in session_obj.participants:
167-
# Add name from ФИО field (column 2) - actual participants
168244
if name and name.strip():
169245
participant_names.add(name.strip())
170246

@@ -175,11 +251,9 @@ async def send_study_notifications(
175251
f"[Обучения] Найдено {len(participant_names)} уникальных участников: {list(participant_names)}"
176252
)
177253

178-
# Send notification to each participant
179254
for participant_name in participant_names:
180255
try:
181-
# Find participant in database
182-
participant = await stp_repo.employee.get_user(
256+
participant = await stp_repo.employee.get_users(
183257
fullname=participant_name
184258
)
185259

@@ -195,15 +269,12 @@ async def send_study_notifications(
195269
)
196270
continue
197271

198-
# Calculate time difference to determine notification type
199272
time_diff = session_obj.date - datetime.now()
200273

201-
# Create notification message
202274
message = await create_study_notification_message(
203275
session_obj, stp_repo, time_diff
204276
)
205277

206-
# Send notification
207278
success = await send_message(bot, participant.user_id, message)
208279

209280
if success:
@@ -233,25 +304,38 @@ async def send_study_notifications(
233304
async def create_study_notification_message(
234305
session: StudySession, stp_repo, time_diff: timedelta
235306
) -> str:
236-
"""Create notification message for study participant
307+
"""Формирует текст уведомления об обучении для участника.
308+
309+
Создает персонализированное сообщение с информацией об обучении,
310+
включая динамический текст времени, ссылку на тренера (если доступна)
311+
и правила посещения обучений.
237312
238313
Args:
239-
session: Study session object
240-
stp_repo: Repository for database operations
241-
time_diff: Time difference until the session
314+
session: Объект сессии обучения с данными о дате, теме, тренере
315+
stp_repo: Репозиторий для операций с базой данных
316+
time_diff: Разница во времени до начала обучения
242317
243318
Returns:
244-
Formatted notification message
319+
str: Отформатированное HTML-сообщение для отправки в Telegram
320+
321+
Note:
322+
- Автоматически определяет тип уведомления (2 часа/1 час/другое)
323+
- Пытается создать ссылку на профиль тренера в Telegram
324+
- Включает блок с правилами посещения обучений
325+
- Использует HTML-разметку для красивого отображения
326+
327+
Examples:
328+
Для обучения через 2 часа:
329+
"Напоминаем, что через 2 часа у тебя запланировано обучение..."
330+
331+
Для обучения через 1 час:
332+
"Напоминаем, что через 1 час у тебя запланировано обучение..."
245333
"""
246-
# Determine notification type based on time difference
247334
if abs(time_diff - timedelta(hours=2)) <= timedelta(minutes=10):
248-
# 2 hours before notification
249335
time_text = "через 2 часа"
250336
elif abs(time_diff - timedelta(hours=1)) <= timedelta(minutes=10):
251-
# 1 hour before notification
252337
time_text = "через 1 час"
253338
else:
254-
# Fallback (shouldn't happen with new logic)
255339
days_until = (session.date.date() - datetime.now().date()).days
256340
if days_until == 0:
257341
time_text = "сегодня"
@@ -260,11 +344,10 @@ async def create_study_notification_message(
260344
else:
261345
time_text = f"через {days_until} дн."
262346

263-
# Get trainer information from database
264347
trainer_text = session.trainer
265348
if session.trainer:
266349
try:
267-
trainer_user = await stp_repo.employee.get_user(fullname=session.trainer)
350+
trainer_user = await stp_repo.employee.get_users(fullname=session.trainer)
268351
if trainer_user and trainer_user.username:
269352
trainer_text = (
270353
f"<a href='t.me/{trainer_user.username}'>{session.trainer}</a>"
@@ -293,20 +376,34 @@ async def create_study_notification_message(
293376

294377

295378
def format_studies_notification_summary(sessions: List[StudySession]) -> str:
296-
"""Format brief summary of upcoming studies for logs
379+
"""Форматирует краткую сводку предстоящих обучений для логов.
380+
381+
Создает компактное описание списка обучений, группируя их по датам
382+
для удобного анализа в логах системы. Используется для мониторинга
383+
и отладки работы планировщика уведомлений.
297384
298385
Args:
299-
sessions: List of upcoming study sessions
386+
sessions: Список предстоящих сессий обучений
300387
301388
Returns:
302-
Brief summary string
389+
str: Краткая строка-сводка с количеством обучений по датам
390+
391+
Examples:
392+
Пустой список:
393+
"No upcoming studies found"
394+
395+
Несколько обучений:
396+
"Upcoming studies: 3, • 15.11.2025: 2 session(s), • 16.11.2025: 1 session(s)"
397+
398+
Note:
399+
Функция не отправляет уведомления, а только формирует текст для логирования.
400+
Используется для получения быстрого обзора предстоящих обучений.
303401
"""
304402
if not sessions:
305403
return "No upcoming studies found"
306404

307405
summary_parts = [f"Upcoming studies: {len(sessions)}"]
308406

309-
# Group by date
310407
dates = {}
311408
for session in sessions:
312409
date_str = session.date.strftime("%d.%m.%Y")

0 commit comments

Comments
 (0)