# 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 | TestSuite(LLM 数据) | `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-ID(REPLACING ==(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 |