## 安全修复 (12项) - Webhook接口添加全局Token认证,过滤敏感请求头 - 修复JWT Base64 padding公式,防止签名验证绕过 - 数据库密码/飞书Token从源码移除,改为环境变量 - 工作流引擎添加路径遍历防护 (_resolve_safe_path) - eval()添加模板长度上限检查 - 审批API添加认证依赖 - 前端v-html增强XSS转义,console.log仅开发模式输出 - 500错误不再暴露内部异常详情 ## Agent运行时修复 (7项) - 删除_inject_knowledge_context中未定义db变量的finally块 - 工具执行添加try/except保护,异常不崩溃Agent - LLM重试计入budget计数器 - self_review异常时passed=False - max_iterations截断标记success=False - 工具参数JSON解析失败时记录警告日志 - run()开始时重置_llm_invocations计数器 ## 配置与基础设施 - DEBUG默认False,SQL_ECHO独立配置项 - init_db()补全13个缺失模型导入 - 新增WEBHOOK_AUTH_TOKEN/SQL_ECHO配置项 - 新增.env.example模板文件 ## 前端修复 (12项) - 登录改用URLSearchParams替代FormData - 401拦截器通过Pinia store统一清理状态 - SSE流超时从60s延长至300s - final/error事件时清除streamTimeout - localStorage聊天记录添加24h TTL - safeParseArgCount替代模板中裸JSON.parse - fetchUser 401时同时清除user对象 ## 新增模块 - 知识进化: knowledge_extractor/retriever/tasks - 数字孪生: shadow_executor/comparison模型 - 行为采集: behavior_middleware/collector/fingerprint_engine - 代码审查: code_review_agent/document_review_agent - 反馈学习: feedback_learner - 瓶颈检测/优化引擎/成本估算/需求估算 - 速率限制器 (rate_limiter) - Alembic迁移 015-020 ## 文档 - 商业化落地计划 - 8篇docs文档 (架构/API/部署/开发/贡献等) - Docker Compose生产配置 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
352 lines
14 KiB
Python
352 lines
14 KiB
Python
"""
|
||
需求评估代理 — 数字分身从历史数据估算需求工时,提供分阶段方案和风险评估
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
import re
|
||
from typing import Any, Dict, List, Optional
|
||
|
||
from sqlalchemy.orm import Session
|
||
|
||
from app.core.database import SessionLocal
|
||
from app.models.execution import Execution
|
||
from app.models.task import Task
|
||
from app.services.fingerprint_engine import fingerprint_engine
|
||
from app.services.behavior_collector import behavior_collector
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class RequirementEstimator:
|
||
"""需求评估代理 — 基于历史数据估算工时与风险"""
|
||
|
||
def estimate(self, user_id: str, title: str, description: str = "",
|
||
modules: Optional[List[str]] = None) -> Dict[str, Any]:
|
||
"""评估需求的工作量和时间。
|
||
|
||
Args:
|
||
user_id: 用户ID
|
||
title: 需求标题
|
||
description: 需求描述
|
||
modules: 涉及的模块/组件列表
|
||
"""
|
||
modules = modules or self._extract_modules(description)
|
||
|
||
# 1. 历史同类需求分析
|
||
similar_tasks = self._find_similar_tasks(title, description)
|
||
|
||
# 2. 复杂度评估
|
||
complexity = self._assess_complexity(title, description, modules)
|
||
|
||
# 3. 时间估算
|
||
time_estimate = self._estimate_time(complexity, similar_tasks, modules)
|
||
|
||
# 4. 风险识别
|
||
risks = self._identify_risks(title, description, modules)
|
||
|
||
# 5. 分阶段建议
|
||
phases = self._suggest_phases(title, description, modules, time_estimate)
|
||
|
||
# 6. 方案建议
|
||
suggestions = self._suggest_approach(complexity, similar_tasks, risks)
|
||
|
||
result = {
|
||
"title": title,
|
||
"complexity": complexity,
|
||
"time_estimate": time_estimate,
|
||
"modules": modules,
|
||
"similar_tasks": similar_tasks,
|
||
"risks": risks,
|
||
"phases": phases,
|
||
"suggestions": suggestions,
|
||
}
|
||
|
||
# 记录行为
|
||
behavior_collector.log_fire_and_forget(
|
||
user_id=user_id,
|
||
category="decision",
|
||
action="estimate_requirement",
|
||
context={"title": title, "modules": modules},
|
||
result={"complexity": complexity["level"], "estimated_hours": time_estimate["best_case_hours"]},
|
||
)
|
||
|
||
return result
|
||
|
||
def _extract_modules(self, description: str) -> List[str]:
|
||
"""从描述中提取可能涉及的模块名。"""
|
||
modules = []
|
||
patterns = [
|
||
r'(?:模块|组件|服务)[::]\s*(\S+)',
|
||
r'(\w+(?:模块|组件|服务|API|数据库|前端|后端))',
|
||
r'(?:涉及|影响|修改)[::]?\s*([^,,\n]+)',
|
||
]
|
||
for pattern in patterns:
|
||
matches = re.findall(pattern, description)
|
||
modules.extend(matches)
|
||
# 去重
|
||
seen = set()
|
||
result = []
|
||
for m in modules:
|
||
if m not in seen:
|
||
seen.add(m)
|
||
result.append(m)
|
||
return result[:10]
|
||
|
||
def _find_similar_tasks(self, title: str, description: str) -> List[Dict[str, Any]]:
|
||
"""查找历史同类需求(基于关键词匹配)。"""
|
||
db: Optional[Session] = None
|
||
try:
|
||
db = SessionLocal()
|
||
|
||
# 提取关键词
|
||
keywords = set(re.findall(r'[\w]{2,}', title + description))
|
||
# 过滤常用词
|
||
stopwords = {'的', '和', '是', '在', '了', '不', '有', '与', '对', '这', '那'}
|
||
keywords = keywords - stopwords
|
||
|
||
if not keywords:
|
||
return []
|
||
|
||
tasks = db.query(Task).filter(
|
||
Task.title.isnot(None),
|
||
Task.actual_hours.isnot(None),
|
||
).order_by(Task.created_at.desc()).limit(200).all()
|
||
|
||
similar = []
|
||
for task in tasks:
|
||
task_text = (task.title or "") + (task.description or "")
|
||
score = sum(1 for kw in keywords if kw in task_text)
|
||
if score >= 2:
|
||
similar.append({
|
||
"task_id": str(task.id) if task.id else None,
|
||
"title": task.title[:100] if task.title else "",
|
||
"actual_hours": float(task.actual_hours) if task.actual_hours else None,
|
||
"similarity_score": score,
|
||
})
|
||
|
||
similar.sort(key=lambda x: x.get("similarity_score", 0), reverse=True)
|
||
return similar[:5]
|
||
|
||
except Exception as e:
|
||
logger.error("查找同类需求失败: %s", e)
|
||
return []
|
||
finally:
|
||
if db:
|
||
try:
|
||
db.close()
|
||
except Exception:
|
||
pass
|
||
|
||
def _assess_complexity(self, title: str, description: str,
|
||
modules: List[str]) -> Dict[str, Any]:
|
||
"""评估需求复杂度。"""
|
||
text = title + " " + description
|
||
factors = {
|
||
"module_count": min(len(modules) if modules else 1, 10),
|
||
"has_migration": 1 if re.search(r'迁移|migration|schema.*change', text, re.I) else 0,
|
||
"has_payment": 1 if re.search(r'支付|付款|资金|金额', text) else 0,
|
||
"has_permission": 1 if re.search(r'权限|permission|auth|role', text, re.I) else 0,
|
||
"has_external_api": 1 if re.search(r'第三方|外部.*API|webhook|回调', text) else 0,
|
||
"has_report": 1 if re.search(r'报表|统计|chart|dashboard|导出', text) else 0,
|
||
"has_real_time": 1 if re.search(r'实时|websocket|push|消息推送', text) else 0,
|
||
"has_batch": 1 if re.search(r'批量|import|export|导入|导出', text) else 0,
|
||
}
|
||
|
||
score = (
|
||
factors["module_count"] * 3
|
||
+ factors["has_migration"] * 10
|
||
+ factors["has_payment"] * 8
|
||
+ factors["has_permission"] * 5
|
||
+ factors["has_external_api"] * 5
|
||
+ factors["has_report"] * 3
|
||
+ factors["has_real_time"] * 4
|
||
+ factors["has_batch"] * 3
|
||
)
|
||
|
||
if score <= 15:
|
||
level = "simple"
|
||
label = "简单"
|
||
elif score <= 30:
|
||
level = "medium"
|
||
label = "中等"
|
||
elif score <= 50:
|
||
level = "complex"
|
||
label = "复杂"
|
||
else:
|
||
level = "very_complex"
|
||
label = "非常复杂"
|
||
|
||
return {
|
||
"level": level,
|
||
"label": label,
|
||
"score": score,
|
||
"factors": factors,
|
||
}
|
||
|
||
def _estimate_time(self, complexity: Dict[str, Any],
|
||
similar_tasks: List[Dict[str, Any]],
|
||
modules: List[str]) -> Dict[str, Any]:
|
||
"""估算开发时间。"""
|
||
level = complexity["level"]
|
||
module_count = len(modules) if modules else 1
|
||
|
||
# 基准时间(小时/模块)
|
||
base_hours = {
|
||
"simple": 8,
|
||
"medium": 16,
|
||
"complex": 32,
|
||
"very_complex": 56,
|
||
}.get(level, 16)
|
||
|
||
# 历史数据调整
|
||
if similar_tasks:
|
||
hist_hours = [t["actual_hours"] for t in similar_tasks if t.get("actual_hours")]
|
||
if hist_hours:
|
||
hist_avg = sum(hist_hours) / len(hist_hours)
|
||
base_hours = base_hours * 0.4 + hist_avg * 0.6
|
||
|
||
best_case = round(base_hours * module_count * 0.8, 1)
|
||
expected = round(base_hours * module_count, 1)
|
||
worst_case = round(base_hours * module_count * 1.8, 1)
|
||
|
||
# 转换为天(8小时/天)
|
||
return {
|
||
"best_case_hours": best_case,
|
||
"expected_hours": expected,
|
||
"worst_case_hours": worst_case,
|
||
"best_case_days": round(best_case / 8, 1),
|
||
"expected_days": round(expected / 8, 1),
|
||
"worst_case_days": round(worst_case / 8, 1),
|
||
"confidence": "high" if similar_tasks and len(similar_tasks) >= 3 else "medium",
|
||
"based_on": f"{len(similar_tasks)} 个历史同类需求" if similar_tasks else "行业基准估算",
|
||
}
|
||
|
||
def _identify_risks(self, title: str, description: str,
|
||
modules: List[str]) -> List[Dict[str, Any]]:
|
||
"""识别需求风险。"""
|
||
text = title + " " + description
|
||
risks = []
|
||
|
||
risk_checks = [
|
||
("high", r'数据.*迁移|schema.*变更|数据库.*改', "数据库结构变更"),
|
||
("high", r'支付|交易|退款|结算', "涉及支付/资金流程"),
|
||
("high", r'删除|物理删除|DROP|TRUNCATE', "包含数据删除操作"),
|
||
("medium", r'第三方.*API|外部.*依赖', "依赖外部API可用性"),
|
||
("medium", r'权限.*改|角色.*调整|auth.*change', "权限体系变更"),
|
||
("medium", r'性能|并发|QPS|高并发', "性能/并发要求"),
|
||
("medium", r'多个.*系统|跨.*系统|集成', "多系统集成复杂度"),
|
||
("low", r'导出|报表|dashboard|可视化', "导出/报表格式兼容"),
|
||
("low", r'手机|移动端|H5|小程序|APP', "多端适配工作"),
|
||
("low", r'旧.*兼容|legacy|向后兼容', "历史兼容性约束"),
|
||
]
|
||
|
||
for severity, pattern, label in risk_checks:
|
||
if re.search(pattern, text):
|
||
risks.append({
|
||
"severity": severity,
|
||
"label": label,
|
||
"mitigation": self._suggest_mitigation(label),
|
||
})
|
||
|
||
return risks
|
||
|
||
def _suggest_mitigation(self, risk_label: str) -> str:
|
||
"""根据风险类型建议缓解措施。"""
|
||
mitigations = {
|
||
"数据库结构变更": "建议编写 migration 脚本并先在 staging 验证",
|
||
"涉及支付/资金流程": "建议增加财务团队 review 环节和完整测试用例",
|
||
"包含数据删除操作": "建议实现软删除,添加二次确认机制",
|
||
"依赖外部API可用性": "建议添加超时重试和降级方案",
|
||
"权限体系变更": "建议灰度发布,充分回归测试",
|
||
"性能/并发要求": "建议提前压测并设置监控告警",
|
||
"多系统集成复杂度": "建议明确接口契约,增加集成测试",
|
||
"导出/报表格式兼容": "建议确认所有目标格式和编码",
|
||
"多端适配工作": "建议使用响应式设计或明确优先级",
|
||
"历史兼容性约束": "建议增加兼容性测试覆盖",
|
||
}
|
||
return mitigations.get(risk_label, "建议细化方案后评估")
|
||
|
||
def _suggest_phases(self, title: str, description: str,
|
||
modules: List[str],
|
||
time_estimate: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||
"""建议分阶段实施计划。"""
|
||
total_days = time_estimate["expected_days"]
|
||
|
||
if total_days <= 3:
|
||
return [{
|
||
"phase": 1,
|
||
"name": "一站式开发",
|
||
"tasks": [f"实现 {title}"],
|
||
"estimated_days": total_days,
|
||
"checkpoint": "功能验收",
|
||
}]
|
||
|
||
phases = []
|
||
if modules and len(modules) > 1:
|
||
mid = max(1, len(modules) // 2)
|
||
phases.append({
|
||
"phase": 1,
|
||
"name": "核心功能",
|
||
"tasks": [f"实现 {m} 模块" for m in modules[:mid]],
|
||
"estimated_days": round(total_days * 0.5, 1),
|
||
"checkpoint": "核心功能可 Demo",
|
||
})
|
||
phases.append({
|
||
"phase": 2,
|
||
"name": "扩展功能",
|
||
"tasks": [f"实现 {m} 模块" for m in modules[mid:]],
|
||
"estimated_days": round(total_days * 0.3, 1),
|
||
"checkpoint": "完整功能验收",
|
||
})
|
||
phases.append({
|
||
"phase": 3,
|
||
"name": "优化 & 测试",
|
||
"tasks": ["性能优化", "完善测试", "文档整理"],
|
||
"estimated_days": round(total_days * 0.2, 1),
|
||
"checkpoint": "上线发布",
|
||
})
|
||
else:
|
||
phases = [
|
||
{"phase": 1, "name": "设计与准备", "tasks": ["技术方案设计", "接口定义"], "estimated_days": round(total_days * 0.2, 1), "checkpoint": "方案评审通过"},
|
||
{"phase": 2, "name": "核心开发", "tasks": [f"实现 {title}"], "estimated_days": round(total_days * 0.5, 1), "checkpoint": "功能完成"},
|
||
{"phase": 3, "name": "测试与上线", "tasks": ["集成测试", "性能测试", "上线部署"], "estimated_days": round(total_days * 0.3, 1), "checkpoint": "上线发布"},
|
||
]
|
||
|
||
return phases
|
||
|
||
def _suggest_approach(self, complexity: Dict[str, Any],
|
||
similar_tasks: List[Dict[str, Any]],
|
||
risks: List[Dict[str, Any]]) -> List[str]:
|
||
"""生成开发策略建议。"""
|
||
suggestions = []
|
||
|
||
level = complexity["level"]
|
||
if level in ("complex", "very_complex"):
|
||
suggestions.append("建议先做技术方案评审,再启动开发")
|
||
suggestions.append("建议拆分为多个独立可交付的 milestone")
|
||
|
||
if len(risks) >= 3:
|
||
suggestions.append(f"识别到 {len(risks)} 项风险,建议逐项制定应对预案")
|
||
|
||
high_risks = [r for r in risks if r["severity"] == "high"]
|
||
if high_risks:
|
||
suggestions.append(f"存在 {len(high_risks)} 项高风险: "
|
||
f"{', '.join(r['label'] for r in high_risks)},强烈建议专项评审")
|
||
|
||
if similar_tasks:
|
||
suggestions.append(f"参考 {len(similar_tasks)} 个历史同类需求的实际工时数据")
|
||
|
||
if level == "simple":
|
||
suggestions.append("需求较简单,可快速迭代,不必过度设计")
|
||
|
||
# 模块数多时建议并行
|
||
factors = complexity.get("factors", {})
|
||
if factors.get("module_count", 0) > 4:
|
||
suggestions.append("涉及模块较多,建议多人并行开发")
|
||
|
||
return suggestions
|
||
|
||
|
||
requirement_estimator = RequirementEstimator()
|