黑盒,白盒处理合并

This commit is contained in:
2026-08-24 18:34:17 +08:00
parent 5a968b4e37
commit cbaf3deb36
75 changed files with 10020 additions and 107 deletions
+229
View File
@@ -0,0 +1,229 @@
import os
import re
from typing import List, Tuple, Optional
from agent.models import ProgramMeta
PGM_PATTERN_MAP = {
'マッチング(1:1)': 'マッチング(1-1).md',
'マッチング(11': 'マッチング(1-1).md',
'マッチング(1:N)': 'マッチング(1-N).md',
'マッチング(1N': 'マッチング(1-N).md',
'マッチング(1:N) + EVALUATE 4パターン分岐': 'マッチング(1-N).md',
'マッチング(N:1)': 'マッチング(N-1).md',
'マッチング(N1': 'マッチング(N-1).md',
'マッチング(M:N)': 'マッチング(M-N).md',
'マッチングMN⇒出力M件': 'マッチング(M-N).md',
'マッチングMN⇒出力N件': 'マッチング(M-N).md',
'マッチングMN⇒出力M×N件': 'マッチング(M-N).md',
'18M:N→M件マッチング)': 'マッチング(M-N).md',
'19M:N→N件マッチング+集計)': 'マッチング(M-N).md',
'20M:N→M×N件直積出力)': 'マッチング(M-N).md',
'レイアウト編集のみ(GETPUT)': 'レイアウト編集のみ(GETPUT).md',
'レイアウト編集のみ(GETPUT': 'レイアウト編集のみ(GETPUT).md',
'レイアウト編集のみ': 'レイアウト編集のみ(GETPUT).md',
'GETPUT(編集出力)': 'GETPUT(編集出力).md',
'項目チェック': '項目チェック(重複含まず).md',
'項目チェック(重複なし)': '項目チェック(重複含まず).md',
'項目チェック(重複なし)': '項目チェック(重複含まず).md',
'項目チェック(重複含まず)': '項目チェック(重複含まず).md',
'項目チェック(重複含む)': '項目チェック(重複含まず).md',
'項目チェック(半角20桁/4桁)': '項目チェック(半角20桁-4桁).md',
'27(半角20桁/4桁チェック)': '項目チェック(半角20桁-4桁).md',
'半角20桁/4桁': '項目チェック(半角20桁-4桁).md',
'振り分け': '振り分け(IF).md',
'振り分け(IF文)': '振り分け(IF).md',
'振り分け(IF文、EVALUATE文)': '振り分け(IF).md',
'振り分け(EVALUATE文)': '振り分け(EVALUATE).md',
'振り分け(EVALUATE文)': '振り分け(EVALUATE).md',
'キーブレイク': 'キーブレイク(集計).md',
'キーブレイク(集計)': 'キーブレイク(集計).md',
'キーブレイク(集計)': 'キーブレイク(集計).md',
'キーブレイク(集約)': 'キーブレイク(集約).md',
'キーブレイク(集約)': 'キーブレイク(集約).md',
'キーブレイク(集計、集約)': 'キーブレイク(集計).md',
'キーブレイク(集計、集約の以外)': 'キーブレイク(非集計).md',
'キーブレイク(集計集約以外)': 'キーブレイク(非集計).md',
'キーブレイク(集計集約以外)': 'キーブレイク(非集計).md',
'キーブレイク(非集計)': 'キーブレイク(非集計).md',
'1:Nキーブレイク(同キー集約)': 'キーブレイク(集計).md',
'1:N+キーブレイク(同キー)': 'キーブレイク(集計).md',
'1:N+同キーキーブレイク': 'キーブレイク(集計).md',
'321:N+同キーキーブレイク)': 'キーブレイク(集計).md',
'1:N+同キー': 'キーブレイク(集計).md',
'1:N+キーブレイク(異キー)': '1-N+キーブレイク(異キー).md',
'1:N+キーブレイク(異キー)': '1-N+キーブレイク(異キー).md',
'1:N+異キーキーブレイク': '1-N+キーブレイク(異キー).md',
'331:N+異キーキーブレイク)': '1-N+キーブレイク(異キー).md',
'1:N+異キー': '1-N+キーブレイク(異キー).md',
'DB更新': 'DB更新.md',
'DB更新 + SYSIN読込(P28)': 'DB更新.md',
'SELECT処理': 'SELECT処理.md',
'SELECT条件': 'SELECT処理.md',
'SELECT条件': 'SELECT処理.md',
'DB検索': 'SELECT処理.md',
'DB検索': 'SELECT処理.md',
'50分割': '50分割.md',
'50分割': '50分割.md',
'25分割': '25分割.md',
'25分割': '25分割.md',
'1125分割)': '25分割.md',
'100分割': '100分割.md',
'100分割': '100分割.md',
'12100分割)': '100分割.md',
'MERGE(複数ファイル結合)': 'MERGE.md',
'MERGE(複数ファイル結合)': 'MERGE.md',
'CSV→FB変換(改行あり)': 'CSV→FB変換.md',
'CSV→FB変換(改行あり)': 'CSV→FB変換.md',
'CSV→FB変換(改行なし)': 'CSV→FB変換.md',
'CSV→FB変換(改行なし)': 'CSV→FB変換.md',
'2段階マッチング(1:1⇒1:1': '2段階マッチング(1:1⇒1:1).md',
'2段階マッチング(11⇒11': '2段階マッチング(1:1⇒1:1).md',
'2段階マッチング(N:1⇒N:1': '2段階マッチング(N:1⇒N:1).md',
'2段階マッチング(N1⇒N1': '2段階マッチング(N:1⇒N:1).md',
'2段階マッチング(M:N⇒M:N': '2段階マッチング(M:N⇒M:N).md',
'2段階マッチング(MN⇒MN': '2段階マッチング(M:N⇒M:N).md',
'マッチング(1:1→1:1 2段階)': '2段階マッチング(1:1⇒1:1).md',
'マッチング(N:1→N:1 2段階)': '2段階マッチング(N:1⇒N:1).md',
'マッチング(M:N→M:N 2段階)': '2段階マッチング(M:N⇒M:N).md',
'内部テーブル検索': '内部テーブル検索.md',
'24(内部テーブル検索)': '内部テーブル検索.md',
'サブプログラム使用': 'サブプログラム.md',
'サブプログラム': 'サブプログラム.md',
'サブPGM': 'サブプログラム.md',
'25(サブPGM': 'サブプログラム.md',
'SYSIN読込': 'SYSIN読込.md',
'SYSIN読込': 'SYSIN読込.md',
'28SYSIN読込)': 'SYSIN読込.md',
'ASCII→EBCDIC変換': 'ASCII→EBCDIC変換.md',
'ASCII→EBCDIC変換': 'ASCII→EBCDIC変換.md',
'29ASCII→EBCDIC変換)': 'ASCII→EBCDIC変換.md',
'SORTINPUT/OUTPUT PROCEDURE': 'SORT.md',
'34SORT INPUT/OUTPUT PROCEDURE': 'SORT.md',
'SORT': 'SORT.md',
'オンラインPGM': 'オンラインPGM.md',
'オンラインPGM': 'オンラインPGM.md',
'オンライン': 'オンラインPGM.md',
'編集出力(ランキング生成)': 'ランキング生成.md',
'36(ランキング生成)': 'ランキング生成.md',
'ランキング生成': 'ランキング生成.md',
'編集出力(印刷制御)': 'GETPUT(編集出力).md',
'編集出力': 'GETPUT(編集出力).md',
}
SPECIAL_FEATURE_CHECKS = [
(['場合', 'EVALUATE', 'IF'], '条件分支.md'),
]
class RuleLoader:
"""根据程序特征加载对应的数据生成规则。"""
def __init__(self, rules_dir: str):
self.pgm_pattern_dir = os.path.join(rules_dir, 'pgm_pattern')
self.special_feature_dir = os.path.join(rules_dir, 'special_feature')
self.last_rule_file = None
def load(self, meta: ProgramMeta) -> Tuple[str, List[str], int]:
"""加载所有相关规则,返回 (合并后的规则文本, 组描述列表, 组数)。"""
pgm_rule = self._load_pgm_pattern_rule(meta.pgm_pattern)
if pgm_rule is None:
raise FileNotFoundError(
f"PGM模式 '{meta.pgm_pattern}' の規則ファイルが見つかりません。"
f"{self.pgm_pattern_dir} に .md ファイルを追加してください。"
)
group_descriptions, group_count = self._parse_group_info(pgm_rule)
parts = [pgm_rule]
for keywords, rule_file in SPECIAL_FEATURE_CHECKS:
if self._detect_feature(meta.process_detail, keywords):
feature_rule = self._read_rule_file(
os.path.join(self.special_feature_dir, rule_file)
)
if feature_rule:
parts.append(feature_rule)
combined = '\n\n---\n\n'.join(parts)
return combined, group_descriptions, group_count
def _load_pgm_pattern_rule(self, pgm_pattern: str) -> Optional[str]:
"""根据 PGMパターン 加载对应的规则文件。"""
filename = PGM_PATTERN_MAP.get(pgm_pattern)
if filename:
path = os.path.join(self.pgm_pattern_dir, filename)
if os.path.exists(path):
self.last_rule_file = filename
return self._read_rule_file(path)
if os.path.isdir(self.pgm_pattern_dir):
available = sorted(os.listdir(self.pgm_pattern_dir))
# 对每个规则文件名(去掉.md),检查它是否出现在 PGMパターン 中
# 半角/全角括弧に加え、'1:N' vs '1-N' のコロン/ハイフン差異も吸収する
pgm_lower = pgm_pattern.lower().replace(':', '-')
for fname in available:
if fname.endswith('.md'):
rule_name = fname[:-3].lower()
if rule_name in pgm_lower:
self.last_rule_file = fname
return self._read_rule_file(os.path.join(self.pgm_pattern_dir, fname))
return None
@staticmethod
def _read_rule_file(path: str) -> str:
with open(path, 'r', encoding='utf-8') as f:
return f.read()
@staticmethod
def _parse_group_info(rule_text: str) -> Tuple[List[str], int]:
"""从规则文本中解析组信息。
只统计表头首列为""的表格(生成的数据组),
避免把记录明细表中的数字(如 record序号 / 第1条)误算成组号。
"""
descriptions = []
group_count = 0
in_group_table = False
for line in rule_text.split('\n'):
stripped = line.strip()
if not stripped.startswith('|'):
in_group_table = False
continue
# 分隔行(|---|)跳过
if re.match(r'^\|[\s\-:]+\|', stripped):
continue
cells = [c.strip() for c in stripped.split('|')[1:-1]]
if not cells:
continue
# 表头行:首列为"组"时进入组表格
if cells[0] == '':
in_group_table = True
continue
if not in_group_table:
continue
if len(cells) >= 2:
try:
group_num = int(cells[0])
if group_num > group_count:
group_count = group_num
descriptions.append(cells[1])
except ValueError:
pass
return descriptions, group_count
@staticmethod
def _detect_feature(process_detail: str, keywords: List[str]) -> bool:
"""检测処理詳細中是否包含特定关键词。"""
for kw in keywords:
if kw in process_detail:
return True
return False