feat: phase2 review fixes
- TIME injection: change from spaces to '2500' (hour>23) for NUMVAL trigger - Runner: add .resolve() to work_dir to fix chdir+relative path breakage - Coverage: per-target field overrides for DP#9-#12 (START-DATE=20240115 etc.) - .gitignore: add compilation artifacts, temp scripts, test outputs
This commit is contained in:
+138
-58
@@ -1050,17 +1050,23 @@ def _dec_str(s, length):
|
||||
|
||||
def _reconcile_unstring_fields(rec, left_field, operator, right_field, want_true,
|
||||
fields, left_chain, assignments, path_assign):
|
||||
right_root, right_chain = trace_to_root(right_field, assignments, fields, path_assign)
|
||||
right_root, right_chain = trace_to_root(right_field, assignments, fields, path_assign=None)
|
||||
if right_root not in rec:
|
||||
logger.debug(f"字段间比较协调:右侧根 {right_root} 不在 rec,跳过")
|
||||
return
|
||||
all_entries = (left_chain or []) + (right_chain or [])
|
||||
for _, asgn in all_entries:
|
||||
if asgn.get('type') not in ('move', 'unstring_split'):
|
||||
if asgn.get('type') not in ('move', 'unstring_split', 'move_literal'):
|
||||
logger.debug(f"字段间比较协调:链含非 MOVE 类型 {asgn.get('type')},跳过")
|
||||
return
|
||||
left_val = str(rec.get(left_field, ''))
|
||||
if not left_val.strip():
|
||||
# 左字段无值时尝试反向:用右字段的值填充左字段
|
||||
right_root_val = str(rec.get(right_root, ''))
|
||||
if right_root_val.strip() and operator in ('=', '==') and want_true:
|
||||
rec[left_field] = right_root_val
|
||||
logger.debug(f"字段间比较协调(反向):{left_field}<={right_root}={right_root_val}")
|
||||
return
|
||||
logger.debug(f"字段间比较协调:左侧 {left_field} 无值,跳过")
|
||||
return
|
||||
length = 0
|
||||
@@ -1388,14 +1394,16 @@ def _enum_search_paths(node, fields):
|
||||
base = re.sub(r'\s*\(.*?\)\s*$', '', cond_tree.field)
|
||||
matching_val = cond_tree.value
|
||||
elem_key = f'{base}({i + 1})'
|
||||
subj = cond_tree.value
|
||||
subj_is_field = any(f['name'] == subj for f in fields)
|
||||
# 确保 match 值与字段 PIC 类型兼容
|
||||
_fmt = next((f.get('pic_info', {}).get('type') for f in fields if f['name'] == elem_key), None)
|
||||
if _fmt in ('alphanumeric', 'alphabetic'):
|
||||
matching_val = str(matching_val).ljust(
|
||||
next((f['pic_info'].get('length', 1) for f in fields if f['name'] == elem_key and f.get('pic_info')), 1)
|
||||
)[:next((f['pic_info'].get('length', 1) for f in fields if f['name'] == elem_key and f.get('pic_info')), 1)]
|
||||
if any(f['name'] == matching_val for f in fields):
|
||||
extra_assign[elem_key] = [{'type': 'move', 'source_vars': [matching_val]}]
|
||||
if subj_is_field:
|
||||
extra_assign[elem_key] = [{'type': 'move', 'source_vars': [subj]}]
|
||||
else:
|
||||
extra_assign[elem_key] = [{'type': 'move_literal', 'literal': matching_val}]
|
||||
non_match = _non_match_for(cond_tree, fields) or ' '
|
||||
@@ -1408,11 +1416,15 @@ def _enum_search_paths(node, fields):
|
||||
for k, v in sp_assign.items():
|
||||
merged_assign.setdefault(k, []).extend(v if isinstance(v, list) else [v])
|
||||
if cond_tree and isinstance(cond_tree, CondLeaf):
|
||||
# Also set the subject field (right side of comparison) to match
|
||||
subj = cond_tree.value
|
||||
if any(f['name'] == subj for f in fields):
|
||||
subj_is_field = any(f['name'] == subj for f in fields)
|
||||
if subj_is_field:
|
||||
# Field-to-field: constrain subject to reference table element value
|
||||
paths.append(([(subj, cond_tree.op, elem_key, True)] + sp_cons, merged_assign))
|
||||
else:
|
||||
# Literal value: set subject field to literal, constrain table element
|
||||
merged_assign[subj] = [{'type': 'move_literal', 'literal': matching_val}]
|
||||
paths.append(([(elem_key, cond_tree.op, matching_val.rstrip(), True)] + sp_cons, merged_assign))
|
||||
paths.append(([(elem_key, cond_tree.op, matching_val.rstrip(), True)] + sp_cons, merged_assign))
|
||||
else:
|
||||
paths.append((sp_cons, merged_assign))
|
||||
|
||||
@@ -1436,32 +1448,76 @@ def _enum_search_paths(node, fields):
|
||||
return paths
|
||||
|
||||
|
||||
def _rebuild_r01line_csv(rec, data_fields):
|
||||
"""直接基于 WRK-CSV 字段构建 CSV 字符串写入 rec['R01LINE']。
|
||||
按 PIC 长度截断各字段,避免 _reconstruct_unstring_sources 污染导致字段过长的 bug。
|
||||
def _rebuild_r01line_csv(rec, data_fields, base_assignments=None):
|
||||
"""基于 UNSTRING 目标字段动态构建 CSV 字符串写入源字段(如 R01LINE)。
|
||||
取代旧硬编码字段列表,支持任意程序的 UNSTRING 结构。
|
||||
按各目标字段的实际 PIC 长度 padding,确保运行时 UNSTRING 正确解析。
|
||||
"""
|
||||
csv_fields = [
|
||||
('WRK-CSV-APPL-ID', 8), ('WRK-CSV-EMP-ID', 8), ('WRK-CSV-APPL-DATE', 8),
|
||||
('WRK-CSV-START-TIME', 4), ('WRK-CSV-END-TIME', 4), ('WRK-CSV-STATUS', 1),
|
||||
('WRK-CSV-OVT-TYPE', 1), ('WRK-CSV-FILLER', 46),
|
||||
]
|
||||
parts = []
|
||||
for fname, flen in csv_fields:
|
||||
val = str(rec.get(fname, ''))
|
||||
if len(val) > flen:
|
||||
val = val[:flen]
|
||||
elif len(val) < flen:
|
||||
val = val.ljust(flen)
|
||||
parts.append(val)
|
||||
csv_value = ','.join(parts)
|
||||
r01_len = 80
|
||||
for f in data_fields:
|
||||
if f['name'] == 'R01LINE':
|
||||
pi = f.get('pic_info', {})
|
||||
r01_len = pi.get('length', 80) or 80
|
||||
break
|
||||
csv_value = csv_value.ljust(r01_len)[:r01_len]
|
||||
rec['R01LINE'] = csv_value
|
||||
if base_assignments is None:
|
||||
return
|
||||
|
||||
groups = {}
|
||||
for tgt, asgn_list in base_assignments.items():
|
||||
for asgn in asgn_list:
|
||||
if asgn.get('type') == 'unstring_split' and asgn.get('source_vars'):
|
||||
src = asgn['source_vars'][0]
|
||||
idx = asgn.get('index', 0)
|
||||
groups.setdefault(src, []).append((idx, tgt))
|
||||
|
||||
if not groups:
|
||||
return
|
||||
|
||||
for src_var, targets in groups.items():
|
||||
targets.sort(key=lambda x: x[0])
|
||||
|
||||
resolved_src = src_var
|
||||
for i, f in enumerate(data_fields):
|
||||
if f['name'] == resolved_src:
|
||||
grp_level = f.get('level', 0)
|
||||
for f2 in data_fields[i + 1:]:
|
||||
if f2.get('level', 0) <= grp_level or f2.get('level') == 77:
|
||||
break
|
||||
if f2.get('pic'):
|
||||
resolved_src = f2['name']
|
||||
break
|
||||
break
|
||||
|
||||
if resolved_src not in rec:
|
||||
continue
|
||||
|
||||
csv_parts = []
|
||||
for idx, tgt in targets:
|
||||
val = str(rec.get(tgt, ''))
|
||||
length = 0
|
||||
for f in data_fields:
|
||||
if f['name'] == tgt and f.get('pic_info'):
|
||||
pi = f['pic_info']
|
||||
ftype = pi.get('type', '')
|
||||
if ftype in ('alphanumeric', 'alphabetic'):
|
||||
length = pi.get('length', 0)
|
||||
elif ftype == 'numeric':
|
||||
length = pi.get('digits', 0) + pi.get('decimal', 0)
|
||||
break
|
||||
if length > 0:
|
||||
if len(val) > length:
|
||||
val = val[:length]
|
||||
elif len(val) < length:
|
||||
val = val.ljust(length)
|
||||
csv_parts.append(val)
|
||||
|
||||
csv_value = ','.join(csv_parts)
|
||||
|
||||
src_len = 0
|
||||
for f in data_fields:
|
||||
if f['name'] == resolved_src:
|
||||
pi = f.get('pic_info', {})
|
||||
if pi:
|
||||
src_len = pi.get('length', 0)
|
||||
break
|
||||
if src_len > 0:
|
||||
csv_value = csv_value.ljust(src_len)[:src_len]
|
||||
|
||||
rec[resolved_src] = csv_value
|
||||
|
||||
|
||||
def generate_records(path_infos, data_fields, base_assignments=None, file_sec=None):
|
||||
@@ -1476,7 +1532,6 @@ def generate_records(path_infos, data_fields, base_assignments=None, file_sec=No
|
||||
records = []
|
||||
kept_path_cons = []
|
||||
term_types = []
|
||||
_zan01_emp_err_count = 0
|
||||
if path_infos:
|
||||
for seq, (path_cons, path_assign, term_type) in enumerate(path_infos, start=1):
|
||||
path_cons = _filter_stop(path_cons)
|
||||
@@ -1595,29 +1650,54 @@ def generate_records(path_infos, data_fields, base_assignments=None, file_sec=No
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
break
|
||||
# Pass B.13: C01CHKRRC 约束与 SUB04CHK 输入字段同步
|
||||
# SUB04CHK 检查 C01CHKDAT(1:8) = SPACES → RC≠0。
|
||||
# 约束系统无法跨 CALL 追溯,需确保 WRK-CSV-EMP-ID/WRK-CSV-APPL-DATE
|
||||
# 与预期的 C01CHKRRC 值一致,使运行时实际 CALL 返回正确结果。
|
||||
# want=False(C01CHKRRC=0,通过)→ EMP-ID 和 APPL-DATE 都有效
|
||||
# want=True(C01CHKRRC≠0,错误)→ 交替:
|
||||
# 奇数个 → EMP-ID 空格 (#4-T)
|
||||
# 偶数个 → EMP-ID有效 + DATE空格 (#5-T)
|
||||
for c in path_cons:
|
||||
if len(c) == 4 and c[0] == 'C01CHKRRC' and c[1] == '<>' and c[2] == 'ZERO':
|
||||
if not c[3]:
|
||||
if 'WRK-CSV-EMP-ID' in rec and str(rec.get('WRK-CSV-EMP-ID', '')).strip() == '':
|
||||
rec['WRK-CSV-EMP-ID'] = '00000101'
|
||||
if 'WRK-CSV-APPL-DATE' in rec and str(rec.get('WRK-CSV-APPL-DATE', '')).strip() == '':
|
||||
rec['WRK-CSV-APPL-DATE'] = '20000101'
|
||||
else:
|
||||
_zan01_emp_err_count += 1
|
||||
if _zan01_emp_err_count % 2 == 0:
|
||||
if 'WRK-CSV-EMP-ID' in rec:
|
||||
rec['WRK-CSV-EMP-ID'] = '00000101'
|
||||
if 'WRK-CSV-APPL-DATE' in rec:
|
||||
rec['WRK-CSV-APPL-DATE'] = ' '
|
||||
break
|
||||
# Pass B.13: C01CHKRRC 约束 → 按 IF 位置映射对应字段
|
||||
# SUB04CHK 校验 W01* 字段(非空格/非零为有效)。
|
||||
# C01CHKRRC 约束按 2030VALIDATESOR 的 IF 出现顺序对应:
|
||||
# 0→EMP-ID, 1→START-DATE, 2→START-TIME, 3→END-DATE, 4→END-TIME
|
||||
# BrSeq frozenset 去重导致只有 3 种 path type 保留:
|
||||
# [True]、[False,True]、[False×5]
|
||||
_c01_wants = [
|
||||
c[3] for c in path_cons
|
||||
if len(c) == 4 and c[0] == 'C01CHKRRC' and c[1] == '<>' and c[2] == 'ZERO'
|
||||
]
|
||||
if _c01_wants:
|
||||
_c01_csv_fields = [
|
||||
'WRK-CSV-EMP-ID', 'WRK-CSV-START-DATE',
|
||||
'WRK-CSV-START-TIME', 'WRK-CSV-END-DATE', 'WRK-CSV-END-TIME',
|
||||
]
|
||||
_csv_to_w01 = {}
|
||||
if base_assignments:
|
||||
for tgt, alist in base_assignments.items():
|
||||
for a in alist:
|
||||
if a.get('type') == 'move' and a.get('source_vars'):
|
||||
_csv_to_w01.setdefault(a['source_vars'][0], tgt)
|
||||
for idx, want in enumerate(_c01_wants):
|
||||
if idx >= len(_c01_csv_fields):
|
||||
break
|
||||
csv_fld = _c01_csv_fields[idx]
|
||||
w01_fld = _csv_to_w01.get(csv_fld)
|
||||
for fname in (csv_fld, w01_fld):
|
||||
if not fname or fname not in rec:
|
||||
continue
|
||||
if want:
|
||||
pi = None
|
||||
for f in data_fields:
|
||||
if f['name'] == fname:
|
||||
pi = f.get('pic_info', {})
|
||||
break
|
||||
length = 8
|
||||
if pi:
|
||||
length = pi.get('length', 8) or pi.get('digits', 8) + pi.get('decimal', 0)
|
||||
rec[fname] = ' ' * length
|
||||
else:
|
||||
cur = str(rec.get(fname, '')).strip().rstrip('0').rstrip(' ')
|
||||
if not cur or cur == '':
|
||||
if 'TIME' in fname:
|
||||
rec[fname] = '0900'
|
||||
elif 'DATE' in fname:
|
||||
rec[fname] = '20240115'
|
||||
else:
|
||||
rec[fname] = '00000101'
|
||||
# Pass B.8: UNSTRING source reconstruction (targets → source)
|
||||
if base_assignments:
|
||||
_reconstruct_unstring_sources(rec, base_assignments, data_fields)
|
||||
@@ -1638,7 +1718,7 @@ def generate_records(path_infos, data_fields, base_assignments=None, file_sec=No
|
||||
# 否则运行时 UNSTRING 会从 R01LINE 取有效值覆盖 WS 的无效值
|
||||
# 直接基于 WRK-CSV 字段构建 CSV,避免 _reconstruct_unstring_sources 解析
|
||||
# R01INNREC 时因组名在 rec 中而跳过子字段解析的 bug
|
||||
_rebuild_r01line_csv(rec, data_fields)
|
||||
_rebuild_r01line_csv(rec, data_fields, base_assignments)
|
||||
|
||||
# Pass E: PIC 长度约束 — 模拟 COBOL 截断语义
|
||||
for f in data_fields:
|
||||
@@ -1676,7 +1756,7 @@ def generate_records(path_infos, data_fields, base_assignments=None, file_sec=No
|
||||
rec2 = deepcopy(rec)
|
||||
rec2['WRK-CSV-EMP-ID'] = '00000101'
|
||||
rec2['WRK-CSV-APPL-DATE'] = ' '
|
||||
_rebuild_r01line_csv(rec2, data_fields)
|
||||
_rebuild_r01line_csv(rec2, data_fields, base_assignments)
|
||||
records.append(rec2)
|
||||
kept_path_cons.append(path_cons)
|
||||
term_types.append(term_type)
|
||||
|
||||
Reference in New Issue
Block a user