added advanced workout

This commit is contained in:
2026-07-01 11:20:13 +03:00
parent 32c211676f
commit 1c61bb4102
38 changed files with 2044 additions and 376 deletions
+53 -27
View File
@@ -7,12 +7,13 @@ import httpx
from sqlalchemy import select
from app.config import get_settings
from app.daily.recap import LLM_TIMEOUT_SEC, RecapService
from app.daily.schedule import resolve_evening_schedule
from app.db.base import SessionLocal
from app.db.models import User
from app.homelab.comfyui import ComfyUIClient
from app.homelab.context import resolve_timezone
from app.homelab.digest import build_morning_digest
from app.homelab.evening_summary import LLM_TIMEOUT_SEC, build_evening_notice
from app.homelab.monitoring import check_netdata_alerts
from app.homelab_scoped.notices import post_chat_notice
from app.homelab.state import get_state, set_state
@@ -30,6 +31,7 @@ async def homelab_watcher_loop() -> None:
await asyncio.sleep(WATCH_INTERVAL_SEC)
await _tick_morning_digest()
await _tick_evening_summary()
await _tick_evening_summary_retries()
settings = get_settings()
_netdata_tick += WATCH_INTERVAL_SEC
if _netdata_tick >= settings.netdata_poll_interval_sec:
@@ -97,6 +99,33 @@ async def _comfyui_reachable(base_url: str) -> bool:
return False
async def _run_evening_recap(db, user: User, day, tz: ZoneInfo, *, scheduled: bool) -> None:
settings = get_settings()
schedule = resolve_evening_schedule(db, user.id)
if not schedule.enabled:
return
image_enabled = settings.evening_summary_image_enabled and settings.comfyui_enabled
if image_enabled:
client = ComfyUIClient()
if not await _comfyui_reachable(client.base_url):
logger.warning("evening_recap ComfyUI unreachable user=%s text_only", user.id)
image_enabled = False
recap = RecapService(db, user.id)
try:
row = await asyncio.wait_for(
recap.generate_and_post(day, tz, image_enabled=image_enabled),
timeout=settings.comfyui_timeout_sec + LLM_TIMEOUT_SEC,
)
except asyncio.TimeoutError:
logger.warning("evening_recap timed out user=%s day=%s scheduled=%s", user.id, day, scheduled)
return
if row and row.status in ("posted", "text_only"):
set_state(db, f"last_evening_summary_date:{user.id}", day.isoformat())
async def _tick_evening_summary() -> None:
settings = get_settings()
if not settings.evening_summary_enabled:
@@ -112,42 +141,39 @@ async def _tick_evening_summary() -> None:
except Exception:
tz = ZoneInfo("Europe/Moscow")
schedule = resolve_evening_schedule(db, user.id)
if not schedule.enabled:
continue
now = datetime.now(tz)
target_min = settings.evening_summary_hour * 60 + settings.evening_summary_minute
target_min = schedule.hour * 60 + schedule.minute
current_min = now.hour * 60 + now.minute
if current_min < target_min or current_min >= target_min + 3:
continue
today = now.date().isoformat()
today = now.date()
state_key = f"last_evening_summary_date:{user.id}"
if get_state(db, state_key) == today:
if get_state(db, state_key) == today.isoformat():
continue
image_enabled = settings.evening_summary_image_enabled and settings.comfyui_enabled
if image_enabled:
client = ComfyUIClient()
if not await _comfyui_reachable(client.base_url):
logger.warning("Evening summary: ComfyUI unreachable, text only")
image_enabled = False
await _run_evening_recap(db, user, today, tz, scheduled=True)
finally:
db.close()
async def _tick_evening_summary_retries() -> None:
db = SessionLocal()
try:
users = db.scalars(select(User).where(User.is_active.is_(True))).all()
for user in users:
tz_name = resolve_timezone(db, user.id)
try:
notice = await asyncio.wait_for(
build_evening_notice(
db,
user.id,
now.date(),
tz,
image_enabled=image_enabled,
),
timeout=settings.comfyui_timeout_sec + LLM_TIMEOUT_SEC,
)
except asyncio.TimeoutError:
logger.warning("Evening summary timed out for user %s", user.id)
continue
if not notice:
continue
tz = ZoneInfo(tz_name)
except Exception:
tz = ZoneInfo("Europe/Moscow")
post_chat_notice(notice, user.id)
set_state(db, state_key, today)
recap = RecapService(db, user.id)
for row in recap.list_failed_for_retry():
await _run_evening_recap(db, user, row.day, tz, scheduled=False)
finally:
db.close()