From 905b69f5685b252d4c19410a0e7a4113489138fc Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 25 Jun 2026 01:30:10 +0300 Subject: [PATCH] style: update weekly summary format to match user layout exactly --- miband_tracker/bot/app.py | 149 +++++++++++++++++++++++++++++++++++++- 1 file changed, 145 insertions(+), 4 deletions(-) diff --git a/miband_tracker/bot/app.py b/miband_tracker/bot/app.py index f204203..ebd9253 100644 --- a/miband_tracker/bot/app.py +++ b/miband_tracker/bot/app.py @@ -401,13 +401,155 @@ async def auto_refresh_main_menu_loop(app: Application) -> None: await asyncio.sleep(AUTO_MENU_REFRESH_INTERVAL) +def format_weekday(date_str: str) -> str: + try: + dt = datetime.strptime(date_str, "%Y-%m-%d") + wd = dt.weekday() + wd_ru = ["пн", "вт", "ср", "чт", "пт", "сб", "вс"] + return wd_ru[wd] + except Exception: + return "" + + +def average_bedtime(sleep_rows) -> float | None: + offsets = [] + for r in sleep_rows: + if not r or "start_time" not in r.keys() or r["start_time"] is None: + continue + try: + dt = datetime.fromtimestamp(r["start_time"], LOCAL_TZ) + minutes = dt.hour * 60 + dt.minute + if minutes > 720: + minutes -= 1440 + offsets.append(minutes) + except Exception: + pass + if not offsets: + return None + return sum(offsets) / len(offsets) + + +def format_bedtime(avg_offset: float | None) -> str: + if avg_offset is None: + return "N/A" + minutes = int(round(avg_offset)) + if minutes < 0: + minutes += 1440 + hour = (minutes // 60) % 24 + minute = minutes % 60 + return f"{hour:02d}:{minute:02d}" + + +def weekly_summary_text() -> str: + today = datetime.now(LOCAL_TZ).date() + start_curr = today - timedelta(days=6) + end_curr = today + + start_prev = today - timedelta(days=13) + end_prev = today - timedelta(days=7) + + # 1. Запросы текущей недели + steps_curr = fetch_all( + "SELECT date, total_steps FROM steps_daily WHERE date BETWEEN ? AND ? ORDER BY date DESC", + (start_curr.isoformat(), end_curr.isoformat()), + ) + sleep_curr = fetch_all( + "SELECT date, total_duration_min, start_time FROM sleep_daily WHERE date BETWEEN ? AND ? ORDER BY date DESC", + (start_curr.isoformat(), end_curr.isoformat()), + ) + + # 2. Запросы предыдущей недели + steps_prev = fetch_all( + "SELECT date, total_steps FROM steps_daily WHERE date BETWEEN ? AND ? ORDER BY date DESC", + (start_prev.isoformat(), end_prev.isoformat()), + ) + sleep_prev = fetch_all( + "SELECT date, total_duration_min, start_time FROM sleep_daily WHERE date BETWEEN ? AND ? ORDER BY date DESC", + (start_prev.isoformat(), end_prev.isoformat()), + ) + + # 3. Расчет текущих показателей + steps_list_curr = [int(r["total_steps"]) for r in steps_curr if r["total_steps"] is not None] + avg_steps_curr = round(sum(steps_list_curr) / len(steps_list_curr)) if steps_list_curr else 0 + + sleep_list_curr = [int(r["total_duration_min"]) for r in sleep_curr if r["total_duration_min"] is not None] + avg_sleep_curr = round(sum(sleep_list_curr) / len(sleep_list_curr)) if sleep_list_curr else 0 + avg_bedtime_curr = average_bedtime(sleep_curr) + + # 4. Расчет предыдущих показателей + steps_list_prev = [int(r["total_steps"]) for r in steps_prev if r["total_steps"] is not None] + avg_steps_prev = round(sum(steps_list_prev) / len(steps_list_prev)) if steps_list_prev else 0 + + sleep_list_prev = [int(r["total_duration_min"]) for r in sleep_prev if r["total_duration_min"] is not None] + avg_sleep_prev = round(sum(sleep_list_prev) / len(sleep_list_prev)) if sleep_list_prev else 0 + avg_bedtime_prev = average_bedtime(sleep_prev) + + # 5. Разницы + diff_steps_str = "" + if avg_steps_prev > 0: + diff_steps = avg_steps_curr - avg_steps_prev + diff_steps_str = f" ({'+' if diff_steps >= 0 else ''}{diff_steps} {'📈' if diff_steps >= 0 else '📉'})" + + diff_sleep_str = "" + if avg_sleep_prev > 0: + diff_sleep = avg_sleep_curr - avg_sleep_prev + diff_sleep_str = f" ({'+' if diff_sleep >= 0 else ''}{diff_sleep} мин {'📈' if diff_sleep >= 0 else '📉'})" + + diff_bedtime_str = "" + if avg_bedtime_prev is not None and avg_bedtime_curr is not None: + diff_bedtime = avg_bedtime_curr - avg_bedtime_prev + emoji = "📈" if diff_bedtime <= 0 else "📉" + sign = "-" if diff_bedtime <= 0 else "+" + diff_bedtime_str = f" ({sign}{abs(round(diff_bedtime))} мин {emoji})" + + # 6. Рекорды + record_steps_row = max(steps_curr, key=lambda r: r["total_steps"] or 0, default=None) + record_steps_str = "N/A" + if record_steps_row and record_steps_row["total_steps"] is not None: + wd = format_weekday(record_steps_row["date"]) + record_steps_str = f"{record_steps_row['total_steps']} ({wd})" + + record_sleep_row = max(sleep_curr, key=lambda r: r["total_duration_min"] or 0, default=None) + record_sleep_str = "N/A" + if record_sleep_row and record_sleep_row["total_duration_min"] is not None: + wd = format_weekday(record_sleep_row["date"]) + dur = record_sleep_row["total_duration_min"] + record_sleep_str = f"{dur // 60} ч {dur % 60} мин ({wd})" + + # 7. Форматирование диапазона дат + start_format = start_curr.strftime("%d") + end_format = end_curr.strftime("%d") + if start_curr.month == end_curr.month: + month_name = RU_MONTHS[start_curr.month - 1] + date_range = f"{start_format}–{end_format} {month_name}" + else: + start_month = RU_MONTHS[start_curr.month - 1] + end_month = RU_MONTHS[end_curr.month - 1] + date_range = f"{start_format} {start_month} — {end_format} {end_month}" + + # 8. Сон часы и минуты + avg_sleep_h = avg_sleep_curr // 60 + avg_sleep_m = avg_sleep_curr % 60 + sleep_time_str = f"{avg_sleep_h} ч {avg_sleep_m} мин" if avg_sleep_curr > 0 else "N/A" + + # 9. Сборка текста + lines = [ + f"📊 Итоги недели: {date_range}\n", + f"🚶 Шаги: {avg_steps_curr} в день{diff_steps_str}", + f"😴 Сон: {sleep_time_str}{diff_sleep_str}", + f"⏰ Засыпание: {format_bedtime(avg_bedtime_curr)}{diff_bedtime_str}\n", + f"🏆 Рекорды: • Шаги: {record_steps_str} • Сон: {record_sleep_str}" + ] + return "\n".join(lines) + + async def weekly_push_loop(app: Application) -> None: - """Send weekly summary push notification every Sunday at 12:00.""" + """Send weekly summary push notification every Sunday at 21:00.""" last_sent_date: str | None = None while True: try: now = datetime.now(LOCAL_TZ) - if now.weekday() == 6 and now.hour == 12 and now.minute == 0: + if now.weekday() == 6 and now.hour == 21 and now.minute == 0: current_date = now.strftime("%Y-%m-%d") if last_sent_date != current_date: last_sent_date = current_date @@ -417,8 +559,7 @@ async def weekly_push_loop(app: Application) -> None: token = current_user_id_var.set(uid) try: if health_db_exists(): - summary_text = period_text(7) - push_text = f"📊 Ваша сводка активности за неделю\n\n{summary_text}" + push_text = weekly_summary_text() await app.bot.send_message( chat_id=uid, text=push_text,