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,13 +2,20 @@ import pytest
|
||||
from types import SimpleNamespace
|
||||
from docx import Document
|
||||
|
||||
from genesis.data_models import ParsedTemplate, ChapterMarker
|
||||
from genesis.data_models import (
|
||||
ControllerInfo, EntityInfo, ExistingSystemInfo, ImpactReport, ParsedTemplate,
|
||||
ChapterMarker, ServiceInfo,
|
||||
)
|
||||
from genesis.writer.orchestrator import WriteOrchestrator
|
||||
from genesis.writer.models import ChapterContent
|
||||
|
||||
|
||||
class FakeEngine:
|
||||
def __init__(self):
|
||||
self.last_variables = None
|
||||
|
||||
def chat_structured(self, *, session_id, prompt, variables, schema, retry_count=2):
|
||||
self.last_variables = variables
|
||||
# 返回一个固定章节内容(title + 一个段落块)
|
||||
return SimpleNamespace(
|
||||
data={"title": variables["title"], "blocks": [{"type": "paragraph", "text": "自动生成的内容"}]},
|
||||
@@ -108,3 +115,90 @@ def test_generate_warns_on_unanchored_heading(tmp_path, caplog):
|
||||
template_path=str(tpl),
|
||||
)
|
||||
assert any("附録" in r.message for r in caplog.records)
|
||||
|
||||
|
||||
# ---------- Impact Agent MVP:影响调查结果作为生成主上下文(2026-08-23) ----------
|
||||
|
||||
def _ss_with_existing(template_path):
|
||||
from genesis.data_models import CellValue, ExcelTable, Provenance, SheetType
|
||||
parsed = ParsedTemplate(
|
||||
file_name=template_path,
|
||||
sections=[
|
||||
ChapterMarker(type="heading", name="はじめに", level=1),
|
||||
ChapterMarker(type="placeholder", name="section:introduction", level=0),
|
||||
],
|
||||
placeholders={},
|
||||
styles={"defined": ["Heading 1"], "used": ["Heading 1"]},
|
||||
)
|
||||
existing = ExistingSystemInfo(
|
||||
controller_layer=[ControllerInfo("OrderController", "OrderController", "OrderController.java",
|
||||
"/trade/order", [], "trade-order/OrderController.java")],
|
||||
service_layer=[],
|
||||
entity_layer=[EntityInfo("OrderDO", "OrderDO", "OrderDO.java", "trade_order", ["id"], "trade-order/OrderDO.java")],
|
||||
api_endpoints=[],
|
||||
source_path="samples/existing-system",
|
||||
)
|
||||
headers = ["機能ID", "機能名", "変更区分", "既存対応"]
|
||||
rows = [{
|
||||
"機能ID": CellValue("F002", Provenance("f.xlsx", "機能一覧", 3, "A", "機能ID")),
|
||||
"機能名": CellValue("订单状态查询扩展", Provenance("f.xlsx", "機能一覧", 3, "B", "機能名")),
|
||||
"変更区分": CellValue("変更", Provenance("f.xlsx", "機能一覧", 3, "G", "変更区分")),
|
||||
"既存対応": CellValue("OrderController", Provenance("f.xlsx", "機能一覧", 3, "H", "既存対応")),
|
||||
}]
|
||||
tables = [ExcelTable(name="機能一覧", detected_type=SheetType.FUNCTION, extraction_method="openpyxl",
|
||||
headers=headers, rows=rows)]
|
||||
return SimpleNamespace(template=parsed, existing_system=existing, tables=tables,
|
||||
impact_report=None)
|
||||
|
||||
|
||||
def test_generate_auto_runs_impact_when_existing_system(tmp_path):
|
||||
"""门控:existing_system 非 None 且未显式传 impact_report → 自动跑影响调查,注入生成上下文。"""
|
||||
tpl = tmp_path / "tpl.docx"
|
||||
out = tmp_path / "out.docx"
|
||||
_make_template(str(tpl))
|
||||
engine = FakeEngine()
|
||||
WriteOrchestrator().generate(
|
||||
_ss_with_existing(str(tpl)), str(out),
|
||||
samples_dir="nonexistent_dir_xyz", engine=engine,
|
||||
)
|
||||
impact = engine.last_variables["impact"]
|
||||
assert "project_type=enhancement" in impact
|
||||
assert "OrderController" in impact
|
||||
|
||||
|
||||
def test_generate_without_existing_system_impact_empty(tmp_path):
|
||||
tpl = tmp_path / "tpl.docx"
|
||||
out = tmp_path / "out.docx"
|
||||
_make_template(str(tpl))
|
||||
engine = FakeEngine()
|
||||
WriteOrchestrator().generate(
|
||||
_ss(str(tpl)), str(out),
|
||||
samples_dir="nonexistent_dir_xyz", engine=engine,
|
||||
)
|
||||
assert engine.last_variables["impact"] == ""
|
||||
|
||||
|
||||
def test_generate_explicit_impact_report_used(tmp_path):
|
||||
from genesis.data_models import ChangeAnalysis, ChangeElement, ChangeType
|
||||
tpl = tmp_path / "tpl.docx"
|
||||
out = tmp_path / "out.docx"
|
||||
_make_template(str(tpl))
|
||||
ca = ChangeAnalysis(project_type="enhancement", new_elements=[], modified_elements=[],
|
||||
deleted_elements=[], unchanged_elements=[], warnings=[])
|
||||
report = ImpactReport(metadata={"version": "v1"}, change_analysis=ca, summary={"new": 0})
|
||||
engine = FakeEngine()
|
||||
ss = _ss_with_existing(str(tpl))
|
||||
WriteOrchestrator().generate(ss, str(out), samples_dir="nonexistent_dir_xyz",
|
||||
engine=engine, impact_report=report)
|
||||
assert engine.last_variables["impact"] != ""
|
||||
# 显式传入时也回填 structured_source,便于 QA/日志读取
|
||||
assert ss.impact_report is report
|
||||
|
||||
|
||||
def test_generate_missing_template_path_raises(tmp_path):
|
||||
parsed = ParsedTemplate(file_name=None, sections=[], placeholders={}, styles={"used": []})
|
||||
with pytest.raises(ValueError, match="template_path"):
|
||||
WriteOrchestrator().generate(
|
||||
SimpleNamespace(template=parsed), str(tmp_path / "out.docx"),
|
||||
samples_dir="nonexistent_dir_xyz", engine=FakeEngine(),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user