feat: add teacher help cards
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from fastapi import HTTPException, status
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.chat import ChatSession, TopicSession
|
||||
from app.models.growth import TeacherHelpCard, TopicSummary
|
||||
from app.models.user import User
|
||||
from app.services.entitlement_service import EntitlementService
|
||||
from app.services.growth_profile_service import GrowthProfileService, topic_summary_dict
|
||||
|
||||
|
||||
class HelpCardService:
|
||||
@staticmethod
|
||||
def generate_for_session(db: Session, *, user: User, session: ChatSession) -> TeacherHelpCard:
|
||||
entitlement = EntitlementService.active_entitlement(db, user)
|
||||
if not entitlement.allow_help_card:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="当前权益暂不支持生成老师求助卡")
|
||||
|
||||
topic = _latest_topic(db, user=user, session=session)
|
||||
if topic is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="当前会话还没有可生成求助卡的主题")
|
||||
|
||||
summary = GrowthProfileService.generate_topic_summary(db, user=user, topic=topic)
|
||||
content = _render_help_card(user=user, topic=topic, summary=summary)
|
||||
card = TeacherHelpCard(
|
||||
user_id=user.id,
|
||||
topic_session_id=topic.id,
|
||||
summary_id=summary.id,
|
||||
content=content,
|
||||
source="topic_summary",
|
||||
)
|
||||
topic.help_card_generated = 1
|
||||
db.add_all([topic, card])
|
||||
db.commit()
|
||||
db.refresh(card)
|
||||
return card
|
||||
|
||||
@staticmethod
|
||||
def list_user_cards(db: Session, *, user: User, limit: int = 20) -> list[TeacherHelpCard]:
|
||||
return list(
|
||||
db.scalars(
|
||||
select(TeacherHelpCard)
|
||||
.where(TeacherHelpCard.user_id == user.id)
|
||||
.order_by(TeacherHelpCard.created_at.desc(), TeacherHelpCard.id.desc())
|
||||
.limit(limit)
|
||||
)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def mark_copied(db: Session, *, user: User, card_id: int) -> TeacherHelpCard:
|
||||
card = db.scalar(select(TeacherHelpCard).where(TeacherHelpCard.id == card_id, TeacherHelpCard.user_id == user.id))
|
||||
if card is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="求助卡不存在")
|
||||
card.copied = 1
|
||||
card.copied_at = _now()
|
||||
db.add(card)
|
||||
db.commit()
|
||||
db.refresh(card)
|
||||
return card
|
||||
|
||||
|
||||
def help_card_dict(card: TeacherHelpCard) -> dict:
|
||||
return {
|
||||
"id": card.id,
|
||||
"userId": card.user_id,
|
||||
"topicSessionId": card.topic_session_id,
|
||||
"summaryId": card.summary_id,
|
||||
"content": card.content,
|
||||
"source": card.source,
|
||||
"copied": bool(card.copied),
|
||||
"copiedAt": card.copied_at,
|
||||
"createdAt": card.created_at,
|
||||
}
|
||||
|
||||
|
||||
def _latest_topic(db: Session, *, user: User, session: ChatSession) -> TopicSession | None:
|
||||
return db.scalar(
|
||||
select(TopicSession)
|
||||
.where(TopicSession.user_id == user.id, TopicSession.chat_session_id == session.id)
|
||||
.order_by(TopicSession.updated_at.desc(), TopicSession.id.desc())
|
||||
.limit(1)
|
||||
)
|
||||
|
||||
|
||||
def _render_help_card(*, user: User, topic: TopicSession, summary: TopicSummary) -> str:
|
||||
data = topic_summary_dict(summary)
|
||||
return (
|
||||
"【给老师的求助卡】\n"
|
||||
"说明:这是我根据本次 AI 对话整理出的求助信息,请老师帮我确认方向。"
|
||||
"我会按实际情况自行删改后再发送。\n\n"
|
||||
f"学员:{user.name or user.nickname or user.phone}\n"
|
||||
f"主题:{topic.title}\n"
|
||||
f"主题时间:{_format_time(topic.started_at)} - {_format_time(topic.ended_at) if topic.ended_at else '进行中'}\n\n"
|
||||
"1. 我遇到的问题\n"
|
||||
f"{topic.core_question or '(请补充)'}\n\n"
|
||||
"2. AI 已经帮我梳理出的重点\n"
|
||||
f"{data.get('summary') or '(暂无摘要)'}\n\n"
|
||||
"3. 我现在最明显的情绪 / 身体感受\n"
|
||||
f"情绪:{data.get('emotions') or '(请补充)'}\n"
|
||||
f"身体感受:{data.get('bodyFeelings') or '(请补充)'}\n\n"
|
||||
"4. 我已经尝试过或被建议的功课\n"
|
||||
f"{data.get('recommendedHomework') or '(请补充)'}\n\n"
|
||||
"5. 我仍然卡住的地方\n"
|
||||
f"{data.get('beliefs') or data.get('nextObservation') or '(请补充)'}\n\n"
|
||||
"6. 我想请老师确认的问题\n"
|
||||
"(请把最想确认的一两个问题写在这里)\n\n"
|
||||
"备注:这张卡片不会自动发送给老师,也不代表已经转人工处理。"
|
||||
)
|
||||
|
||||
|
||||
def _format_time(value: datetime | None) -> str:
|
||||
if value is None:
|
||||
return "未知"
|
||||
return value.strftime("%Y-%m-%d %H:%M")
|
||||
|
||||
|
||||
def _now() -> datetime:
|
||||
return datetime.now(UTC).replace(tzinfo=None)
|
||||
|
||||
Reference in New Issue
Block a user