Files
cobol-java-v3/docs/superpowers/specs/2026-07-12-design-data-generator.md

333 lines
11 KiB
Markdown
Raw Permalink 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.
# DesignDataGenerator — 式样书驱动测试数据生成
## 1. 概要
### 目的
V3 现有两条数据生成路径:
- **白盒数据**`cobol_testgen.generate_data()`):基于 MC/DC 路径枚举,覆盖决策点
- **LLM 测试数据**`Agent2Data.design()`):基于 FieldTree + DeepSeek API
缺少一条能读取日文详细设计书(`.md`)并生成机能测试数据的路径。
`DesignDataGenerator` 填补这个空缺。它解析式样书中的业务规则、输入输出定义、DB 结构,通过 LLM 生成有业务意义的测试数据。与白盒数据合并后,覆盖可以同时命中决策点分支和业务场景路径。
### 与其他 Agent 的关系
| Agent | 输入 | 输出 | 位置 |
|-------|------|------|------|
| `Agent1Parser` | COPYBOOK | FieldTree | `agents/agent1_parser.py` |
| **`DesignDataGenerator`** | **式样书 .md + 源码 + COPYBOOK** | **`list[dict]`(机能数据)** | **`agents/design_data.py`** ← 新增 |
| `Agent2Data` | FieldTree | TestSuiteLLM 数据) | `agents/agent2_data.py` |
| `Agent3Diagnostic` | FieldResult | 诊断文本 | `agents/agent3_diagnostic.py` |
## 2. 接口定义
### `DesignDataGenerator`
```python
class DesignDataGenerator:
def __init__(
self,
llm_client: LLMClient,
cpy_dirs: list[str | Path],
rules_dir: str | Path = "rules",
):
...
def generate(
self,
design_md_text: str, # 式样书 .md 全文
source_text: str, # COBOL 源码全文
file_db_md_text: str | None = None, # COPY句定义书 .md(可选)
db_md_text: str | None = None, # DB 定义书 .md(可选)
replacing_rules: dict[str, str] | None = None, # REPLACING 展开规则
v3_field_names: list[str] | None = None, # V3 字段名参考列表
) -> list[dict]:
"""生成机能测试数据。
Returns:
list[dict]: 每条记录为 {field_name: value} 格式。
字段名已被 REPLACING 展开,与 generate_data() 兼容。
"""
...
```
### 调用侧入口
```python
def generate_all_data(
program_id: str,
src_text: str,
copybook_dirs: list[str | Path],
st: dict | None = None, # extract_structure 结果(传入可跳过重复解析)
config: Config | None = None, # 用于读取 LLM / 设计书路径配置
design_doc_dir: str | Path | None = None, # 式样书目录
file_db_md_path: str | Path | None = None, # COPY句定义书路径
db_md_path: str | Path | None = None, # DB定义书路径
merge_strategy: str = "merge_to_normal", # merge_to_normal / as_separate_scenes / auto
) -> list[dict]:
"""白盒数据 + 机能数据 + 策略补充 全量生成与合并。
合并策略:
- merge_to_normal: 全部合到一条记录集中
- as_separate_scenes: 返回 (main_records, func_records),由调用方决定如何分场景
- auto: DEPRECATED
"""
...
```
## 3. 架构与数据流
```text
[式样书 .md] ──┐
[源码 .cbl] ───┤
[COPYBOOK] ────┤
[DB定义书 .md] ┘
DesignDataGenerator.generate()
├── InputParser 解析式样书 → ProgramMeta
├── RuleLoader 模式匹配 → 规则文本
├── PromptBuilder LLM 提示词
├── LLMClient DeepSeek API
├── 字段名映射 REPLACING 展开 + V3 字段名参考
└── 返回 list[dict]
generate_all_data()
├── ① generate_data() → 白盒数据
├── ② DesignDataGenerator → 机能数据(式样书存在时)
├── ③ strategy_supplement() → 策略补充
├── ④ 去重(基于 hash)
└── ⑤ 返回 merged_records
```
## 4. 字段名兼容性处理
### 问题
外部 Agent 从 COPYBOOK 解析字段名,含有未展开的 `(A)` 占位符:
```
(A)EMP-ID → V3 是 R01EMP-IDREPLACING ==(A)== BY ==R01== 后)
(A)DATE → V3 是 R01DATE
```
### 映射算法
```python
def _resolve_field_names(
records: list[dict],
replacing_rules: dict[str, str] | None,
v3_field_names: set[str] | None,
) -> list[dict]:
"""将外部 Agent 输出的字段名映射为 V3 兼容名称。"""
if not (replacing_rules or v3_field_names):
return records # 无需映射
result = []
for rec in records:
mapped = {}
for key, val in rec.items():
new_key = key
# 第一步:REPLACING 展开
if replacing_rules:
for old, new in replacing_rules.items():
if old in new_key:
new_key = new_key.replace(old, new)
# 第二步:如果 V3 字段名参考列表存在,精确匹配
if v3_field_names and new_key not in v3_field_names:
# 尝试去掉连字符
no_hyphen = new_key.replace("-", "")
if no_hyphen in v3_field_names:
new_key = no_hyphen
# 尝试下划线转空
no_underscore = new_key.replace("_", "")
if no_underscore in v3_field_names:
new_key = no_underscore
else:
continue # 无法映射 → 丢弃
mapped[new_key] = val
result.append(mapped)
return result
```
### REPLACING 规则提取
从 COBOL 源码中提取 REPLACING 规则:
```python
def _extract_replacing_rules(source_text: str) -> dict[str, str]:
"""从 COPY ... REPLACING ... 语句中提取替换规则。"""
rules = {}
for m in re.finditer(
r'COPY\s+(\w+)\s+REPLACING\s+(?:==(\w+)==\s+BY\s+==(\w+)==\s*)*\.',
source_text, re.IGNORECASE
):
for i in range(0, len(m.groups()) - 1, 2):
old = m.group(i + 2)
new = m.group(i + 3)
if old and new:
rules[old] = new
return rules
```
## 5. 去重策略
### 问题
白盒数据和机能数据可能包含重复记录(完全相同或高度相似)。重复会导致:
- COBOL 程序处理重复
- INSERT PK 冲突
- gcov 行计数失真
### 解决方案
```python
def _dedup(
main_records: list[dict],
additional_records: list[dict],
key_fields: list[str] | None = None,
) -> list[dict]:
"""合并+去重,additional 优先保留。"""
seen = set()
result = []
# 先处理 additional(机能数据优先)
for rec in additional_records:
h = _hash(rec, key_fields)
if h not in seen:
seen.add(h)
result.append(rec)
# 后处理 main
for rec in main_records:
h = _hash(rec, key_fields)
if h not in seen:
seen.add(h)
result.append(rec)
return result
def _hash(rec: dict, key_fields: list[str] | None) -> tuple:
"""生成记录的特征哈希。"""
if key_fields:
return tuple(rec.get(k, '') for k in key_fields)
return tuple(sorted(rec.items()))
```
## 6. 多轮融合策略
### 策略选项
```yaml
# 在 config/programs/{pid}.yaml 或 CLI 参数中控制
merge_strategy: merge_to_normal # 默认值
```
| 策略 | 行为 | 适用场景 |
|------|------|---------|
| `merge_to_normal` | 机能数据合并到 normal 场景,与白盒数据一起执行 | 简单程序,一轮覆盖所有 |
| `as_separate_scenes` | 机能数据作为独立场景 `run_func/` 执行 | 复杂程序,业务场景与白盒路径不兼容 |
| `auto` | 有设计书时用 `as_separate_scenes`,否则 `merge_to_normal` | 通用场景 |
### `as_separate_scenes` 时的目录
```
runtime/{pid}/
run_normal/ ← 白盒数据(MC/DC 路径)
input/ json/ output/
run_func/ ← 机能数据(式样书驱动)
input/ json/ output/
gcov/run_normal/ ← 各场景独立 .gcda
gcov/run_func/
```
## 7. 调用链路
### DB 管道(`orchestrator_db.py:step2_generate_inputs()`
```python
# 原本
recs = generate_data(src_text, st, copybook_dirs=cbd)
# 改为
recs = generate_all_data(
program_id=self.program_id,
src_text=src_text,
copybook_dirs=cbd,
st=st,
config=self.config,
design_doc_dir=str(v3_root / "详细设计书"),
file_db_md_path=str(v3_root / "详细设计书" / "COPY句定义书.md"),
)
```
### 原生管道(`cobol_testgen/__init__.py:main()`
```python
# 原本
recs = generate_data(src_text, st, copybook_dirs=cbd)
# 改为
recs = generate_all_data(
program_id=filepath.stem,
src_text=source,
copybook_dirs=[str(filepath.parent / '..' / 'cpy')],
st=st,
design_doc_dir=str(design_doc_base),
)
```
## 8. 外部 Agent 代码迁移清单
### 从 `jcl-cobol-data-create/` 迁移
| 源文件 | 目标 | 变更 |
|--------|------|------|
| `input_parser.py` | `agents/design_data_input_parser.py` | 接收文本而非路径 |
| `rule_loader.py` | 内置到 `design_data.py` 方法 | 简化接口 |
| `prompt_builder.py` | 内置到 `design_data.py` | 使用 `LLMClient` |
| `api_client.py` | **不需要** | V3 已有 `agents/llm.py` |
| `output_writer.py` | **不需要** | 不写文件 |
| `models.py` | 部分并入 `design_data.py` | 按需移植数据类 |
| `rules/pgm_pattern/*.md` | `rules/pgm_pattern/` | 直接复制 |
| `rules/special_feature/*.md` | `rules/special_feature/` | 直接复制 |
| `layout/*.md` | `layout/` | 参考用 |
### 新建文件清单
| 文件 | 说明 |
|------|------|
| `agents/design_data.py` | DesignDataGenerator 主类 |
| `agents/design_data_input_parser.py` | 式样书解析器(从外部 Agent 移植) |
| `cobol_testgen/data_merger.py` | `generate_all_data()` + 去重 + 字段名映射 |
## 9. 错误处理
### 场景
| 场景 | 行为 |
|------|------|
| 式样书 .md 不存在 | 跳过机能数据,仅返回白盒数据 |
| LLM 调用超时 | 捕获异常,跳过机能数据,日志告警 |
| LLM 返回非法 JSON | `try/except`,返回空列表 |
| 字段名映射失败(无法匹配 V3 字段名) | 丢弃该字段,日志记录 |
| 式样书存在但无法解析(格式不标准) | `try/except`,跳过机能数据 |
## 10. 测试计划
| 测试 | 方法 |
|------|------|
| 式样书不存在时回退到仅白盒 | mock `os.path.exists` → False |
| LLM 超时/出错时回退 | mock `LLMClient.call` → raise TimeoutError |
| 字段名 REPLACING 映射 | Fixture 含 `(A)EMP-ID` → 期待 `R01EMP-ID` |
| 去重逻辑 | 白盒 10 条 + 机能 5 条(含 2 条重复)→ 期待 13 条 |
| 多轮融合策略 `as_separate_scenes` | 验证 `run_func/` 目录被创建 |
| 实际 COBOL 执行 + gcov | KIN08DBU 白盒+机能合并后覆盖率是否 >= 原 52/60 |