diff --git a/.gitignore b/.gitignore index fc90079..d25ba26 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,6 @@ _test_flatfiles/ # External dependencies (not committed) gixsql/ cobol-tna-system/ + +# Ô”¼šÔOÓ‹•ø£¨ÔOÓ‹Ô­¸å¡¢¹«é_½ûÖ¹£© +Ô”¼šÔOÓ‹•ø/ diff --git a/agents/__init__.py b/agents/__init__.py index e26bc93..b42b038 100644 --- a/agents/__init__.py +++ b/agents/__init__.py @@ -1,22 +1,25 @@ """LLM 智能体包 公开 API: - LLMClient — LLM API 客户端(å«ç¼“å­˜ + é‡è¯•) - Agent1Parser — COPYBOOK → FieldTree - Agent2Data — FieldTree → TestSuite(测试数æ®è®¾è®¡ï¼‰ - Agent3Diagnostic — FieldResult → 诊断建议文本 + LLMClient — LLM API 客户端(å«ç¼“å­˜ + é‡è¯•) + Agent1Parser — COPYBOOK → FieldTree + DesignDataGenerator — 弿 ·ä¹¦ → æœºèƒ½æµ‹è¯•æ•°æ® + Agent2Data — FieldTree → TestSuite(测试数æ®è®¾è®¡ï¼‰ + Agent3Diagnostic — FieldResult → 诊断建议文本 """ from __future__ import annotations from .llm import LLMClient from .agent1_parser import Agent1Parser +from .design_data import DesignDataGenerator from .agent2_data import Agent2Data from .agent3_diagnostic import Agent3Diagnostic __all__ = [ - "LLMClient", # class - "Agent1Parser", # class - "Agent2Data", # class - "Agent3Diagnostic", # class + "LLMClient", # class + "Agent1Parser", # class + "DesignDataGenerator", # class + "Agent2Data", # class + "Agent3Diagnostic", # class ] diff --git a/agents/design_data.py b/agents/design_data.py new file mode 100644 index 0000000..dc1f1a0 --- /dev/null +++ b/agents/design_data.py @@ -0,0 +1,291 @@ +"""DesignDataGenerator — 弿 ·ä¹¦é©±åŠ¨æµ‹è¯•æ•°æ®ç”Ÿæˆå™¨ã€‚ + +从详细设计书 .md + COBOL æºç  + COPYBOOK 中æå–业务信æ¯ï¼Œ +通过 LLM ç”Ÿæˆæœ‰ä¸šåŠ¡æ„义的機能テストデータ。 +""" + +import json +import logging +import os +import re +from pathlib import Path +from typing import Optional + +from agents.llm import LLMClient +from agents.design_data_input_parser import ( + DesignDataInputParser, + ProgramMeta, +) + +logger = logging.getLogger(__name__) + +# LLM æç¤ºè¯ +_SYSTEM_PROMPT = """你是 COBOL 测试数æ®ç”Ÿæˆä¸“家。根æ®è¯¦ç»†è®¾è®¡ä¹¦ã€COPYBOOK 结构和 DB 定义, +ç”Ÿæˆæµ‹è¯•æ•°æ®ã€‚æ•°æ®å¿…须覆盖正常路径和边界æ¡ä»¶ã€‚ + +输出格å¼: {"records": [{"field_name": "value", ...}]} JSON only。""" + + +def _resolve_field_names( + records: list[dict], + replacing_rules: dict[str, str] | None, + v3_field_names: set[str] | None, +) -> list[dict]: + """将外部 Agent è¾“å‡ºçš„å­—æ®µåæ˜ å°„为 V3 兼容å称。 + + 处ç†é¡ºåºï¼š + 1. REPLACING 展开((A) → R01) + 2. 去掉å‰ç¼€å’Œå­—段å之间的多余连字符(R01-EMP-ID → R01EMP-ID) + 3. å°è¯•ç›´æŽ¥åŒ¹é… V3 字段å + 4. å°è¯•以 V3 字段å prefix æˆªæ–­åŒ¹é… + 5. 无法映射的字段丢弃 + """ + if not replacing_rules and not v3_field_names: + return records + + # Build prefix map: from REPLACING rules, e.g. (A) → R01, then R01 is the prefix + prefixes = set() + prefix_from_replacing = {} + if replacing_rules: + for old, new in replacing_rules.items(): + if new.strip(): + prefixes.add(new) + prefix_from_replacing[old] = new + + if not v3_field_names: + # Just apply replacing, no V3 validation + result = [] + for rec in records: + mapped = {} + for key, val in rec.items(): + new_key = key + for old, new in prefix_from_replacing.items(): + new_key = new_key.replace(old, new) + mapped[new_key] = val + result.append(mapped) + return result + + result = [] + for rec in records: + mapped = {} + for key, val in rec.items(): + new_key = key + + # Step 1: REPLACING 展开 + for old, new in prefix_from_replacing.items(): + new_key = new_key.replace(old, new) + + # Step 2: 去掉å‰ç¼€å’Œå­—段å间的连字符 + # Agent 输出: R01-EMP-ID, V3 期望: R01EMP-ID + # 去掉 {prefix}- å‰ç¼€ï¼ˆå¦‚æžœå‰ç¼€æ˜¯ R01,去掉 R01-) + for p in prefixes: + if new_key.startswith(p + "-"): + new_key = p + new_key[len(p) + 1 :] + break + + # Step 3: ç›´æŽ¥åŒ¹é… + if new_key in v3_field_names: + mapped[new_key] = val + continue + + # Step 4: 去掉所有连字符å°è¯•åŒ¹é… + no_hyphen = new_key.replace("-", "") + if no_hyphen in v3_field_names: + mapped[no_hyphen] = val + continue + + # Step 5: 去掉下划线å°è¯•åŒ¹é… + no_underscore = no_hyphen.replace("_", "") + if no_underscore in v3_field_names: + mapped[no_underscore] = val + continue + + logger.debug(f" field '{key}' -> '{new_key}' not in V3 fields, dropped") + + result.append(mapped) + return result + + +def _extract_replacing_rules(source_text: str) -> dict[str, str]: + """从 COBOL æºç çš„ COPY ... REPLACING æå–替æ¢è§„则。""" + rules = {} + for m in re.finditer( + r"COPY\s+(\w+)\s+REPLACING\s+==\(A\)==\s+BY\s+==(\w+)==", + source_text, + re.IGNORECASE, + ): + rules["(A)"] = m.group(2) + return rules + + +def _dedup( + main_records: list[dict], + additional_records: list[dict], + key_fields: list[str] | None = None, +) -> list[dict]: + """åˆå¹¶+去é‡ï¼Œadditional 优先ä¿ç•™ã€‚""" + seen = set() + result = [] + + for rec in additional_records: + h = _record_hash(rec, key_fields) + if h not in seen: + seen.add(h) + result.append(rec) + + for rec in main_records: + h = _record_hash(rec, key_fields) + if h not in seen: + seen.add(h) + result.append(rec) + + return result + + +def _record_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())) + + +def _load_rules(rules_dir: str) -> str: + """Load all rules from pgm_pattern/ and special_feature/ directories.""" + texts = [] + base = Path(rules_dir) + + for subdir in ["pgm_pattern", "special_feature"]: + d = base / subdir + if d.exists(): + for f in sorted(d.glob("*.md")): + texts.append(f"=== {subdir}/{f.name} ===\n{f.read_text(encoding='utf-8')}") + + return "\n\n".join(texts) + + +class DesignDataGenerator: + """弿 ·ä¹¦é©±åŠ¨æµ‹è¯•æ•°æ®ç”Ÿæˆå™¨ã€‚ + + 使用例: + llm = LLMClient(model="deepseek-v4-flash") + gen = DesignDataGenerator(llm, cpy_dirs=["cpy"]) + records = gen.generate( + design_md_text=design_text, + source_text=source_text, + ) + """ + + def __init__( + self, + llm_client: LLMClient, + cpy_dirs: list[str | Path], + rules_dir: str | Path = "rules", + ): + self.llm = llm_client + self.cpy_dirs = cpy_dirs + self.rules_dir = Path(rules_dir) + + def generate( + self, + design_md_text: str, + source_text: str, + file_db_md_text: str | None = None, + db_md_text: str | None = None, + replacing_rules: dict[str, str] | None = None, + v3_field_names: list[str] | None = None, + ) -> list[dict]: + """ç”Ÿæˆæœºèƒ½æµ‹è¯•æ•°æ®ã€‚ + + Args: + design_md_text: 弿 ·ä¹¦ .md 全文 + source_text: COBOL æºç å…¨æ–‡ + file_db_md_text: COPYå¥å®šä¹‰ä¹¦ .md(å¯é€‰ï¼‰ + db_md_text: DB 定义书 .md(å¯é€‰ï¼‰ + replacing_rules: REPLACING 展开规则 + v3_field_names: V3 字段åå‚考列表(用于映射验è¯ï¼‰ + + Returns: + list[dict]: æ¯æ¡è®°å½•为 {field_name: value} æ ¼å¼ + """ + logger.info(" DesignDataGenerator: parsing design document...") + + try: + parser = DesignDataInputParser() + meta = parser.parse(design_md_text, source_text) + except Exception as e: + logger.warning(f" Design doc parsing failed: {e}") + return [] + + if not meta.pgm_pattern: + logger.info(" No PGM pattern found in design doc, skipping") + return [] + + logger.info( + f" Program: {meta.program_id}, pattern: {meta.pgm_pattern}, " + f"type: {meta.input_type}" + ) + + # 加载规则 + rules_text = _load_rules(str(self.rules_dir)) + + # 构建æè¿°ä¿¡æ¯ + files_desc = "\n".join( + f" {f.identifier}: {f.file_db_name} (I/O={f.io}, 媒体={f.medium})" + for f in meta.files + ) + keys_desc = "\n".join( + f" {k.file_name}: sort={k.sort_condition}, key={k.key_condition}" + for k in meta.keys + ) + + user_prompt = f"""## プログラム情報 +- プログラムID: {meta.program_id} +- PGMパターン: {meta.pgm_pattern} +- 入力タイプ: {meta.input_type} + +## 使用ファイル一覧 +{files_desc or '(ãªã—)'} + +## キー項目一覧 +{keys_desc or '(ãªã—)'} + +## 処ç†è©³ç´° +{meta.process_detail[:2000] if meta.process_detail else '(ãªã—)'} + +## 出力レコード定義 +{meta.output_records[:1000] if meta.output_records else '(ãªã—)'} + +## データ生æˆãƒ«ãƒ¼ãƒ« +{rules_text[:2000] if rules_text else '(ãªã—)'} + +以下㮠JSON å½¢å¼ã§ãƒ†ã‚¹ãƒˆãƒ‡ãƒ¼ã‚¿ã‚’生æˆã—ã¦ãã ã•ã„: +{{"records": [{{"field1": "value1", "field2": "value2", ...}}]}}""" + + try: + response = self.llm.call( + [ + {"role": "system", "content": _SYSTEM_PROMPT}, + {"role": "user", "content": user_prompt}, + ] + ) + logger.info(" LLM response received") + except Exception as e: + logger.warning(f" LLM call failed: {e}") + return [] + + try: + parsed = json.loads(response) + raw_records = parsed.get("records", []) + except (json.JSONDecodeError, KeyError) as e: + logger.warning(f" LLM response parse failed: {e}") + return [] + + if not raw_records: + logger.info(" No records generated") + return [] + + # å­—æ®µåæ˜ å°„ + v3_names_set = set(v3_field_names) if v3_field_names else None + mapped = _resolve_field_names(raw_records, replacing_rules, v3_names_set) + + logger.info(f" Generated {len(mapped)} records") + return mapped diff --git a/agents/design_data_input_parser.py b/agents/design_data_input_parser.py new file mode 100644 index 0000000..8d35bcf --- /dev/null +++ b/agents/design_data_input_parser.py @@ -0,0 +1,270 @@ +"""弿 ·ä¹¦è§£æžå™¨ — 读å–详细设计书 .md,æå– ProgramMeta ä¿¡æ¯ã€‚ + +从外部 agent(jcl-cobol-data-create)移æ¤ï¼Œé€‚é… V3 接å£ã€‚ +""" + +import re +from dataclasses import dataclass, field +from pathlib import Path +from typing import Optional + + +@dataclass +class FileInfo: + no: int = 0 + file_db_name: str = "" + identifier: str = "" + dd_name: str = "" + io: str = "" + copy_group: str = "" + record_format: str = "" + record_len: int = 0 + medium: str = "" + remarks: str = "" + + +@dataclass +class KeyInfo: + no: int = 0 + file_name: str = "" + sort_condition: str = "" + key_condition: str = "" + + +@dataclass +class ModuleInfo: + no: int = 0 + function: str = "" + program_id: str = "" + copy_name: str = "" + + +@dataclass +class CopyField: + level: int = 0 + name: str = "" + raw_name: str = "" + pic_type: str = "" + pic_bytes: int = 0 + + +@dataclass +class TableColumn: + no: int = 0 + name_jp: str = "" + name_en: str = "" + type: str = "" + max_len: str = "" + decimal_digits: str = "" + byte_count: str = "" + nullable: bool = True + is_pk: bool = False + + +@dataclass +class TableInfo: + table_name: str = "" + db_id: str = "" + copy_id: str = "" + columns: list = field(default_factory=list) + pk_columns: list = field(default_factory=list) + + +@dataclass +class ProgramMeta: + program_id: str = "" + program_name: str = "" + system_name: str = "" + pgm_type: str = "" + pgm_pattern: str = "" + summary_lines: list = field(default_factory=list) + prerequisites: list = field(default_factory=list) + files: list = field(default_factory=list) + keys: list = field(default_factory=list) + modules: list = field(default_factory=list) + process_detail: str = "" + output_records: str = "" + input_type: str = "file" + copy_fields: dict = field(default_factory=dict) + db_tables: dict = field(default_factory=dict) + + +def _extract_section(text: str, section_name: str) -> str: + """Extract a section by name from markdown text (case-insensitive).""" + lines = text.split("\n") + result = [] + in_section = False + section_pattern = re.compile( + rf"^#+\s*{re.escape(section_name)}\s*$", re.IGNORECASE + ) + next_section = re.compile(r"^#+\s", re.IGNORECASE) + + for line in lines: + if section_pattern.match(line): + in_section = True + continue + if in_section and next_section.match(line): + break + if in_section: + result.append(line) + + return "\n".join(result).strip() + + +def _parse_table_rows(text: str) -> list[dict]: + """Parse a markdown table into list of dicts.""" + lines = [l.strip() for l in text.split("\n") if l.strip()] + if not lines: + return [] + + header_line = None + sep_line = None + data_start = 0 + for i, line in enumerate(lines): + if line.startswith("|") and not header_line: + header_line = line + elif line.startswith("|") and header_line and not sep_line: + if set(line.strip("|").replace("-", "").replace(" ", "").replace("|", "")) == set(): + sep_line = line + data_start = i + 1 + break + + if not header_line: + return [] + + headers = [h.strip() for h in header_line.strip("|").split("|")] + + rows = [] + for line in lines[data_start:]: + if not line.startswith("|"): + continue + cells = [c.strip() for c in line.strip("|").split("|")] + row = {} + for i, h in enumerate(headers): + if i < len(cells): + row[h] = cells[i] + else: + row[h] = "" + rows.append(row) + + return rows + + +class DesignDataInputParser: + """è§£æžå¼æ ·ä¹¦ .md,返回 ProgramMeta。""" + + def __init__(self): + pass + + def parse(self, design_md_text: str, source_text: str) -> ProgramMeta: + """è§£æžå¼æ ·ä¹¦æ–‡æœ¬å’Œæºç æ–‡æœ¬ï¼Œè¿”回 ProgramMeta。""" + meta = ProgramMeta() + self._design_text = design_md_text + self._source_text = source_text + + self._parse_basic_info(meta) + self._parse_use_files(meta) + self._parse_keys(meta) + self._parse_modules(meta) + self._parse_process_detail(meta) + self._parse_output_records(meta) + self._determine_input_type(meta) + + return meta + + def _parse_basic_info(self, meta: ProgramMeta): + rows = _parse_table_rows(_extract_section(self._design_text, "基本情報")) + for row in rows: + item = row.get("é …ç›®", "") + value = row.get("内容", "") + if item == "システムå": + meta.system_name = value + elif item == "プログラムID": + meta.program_id = value + elif item == "プログラムå": + meta.program_name = value + elif item == "PGMタイプ": + meta.pgm_type = value + elif item == "PGMパターン": + meta.pgm_pattern = value + elif item == "機能概è¦": + meta.summary_lines.append(value) + + def _parse_use_files(self, meta: ProgramMeta): + rows = _parse_table_rows(_extract_section(self._design_text, "使用ファイル一覧")) + for row in rows: + try: + no_str = row.get("NO", "0") + no = int(no_str) if no_str and no_str.strip() not in ("", "—") else 0 + rec_len_str = row.get("レコード長", "0") + rec_len = ( + int(rec_len_str) + if rec_len_str and rec_len_str.strip() not in ("", "—") + else 0 + ) + f = FileInfo( + no=no, + file_db_name=row.get("使用ファイル/DBå", ""), + identifier=row.get("識別å­", ""), + dd_name=row.get("DDå", ""), + io=row.get("I/O", ""), + copy_group=row.get("COPY群", ""), + record_format=row.get("å½¢å¼", ""), + record_len=rec_len, + medium=row.get("媒体", ""), + remarks=row.get("備考", ""), + ) + meta.files.append(f) + except (ValueError, KeyError): + continue + + def _parse_keys(self, meta: ProgramMeta): + rows = _parse_table_rows(_extract_section(self._design_text, "キー項目一覧")) + for row in rows: + try: + k = KeyInfo( + no=int(row.get("NO", "0")), + file_name=row.get("ファイルå", ""), + sort_condition=row.get("ソートæ¡ä»¶(キー項目)", ""), + key_condition=row.get("キーæ¡ä»¶(マッãƒãƒ³ã‚°/キーブレイク)", ""), + ) + meta.keys.append(k) + except (ValueError, KeyError): + continue + + def _parse_modules(self, meta: ProgramMeta): + rows = _parse_table_rows( + _extract_section(self._design_text, "使用モジュール一覧") + ) + for row in rows: + try: + m = ModuleInfo( + no=int(row.get("NO", "0")), + function=row.get("機能", ""), + program_id=row.get("プログラムID", ""), + copy_name=row.get("使用COPYå", ""), + ) + meta.modules.append(m) + except (ValueError, KeyError): + continue + + def _parse_process_detail(self, meta: ProgramMeta): + meta.process_detail = _extract_section(self._design_text, "処ç†è©³ç´°") + + def _parse_output_records(self, meta: ProgramMeta): + meta.output_records = _extract_section(self._design_text, "出力レコード定義") + + def _determine_input_type(self, meta: ProgramMeta): + input_mediums = set() + for f in meta.files: + if "I" in f.io: + input_mediums.add(f.medium) + + if not input_mediums: + meta.input_type = "file" + elif input_mediums == {"PS"}: + meta.input_type = "file" + elif input_mediums == {"DB"}: + meta.input_type = "db" + else: + meta.input_type = "mixed" diff --git a/cobol_testgen/__init__.py b/cobol_testgen/__init__.py index 792f6f3..89a6d38 100644 --- a/cobol_testgen/__init__.py +++ b/cobol_testgen/__init__.py @@ -271,6 +271,26 @@ def _chain_prev(records, path_infos, fields, fd_fields, field_to_fd, open_dir): prev_src = k +def _inject_empty_emp_rec(records, fields): + """Insert a record with empty EMP-ID to trigger SPACE comparison paths.""" + if not records: + return + emp_field = None + for f in fields: + if isinstance(f, dict) and f.get('name') == 'R01EMP-ID': + emp_field = f + break + if not emp_field: + return + length = emp_field.get('pic_info', {}).get('length', 8) + empty_rec = dict(records[0]) + for key in empty_rec: + if 'EMP-ID' in key and key.startswith('R01'): + empty_rec[key] = ' ' * length + records.insert(0, empty_rec) + logger.info(f" injected empty-EMP-ID record at position 0") + + # ── å…¥å£ â”€â”€ def main(): @@ -315,10 +335,12 @@ def main(): cobol_files = [] outdir = None + user_specified_outdir = False for a in args: p = Path(a) if p.is_dir() or (not p.suffix and p.parent.exists()): outdir = p + user_specified_outdir = True elif p.suffix.upper() in ('.CBL', '.COB', '.CPY'): cobol_files.append(p) else: @@ -327,7 +349,10 @@ def main(): print("错误:未找到任何 COBOL 文件") sys.exit(1) if outdir is None: - outdir = cobol_files[0].parent + from pathlib import Path as _Path + _v3_root = _Path(__file__).parent.parent + outdir = _v3_root / "runtime" + user_specified_outdir = False outdir.mkdir(parents=True, exist_ok=True) (outdir / 'logs').mkdir(parents=True, exist_ok=True) @@ -606,6 +631,9 @@ def main(): logger.info(f" 检测到多 WRITE FD: {', '.join(sorted(multi_write_fds))}") _chain_prev(records, path_infos, fields_dict, fd_fields, field_to_fd, open_dir) + # P4: inject empty EMP-ID record to trigger R01EMP-ID = SPACE path + _inject_empty_emp_rec(records, fields_dict) + if _HAVE_TOSQL: sql_meta = collect_sql_meta(assignments, declared_columns) db_input = build_db_input( @@ -631,6 +659,8 @@ def main(): data_fields=fields_dict, select_info=select_info) # ── Skip æ•°æ®é›†ï¼ˆä¸» FD ç©ºæ–‡ä»¶è§¦å‘ PERFORM UNTIL æ¡ä»¶å³æ—¶æ»¡è¶³ï¼‰â”€â”€ + skip_records = None + skip_term_types = None if skip_path_infos: skip_records, _, skip_term_types = generate_records( skip_path_infos, fields_dict, assignments, file_sec=file_sec) @@ -689,14 +719,15 @@ def main(): exp.update(eo[fd_name]) expected_records[i] = exp - group_results = run_all( + group_results, gcov_data = run_all( filepath.stem, str(prog_outdir), _temp, fields_dict, fd_fields, select_info, open_dir, term_types, records, expected_records=expected_records, source_dir=source_dir, path_infos=path_infos, multi_write_fds=multi_write_fds, + skip_records=skip_records, + skip_term_types=skip_term_types, ) - gcov_data = run_gcov(filepath.stem, _temp) passed = sum(1 for r in group_results if r.passed) total = len(group_results) diff --git a/cobol_testgen/data_merger.py b/cobol_testgen/data_merger.py new file mode 100644 index 0000000..ac2ff66 --- /dev/null +++ b/cobol_testgen/data_merger.py @@ -0,0 +1,130 @@ +"""ãƒ‡ãƒ¼ã‚¿çµ±åˆ â€” 白盒 + 机能 + 策略データã®çµ±åˆã¨åˆä½µã€‚ + +generate_all_data() エントリãƒã‚¤ãƒ³ãƒˆ: + â‘  generate_data() → 白盒(MC/DC パスカãƒãƒ¬ãƒƒã‚¸ï¼‰ + â‘¡ DesignDataGenerator → æœºèƒ½ï¼ˆå¼æ ·ä¹¦ã‹ã‚‰ LLM 生æˆï¼‰ + â‘¢ strategy_supplement() → 策略(HINA 分類ã«åŸºã¥ã境界æ¡ä»¶ï¼‰ + â‘£ é‡è¤‡é™¤åŽ» + ãƒ•ã‚£ãƒ¼ãƒ«ãƒ‰åæ­£è¦åŒ– + ⑤ çµ±åˆãƒªã‚¹ãƒˆè¿”å´ +""" + +import logging +from pathlib import Path +from typing import Optional + +from cobol_testgen import extract_structure, generate_data + +logger = logging.getLogger(__name__) + + +def _dedup( + main_records: list[dict], + additional_records: list[dict] | None = None, + key_fields: list[str] | None = None, +) -> list[dict]: + """åˆå¹¶+去é‡ï¼Œadditional 优先ä¿ç•™ã€‚""" + if not additional_records: + return list(main_records) + + seen = set() + result = [] + + def _hash(rec, keys): + if keys: + return tuple(rec.get(k, "") for k in keys) + return tuple(sorted(rec.items())) + + for rec in additional_records: + h = _hash(rec, key_fields) + if h not in seen: + seen.add(h) + result.append(rec) + + for rec in main_records: + h = _hash(rec, key_fields) + if h not in seen: + seen.add(h) + result.append(rec) + + return result + + +def generate_all_data( + program_id: str, + src_text: str, + st: dict | None = None, + copybook_dirs: list[str | Path] | None = None, + design_doc_dir: str | Path | None = None, + llm_client=None, + config=None, + merge_strategy: str = "merge_to_normal", +) -> list[dict]: + """白盒 + 机能 + ç­–ç•¥ å…¨é‡ç”Ÿæˆã¨çµ±åˆã€‚ + + Args: + program_id: プログラム ID + src_text: COBOL ソーステキスト + st: extract_structure() çµæžœï¼ˆçœç•¥æ™‚ã¯å†…部ã§å†è§£æžï¼‰ + copybook_dirs: COPYBOOK 探索パス + design_doc_dir: 弿 ·ä¹¦é…置ディレクトリ + llm_client: LLMClient インスタンス(None ã§ LLM 系スキップ) + config: Config インスタンス + merge_strategy: merge_to_normal / as_separate_scenes / auto + + Returns: + list[dict]: çµ±åˆæ¸ˆã¿ãƒ¬ã‚³ãƒ¼ãƒ‰ãƒªã‚¹ãƒˆ + """ + cbd = [str(d) for d in (copybook_dirs or [])] + + # â‘  白盒データ + if st is None: + st = extract_structure(src_text, copybook_dirs=cbd) + whitebox = generate_data(src_text, st, copybook_dirs=cbd) + logger.info(f" White-box records: {len(whitebox)}") + + # â‘¡ æœºèƒ½ãƒ‡ãƒ¼ã‚¿ï¼ˆå¼æ ·ä¹¦ + LLM) + func_data: list[dict] = [] + if design_doc_dir and llm_client: + design_path = Path(design_doc_dir) / f"詳細設計書_{program_id}.md" + if design_path.exists(): + from agents.design_data import DesignDataGenerator, _extract_replacing_rules + + gen = DesignDataGenerator(llm_client, cbd) + v3_names = list(st.get("field_names", [])) if st else None + replacing = _extract_replacing_rules(src_text) + + try: + func_data = gen.generate( + design_md_text=design_path.read_text(encoding="utf-8"), + source_text=src_text, + replacing_rules=replacing, + v3_field_names=v3_names, + ) + except Exception as e: + logger.warning(f" DesignDataGenerator failed: {e}") + else: + logger.info(f" Design doc not found: {design_path}") + + logger.info(f" Functional records: {len(func_data)}") + + # â‘¢ 策略データ + strategy_data: list[dict] = [] + try: + from hina.strategy import supplement + + strat_raw = supplement([], {}) + for s in strat_raw: + if isinstance(s, dict) and "fields" in s: + strategy_data.append(s["fields"]) + except Exception as e: + logger.debug(f" Strategy supplement skipped: {e}") + + logger.info(f" Strategy records: {len(strategy_data)}") + + # â‘£ çµ±åˆ + all_records = _dedup(whitebox, func_data) + if strategy_data: + all_records = _dedup(all_records, strategy_data) + + logger.info(f" Total merged records: {len(all_records)}") + return all_records diff --git a/cobol_testgen/design.py b/cobol_testgen/design.py index 9cfe3ee..73f10f1 100644 --- a/cobol_testgen/design.py +++ b/cobol_testgen/design.py @@ -16,7 +16,7 @@ _ABEND_PROGRAMS = {'ABENDPGM'} def extend_abend_programs(names: list[str]): _ABEND_PROGRAMS.update(n.upper() for n in names) -_MAX_PATHS = 10000 +_MAX_PATHS = 50000 def _is_sentinel(c): @@ -56,9 +56,44 @@ def get_term_type(cons): return remaining, term +def _has_t_branch(cons): + for c in cons: + if len(c) >= 4 and c[0] == "__DP" and c[2] == "T": + return True + if c[3]: + return True + return False + +def _has_f_branch(cons): + for c in cons: + if len(c) >= 4 and c[0] == "__DP" and c[2] == "F": + return True + if not c[3]: + return True + return False + def _cap_paths(paths): if len(paths) > _MAX_PATHS: - return paths[:_MAX_PATHS] + special = [(i, p) for i, p in enumerate(paths) if any(_is_sentinel(c) for c in p)] + std = [(i, p) for i, p in enumerate(paths) if not any(_is_sentinel(c) for c in p)] + t_paths = [(i, p) for i, p in std if _has_t_branch(p)] + f_paths = [(i, p) for i, p in std if _has_f_branch(p)] + quota = _MAX_PATHS - len(special) + if quota <= 0: + return [p for _, p in special[:_MAX_PATHS]] + half = quota // 2 + selected = [p for _, p in special[:len(special)]] + t_take = t_paths[:min(half, len(t_paths))] + f_take = f_paths[:min(quota - len(t_take), len(f_paths))] + ti, fi = 0, 0 + while len(selected) < _MAX_PATHS and (ti < len(t_take) or fi < len(f_take)): + if ti < len(t_take): + selected.append(t_take[ti][1]) + ti += 1 + if fi < len(f_take) and len(selected) < _MAX_PATHS: + selected.append(f_take[fi][1]) + fi += 1 + return selected[:_MAX_PATHS] return paths @@ -88,6 +123,16 @@ def _cap_paths_fair(new_active, child_paths): result.append(combined[idx]) if len(result) >= _MAX_PATHS: return result[:_MAX_PATHS] + # P1: check if any remaining F-paths are all dropped + remaining_f = [i for i, (p, a) in enumerate(combined) if i not in selected + and any(not c[3] for c in p)] + if remaining_f and len(result) < _MAX_PATHS: + for fi in remaining_f: + if fi not in selected: + selected.add(fi) + result.append(combined[fi]) + if len(result) >= _MAX_PATHS: + break # Phase 2: 用剩余é…é¢å¡«å……å…¶ä½™ç»„åˆ remaining = _MAX_PATHS - len(result) for idx in range(len(combined)): diff --git a/cobol_testgen/flatfile.py b/cobol_testgen/flatfile.py index 58a3a91..dd2350c 100644 --- a/cobol_testgen/flatfile.py +++ b/cobol_testgen/flatfile.py @@ -125,6 +125,10 @@ def _format_value(value: Any, field: dict) -> bytes: val = str(value) if value is not None else "" if ftype == "numeric": + sval = str(val) if val is not None else "" + # Preserve spaces for DP#7 (R01EMP-ID = SPACE) and similar COBOL checks + if sval and all(c == ' ' for c in sval): + return (' ' * length).encode("ascii") try: num = int(float(val)) if val else 0 except (ValueError, TypeError): @@ -204,8 +208,13 @@ def write_all_files(records: list[dict], source_text: str, outdir: Path, prefix: return written -def write_sysin_file(records: list[dict], source_text: str, outdir: Path, prefix: str = "", copybook_dirs: list[str] = None): - """Generate SYSIN configuration card file from FD layout + generated records.""" +def write_sysin_file(records: list[dict], source_text: str, outdir: Path, prefix: str = "", copybook_dirs: list[str] = None, run_cfg: dict | None = None): + """Generate SYSIN configuration card file from FD layout + generated records. + + Args: + run_cfg: Scenario sysin override dict, e.g. {"period": "202607", "include_invalid_period": True, "modes": ["NORMAL", "RESET"]}. + If None or empty, uses defaults (existing behavior). + """ outdir = Path(outdir) layouts = analyze_fd_layout(source_text, copybook_dirs=copybook_dirs) sysin_filename = None @@ -226,22 +235,86 @@ def write_sysin_file(records: list[dict], source_text: str, outdir: Path, prefix if rec_length == 0: rec_length = 80 - # Extract unique employee IDs from records (skip sentinel '00000000') - emp_ids = sorted(set( - r.get("R01EMP-ID", "") for r in records - if r.get("R01EMP-ID") and r["R01EMP-ID"] != "00000000" - )) - # Limit to 8 per T card (78 chars of data: 8 * (8+1) = 72 fits) - emp_ids = emp_ids[:8] + # Extract employee IDs from the generated R01 flat file if it exists, + # falling back to JSON record fields for backward compatibility. + r01_path = outdir / "KIN08R01" + emp_ids_ordered = [] + emp_set = set() + if r01_path.exists(): + rec_size = 200 + data = r01_path.read_bytes() + num_recs = len(data) // rec_size + for i in range(num_recs): + off = i * rec_size + eid = data[off:off+8].decode("ascii", errors="replace").strip() + if eid and eid != "00000000": + emp_ids_ordered.append(eid) + else: + for r in records: + eid = r.get("R01EMP-ID", "") or r.get("R01INNREC", {}).get("R01EMP-ID", "") + if not eid or eid == "00000000": + line = r.get("R01LINE", "") + if line: + eid = line.split(",")[0].strip() + if eid and eid != "00000000": + emp_ids_ordered.append(eid) + if eid not in emp_set: + emp_set.add(eid) + else: + emp_ids_ordered.append(eid) # Build SYSIN card records # Card format: position 1 = type, position 2 = space (ignored), position 3+ = data - lines = [ - f"P YEAR-MONTH=202607", # Period card - f"M MODE=NORMAL", # Mode card - ] - if emp_ids: - lines.append(f"T {','.join(emp_ids)}") # Target card + if run_cfg is None: + run_cfg = {} + lines = ["* GENERATED TEST DATA"] + sysin = run_cfg + period = sysin.get("period", "202607") + if period is not None: + lines.append(f"P YEAR-MONTH={period}") + if sysin.get("include_invalid_period", True): + lines.append("P YEAR-MONTH=000000") + for mode in (sysin.get("modes") or ["NORMAL"]): + lines.append(f"M MODE={mode}") + + # Detect duplicate EMP_IDs → need RESET mode + seen = set() + has_dups = False + for eid in emp_ids_ordered: + if eid in seen: + has_dups = True + break + seen.add(eid) + + if has_dups: + lines.append("M MODE=RESET") + chunks = [emp_ids_ordered[i:i+8] for i in range(0, len(emp_ids_ordered), 8)] + for chunk in chunks: + lines.append(f"T {','.join(chunk)}") + lines.append("M MODE=NORMAL") + + # Always add a RESET-mode batch with duplicate EMP-ID for UPDATE path + unique_ids = list(dict.fromkeys(emp_ids_ordered)) + if not unique_ids: + unique_ids = ["EMP00001", "EMP00002", "EMP00003", "EMP00004", "EMP00005"] + # Inject duplicates: use first EMP-ID twice to trigger RESET/UPDATE + if len(unique_ids) >= 1: + dup_id = unique_ids[0] + reset_ids = [dup_id, dup_id] + lines.append("M MODE=RESET") + lines.append(f"T {','.join(reset_ids)}") + lines.append("M MODE=NORMAL") + # Unique T card for NORMAL mode + if unique_ids: + chunks = [unique_ids[i:i+8] for i in range(0, len(unique_ids), 8)] + for chunk in chunks: + lines.append(f"T {','.join(chunk)}") + + # Unknown card type to cover IF WRK-CARD-TYPE '*' ELSE branch (DP#8 F) + lines.append("X UNKNOWN") + + # End with RESET mode so 3000STPSOR runs the RESET path (DP#22-#23) + lines.append("M MODE=RESET") # Write as fixed-length flat file outpath = outdir / (prefix + sysin_filename) diff --git a/cobol_testgen/gcov.py b/cobol_testgen/gcov.py index 01abdff..664466f 100644 --- a/cobol_testgen/gcov.py +++ b/cobol_testgen/gcov.py @@ -157,6 +157,9 @@ def mark_from_gcov(decision_points: list, gcov_data: dict[int, int], dp.active_branches.add('T') if else_cov: dp.active_branches.add('F') + # P0: ELSE-less IF — F is structurally mandatory when IF line executed + if not else_lines and then_lines and count > 0: + dp.active_branches.add('F') # 如果体行范围为空或无法判断,回退到基于 IF 行计数 if not then_lines and not else_lines: if count > 0: diff --git a/cobol_testgen/read.py b/cobol_testgen/read.py index 20bf67b..9ffadfe 100644 --- a/cobol_testgen/read.py +++ b/cobol_testgen/read.py @@ -653,12 +653,12 @@ def parse_file_section(source: str) -> dict: def scan_open_statements(source: str) -> dict: - """?? OPEN ????? {?????: 'INPUT'|'OUTPUT'|'I-O'}""" + """Parse OPEN statements, returns {file_name: 'INPUT'|'OUTPUT'|'I-O'}. + Handles multi-line OPEN (e.g. OPEN INPUT X\\n OUTPUT Y.).""" dirs = {} for m in re.finditer( - r'OPEN\s+((?:INPUT|OUTPUT|I-O)\s+[\w\s-]+' - r'(?:\s+(?:INPUT|OUTPUT|I-O)\s+[\w\s-]+)*)', - source, re.IGNORECASE + r'OPEN\s+((?:INPUT|OUTPUT|I-O)\s+[\w\s-]+?)\.', + source, re.IGNORECASE | re.DOTALL ): full = m.group(1) full = re.sub(r'\s+', ' ', full) diff --git a/cobol_testgen/runner.py b/cobol_testgen/runner.py index 39a89c4..15fadc1 100644 --- a/cobol_testgen/runner.py +++ b/cobol_testgen/runner.py @@ -351,9 +351,16 @@ def run_all(program_name: str, outdir: str, temp_dir: str, expected_records: list[dict] | None = None, source_dir: str | None = None, path_infos: list | None = None, - multi_write_fds: set | None = None - ) -> list[GroupResult]: - """完整编排:编译 → 准备目录 → é€ç»„执行 → 出力ä¿å­˜ã€‚""" + multi_write_fds: set | None = None, + skip_records: list[dict] | None = None, + skip_term_types: list[str] | None = None + ) -> tuple[list[GroupResult], dict[int, int] | None]: + """完整编排:编译 → 准备目录 → é€ç»„执行 → 出力ä¿å­˜ã€‚ + + Returns: + (results_list, merged_gcov_data) + merged_gcov_data is None when no gcov runs. + """ source_dir = source_dir or str(Path(outdir).parent) work_dir = Path(temp_dir) work_dir.mkdir(parents=True, exist_ok=True) @@ -362,6 +369,11 @@ def run_all(program_name: str, outdir: str, temp_dir: str, multi_write_fds = multi_write_fds or set() fd_field_dicts = _build_fd_field_dicts(fd_fields, fields_dict) + assign_names = _input_assign_names(select_info, open_dir, fd_fields) + + def _is_output_fd(fd_name: str) -> bool: + dir_val = open_dir.get(fd_name, '') + return dir_val in ('OUTPUT', 'I-O') # ── 1. SUB 编译(V3)── sub_dir = _resolve_sub_dir(source_dir) @@ -373,63 +385,99 @@ def run_all(program_name: str, outdir: str, temp_dir: str, program_name, source_dir, str(work_dir), sub_o, cpy_dir ) - # ── 3. 入力ファイルé…置(V3)── - input_dir = Path(outdir) / 'input' - assign_names = _input_assign_names(select_info, open_dir, fd_fields) - if input_dir.is_dir(): - for assign in assign_names: - src = input_dir / assign - if src.exists(): - shutil.copy2(str(src), str(work_dir / assign)) - logger.info(f" INPUT: {assign} ({src.stat().st_size} bytes)") + # ── 3. 场景定义 ── + scenes = [("main", records, term_types, expected, + Path(outdir) / 'input', Path(outdir) / 'output')] + if skip_records: + skip_expected = [{}] * len(skip_records) + skip_term = skip_term_types or ['normal'] * len(skip_records) + scenes.append(("skip", skip_records, skip_term, skip_expected, + Path(outdir) / 'input_skip', Path(outdir) / 'run_skip' / 'output')) - # ── 4. æ¸…ç†æ—§ gcda + 执行 ── - _clean_gcda(str(work_dir)) + results = [] + gcov_data_sets = [] + gcov_root = work_dir / "gcov" - def _is_output_fd(fd_name: str) -> bool: - dir_val = open_dir.get(fd_name, '') - return dir_val in ('OUTPUT', 'I-O') + for scene_id, scene_recs, scene_terms, scene_expected, src_in_dir, dst_out_dir in scenes: + # ── 3a. 入力ファイルé…ç½® ── + if src_in_dir.is_dir(): + for assign in assign_names: + src = src_in_dir / assign + if src.exists(): + shutil.copy2(str(src), str(work_dir / assign)) - # output_input_files åªå†™å…¥éž abend è®°å½•ï¼ŒåŒæ­¥è¿‡æ»¤ expected - filtered_expected = [] - for i, rec in enumerate(expected): - term = term_types[i] if i < len(term_types) else 'normal' - if term != 'abend': - filtered_expected.append(rec) - expected = filtered_expected + # ── 3b. æ¸…ç†æ—§ gcda ── + _clean_gcda(str(work_dir)) - group = GroupInfo( - name=program_name, - records=records, - expected_outputs=expected, - expected_returncode=0, - fd_field_dicts=fd_field_dicts, - open_dir=open_dir, - select_info=select_info, - multi_write_fds=multi_write_fds, - ) + # ── 3c. 过滤 non-abend ── + filtered_exp = [] + for i, rec in enumerate(scene_expected): + term = scene_terms[i] if i < len(scene_terms) else 'normal' + if term != 'abend': + filtered_exp.append(rec) - r = run_group(group, exe_path, str(work_dir)) - results = [r] + group = GroupInfo( + name=f"{program_name}_{scene_id}", + records=scene_recs, + expected_outputs=filtered_exp, + expected_returncode=0, + fd_field_dicts=fd_field_dicts, + open_dir=open_dir, + select_info=select_info, + multi_write_fds=multi_write_fds, + ) - # 出力拷è´åˆ° output_dir - output_dir = Path(outdir) / 'output' - output_dir.mkdir(parents=True, exist_ok=True) - for fd_name in fd_field_dicts: - if not _is_output_fd(fd_name): - continue - sel = select_info.get(fd_name, {}) - assign = sel.get('assign', fd_name) if isinstance(sel, dict) else fd_name - src = os.path.join(str(work_dir), assign) - dst = output_dir / assign - if os.path.exists(src): - shutil.copy2(src, str(dst)) + # ── 3d. 执行 ── + r = run_group(group, exe_path, str(work_dir)) + results.append(r) - status = '✓' if r.passed else '✗' - logger.info(f" 组 '{group.name}': returncode={r.returncode}, " - f"{len(r.details)} fields, {status}") - logger.info(f" EXIT={r.returncode}, 出力パス={work_dir}") - return results + status = '✓' if r.passed else '✗' + logger.info(f" 组 '{group.name}': returncode={r.returncode}, {status}") + + # ── 3e. 出力ä¿å­˜ ── + dst_out_dir.mkdir(parents=True, exist_ok=True) + for fd_name in fd_field_dicts: + if not _is_output_fd(fd_name): + continue + sel = select_info.get(fd_name, {}) + assign = sel.get('assign', fd_name) if isinstance(sel, dict) else fd_name + src = os.path.join(str(work_dir), assign) + if os.path.exists(src): + shutil.copy2(src, str(dst_out_dir / assign)) + + # ── 3f. .gcda 隔离(.gcno 是共享的,COPYï¼›.gcda 是æ¯åœºæ™¯ç‹¬ç«‹çš„,MOVE) + scene_gcov_dir = gcov_root / f"run_{scene_id}" + scene_gcov_dir.mkdir(parents=True, exist_ok=True) + for f in work_dir.glob("*.gcda"): + if f.is_file() and f.stat().st_size > 0: + dst = scene_gcov_dir / f.name + if dst.exists(): + dst.unlink() + shutil.move(str(f), str(dst)) + for f in work_dir.glob("*.gcno"): + if f.is_file() and f.stat().st_size > 0: + dst = scene_gcov_dir / f.name + if not dst.exists(): + shutil.copy2(str(f), str(dst)) + + # ── 3g. 收集该场景的 gcov æ•°æ® â”€â”€ + from .gcov import run_gcov as _run_gcov + scene_data = _run_gcov(program_name, str(scene_gcov_dir)) + if scene_data: + gcov_data_sets.append(scene_data) + + logger.info(f" {scene_id} 完了, output={dst_out_dir}") + + # ── 4. åˆå¹¶ gcov ── + merged_gcov = None + if gcov_data_sets: + merged_gcov = {} + for ds in gcov_data_sets: + for line, count in ds.items(): + merged_gcov[line] = max(merged_gcov.get(line, 0), count) + logger.info(f" Merged gcov from {len(gcov_data_sets)} runs ({len(merged_gcov)} lines)") + + return results, merged_gcov # ── run_and_compare(被 --run 调用,SOURCE 兼容)── diff --git a/config/__init__.py b/config/__init__.py index 21fd025..1d0af63 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -14,8 +14,8 @@ class Config: project_name: str = "" copybook_paths: list = field(default_factory=lambda: ["./copybooks"]) dialect: str = "ibm" - llm_model: str = "gpt-4o-mini" - llm_timeout: int = 15 + llm_model: str = "deepseek-v4-flash" + llm_timeout: int = 120 llm_cache_dir: str = ".cache/llm" coverage_default: str = "boundary" rounding_mode: str = "TRUNCATE" diff --git a/config/program_schema.py b/config/program_schema.py index f1b956e..030940b 100644 --- a/config/program_schema.py +++ b/config/program_schema.py @@ -24,6 +24,24 @@ class TableDef: sql_name: Optional[str] = None # COBOL SQL table name if different from YAML name +@dataclass +class SysinDef: + """SYSIN card configuration for a single run scenario. + Each program defines its own sysin fields; period/modes are KIN08DBU specific. + """ + period: str | None = "202607" + include_invalid_period: bool = False + modes: list[str] = field(default_factory=lambda: ["NORMAL"]) + + +@dataclass +class ScenarioDef: + """A single run scenario: one set of inputs for one COBOL execution.""" + id: str + sysin: SysinDef = field(default_factory=SysinDef) + inject_duplicate_pk: bool = False + + @dataclass class ProgramSchema: program_id: str @@ -31,6 +49,7 @@ class ProgramSchema: subprograms: list[str] = field(default_factory=list) db_type: str = "SQLite" db_name: str = "OVERTIME.DB" + runs: list[ScenarioDef] = field(default_factory=list) @classmethod def from_yaml(cls, path: str | Path) -> ProgramSchema: @@ -44,12 +63,22 @@ class ProgramSchema: name=t["name"], columns=cols, sql_name=t.get("sql_name"), )) + runs_raw = raw.get("runs", []) + runs = [] + for r in runs_raw: + sysin_raw = r.get("sysin", {}) + runs.append(ScenarioDef( + id=r["id"], + sysin=SysinDef(**sysin_raw), + inject_duplicate_pk=r.get("inject_duplicate_pk", False), + )) return cls( program_id=raw["program_id"], db_tables=tables, subprograms=raw.get("subprograms", []), db_type=raw.get("db_type", "SQLite"), db_name=raw.get("db_name", "OVERTIME.DB"), + runs=runs, ) diff --git a/config/programs/KIN08DBU.yaml b/config/programs/KIN08DBU.yaml index e78b7be..9929298 100644 --- a/config/programs/KIN08DBU.yaml +++ b/config/programs/KIN08DBU.yaml @@ -52,3 +52,28 @@ db_tables: subprograms: - SUB02MSG - SUB03END + +runs: + - id: normal + # 標準シナリオ:P 塿œ‰åй + RESET → 大部分ã®åˆ†å²ã‚’ã‚«ãƒãƒ¼ + sysin: + period: "202607" + include_invalid_period: true + modes: ["NORMAL", "RESET"] + inject_duplicate_pk: false + + - id: no_period + # #1 T(NOT WRK-PERIOD-OK)をカãƒãƒ¼ï¼šP å¡ãªã— + sysin: + period: null + include_invalid_period: false + modes: ["NORMAL"] + inject_duplicate_pk: false + + - id: insert_error + # #13 T / #28 T(INSERT SQL エラー)をカãƒãƒ¼ï¼šPK ç«¶åˆè¡Œæ³¨å…¥ + sysin: + period: "202607" + include_invalid_period: true + modes: ["NORMAL", "RESET"] + inject_duplicate_pk: true diff --git a/docs/output-directory-rules.md b/docs/output-directory-rules.md new file mode 100644 index 0000000..0769f82 --- /dev/null +++ b/docs/output-directory-rules.md @@ -0,0 +1,53 @@ +# 出力目录规则 + +## å•轮(runs: [] 或 YAML ä¸å­˜åœ¨ï¼‰ + +```text +runtime/{program_id}/ + input/KIN08R01, KIN08S01 ↠COBOL å…¥åŠ›å¹³é¢æ–‡ä»¶ + json/{program_id}.json ↠JSON(input/working_storage/termination) + output/KIN08W01 ↠COBOL å‡ºåŠ›å¹³é¢æ–‡ä»¶ + gcov/ ↠.gcda/.gcno(直下) +``` + +## 多轮(runs: 定义在 config/programs/{program_id}.yaml 中) + +```text +runtime/{program_id}/ + run_{scenario_id}/ + input/KIN08R01, KIN08S01 ↠該当场景ã®å…¥åŠ› + json/{program_id}.json ↠該当场景㮠JSON + output/KIN08W01 ↠該当场景㮠COBOL 出力 + gcov/ + run_{scenario_id}/ ↠該当场景㮠.gcda/.gcno +``` + +## å•è½®/多轮共有 + +- 编译: `work_dir/bin/{program_id}.exe`(全场景共享) +- .gcno: `work_dir/bin/{program_id}_pp.gcno`(编译时生æˆï¼ŒCOPY 到å„场景) +- DB: `C:/Temp/gix/{program_id}_{scenario_id}.db`(多轮场景)或 `C:/Temp/gix/{program_id}.db`(å•轮) + - è¿è¡Œå‰ COPY 到 `C:/Temp/gix/{program_id}.db`(COBOL çš„ gixsql 硬编ç è¿žæŽ¥æ­¤è·¯å¾„) +- SYSIN 文件: ç”± `write_sysin_file(run_cfg=scenario.sysin)` æŒ‰åœºæ™¯ç”Ÿæˆ + +## 覆盖率åˆå¹¶ï¼ˆå¤šè½®ï¼‰ + +- å„场景独立跑 gcov → è§£æž `.cbl.gcov` → `{line: count}` å­—å…¸ +- Python 级åˆå¹¶: `merged[line] = max(å„场景的 count)` +- åˆå¹¶åŽæ•°æ®ä¼ ç»™ `run_coverage()` + +## YAML åœºæ™¯å®šä¹‰æ ¼å¼ + +`config/programs/{program_id}.yaml` 追加 `runs:` 字段: + +```yaml +runs: + - id: normal + sysin: + period: "202607" # P å¡å€¤ï¼ˆnull=æ—  P å¡ï¼‰ + include_invalid_period: true # 是å¦è¿½åŠ æ— æ•ˆ P å¡ + modes: ["NORMAL", "RESET"] # M å¡åˆ—表 + inject_duplicate_pk: false +``` + +æ¯ä¸ªåœºæ™¯çš„ `sysin` 字段由该程åºçš„ `write_sysin_file()` è§£æžï¼Œå­—段å«ä¹‰ä¸è·¨ç¨‹åºé€šç”¨ã€‚ diff --git a/docs/superpowers/specs/2026-07-12-design-data-generator.md b/docs/superpowers/specs/2026-07-12-design-data-generator.md new file mode 100644 index 0000000..98d45b2 --- /dev/null +++ b/docs/superpowers/specs/2026-07-12-design-data-generator.md @@ -0,0 +1,332 @@ +# 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 | diff --git a/layout/DB定義書.md b/layout/DB定義書.md new file mode 100644 index 0000000..34a4ecd --- /dev/null +++ b/layout/DB定義書.md @@ -0,0 +1,347 @@ +# DB定義書 + +## 変更履歴 + +| No | 変更内容 | 担当者 | 変更日 | 承èªè€… | 備考 | +|----|---------|--------|--------|--------|------| +| 1 | æ–°è¦ä½œæˆï¼ˆå…¨8テーブル) | AI | 2026/06/23 | | サブシステムA(6) + B(2) | +| 2 | SALARYDB 4テーブル追加 | AI | 2026/07/08 | | サブシステムC(給与計算) | + +--- + +# EMP_MASTER — 社員マスタ + +## Layout(テーブル定義) + +### DB基本情報 + +| DBå | DB ID | COPY ID | INDEX | +|------|-------|---------|-------| +| 勤怠休暇管ç†DB | EMP_MASTER | — | PK: EMP_ID | + +### カラム定義 + +| No | é …ç›®å | é …ç›®å(英字å) | TYPE | 最大長 | å°æ•°æ¡ | ãƒã‚¤ãƒˆæ•° | NULL | DEFAULT | PK | INDEX1 | INDEX2 | INDEX3 | 備考 | +|----|--------|---------------|------|--------|--------|---------|------|---------|----|--------|--------|--------|------| +| 1 | ç¤¾å“¡ç•ªå· | EMP_ID | CHAR | 8 | | 8 | NOT NULL | | ✓ | | | | | +| 2 | 部署ID | DEPT_ID | CHAR | 4 | | 4 | NOT NULL | | | | | | | +| 3 | æ°å | EMP_NAME | VARCHAR | 50 | | 50 | NOT NULL | | | | | | | +| 4 | ステータス | STATUS | CHAR | 1 | | 1 | NOT NULL | | | | | | '1'=在ç±, '9'=é€€è· | + +### 備考 + +- サブシステムA: å‚ç…§ã®ã¿ï¼ˆSELECT) +- サブシステムB: 使用ã—ãªã„ + +--- + +# LEAVE_RECORDS — 休暇申請記録 + +## Layout(テーブル定義) + +### DB基本情報 + +| DBå | DB ID | COPY ID | INDEX | +|------|-------|---------|-------| +| 勤怠休暇管ç†DB | LEAVE_RECORDS | — | PK: APPLICATION_ID | + +### カラム定義 + +| No | é …ç›®å | é …ç›®å(英字å) | TYPE | 最大長 | å°æ•°æ¡ | ãƒã‚¤ãƒˆæ•° | NULL | DEFAULT | PK | INDEX1 | INDEX2 | INDEX3 | 備考 | +|----|--------|---------------|------|--------|--------|---------|------|---------|----|--------|--------|--------|------| +| 1 | ç”³è«‹ç•ªå· | APPLICATION_ID | INTEGER | 10 | | 4 | NOT NULL | | ✓ | | | | 自動採番 | +| 2 | ç¤¾å“¡ç•ªå· | EMP_ID | CHAR | 8 | | 8 | NOT NULL | | | | | | | +| 3 | 休暇種別 | LEAVE_TYPE | CHAR | 2 | | 2 | NOT NULL | | | | | | 01/02/03/04 | +| 4 | é–‹å§‹æ—¥ | START_DATE | CHAR | 8 | | 8 | NOT NULL | | | | | | YYYYMMDD | +| 5 | 開始時刻 | START_TIME | CHAR | 4 | | 4 | NOT NULL | | | | | | HHMM | +| 6 | 終了日 | END_DATE | CHAR | 8 | | 8 | NOT NULL | | | | | | YYYYMMDD | +| 7 | 終了時刻 | END_TIME | CHAR | 4 | | 4 | NOT NULL | | | | | | HHMM | +| 8 | ステータス | STATUS | CHAR | 1 | | 1 | NOT NULL | | | | | | '1'=有効, '9'=å–æ¶ˆ | + +### 備考 + +- KIN02UPDãŒINSERT/DELETEを実行 +- KIN03EXPãŒSELECT(日付展開用) +- å–æ¶ˆã¯DELETE FROM ã§ç‰©ç†å‰Šé™¤ + +--- + +# HOLIDAY_CALENDAR — 休日カレンダー + +## Layout(テーブル定義) + +### DB基本情報 + +| DBå | DB ID | COPY ID | INDEX | +|------|-------|---------|-------| +| 勤怠休暇管ç†DB | HOLIDAY_CALENDAR | — | PK: HOLIDAY_DATE | + +### カラム定義 + +| No | é …ç›®å | é …ç›®å(英字å) | TYPE | 最大長 | å°æ•°æ¡ | ãƒã‚¤ãƒˆæ•° | NULL | DEFAULT | PK | INDEX1 | INDEX2 | INDEX3 | 備考 | +|----|--------|---------------|------|--------|--------|---------|------|---------|----|--------|--------|--------|------| +| 1 | 休日日付 | HOLIDAY_DATE | CHAR | 8 | | 8 | NOT NULL | | ✓ | | | | YYYYMMDD | +| 2 | 説明 | DESCRIPTION | VARCHAR | 50 | | 50 | NULLè¨±å¯ | NULL | | | | | ä¾‹ï¼šã€Œå»ºå›½è¨˜å¿µã®æ—¥ã€ | + +### 備考 + +- åœŸæ—¥ã¯æ›œæ—¥åˆ¤å®šã§å‡¦ç†ã™ã‚‹ãŸã‚ã€ã“ã®ãƒ†ãƒ¼ãƒ–ルã«ã¯ç¥æ—¥ã®ã¿æ ¼ç´ +- KIN03EXP, KIN06CLDãŒSELECTã§å‚ç…§ + +--- + +# SICK_LEAVE_RATE — 病欠控除率 + +## Layout(テーブル定義) + +### DB基本情報 + +| DBå | DB ID | COPY ID | INDEX | +|------|-------|---------|-------| +| 勤怠休暇管ç†DB | SICK_LEAVE_RATE | — | PK: LEAVE_TYPE | + +### カラム定義 + +| No | é …ç›®å | é …ç›®å(英字å) | TYPE | 最大長 | å°æ•°æ¡ | ãƒã‚¤ãƒˆæ•° | NULL | DEFAULT | PK | INDEX1 | INDEX2 | INDEX3 | 備考 | +|----|--------|---------------|------|--------|--------|---------|------|---------|----|--------|--------|--------|------| +| 1 | 休暇種別 | LEAVE_TYPE | CHAR | 2 | | 2 | NOT NULL | | ✓ | | | | '04'固定 | +| 2 | 控除率 | DEDUCTION_RATE | DECIMAL | 3 | 2 | 3 | NOT NULL | | | | | | 例:0.50(50%控除) | + +### 備考 + +- 全社員共通ã®è¨­å®šå€¤ +- ç¾æ™‚点ã§ã¯å‚照プログラム未実装(設計上定義) + +--- + +# DAILY_RECORDS — 日別勤怠記録 + +## Layout(テーブル定義) + +### DB基本情報 + +| DBå | DB ID | COPY ID | INDEX | +|------|-------|---------|-------| +| 勤怠休暇管ç†DB | DAILY_RECORDS | — | PK: (EMP_ID, TARGET_DATE) | + +### カラム定義 + +| No | é …ç›®å | é …ç›®å(英字å) | TYPE | 最大長 | å°æ•°æ¡ | ãƒã‚¤ãƒˆæ•° | NULL | DEFAULT | PK | INDEX1 | INDEX2 | INDEX3 | 備考 | +|----|--------|---------------|------|--------|--------|---------|------|---------|----|--------|--------|--------|------| +| 1 | ç¤¾å“¡ç•ªå· | EMP_ID | CHAR | 8 | | 8 | NOT NULL | | ✓ | | | | | +| 2 | 対象日 | TARGET_DATE | CHAR | 8 | | 8 | NOT NULL | | ✓ | | | | YYYYMMDD | +| 3 | 出勤時刻 | TIME_IN | CHAR | 4 | | 4 | NOT NULL | | | | | | HHMM, '0000'=打刻ãªã— | +| 4 | 退勤時刻 | TIME_OUT | CHAR | 4 | | 4 | NOT NULL | | | | | | HHMM, '0000'=打刻ãªã— | +| 5 | 年休時間 | ANNUAL_LEAVE_H | DECIMAL | 6 | 1 | 5 | NOT NULL | | | | | | 0.1hå˜ä½ | +| 6 | äº‹å‡æ™‚é–“ | PERSONAL_LEAVE_H | DECIMAL | 6 | 1 | 5 | NOT NULL | | | | | | 0.1hå˜ä½ | +| 7 | å› å…¬ç‰¹æ‰¹å‡æ™‚é–“ | OFFICIAL_LEAVE_H | DECIMAL | 6 | 1 | 5 | NOT NULL | | | | | | 0.1hå˜ä½ | +| 8 | 病欠時間 | SICK_LEAVE_H | DECIMAL | 6 | 1 | 5 | NOT NULL | | | | | | 0.1hå˜ä½ | +| 9 | 未申請欠勤時間 | UNAPPROVED_ABSENT_H | DECIMAL | 6 | 1 | 5 | NOT NULL | | | | | | 0.1hå˜ä½ | +| 10 | 更新日時 | UPDATED_AT | TIMESTAMP | — | | 10 | NOT NULL | | | | | | DEFAULT CURRENT_TIMESTAMP | + +### 備考 + +- KIN08DBUãŒKIN07DAIã®å‡ºåŠ›ãƒ•ã‚¡ã‚¤ãƒ«ã‹ã‚‰INSERT +- 1社員1æ—¥ï¼1レコード。出勤日ã®ã¿å­˜åœ¨ã€‚ + +--- + +# MONTHLY_ABSENCE — 月次統計 + +## Layout(テーブル定義) + +### DB基本情報 + +| DBå | DB ID | COPY ID | INDEX | +|------|-------|---------|-------| +| 勤怠休暇管ç†DB | MONTHLY_ABSENCE | — | PK: (EMP_ID, YEAR_MONTH) | + +### カラム定義 + +| No | é …ç›®å | é …ç›®å(英字å) | TYPE | 最大長 | å°æ•°æ¡ | ãƒã‚¤ãƒˆæ•° | NULL | DEFAULT | PK | INDEX1 | INDEX2 | INDEX3 | 備考 | +|----|--------|---------------|------|--------|--------|---------|------|---------|----|--------|--------|--------|------| +| 1 | ç¤¾å“¡ç•ªå· | EMP_ID | CHAR | 8 | | 8 | NOT NULL | | ✓ | | | | | +| 2 | 対象年月 | YEAR_MONTH | CHAR | 6 | | 6 | NOT NULL | | ✓ | | | | YYYYMM | +| 3 | 年休åˆè¨ˆ | ANNUAL_LEAVE_H | DECIMAL | 6 | 1 | 5 | NOT NULL | | | | | | | +| 4 | 事å‡åˆè¨ˆ | PERSONAL_LEAVE_H | DECIMAL | 6 | 1 | 5 | NOT NULL | | | | | | | +| 5 | 因公特批å‡åˆè¨ˆ | OFFICIAL_LEAVE_H | DECIMAL | 6 | 1 | 5 | NOT NULL | | | | | | | +| 6 | 病欠åˆè¨ˆ | SICK_LEAVE_H | DECIMAL | 6 | 1 | 5 | NOT NULL | | | | | | | +| 7 | 未申請欠勤åˆè¨ˆ | UNAPPROVED_ABSENT_H | DECIMAL | 6 | 1 | 5 | NOT NULL | | | | | | | +| 8 | 更新日時 | UPDATED_AT | TIMESTAMP | — | | 10 | NOT NULL | | | | | | DEFAULT CURRENT_TIMESTAMP | + +### 備考 + +- KIN08DBUãŒæœˆæ¬¡é›†è¨ˆå¾Œã«UPSERT(MERGE) +- サブシステムC(給与計算)ã¸ã®é€£æºå…ƒ + +--- + +# OVT_APPLICATIONS — 個別加ç­ç”³è«‹ãƒ†ãƒ¼ãƒ–ル + +## Layout(テーブル定義) + +### DB基本情報 + +| DBå | DB ID | COPY ID | INDEX | +|------|-------|---------|-------| +| 残業統計管ç†DB | OVT_APPLICATIONS | — | PK: APPL_ID | + +### カラム定義 + +| No | é …ç›®å | é …ç›®å(英字å) | TYPE | 最大長 | å°æ•°æ¡ | ãƒã‚¤ãƒˆæ•° | NULL | DEFAULT | PK | INDEX1 | INDEX2 | INDEX3 | 備考 | +|----|--------|---------------|------|--------|--------|---------|------|---------|----|--------|--------|--------|------| +| 1 | ç”³è«‹ç•ªå· | APPL_ID | CHAR | 8 | | 8 | NOT NULL | | ✓ | | | | YYYYMMDD+SEQ | +| 2 | ç¤¾å“¡ç•ªå· | EMP_ID | CHAR | 8 | | 8 | NOT NULL | | | | | | | +| 3 | 申請日 | APPL_DATE | CHAR | 8 | | 8 | NOT NULL | | | | | | YYYYMMDD | +| 4 | 残業種別 | OVT_TYPE | CHAR | 1 | | 1 | NOT NULL | | | | | | W=平日, H=休日 | +| 5 | 開始時刻 | START_TIME | CHAR | 4 | | 4 | NOT NULL | | | | | | HHMM | +| 6 | 終了時刻 | END_TIME | CHAR | 4 | | 4 | NOT NULL | | | | | | HHMM | +| 7 | åŠ ç­æ™‚é–“ | OVT_HOURS | DECIMAL | 4 | 1 | 4 | NOT NULL | | | | | | 0.1hå˜ä½ | +| 8 | ステータス | STATUS | CHAR | 1 | | 1 | NOT NULL | | | | | | 0=有効, 9=å–æ¶ˆ | +| 9 | 更新日時 | UPDATED_AT | TIMESTAMP | — | | 10 | NOT NULL | | | | | | | + +### 備考 + +- ZAN06UPDãŒINSERT/UPDATEを実行 +- STATUS=9ã§å–消(物ç†å‰Šé™¤ã¯ã—ãªã„) + +--- + +# OVT_MONTHLY — 月次集計テーブル + +## Layout(テーブル定義) + +### DB基本情報 + +| DBå | DB ID | COPY ID | INDEX | +|------|-------|---------|-------| +| 残業統計管ç†DB | OVT_MONTHLY | — | PK: (EMP_ID, YEAR_MONTH, OVT_TYPE) | + +### カラム定義 + +| No | é …ç›®å | é …ç›®å(英字å) | TYPE | 最大長 | å°æ•°æ¡ | ãƒã‚¤ãƒˆæ•° | NULL | DEFAULT | PK | INDEX1 | INDEX2 | INDEX3 | 備考 | +|----|--------|---------------|------|--------|--------|---------|------|---------|----|--------|--------|--------|------| +| 1 | ç¤¾å“¡ç•ªå· | EMP_ID | CHAR | 8 | | 8 | NOT NULL | | ✓ | | | | | +| 2 | 対象年月 | YEAR_MONTH | CHAR | 6 | | 6 | NOT NULL | | ✓ | | | | YYYYMM | +| 3 | 残業種別 | OVT_TYPE | CHAR | 1 | | 1 | NOT NULL | | ✓ | | | | W=平日, H=休日 | +| 4 | åŠ ç­æ™‚é–“åˆè¨ˆ | OVT_HOURS | DECIMAL | 6 | 1 | 5 | NOT NULL | | | | | | | +| 5 | 加ç­å›žæ•° | OVT_COUNT | INTEGER | 10 | | 4 | NOT NULL | | | | | | | +| 6 | 更新日時 | UPDATED_AT | TIMESTAMP | — | | 10 | NOT NULL | | | | | | | + +### 備考 + +- ZAN06UPDãŒæœˆæ¬¡é›†è¨ˆçµæžœã‚’UPSERT + +--- + +# EMP-MASTER(SALARYDB) — 社員マスタ(給与計算用) + +## Layout(テーブル定義) + +### DB基本情報 + +| DBå | DB ID | COPY ID | INDEX | +|------|-------|---------|-------| +| 給与計算DB | EMP-MASTER | KYU01REC | PK: EMP-ID | + +### カラム定義 + +| No | é …ç›®å | é …ç›®å(英字å) | TYPE | 最大長 | å°æ•°æ¡ | ãƒã‚¤ãƒˆæ•° | NULL | DEFAULT | PK | INDEX1 | INDEX2 | INDEX3 | 備考 | +|----|--------|---------------|------|--------|--------|---------|------|---------|----|--------|--------|--------|------| +| 1 | ç¤¾å“¡ç•ªå· | EMP-ID | CHAR | 8 | | 8 | NOT NULL | | ✓ | | | | | +| 2 | æ°å | EMP-NAME | VARCHAR | 40 | | 40 | NOT NULL | | | | | | | +| 3 | 部署コード | DEPT-CODE | CHAR | 2 | | 2 | NOT NULL | | | | | | | +| 4 | 地域コード | REGION-CODE | CHAR | 2 | | 2 | NOT NULL | | | | | | | +| 5 | è·ç¨®ã‚³ãƒ¼ãƒ‰ | CATEGORY-CODE | CHAR | 3 | | 3 | NOT NULL | | | | | | | +| 6 | 基本給 | BASE-SALARY | DECIMAL | 9 | 0 | 5 | NOT NULL | | | | | | | +| 7 | 時給 | HOURLY-RATE | DECIMAL | 7 | 0 | 4 | NOT NULL | | | | | | | +| 8 | 扶養人数 | DEPENDENT-COUNT | SMALLINT | — | | 2 | NOT NULL | | | | | | | +| 9 | ステータス | STATUS | CHAR | 1 | | 1 | NOT NULL | | | | | | '0'=在ç±, '9'=é€€è· | +| 10 | 更新日時 | UPDATED-AT | TIMESTAMP | — | | 10 | NOT NULL | CURRENT TIMESTAMP | | | | | | + +### 備考 + +- KYU02REGãŒINSERT/UPSERTを実行 +- KYU04CALãŒSELECTã§å‚ç…§ + +--- + +# TAX-TABLE(SALARYDB) — 所得税率テーブル + +## Layout(テーブル定義) + +### DB基本情報 + +| DBå | DB ID | COPY ID | INDEX | +|------|-------|---------|-------| +| 給与計算DB | TAX-TABLE | — | PK: (TAX-FROM, TAX-TO) | + +### カラム定義 + +| No | é …ç›®å | é …ç›®å(英字å) | TYPE | 最大長 | å°æ•°æ¡ | ãƒã‚¤ãƒˆæ•° | NULL | DEFAULT | PK | INDEX1 | INDEX2 | INDEX3 | 備考 | +|----|--------|---------------|------|--------|--------|---------|------|---------|----|--------|--------|--------|------| +| 1 | èª²ç¨Žä¸‹é™ | TAX-FROM | DECIMAL | 9 | 0 | 5 | NOT NULL | | ✓ | | | | | +| 2 | èª²ç¨Žä¸Šé™ | TAX-TO | DECIMAL | 9 | 0 | 5 | NOT NULL | | ✓ | | | | | +| 3 | 税率 | TAX-RATE | DECIMAL | 5 | 4 | 3 | NOT NULL | | | | | | | +| 4 | æŽ§é™¤é¡ | DEDUCTION | DECIMAL | 9 | 0 | 5 | NOT NULL | | | | | | | + +### 備考 + +- KYU05DEDãŒSELECT ... BETWEENã§å‚ç…§ +- 課税所得ã«å¯¾ã™ã‚‹è¶…éŽç´¯é€²ç¨Žçއ + +--- + +# INSURANCE-TABLE(SALARYDB) — 社会ä¿é™ºæ–™çŽ‡ãƒ†ãƒ¼ãƒ–ãƒ« + +## Layout(テーブル定義) + +### DB基本情報 + +| DBå | DB ID | COPY ID | INDEX | +|------|-------|---------|-------| +| 給与計算DB | INSURANCE-TABLE | — | PK: (INS-INCOME-FROM, INS-INCOME-TO) | + +### カラム定義 + +| No | é …ç›®å | é …ç›®å(英字å) | TYPE | 最大長 | å°æ•°æ¡ | ãƒã‚¤ãƒˆæ•° | NULL | DEFAULT | PK | INDEX1 | INDEX2 | INDEX3 | 備考 | +|----|--------|---------------|------|--------|--------|---------|------|---------|----|--------|--------|--------|------| +| 1 | æ‰€å¾—ä¸‹é™ | INS-INCOME-FROM | DECIMAL | 9 | 0 | 5 | NOT NULL | | ✓ | | | | | +| 2 | æ‰€å¾—ä¸Šé™ | INS-INCOME-TO | DECIMAL | 9 | 0 | 5 | NOT NULL | | ✓ | | | | | +| 3 | å¥åº·ä¿é™ºæ–™çއ | INS-HEALTH-RATE | DECIMAL | 7 | 6 | 4 | NOT NULL | | | | | | | +| 4 | 厚生年金ä¿é™ºæ–™çއ | INS-PENSION-RATE | DECIMAL | 7 | 6 | 4 | NOT NULL | | | | | | | +| 5 | 更新日時 | UPDATED-AT | TIMESTAMP | — | | 10 | NOT NULL | CURRENT TIMESTAMP | | | | | | + +### 備考 + +- KYU05DEDãŒå†…部テーブル(SEARCH ALL)ã«å–込んã§å‚ç…§ +- 被ä¿é™ºè€…負担分ã®ã¿ + +--- + +# SALARY-RESULTS(SALARYDB) — çµ¦ä¸Žè¨ˆç®—çµæžœãƒ†ãƒ¼ãƒ–ル + +## Layout(テーブル定義) + +### DB基本情報 + +| DBå | DB ID | COPY ID | INDEX | +|------|-------|---------|-------| +| 給与計算DB | SALARY-RESULTS | — | PK: (EMP-ID, YEAR-MONTH) | + +### カラム定義 + +| No | é …ç›®å | é …ç›®å(英字å) | TYPE | 最大長 | å°æ•°æ¡ | ãƒã‚¤ãƒˆæ•° | NULL | DEFAULT | PK | INDEX1 | INDEX2 | INDEX3 | 備考 | +|----|--------|---------------|------|--------|--------|---------|------|---------|----|--------|--------|--------|------| +| 1 | ç¤¾å“¡ç•ªå· | EMP-ID | CHAR | 8 | | 8 | NOT NULL | | ✓ | | | | | +| 2 | 対象年月 | YEAR-MONTH | CHAR | 6 | | 6 | NOT NULL | | ✓ | | | | YYYYMM | +| 3 | ç·æ”¯çµ¦é¡ | GROSS-PAYMENT | DECIMAL | 9 | 0 | 5 | NOT NULL | | | | | | | +| 4 | æºæ³‰æ‰€å¾—税 | INCOME-TAX | DECIMAL | 9 | 0 | 5 | NOT NULL | | | | | | | +| 5 | 社会ä¿é™ºæ–™ | INSURANCE | DECIMAL | 9 | 0 | 5 | NOT NULL | | | | | | | +| 6 | 使°‘税 | RESIDENT-TAX | DECIMAL | 9 | 0 | 5 | NOT NULL | | | | | | | +| 7 | å·®å¼•æ”¯çµ¦é¡ | NET-PAYMENT | DECIMAL | 9 | 0 | 5 | NOT NULL | | | | | | | +| 8 | 更新日時 | UPDATED-AT | TIMESTAMP | — | | 10 | NOT NULL | CURRENT TIMESTAMP | | | | | | + +### 備考 + +- KYU06UPDãŒINSERT/UPSERTを実行 diff --git a/layout/JSONæ ¼å¼è¯´æ˜Žv2.0.md b/layout/JSONæ ¼å¼è¯´æ˜Žv2.0.md new file mode 100644 index 0000000..9063a7d --- /dev/null +++ b/layout/JSONæ ¼å¼è¯´æ˜Žv2.0.md @@ -0,0 +1,435 @@ +# cobol_testgen v4 ç”Ÿæˆ JSON — æ ¼å¼è¯´æ˜Ž v2.0 + +## 修订履历 + +| 版本 | 日期 | å˜æ›´å†…容 | +|------|------|---------| +| v2.0 | | 明确1个json文件=1组数æ®ï¼ˆgroup)的命åè§„åˆ™ï¼Œæ ¼å¼æœ¬èº«ä¸Žv1ç›¸åŒ | + +## 说明 + +v2.0 **æ ¼å¼æœ¬èº«ä¸Žv1完全一致**,仅补充: +1. 一组数æ®å•独放一个json文件 +2. 一个文件内的`records`数组包å«è¯¥ç»„的全部处ç†å•å…ƒ +3. 命å规则追加 `_g{groupId}` æ ‡è¯†ç»„å· + +--- + +## 完整规格 JSON(`output/json/{プログラムå}_g{groupId}.json`) + +### 最外层结构 + +```json +{ + "program": "SAN01MAT", + "records": [ + { + "input": { ... } + } + ] +} +``` + +**1æ¡record = 1次处ç†å•å…ƒ**。例如匹é…(1:1)时: +- åŒ¹é…æˆåŠŸï¼šrecordå†…åŒæ—¶åŒ…å«ä¸¤ä¸ªè¾“å…¥FD +- R01-only:record内åªå«R01çš„FD +- R02-only:record内åªå«R02çš„FD + +多个record按处ç†é¡ºåºæŽ’列,组æˆä¸€ç»„完整的测试数æ®ã€‚ + +--- + +## 例1ï¼šå•æ–‡ä»¶CSV输入(1组1æ¡è®°å½•) + +文件 `ZAN01CHK_g1.json`: + +```json +{ + "program": "ZAN01CHK", + "records": [ + { + "input": { + "R01INNFIL": { + "R01LINE": "A0000000, , , , ,0, , " + } + } + } + ] +} +``` + +## 例2ï¼šå•æ–‡ä»¶äºŒè¿›åˆ¶è¾“入(1组多æ¡è®°å½•) + +文件 `ZAN02CHK_g1.json`: + +```json +{ + "program": "ZAN02CHK", + "records": [ + { + "input": { + "R01INNFIL": { + "R01APPL-ID": "A0000001", + "R01EMP-ID": "00000101", + "R01APPL-DATE": "20000101", + "R01START-TIME":"0201", + "R01END-TIME": "0301", + "R01STATUS": "B", + "R01OVT-TYPE": "C", + "R01FILLER": "D000000000000000000000000000000000000000000001" + } + } + }, + { + "input": { + "R01INNFIL": { + "R01APPL-ID": "A0000002", + "R01EMP-ID": "00000102", + "R01APPL-DATE": "20000102", + "R01START-TIME":"0202", + "R01END-TIME": "0302", + "R01STATUS": "C", + "R01OVT-TYPE": "D", + "R01FILLER": "D000000000000000000000000000000000000000000002" + } + } + } + ] +} +``` + +## 例3:多文件输入(与v1相åŒï¼‰ + +文件 `ZAN03CHK_g1.json`: + +```json +{ + "program": "ZAN03CHK", + "records": [ + { + "input": { + "R01INNFIL": { + "R01APPL-ID": "A0000001", + "R01EMP-ID": "00000101", + "R01APPL-DATE": "20000101", + "R01START-TIME":"0201", + "R01END-TIME": "0301", + "R01STATUS": "B", + "R01OVT-TYPE": "C", + "R01FILLER": "D000000000000000000000000000000000000000000001" + }, + "R02INNFIL": { + "R02EMP-ID": "00000401", + "R02DATE": "20000101", + "R02TIME-IN": "0501", + "R02TIME-OUT": "0601", + "R02FILLER": "E0000000000000000000000000000000000000000000000000000001" + }, + "R03INNFIL": { + "R03HOLIDAY-DATE": "20000101", + "R03HOLIDAY-FLG": "F", + "R03FILLER": "G0000000000000000000000000000000000000000000000000000000000000000000001" + } + } + } + ] +} +``` + +## 例4:二文件匹é…(マッãƒãƒ³ã‚°1:1) + +v1中ZAN04MAT示例是1æ¡record = 1次匹é…处ç†ã€‚ + +对于匹é…(1:1)测试,**1ç»„æ•°æ® = 1个json文件**。1组内包å«å¤šæ¡record(R01-onlyã€R02-onlyã€åŒ¹é…),按处ç†é¡ºåºæŽ’列。 + +### 文件1:`SAN01MAT_g1.json` + +两端ä¸åŒ¹é…:先处ç†R01-only(a001) → 3次匹é…(a002/b002, a003/b003, a004/b004) → 最åŽR02-only(a005)。 + +```json +{ + "program": "SAN01MAT", + "records": [ + { + "input": { + "R01INNFIL": { + "R01A001": "a001", + "R01A002": "b001", + "R01A003": "c001", + "R01DATA": "c00000000011" + } + } + }, + { + "input": { + "R01INNFIL": { + "R01A001": "a002", + "R01A002": "b002", + "R01A003": "c002", + "R01DATA": "c00000000012" + }, + "R02INNFIL": { + "R02A001": "a002", + "R02A002": "b002", + "R02A003": "c002", + "R02DATA": "c00000000012" + } + } + }, + { + "input": { + "R01INNFIL": { + "R01A001": "a003", + "R01A002": "b003", + "R01A003": "c003", + "R01DATA": "c00000000013" + }, + "R02INNFIL": { + "R02A001": "a003", + "R02A002": "b003", + "R02A003": "c003", + "R02DATA": "c00000000013" + } + } + }, + { + "input": { + "R01INNFIL": { + "R01A001": "a004", + "R01A002": "b004", + "R01A003": "c004", + "R01DATA": "c00000000014" + }, + "R02INNFIL": { + "R02A001": "a004", + "R02A002": "b004", + "R02A003": "c004", + "R02DATA": "c00000000014" + } + } + }, + { + "input": { + "R02INNFIL": { + "R02A001": "a005", + "R02A002": "b005", + "R02A003": "c005", + "R02DATA": "c00000000015" + } + } + } + ] +} +``` + +### 文件2:`SAN01MAT_g2.json` + +åå‘两端ä¸åŒ¹é…:先处ç†R02-only(a001) → 3æ¬¡åŒ¹é… â†’ 最åŽR01-only(a005)。 + +```json +{ + "program": "SAN01MAT", + "records": [ + { + "input": { + "R02INNFIL": { + "R02A001": "a001", + "R02A002": "b001", + "R02A003": "c001", + "R02DATA": "c00000000011" + } + } + }, + { + "input": { + "R01INNFIL": { + "R01A001": "a002", + "R01A002": "b002", + "R01A003": "c002", + "R01DATA": "c00000000012" + }, + "R02INNFIL": { + "R02A001": "a002", + "R02A002": "b002", + "R02A003": "c002", + "R02DATA": "c00000000012" + } + } + }, + { + "input": { + "R01INNFIL": { + "R01A001": "a003", + "R01A002": "b003", + "R01A003": "c003", + "R01DATA": "c00000000013" + }, + "R02INNFIL": { + "R02A001": "a003", + "R02A002": "b003", + "R02A003": "c003", + "R02DATA": "c00000000013" + } + } + }, + { + "input": { + "R01INNFIL": { + "R01A001": "a004", + "R01A002": "b004", + "R01A003": "c004", + "R01DATA": "c00000000014" + }, + "R02INNFIL": { + "R02A001": "a004", + "R02A002": "b004", + "R02A003": "c004", + "R02DATA": "c00000000014" + } + } + }, + { + "input": { + "R01INNFIL": { + "R01A001": "a005", + "R01A002": "b005", + "R01A003": "c005", + "R01DATA": "c00000000015" + } + } + } + ] +} +``` + +### 文件3:`SAN01MAT_g3.json` + +中间ä¸åŒ¹é…:先匹é…(a001) → R01-only(a003) → 匹é…(a004) → R02-only(a002) → 匹é…(a005)。 + +```json +{ + "program": "SAN01MAT", + "records": [ + { + "input": { + "R01INNFIL": { + "R01A001": "a001", + "R01A002": "b001", + "R01A003": "c001", + "R01DATA": "c00000000011" + }, + "R02INNFIL": { + "R02A001": "a001", + "R02A002": "b001", + "R02A003": "c001", + "R02DATA": "c00000000011" + } + } + }, + { + "input": { + "R01INNFIL": { + "R01A001": "a003", + "R01A002": "b003", + "R01A003": "c003", + "R01DATA": "c00000000013" + } + } + }, + { + "input": { + "R01INNFIL": { + "R01A001": "a004", + "R01A002": "b004", + "R01A003": "c004", + "R01DATA": "c00000000014" + }, + "R02INNFIL": { + "R02A001": "a004", + "R02A002": "b004", + "R02A003": "c004", + "R02DATA": "c00000000014" + } + } + }, + { + "input": { + "R02INNFIL": { + "R02A001": "a002", + "R02A002": "b002", + "R02A003": "c002", + "R02DATA": "c00000000012" + } + } + }, + { + "input": { + "R01INNFIL": { + "R01A001": "a005", + "R01A002": "b005", + "R01A003": "c005", + "R01DATA": "c00000000015" + }, + "R02INNFIL": { + "R02A001": "a005", + "R02A002": "b005", + "R02A003": "c005", + "R02DATA": "c00000000015" + } + } + } + ] +} +``` + +--- + +## DB类型时的输出 + +输入类型为DB时,æ¯ç»„生æˆä¸€ä¸ªç‹¬ç«‹çš„SQL insert文件。 + +| 文件ç§ç±» | 命å规则 | 例 | +|---------|--------|-----| +| Insert SQL(一组) | `{プログラムID}_g{groupId}.sql` | `SAN01MAT_g1.sql` | + +例(组1): + +```sql +-- Group: 1 +-- 用途: å‘˜å·¥ä¸»è¡¨åŸºç¡€æ•°æ® +INSERT INTO EMPLOYEE (EMP_ID, EMP_NAME, BIRTH_DATE, SALARY) VALUES +('0000000001', '测试太郎', '1990-04-01', 350000.00), +('0000000002', '测试花å­', '1995-07-15', 420000.00); +``` + +--- + +## 字段值规则 + +| PIC | JSON内表示 | 例 | +|-----|-----------|-----| +| `PIC X(n)` | å·¦å¯¹é½ + 空格填充 | `"A0000001"` (8æ¡) | +| `PIC 9(n)` | å³å¯¹é½ + å‰è¡¥é›¶ | `"00000101"` (8æ¡) | +| `PIC S9(n)` | ç¬¦å· + å³å¯¹é½ + å‰è¡¥é›¶ | `"+0000101"` | +| `PIC S9(n)V9(m)` | ç¬¦å· + å³å¯¹é½ + å«å°æ•°ç‚¹ | `"+001234567"` (V9(2)时实际为 +0012345.67) | +| `PIC S9(n) COMP` | å进制数字字符串(å«ç¬¦å·ï¼‰ | `"300"`ã€`"-100000"` | +| `PIC S9(n) COMP-3` | å进制数字字符串(å«ç¬¦å·ï¼‰ | `"1234"`ã€`"-5678"` | +| `PIC 9(n) COMP-3` | å进制数字字符串(无符å·ï¼‰ | `"1234"` | +| FILLER(纯ä¿ç•™ï¼‰ | 字段å为`FILLER`的未使用区域 | 嫿œ‰ç»„ID和记录编å·çš„å¯è¾¨è¯†æ¨¡å¼ `"D000000...001"` | +| FILLER(业务ä¿ç•™ï¼‰ | å­—æ®µåæœ‰å…·ä½“å称但实际未使用的ä¿ç•™åŒº | PIC X(n)填全空格ã€PIC 9(n)填全零 | + +--- + +## 输出目录结构 + +``` +output/ +├── json/ # 完整JSON(æ¯ç»„一个文件,输入类型=文件时输出) +│ ├── SAN01MAT_g1.json +│ ├── SAN01MAT_g2.json +│ └── SAN01MAT_g3.json +└── db/ # DB insert SQL(æ¯ç»„一个文件,输入类型=DB时输出) + ├── SAN01MAT_g1.sql + ├── SAN01MAT_g2.sql + └── SAN01MAT_g3.sql +``` diff --git a/layout/sample_ソース_SAN01MAT.cbl b/layout/sample_ソース_SAN01MAT.cbl new file mode 100644 index 0000000..5bc28a5 --- /dev/null +++ b/layout/sample_ソース_SAN01MAT.cbl @@ -0,0 +1,432 @@ + IDENTIFICATION DIVISION. + PROGRAM-ID. SAN01MAT. + ***************************************************************** + * システムå : サンプルシステム * + * プログラムID : SAN01MAT * + * プログラムå : ï¼¸ï¼¸ï¼¸ï¼¸ï¼¸ï¼¸å‡¦ç† * + * ä½œæˆæ—¥ : YYYY-MM-DD * + * å‡¦ç†æ¦‚è¦ : マッãƒãƒ³ã‚°ï¼ˆ1:1)を行ã†ã€‚ * + * マッãƒã®å ´åˆã€ãƒ•ァイルW01を出力ã™ã‚‹ã€‚ * + * ファイルR01ã®ã¿ã®å ´åˆãƒ•ァイルW02を出力ã™ã‚‹ã€‚* + * ファイルR02ã®ã¿ã®å ´åˆãƒ•ァイルW03を出力ã™ã‚‹ã€‚* + * * + ***************************************************************** + * 更新履歴 * + *---------------------------------------------------------------* + * 更新日付 担当者 更新内容 * + *---------------------------------------------------------------* + * YY-MM-DD @@@ æ–°è¦ä½œæˆ * + * * + ***************************************************************** + ENVIRONMENT DIVISION. + CONFIGURATION SECTION. + SOURCE-COMPUTER. IBM-ZSERIES. + OBJECT-COMPUTER. IBM-ZSERIES. + * + INPUT-OUTPUT SECTION. + FILE-CONTROL. + SELECT R01INNFIL ASSIGN TO SAN01R01. + SELECT R02INNFIL ASSIGN TO SAN01R02. + SELECT W010UTFIL ASSIGN TO SAN01W01. + SELECT W020UTFIL ASSIGN TO SAN01W02. + SELECT W030UTFIL ASSIGN TO SAN01W03. + * + DATA DIVISION. + FILE SECTION. + * + ***************************************************************** + * ##RO1## * + ***************************************************************** + FD R01INNFIL + LABEL RECORD IS STANDARD + BLOCK CONTAINS 0 + RECORDING MODE IS F. + 01 RO1INNREC. + COPY BBBBBFC REPLACING ==(A)== BY ==R01==. + * + ***************************************************************** + * ##RO2## * + ***************************************************************** + FD R02INNFIL + LABEL RECORD IS STANDARD + BLOCK CONTAINS 0 + RECORDING MODE IS F. + 01 RO2INNREC. + COPY BBBBBFC REPLACING ==(A)== BY ==R02==. + * + ***************************************************************** + * ##WO1## * + ***************************************************************** + FD W010UTFIL + LABEL RECORD IS STANDARD + BLOCK CONTAINS 0 + RECORDING MODE IS F. + 01 WO10UTREC. + COPY BBBBBFC REPLACING ==(A)== BY ==WO1==. + * + ***************************************************************** + * ##WO2## * + ***************************************************************** + FD W020UTFIL + LABEL RECORD IS STANDARD + BLOCK CONTAINS 0 + RECORDING MODE IS F. + 01 WO20UTREC. + COPY BBBBBFC REPLACING ==(A)== BY ==WO2==. + * + ***************************************************************** + * ##W03## * + ***************************************************************** + FD W030UTFIL + LABEL RECORD IS STANDARD + BLOCK CONTAINS 0 + RECORDING MODE IS F. + 01 WO30UTREC. + COPY BBBBBFC REPLACING ==(A)== BY ==WO3==. + * + WORKING-STORAGE SECTION. + * + ***************************************************************** + * SYSIN領域 * + ***************************************************************** + *01 SYSARA. + * + ***************************************************************** + * コンスタント領域 * + ***************************************************************** + 01 CNSARA. + 03 CNS-PRGIDX PIC X(008) VALUE 'SAN01MAT'. + 03 CNS-UNYSUTKES-1 PIC X(001) VALUE '1'. + 03 CNS-CFKFCA PIC X(001) VALUE 'N'. + 03 CNS-MSGSTR PIC 9(003) VALUE 001. + 03 CNS-MSGFIN PIC 9(003) VALUE 002. + 03 CNS-MSGSUBEEK PIC 9(003) VALUE 005. + 03 CNS-MSGIINKES PIC 9(003) VALUE 006. + 03 CNS-MSGOUTKES PIC 9(003) VALUE 007. + 03 CNS-MSGKEYINF PIC 9(003) VALUE 033. + 03 CNS-KN0002 PIC 9(001) VALUE 2. + 03 CNS-ABD999 PIC 9(003) VALUE 999. + * + ***************************************************************** + * フラグ領域 * + ***************************************************************** + *01 FLGARA. + * + ***************************************************************** + * カウンタ領域 * + ***************************************************************** + 01 CUNARA. + 03 CUN-RO1INN PIC S9(009) COMP-3 VALUE ZERO. + 03 CUN-R02INN PIC S9(009) COMP-3 VALUE ZERO. + 03 CUN-WO1OUT PIC S9(009) COMP-3 VALUE ZERO. + 03 CUN-WO2OUT PIC S9(009) COMP-3 VALUE ZERO. + 03 CUN-WO3OUT PIC S9(009) COMP-3 VALUE ZERO. + * + ***************************************************************** + * 作業領域 * + ***************************************************************** + 01 WRKARA. + *** é‹ç”¨æ—¥ä»˜ + 03 WRK-U06 PIC 9(008). + 03 WRK-U06ARA REDEFINES WRK-U06. + 05 WRK-UYOYMX PIC 9(006). + 05 WRK-UYOHII PIC 9(002). + *** マッãƒãƒ³ã‚°ã‚­ãƒ¼ + 03 WRK-R01KEY. + 05 WRK-R01KEY001 PIC X(010). + 05 WRK-R01KEY002 PIC X(010). + 05 WRK-R01KEY003 PIC X(010). + 03 WRK-R02KEY. + 05 WRK-R02KEY001 PIC X(010). + 05 WRK-R02KEY002 PIC X(010). + 05 WRK-R02KEY003 PIC X(010). + * + ***************************************************************** + * サブプログラム連絡領域 * + ***************************************************************** + *** é‹ç”¨æ—¥ä»˜å–å¾— + COPY D01010AC. + *** 入力件数表示ï¼å…¥åŠ›ãƒ¬ã‚³ãƒ¼ãƒ‰ã‚·ãƒ¼ã‚±ãƒ³ã‚¹ãƒã‚§ãƒƒã‚¯SR用 + COPY S01MSGAC. + *** メッセージ編集出力SR用 + COPY MSG000AC. + * + PROCEDURE DIVISION. + ***************************************************************** + * サブモジュールNO: (0.0) * + * サブモジュールå: åˆ¶å¾¡å‡¦ç† * + * å‡¦ç†æ¦‚è¦ : ãƒ¡ã‚¤ãƒ³ã‚³ãƒ³ãƒˆãƒ­ãƒ¼ãƒ«å‡¦ç† * + ***************************************************************** + 0000MAJCOLSOR SECTION. + * + *** åˆæœŸå‡¦ç† + PERFORM 1000ITTSOR. + * + *** ãƒ¡ã‚¤ãƒ³å‡¦ç† + PERFORM 2000MAJSOR + UNTIL WRK-R01KEY = HIGH-VALUE + AND WRK-R02KEY = HIGH-VALUE. + * + *** çµ‚äº†å‡¦ç† + PERFORM 3000STPSOR. + * + 0000MAJCOLSOREXT. + GOBACK. + ***************************************************************** + * サブモジュールNO: (1.0) * + * サブモジュールå: åˆæœŸå‡¦ç† * + * å‡¦ç†æ¦‚è¦ : 開始メッセージ出力・å„ç¨®åˆæœŸåŒ–å‡¦ç† * + ***************************************************************** + 1000ITTSOR SECTION. + * + *** 開始メッセージ出力 + INITIALIZE M00MHOPAR. + MOVE CNS-MSGSTR TO M00MSGCOD. + PERFORM 4000MSGOUTSOR. + * + *** コンãƒã‚¤ãƒ«æ—¥æ™‚出力 + INITIALIZE M00MHOPAR. + MOVE CNS-MSGKEYINF TO M00MSGCOD. + MOVE FUNCTION WHEN-COMPILED TO M00UMKDATS22(1). + MOVE 'COMPILED' TO M00UMKDATS22(2). + PERFORM 4000MSGOUTSOR. + * + *** ãƒ¯ãƒ¼ã‚¯ã‚¨ãƒªã‚¢åˆæœŸåŒ– + INITIALIZE WRKARA. + * + *** é‹ç”¨æ—¥ä»˜å–å¾— + INITIALIZE D01UBSPAR. + CALL 'DAT01S0' USING D01UBSPAR. + IF D01FKICOD = ZERO + MOVE D01U06 TO WRK-U06 + ELSE + INITIALIZE M00MHOPAR + MOVE CNS-MSGSUBEEK TO M00MSGCOD + MOVE 'DAT01SO' TO M00UMKDATS22(1) + MOVE D01FKICOD TO M00UMKDATS22(2) + PERFORM 4000MSGOUTSOR + PERFORM 9999ABDSOR + END-IF. + * + *** 入出力ファイルOPEN + OPEN INPUT R01INNFIL + R02INNFIL + OUTPUT W010UTFIL + W020UTFIL + W030UTFIL. + * + *** #R01#を読ã¿è¾¼ã¿ + PERFORM 1100R01INNSOR. + *** #R02#を読ã¿è¾¼ã¿ + PERFORM 1200R02INNSOR. + * + 1000ITTSOREXT. + EXIT. + ***************************************************************** + * サブモジュールNO:(1.1) * + * サブモジュールå:##RO1#ï¼ƒèª­è¾¼å‡¦ç† * + * å‡¦ç†æ¦‚è¦ :レコード読込・キー設定・SEQãƒã‚§ãƒƒã‚¯å‡¦ç† * + ***************************************************************** + 1100R01INNSOR SECTION. + * + READ R01INNFIL + AT END + MOVE HIGH-VALUE TO WRK-R01KEY + NOT AT END + ADD 1 TO CUN-RO1INN + MOVE R01A001 TO WRK-R01KEY001 + MOVE R01A002 TO WRK-R01KEY002 + MOVE R01A003 TO WRK-R01KEY003 + *** 入力件数表示ï¼ã‚·ãƒ¼ã‚±ãƒ³ã‚¹ãƒã‚§ãƒƒã‚¯å‡¦ç† + MOVE 'SAN01R01' TO S01DDM. + MOVE CNS-UNYSUTKES-1 TO SO1HYOKESKJCKUB + MOVE CNS-CFKFCA TO S01JKDKIYFLG + MOVE WRK-R01KEY TO SO1SEQCECKOM + COPY CHK01SRP REPLACING ==(A)==BY ==S01==. + END-READ. + * + 1100R01INNSOREXT. + EXIT. + ***************************************************************** + * サブモジュールNO:(1.2) * + * サブモジュールå:##RO2#ï¼ƒèª­è¾¼å‡¦ç† * + * å‡¦ç†æ¦‚è¦ :レコード読込・キー設定・SEQãƒã‚§ãƒƒã‚¯å‡¦ç† * + ***************************************************************** + 1200R02INNSOR SECTION. + * + READ R02INNFIL + AT END + MOVE HIGH-VALUE TO WRK-R02KEY + NOT AT END + ADD 1 TO CUN-R02INN + MOVE R02A001 TO WRK-R02KEY001 + MOVE R02A002 TO WRK-R02KEY002 + MOVE R02A003 TO WRK-R02KEY003 + *** 入力件数表示ï¼ã‚·ãƒ¼ã‚±ãƒ³ã‚¹ãƒã‚§ãƒƒã‚¯å‡¦ç† + MOVE 'SAN01R02' TO S01DDM. + MOVE CNS-UNYSUTKES-1 TO SO1HYOKESKJCKUB + MOVE CNS-CFKFCA TO S01JKDKIYFLG + MOVE WRK-R02KEY TO SO1SEQCECKOM + COPY CHK01SRP REPLACING ==(A)== BY ==S01==. + END-READ. + * + 1200R02INNSOREXT. + EXIT. + ***************************************************************** + * サブプログラムNO:(2.0) * + * サブプログラムå:ä¸»å‡¦ç† * + * å‡¦ç†æ¦‚è¦ :マッãƒãƒ³ã‚°ï¼ˆ1:1)を行ㆠ* + ***************************************************************** + 2000MAJSOR SECTION. + * + EVALUATE TRUE + *** マッム+ WHEN WRK-R01KEY = WRK-R02KEY + *** #WO1#ã‚’ç·¨é›†å‡ºåŠ›å‡¦ç† + PERFORM 2100W010UTSOR + * + *** #RO1#を読ã¿è¾¼ã¿ + PERFORM 1100R01INNSOR + *** #RO2#を読ã¿è¾¼ã¿ + PERFORM 1200R02INNSOR + * + *** アンマッãƒ(##R01##ã®ã¿) + WHEN WRK-R01KEY < WRK-R02KEY + *** #WO2#ã‚’ç·¨é›†å‡ºåŠ›å‡¦ç† + PERFORM 2200W020UTSOR + * + *** #RO1#を読ã¿è¾¼ã¿ + PERFORM 1100R01INNSOR + * + *** アンマッãƒ(##R02##ã®ã¿) + WHEN WRK-R01KEY > WRK-R02KEY + *** #WO3#ã‚’ç·¨é›†å‡ºåŠ›å‡¦ç† + PERFORM 2300W030UTSOR + * + *** #R02#を読ã¿è¾¼ã¿ + PERFORM 1200R02INNSOR + END-EVALUATE. + * + 2000MAJSOREXT. + EXIT. + ***************************************************************** + * サブモジュールNO:(2.1) * + * サブモジュールå:##WO1#ï¼ƒç·¨é›†å‡ºåŠ›å‡¦ç† * + * å‡¦ç†æ¦‚è¦ :ãƒ¬ã‚³ãƒ¼ãƒ‰ç·¨é›†ãƒ»å‡ºåŠ›å‡¦ç† * + ***************************************************************** + 2100W010UTSOR SECTION. + * + MOVE RO1INNREC TO W010UTREC. + MOVE R02INNREC TO W010UTREC. + * + WRITE WO1OUTREC. + ADD 1 TO CUN-WO1OUT. + + 2100W010UTSOREXT. + EXIT. + ***************************************************************** + * サブプログラムNO:(2.2) * + * サブプログラムå:##WO2#ã‚’ç·¨é›†å‡ºåŠ›å‡¦ç† * + * å‡¦ç†æ¦‚è¦ :ãƒ¬ã‚³ãƒ¼ãƒ‰ç·¨é›†ãƒ»å‡ºåŠ›å‡¦ç† * + ***************************************************************** + 2200W020UTSOR SECTION. + * + MOVE RO1INNREC TO W020UTREC. + * + WRITE WO2OUTREC. + ADD 1 TO CUN-WO2OUT. + * + 2200W020UTSOREXT. + EXIT. + ***************************************************************** + * サブプログラムNO:(2.3) * + * サブプログラムå:##WO3#ã‚’ç·¨é›†å‡ºåŠ›å‡¦ç† * + * å‡¦ç†æ¦‚è¦ :ãƒ¬ã‚³ãƒ¼ãƒ‰ç·¨é›†ãƒ»å‡ºåŠ›å‡¦ç† * + ***************************************************************** + 2300W030UTSOR SECTION. + * + MOVE R02INNREC TO W03OUTREC. + * + WRITE WO3OUTREC. + ADD 1 TO CUN-WO3OUT. + * + 2300W030UTSOREXT. + EXIT. + ***************************************************************** + * サブモジュールNO:(3.0) * + * サブモジュールå:çµ‚äº†å‡¦ç† * + * å‡¦ç†æ¦‚è¦ :ファイルクローズ・件数ã¨çµ‚了メッセージ出力 * + ***************************************************************** + 3000STPSOR SECTION. + * + *** 入出力ファイルCLOSE + CLOSE R01INNFIL + R02INNFIL + W010UTFIL + W020UTFIL + W030UTFIL. + * + *** 入出力ファイル件数出力 + INITIALIZE M00MHOPAR. + MOVE CNS-MSGIINKES TO M00MSGCOD. + MOVE 'SAN01R01' TO M00UMKDATS22(1). + MOVE CUN-RO1INN TO M00UMKDATS22(2). + PERFORM 4000MSGOUTSOR. + * + INITIALIZE M00MHOPAR. + MOVE CNS-MSGIINKES TO M00MSGCOD. + MOVE 'SAN01R02' TO M00UMKDATS22(1). + MOVE CUN-R02INN TO M00UMKDATS22(2). + PERFORM 4000MSGOUTSOR. + * + INITIALIZE M00MHOPAR. + MOVE CNS-MSGIINKES TO M00MSGCOD. + MOVE 'SAN01W01' TO M00UMKDATS22(1). + MOVE CUN-WO1OUT TO M00UMKDATS22(2). + PERFORM 4000MSGOUTSOR. + * + INITIALIZE M00MHOPAR. + MOVE CNS-MSGIINKES TO M00MSGCOD. + MOVE 'SAN01W02' TO M00UMKDATS22(1). + MOVE CUN-WO2OUT TO M00UMKDATS22(2). + PERFORM 4000MSGOUTSOR. + * + INITIALIZE M00MHOPAR. + MOVE CNS-MSGIINKES TO M00MSGCOD. + MOVE 'SAN01W03' TO M00UMKDATS22(1). + MOVE CUN-WO3OUT TO M00UMKDATS22(2). + PERFORM 4000MSGOUTSOR. + * + *** 終了メッセージ出力 + INITIALIZE M00MHOPAR. + MOVE CNS-MSGFIN TO M00MSGCOD. + PERFORM 4000MSGOUTSOR. + * + 3000STPSOREXT. + EXIT. + ***************************************************************** + * サブプログラムNO:(4.0) * + * サブプログラムå:ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ç·¨é›†å‡ºåŠ›å‡¦ç† * + * å‡¦ç†æ¦‚è¦ :メッセージ編集出力サブPGM呼出 * + ***************************************************************** + 4000MSGOUTSOR SECTION. + * + MOVE CNS-KN0002 TO M00KNOCOD + MOVE CNS-KN0002 TO M00SYUCOD. + MOVE CNS-PRGIDX TO M00PRGIDX. + CALL 'MSG0SRP' USING M00MSGPAR. + * + 4000MSGOUTSOREXT. + EXIT. + ***************************************************************** + * サブプログラムNO:(9.9) * + * サブプログラムå:ABENDå‡¦ç† * + * å‡¦ç†æ¦‚è¦ :ABENDサブPGM呼出 * + ***************************************************************** + 9999ABDSOR SECTION. + * + MOVE CNS-ABD999 TO M00ABDCOD. + CALL 'END01TO' USING M00MSGPAR. + * + 9999ABDSOREXT. + EXIT. diff --git a/layout/sample_詳細設計書_SAN01MAT.md b/layout/sample_詳細設計書_SAN01MAT.md new file mode 100644 index 0000000..5549762 --- /dev/null +++ b/layout/sample_詳細設計書_SAN01MAT.md @@ -0,0 +1,141 @@ +# 詳細設計書 + +## 基本情報 + +| # | é …ç›® | 内容 | +|---|------|------| +| 1 | システムå | サンプルシステム | +| 2 | プログラムID | SAN01MAT | +| 3 | プログラムå | ï¼¸ï¼¸ï¼¸ï¼¸ï¼¸ï¼¸å‡¦ç† | +| 4 | PGMパターン | マッãƒãƒ³ã‚°(1:1) | +| 5 | æ©Ÿèƒ½æ¦‚è¦ | マッãƒãƒ³ã‚°ï¼ˆ1:1)を行ã†ã€‚ | +| 6 | | マッãƒã®å ´åˆã€ãƒ•ァイルW01を出力ã™ã‚‹ã€‚ | +| 7 | | ファイルR01ã®ã¿ã®å ´åˆãƒ•ァイルW02を出力ã™ã‚‹ã€‚ | +| 8 | | ファイルR02ã®ã¿ã®å ´åˆãƒ•ァイルW03を出力ã™ã‚‹ã€‚ | + +※PGMパターン:マッãƒãƒ³ã‚°(1:1ã€1:Nã€M:N)ã€ãƒ¬ã‚¤ã‚¢ã‚¦ãƒˆç·¨é›†ã®ã¿(GETPUT)ã€é …ç›®ãƒã‚§ãƒƒã‚¯ã€æŒ¯ã‚Šåˆ†ã‘ã€ã‚­ãƒ¼ãƒ–レイクã€ã‚­ãƒ¼ãƒ–レイク(集計ã€é›†ç´„)ã€DBæ›´æ–° + +### 剿æ¡ä»¶ + +| NO | 対象ファイル | æ¡ä»¶ | +|----|-------------|------| +| 1 | ファイルR01 | キー項目A001>A002>A003ã§æ˜‡é †ã§ã‚½ãƒ¼ãƒˆ | +| 2 | ファイルR02 | キー項目A001>A002>A003ã§æ˜‡é †ã§ã‚½ãƒ¼ãƒˆ | + +### 使用ファイル一覧 + +| NO | 使用ファイル/DBå | è­˜åˆ¥å­ | DDå | I/O | COPY群 | å½¢å¼ | ブロック | レコード長 | 媒体 | 備考 | +|----|------------------|--------|------|-----|--------|------|---------|-----------|------|------| +| 1 | ファイルR01 | R01 | SAN01R01 | I | BBBBBFC | FB | | 120 | PS | | +| 2 | ファイルR02 | R02 | SAN01R02 | I | BBBBBFC | FB | | 120 | PS | | +| 3 | ファイルW01 | W01 | SAN01W01 | O | BBBBBFC | FB | | 120 | PS | | +| 4 | ファイルW02 | W02 | SAN01W02 | O | BBBBBFC | FB | | 120 | PS | | +| 5 | ファイルW03 | W03 | SAN01W03 | O | BBBBBFC | FB | | 120 | PS | | + +### キー項目一覧 + +| NO | ファイルå | ソートæ¡ä»¶(キー項目) | キーæ¡ä»¶(マッãƒãƒ³ã‚°/キーブレイク) | +|----|-----------|---------------------|-------------------------------------------| +| 1 | ファイルR01 | A001>A002>A003(é‡è¤‡NG) | A001>A002>A003 | +| 2 | ファイルR02 | A001>A002>A003(é‡è¤‡NG) | A001>A002>A003 | + +### 使用モジュール一覧 + +| NO | 機能 | プログラムID | 使用COPYå | +|----|------|-------------|-----------| +| 1 | 入力件数表示ï¼ã‚·ãƒ¼ã‚±ãƒ³ã‚¹ãƒã‚§ãƒƒã‚¯å‡¦ç†SUB | CHK01SRP | S01MSGAC | +| 2 | é‹ç”¨æ—¥å–å¾—SUB | DAT01S0 | D01010AC | +| 3 | メッセージ編集出力処ç†SUB | MSG0SRP | MSG000AC | +| 4 | ABEND処ç†SUB | END01TO | MSG000AC | + +--- + +## 処ç†è©³ç´° + +``` +1.åˆæœŸå‡¦ç†ï¼ˆ1000ITTSOR) + 1-1.開始メッセージ出力 + ã€ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ç·¨é›†ã€‘ + メッセージ番å·ï¼š1(開始メッセージ) + 1-2.コンパイル日時出力 + ã€ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ç·¨é›†ã€‘ + メッセージ番å·ï¼š33(コンパイル日時) + PARM1:コンパイル日時 + PARM2:'COMPILED' + 1-3.ワークエリアã®åˆæœŸåŒ– + 1-4.é‹ç”¨æ—¥å–å¾—SUB(DAT01S0)ã«ã‚ˆã‚Šé‹ç”¨æ—¥ã‚’å–å¾—ã™ã‚‹ã€‚ + 復帰コード≠ZEROã®å ´åˆã€ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‚’出力ã—ã€ABEND処ç†SUBを呼ã³å‡ºã—異常終了ã™ã‚‹ã€‚ + ã€ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ç·¨é›†ã€‘ + メッセージ番å·ï¼š5(サブエラー) + PARM1:'DAT01SO' + PARM2:復帰コード + ã€ABEND処ç†SUB】 + ABENDコード:999 + 1-5.使用ファイルã®ã‚ªãƒ¼ãƒ–ン + 1-6.R01を読ã¿è¾¼ã‚€ã€‚(1100R01INNSOR)(1回目) + 1-7.R02を読ã¿è¾¼ã‚€ã€‚(1200R02INNSOR)(1回目) + +2.主処ç†ï¼ˆ2000MAJSOR)(R01ã€R02ã‚’å…¨ã¦èª­ã¿çµ‚ãˆã‚‹ã¾ã§ä¸‹è¨˜ã‚’繰り返ã™ï¼‰ + 2-1.マッãƒã®å ´åˆ + 2-1-1.R01ã‚’W01ã«ãã®ã¾ã¾å‡ºåŠ›ã™ã‚‹ã€‚(2100W010UTSOR) + *出力編集(W01)をå‚ç…§ + 2-1-2.R01を読ã¿è¾¼ã‚€ã€‚(1100R01INNSOR)(2件目以é™ï¼‰ + 2-1-3.R02を読ã¿è¾¼ã‚€ã€‚(1200R02INNSOR)(2件目以é™ï¼‰ + 2-2.R01ã®ã¿ã®å ´åˆ + 2-2-1.R01ã‚’W02ã«ãã®ã¾ã¾å‡ºåŠ›ã™ã‚‹ã€‚(2200W020UTSOR) + *出力編集(W02)をå‚ç…§ + 2-2-2.R01を読ã¿è¾¼ã‚€ã€‚(1100R01INNSOR)(2件目以é™ï¼‰ + 2-3.R02ã®ã¿ã®å ´åˆ + 2-3-1.R02ã‚’W03ã«ãã®ã¾ã¾å‡ºåŠ›ã™ã‚‹ã€‚(2300W030UTSOR) + *出力編集(W03)をå‚ç…§ + 2-3-2.R02を読ã¿è¾¼ã‚€ã€‚(1200R02INNSOR)(2件目以é™ï¼‰ + +3.終了処ç†ï¼ˆ3000STPSOR) + 3-1.入出力ファイルã®ã‚¯ãƒ­ãƒ¼ã‚º + 3-2.入出力件数出力メッセージ出力 + ã€å…¥åŠ›ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ç·¨é›†ã€‘ + メッセージ番å·ï¼š6(入力件数メッセージ) + PARM1:当該入力ファイルã®DDå + PARM2:当該入力ファイルã®ä»¶æ•° + ã€å‡ºåŠ›ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ç·¨é›†ã€‘ + メッセージ番å·ï¼š7(出力件数メッセージ) + PARM1:当該出力ファイルã®DDå + PARM2:当該出力ファイルã®ä»¶æ•° + 3-3.終了メッセージ出力 + ã€ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ç·¨é›†ã€‘ + メッセージ番å·ï¼š2(終了メッセージ) +``` + +--- + +## 出力レコード定義 + +### 出力ファイル1(W01) + +| No | é …ç›®å | 設定元 | 備考 | +|----|--------|--------|------| +| 1 | A001 | R01.åŒé …目を設定 | | +| 2 | A002 | R01.åŒé …目を設定 | | +| 3 | A003 | R01.åŒé …目を設定 | | +| 4 | … | | | +| 5 | A020 | R01.åŒé …目を設定 | | + +### 出力ファイル2(W02) + +| No | é …ç›®å | 設定元 | 備考 | +|----|--------|--------|------| +| 1 | A001 | R01.åŒé …目を設定 | | +| 2 | A002 | R01.åŒé …目を設定 | | +| 3 | A003 | R01.åŒé …目を設定 | | +| 4 | … | | | +| 5 | A020 | R01.åŒé …目を設定 | | + +### 出力ファイル3(W03) + +| No | é …ç›®å | 設定元 | 備考 | +|----|--------|--------|------| +| 1 | A001 | R02.åŒé …目を設定 | | +| 2 | A002 | R02.åŒé …目を設定 | | +| 3 | A003 | R02.åŒé …目を設定 | | +| 4 | … | | | +| 5 | A020 | R02.åŒé …目を設定 | | diff --git a/layout/ファイル定義書_xxxxxxx.md b/layout/ファイル定義書_xxxxxxx.md new file mode 100644 index 0000000..25de2e3 --- /dev/null +++ b/layout/ファイル定義書_xxxxxxx.md @@ -0,0 +1,142 @@ +# ファイル定義書 + +## 変更履歴 + +| No | 変更内容 | 担当者 | 変更日 | 承èªè€… | 備考 | +|----|---------|--------|--------|--------|------| +| 1 | æ–°è¦ä½œæˆ | AI | YYYY/MM/DD | | | + +## Layout(レコードレイアウト) + +### ファイル基本情報 + +| ファイルå | COPY ID | 媒体 | ãƒ¬ã‚³ãƒ¼ãƒ‰å½¢å¼ | レコード長 | +|-----------|---------|------|-------------|-----------| +| | | PS | FB | | + +### レコード定義 + +| No | レベル | é …ç›®å | é …ç›®å(英字å) | 属性 | ãƒã‚¤ãƒˆæ•° | OCCURS | é–‹å§‹ä½ç½® | REDEFINES | 所属グループ | 備考 | +|----|--------|--------|---------------|------|---------|--------|---------|-----------|-------------|------| +| 1 | | | | | | | | | | | +| 2 | | | | | | | | | | | +| 3 | | | | | | | | | | | +| 4 | | | | | | | | | | | + +--- + +## 記載説明 + +| é …ç›® | 説明 | +|------|------| +| 媒体 | ファイル(PS)ã®å ´åˆã¯"PS"ã¨è¨˜éŒ² | +| ãƒ¬ã‚³ãƒ¼ãƒ‰å½¢å¼ | 固定長(FB)ã¾ãŸã¯å¯å¤‰é•·(VB)ã¨è¨˜éŒ² | +| レコード長 | レコード長ã¯å…¨é …ç›®ã®ãƒã‚¤ãƒˆæ•°ã‚’åˆè¨ˆã—ã¦è¨˜éŒ²ã€‚FDã®RECORD SIZEã¨ä¸€è‡´ã•ã›ã‚‹ã“ã¨ã€‚REDEFINESé …ç›®ã¯åˆè¨ˆã«å«ã¾ãªã„。 | +| レベル | レベル番å·ã®å®šç¾©ï¼š01, 03, 05, 07, 09... ã¨é£›ã³é£›ã³ã®ãƒ¬ãƒ™ãƒ«ç•ªå·ã¨ã™ã‚‹ã€‚åŒã˜ãƒ¬ãƒ™ãƒ«ç•ªå·ã¯å…„弟関係ã¨ãªã‚‹ã€‚ | +| 属性(PIC TYPE) | 属性(TYPE)ã¨ãƒã‚¤ãƒˆæ•°ã‚’記入ã™ã‚‹ã‚‚ã®ã¨ã™ã‚‹ã€‚原則PIC記法ã§è¨˜éŒ²ã€‚COBOLè¨˜è¿°ã«æ²¿ã£ãŸã‚·ãƒ¼ãƒˆã®è¨˜è¼‰å†…å®¹ã«æº–拠。 | + +## COBOLデータ型一覧 + +| No | COBOL記述 | 略記法 | ãƒã‚¤ãƒˆæ•° | 説明 | 日本語説明 | 例 | ãƒã‚¤ãƒˆæ•°(例) | +|----|-----------|--------|---------|------|-----------|-----|-------------| +| 1 | PIC X(n) | X(n) | n | 英数字 | 固定長英数字 | X(10) | 10 | +| 2 | PIC G(n) | G(n) | n×2 | DBCS文字 | ダブルãƒã‚¤ãƒˆæ–‡å­—(日本語) | G(20) | 40 | +| 3 | PIC 9(n) | 9(n) | n | 数字・10進(符å·ç„¡) | 符å·ç„¡ã—æ•´æ•° | 9(7) | 7 | +| 4 | PIC S9(n) | S9(n) | n | 数字・10進(ç¬¦å·æœ‰) | 符å·ä»˜ãæ•´æ•° | S9(7) | 7 | +| 5 | PIC S9(n)V9(m) | S9(n)V9(m) | n+m | 数字・10進(ç¬¦å·æœ‰ãƒ»å°æ•°) | æš—é»™å°æ•°ç‚¹ä»˜ã(DISPLAY) | S9(7)V9(2) | 9 | +| 6 | PIC S9(n) COMP-3 | S9(n) COMP-3 | INT((n+2)/2) | パック10進(ç¬¦å·æœ‰) | 内部10進数 | S9(7) COMP-3 | 4 | +| 7 | PIC 9(n) COMP-3 | 9(n) COMP-3 | INT((n+1)/2) | パック10進(符å·ç„¡) | 内部10進数(符å·ç„¡) | 9(7) COMP-3 | 4 | +| 8 | PIC S9(n)V9(m) COMP-3 | S9(n)V9(m) COMP-3 | INT((n+m+2)/2) | パック10進(ç¬¦å·æœ‰ãƒ»å°æ•°) | æš—é»™å°æ•°ç‚¹ä»˜ãCOMP-3 | S9(7)V9(2) COMP-3 | 5 | +| 9 | PIC S9(4) COMP | S9(4) COMP | 2 | 2進(2ãƒã‚¤ãƒˆ) | åŠèªžé•·ãƒã‚¤ãƒŠãƒª | S9(4) COMP | 2 | +| 10 | PIC S9(9) COMP | S9(9) COMP | 4 | 2進(4ãƒã‚¤ãƒˆ) | 全語長ãƒã‚¤ãƒŠãƒª | S9(9) COMP | 4 | +| 11 | PIC S9(18) COMP | S9(18) COMP | 8 | 2進(8ãƒã‚¤ãƒˆ) | 2語長ãƒã‚¤ãƒŠãƒª | S9(18) COMP | 8 | +| 12 | PIC S9(n)V9(m) COMP | S9(n)V9(m) COMP | n+m≦4→2, ≦9→4, ≦18→8 | 2進(ç¬¦å·æœ‰ãƒ»å°æ•°) | æš—é»™å°æ•°ç‚¹ä»˜ãCOMP | S9(9)V9(2) COMP | 8 | +| 13 | PIC S9(n) BINARY | S9(n) BINARY | n≦4→2, n≦9→4, n≦18→8 | 2進(ãƒã‚¤ãƒŠãƒª) | COMPã¨åŒä¸€ | S9(15) BINARY | 8 | +| 14 | ZZZ9 | ZZZ9 | Zã®æ•°+9ã®æ•° | 数字編集パターン | ゼロ抑制編集 | ZZZZ9 | 5 | + +## COBOL記述例 + +### 例1: 基本レイアウト + +COBOL定義: +``` +01 TEST-RECORD. + 03 FIELD-1 PIC X(10). + 03 FIELD-2 PIC S9(7) COMP-3. + 03 FIELD-3 PIC 9(4). + 03 FIELD-4 PIC S9(9) COMP. + 03 FILLER PIC X(5). +``` + +FD記述: + +| No | レベル | é …ç›®å | é …ç›®å(英字å) | 属性(PIC TYPE) | ãƒã‚¤ãƒˆæ•° | OCCURS | é–‹å§‹ä½ç½® | REDEFINES | 所属グループ | 備考 | +|----|--------|--------|---------------|----------------|---------|--------|---------|-----------|-------------|------| +| 1 | 01 | テストレコード | TEST-RECORD | | | | 1 | | | | +| 2 | 03 | フィールド1 | FIELD-1 | X | 10 | | 1 | | TEST-RECORD | | +| 3 | 03 | フィールド2 | FIELD-2 | S9(7) COMP-3 | 5 | | 11 | | TEST-RECORD | | +| 4 | 03 | フィールド3 | FIELD-3 | 9(4) | 4 | | 16 | | TEST-RECORD | | +| 5 | 03 | 予約 | FILLER | X(5) | 5 | | 20 | | TEST-RECORD | | + +### 例2: REDEFINESを使用ã™ã‚‹å ´åˆ + +COBOL定義: +``` +01 REDEF-REC. + 03 BASE-FIELD PIC X(10). + 03 REDEF-FIELD REDEFINES BASE-FIELD. + 05 INFO-1 PIC X(5). + 05 INFO-2 PIC X(5). +``` + +FD記述: + +| No | レベル | é …ç›®å | é …ç›®å(英字å) | 属性(PIC TYPE) | ãƒã‚¤ãƒˆæ•° | OCCURS | é–‹å§‹ä½ç½® | REDEFINES | 所属グループ | 備考 | +|----|--------|--------|---------------|----------------|---------|--------|---------|-----------|-------------|------| +| 1 | 01 | REDEFINESレコード | REDEF-REC | | | | 1 | | | | +| 2 | 03 | ベースフィールド | BASE-FIELD | X(10) | 10 | | 1 | | REDEF-REC | | +| 3 | 03 | å†å®šç¾©ãƒ•ィールド | REDEF-FIELD | | | | 11 | BASE-FIELD | REDEF-REC | | +| 4 | 05 | 情報1 | INFO-1 | 9(5) | 5 | | 11 | | REDEF-REC | | +| 5 | 05 | 情報2 | INFO-2 | 9(5) | 5 | | 16 | | REDEF-REC | | + +### 例3: OCCURSを使用ã™ã‚‹å ´åˆ + +COBOL定義: +``` +01 OCCURS-REC. + 03 OCCURS-ITEM OCCURS 3. + 05 SUB-1 PIC X(5). + 03 AFTER-ITEM PIC 9(3). +``` + +FD記述: + +| No | レベル | é …ç›®å | é …ç›®å(英字å) | 属性(PIC TYPE) | ãƒã‚¤ãƒˆæ•° | OCCURS | é–‹å§‹ä½ç½® | REDEFINES | 所属グループ | 備考 | +|----|--------|--------|---------------|----------------|---------|--------|---------|-----------|-------------|------| +| 1 | 01 | OCCURSレコード | OCCURS-REC | | | | 1 | | | | +| 2 | 03 | OCCURSé …ç›® | OCCURS-ITEM | | | 3 | 1 | | OCCURS-REC | | +| 3 | 05 | サブ項目1 | SUB-1 | X(5) | 5 | | 1 | | OCCURS-REC | | +| 4 | 03 | OCCURS後 | AFTER-ITEM | 9(3) | 3 | | 16 | | OCCURS-REC | | + +### 例4: OCCURSã®å¤šé‡å…¥ã‚Œå­ + +COBOL定義: +``` +01 OCCURS-MULTI-REC. + 03 GRP-ITEM OCCURS 3. + 05 SUB1 PIC X(5). + 05 SUB2 OCCURS 4. + 07 SUB2-DATA PIC 9(3). + 03 AFTER-ITEM PIC 9(3). +``` + +FD記述: + +| No | レベル | é …ç›®å | é …ç›®å(英字å) | 属性(PIC TYPE) | ãƒã‚¤ãƒˆæ•° | OCCURS | é–‹å§‹ä½ç½® | REDEFINES | 所属グループ | 備考 | +|----|--------|--------|---------------|----------------|---------|--------|---------|-----------|-------------|------| +| 1 | 01 | OCCURS多é‡å…¥ã‚Œå­ãƒ¬ã‚³ãƒ¼ãƒ‰ | OCCURS-MULTI-REC | | | | 1 | | | | +| 2 | 03 | グループ項目 | GRP-ITEM | | | 3 | 1 | | OCCURS-MULTI-REC | | +| 3 | 05 | サブ項目1 | SUB-1 | X(5) | 5 | | 1 | | OCCURS-MULTI-REC | | +| 4 | 05 | サブ項目2 | SUB-2 | | | 4 | 6 | | OCCURS-MULTI-REC | | +| 5 | 07 | サブ項目2データ | SUB2-DATA | 9(3) | 3 | | 6 | | OCCURS-MULTI-REC | | +| 6 | 03 | OCCURS後 | AFTER-ITEM | 9(3) | 3 | | 52 | | OCCURS-MULTI-REC | | diff --git a/layout/詳細設計書_XXXXXXXX.md b/layout/詳細設計書_XXXXXXXX.md new file mode 100644 index 0000000..a36a817 --- /dev/null +++ b/layout/詳細設計書_XXXXXXXX.md @@ -0,0 +1,145 @@ +# 詳細設計書 + +## 基本情報 + +| # | é …ç›® | 内容 | +|---|------|------| +| 1 | システムå | SSSSシステム | +| 2 | プログラムID | SSSnnFFF | +| 3 | プログラムå | ï¼¸ï¼¸ï¼¸ï¼¸ï¼¸ï¼¸å‡¦ç† | +| 4 | PGMパターン | | +| 5 | æ©Ÿèƒ½æ¦‚è¦ | | +| 6 | | | +| 7 | | | +| 8 | | | + +※PGMパターン:マッãƒãƒ³ã‚°(1:1ã€1:Nã€M:N)ã€ãƒ¬ã‚¤ã‚¢ã‚¦ãƒˆç·¨é›†ã®ã¿(GETPUT)ã€é …ç›®ãƒã‚§ãƒƒã‚¯ã€æŒ¯ã‚Šåˆ†ã‘ã€ã‚­ãƒ¼ãƒ–レイクã€ã‚­ãƒ¼ãƒ–レイク(集計ã€é›†ç´„)ã€DBæ›´æ–° + +### 剿æ¡ä»¶ + +| NO | 対象ファイル | æ¡ä»¶ | +|----|-------------|------| +| 1 | | | +| 2 | | | + +### 使用ファイル一覧 + +| NO | 使用ファイル/DBå | è­˜åˆ¥å­ | DDå | I/O | COPY群 | å½¢å¼ | ブロック | レコード長 | 媒体 | 備考 | +|----|------------------|--------|------|-----|--------|------|---------|-----------|------|------| +| 1 | | | | | | | | | | | +| 2 | | | | | | | | | | | +| 3 | | | | | | | | | | | +| 4 | | | | | | | | | | | +| 5 | | | | | | | | | | | + +### キー項目一覧 + +| NO | ファイルå | ソートæ¡ä»¶(キー項目) | キーæ¡ä»¶(マッãƒãƒ³ã‚°/キーブレイク) | +|----|-----------|---------------------|-------------------------------------------| +| 1 | | | | +| 2 | | | | + +### 使用モジュール一覧 + +| NO | 機能 | プログラムID | 使用COPYå | +|----|------|-------------|-----------| +| 1 | | | | +| 2 | | | | +| 3 | | | | +| 4 | | | | + +--- + +## 処ç†è©³ç´° + +``` +大項目(1æ¡):「1.ã€ã€Œ2.ã€ã€Œ3.ã€ã®ã„ãšã‚Œã‹ + 中項目(2æ¡):対応ã™ã‚‹ãƒ‘ラグラフåを括弧書ãã§ä½µè¨˜ + å°é …ç›®(3æ¡):個別処ç†ã®èª¬æ˜Ž + 詳細:メッセージ番å·ã€PARMã€æ¡ä»¶åˆ†å²ã€ABENDコード等 +``` + +### 処ç†è©³ç´°ãƒ•ォーマット(記入例) + +``` +1.åˆæœŸå‡¦ç† (1000ITTSOR) + 1-1.開始メッセージ出力 + メッセージ番å·: 1(開始メッセージ) + 1-2.コンパイル日時出力 + メッセージ番å·: 33(コンパイル日時) + PARM1: コンパイル日時 + PARM2: 'COMPILED' + 1-3.ãƒ¯ãƒ¼ã‚¯ã‚¨ãƒªã‚¢åˆæœŸåŒ– + 1-4.サブプログラム呼出(å„ç¨®åˆæœŸè¨­å®šï¼‰ + 戻りコードãŒZEROã®å ´åˆã€æ­£å¸¸ç¶™ç¶šã€‚ + ãれ以外ã®å ´åˆã€ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸å‡ºåŠ›å¾Œã€ABEND処ç†ã¸é·ç§»ã€‚ + メッセージ番å·: 5(サブエラー) + PARM1: 呼出プログラムå + PARM2: 戻りコード + ABENDコード: 999 + 1-5.入出力ファイルOPEN + 1-6.入力ファイル1を読ã¿è¾¼ã¿ã€‚ + 1-7.入力ファイル2を読ã¿è¾¼ã¿ã€‚ + +2.ä¸»å‡¦ç† (2000MAJSOR)(全入力ファイルを読ã¿åˆ‡ã‚Šçµ‚了ã™ã‚‹ã¾ã§ç¹°ã‚Šè¿”ã—) + 2-1.æ¡ä»¶Aã®å ´åˆ + 2-1-1.出力ファイル1ã«å‡ºåŠ›ã™ã‚‹ã€‚ + 出力後ã€å‡ºåŠ›ãƒ•ã‚¡ã‚¤ãƒ«1ã®ã‚«ã‚¦ãƒ³ã‚¿ã‚’加算 + 2-1-2.入力ファイル1を読ã¿è¾¼ã¿ã€‚ + 2-2.æ¡ä»¶Bã®å ´åˆ + 2-2-1.出力ファイル2ã«å‡ºåŠ›ã™ã‚‹ã€‚ + 出力後ã€å‡ºåŠ›ãƒ•ã‚¡ã‚¤ãƒ«2ã®ã‚«ã‚¦ãƒ³ã‚¿ã‚’加算 + 2-2-2.入力ファイル2を読ã¿è¾¼ã¿ã€‚ + 2-3.æ¡ä»¶Cã®å ´åˆ + 2-3-1.出力ファイル3ã«å‡ºåŠ›ã™ã‚‹ã€‚ + 出力後ã€å‡ºåŠ›ãƒ•ã‚¡ã‚¤ãƒ«3ã®ã‚«ã‚¦ãƒ³ã‚¿ã‚’加算 + 2-3-2.入力ファイル2を読ã¿è¾¼ã¿ã€‚ + +3.çµ‚äº†å‡¦ç† (3000STPSOR) + 3-1.入出力ファイルCLOSE + 3-2.入出力ファイル件数出力 + メッセージ番å·: 6(入力件数) + PARM1: 該当ファイルDDå + PARM2: 該当ファイル件数 + メッセージ番å·: 7(出力件数) + PARM1: 該当ファイルDDå + PARM2: 該当ファイル件数 + 3-3.終了メッセージ出力 + メッセージ番å·: 2(終了メッセージ) +``` + +--- + +## 出力レコード定義 + +出力ファイルã”ã¨ã«ä»¥ä¸‹ã®å½¢å¼ã§è¨˜è¿°ã™ã‚‹ã€‚ + +### 出力ファイル1(例: W01) + +| No | é …ç›®å | 設定元 | 備考 | +|----|--------|--------|------| +| 1 | | | | +| 2 | | | | +| 3 | | | | +| 4 | | | | +| 5 | | | | + +### 出力ファイル2(例: W02) + +| No | é …ç›®å | 設定元 | 備考 | +|----|--------|--------|------| +| 1 | | | | +| 2 | | | | +| 3 | | | | +| 4 | | | | +| 5 | | | | + +### 出力ファイル3(例: W03) + +| No | é …ç›®å | 設定元 | 備考 | +|----|--------|--------|------| +| 1 | | | | +| 2 | | | | +| 3 | | | | +| 4 | | | | +| 5 | | | | diff --git a/orchestrator_db.py b/orchestrator_db.py index a4170ba..b21e942 100644 --- a/orchestrator_db.py +++ b/orchestrator_db.py @@ -4,6 +4,7 @@ from __future__ import annotations import json import logging import os +import re import subprocess import sqlite3 from dataclasses import dataclass, field @@ -11,18 +12,21 @@ from pathlib import Path from typing import Optional from config import Config -from config.program_schema import ProgramSchema, load_schema +from config.program_schema import ProgramSchema, load_schema, ScenarioDef from cobol_testgen import extract_structure, generate_data from cobol_testgen.flatfile import write_all_files, write_sysin_file from cobol_testgen.file_io import read_output_file -from cobol_testgen.read import preprocess, resolve_copybooks, resolve_sql_includes, parse_file_control, parse_file_section, parse_data_division, extract_data_division, scan_open_statements +from cobol_testgen.read import preprocess, resolve_copybooks, resolve_sql_includes, parse_file_control, parse_file_section, parse_data_division, extract_data_division, extract_procedure_division, scan_open_statements from cobol_testgen.read import strip_exec_sql_from_data_div from cobol_testgen.gcov import run_gcov from cobol_testgen.coverage import run_coverage, generate_coverage_index from cobol_testgen.design_mcdc import enum_paths as mcdc_enum_paths from cobol_testgen.to_sql import collect_sql_meta, build_db_input -from cobol_testgen.core import extract_sql_assignments +from cobol_testgen.core import extract_sql_assignments, classify_field_roles, _init_child_names from cobol_testgen import expand_occurs +from cobol_testgen.design import get_term_type, generate_records +from cobol_testgen.output import output_json +from cobol_testgen.pipeline_bridge import build_branch_tree_fallback import shutil from data.diff_result import VerificationRun, FieldResult from runners.gixsql_runner import GixsqlCobolRunner, GixsqlTableData @@ -80,6 +84,8 @@ class GixsqlOrchestrator: self.pp_path: Optional[Path] = None self.exe_path: Optional[Path] = None self.java_input_path: Optional[Path] = None + self._current_db_path: Optional[Path] = None # scenario-specific DB path + self._multi_run_gcov_data: dict[int, int] | None = None # merged multi-run gcov data self.java_output_path: Optional[Path] = None self.generated_records: list[dict] = [] self.generated_structure: dict | None = None @@ -110,7 +116,13 @@ class GixsqlOrchestrator: flat_cpy.append(str(dst)) # Copy SUB programs - sub_dirs = [self.cobol_src_dir, self.cobol_src_dir.parent / "sub"] + v3_root = Path(__file__).parent + sub_dirs = [ + self.cobol_src_dir, + self.cobol_src_dir.parent / "sub", + v3_root.parent / "cobol-tna-system" / "sub", + v3_root.parent / "production" / "sub", + ] for sub in self.schema.subprograms: found = False for sd in sub_dirs: @@ -160,75 +172,393 @@ class GixsqlOrchestrator: # ── Step 2: å…¥åŠ›ãƒ‡ãƒ¼ã‚¿ç”Ÿæˆ â”€â”€ - def step2_generate_inputs(self) -> DbPipelineResult: - """ãƒ†ã‚¹ãƒˆãƒ‡ãƒ¼ã‚¿ç”Ÿæˆ + フラットファイル出力 + DBåˆæœŸåŒ–""" + def step2_generate_inputs(self, scenario: ScenarioDef | None = None) -> DbPipelineResult: + """ãƒ†ã‚¹ãƒˆãƒ‡ãƒ¼ã‚¿ç”Ÿæˆ + フラットファイル出力 + DBåˆæœŸåŒ– + + Args: + scenario: 多輪実行時ã®ã‚·ãƒŠãƒªã‚ªå®šç¾©ã€‚None=å˜è¼ªï¼ˆå¾“æ¥å‹•作)。 + """ try: src_text = self.src_path.read_text(encoding="utf-8-sig") # Use the pre-gixpp source for Lark parsing (gixpp output contains SQLCA etc.) parse_text = self.pp_path.read_text(encoding="utf-8") if self.pp_path and self.pp_path.exists() else src_text - # COBOL è§£æž + ãƒ†ã‚¹ãƒˆãƒ‡ãƒ¼ã‚¿ç”Ÿæˆ + # COBOL è§£æž + テストデータ生æˆï¼ˆç™½ç›’ + 機能 + ç­–ç•¥ çµ±åˆï¼‰ cbd = [str(d) for d in self.copybook_dirs] st = extract_structure(src_text, copybook_dirs=cbd) self.generated_structure = st - recs = generate_data(src_text, st, copybook_dirs=cbd) + from cobol_testgen.data_merger import generate_all_data + v3_root = Path(__file__).parent + design_doc_dir = v3_root / "詳細設計書" + if not design_doc_dir.exists(): + design_doc_dir = None + # LLMClient 㯠API key ãŒå¿…è¦ãªå ´åˆã®ã¿åˆæœŸåŒ–(未設定時㯠None → スキップ) + llm = None + if hasattr(self.config, 'llm_model') and self.config.llm_model: + from agents.llm import LLMClient + try: + llm = LLMClient(model=self.config.llm_model, timeout=self.config.llm_timeout) + except Exception: + pass + recs = generate_all_data( + program_id=self.program_id, + src_text=src_text, + st=st, + copybook_dirs=cbd, + design_doc_dir=str(design_doc_dir) if design_doc_dir else None, + llm_client=llm, + config=self.config, + ) # Post-process: link R02 cancel APPL-IDs to matching R01 insert APPL-IDs for rec in recs: if 'R02APPL-ID' in rec and 'R01APPL-ID' in rec: rec['R02APPL-ID'] = rec['R01APPL-ID'] - # DB åˆæœŸãƒ‡ãƒ¼ã‚¿æ§‹ç¯‰: single DB under V3 runtime/ dir - self.db_path.parent.mkdir(parents=True, exist_ok=True) - self._init_database(self.db_path) + # シナリオã«å¿œã˜ãŸ DB パス + if scenario: + db_path = Path("C:/Temp/gix") / f"{self.program_id}_{scenario.id}.db" + self._current_db_path = db_path + else: + db_path = self.db_path + self._current_db_path = None + + # DB åˆæœŸãƒ‡ãƒ¼ã‚¿æ§‹ç¯‰: clean stale DB first + if db_path.exists(): + db_path.unlink() + db_path.parent.mkdir(parents=True, exist_ok=True) + self._init_database(db_path) # DB åˆæœŸè¡ŒæŠ•入(DELETE/UPDATE ãŒä½œç”¨ã™ã‚‹è¡Œã€SELECT ãŒè¿”ã™è¡Œï¼‰ - self._populate_database(self.db_path, src_text, recs) + self._populate_database(db_path, src_text, recs) + # P5: inject duplicate-PK rows (scenario ã§åˆ¶å¾¡) + if scenario is None or scenario.inject_duplicate_pk: + self._inject_sql_error_rows(db_path, recs) - # フラットファイル書ã出㗠- input_dir = self.work_dir / "input" + # Patch R01LINE records with EMP-IDs matching the record's own EMP-ID + for i, rec in enumerate(recs): + line = rec.get('R01LINE', '') + if not line: + continue + parts = line.split(',', 1) + if len(parts) != 2: + continue + # Get EMP-ID from the record's own field (e.g., R01EMP-ID) + emp_id = rec.get('R01EMP-ID', '') + if not emp_id or emp_id == '00000000': + emp_id = rec.get('HV-EMP-ID', '') + if not emp_id or emp_id == '00000000': + emp_id = f"EMP{str(i).zfill(5)}" + # First record: empty EMP-ID to trigger R01EMP-ID = SPACE path (DP#12) + if i == 0: + rec['R01LINE'] = f"{' '*8},{parts[1]}" + rec['R01EMP-ID'] = ' ' * len(emp_id) + else: + rec['R01LINE'] = f"{emp_id.ljust(8)},{parts[1]}" + rec['R01EMP-ID'] = emp_id + # Inject duplicate EMP-IDs for last 3 records to trigger AGG UPDATE + # path (DP#19-#20). Use the LARGEST EMP-ID from the last 8 of the + # sorted unique list, so the dup ID falls in the last TARGET card + # batch (which sets TARGET-COUNT). Set R01DATE to keep YEAR_MONTH + # the same but different day avoids PK conflict in DAILY_RECORDS. + # Works for both FIXED (KIN07REC) and LINE SEQUENTIAL formats. + if len(recs) > 3: + # Collect unique EMP-IDs (non-blank, non-zero) + all_ids = set() + for r in recs: + eid = r.get('R01EMP-ID', '') + if eid and eid.strip() and eid != '00000000': + all_ids.add(eid) + sorted_ids = sorted(all_ids) + # Pick from the last chunk (matches last TARGET card batch) + # T chunk = 8 per card, so last chunk index = len % 8 or 8 + n = len(sorted_ids) + last_chunk_start = n - (n % 8 or 8) + dup_eid = sorted_ids[last_chunk_start] if sorted_ids else '' + # Find a record with this EMP-ID for its DATE + src_date = '' + for r in recs: + if r.get('R01EMP-ID', '') == dup_eid: + src_date = r.get('R01DATE', '') + break + dup_date = src_date + for j in range(max(1, len(recs)-3), len(recs)): + rec = recs[j] + if not dup_eid: + continue + rec['R01EMP-ID'] = dup_eid + # FIXED format: keep same YEAR_MONTH but different day + # to avoid PK conflict in DAILY_RECORDS INSERT. + if dup_date and len(dup_date) >= 6: + dup_ym = dup_date[:6] + orig_date = rec.get('R01DATE', '') + if orig_date and len(orig_date) >= 8: + rec['R01DATE'] = dup_ym + orig_date[6:8] + else: + rec['R01DATE'] = dup_ym + '01' + # LINE SEQUENTIAL format: patch R01LINE + line = rec.get('R01LINE', '') + if line: + parts = line.split(',', 1) + if len(parts) == 2: + rec['R01LINE'] = f"{dup_eid.ljust(8)},{parts[1]}" + + # 出力先ディレクトリ(シナリオ毎ã«åˆ†é›¢ï¼‰ + run_label = f"run_{scenario.id}" if scenario else "" + output_root = self.work_dir / run_label if scenario else self.work_dir + input_dir = output_root / "input" input_dir.mkdir(parents=True, exist_ok=True) + + # フラットファイル書ã出ã—(全シナリオåŒä¸€ï¼‰ flats = write_all_files(recs, src_text, input_dir, copybook_dirs=[str(d) for d in self.copybook_dirs]) - # SYSIN 設定ファイル生æˆï¼ˆãƒ—ログラム固有ã®ã‚«ãƒ¼ãƒ‰å½¢å¼ï¼‰ + # SYSIN 設定ファイル生æˆï¼ˆã‚·ãƒŠãƒªã‚ªæ¯Žã« run_cfg を渡ã™ï¼‰ + run_cfg = None + if scenario: + run_cfg = { + "period": scenario.sysin.period, + "include_invalid_period": scenario.sysin.include_invalid_period, + "modes": scenario.sysin.modes, + } sysin_path = write_sysin_file(recs, src_text, input_dir, - copybook_dirs=[str(d) for d in self.copybook_dirs]) + copybook_dirs=[str(d) for d in self.copybook_dirs], + run_cfg=run_cfg) if sysin_path: logger.info(f" SYSIN file written: {sysin_path}") flats.append(("SYSIN", sysin_path, 0)) + # Pre-populate MONTHLY_ABSENCE with matching EMP-ID/YEAR-MONTH for + # SELECT COUNT(*) → HV-CNT > 0 → UPDATE path (DP#27). Must run AFTER + # write_all_files (so the R01 flat file exists). + db_for_seed = self._current_db_path or self.db_path + self._seed_matching_monthly_rows(db_for_seed, recs, max_seed=20, + r01_dir=input_dir) + + # ── JSON 出力(Java 検証用) ── + try: + pp = preprocess(src_text, extra_search_paths=cbd) + data_div = extract_data_division(pp) + data_fields = parse_data_division(data_div) if data_div else [] + fdict = [] + for f in data_fields: + fdict.append({ + 'name': f.name, 'level': f.level, 'pic': f.pic, + 'pic_info': { + 'type': f.pic_info.type if f.pic_info else 'unknown', + 'digits': f.pic_info.digits if f.pic_info else 0, + 'decimal': f.pic_info.decimal if f.pic_info else 0, + 'length': f.pic_info.length if f.pic_info else 0, + 'signed': f.pic_info.signed if f.pic_info else False, + }, + 'section': f.section, 'occurs': f.occurs_count, + 'occurs_depending': f.occurs_depending, + 'value': f.value, 'values': f.values, + 'redefines': f.redefines, 'usage': f.usage, + }) + fdict = expand_occurs(fdict) + proc_div = extract_procedure_division(pp) + branch_tree, assignments = build_branch_tree_fallback(proc_div, fdict) + sql_assigns = extract_sql_assignments(src_text) + for tgt, asgn_list in sql_assigns.items(): + for asgn in asgn_list: + assignments.setdefault(tgt, []).append(asgn) + + # fd_fields / field_to_fd + file_sec = parse_file_section(pp) or {} + fd_fields = {} + field_to_fd = {} + for fd_name, rec_names in file_sec.items(): + fds = [] + seen = set() + for rec in rec_names: + if rec not in seen: + fds.append(rec) + seen.add(rec) + for child in _init_child_names(rec, fdict): + if child not in seen: + fds.append(child) + seen.add(child) + fd_fields[fd_name] = fds + for child in fds: + field_to_fd[child] = fd_name + + open_dir = scan_open_statements(proc_div) if proc_div else {} + + # Roles + path info + termination types + roles = classify_field_roles(branch_tree, assignments, fdict, + source=src_text, + proc_text=proc_div) + branch_paths = mcdc_enum_paths(branch_tree, fdict) + path_infos = [(c, a, get_term_type(c)[1]) for c, a in branch_paths] + json_records, _, term_types = generate_records( + path_infos, fdict, assignments, file_sec=file_sec) + + # DB input for JSON + data_div2, declared_columns = strip_exec_sql_from_data_div(data_div) + sql_meta = collect_sql_meta(assignments, declared_columns) + db_input = None + if sql_meta: + db_input = build_db_input( + branch_paths, fdict, assignments, + sql_meta, declared_columns, records=recs) + + # Write main JSON(シナリオ毎ã«åˆ†é›¢ï¼‰ + json_outdir = output_root / "json" + json_outdir.mkdir(parents=True, exist_ok=True) + json_path = json_outdir / f"{self.program_id}.json" + output_json(json_records, json_path, roles, + fd_fields=fd_fields, field_to_fd=field_to_fd, + open_dir=open_dir, term_types=term_types, + db_input=db_input, data_fields=fdict) + + logger.info(f" JSON output: {json_path}") + flats.append(("JSON", json_path, 0)) + except Exception as ej: + logger.warning(f" JSON output skipped: {ej}") + self.generated_records = recs + db_for_result = str(self._current_db_path or self.db_path) return DbPipelineResult( self.program_id, 2, True, data={"records": len(recs), "flat_files": len(flats), - "db_path": str(self.db_path)}, + "db_path": db_for_result}, ) except Exception as e: return DbPipelineResult(self.program_id, 2, False, str(e)) # ── Step 3: COBOL 実行 ── - def step3_run_cobol(self) -> DbPipelineResult: - """COBOL DB プログラム実行""" + def step3_run_cobol(self, scenario: ScenarioDef | None = None) -> DbPipelineResult: + """COBOL DB プログラム実行(環境変数ã§å…¥å‡ºåŠ›å…ˆã‚’æŒ¯ã‚Šåˆ†ã‘) + + Args: + scenario: 多輪実行時ã®ã‚·ãƒŠãƒªã‚ªã€‚None=å˜è¼ªã€‚ + """ if not self.exe_path or not self.exe_path.exists(): return DbPipelineResult(self.program_id, 3, False, "exe not found (run step1 first)") - # Subprogram DLLs are in cobol-tna-system/bin/ + + # シナリオ毎ã®å‡ºåŠ›å…ˆ + run_label = f"run_{scenario.id}" if scenario else "" + run_dir = self.runtime_dir / run_label if scenario else self.runtime_dir + input_dir = run_dir / "input" + output_dir = run_dir / "output" + gcov_dir = self.runtime_dir / "gcov" + input_dir.mkdir(parents=True, exist_ok=True) + output_dir.mkdir(parents=True, exist_ok=True) + gcov_dir.mkdir(parents=True, exist_ok=True) + + # シナリオ毎㮠CWD = run_{id}/ã€å˜è¼ªæ™‚㯠runtime_dir 直下 + cwd = run_dir + + # 入力ファイル(work_dir/run_{id}/input/ → runtime/run_{id}/input/) + gen_input_dir = self.work_dir / f"run_{scenario.id}" / "input" if scenario else self.work_dir / "input" + if gen_input_dir.exists(): + for f in gen_input_dir.iterdir(): + if f.is_file(): + (input_dir / f.name).write_bytes(f.read_bytes()) + + # JSON 出力(work_dir/run_{id}/json/ → runtime/run_{id}/json/) + gen_json_dir = self.work_dir / f"run_{scenario.id}" / "json" if scenario else self.work_dir / "json" + if gen_json_dir.exists(): + json_dir = run_dir / "json" + json_dir.mkdir(parents=True, exist_ok=True) + for f in gen_json_dir.iterdir(): + if f.is_file() and f.suffix.lower() == '.json': + (json_dir / f.name).write_bytes(f.read_bytes()) + + # Scan ASSIGN TO + OPEN direction → build env overrides + assign_map = self._scan_assign_to() + env_overrides = {} + for fname, direction in assign_map.items(): + if direction == "INPUT": + env_overrides[fname] = os.path.join("input", fname) + else: + env_overrides[fname] = os.path.join("output", fname) + + # シナリオ毎㮠DB パス + db_path = self._current_db_path or self.db_path + # GIXSQL_DB_PATH ãŒåйã‹ãªã„ãŸã‚ã€ãƒ‡ãƒ•ォルト DB ã«ã‚·ãƒŠãƒªã‚ª DB をコピーã™ã‚‹ + if scenario is not None and db_path != self.db_path: + if db_path.exists(): + if self.db_path.exists(): + self.db_path.unlink() + shutil.copy2(str(db_path), str(self.db_path)) + db_path = self.db_path + + # .gcda 㯠CWD(= run_dirï¼‰ã«æ›¸ã出ã•れるã®ã§ã€å®Ÿè¡Œå¾Œã« gcov/run_{id}/ ã«ç§»å‹•ã™ã‚‹ + + # Subprogram DLLs cobol_bin = Path(self.cobol_src_dir).parent / "bin" - self.runtime_dir.mkdir(parents=True, exist_ok=True) + result = self.runner.run( - self.exe_path, self.runtime_dir, - self.db_path, - input_dir=self.work_dir / "input", + self.exe_path, cwd, + db_path, + input_dir=None, cobol_lib_path=str(cobol_bin) if cobol_bin.exists() else None, + env_overrides=env_overrides, ) + + # .gcda ã‚’ gcov/ ã«ã‚³ãƒ”ー(シナリオ毎㫠gcov/run_{id}/) + # GnuCOBOL 㯠.gcno ãŒç”Ÿæˆã•れ㟠CWD (= compile CWD = exe_dir) ã« .gcda を書ã出ã™ã€‚ + # 複数シナリオ㧠.gcno ã¯å…±æœ‰ã•れるãŸã‚ COPY ã§è¡Œã†ï¼ˆMOVE ä¸å¯ï¼‰ã€‚ + gcda_src_dirs = [cwd] # ランタイム CWD + exe_dir_for_gcda = self.work_dir / "bin" + if exe_dir_for_gcda.exists() and exe_dir_for_gcda not in gcda_src_dirs: + gcda_src_dirs.append(exe_dir_for_gcda) + if scenario is None: + gcda_src_dirs.append(self.runtime_dir) # 従æ¥äº’æ› + gcda_dst_dir = gcov_dir / run_label if scenario else gcov_dir + gcda_dst_dir.mkdir(parents=True, exist_ok=True) + for ext in (".gcda", ".gcno"): + for sd in gcda_src_dirs: + for f in sd.glob(f"*{ext}"): + if f.is_file() and f.stat().st_size > 0: + dst = gcda_dst_dir / f.name + if not dst.exists() or f.stat().st_mtime > dst.stat().st_mtime: + shutil.copy2(str(f), str(dst)) + return DbPipelineResult( self.program_id, 3, result.success, - data={"returncode": result.returncode, "log": result.log[:500]}, + data={"returncode": result.returncode, "log": result.log[:500], + "input_dir": str(input_dir), "output_dir": str(output_dir), + "gcov_dir": str(gcov_dir)}, ) + # ── マルãƒãƒ©ãƒ³ gcov マージ ── + + def _merge_multi_run_gcov(self) -> dict[int, int] | None: + """Run gcov per scenario, parse results, merge {line: count} dicts. + + Returns merged gcov_data or None if no multi-run data available. + """ + from cobol_testgen.gcov import run_gcov, parse_cbl_gcov + gcov_dir = self.runtime_dir / "gcov" + run_dirs = sorted(gcov_dir.glob("run_*")) + if len(run_dirs) <= 1: + return None + + # Ensure .gcno is in each run dir (copy from compile CWD if needed) + bin_gcno = self.work_dir / "bin" + if bin_gcno.exists(): + for sd in run_dirs: + for f in bin_gcno.glob("*.gcno"): + dst = sd / f.name + if not dst.exists(): + shutil.copy2(str(f), str(dst)) + + merged_data: dict[int, int] = {} + for sd in run_dirs: + data = run_gcov(f"{self.program_id}_pp", str(sd)) + if data: + for line, count in data.items(): + merged_data[line] = max(merged_data.get(line, 0), count) + + logger.info(f" Merged gcov from {len(run_dirs)} runs ({len(merged_data)} lines)") + return merged_data + # ── ã‚«ãƒãƒ¬ãƒƒã‚¸ãƒ¬ãƒãƒ¼ãƒˆï¼ˆãƒ‘イプライン外ã€ã‚ªãƒ—ション) ── def generate_coverage_report(self, @@ -236,33 +566,68 @@ class GixsqlOrchestrator: """COBOL 実行後:gcov データåŽé›† + é™çš„パスã¨ãƒžãƒ¼ã‚¸ã— HTML レãƒãƒ¼ãƒˆ""" try: if not self.exe_path or not self.exe_path.exists(): - return DbPipelineResult(self.program_id, 0, False, - "exe not found (run step3 first)") + # Fallback: look for exe in standard build location + fallback = self.work_dir / "bin" / f"{self.program_id}.exe" + if fallback.exists(): + self.exe_path = fallback + else: + return DbPipelineResult(self.program_id, 0, False, + f"exe not found at {self.exe_path} or {fallback}") if output_dir is None: v3_root = Path(__file__).parent output_dir = v3_root / "reports" / self.program_id / "coverage" output_dir = Path(output_dir) - # 1. Copy .gcno + .gcda from CWD (compile-time cwd) to runtime_dir - # cobc generates .gcno in CWD; at runtime, program writes .gcda to same CWD - gcno_gcda_count = 0 - for ext in (".gcno", ".gcda"): - for f in Path.cwd().glob(f"*{ext}"): - if f.stat().st_size > 0: - shutil.copy2(str(f), str(self.runtime_dir / f.name)) - gcno_gcda_count += 1 - if gcno_gcda_count == 0: - return DbPipelineResult(self.program_id, 0, False, - f"no .gcno/.gcda found in CWD (--coverage missing?)") + # 1. Use pre-merged multi-run gcov data if available (skip gcov re-run) + if self._multi_run_gcov_data is not None: + gcov_data = self._multi_run_gcov_data + # Also merge subprogram gcov data from each scenario + from cobol_testgen.gcov import run_gcov as _run_gcov + gcov_dir = self.runtime_dir / "gcov" + for sub in self.schema.subprograms: + sub_merged: dict[int, int] = {} + for sd in sorted(gcov_dir.glob("run_*")): + sub_data = _run_gcov(sub, str(sd)) + if sub_data: + for line, cnt in sub_data.items(): + sub_merged[line] = max(sub_merged.get(line, 0), cnt) + if sub_merged: + gcov_data.update(sub_merged) + else: + # Single-run: collect .gcno/.gcda and run gcov + gcov_dir = self.runtime_dir / "gcov" + gcov_dir.mkdir(parents=True, exist_ok=True) + v3_root = Path(__file__).parent + extra_search = list(gcov_dir.glob("run_*")) + [self.work_dir / "bin"] + for search_dir in (v3_root, self.work_dir, self.runtime_dir, gcov_dir, Path.home(), *extra_search): + for ext in (".gcno", ".gcda"): + for f in search_dir.glob(f"*{ext}"): + if f.stat().st_size > 0: + dst = gcov_dir / f.name + if not dst.exists() or f.stat().st_mtime > dst.stat().st_mtime: + shutil.copy2(str(f), str(dst)) + # Count what we have + gcno_gcda_count = 0 + for ext in (".gcno", ".gcda"): + for f in gcov_dir.glob(f"*{ext}"): + if f.stat().st_size > 0 and (f.name.startswith(self.program_id) or f.name.startswith("SUB")): + gcno_gcda_count += 1 + if gcno_gcda_count == 0: + for sd in (v3_root, self.work_dir, self.runtime_dir, gcov_dir): + for ext2 in (".gcno", ".gcda"): + files = list(sd.glob(f"*{ext2}")) + logger.error(f"gcov-check: {sd}\\*{ext2} -> {len(files)} files: {[f.name for f in files[:5]]}") + return DbPipelineResult(self.program_id, 0, False, + "no .gcno/.gcda found (--coverage missing?)") - # 3. Parse gcov data - gcov_data = run_gcov(f"{self.program_id}_pp", str(self.runtime_dir)) - if not gcov_data: - gcov_data = run_gcov(self.program_id, str(self.runtime_dir)) - for sub in self.schema.subprograms: - sd = run_gcov(sub, str(self.runtime_dir)) - if sd: - gcov_data.update(sd) + # 3. Parse gcov data + gcov_data = run_gcov(f"{self.program_id}_pp", str(gcov_dir)) + if not gcov_data: + gcov_data = run_gcov(self.program_id, str(gcov_dir)) + for sub in self.schema.subprograms: + sd = run_gcov(sub, str(gcov_dir)) + if sd: + gcov_data.update(sd) # 4. Static branch tree from step2 st = self.generated_structure @@ -319,13 +684,16 @@ class GixsqlOrchestrator: ) generate_coverage_index([cov_result], str(output_dir.parent)) - # Clean up .gcno/.gcda from CWD (avoid accumulation) - for ext in (".gcno", ".gcda"): - for f in Path.cwd().glob(f"*{ext}"): - try: - f.unlink() - except PermissionError: - pass + # Clean up .gcno/.gcda from v3_root + CWD (avoid accumulation) + for clean_dir in (v3_root, Path.cwd()): + if clean_dir == gcov_dir: + continue + for ext in (".gcno", ".gcda"): + for f in clean_dir.glob(f"*{ext}"): + try: + f.unlink() + except PermissionError: + pass total = cov_result.get("total_branches", 0) covered = cov_result.get("covered_branches", 0) @@ -348,12 +716,13 @@ class GixsqlOrchestrator: def step4_extract_intermediate(self) -> DbPipelineResult: """SQLite → JSON 中介データ抽出(Step 4: DB→Java中介データ)""" - if not self.db_path or not self.db_path.exists(): + db_path = self._current_db_path or self.db_path + if not db_path or not db_path.exists(): return DbPipelineResult(self.program_id, 4, False, "db not found (run step3 first)") try: - conn = sqlite3.connect(str(self.db_path)) + conn = sqlite3.connect(str(db_path)) conn.row_factory = sqlite3.Row # Read from actual COBOL SQL tables (using sql_name or name) @@ -421,17 +790,18 @@ class GixsqlOrchestrator: def step6_verify(self) -> VerificationRun: """Java 出力㨠COBOL 期待値を比較""" + db_path = self._current_db_path or self.db_path vr = VerificationRun( program=self.program_id, runner="gixsql", gixsql_version="0.9.1", - sqlite_path=str(self.db_path) if self.db_path else "", + sqlite_path=str(db_path) if db_path else "", step_reached=6, ) - if self.db_path and self.db_path.exists(): + if db_path and db_path.exists(): after_tables = self.runner.read_db_tables( - self.db_path, + db_path, [t.name for t in self.schema.db_tables], ) for table_data in after_tables: @@ -451,58 +821,123 @@ class GixsqlOrchestrator: def run_all(self, skip_steps: set[int] | None = None, generate_coverage: bool = True) -> VerificationRun: - """Step 1 → 6 を順次実行(skip_jvm=True ã§ Step 5/6 をスキップ)""" + """Step 1 → 6 を順次実行(skip_jvm=True ã§ Step 5/6 をスキップ) + + 多輪実行:schema.runs ãŒå®šç¾©ã•れã¦ã„れã°å„シナリオを順次実行ã—ã€æœ€å¾Œã« gcov をマージ。 + schema.runs ãŒç©ºã®å ´åˆã¯å˜è¼ªï¼ˆå¾“æ¥å‹•作)。 + """ skip = set(skip_steps or []) if self.skip_jvm: skip.update({5, 6}) - steps = [ - (1, self.step1_setup_environment), - (2, self.step2_generate_inputs), - (3, self.step3_run_cobol), - (4, self.step4_extract_intermediate), - ] - if not self.skip_jvm: - steps.extend([ - (5, self.step5_run_java), - (6, self.step6_verify), - ]) - results = [] - last_step = max(s for s, _ in steps) - for step_num, step_fn in steps: - if step_num in skip: - continue - logger.info(f" Step {step_num}...") - result = step_fn() - results.append(result) - if not result.success and step_num < last_step: - vr = VerificationRun( + scenarios = self.schema.runs or [ScenarioDef(id="default")] + is_multi = len(scenarios) > 1 or (len(scenarios) == 1 and scenarios[0].id != "default") + + # Step 1: compile once + if 1 not in skip: + logger.info(" Step 1 (compile)...") + r1 = self.step1_setup_environment() + if not r1.success: + return VerificationRun( program=self.program_id, runner="gixsql", status="BLOCKED", exit_code=2, - step_reached=step_num, - debug={"step_results": [r.__dict__ for r in results]}, + step_reached=1, ) - return vr - # Optional coverage report (non-blocking, not part of numbered pipeline) - cv_flags = getattr(self.config, 'gixsql_compile_flags', '') - if '--coverage' in cv_flags and generate_coverage: - self.generate_coverage_report() + # Each scenario: generate inputs + run COBOL + for scenario in scenarios: + label = f" [{scenario.id}]" if is_multi else "" + logger.info(f" Step 2 (generate inputs){label}...") + r2 = self.step2_generate_inputs(scenario if is_multi else None) + if not r2.success: + return VerificationRun( + program=self.program_id, runner="gixsql", + status="BLOCKED", exit_code=2, + step_reached=2, + ) + logger.info(f" Step 3 (run COBOL){label}...") + r3 = self.step3_run_cobol(scenario if is_multi else None) + if not r3.success: + return VerificationRun( + program=self.program_id, runner="gixsql", + status="BLOCKED", exit_code=2, + step_reached=3, + ) + + # Step 4: extract intermediate (last scenario wins for DB path) + if 4 not in skip: + logger.info(" Step 4 (extract)...") + self.step4_extract_intermediate() if not self.skip_jvm: - vr = results[-1] # step6_verify returned VerificationRun - vr.debug["step_results"] = [r.__dict__ for r in results[:-1] if r] - else: - vr = VerificationRun( - program=self.program_id, runner="gixsql", - status="PASS", exit_code=0, - step_reached=last_step, - debug={"step_results": [r.__dict__ for r in results]}, - ) + steps_remaining = [5, 6] + for step_num in steps_remaining: + if step_num in skip: + continue + logger.info(f" Step {step_num}...") + if step_num == 5: + self.step5_run_java() + elif step_num == 6: + vr = self.step6_verify() + + # Optional coverage report (non-blocking) + cv_flags = getattr(self.config, 'gixsql_compile_flags', '') + if '--coverage' in cv_flags and generate_coverage: + if is_multi: + merged = self._merge_multi_run_gcov() + self._multi_run_gcov_data = merged + self.generate_coverage_report() + + vr = VerificationRun( + program=self.program_id, runner="gixsql", + status="PASS", exit_code=0, + step_reached=6 if not self.skip_jvm else 4, + ) return vr # ── Internal helpers ── + def _scan_assign_to(self) -> dict[str, str]: + """Scan COBOL source for SELECT/ASSIGN-TO + OPEN direction. + Returns {filename: direction} where direction is 'INPUT' or 'OUTPUT'. + Works for both quoted (\"KIN08R01\") and bare (KIN01R01) ASSIGN. + """ + src_text = self.src_path.read_text(encoding="utf-8-sig") + assign_map: dict[str, str] = {} + # First pass: collect all SELECT/ASSIGN-TO mappings + select_to_file: dict[str, str] = {} + for m in re.finditer( + r'SELECT\s+(\w+)\s+ASSIGN\s+TO\s+"?([^"\s.]+)', + src_text, re.IGNORECASE + ): + sel_name = m.group(1) + fname = m.group(2).strip().rstrip('"') + select_to_file[sel_name] = fname + assign_map[fname] = "UNKNOWN" + + # Second pass: determine direction from OPEN statements + # Handle both simple (OPEN INPUT X) and compound (OPEN INPUT X OUTPUT Y) + for m in re.finditer( + r'OPEN\s+((?:INPUT|OUTPUT|I-O|EXTEND)\s+\w+)' + r'((?:\s+(?:INPUT|OUTPUT|I-O|EXTEND)\s+\w+)*)', + src_text, re.IGNORECASE + ): + # Parse the OPEN payload: "INPUT X" + " OUTPUT Y" + payload = m.group(1) + m.group(2) + for part in re.finditer( + r'(INPUT|OUTPUT|I-O|EXTEND)\s+(\w+)', payload, re.IGNORECASE + ): + direction = part.group(1).upper() + sel_name = part.group(2) + if sel_name in select_to_file: + fname = select_to_file[sel_name] + if direction in ("INPUT", "I-O"): + assign_map[fname] = "INPUT" + else: + assign_map[fname] = "OUTPUT" + + return assign_map + def _init_database(self, db_path: Path): """Create tables from schema + COBOL EXEC SQL table definitions.""" self._create_tables(db_path) @@ -599,3 +1034,159 @@ class GixsqlOrchestrator: conn.commit() conn.close() logger.info(f" DB populated: {db_path}") + + def _inject_sql_error_rows(self, db_path: Path, records: list[dict] | None = None): + """Insert duplicate-PK rows to trigger SQL error handling paths in COBOL.""" + conn = sqlite3.connect(str(db_path)) + for table in self.schema.db_tables: + pk_cols = [c.name for c in table.columns if c.primary_key] + if not pk_cols: + continue + col_names = [c.name for c in table.columns] + try: + rows = conn.execute(f"SELECT * FROM [{table.name}] LIMIT 2").fetchall() + if len(rows) < 1: + # For empty tables, generate synthetic error rows from test record data + synthetic = self._make_synthetic_error_rows(table, records) + if synthetic: + rows = synthetic + else: + continue + quoted = ", ".join(f"[{c}]" for c in col_names) + ph = ", ".join("?" for _ in col_names) + for row in rows: + vals = tuple(str(rows[0][i]) if c in pk_cols else "X" for i, c in enumerate(col_names)) + conn.execute(f"INSERT OR IGNORE INTO [{table.name}] ({quoted}) VALUES ({ph})", vals) + logger.info(f" SQL error test row injected into {table.name}") + except Exception as e: + logger.debug(f" SQL error row injection skipped: {e}") + conn.commit() + conn.close() + + def _seed_matching_monthly_rows(self, db_path: Path, records: list[dict] | None, + max_seed: int = 1, + r01_dir: Path | None = None): + """Pre-populate MONTHLY_ABSENCE with rows matching actual R01 record data. + Reads the generated R01 flat file (200-byte fixed records, KIN07REC layout), + extracts unique (EMP_ID, YEAR_MONTH) pairs, and inserts a SUBSET of them. + This ensures some AGG entries find HV-CNT > 0 (UPDATE, DP#27) and + others find HV-CNT = 0 (INSERT, DP#28).""" + r01_path = (r01_dir or self.work_dir / "input") / "KIN08R01" + if not r01_path.exists(): + logger.info(" R01 file not found, skipping MONTHLY_ABSENCE seed") + return + conn = sqlite3.connect(str(db_path)) + monthly_table = None + for t in self.schema.db_tables: + if t.name == "MONTHLY_ABSENCE": + monthly_table = t + break + if not monthly_table: + conn.close() + return + col_names = [c.name for c in monthly_table.columns] + quoted = ", ".join(f"[{c}]" for c in col_names) + ph = ", ".join("?" for _ in col_names) + seen = set() + pairs = [] + rows_inserted = 0 + # KIN07REC layout (each record is 200 bytes): + # EMP-ID PIC 9(008) offset 0, 8 bytes + # DATE PIC 9(008) offset 8, 8 bytes + # ... remaining fields (not needed) + rec_size = 200 + with open(str(r01_path), 'rb') as f: + data = f.read() + num_recs = len(data) // rec_size + for i in range(num_recs): + off = i * rec_size + emp_id = data[off:off+8].decode('ascii', errors='replace').strip() + date = data[off+8:off+16].decode('ascii', errors='replace').strip() + year_month = date[:6] if len(date) >= 6 else date + if not emp_id or not year_month or emp_id == '00000000': + continue + key = (emp_id, year_month) + if key in seen: + continue + seen.add(key) + pairs.append((emp_id, year_month)) + # Sort by EMP_ID for deterministic behavior, then seed only `max_seed` pairs + pairs.sort(key=lambda x: x[0]) + conn = sqlite3.connect(str(db_path)) + monthly_table = None + for t in self.schema.db_tables: + if t.name == "MONTHLY_ABSENCE": + monthly_table = t + break + if not monthly_table: + conn.close() + return + col_names = [c.name for c in monthly_table.columns] + quoted = ", ".join(f"[{c}]" for c in col_names) + ph = ", ".join("?" for _ in col_names) + for seed_idx, (emp_id, year_month) in enumerate(pairs): + if seed_idx >= max_seed: + break + vals = { + "EMP_ID": emp_id, + "YEAR_MONTH": year_month, + "ANNUAL_LEAVE_H": "0", + "PERSONAL_LEAVE_H": "0", + "OFFICIAL_LEAVE_H": "0", + "SICK_LEAVE_H": "0", + "UNAPPROVED_ABSENT_H": "0", + "UPDATED_AT": "2026-01-01 00:00:00", + } + row = tuple(vals.get(c, "") for c in col_names) + try: + conn.execute(f"INSERT OR IGNORE INTO [MONTHLY_ABSENCE] ({quoted}) VALUES ({ph})", row) + rows_inserted += 1 + except Exception: + pass + conn.commit() + conn.close() + if rows_inserted: + logger.info(f" MONTHLY_ABSENCE: {rows_inserted}/{len(pairs)} matching rows seeded (DP#27 F + DP#28 F)") + elif pairs: + logger.info(f" MONTHLY_ABSENCE: 0 seeded — all AGG entries will INSERT (DP#28 F)") + + def _make_synthetic_error_rows(self, table, records: list[dict] | None) -> list[tuple] | None: + """Build synthetic error rows for an empty table from test record data.""" + if not records or len(records) < 2: + return None + pk_cols = [c.name for c in table.columns if c.primary_key] + if not pk_cols: + return None + + # Map COBOL host-variable names to table column names + # KIN08DBU DAILY_RECORDS: EMP_ID=HV-EMP-ID, TARGET_DATE=HV-TARGET-DATE + # KIN08DBU MONTHLY_ABSENCE: EMP_ID=HV-EMP-ID, YEAR_MONTH=HV-YEAR-MONTH + hv_map = { + 'EMP_ID': ('HV-EMP-ID', 'R01EMP-ID', ''), + 'TARGET_DATE': ('HV-TARGET-DATE', ''), + 'YEAR_MONTH': ('HV-YEAR-MONTH', ''), + 'TIME_IN': ('HV-TIME-IN', ''), + 'TIME_OUT': ('HV-TIME-OUT', ''), + 'ANNUAL_LEAVE_H': ('HV-ANNUAL-H', ''), + 'PERSONAL_LEAVE_H': ('HV-PERSONAL-H', ''), + 'OFFICIAL_LEAVE_H': ('HV-OFFICIAL-H', ''), + 'SICK_LEAVE_H': ('HV-SICK-H', ''), + 'UNAPPROVED_ABSENT_H': ('HV-ABSENT-H', ''), + } + + result = [] + for idx in range(min(2, len(records))): + rec = records[idx] + vals = [] + for col in table.columns: + val = None + if col.name in hv_map: + for key in hv_map[col.name]: + if key and key in rec: + val = rec[key] + break + if val is None: + val = ' ' if col.name in pk_cols else '' + vals.append(str(val) if val is not None else '') + result.append(tuple(vals)) + return result if result else None diff --git a/rules/pgm_pattern/50分割.md b/rules/pgm_pattern/50分割.md new file mode 100644 index 0000000..a034ab2 --- /dev/null +++ b/rules/pgm_pattern/50分割.md @@ -0,0 +1,25 @@ +# 50分割 データ生æˆè¦å‰‡ + +## æ¦‚è¦ + +入力ファイルã®ãƒ¬ã‚³ãƒ¼ãƒ‰ã‚’50ä»¶ãšã¤åˆ†å‰²ã—ã€è¤‡æ•°ã®å‡ºåŠ›ãƒ•ã‚¡ã‚¤ãƒ«ã«æŒ¯ã‚Šåˆ†ã‘るプログラムã®ãƒ†ã‚¹ãƒˆãƒ‡ãƒ¼ã‚¿ã€‚ + +## 入力 + +- 入力ファイルã®COPYBOOKフィールド定義 + +## 生æˆãƒ‡ãƒ¼ã‚¿çµ„ + +| 组 | 用途 | 内容 | +|----|------|------| +| 1 | 通常分割 | 51件以上ã®ãƒ‡ãƒ¼ã‚¿ï¼ˆ50ä»¶ã®å¢ƒç•Œã‚’è¶…ãˆã‚‹ã‚±ãƒ¼ã‚¹ï¼‰+ 50件未満ã®ãƒ‡ãƒ¼ã‚¿ | + +## ãƒ‡ãƒ¼ã‚¿ç”Ÿæˆæ‰‹é † + +1. 入力ファイルã®ãƒ•ィールド定義をå–å¾— +2. 組1ã«51件以上ã®ãƒ¬ã‚³ãƒ¼ãƒ‰ã‚’生æˆï¼ˆåˆ†å‰²å¢ƒç•Œãƒ†ã‚¹ãƒˆç”¨ï¼‰ +3. å„フィールドã¯PIC定義ã«å¾“ã„実在ã—ãã†ãªå€¤ã‚’ç”Ÿæˆ + +## 出力 + +å„組1 JSONファイル。 diff --git a/rules/pgm_pattern/CSV→FB変æ›.md b/rules/pgm_pattern/CSV→FB変æ›.md new file mode 100644 index 0000000..43e0d2f --- /dev/null +++ b/rules/pgm_pattern/CSV→FB変æ›.md @@ -0,0 +1,28 @@ +# CSV→FBå¤‰æ› ãƒ‡ãƒ¼ã‚¿ç”Ÿæˆè¦å‰‡ + +## æ¦‚è¦ + +CSVå½¢å¼ã®å…¥åŠ›ãƒ•ã‚¡ã‚¤ãƒ«ã‚’èª­ã¿å–りã€å›ºå®šé•·ï¼ˆFB)レコードã«å¤‰æ›ã—ã¦å‡ºåŠ›ã™ã‚‹ãƒ—ログラムã®ãƒ†ã‚¹ãƒˆãƒ‡ãƒ¼ã‚¿ã€‚ +CSVã®ã‚«ãƒ³ãƒžåŒºåˆ‡ã‚Šåˆ†è§£ã€å¼•用符内改行処ç†ã€é …ç›®ãƒã‚§ãƒƒã‚¯ã‚’å«ã‚€ã€‚ + +## 入力 + +- 入力CSVã®é …目定義(処ç†è©³ç´°ã‹ã‚‰ç‰¹å®šï¼‰ +- 出力COPYBOOKã®ãƒ•ィールド定義 + +## 生æˆãƒ‡ãƒ¼ã‚¿çµ„ + +| 组 | 用途 | 内容 | +|----|------|------| +| 1 | 正常データ | å…¨é …ç›®ãŒæ­£ã—ã„CSVレコード 3〜5件。異ãªã‚‹å€¤ã®ãƒãƒªã‚¨ãƒ¼ã‚·ãƒ§ãƒ³ã‚’ç¢ºä¿ | +| 2 | 異常データ | 項目数ä¸è¶³ã€é …目数éŽå‰°ã€ä¸æ­£ãªå€¤ã®ãƒ¬ã‚³ãƒ¼ãƒ‰ã‚’å«ã‚€ãƒ‡ãƒ¼ã‚¿ | + +## ãƒ‡ãƒ¼ã‚¿ç”Ÿæˆæ‰‹é † + +1. CSVã®é …目数ã¨é …ç›®åを処ç†è©³ç´°ã‹ã‚‰ç‰¹å®š +2. 正常データ: カンマ区切りCSV行文字列を生æˆã€‚値ã¯PIC定義ã«å¾“ㆠ+3. 異常データ: 項目数ä¸ä¸€è‡´ã€ç©ºå€¤ã€ä¸æ­£ãƒ•ォーマットをå«ã‚€ + +## 出力 + +入力ã¯CSV文字列(カンマ区切り)ã¨ã—ã¦1ãƒ•ã‚£ãƒ¼ãƒ«ãƒ‰ã«æ ¼ç´ã€‚å„組1 JSONファイル。 diff --git a/rules/pgm_pattern/DBæ›´æ–°.md b/rules/pgm_pattern/DBæ›´æ–°.md new file mode 100644 index 0000000..73efd5a --- /dev/null +++ b/rules/pgm_pattern/DBæ›´æ–°.md @@ -0,0 +1,79 @@ +# DBæ›´æ–° データ生æˆè¦å‰‡ + +## æ¦‚è¦ + +入力ファイルã®ãƒ¬ã‚³ãƒ¼ãƒ‰ã‚’DBテーブルã«INSERT/UPDATE/DELETEã™ã‚‹ãƒ—ログラムã®ãƒ†ã‚¹ãƒˆãƒ‡ãƒ¼ã‚¿ã‚’生æˆã™ã‚‹ã€‚ +出力ã¯SQL INSERTファイル。 + +## 入力 + +- 詳細設計書ã®å‡¦ç†è©³ç´°ï¼ˆSQLæ“作ã¨åˆ†å²æ¡ä»¶ï¼‰ +- 入力ファイルã®COPYBOOKフィールド定義 +- DBテーブル定義ã¨ä¸»ã‚­ãƒ¼ +- キー項目一覧 + +## 生æˆãƒ‡ãƒ¼ã‚¿çµ„ + +| 组 | 用途 | 内容 | æ¡ä»¶ | +|----|------|------|------| +| 1 | 正常INSERT | 5ä»¶ä»¥ä¸Šã®æ–°è¦INSERT + 末尾ã«å¤škeyテスト | å¿…é ˆ | +| 2 | INSERT+UPDATE | 3件以上ã®é‡è¤‡ä¸»ã‚­ãƒ¼ãƒ¬ã‚³ãƒ¼ãƒ‰ï¼ˆUPDATEトリガー用)+ 末尾ã«å¤škeyテスト | å¿…é ˆ | +| 3 | DELETE対象 | 2件以上ã®DELETE対象レコード + 末尾ã«å¤škeyテスト | 処ç†è©³ç´°ã«DELETEæ“作ãŒã‚ã‚‹å ´åˆã®ã¿ | + +## 多keyテスト(å„çµ„ã®æœ«å°¾ã«è¿½åŠ ï¼‰ + +キーãŒè¤‡æ•°é …目(例: A001, A002, A003ï¼‰ã§æ§‹æˆã•れる場åˆã€ä»¥ä¸‹ã®ãƒ†ã‚¹ãƒˆãƒ‡ãƒ¼ã‚¿ã‚’追加ã™ã‚‹ã€‚ + +**手順:** +1. 該当グループ内ã‹ã‚‰1ä»¶ã®ãƒ¬ã‚³ãƒ¼ãƒ‰ã‚’é¸ã³ã€åŸºæº–データã¨ã™ã‚‹ +2. キー項目ãŒN個ã‚ã‚‹å ´åˆã€Nä»¶ã®è¿½åŠ ãƒ¬ã‚³ãƒ¼ãƒ‰ã‚’ç”Ÿæˆã™ã‚‹ +3. å„追加レコードã¯ã€Œ1ã¤ã®ã‚­ãƒ¼é …ç›®ã®ã¿åŸºæº–データã¨ç•°ãªã‚Šã€æ®‹ã‚Šã®ã‚­ãƒ¼é …ç›®ã¯åŸºæº–データã¨å®Œå…¨ä¸€è‡´ã€ã¨ã™ã‚‹ +4. éžã‚­ãƒ¼é …目(DATA部分)ã¯åŸºæº–データã¨åŒã˜å€¤ã§ã‚ˆã„ + +**例: キー㌠(A001, A002, A003) ã®å ´åˆã€åŸºæº–データ (a001, b001, c001) ã«å¯¾ã—:** +- 追加1: (a002, b001, c001) → A001ã®ã¿ç•°ãªã‚‹ → INSERTã•れるã¹ã(別レコード) +- 追加2: (a001, b002, c001) → A002ã®ã¿ç•°ãªã‚‹ → INSERTã•れるã¹ã(別レコード) +- 追加3: (a001, b001, c002) → A003ã®ã¿ç•°ãªã‚‹ → INSERTã•れるã¹ã(別レコード) + +ã“れã«ã‚ˆã‚Šã€ãƒ—ãƒ­ã‚°ãƒ©ãƒ ãŒæ­£ã—ã„ã‚­ãƒ¼é …ç›®ã§æ¯”較ã—ã¦ã„ã‚‹ã“ã¨ã‚’検証ã™ã‚‹ã€‚ + +## ãƒ‡ãƒ¼ã‚¿ç”Ÿæˆæ‰‹é † + +1. **SQLæ“作ã®ç‰¹å®š**: 処ç†è©³ç´°ã‹ã‚‰INSERT/UPDATE/DELETE/UPSERTã®æœ‰ç„¡ã‚’確èªã™ã‚‹ +2. **キー項目ã®ç‰¹å®š**: キー項目一覧ã‹ã‚‰DBテーブルã®ä¸»ã‚­ãƒ¼ã‚’特定ã™ã‚‹ +3. **フィールド値生æˆ**: 入力COPYBOOKã®å„フィールドã«å¯¾ã—PIC定義ã«å¾“ã„値を生æˆ: + - PIC X(n): 左詰ã‚・スペース埋゠+ - PIC 9(n): å³è©°ã‚・先行ゼロ + - PIC S9(n) COMP / COMP-3: 通常ã®10進数文字列(符å·ä»˜ã・ãªã—) + - DATE / 日付系フィールド: 実在ã—ãã†ãªæ—¥ä»˜ï¼ˆYYYYMMDDå½¢å¼ï¼‰ + - TIME / 時刻系フィールド: 実在ã—ãã†ãªæ™‚刻(HHMMå½¢å¼ï¼‰ + - ID / 番å·ç³»ãƒ•ィールド: 連番 + - NAME系フィールド: 実在ã—ãã†ãªæ°å + - é‡‘é¡ / SALARYç³»: ç¾å®Ÿçš„ãªé‡‘é¡ +4. **組1 (正常INSERT)**: + - 全レコードã®ä¸»ã‚­ãƒ¼ã‚’é‡è¤‡ãªã—ã§ç”Ÿæˆ + - å„レコードã®éžã‚­ãƒ¼ãƒ•ィールドã¯ç•°ãªã‚‹å€¤ã‚’設定 + - 末尾ã«å¤škeyテストデータを追加 +5. **組2 (INSERT+UPDATE)**: + - 一部ã®ãƒ¬ã‚³ãƒ¼ãƒ‰ã®ä¸»ã‚­ãƒ¼ã‚’組1ã¨é‡è¤‡ã•ã›ã‚‹ + - é‡è¤‡ãƒ¬ã‚³ãƒ¼ãƒ‰ã®éžã‚­ãƒ¼ãƒ•ィールド値ã¯çµ„1ã¨ç•°ãªã‚‹å€¤ã«ã™ã‚‹ + - 末尾ã«å¤škeyテストデータを追加 +6. **組3 (DELETE対象)**: 処ç†è©³ç´°ã«DELETEæ“作ãŒã‚ã‚‹å ´åˆã®ã¿ç”Ÿæˆ + - STATUS='9'ãªã©ã®å‰Šé™¤ãƒ•ラグ付ãデータ + - 末尾ã«å¤škeyテストデータを追加 + +## å‡ºåŠ›å½¢å¼ + +å„組1ã¤ã®SQLファイル: `{プログラムID}_g{groupId}.sql` + +```sql +-- Group: 1 +-- 用途: 正常INSERT + 多keyテスト +INSERT INTO TABLE_NAME (COL1, COL2, ...) VALUES +('val1', 'val2', ...), +('val3', 'val4', ...); +``` + +**注æ„:** +- 出力ã¯ç´”粋ãªSQL INSERTæ–‡ã®ã¿ï¼ˆã‚³ãƒ¼ãƒ‰ãƒ–ロックã§å›²ã¾ãªã„) +- 複数行を1ã¤ã®INSERTæ–‡ã«ã¾ã¨ã‚る(VALUESã®å¾Œã«ã‚«ãƒ³ãƒžåŒºåˆ‡ã‚Šã§è¤‡æ•°è¡Œï¼‰ diff --git a/rules/pgm_pattern/GETPUT(編集出力).md b/rules/pgm_pattern/GETPUT(編集出力).md new file mode 100644 index 0000000..8ab739e --- /dev/null +++ b/rules/pgm_pattern/GETPUT(編集出力).md @@ -0,0 +1,28 @@ +# GETPUT(編集出力)データ生æˆè¦å‰‡ + +## æ¦‚è¦ + +DBã¾ãŸã¯ãƒ•ァイルã‹ã‚‰ãƒ‡ãƒ¼ã‚¿ã‚’読ã¿å–りã€ç·¨é›†ãƒ»æ•´å½¢ã—ã¦å‡ºåŠ›ã™ã‚‹ãƒ—ログラムã®ãƒ†ã‚¹ãƒˆãƒ‡ãƒ¼ã‚¿ã€‚ +レイアウト編集ã®ã¿(GETPUT)ã¨åŒæ§˜ã®ã‚·ãƒ³ãƒ—ルãªREAD→編集→WRITEパターン。 + +## 入力 + +- 入力ソースã®ãƒ•ィールド定義(COPYBOOKã¾ãŸã¯DBテーブル定義) +- 出力レコード定義 + +## 生æˆãƒ‡ãƒ¼ã‚¿çµ„ + +| 组 | 用途 | 内容 | +|----|------|------| +| 1 | 基本データ | 通常値2〜5件。å„ãƒ•ã‚£ãƒ¼ãƒ«ãƒ‰ã«æœ‰åйãªå€¤ã‚’設定。異ãªã‚‹å€¤ã®ãƒãƒªã‚¨ãƒ¼ã‚·ãƒ§ãƒ³ã‚’ç¢ºä¿ | + +## ãƒ‡ãƒ¼ã‚¿ç”Ÿæˆæ‰‹é † + +1. 入力フィールドを特定 +2. å„フィールドã®PIC定義ã«å¾“ã„å€¤ã‚’ç”Ÿæˆ +3. 実在ã—ãã†ãªå€¤ï¼ˆæ—¥ä»˜ãƒ»æ™‚刻・金é¡ãƒ»æ°åç­‰ï¼‰ã‚’ç”Ÿæˆ +4. レコード間ã§ç•°ãªã‚‹å€¤ã‚’設定 + +## 出力 + +å„組1 JSONファイル。DBã‹ã‚‰ã®å…¥åŠ›ã®å ´åˆã¯SQLファイルも出力。 diff --git a/rules/pgm_pattern/MERGE.md b/rules/pgm_pattern/MERGE.md new file mode 100644 index 0000000..f77e297 --- /dev/null +++ b/rules/pgm_pattern/MERGE.md @@ -0,0 +1,25 @@ +# MERGE(複数ファイルçµåˆ) データ生æˆè¦å‰‡ + +## æ¦‚è¦ + +複数ã®å…¥åŠ›ãƒ•ã‚¡ã‚¤ãƒ«ã‚’ã‚­ãƒ¼ã§ã‚½ãƒ¼ãƒˆãƒ»ãƒžãƒ¼ã‚¸ã—ã€çµ±åˆã•れãŸ1ã¤ã®å‡ºåŠ›ãƒ•ã‚¡ã‚¤ãƒ«ã‚’ç”Ÿæˆã™ã‚‹ãƒ—ログラムã®ãƒ†ã‚¹ãƒˆãƒ‡ãƒ¼ã‚¿ã€‚ + +## 入力 + +- 詳細設計書ã®ã‚­ãƒ¼é …目一覧(マージキー) +- å„入力ファイルã®COPYBOOKフィールド定義 + +## 生æˆãƒ‡ãƒ¼ã‚¿çµ„ + +| 组 | 用途 | 内容 | +|----|------|------| +| 1 | 全ファイルカãƒãƒ¼ | 全入力ファイルã®ãƒ‡ãƒ¼ã‚¿ãŒå‡ºåŠ›ã«å映ã•れるデータ + 末尾多keyテスト | +| 2 | キー境界 | åŒä¸€ã‚­ãƒ¼å€¤ãŒè¤‡æ•°ãƒ•ァイルã«åˆ†æ•£ã—ã¦ã„るケース + 末尾多keyテスト | + +## 多keyテスト + +キーãŒè¤‡æ•°é …ç›®ã®å ´åˆã€å„組末尾ã«è¿½åŠ ã€‚å„キー項目ã®ã¿ç•°ãªã‚‹N件を生æˆã—ã€æ­£ã—ã„キーã§ãƒžãƒ¼ã‚¸ã•れるã“ã¨ã‚’検証。 + +## 出力 + +å„組1 JSONファイル。 diff --git a/rules/pgm_pattern/SELECT処ç†.md b/rules/pgm_pattern/SELECT処ç†.md new file mode 100644 index 0000000..dd8b995 --- /dev/null +++ b/rules/pgm_pattern/SELECT処ç†.md @@ -0,0 +1,31 @@ +# SELECTå‡¦ç† ãƒ‡ãƒ¼ã‚¿ç”Ÿæˆè¦å‰‡ + +## æ¦‚è¦ + +DBテーブルã‹ã‚‰æ¡ä»¶ä»˜ãSELECTã§ãƒ‡ãƒ¼ã‚¿ã‚’å–å¾—ã—ã€å–å¾—çµæžœã«åŸºã¥ã„ã¦å‡¦ç†ã‚’行ã†ãƒ—ログラムã®ãƒ†ã‚¹ãƒˆãƒ‡ãƒ¼ã‚¿ã€‚入力ã¯DB(SELECTçµæžœï¼‰ã§ã‚りã€å‡ºåŠ›ã¯SQL INSERTファイル。 + +## 入力 + +- 詳細設計書ã®å‡¦ç†è©³ç´°ï¼ˆSELECTæ–‡ã®WHEREæ¡ä»¶ï¼‰ +- DBテーブル定義(テーブルåã€ã‚«ãƒ©ãƒ ã€ä¸»ã‚­ãƒ¼ï¼‰ + +## 生æˆãƒ‡ãƒ¼ã‚¿çµ„ + +| 组 | 用途 | 内容 | +|----|------|------| +| 1 | 基本データ | SELECTæ¡ä»¶ã«ãƒžãƒƒãƒã™ã‚‹ãƒ‡ãƒ¼ã‚¿ + マッãƒã—ãªã„データ + 末尾多keyテスト | + +## 多keyテスト + +WHEREæ¡ä»¶ã«è¤‡æ•°ã‚­ãƒ¼ãŒé–¢ã‚ã‚‹å ´åˆã€åŸºæº–データã«å¯¾ã—å„キー項目ã®ã¿ç•°ãªã‚‹N件を生æˆã€‚マッãƒã™ã‚‹ãƒ‘ターンã¨ãƒžãƒƒãƒã—ãªã„パターンã®ä¸¡æ–¹ã‚’ã‚«ãƒãƒ¼ã€‚ + +## ãƒ‡ãƒ¼ã‚¿ç”Ÿæˆæ‰‹é † + +1. SELECTæ–‡ã®WHEREæ¡ä»¶ã‚’抽出 +2. æ¡ä»¶ã«ãƒžãƒƒãƒã™ã‚‹ãƒ¬ã‚³ãƒ¼ãƒ‰ã‚’ç”Ÿæˆ +3. æ¡ä»¶ã«ãƒžãƒƒãƒã—ãªã„ãƒ¬ã‚³ãƒ¼ãƒ‰ï¼ˆå¢ƒç•Œå€¤ï¼‰ã‚’ç”Ÿæˆ +4. 多keyテストデータを追加 + +## 出力 + +å„組1 SQL INSERTファイル。 diff --git a/rules/pgm_pattern/キーブレイク(集約).md b/rules/pgm_pattern/キーブレイク(集約).md new file mode 100644 index 0000000..71706a0 --- /dev/null +++ b/rules/pgm_pattern/キーブレイク(集約).md @@ -0,0 +1,32 @@ +# キーブレイク(集約) データ生æˆè¦å‰‡ + +## æ¦‚è¦ + +キー項目ã§ã‚½ãƒ¼ãƒˆæ¸ˆã¿ã®å…¥åŠ›ãƒ•ã‚¡ã‚¤ãƒ«ã‚’èª­ã¿å–りã€ã‚­ãƒ¼ãƒ–レイク時ã«é›†ç´„レコード(カウント・åˆè¨ˆãªã©ï¼‰ã‚’出力ã™ã‚‹ãƒ—ログラムã®ãƒ†ã‚¹ãƒˆãƒ‡ãƒ¼ã‚¿ã‚’生æˆã™ã‚‹ã€‚ + +## 入力 + +- 詳細設計書ã®ã‚­ãƒ¼é …目一覧 +- 入力ファイルã®COPYBOOKフィールド定義 + +## 生æˆãƒ‡ãƒ¼ã‚¿çµ„ + +| 组 | 用途 | 内容 | +|----|------|------| +| 1 | 基本集約 | 複数グループã®é›†ç´„データ + 末尾ã«å¤škeyテスト | +| 2 | 境界ケース | キー境界ã§ã®æ­£ã—ã„集約切替を確èªã™ã‚‹ãƒ‡ãƒ¼ã‚¿ + 末尾ã«å¤škeyテスト | + +## 多keyテスト + +キーãŒè¤‡æ•°é …ç›®ã®å ´åˆã€å„組末尾ã«è¿½åŠ ã€‚1レコードを基準ã«ã€å„キー項目ã®ã¿ç•°ãªã‚‹N件を生æˆã€‚ + +## ãƒ‡ãƒ¼ã‚¿ç”Ÿæˆæ‰‹é † + +1. キー項目ã®ç‰¹å®š +2. 组1: 3グループ以上ã€å„グループ内ã«è¤‡æ•°ãƒ¬ã‚³ãƒ¼ãƒ‰ï¼ˆé›†ç´„対象) +3. 组2: キー値ãŒ1ä»¶ãšã¤ç•°ãªã‚‹å¢ƒç•Œãƒ‡ãƒ¼ã‚¿ +4. å„組末尾ã«å¤škeyテスト + +## 出力 + +å„組1 JSONファイル。 diff --git a/rules/pgm_pattern/キーブレイク(集計).md b/rules/pgm_pattern/キーブレイク(集計).md new file mode 100644 index 0000000..56bbccd --- /dev/null +++ b/rules/pgm_pattern/キーブレイク(集計).md @@ -0,0 +1,37 @@ +# キーブレイク(集計) データ生æˆè¦å‰‡ + +## æ¦‚è¦ + +キー項目ã§ã‚½ãƒ¼ãƒˆæ¸ˆã¿ã®å…¥åŠ›ãƒ•ã‚¡ã‚¤ãƒ«ã‚’èª­ã¿å–りã€ã‚­ãƒ¼ãŒå¤‰ã‚るタイミング(キーブレイク)ã§é›†è¨ˆå€¤ã‚’計算ã—ã€1グループã«ã¤ã1ä»¶ã®é›†è¨ˆãƒ¬ã‚³ãƒ¼ãƒ‰ã‚’出力ã™ã‚‹ãƒ—ログラムã®ãƒ†ã‚¹ãƒˆãƒ‡ãƒ¼ã‚¿ã‚’生æˆã™ã‚‹ã€‚ + +## 入力 + +- 詳細設計書ã®ã‚­ãƒ¼é …目一覧(ソートキー) +- 入力ファイルã®COPYBOOKフィールド定義 +- 集計対象フィールド(処ç†è©³ç´°ã‹ã‚‰ç‰¹å®šï¼‰ + +## 生æˆãƒ‡ãƒ¼ã‚¿çµ„ + +| 组 | 用途 | 内容 | +|----|------|------| +| 1 | 基本集計 | 複数グループ・å„グループ内複数レコードã®ãƒ‡ãƒ¼ã‚¿ + 末尾ã«å¤škeyテスト | +| 2 | å˜ä¸€ãƒ¬ã‚³ãƒ¼ãƒ‰ã‚°ãƒ«ãƒ¼ãƒ— | 1レコードã®ã¿ã®ã‚°ãƒ«ãƒ¼ãƒ—ã‚’å«ã‚€ãƒ‡ãƒ¼ã‚¿ + 末尾ã«å¤škeyテスト | + +## 多keyテスト(å„çµ„ã®æœ«å°¾ã«è¿½åŠ ï¼‰ + +キーãŒè¤‡æ•°é …ç›®ã§æ§‹æˆã•れる場åˆ: +1. 該当グループ内ã‹ã‚‰1ä»¶ã®ãƒ¬ã‚³ãƒ¼ãƒ‰ã‚’é¸ã³åŸºæº–データã¨ã™ã‚‹ +2. N個ã®ã‚­ãƒ¼é …ç›®ã«å¯¾ã—ã€ãれãžã‚Œ1é …ç›®ã®ã¿ç•°ãªã‚‹Nä»¶ã®è¿½åŠ ãƒ¬ã‚³ãƒ¼ãƒ‰ã‚’ç”Ÿæˆ +3. ã“れらã®ãƒ¬ã‚³ãƒ¼ãƒ‰ã¯åˆ¥ã‚°ãƒ«ãƒ¼ãƒ—ã¨ã—ã¦æ‰±ã‚れるã¹ã(集計値ãŒåˆ¥ã«ãªã‚‹ï¼‰ + +## ãƒ‡ãƒ¼ã‚¿ç”Ÿæˆæ‰‹é † + +1. キー項目を特定(キー項目一覧ã®ã‚½ãƒ¼ãƒˆæ¡ä»¶ã‹ã‚‰ï¼‰ +2. 集計フィールドを特定(処ç†è©³ç´°ã‹ã‚‰COMPUTE/ADD対象を抽出) +3. 组1: 3グループ以上ã€å„グループ2〜5レコードã€ã‚­ãƒ¼æ˜‡é † +4. 组2: å˜ä¸€ãƒ¬ã‚³ãƒ¼ãƒ‰ã‚°ãƒ«ãƒ¼ãƒ—ã¨è¤‡æ•°ãƒ¬ã‚³ãƒ¼ãƒ‰ã‚°ãƒ«ãƒ¼ãƒ—ã®æ··åœ¨ +5. å„組末尾ã«å¤škeyテストデータを追加 + +## 出力 + +å„組1 JSONファイル。 diff --git a/rules/pgm_pattern/キーブレイク(集計ã€é›†ç´„).md b/rules/pgm_pattern/キーブレイク(集計ã€é›†ç´„).md new file mode 100644 index 0000000..7c15963 --- /dev/null +++ b/rules/pgm_pattern/キーブレイク(集計ã€é›†ç´„).md @@ -0,0 +1,25 @@ +# キーブレイク(集計ã€é›†ç´„) データ生æˆè¦å‰‡ + +## æ¦‚è¦ + +ソート済ã¿å…¥åŠ›ãƒ•ã‚¡ã‚¤ãƒ«ã‚’èª­ã¿å–りã€ã‚­ãƒ¼ãƒ–レイクã§é›†è¨ˆãƒ»é›†ç´„を行ã„çµæžœã‚’出力ã™ã‚‹ãƒ—ログラムã®ãƒ†ã‚¹ãƒˆãƒ‡ãƒ¼ã‚¿ã€‚複数ファイル入力ã®å ´åˆã¯ãƒžãƒƒãƒãƒ³ã‚°ã¨é›†è¨ˆã‚’組ã¿åˆã‚ã›ã‚‹ã€‚ + +## 入力 + +- 詳細設計書ã®ã‚­ãƒ¼é …目一覧ã€ä½¿ç”¨ãƒ•ァイル一覧 +- å„入力ファイルã®COPYBOOKフィールド定義 + +## 生æˆãƒ‡ãƒ¼ã‚¿çµ„ + +| 组 | 用途 | 内容 | +|----|------|------| +| 1 | 通常集計 | 複数グループ・å„グループ複数レコード + 末尾多keyテスト | +| 2 | åŒã‚­ãƒ¼å†…集約 | åŒä¸€ã‚­ãƒ¼å†…ã§N件集約ã•れるデータ + 末尾多keyテスト | + +## 多keyテスト + +キーãŒè¤‡æ•°é …ç›®ã®å ´åˆã€å„組末尾ã«è¿½åŠ ã€‚1レコードを基準ã«å„キー項目ã®ã¿ç•°ãªã‚‹N件を生æˆã€‚ + +## 出力 + +å„組1 JSONファイル。 diff --git a/rules/pgm_pattern/マッãƒãƒ³ã‚°(1-1).md b/rules/pgm_pattern/マッãƒãƒ³ã‚°(1-1).md new file mode 100644 index 0000000..b45c577 --- /dev/null +++ b/rules/pgm_pattern/マッãƒãƒ³ã‚°(1-1).md @@ -0,0 +1,78 @@ +# マッãƒãƒ³ã‚°(1:1) æ•°æ®ç”Ÿæˆè§„则 + +## æ¦‚è¦ + +2个输入文件按指定键进行1:1匹é…ï¼Œè¾“å‡ºåŒ¹é…æˆåŠŸ/ä»…R01/ä»…R02三ç§ç»“果。 + +## 输入 + +- 剿æ¡ä»¶ï¼šä»Žè¯¦ç»†è®¾è®¡ä¹¦è§£æžåŒ¹é…键(A001>A002>A003...) +- 输入文件:2个(R01ã€R02) +- é”®æ¡ä»¶ï¼šé”®å€¼å”¯ä¸€ï¼ˆé‡è¤‡NG),å‡åºæŽ’åº + +## 生æˆçš„æ•°æ®ç»„ + +| 组 | 用途 | R01记录数 | R02记录数 | 说明 | +|----|------|-----------|-----------|------| +| 1 | 两端ä¸åŒ¹é… | 4 | 4 | 第1æ¡R01-onlyã€æœ€åŽ1æ¡R02-onlyã€ä¸­é—´3æ¡ä»¥ä¸ŠåŒ¹é… | +| 2 | åå‘两端ä¸åŒ¹é… | 4 | 4 | 第1æ¡R02-onlyã€æœ€åŽ1æ¡R01-onlyã€ä¸­é—´3æ¡ä»¥ä¸ŠåŒ¹é… | +| 3 | 中间ä¸åŒ¹é… | 5 | 5 | 首尾匹é…ã€ä¸­é—´æœ‰R01-onlyå’ŒR02-onlyå„1æ¡ | + +### 组1:两端ä¸åŒ¹é… + +R01键:a001(R01-only) → a002 → a003 → a004 +R02键:a002 → a003 → a004 → a005(R02-only) + +records数组(按处ç†é¡ºåºï¼‰ï¼š +| recordåºå· | 内容 | 说明 | +|-----------|------|------| +| 1 | R01(a001) | R01-only | +| 2 | R01(a002) + R02(a002) | åŒ¹é… | +| 3 | R01(a003) + R02(a003) | åŒ¹é… | +| 4 | R01(a004) + R02(a004) | åŒ¹é… | +| 5 | R02(a005) | R02-only | + +组1ï¼šéœ€è¦æ³¨æ„当key为n个项目组åˆè€Œæˆæ—¶ï¼Œåœ¨R01的最åŽéœ€è¦åŠ ä¸Šnæ¡æ•°æ®ã€‚ +å’ŒR02(a005)的所有keyç›¸æ¯”ï¼Œåªæœ‰å½“å‰ç¬¬n个key与R02(a005)çš„keyä¸ä¸€è‡´ã€‚ä¿è¯key的比较没有问题。 + + +### 组2:åå‘两端ä¸åŒ¹é… + +R01键:a002 → a003 → a004 → a005(R01-only) +R02键:a001(R02-only) → a002 → a003 → a004 + +records数组: +| recordåºå· | 内容 | 说明 | +|-----------|------|------| +| 1 | R02(a001) | R02-only | +| 2 | R01(a002) + R02(a002) | åŒ¹é… | +| 3 | R01(a003) + R02(a003) | åŒ¹é… | +| 4 | R01(a004) + R02(a004) | åŒ¹é… | +| 5 | R01(a005) | R01-only | + +### 组3:中间ä¸åŒ¹é… + +R01键:a001 → a003(R01-only) → a004 → a005 +R02键:a001 → a002(R02-only) → a003 → a005 + +records数组: +| recordåºå· | 内容 | 说明 | +|-----------|------|------| +| 1 | R01(a001) + R02(a001) | åŒ¹é… | +| 2 | R01(a003) | R01-only | +| 3 | R01(a004) + R02(a003) | åŒ¹é… | +| 4 | R02(a002) | R02-only | +| 5 | R01(a005) + R02(a005) | åŒ¹é… | + +## æ•°æ®ç”Ÿæˆæ­¥éª¤ + +1. ä»Žå‰ææ¡ä»¶è§£æžåŒ¹é…键字段(如 A001>A002>A003) +2. 从COPYBOOK获å–å„字段的PIC定义 +3. 生æˆé”®å€¼ï¼šé”®å€¼ä»¥ a001/b001/c001 æ ¼å¼é€’增,ä¿è¯å‡åºæŽ’列 +4. éžé”®å­—段的数æ®ç”Ÿæˆï¼šæ ¹æ®å­—段å关键è¯ç”Ÿæˆç¬¦åˆå®žé™…的数æ®ï¼ŒåŒæ—¶å°½é‡ä¿è¯æ¯æ¡ç›¸é‚»çš„æ•°æ®çš„相åŒé¡¹ç›®ä¸ä¸€è‡´ã€‚ +5. 按å„组规则组装records数组 +6. 组å·ä»Ž1å¼€å§‹è¿žç»­ç¼–å· + +## 输出 + +æ¯ä¸ªç»„输出一个json文件:`{プログラムID}_g{groupId}.json` diff --git a/rules/pgm_pattern/マッãƒãƒ³ã‚°(1-N).md b/rules/pgm_pattern/マッãƒãƒ³ã‚°(1-N).md new file mode 100644 index 0000000..6ab3e51 --- /dev/null +++ b/rules/pgm_pattern/マッãƒãƒ³ã‚°(1-N).md @@ -0,0 +1,69 @@ +# マッãƒãƒ³ã‚°(1:N) æ•°æ®ç”Ÿæˆè§„则 + +## æ¦‚è¦ + +2个输入文件,R01为主驱动,R02为从侧。1ä»¶R01å¯å¯¹åº”多件R02(1:N)。 + +## 输入 + +- 输入文件:2个(R01主驱动ã€R02从侧) +- 剿æ¡ä»¶ï¼šåŒæ–¹æŒ‰keyå‡åºæŽ’åº +- 核心逻辑:EVALUATE TRUE 3分å²ï¼ˆR01key) + +## 生æˆçš„æ•°æ®ç»„ + +| 组 | 用途 | R01记录数 | R02记录数 | 说明 | +|----|------|-----------|-----------|------| +| 1 | 1:1åŒ¹é… | 3 | 3 | 全部1:1匹é…,中间R01-onlyå’ŒR02-only | +| 2 | 1:NåŒ¹é… | 3 | 5 | R01有1:N匹é…(åŒkey多æ¡R02) | +| 3 | R02-å…ˆç»“æŸ | 3 | 2 | R01先结æŸï¼Œç¬¬ä¸€æ¡æ•°æ®çš„R01key<第一æ¡çš„r02æ•°æ® | +| 4 | R02-only | 2 | 4 | R02先结æŸï¼Œç¬¬ä¸€æ¡æ•°æ®çš„R01key>第一æ¡çš„r02æ•°æ® | + +### 组1:1:1åŒ¹é… + +R01: a001→a002→a003 +R02: a001→a002→a003 + +records按处ç†é¡ºåºï¼š +| recordåºå· | 内容 | 说明 | +|-----------|------|------| +| 1 | R01(a001)+R02(a001) | åŒ¹é… | +| 2 | R01(a002)+R02(a002) | åŒ¹é… | +| 3 | R01(a003)+R02(a003) | åŒ¹é… | + +组1ï¼šéœ€è¦æ³¨æ„当key为n个项目组åˆè€Œæˆæ—¶ï¼Œåœ¨R01的最åŽéœ€è¦åŠ ä¸Šnæ¡æ•°æ®ã€‚ +å’ŒR02(a003)的所有keyç›¸æ¯”ï¼Œåªæœ‰å½“å‰ç¬¬n个key与R02(a003)çš„keyä¸ä¸€è‡´ã€‚ä¿è¯key的比较没有问题。 + + +### 组2:1:NåŒ¹é… + +R01: a001→a002→a003 +R02: a001→a002→a002→a002→a003 + (åŒkey有3æ¡) + +records按处ç†é¡ºåºï¼š +| recordåºå· | 内容 | 说明 | +|-----------|------|------| +| 1 | R01(a001)+R02(a001) | 匹é…(第1ä»¶) | +| 2 | R01(a002)+R02(a002) | 匹é…(第1ä»¶) | +| 3 | R01(a002)+R02(a002) | 匹é…(第2ä»¶) | +| 4 | R01(a002)+R02(a002) | 匹é…(第3ä»¶) | +| 5 | R01(a003)+R02(a003) | åŒ¹é… | + +### 组3:R02-å…ˆç»“æŸ + +R01: a001→a002→a003 +R02: a002→a002 + +records:全部R01-only + +### 组4:R01-å…ˆç»“æŸ + +R01: a002→a003 +R02: a001→a002→a003→a004 + +records:R01(a001)→R02(b001~b004)→R01(a002) + +## 输出 + +æ¯ç»„一个json文件:`{プログラムID}_g{groupId}.json` diff --git a/rules/pgm_pattern/マッãƒãƒ³ã‚°(N-1).md b/rules/pgm_pattern/マッãƒãƒ³ã‚°(N-1).md new file mode 100644 index 0000000..7033de7 --- /dev/null +++ b/rules/pgm_pattern/マッãƒãƒ³ã‚°(N-1).md @@ -0,0 +1,36 @@ +# マッãƒãƒ³ã‚°(N:1) データ生æˆè¦å‰‡ + +## æ¦‚è¦ + +マッãƒãƒ³ã‚°(1:1)ã®é€†ã€‚R01ãŒä¸»é§†å‹•ã§R02ãŒå¾“å´ã€‚1:Nã®é€†æ§‹æˆã€‚ + +## 生æˆãƒ‡ãƒ¼ã‚¿çµ„ + +| 组 | 用途 | R01 | R02 | 説明 | +|----|------|-----|-----|------| +| 1 | 両端ä¸ä¸€è‡´ | 4 | 4 | 1ä»¶ç›®R02-onlyã€æœ€çµ‚R01-onlyã€ä¸­é–“3件マッム+ 末尾多keyテスト | +| 2 | 逆方å‘両端ä¸ä¸€è‡´ | 4 | 4 | 1ä»¶ç›®R01-onlyã€æœ€çµ‚R02-onlyã€ä¸­é–“3件マッム+ 末尾多keyテスト | +| 3 | 中間ä¸ä¸€è‡´ | 5 | 5 | 先頭末尾マッãƒã€ä¸­é–“ã«R01-onlyã¨R02-onlyå„1ä»¶ + 末尾多keyテスト | + +## 组1:両端ä¸ä¸€è‡´ +R01: a002→a003→a004→a005(R01-only) +R02: a001(R02-only)→a002→a003→a004 + +## 组2ï¼šé€†æ–¹å‘ +R01: a001(R01-only)→a002→a003→a004 +R02: a002→a003→a004→a005(R02-only) + +## 组3:中間ä¸ä¸€è‡´ +R01: a001→a003(R01-only)→a004→a005 +R02: a001→a002(R02-only)→a004→a005 + +## 多keyテスト + +キーãŒè¤‡æ•°é …ç›®ã®å ´åˆã€ç»„1・组2ã®æœ«å°¾ã«è¿½åŠ ã€‚ +组1: R01-onlyã®æœ€çµ‚R01データを基準ã«ã€å„キー項目ã®ã¿ç•°ãªã‚‹Nä»¶ã‚’R02å´ã«è¿½åŠ  +组2: R02-onlyã®æœ€çµ‚R02データを基準ã«ã€å„キー項目ã®ã¿ç•°ãªã‚‹Nä»¶ã‚’R01å´ã«è¿½åŠ  +ã“れã«ã‚ˆã‚Šãƒžãƒƒãƒãƒ³ã‚°ã‚­ãƒ¼ã®æ¯”較ãŒå…¨é …目正ã—ã行ã‚れã¦ã„ã‚‹ã“ã¨ã‚’検証。 + +## 出力 + +å„組1 JSONファイル。 diff --git a/rules/pgm_pattern/レイアウト編集ã®ã¿(GETPUT).md b/rules/pgm_pattern/レイアウト編集ã®ã¿(GETPUT).md new file mode 100644 index 0000000..794c268 --- /dev/null +++ b/rules/pgm_pattern/レイアウト編集ã®ã¿(GETPUT).md @@ -0,0 +1,32 @@ +# レイアウト編集ã®ã¿(GETPUT) データ生æˆè¦å‰‡ + +## æ¦‚è¦ + +入力ファイルã¾ãŸã¯DBã‹ã‚‰ãƒ¬ã‚³ãƒ¼ãƒ‰ã‚’読ã¿å–りã€é …ç›®ã®ç·¨é›†ãƒ»å¤‰æ›ã‚’行ã£ã¦ãã®ã¾ã¾å‡ºåŠ›ãƒ•ã‚¡ã‚¤ãƒ«ã«æ›¸ã出ã™ãƒ—ログラムã®ãƒ†ã‚¹ãƒˆãƒ‡ãƒ¼ã‚¿ã‚’生æˆã™ã‚‹ã€‚ + +処ç†ãƒ‘ターン:READ → 編集/計算/å¤‰æ› â†’ WRITE(シンプルãªãƒ‘イプライン) + +## 入力 + +- 詳細設計書ã®å‡¦ç†è©³ç´°ï¼ˆç·¨é›†ãƒ»å¤‰æ›å†…容) +- 入力ファイルã®COPYBOOKフィールド定義 +- 出力レコード定義(出力項目ã¨è¨­å®šå…ƒã®å¯¾å¿œï¼‰ + +## 生æˆãƒ‡ãƒ¼ã‚¿çµ„ + +| 组 | 用途 | 内容 | +|----|------|------| +| 1 | 基本データ | 通常パターン2〜5件。å„ãƒ•ã‚£ãƒ¼ãƒ«ãƒ‰ã«æœ‰åйãªå€¤ã‚’設定。 | + +## ãƒ‡ãƒ¼ã‚¿ç”Ÿæˆæ‰‹é † + +1. **å…¥åŠ›ãƒ•ã‚£ãƒ¼ãƒ«ãƒ‰ã®æŠŠæ¡**: COPYBOOKã‹ã‚‰å…¨å…¥åŠ›ãƒ•ã‚£ãƒ¼ãƒ«ãƒ‰ã‚’å–å¾— +2. **データ生æˆ**: + - 通常値: å„フィールドã®PIC定義ã«å¾“ã„æœ‰åйãªå€¤ã‚’ç”Ÿæˆ + - ç•°ãªã‚‹å€¤: レコード間ã§åŒã˜é …ç›®ã«ç•°ãªã‚‹å€¤ã‚’設定ã—ãƒãƒªã‚¨ãƒ¼ã‚·ãƒ§ãƒ³ã‚’ç¢ºä¿ +3. **値ã®ãƒ«ãƒ¼ãƒ«**: + - PIC X(n): 左詰ã‚・スペース埋ã‚ã€å®Ÿåœ¨ã—ãã†ãªæ–‡å­—列 + - PIC 9(n): å³è©°ã‚・先行ゼロã€å®Ÿåœ¨ã—ãã†ãªæ•°å€¤ + - DATE/日付系: 実在ã—ãã†ãªæ—¥ä»˜ + - 金é¡/SALARYç³»: ç¾å®Ÿçš„ãªé‡‘é¡ +4. **出力**: 1組 = 1 JSONファイル diff --git a/rules/pgm_pattern/振り分ã‘.md b/rules/pgm_pattern/振り分ã‘.md new file mode 100644 index 0000000..361be83 --- /dev/null +++ b/rules/pgm_pattern/振り分ã‘.md @@ -0,0 +1,54 @@ +# 振り分㑠データ生æˆè¦å‰‡ + +## æ¦‚è¦ + +å˜ä¸€ã®å…¥åŠ›ãƒ•ã‚¡ã‚¤ãƒ«ï¼ˆCSVã¾ãŸã¯å›ºå®šé•·ï¼‰ã‚’読ã¿å–ã‚Šã€æ¡ä»¶åˆ†å²ã«åŸºã¥ã„ã¦è¤‡æ•°ã®å‡ºåŠ›ãƒ•ã‚¡ã‚¤ãƒ«ã«æŒ¯ã‚Šåˆ†ã‘るプログラムã®ãƒ†ã‚¹ãƒˆãƒ‡ãƒ¼ã‚¿ã‚’生æˆã™ã‚‹ã€‚ + +## 入力 + +- 詳細設計書ã®å‡¦ç†è©³ç´°ï¼ˆåˆ†å²æ¡ä»¶ã¨å‡ºåŠ›å…ˆã®å¯¾å¿œï¼‰ +- 入力ファイルã®COPYBOOKフィールド定義 + +## 生æˆãƒ‡ãƒ¼ã‚¿çµ„ + +| 组 | 用途 | 内容 | +|----|------|------| +| 1 | 全分支カãƒãƒ¼ | ã™ã¹ã¦ã®åˆ†å²æ¡ä»¶ã‚’ã‚«ãƒãƒ¼ã™ã‚‹ãƒ‡ãƒ¼ã‚¿ã€‚å„å‡ºåŠ›å…ˆã«æœ€ä½Ž1ä»¶ãšã¤æŒ¯ã‚Šåˆ†ã‘られるよã†ã«ã™ã‚‹ã€‚ | + +## ãƒ‡ãƒ¼ã‚¿ç”Ÿæˆæ‰‹é † + +1. **åˆ†å²æ¡ä»¶ã®ç‰¹å®š**: 処ç†è©³ç´°ã‹ã‚‰ã™ã¹ã¦ã®åˆ†å²æ¡ä»¶ã¨ãれã«å¯¾å¿œã™ã‚‹å‡ºåŠ›å…ˆã‚’æŠ½å‡ºã™ã‚‹ +2. **分å²ãƒ‘ターンã®åˆ—挙**: 正常系(検証通éŽâ†’正常出力)ã¨ç•°å¸¸ç³»ï¼ˆã‚¨ãƒ©ãƒ¼â†’ERROR-LOG)ã®å…¨ãƒ‘ターンを列挙 +3. **データ生æˆ**: + - å„分å²ãƒ‘ã‚¿ãƒ¼ãƒ³ã«æœ€ä½Ž1ä»¶ã®ãƒ¬ã‚³ãƒ¼ãƒ‰ã‚’ç”Ÿæˆ + - フィールド値ã¯PIC定義ã«å¾“ã„ç”Ÿæˆ + - 正常系データ: å…¨ãƒã‚§ãƒƒã‚¯ã‚’通éŽã™ã‚‹æœ‰åйãªå€¤ + - 異常系データ: 該当ã™ã‚‹ãƒã‚§ãƒƒã‚¯ã«å¼•ã£ã‹ã‹ã‚‹å€¤ï¼ˆä¸æ­£ãªæ—¥ä»˜ã€ä¸æ­£ãªæ™‚刻ã€ç¯„囲外ã®å€¤ãªã©ï¼‰ +4. **値ã®ãƒ«ãƒ¼ãƒ«**: + - PIC X(n): 左詰ã‚・スペース埋゠+ - PIC 9(n): å³è©°ã‚・先行ゼロ + - DATE/日付系: 実在ã—ãã†ãªæ—¥ä»˜ï¼ˆYYYYMMDDå½¢å¼ï¼‰ã€ç•°å¸¸ç³»ã§ã¯ä¸æ­£ãªæ—¥ä»˜ï¼ˆ99999999等) + - TIME/時刻系: 実在ã—ãã†ãªæ™‚刻(HHMMå½¢å¼ï¼‰ã€ç•°å¸¸ç³»ã§ã¯ç¯„å›²å¤–ã®æ™‚刻 + - STATUSç³»: 0/1/9 ãªã©ã®ã‚³ãƒ¼ãƒ‰å€¤ + - CSV項番å·ãƒ•ィールド: 分割後ã®é …目数 +5. **出力**: 1組 = 1 JSONファイルã«å…¨ãƒ¬ã‚³ãƒ¼ãƒ‰ã‚’æ ¼ç´ + +## å‡ºåŠ›å½¢å¼ + +```json +{ + "program": "ZAN01CHK", + "records": [ + { + "input": { + "DDå": { + "フィールドå": "値", + ... + } + } + } + ] +} +``` + +å„レコードãŒ1回ã®å‡¦ç†ã«å¯¾å¿œã€‚正常系→異常系→特殊分å²ã®é †ã«ä¸¦ã¹ã‚‹ã€‚ diff --git a/rules/pgm_pattern/é …ç›®ãƒã‚§ãƒƒã‚¯.md b/rules/pgm_pattern/é …ç›®ãƒã‚§ãƒƒã‚¯.md new file mode 100644 index 0000000..fdb553f --- /dev/null +++ b/rules/pgm_pattern/é …ç›®ãƒã‚§ãƒƒã‚¯.md @@ -0,0 +1,31 @@ +# é …ç›®ãƒã‚§ãƒƒã‚¯ データ生æˆè¦å‰‡ + +## æ¦‚è¦ + +入力ファイル(CSV)ã®å„レコードã«å¯¾ã—ã€è¤‡æ•°ã®é …ç›®ãƒã‚§ãƒƒã‚¯ã‚’実施ã—ã€æ­£å¸¸ãƒ‡ãƒ¼ã‚¿ã¨ç•°å¸¸ãƒ‡ãƒ¼ã‚¿ã‚’別々ã®å‡ºåŠ›ãƒ•ã‚¡ã‚¤ãƒ«ã«æŒ¯ã‚Šåˆ†ã‘るプログラムã®ãƒ†ã‚¹ãƒˆãƒ‡ãƒ¼ã‚¿ã‚’生æˆã™ã‚‹ã€‚ + +## 入力 + +- 詳細設計書ã®å‡¦ç†è©³ç´°ï¼ˆãƒã‚§ãƒƒã‚¯é …目一覧) +- 入力ファイルã®COPYBOOKフィールド定義 + +## 生æˆãƒ‡ãƒ¼ã‚¿çµ„ + +| 组 | 用途 | 内容 | +|----|------|------| +| 1 | å…¨ãƒã‚§ãƒƒã‚¯ã‚«ãƒãƒ¼ | ã™ã¹ã¦ã®ãƒã‚§ãƒƒã‚¯é …ç›®ã«ã¤ã„ã¦ã€åˆæ ¼ãƒ‡ãƒ¼ã‚¿ã¨ä¸åˆæ ¼ãƒ‡ãƒ¼ã‚¿ã®ä¸¡æ–¹ã‚’ã‚«ãƒãƒ¼ã™ã‚‹ã€‚ | + +## ãƒ‡ãƒ¼ã‚¿ç”Ÿæˆæ‰‹é † + +1. **ãƒã‚§ãƒƒã‚¯é …ç›®ã®ç‰¹å®š**: 処ç†è©³ç´°ã‹ã‚‰ã™ã¹ã¦ã®ãƒã‚§ãƒƒã‚¯é …目(フィールド数ã€å€¤åŸŸã€é‡è¤‡ã€æ™‚刻範囲ã€ã‚³ãƒ¼ãƒ‰å€¤ãªã©ï¼‰ã‚’抽出ã™ã‚‹ +2. **å„ãƒã‚§ãƒƒã‚¯ã«å¯¾ã™ã‚‹ãƒ‡ãƒ¼ã‚¿ç”Ÿæˆ**: + - åˆæ ¼ãƒ‡ãƒ¼ã‚¿: å…¨ãƒã‚§ãƒƒã‚¯ã‚’通éŽã™ã‚‹æœ‰åйãªå€¤ + - ä¸åˆæ ¼ãƒ‡ãƒ¼ã‚¿: å„ãƒã‚§ãƒƒã‚¯é …ç›®ã«ç‰¹åŒ–ã—ãŸä¸æ­£ãªå€¤ã‚’å«ã‚€ãƒ¬ã‚³ãƒ¼ãƒ‰ +3. **値ã®ãƒ«ãƒ¼ãƒ«**: + - フィールド数ãƒã‚§ãƒƒã‚¯: æ­£ã—ã„項目数ï¼è¶³ã‚Šãªã„ï¼å¤šã„ + - 日付ãƒã‚§ãƒƒã‚¯: 正常日付 vs 䏿­£æ—¥ä»˜ (99999999ç­‰) + - 時刻ãƒã‚§ãƒƒã‚¯: 正常時刻 (0900-1800) vs 範囲外 (2500ç­‰) + - é‡è¤‡ãƒã‚§ãƒƒã‚¯: åŒä¸€ã‚­ãƒ¼2ä»¶ vs 全件別キー + - コード値ãƒã‚§ãƒƒã‚¯: 有効コード vs 無効コード + - 社員番å·: 8æ¡æ•°å­— vs éžæ•°å­—ï¼æ¡æ•°ä¸è¶³ +4. **出力**: 1組 = 1 JSONファイル diff --git a/rules/special_feature/æ¡ä»¶åˆ†æ”¯.md b/rules/special_feature/æ¡ä»¶åˆ†æ”¯.md new file mode 100644 index 0000000..82a21b0 --- /dev/null +++ b/rules/special_feature/æ¡ä»¶åˆ†æ”¯.md @@ -0,0 +1,27 @@ +# æ¡ä»¶åˆ†æ”¯ æ•°æ®ç”Ÿæˆè§„则 + +## æ¦‚è¦ + +检索详细设计中処ç†è©³ç´°çš„æ‰€æœ‰æ¡ä»¶åˆ†æ”¯ï¼Œå¢žåŠ è¦†ç›–æ‰€æœ‰åˆ†æ”¯çš„è¾“å…¥æ•°æ®ã€‚ + +## 输入 + +- 输入文件:详细设计书的処ç†è©³ç´°éƒ¨åˆ† + +## 生æˆçš„æ•°æ®ç»„ + +由你决定,以尽å¯èƒ½å°‘的数æ®ç»„完æˆåˆ†æ”¯è¦†ç›– + + +## æ•°æ®ç”Ÿæˆæ­¥éª¤ + +1. é历処ç†è©³ç´°çš„æ‰€æœ‰å†…容 +2. 记录所有æ¡ä»¶åˆ†æ”¯ +3. 找到æ¡ä»¶åˆ¤æ–­æ•°æ®ä¸Žè¾“入的关系 +4. æ•°æ®ç”Ÿæˆï¼šæ ¹æ®å­—段å关键è¯ç”Ÿæˆç¬¦åˆå®žé™…的数æ®ï¼ŒåŒæ—¶å°½é‡ä¿è¯æ¯æ¡ç›¸é‚»çš„æ•°æ®çš„相åŒé¡¹ç›®ä¸ä¸€è‡´ã€‚ +5. 按å„组规则组装records数组 +6. 组å·ä»Žä¹‹å‰çš„æœ€åŽä¸€ç»„+1å¼€å§‹è¿žç»­ç¼–å· + +## 输出 + +æ¯ä¸ªç»„输出一个json文件:`{プログラムID}_g{groupId}.json` diff --git a/runners/gixsql_runner.py b/runners/gixsql_runner.py index c610b7d..5f0cb8a 100644 --- a/runners/gixsql_runner.py +++ b/runners/gixsql_runner.py @@ -310,22 +310,13 @@ class GixsqlCobolRunner: logger.info(f" cobc: {' '.join(cmd)}") try: - r = subprocess.run(cmd, capture_output=True, timeout=60, env=self._build_env()) + r = subprocess.run(cmd, capture_output=True, timeout=60, env=self._build_env(), + cwd=str(exe_dir)) log = (r.stdout.decode("utf-8", "replace") + "\n" + r.stderr.decode("utf-8", "replace")) if r.returncode != 0: return GixsqlBuildResult(False, log=log[:1000]) - # After successful compile, copy .gcno from CWD to exe_dir - pp_stem = pp_path.stem # e.g. "KIN02UPD_pp" - exe_stem = exe_path.stem # e.g. "KIN02UPD" - copied = 0 - for gcno in Path.cwd().glob("*.gcno"): - # Match .gcno files belonging to this compile (by subprogram name or pp_stem partial match) - dst = exe_dir / gcno.name - dst.write_bytes(gcno.read_bytes()) - copied += 1 - if copied: - logger.debug(f" gcno copied: {copied} files to {exe_dir}") + # .gcno files are generated in exe_dir (compile CWD). No need to copy. return GixsqlBuildResult(True, exe_path=str(exe_path), log=log[:500]) except subprocess.TimeoutExpired: return GixsqlBuildResult(False, log="Compile timeout (60s)") @@ -334,7 +325,8 @@ class GixsqlCobolRunner: db_path: str | Path, input_dir: str | Path | None = None, timeout: int = 30, - cobol_lib_path: str | Path | None = None) -> GixsqlRunResult: + cobol_lib_path: str | Path | None = None, + env_overrides: dict[str, str] | None = None) -> GixsqlRunResult: """Step 3: COBOL DB プログラム実行""" exe_path = Path(exe_path) work_dir = Path(work_dir) @@ -379,6 +371,8 @@ class GixsqlCobolRunner: env["GIXSQL_DB_PATH"] = str(db_path) if cobol_lib_path: env["COB_LIBRARY_PATH"] = str(cobol_lib_path) + if env_overrides: + env.update(env_overrides) if input_dir: idir = Path(input_dir) @@ -386,8 +380,7 @@ class GixsqlCobolRunner: for f in idir.iterdir(): if f.is_file(): dst = work_dir / f.name - if not dst.exists(): - dst.write_bytes(f.read_bytes()) + dst.write_bytes(f.read_bytes()) cmd = [str(exe_path)] logger.info(f" run: {' '.join(cmd)} (cwd={work_dir}, db={db_path})")