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

208 lines
7.0 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 SignalCaptureSourceAnalyzersMixin:
async def _analyze_webpage(self, url: str, title: str, content: str, tenant_id: int) -> Dict[str, Any]:
"""AI 分析网页价值"""
prompt = f"""分析以下网页内容是否有价值:
标题:{title}
URL: {url}
内容摘要:{content[:500]}
请判断:
1. 是否是技术文档、竞品信息、行业趋势?
2. 是否值得团队关注?
3. 影响程度(0-1)?
以 JSON 格式返回:
{{
"is_valuable": boolean,
"reason": "原因",
"impact_score": 0.8,
"priority": "high/medium/low",
"analysis": {{}},
"suggested_tasks": [{{"task_name": "任务", "type": "backend", "estimated_hours": 8}}]
}}
如果不是有价值内容,is_valuable 设为 false。"""
try:
response = await self.llm.generate(
prompt,
db=self.db,
tenant_id=tenant_id,
)
response = response.strip()
if response.startswith('```'):
response = response.split('```')[1]
if response.startswith('json'):
response = response[4:]
return json.loads(response.strip())
except:
return {'is_valuable': False, 'reason': 'AI 分析失败'}
async def _analyze_github_event(self, repo_url: str, event_type: str, event_data: Dict, tenant_id: int) -> Dict[str, Any]:
"""AI 分析 GitHub 事件"""
description = f"{event_type}: {event_data.get('title', event_data.get('message', ''))}"
prompt = f"""分析以下 GitHub 事件是否有价值:
仓库:{repo_url}
事件:{description}
请判断:
1. 是否是技术栈更新、安全修复、重要功能?
2. 是否影响我们的项目?
3. 影响程度(0-1)?
以 JSON 格式返回:
{{
"is_valuable": boolean,
"reason": "原因",
"impact_score": 0.8,
"priority": "high/medium/low",
"analysis": {{}},
"suggested_tasks": [{{"task_name": "任务", "type": "backend", "estimated_hours": 8}}]
}}
如果不是有价值内容,is_valuable 设为 false。"""
try:
response = await self.llm.generate(
prompt,
db=self.db,
tenant_id=tenant_id,
)
response = response.strip()
if response.startswith('```'):
response = response.split('```')[1]
if response.startswith('json'):
response = response[4:]
return json.loads(response.strip())
except:
return {'is_valuable': False, 'reason': 'AI 分析失败'}
async def _analyze_hackernews_story(self, story: Dict, tenant_id: int) -> Dict[str, Any]:
"""AI 分析 HackerNews 故事"""
prompt = f"""分析以下 HackerNews 故事是否是重要技术趋势:
标题:{story.get('title', '')}
链接:{story.get('url', '')}
得分:{story.get('points', 0)}
请判断:
1. 是否是行业趋势、技术突破?
2. 是否值得我们关注?
3. 影响程度(0-1)?
以 JSON 格式返回:
{{
"is_strategic": boolean,
"impact_score": 0.8,
"priority": "high/medium/low",
"analysis": {{}},
"suggested_tasks": [{{"task_name": "任务", "type": "backend", "estimated_hours": 8}}]
}}
如果不是战略性内容,is_strategic 设为 false。"""
try:
response = await self.llm.generate(
prompt,
db=self.db,
tenant_id=tenant_id,
)
response = response.strip()
if response.startswith('```'):
response = response.split('```')[1]
if response.startswith('json'):
response = response[4:]
result = json.loads(response.strip())
# 重命名字段以匹配其他方法
return {
'is_valuable': result.get('is_strategic', False),
'reason': result.get('reason', ''),
'impact_score': result.get('impact_score', 0.5),
'priority': result.get('priority', 'low'),
'analysis': result.get('analysis', {}),
'suggested_tasks': result.get('suggested_tasks', [])
}
except:
return {'is_valuable': False, 'reason': 'AI 分析失败'}
async def _auto_link_to_projects(
self,
signal_id: int,
analysis: Dict[str, Any],
tenant_id: int,
message_content: Optional[str] = None,
) -> List[int]:
"""自动关联信号到相关项目"""
_ = message_content
classification = str(analysis.get("classification") or "").lower()
if classification in {"new_project", "pending_classification", "non_requirement"}:
return []
if classification == "existing_project":
matched_ids: List[int] = []
for pid in analysis.get("matched_project_ids", []) or []:
try:
matched_ids.append(int(pid))
except Exception:
continue
matched_ids = sorted(set(matched_ids))
if not matched_ids:
return []
result = await self.db.execute(
select(Project.id).where(
and_(
Project.id.in_(matched_ids),
Project.status == "active",
Project.tenant_id == tenant_id,
)
)
)
valid_ids = {int(v) for v in result.scalars().all()}
if not valid_ids:
return []
confidence = float(analysis.get("confidence") or 0.0)
relevance = max(0.5, min(0.98, 0.55 + confidence * 0.4))
link_type = "ai_existing_project_signal"
else:
result = await self.db.execute(
select(Project.id).where(
and_(Project.status == "active", Project.tenant_id == tenant_id)
)
)
valid_ids = {int(v) for v in result.scalars().all()}
if not valid_ids:
return []
relevance = 0.7
link_type = "auto_captured"
linked_project_ids: List[int] = []
for project_id in sorted(valid_ids):
existing = await self.db.execute(
text("SELECT id FROM project_signal_links WHERE project_id = :project_id AND signal_id = :signal_id"),
{"project_id": project_id, "signal_id": signal_id},
)
if existing.first() is None:
from ...models import ProjectSignalLink
link = ProjectSignalLink(
project_id=project_id,
signal_id=signal_id,
link_type=link_type,
relevance_score=relevance,
tenant_id=tenant_id,
)
self.db.add(link)
linked_project_ids.append(project_id)
await self.db.commit()
return linked_project_ids