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, }