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

200 lines
7.9 KiB
Python

from __future__ import annotations
from .shared import *
class SignalCaptureLegacyMixin:
async def _capture_from_feishu_legacy_flow(
self,
*,
trace_id: str,
group_chat_id: str,
message_content: str,
cleaned_message: str,
proposer_name: str,
proposer_open_id: Optional[str],
mentioned_bot_names: Optional[List[str]],
tenant_id: int,
captured_dt: datetime,
) -> Dict[str, Any]:
projects_context = await self._load_active_projects_context(
tenant_id=tenant_id,
)
classification = await self._analyze_chat_message(
message=cleaned_message,
projects_context=projects_context,
tenant_id=tenant_id,
)
self._emit_ai_think(
"classification_done",
trace_id=trace_id,
classification=classification.get("classification"),
confidence=classification.get("confidence"),
matched_project_ids=classification.get("matched_project_ids", []),
reason=classification.get("reason"),
)
if not classification.get("is_requirement", False):
self._emit_ai_think(
"capture_skip_not_requirement",
trace_id=trace_id,
reason=classification.get("reason", "non_requirement"),
)
return {"status": "ignored", "reason": classification.get("reason", "non_requirement")}
project_scope = str(classification.get("classification") or "pending_classification")
if project_scope == "non_requirement":
self._emit_ai_think(
"capture_skip_non_requirement",
trace_id=trace_id,
reason=classification.get("reason", "non_requirement"),
)
return {"status": "ignored", "reason": classification.get("reason", "non_requirement")}
await ensure_signal_id_sequence(self.db)
strategic_payload = {
"captured_at": captured_dt.isoformat(),
"source_context": {
"group_id": group_chat_id,
"sender": proposer_name,
"sender_open_id": proposer_open_id,
"mentioned_bot_names": [
self._normalize_single_line_text(name, max_len=80)
for name in (mentioned_bot_names or [])
if self._normalize_single_line_text(name, max_len=80)
],
},
"original_message": (message_content or "")[:500],
"cleaned_message": cleaned_message[:500],
"ai_analysis": {
"requirement_project_scope": project_scope,
"reason": classification.get("reason", ""),
"confidence": classification.get("confidence", 0.0),
"matched_project_ids": classification.get("matched_project_ids", []),
"matched_project_titles": classification.get("matched_project_titles", []),
"summary": classification.get("summary", ""),
},
}
signal = Signal(
source="Feishu",
content=f"[{proposer_name}]: {self._normalize_single_line_text(cleaned_message, max_len=200)}",
impact_score=float(classification.get("impact_score") or 0.7),
priority=(classification.get("priority") or "medium"),
processed=False,
strategic_analysis=json.dumps(strategic_payload, ensure_ascii=False),
tasks_generated=json.dumps([], ensure_ascii=False),
tenant_id=tenant_id,
)
self.db.add(signal)
await self.db.commit()
await self.db.refresh(signal)
self._emit_ai_think("signal_created", trace_id=trace_id, signal_id=signal.id)
linked_project_ids: List[int] = []
try:
if project_scope == "new_project":
new_project_id = await self._auto_create_project_from_new_signal(
signal_id=int(signal.id),
message=cleaned_message,
sender=proposer_name,
tenant_id=tenant_id,
requested_at=captured_dt,
)
linked_project_ids = [new_project_id] if new_project_id else []
else:
linked_project_ids = await self._auto_link_to_projects(
signal.id,
classification,
tenant_id,
message_content=cleaned_message,
)
self._emit_ai_think(
"project_link_done",
trace_id=trace_id,
signal_id=signal.id,
linked_project_ids=linked_project_ids,
)
except Exception as exc:
self._emit_ai_think("project_link_failed", trace_id=trace_id, error=str(exc))
auto_tasks: List[Dict[str, Any]] = []
ai_execution: Dict[str, Any] = {
"trace_id": trace_id,
"status": "skipped",
"reason": "no_linked_project",
"project_id": None,
"summary": "",
"tasks_created": 0,
}
if linked_project_ids:
primary_project_id = int(linked_project_ids[0])
try:
auto_tasks, ai_execution = await self._auto_orchestrate_project_tasks(
trace_id=trace_id,
tenant_id=tenant_id,
project_id=primary_project_id,
signal_id=int(signal.id),
cleaned_message=cleaned_message,
classification=classification,
proposer_name=proposer_name,
captured_at=captured_dt,
)
except Exception as exc:
self._emit_ai_think(
"task_orchestrate_failed",
trace_id=trace_id,
project_id=primary_project_id,
error=str(exc),
)
ai_execution = {
"trace_id": trace_id,
"status": "failed",
"reason": str(exc),
"project_id": primary_project_id,
"summary": "",
"tasks_created": 0,
}
strategic_payload["ai_execution"] = ai_execution
signal.tasks_generated = json.dumps(auto_tasks, ensure_ascii=False)
signal.strategic_analysis = json.dumps(strategic_payload, ensure_ascii=False)
signal.processed = bool(linked_project_ids or auto_tasks)
await self.db.commit()
target_url = "/ai-steward"
if linked_project_ids:
target_url = f"/projects/{linked_project_ids[0]}/board"
try:
notification_service = NotificationService(self.db)
await notification_service.notify_signal_created(
tenant_id=tenant_id,
signal_id=int(signal.id),
signal_content=cleaned_message,
source="feishu",
target_url=target_url,
proposer=proposer_name,
occurred_at=captured_dt,
)
except Exception as exc:
self._emit_ai_think("notify_failed", trace_id=trace_id, error=str(exc))
self._emit_ai_think(
"capture_done",
trace_id=trace_id,
signal_id=signal.id,
linked_project_ids=linked_project_ids,
auto_tasks=len(auto_tasks),
)
return {
"signal_id": str(signal.id),
"status": "captured",
"impact_score": signal.impact_score,
"auto_tasks": auto_tasks,
"project_scope": project_scope,
"classification": project_scope,
"linked_project_ids": linked_project_ids,
"created_project_id": linked_project_ids[0] if project_scope == "new_project" and linked_project_ids else None,
"proposer": proposer_name,
"ai_trace_id": trace_id,
}