Files
cobol-java-v3/black-box-data-create/agent/rule_loader.py
T
2026-08-24 18:34:17 +08:00

230 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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段階マッチング(11⇒11.md',
'2段階マッチング(11⇒11)': '2段階マッチング(11⇒11.md',
'2段階マッチング(N:1⇒N:1': '2段階マッチング(N1⇒N1.md',
'2段階マッチング(N1⇒N1)': '2段階マッチング(N1⇒N1.md',
'2段階マッチング(M:N⇒M:N': '2段階マッチング(MN⇒MN.md',
'2段階マッチング(MN⇒MN)': '2段階マッチング(MN⇒MN.md',
'マッチング(1:1→1:1 2段階)': '2段階マッチング(11⇒11.md',
'マッチング(N:1→N:1 2段階)': '2段階マッチング(N1⇒N1.md',
'マッチング(M:N→M:N 2段階)': '2段階マッチング(MN⇒MN.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