added RAG, Multiuser, TG bot

This commit is contained in:
2026-06-13 20:20:56 +00:00
parent 66e1b0e29e
commit c8a9429bed
142 changed files with 19901 additions and 8790 deletions
+111 -114
View File
@@ -1,114 +1,111 @@
from datetime import datetime, timedelta, timezone
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.config import get_settings
from app.db.base import SessionLocal
from app.db.models import ChatSession, FitnessReminder, Message
from app.fitness.service import FitnessService
KIND_LABELS = {
"water": "Вода",
"meal": "Еда",
"workout": "Тренировка",
"weigh_in": "Взвешивание",
}
def _post_fitness_notice(content: str) -> None:
db = SessionLocal()
try:
session = db.scalar(
select(ChatSession).order_by(ChatSession.updated_at.desc()).limit(1)
)
if not session:
session = ChatSession(title="Фитнес")
db.add(session)
db.commit()
db.refresh(session)
db.add(Message(session_id=session.id, role="notice", content=content))
db.commit()
finally:
db.close()
def _build_notice(kind: str, summary: dict) -> str:
label = KIND_LABELS.get(kind, kind)
totals = summary.get("totals") or {}
targets = summary.get("targets") or {}
water_l = totals.get("water_ml", 0) / 1000
water_target = targets.get("water_ml", 2500) / 1000
cals = totals.get("calories", 0)
cal_target = targets.get("calories", 2000)
if kind == "water":
return (
f"💪 **{label}** · выпито {water_l:.1f}/{water_target:.1f} л сегодня. "
"Пора выпить стакан воды."
)
if kind == "meal":
return (
f"💪 **{label}** · {cals:.0f}/{cal_target:.0f} ккал за день. "
"Не забудь залогировать приём пищи."
)
if kind == "workout":
workouts = summary.get("workouts") or []
if workouts:
return f"💪 **{label}** · сегодня уже была тренировка. Отдыхай или лёгкая активность."
return "💪 **Тренировка** · запланирована на сегодня. Время двигаться!"
if kind == "weigh_in":
return "💪 **Взвешивание** · пора записать вес (log_weight)."
return f"💪 **{label}** · напоминание"
def check_reminders(db: Session) -> list[str]:
if not get_settings().fitness_reminders_enabled:
return []
now = datetime.now(timezone.utc)
service = FitnessService(db)
summary = service.get_daily_summary()
fired: list[str] = []
reminders = db.scalars(
select(FitnessReminder).where(FitnessReminder.enabled.is_(True))
).all()
for rem in reminders:
should_fire = False
if rem.interval_hours:
if rem.last_fired_at is None:
should_fire = now.hour >= rem.hour
else:
delta = now - rem.last_fired_at.replace(tzinfo=timezone.utc)
should_fire = delta >= timedelta(hours=rem.interval_hours)
else:
if rem.kind == "weigh_in":
if rem.last_fired_at:
delta = now - rem.last_fired_at.replace(tzinfo=timezone.utc)
should_fire = delta >= timedelta(days=7)
else:
should_fire = now.hour == rem.hour and now.minute >= rem.minute
else:
if rem.last_fired_at:
last = rem.last_fired_at.replace(tzinfo=timezone.utc)
already_today = last.date() == now.date()
if already_today:
continue
should_fire = now.hour == rem.hour and now.minute >= rem.minute
if not should_fire:
continue
notice = _build_notice(rem.kind, summary)
rem.last_fired_at = now
fired.append(notice)
if fired:
db.commit()
for notice in fired:
_post_fitness_notice(notice)
return fired
from datetime import datetime, timedelta, timezone
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.chat.notice_inbox import post_notice_to_latest_chat
from app.config import get_settings
from app.db.models import FitnessReminder, User
from app.fitness.service import FitnessService
KIND_LABELS = {
"water": "Вода",
"meal": "Еда",
"workout": "Тренировка",
"weigh_in": "Взвешивание",
}
def _build_notice(kind: str, summary: dict) -> str:
label = KIND_LABELS.get(kind, kind)
totals = summary.get("totals") or {}
targets = summary.get("targets") or {}
water_l = totals.get("water_ml", 0) / 1000
water_target = targets.get("water_ml", 2500) / 1000
cals = totals.get("calories", 0)
cal_target = targets.get("calories", 2000)
if kind == "water":
return (
f"💪 **{label}** · выпито {water_l:.1f}/{water_target:.1f} л сегодня. "
"Пора выпить стакан воды."
)
if kind == "meal":
return (
f"💪 **{label}** · {cals:.0f}/{cal_target:.0f} ккал за день. "
"Не забудь залогировать приём пищи."
)
if kind == "workout":
workouts = summary.get("workouts") or []
if workouts:
return f"💪 **{label}** · сегодня уже была тренировка. Отдыхай или лёгкая активность."
return "💪 **Тренировка** · запланирована на сегодня. Время двигаться!"
if kind == "weigh_in":
return "💪 **Взвешивание** · пора записать вес (log_weight)."
return f"💪 **{label}** · напоминание"
def _check_user_reminders(db: Session, user_id: int) -> list[str]:
now = datetime.now(timezone.utc)
service = FitnessService(db, user_id)
summary = service.get_daily_summary()
fired: list[str] = []
reminders = db.scalars(
select(FitnessReminder).where(
FitnessReminder.user_id == user_id,
FitnessReminder.enabled.is_(True),
)
).all()
for rem in reminders:
should_fire = False
if rem.interval_hours:
if rem.last_fired_at is None:
should_fire = now.hour >= rem.hour
else:
delta = now - rem.last_fired_at.replace(tzinfo=timezone.utc)
should_fire = delta >= timedelta(hours=rem.interval_hours)
else:
if rem.kind == "weigh_in":
if rem.last_fired_at:
delta = now - rem.last_fired_at.replace(tzinfo=timezone.utc)
should_fire = delta >= timedelta(days=7)
else:
should_fire = now.hour == rem.hour and now.minute >= rem.minute
else:
if rem.last_fired_at:
last = rem.last_fired_at.replace(tzinfo=timezone.utc)
already_today = last.date() == now.date()
if already_today:
continue
should_fire = now.hour == rem.hour and now.minute >= rem.minute
if not should_fire:
continue
notice = _build_notice(rem.kind, summary)
rem.last_fired_at = now
fired.append(notice)
if fired:
for notice in fired:
post_notice_to_latest_chat(notice, user_id)
return fired
def check_reminders(db: Session) -> list[str]:
if not get_settings().fitness_reminders_enabled:
return []
users = db.scalars(select(User).where(User.is_active.is_(True))).all()
all_fired: list[str] = []
for user in users:
all_fired.extend(_check_user_reminders(db, user.id))
if all_fired:
db.commit()
return all_fired