200 lines
7.9 KiB
Python
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,
|
|
}
|
|
|