PMFlow/backend/app/services/signal_capture_service/mixin_new_project.py

149 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
from .shared import *
class SignalCaptureNewProjectMixin:
async def _auto_create_project_from_new_signal(
self,
*,
signal_id: int,
message: str,
sender: str,
tenant_id: int,
requested_at: Optional[datetime] = None,
) -> Optional[int]:
"""当分类为 new_project 时,自动创建项目并关联当前信号。"""
requested_dt = requested_at or now_cn_aware()
if requested_dt.tzinfo is None:
requested_dt = requested_dt.replace(tzinfo=CN_TZ)
project_brief = await self._draft_new_project_brief(
message=message,
tenant_id=tenant_id,
sender=sender,
requested_at=requested_dt,
)
title = self._normalize_single_line_text(project_brief.get("title") or "", max_len=120)
if not title:
title = self._normalize_single_line_text(message or "飞书新需求立项", max_len=40) or "飞书新需求立项"
raw_desc = self._normalize_multiline_text(project_brief.get("description") or message or "", max_len=1200)
requested_at_text = self._format_cn_time(requested_dt)
description = self._normalize_multiline_text(
f"需求提出人:{sender}\n"
f"提出时间:{requested_at_text}\n"
f"需求来源:飞书群聊\n\n"
f"需求说明:{raw_desc or self._normalize_multiline_text(message, max_len=500)}",
max_len=1800,
)
assigned_role = str(project_brief.get("primary_role") or "ai_engineer").strip()[:50] or "ai_engineer"
owner_id: Optional[int] = None
owner_result = await self.db.execute(
select(User.id).where(
and_(
User.tenant_id == tenant_id,
User.status == "active",
User.role.in_(["BOSS", "ADMIN"]),
)
).order_by(User.id.asc()).limit(1)
)
owner_id = owner_result.scalar_one_or_none()
await ensure_project_id_sequence(self.db)
project = Project(
name=title[:80] or "新项目",
title=title,
description=description,
owner_id=owner_id,
status="active",
assigned_role=assigned_role,
signal_count=1,
tenant_id=tenant_id,
)
self.db.add(project)
await self.db.flush()
await ensure_project_default_members(
self.db,
tenant_id=tenant_id,
project_id=int(project.id),
owner_id=int(project.owner_id) if project.owner_id is not None else None,
assigned_role=project.assigned_role,
)
existing = await self.db.execute(
text("SELECT id FROM project_signal_links WHERE project_id = :project_id AND signal_id = :signal_id"),
{"project_id": int(project.id), "signal_id": int(signal_id)},
)
if existing.first() is None:
link = ProjectSignalLink(
project_id=int(project.id),
signal_id=int(signal_id),
link_type="ai_new_project_signal",
relevance_score=0.98,
tenant_id=tenant_id,
)
self.db.add(link)
await self.db.execute(
text("UPDATE signals SET processed = :processed WHERE id = :signal_id"),
{"processed": True, "signal_id": int(signal_id)},
)
await self.db.commit()
self._emit_ai_think(
"new_project_created_from_signal",
signal_id=signal_id,
project_id=int(project.id),
sender=sender,
)
return int(project.id)
async def _draft_new_project_brief(
self,
*,
message: str,
tenant_id: int,
sender: str,
requested_at: datetime,
) -> Dict[str, Any]:
requested_at_text = self._format_cn_time(requested_at)
prompt = f"""你是 PMFlow 的立项助手。基于下面需求消息,提炼新项目基础信息。
需求提出人:{sender}
提出时间:{requested_at_text}
需求消息:
{message[:1000]}
输出 JSON:
{{
"title": "项目标题(15-30字)",
"description": "项目简介(80-200字)",
"primary_role": "ai_engineer|backend|frontend|devops"
}}
只输出 JSON,不要 markdown,不要解释,不要乱码。"""
try:
raw = await self.llm.generate(
prompt,
db=self.db,
tenant_id=tenant_id,
function_name="feishu_new_project_brief",
)
parsed = self._parse_llm_json_response(raw)
role = str(parsed.get("primary_role") or "ai_engineer").strip().lower()
if role not in {"ai_engineer", "backend", "frontend", "devops"}:
role = "ai_engineer"
return {
"title": self._normalize_single_line_text(str(parsed.get("title") or ""), max_len=120),
"description": self._normalize_multiline_text(str(parsed.get("description") or ""), max_len=1200),
"primary_role": role,
}
except Exception as exc:
self._emit_ai_think("draft_new_project_brief_failed", error=str(exc))
return {
"title": self._normalize_single_line_text(message or "飞书新需求立项", max_len=40),
"description": self._normalize_multiline_text(message, max_len=300),
"primary_role": "ai_engineer",
}