feat(impact): Impact Agent MVP —— 变更点定位 + 影响调查书(追加改修场景)
- 门控:用户提供 existing_system 路径 → 进入影响调查;未提供 → 原流程不变
- CodeParser 解析 Java(@RestController/@Service/@Entity/@TableName)+ ExistingSystemExplorer 组装
- ImpactAgent 变更点定位(变更区分×既存対応 确定性比对,无 LLM)→ ImpactReport(JSON 可下载)
- 影响调查结果作为 Writer 生成概要设计书的主上下文({{impact}},无专用影响章)
- source_aggregator 解除 existing_system=None 硬编码
- 既有系统样本 sunOnly/stock-trade-system(无 LICENSE,仅测试输入,保留来源标注)
- 新造股票交易域追加改修样本 要件定義_追加改修_股票.xlsx(对齐 sunOnly 真实类名)
- 全量 351 passed / 99.27% 覆盖;门禁 PASS(16 要素:新规5/変更8/削除3/未受影响50)
This commit is contained in:
@@ -2,6 +2,7 @@ import pytest
|
||||
from pathlib import Path
|
||||
|
||||
from genesis.data_models import StructuredSource
|
||||
from genesis.impact.code_parser import CodeParseError
|
||||
from genesis.parsers.source_aggregator import SourceParser
|
||||
|
||||
from tests.docx_helpers import make_rule_doc, new_document, save_document
|
||||
@@ -125,3 +126,76 @@ def test_validate_path_returns_resolved_path(tmp_path):
|
||||
result = _validate_path(str(good), (".xlsx",))
|
||||
assert isinstance(result, Path)
|
||||
assert result.suffix.lower() == ".xlsx"
|
||||
|
||||
|
||||
# ---------- Impact Agent MVP:existing_system_path(2026-08-23) ----------
|
||||
|
||||
JAVA_PROJECT = {
|
||||
"trade-order/OrderController.java": (
|
||||
"package com.trade.order.controller;\n"
|
||||
"import org.springframework.web.bind.annotation.*;\n"
|
||||
"@RestController\n"
|
||||
"@RequestMapping(\"/api/order\")\n"
|
||||
"public class OrderController {\n"
|
||||
" @GetMapping(\"/{id}\")\n"
|
||||
" public String get(Long id) { return \"ok\"; }\n"
|
||||
"}\n"
|
||||
),
|
||||
"trade-order/OrderServiceImpl.java": (
|
||||
"package com.trade.order.service;\n"
|
||||
"import org.springframework.stereotype.Service;\n"
|
||||
"@Service\n"
|
||||
"public class OrderServiceImpl {\n"
|
||||
" public void createOrder() {}\n"
|
||||
"}\n"
|
||||
),
|
||||
"trade-order/OrderDO.java": (
|
||||
"package com.trade.order.entity;\n"
|
||||
"import com.baomidou.mybatisplus.annotation.TableName;\n"
|
||||
"@TableName(\"trade_order\")\n"
|
||||
"public class OrderDO {\n"
|
||||
" private Long id;\n"
|
||||
"}\n"
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _java_project(tmp_path) -> str:
|
||||
for rel, content in JAVA_PROJECT.items():
|
||||
p = tmp_path / "existing" / rel
|
||||
p.parent.mkdir(parents=True, exist_ok=True)
|
||||
p.write_text(content, encoding="utf-8")
|
||||
return str(tmp_path / "existing")
|
||||
|
||||
|
||||
def test_parse_with_existing_system_path(tmp_path):
|
||||
xlsx = _xlsx(tmp_path)
|
||||
existing = _java_project(tmp_path)
|
||||
|
||||
result = SourceParser().parse(requirement_paths=[xlsx], existing_system_path=existing)
|
||||
|
||||
assert result.existing_system is not None
|
||||
names = {c.class_name for c in result.existing_system.controller_layer}
|
||||
assert "OrderController" in names
|
||||
assert {s.class_name for s in result.existing_system.service_layer} == {"OrderServiceImpl"}
|
||||
assert {e.class_name for e in result.existing_system.entity_layer} == {"OrderDO"}
|
||||
|
||||
|
||||
def test_parse_without_existing_system_path_keeps_none(tmp_path):
|
||||
xlsx = _xlsx(tmp_path)
|
||||
result = SourceParser().parse(requirement_paths=[xlsx])
|
||||
assert result.existing_system is None
|
||||
|
||||
|
||||
def test_parse_existing_system_path_invalid_dir_raises(tmp_path):
|
||||
xlsx = _xlsx(tmp_path)
|
||||
with pytest.raises(CodeParseError):
|
||||
SourceParser().parse(requirement_paths=[xlsx], existing_system_path=tmp_path / "nope")
|
||||
|
||||
|
||||
def test_parse_existing_system_path_without_java_raises(tmp_path):
|
||||
xlsx = _xlsx(tmp_path)
|
||||
empty = tmp_path / "empty"
|
||||
empty.mkdir()
|
||||
with pytest.raises(CodeParseError):
|
||||
SourceParser().parse(requirement_paths=[xlsx], existing_system_path=empty)
|
||||
|
||||
Reference in New Issue
Block a user