From 495f5c724ed6031f5d771d18b09922cb5fd7c1a5 Mon Sep 17 00:00:00 2001 From: John Holland Date: Wed, 6 May 2026 00:00:00 -0400 Subject: [PATCH] feat: migrate map_walker.py + x12file.py producers to pyx12 codes (PR 4 of 6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fourth slice of the X12 error code generalization plan. Walker and parser-time emissions now use pyx12 codes. With this slice, every producer in the codebase emits pyx12 codes; the legacy raw-X12 fallback in error_997 / error_999 / errh_json (added in PR 1) is no longer exercised — PR 5 removes it. Adds 5 new pyx12 codes to ERROR_CODES table to match producer paths: - SEG_5_SEGMENT_REPEAT_EXCEEDED (was incorrectly named SEG_4_*; the walker emits "5" for segment-too-many, not "4") - SEG_1_INVALID_SEG_ID (parser invalid segment id) - SEG_1_LEADING_SPACE (parser leading whitespace) - SEG_8_SEGMENT_EMPTY (parser empty segment) Producer migration: pyx12/map_walker.py (8 sites): - Segment usage='N': "2" -> SEG_2_SEGMENT_NOT_USED - Segment exceeds max repeat: "5" -> SEG_5_SEGMENT_REPEAT_EXCEEDED - Mandatory segment missing (line 322): "3" -> SEG_3_MANDATORY_SEGMENT_MISSING (stored in MissingMandatorySeg buffer, flushed via line 491) - Segment not found in map: "1" -> SEG_1_SEGMENT_NOT_FOUND - Mandatory loop missing (line 524): "3" -> SEG_3_MANDATORY_LOOP_MISSING - Loop usage='N': "2" -> SEG_2_LOOP_NOT_USED - Loop exceeds max repeat: "4" -> SEG_4_LOOP_REPEAT_EXCEEDED pyx12/x12file.py (7 sites): - Empty segment: "8" -> SEG_8_SEGMENT_EMPTY - Invalid seg id (lines 105, 364, 370): "1" -> SEG_1_INVALID_SEG_ID - Leading space: "1" -> SEG_1_LEADING_SPACE - Trailing element terminators: "SEG1" -> SEG_8_TRAILING_TERMINATORS - HL count mismatch: "HL1" -> SEG_8_HL_COUNT_MISMATCH - HL invalid parent: "HL2" -> SEG_8_HL_INVALID_PARENT - LX count mismatch: "LX" -> SEG_8_LX_COUNT_MISMATCH The historical "SEG1" -> "8" inline remap was already removed from the visitors as part of PR 1's table-aware lookup conversion. The table entry SEG_8_TRAILING_TERMINATORS (ak/ik="8") preserves the same output behavior. HL1/HL2/LX entries have ak/ik=None to preserve their historical "filtered out of ack" behavior. Test updates: - 7 test_x12file assertions migrated to expect pyx12 codes (parser test_trailing_ele_delim shared via inheritance accounted for the 14 apparent failures from a single source change) - 13 test_map_walker assertions migrated (walker error path tests) - All resJson fixtures regenerated; the JSON channel now consistently carries pyx12 codes for every error level Verification: - pytest pyx12/test/: 579 passed (no count change) - mypy --strict pyx12: clean (88 files) - ruff check + format --check: clean Co-Authored-By: Claude Opus 4.7 --- pyx12/error_codes.py | 38 ++++++++++++--- pyx12/map_walker.py | 41 +++++++++++++--- pyx12/test/test_map_walker.py | 41 +++++++++++----- pyx12/test/test_x12file.py | 20 ++++---- pyx12/test/x12testdata.py | 92 +++++++++++++++++------------------ pyx12/x12file.py | 25 +++++++--- 6 files changed, 167 insertions(+), 90 deletions(-) diff --git a/pyx12/error_codes.py b/pyx12/error_codes.py index a30d47b8..d5c7790e 100644 --- a/pyx12/error_codes.py +++ b/pyx12/error_codes.py @@ -89,10 +89,13 @@ class ErrorCodeSpec: SEG_2_LOOP_NOT_USED = "SEG_2_loop_not_used" SEG_3_MANDATORY_SEGMENT_MISSING = "SEG_3_mandatory_segment_missing" SEG_3_MANDATORY_LOOP_MISSING = "SEG_3_mandatory_loop_missing" -SEG_4_SEGMENT_REPEAT_EXCEEDED = "SEG_4_segment_repeat_exceeded" +SEG_5_SEGMENT_REPEAT_EXCEEDED = "SEG_5_segment_repeat_exceeded" SEG_4_LOOP_REPEAT_EXCEEDED = "SEG_4_loop_repeat_exceeded" # From x12file.py parser. SEG1 historically remapped inline to AK/IK "8"; # HL1/HL2/LX historically had no ack remap (filtered out). +SEG_1_INVALID_SEG_ID = "SEG_1_invalid_seg_id" +SEG_1_LEADING_SPACE = "SEG_1_leading_space" +SEG_8_SEGMENT_EMPTY = "SEG_8_segment_empty" SEG_8_TRAILING_TERMINATORS = "SEG_8_trailing_terminators" SEG_8_HL_COUNT_MISMATCH = "SEG_8_hl_count_mismatch" SEG_8_HL_INVALID_PARENT = "SEG_8_hl_invalid_parent" @@ -286,21 +289,42 @@ class ErrorCodeSpec: ak_code="3", ik_code="3", ), - SEG_4_SEGMENT_REPEAT_EXCEEDED: ErrorCodeSpec( - code=SEG_4_SEGMENT_REPEAT_EXCEEDED, + SEG_5_SEGMENT_REPEAT_EXCEEDED: ErrorCodeSpec( + code=SEG_5_SEGMENT_REPEAT_EXCEEDED, level="SEG", - description="Segment repeat count exceeded", - ak_code="4", - ik_code="4", + description="Segment repeat count exceeded (segment exceeds maximum use)", + ak_code="5", + ik_code="5", ), SEG_4_LOOP_REPEAT_EXCEEDED: ErrorCodeSpec( code=SEG_4_LOOP_REPEAT_EXCEEDED, level="SEG", - description="Loop repeat count exceeded", + description="Loop repeat count exceeded (loop occurs over maximum times)", ak_code="4", ik_code="4", ), # --- Segment-level: parser --- + SEG_1_INVALID_SEG_ID: ErrorCodeSpec( + code=SEG_1_INVALID_SEG_ID, + level="SEG", + description="Segment identifier is malformed (parser-time invalid seg ID)", + ak_code="1", + ik_code="1", + ), + SEG_1_LEADING_SPACE: ErrorCodeSpec( + code=SEG_1_LEADING_SPACE, + level="SEG", + description="Segment line started with leading whitespace (parser-time)", + ak_code="1", + ik_code="1", + ), + SEG_8_SEGMENT_EMPTY: ErrorCodeSpec( + code=SEG_8_SEGMENT_EMPTY, + level="SEG", + description="Segment is empty (parser-time)", + ak_code="8", + ik_code="8", + ), # SEG_8_TRAILING_TERMINATORS captures the historical "SEG1" code which # the visitors inline-remapped to AK/IK "8". This table entry replaces # the inline remap. diff --git a/pyx12/map_walker.py b/pyx12/map_walker.py index d52b9ad6..6eb4089c 100644 --- a/pyx12/map_walker.py +++ b/pyx12/map_walker.py @@ -25,6 +25,15 @@ import pyx12.segment # Intrapackage imports +from .error_codes import ( + SEG_1_SEGMENT_NOT_FOUND, + SEG_2_LOOP_NOT_USED, + SEG_2_SEGMENT_NOT_USED, + SEG_3_MANDATORY_LOOP_MISSING, + SEG_3_MANDATORY_SEGMENT_MISSING, + SEG_4_LOOP_REPEAT_EXCEEDED, + SEG_5_SEGMENT_REPEAT_EXCEEDED, +) from .error_item import SegError from .errors import EngineError from .nodeCounter import NodeCounter @@ -250,7 +259,9 @@ def _check_seg_usage( raise EngineError("Segment usage must be R, S, or N (got %r)" % (seg_node.usage,)) if seg_node.usage == "N": err_str = "Segment %s found but marked as not used" % (seg_node.id) - self.errors_pending.append(SegError(err_cde="2", err_str=err_str, src_line=cur_line)) + self.errors_pending.append( + SegError(err_cde=SEG_2_SEGMENT_NOT_USED, err_str=err_str, src_line=cur_line) + ) elif seg_node.usage == "R" or seg_node.usage == "S": if ( self.counter.get_count(seg_node.x12path) > seg_node.get_max_repeat() @@ -262,7 +273,7 @@ def _check_seg_usage( ) self.errors_pending.append( SegError( - err_cde="5", + err_cde=SEG_5_SEGMENT_REPEAT_EXCEEDED, err_str=err_str, src_line=cur_line, map_node=seg_node, @@ -319,7 +330,15 @@ def _try_match_segment_child( fake_seg = pyx12.segment.Segment("%s" % (child.id), "~", "*", ":") err_str = 'Mandatory segment "%s" (%s) missing' % (child.name, child.id) self.mandatory_segs_missing.append( - MissingMandatorySeg(child, fake_seg, "3", err_str, seg_count, cur_line, ls_id) + MissingMandatorySeg( + child, + fake_seg, + SEG_3_MANDATORY_SEGMENT_MISSING, + err_str, + seg_count, + cur_line, + ls_id, + ) ) return None @@ -462,7 +481,7 @@ def _seg_not_found_error( err_str = "Segment %s not found. Started at %s" % (seg_str, orig_node.get_path()) self.errors_pending.append( SegError( - err_cde="1", + err_cde=SEG_1_SEGMENT_NOT_FOUND, err_str=err_str, src_line=cur_line, map_node=orig_node, @@ -521,7 +540,13 @@ def _record_loop_missing_if_required( err_str = 'Mandatory loop "%s" (%s) missing' % (loop_node.name, loop_node.id) self.mandatory_segs_missing.append( MissingMandatorySeg( - first_child_node, fake_seg, "3", err_str, seg_count, cur_line, ls_id + first_child_node, + fake_seg, + SEG_3_MANDATORY_LOOP_MISSING, + err_str, + seg_count, + cur_line, + ls_id, ) ) @@ -672,7 +697,9 @@ def _check_loop_usage( raise EngineError("Loop usage must be R, S, or N (got %r)" % (loop_node.usage,)) if loop_node.usage == "N": err_str = "Loop %s found but marked as not used" % (loop_node.id) - self.errors_pending.append(SegError(err_cde="2", err_str=err_str, src_line=cur_line)) + self.errors_pending.append( + SegError(err_cde=SEG_2_LOOP_NOT_USED, err_str=err_str, src_line=cur_line) + ) elif loop_node.usage in ("R", "S"): self.counter.reset_to_node(loop_node.x12path) self.counter.increment(loop_node.x12path) @@ -684,7 +711,7 @@ def _check_loop_usage( ) self.errors_pending.append( SegError( - err_cde="4", + err_cde=SEG_4_LOOP_REPEAT_EXCEEDED, err_str=err_str, src_line=cur_line, map_node=loop_node, diff --git a/pyx12/test/test_map_walker.py b/pyx12/test/test_map_walker.py index 98308fb8..c8a70ed4 100644 --- a/pyx12/test/test_map_walker.py +++ b/pyx12/test/test_map_walker.py @@ -234,7 +234,7 @@ def test_loop_required_fail1(self): apply_walk_errors(self.errh, walk_errors) # result = node.is_valid(seg_data, self.errh) # self.assertFalse(result) - self.assertEqual(self.errh.err_cde, "3", self.errh.err_str) + self.assertEqual(self.errh.err_cde, "SEG_3_mandatory_loop_missing", self.errh.err_str) self.assertEqual(get_id_list(pop), []) self.assertEqual(get_id_list(push), ["2000B"]) @@ -415,7 +415,7 @@ def test_segment_required_fail1(self): seg_data = pyx12.segment.Segment("N4*NOWHERE*MA*30001~", "~", "*", ":") (node, pop, push, walk_errors) = self.walker.walk_errors(node, seg_data, 5, 4, None) apply_walk_errors(self.errh, walk_errors) - self.assertEqual(self.errh.err_cde, "3", self.errh.err_str) + self.assertEqual(self.errh.err_cde, "SEG_3_mandatory_loop_missing", self.errh.err_str) self.assertEqual(get_id_list(pop), []) self.assertEqual(get_id_list(push), []) @@ -432,7 +432,7 @@ def test_found_unused_segment1(self): apply_walk_errors(self.errh, walk_errors) # result = node.is_valid(comp, self.errh) # self.assertTrue(result) - self.assertEqual(self.errh.err_cde, "2", self.errh.err_str) + self.assertEqual(self.errh.err_cde, "SEG_2_segment_not_used", self.errh.err_str) self.assertEqual(get_id_list(pop), []) self.assertEqual(get_id_list(push), []) @@ -484,7 +484,7 @@ def test_segment_id_short(self): (node, pop, push, walk_errors) = self.walker.walk_errors(node, seg_data, 5, 4, None) apply_walk_errors(self.errh, walk_errors) self.assertEqual(node, None) - self.assertEqual(self.errh.err_cde, "1", self.errh.err_str) + self.assertEqual(self.errh.err_cde, "SEG_1_segment_not_found", self.errh.err_str) self.assertEqual(get_id_list(pop), []) self.assertEqual(get_id_list(push), []) @@ -495,7 +495,7 @@ def test_segment_id_long(self): (node, pop, push, walk_errors) = self.walker.walk_errors(node, seg_data, 5, 4, None) apply_walk_errors(self.errh, walk_errors) self.assertEqual(node, None) - self.assertEqual(self.errh.err_cde, "1", self.errh.err_str) + self.assertEqual(self.errh.err_cde, "SEG_1_segment_not_found", self.errh.err_str) self.assertEqual(get_id_list(pop), []) self.assertEqual(get_id_list(push), []) @@ -558,7 +558,7 @@ def test_count_fail1(self): self.assertNotEqual(node, None, "Node not found") (node, pop, push, walk_errors) = self.walker.walk_errors(node, seg_data, 5, 4, None) apply_walk_errors(self.errh, walk_errors) - self.assertEqual(self.errh.err_cde, "5", self.errh.err_str) + self.assertEqual(self.errh.err_cde, "SEG_5_segment_repeat_exceeded", self.errh.err_str) self.assertEqual(get_id_list(pop), []) self.assertEqual(get_id_list(push), []) @@ -602,7 +602,7 @@ def test_max_loop_count_fail1(self): self.errh.reset() (node, pop, push, walk_errors) = self.walker.walk_errors(node, seg_data, 5, 4, None) apply_walk_errors(self.errh, walk_errors) - self.assertEqual(self.errh.err_cde, "4", self.errh.err_str) + self.assertEqual(self.errh.err_cde, "SEG_4_loop_repeat_exceeded", self.errh.err_str) self.assertEqual(get_id_list(pop), ["2400"]) self.assertEqual(get_id_list(push), ["2400"]) @@ -703,7 +703,7 @@ def test_ord_bad1(self): seg_data = pyx12.segment.Segment("REF*17*A232~", "~", "*", ":") (node, pop, push, walk_errors) = self.walker.walk_errors(node, seg_data, 5, 4, None) apply_walk_errors(self.errh, walk_errors) - self.assertEqual(self.errh.err_cde, "1", self.errh.err_str) + self.assertEqual(self.errh.err_cde, "SEG_1_segment_not_found", self.errh.err_str) self.assertEqual(get_id_list(pop), []) self.assertEqual(get_id_list(push), []) @@ -980,7 +980,10 @@ def test_no_match_required_uncounted_accumulates_missing(self): self.assertIsNone(result) self.assertEqual(len(self.walker.mandatory_segs_missing), 1) self.assertEqual(self.walker.mandatory_segs_missing[0].seg_node, child) - self.assertEqual(self.walker.mandatory_segs_missing[0].err_cde, "3") + self.assertEqual( + self.walker.mandatory_segs_missing[0].err_cde, + "SEG_3_mandatory_segment_missing", + ) def test_no_match_required_already_counted_no_accumulation(self): """Required child with count >= 1 + non-matching seg -> None, no accumulation.""" @@ -1007,7 +1010,10 @@ def test_match_unused_segment_emits_error_2(self): loop_node, child, seg_data, child, 5, 4, None, [] ) self.assertIsNotNone(result) - self.assertEqual([e.err_cde for e in self.walker.errors_pending], ["2"]) + self.assertEqual( + [e.err_cde for e in self.walker.errors_pending], + ["SEG_2_segment_not_used"], + ) def test_match_exceeds_max_count_emits_error_5(self): """Increment past max_repeat on R/S match -> err_cde '5' accumulated.""" @@ -1025,7 +1031,10 @@ def test_match_exceeds_max_count_emits_error_5(self): loop_node, child, seg_data, child, 5, 4, None, [] ) self.assertIsNotNone(result) - self.assertEqual([e.err_cde for e in self.walker.errors_pending], ["5"]) + self.assertEqual( + [e.err_cde for e in self.walker.errors_pending], + ["SEG_5_segment_repeat_exceeded"], + ) class TryMatchLoopChild(unittest.TestCase): @@ -1092,7 +1101,10 @@ def test_no_match_required_uncounted_accumulates_missing(self): self.assertEqual(len(self.walker.mandatory_segs_missing), 1) # The accumulated entry references the loop's first child, not the loop itself. self.assertEqual(self.walker.mandatory_segs_missing[0].seg_node, first_child) - self.assertEqual(self.walker.mandatory_segs_missing[0].err_cde, "3") + self.assertEqual( + self.walker.mandatory_segs_missing[0].err_cde, + "SEG_3_mandatory_loop_missing", + ) def test_no_match_required_counted_no_accumulation(self): """Required loop with count >= 1 + non-matching seg -> None, no accumulation.""" @@ -1112,7 +1124,10 @@ def test_match_exceeds_max_loop_count_emits_error_4(self): seg_data = pyx12.segment.Segment("LX*51", "~", "*", ":") result = self.walker._try_match_loop_child(child, seg_data, 5, 4, None, []) self.assertIsNotNone(result) - self.assertEqual([e.err_cde for e in self.walker.errors_pending], ["4"]) + self.assertEqual( + [e.err_cde for e in self.walker.errors_pending], + ["SEG_4_loop_repeat_exceeded"], + ) # ---- pop list pass-through ---- diff --git a/pyx12/test/test_x12file.py b/pyx12/test/test_x12file.py index 04e300fd..6dd41a0e 100644 --- a/pyx12/test/test_x12file.py +++ b/pyx12/test/test_x12file.py @@ -81,7 +81,7 @@ def test_trailing_ele_delim(self): if len(errors) > 0: err_cde = errors[0][1] err_str = errors[0][2] - self.assertEqual(err_cde, "SEG1", err_str) + self.assertEqual(err_cde, "SEG_8_trailing_terminators", err_str) class ISA_header(X12fileTestCase): @@ -260,7 +260,7 @@ def test_HL_increment_bad(self): str1 += "GE*1*17~\n" str1 += "IEA*1*000010121~\n" (err_cde, err_str) = self._get_first_error(str1) - self.assertEqual(err_cde, "HL1", err_str) + self.assertEqual(err_cde, "SEG_8_hl_count_mismatch", err_str) def test_HL_parent_good(self): seg = None @@ -290,7 +290,7 @@ def test_HL_parent_bad_invalid(self): str1 += "GE*1*17~\n" str1 += "IEA*1*000010121~\n" (err_cde, err_str) = self._get_first_error(str1) - self.assertEqual(err_cde, "HL2", err_str) + self.assertEqual(err_cde, "SEG_8_hl_invalid_parent", err_str) def xtest_HL_parent_bad_blank(self): seg = None @@ -305,7 +305,7 @@ def xtest_HL_parent_bad_blank(self): str1 += "GE*1*17~\n" str1 += "IEA*1*000010121~\n" (err_cde, err_str) = self._get_first_error(str1) - self.assertEqual(err_cde, "HL2", err_str) + self.assertEqual(err_cde, "SEG_8_hl_invalid_parent", err_str) class Formatting(X12fileTestCase): @@ -344,7 +344,7 @@ def test_segment_id_short(self): str1 = "ISA*00* *00* *ZZ*ZZ000 *ZZ*ZZ001 *030828*1128*U*00401*000010121*0*T*:~\n" str1 += "Z*0019~\n" (err_cde, err_str) = self._get_first_error(str1) - self.assertEqual(err_cde, "1", err_str) + self.assertEqual(err_cde, "SEG_1_invalid_seg_id", err_str) def test_segment_last_space(self): str1 = "ISA*00* *00* *ZZ*ZZ000 *ZZ*ZZ001 *030828*1128*U*00401*000010121*0*T*:~\n" @@ -361,25 +361,25 @@ def test_segment_id_long(self): str1 = "ISA*00* *00* *ZZ*ZZ000 *ZZ*ZZ001 *030828*1128*U*00401*000010121*0*T*:~\n" str1 += "ZZZZ*0019~\n" (err_cde, err_str) = self._get_first_error(str1) - self.assertEqual(err_cde, "1", err_str) + self.assertEqual(err_cde, "SEG_1_invalid_seg_id", err_str) def test_segment_id_empty(self): str1 = "ISA*00* *00* *ZZ*ZZ000 *ZZ*ZZ001 *030828*1128*U*00401*000010121*0*T*:~\n" str1 += "*1~\n" (err_cde, err_str) = self._get_first_error(str1) - self.assertEqual(err_cde, "1", err_str) + self.assertEqual(err_cde, "SEG_1_invalid_seg_id", err_str) def test_segment_empty(self): str1 = "ISA*00* *00* *ZZ*ZZ000 *ZZ*ZZ001 *030828*1128*U*00401*000010121*0*T*:~\n" str1 += "TST~\n" (err_cde, err_str) = self._get_first_error(str1) - self.assertEqual(err_cde, "8", err_str) + self.assertEqual(err_cde, "SEG_8_segment_empty", err_str) def test_segment_trailing_space(self): str1 = "ISA*00* *00* *ZZ*ZZ000 *ZZ*ZZ001 *030828*1128*U*00401*000010121*0*T*:~ \n" str1 += "ZZ*0019~ \n" (err_cde, err_str) = self._get_first_error(str1) - self.assertEqual(err_cde, "1", err_str) + self.assertEqual(err_cde, "SEG_1_leading_space", err_str) class FileString(X12fileTestCase): @@ -606,7 +606,7 @@ def test_LX_increment_bad(self): str1 += "GE*1*17~\n" str1 += "IEA*1*000010121~\n" (err_cde, err_str) = self._get_first_error(str1, "837") - self.assertEqual(err_cde, "LX", err_str) + self.assertEqual(err_cde, "SEG_8_lx_count_mismatch", err_str) class InterchangeVersion(X12fileTestCase): diff --git a/pyx12/test/x12testdata.py b/pyx12/test/x12testdata.py index 0210dad9..39ec7f4a 100644 --- a/pyx12/test/x12testdata.py +++ b/pyx12/test/x12testdata.py @@ -526,7 +526,7 @@ "cur_line": 5, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Utilization Management Organization (UMO) Level" (2000A) missing', "err_val": None, @@ -553,7 +553,7 @@ "cur_line": 8, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Utilization Management Organization (UMO) Level" (2000A) missing', "err_val": None, @@ -580,7 +580,7 @@ "cur_line": 11, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Utilization Management Organization (UMO) Level" (2000A) missing', "err_val": None, @@ -619,7 +619,7 @@ "cur_line": 16, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_segment_missing", "x12_code": "3", "err_str": 'Mandatory segment "Transmission Type Identification" (REF) missing', "err_val": None, @@ -636,7 +636,7 @@ "cur_line": 16, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Submitter Name" (1000A) missing', "err_val": None, @@ -653,7 +653,7 @@ "cur_line": 16, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Receiver Name" (1000B) missing', "err_val": None, @@ -670,7 +670,7 @@ "cur_line": 16, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Billing/Pay-To Provider Hierarchical Level" (2000A) missing', "err_val": None, @@ -709,7 +709,7 @@ "cur_line": 20, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Table 1 - Header" (HEADER) missing', "err_val": None, @@ -748,7 +748,7 @@ "cur_line": 24, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Table 1 - Header" (HEADER) missing', "err_val": None, @@ -787,7 +787,7 @@ "cur_line": 28, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Table 1 - Header" (HEADER) missing', "err_val": None, @@ -836,7 +836,7 @@ "cur_line": 35, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Utilization Management Organization (UMO) Level" (2000A) missing', "err_val": None, @@ -863,7 +863,7 @@ "cur_line": 38, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Utilization Management Organization (UMO) Level" (2000A) missing', "err_val": None, @@ -890,7 +890,7 @@ "cur_line": 41, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Utilization Management Organization (UMO) Level" (2000A) missing', "err_val": None, @@ -929,7 +929,7 @@ "cur_line": 46, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_segment_missing", "x12_code": "3", "err_str": 'Mandatory segment "Transmission Type Identification" (REF) missing', "err_val": None, @@ -946,7 +946,7 @@ "cur_line": 46, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Submitter Name" (1000A) missing', "err_val": None, @@ -963,7 +963,7 @@ "cur_line": 46, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Receiver Name" (1000B) missing', "err_val": None, @@ -980,7 +980,7 @@ "cur_line": 46, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Billing/Pay-To Provider Hierarchical Level" (2000A) missing', "err_val": None, @@ -1019,7 +1019,7 @@ "cur_line": 50, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Table 1 - Header" (HEADER) missing', "err_val": None, @@ -1332,8 +1332,8 @@ "cur_line": 24, "errors": [ { - "err_cde": "SEG1", - "x12_code": "SEG1", + "err_cde": "SEG_8_trailing_terminators", + "x12_code": "8", "err_str": "Segment contains trailing element terminators", "err_val": None, } @@ -1442,7 +1442,7 @@ "cur_line": 10, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Billing Provider Name" (2010AA) missing', "err_val": None, @@ -1899,7 +1899,7 @@ "cur_line": 41, "errors": [ { - "err_cde": "1", + "err_cde": "SEG_1_segment_not_found", "x12_code": "1", "err_str": "Segment N1*PR not found. Started at /ISA_LOOP/GS_LOOP/ST_LOOP/DETAIL/2000/2100/2110/LQ", "err_val": None, @@ -1916,7 +1916,7 @@ "cur_line": 42, "errors": [ { - "err_cde": "1", + "err_cde": "SEG_1_segment_not_found", "x12_code": "1", "err_str": "Segment N3*P.O. BOX 30479 not found. Started at /ISA_LOOP/GS_LOOP/ST_LOOP/DETAIL/2000/2100/2110/LQ", "err_val": None, @@ -1933,7 +1933,7 @@ "cur_line": 43, "errors": [ { - "err_cde": "1", + "err_cde": "SEG_1_segment_not_found", "x12_code": "1", "err_str": "Segment N4*LANSING not found. Started at /ISA_LOOP/GS_LOOP/ST_LOOP/DETAIL/2000/2100/2110/LQ", "err_val": None, @@ -1950,7 +1950,7 @@ "cur_line": 44, "errors": [ { - "err_cde": "1", + "err_cde": "SEG_1_segment_not_found", "x12_code": "1", "err_str": "Segment N1*PE not found. Started at /ISA_LOOP/GS_LOOP/ST_LOOP/DETAIL/2000/2100/2110/LQ", "err_val": None, @@ -2359,7 +2359,7 @@ "cur_line": 4, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Table 1 - Header" (HEADER) missing', "err_val": None, @@ -2376,7 +2376,7 @@ "cur_line": 4, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Billing/Pay-To Provider Hierarchical Level" (2000A) missing', "err_val": None, @@ -2863,7 +2863,7 @@ "cur_line": 387, "errors": [ { - "err_cde": "4", + "err_cde": "SEG_4_loop_repeat_exceeded", "x12_code": "4", "err_str": "Loop 2400 exceeded max count. Found 51, should have 50", "err_val": None, @@ -2880,7 +2880,7 @@ "cur_line": 394, "errors": [ { - "err_cde": "4", + "err_cde": "SEG_4_loop_repeat_exceeded", "x12_code": "4", "err_str": "Loop 2400 exceeded max count. Found 52, should have 50", "err_val": None, @@ -2897,7 +2897,7 @@ "cur_line": 401, "errors": [ { - "err_cde": "4", + "err_cde": "SEG_4_loop_repeat_exceeded", "x12_code": "4", "err_str": "Loop 2400 exceeded max count. Found 53, should have 50", "err_val": None, @@ -2914,7 +2914,7 @@ "cur_line": 408, "errors": [ { - "err_cde": "4", + "err_cde": "SEG_4_loop_repeat_exceeded", "x12_code": "4", "err_str": "Loop 2400 exceeded max count. Found 54, should have 50", "err_val": None, @@ -3401,7 +3401,7 @@ "cur_line": 387, "errors": [ { - "err_cde": "4", + "err_cde": "SEG_4_loop_repeat_exceeded", "x12_code": "4", "err_str": "Loop 2400 exceeded max count. Found 51, should have 50", "err_val": None, @@ -3418,7 +3418,7 @@ "cur_line": 394, "errors": [ { - "err_cde": "4", + "err_cde": "SEG_4_loop_repeat_exceeded", "x12_code": "4", "err_str": "Loop 2400 exceeded max count. Found 52, should have 50", "err_val": None, @@ -3435,7 +3435,7 @@ "cur_line": 401, "errors": [ { - "err_cde": "4", + "err_cde": "SEG_4_loop_repeat_exceeded", "x12_code": "4", "err_str": "Loop 2400 exceeded max count. Found 53, should have 50", "err_val": None, @@ -3452,7 +3452,7 @@ "cur_line": 408, "errors": [ { - "err_cde": "4", + "err_cde": "SEG_4_loop_repeat_exceeded", "x12_code": "4", "err_str": "Loop 2400 exceeded max count. Found 54, should have 50", "err_val": None, @@ -3563,7 +3563,7 @@ "cur_line": 5, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Utilization Management Organization (UMO) Level" (2000A) missing', "err_val": None, @@ -3590,7 +3590,7 @@ "cur_line": 8, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Utilization Management Organization (UMO) Level" (2000A) missing', "err_val": None, @@ -3617,7 +3617,7 @@ "cur_line": 11, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Utilization Management Organization (UMO) Level" (2000A) missing', "err_val": None, @@ -3656,7 +3656,7 @@ "cur_line": 16, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_segment_missing", "x12_code": "3", "err_str": 'Mandatory segment "Transmission Type Identification" (REF) missing', "err_val": None, @@ -3673,7 +3673,7 @@ "cur_line": 16, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Submitter Name" (1000A) missing', "err_val": None, @@ -3690,7 +3690,7 @@ "cur_line": 16, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Receiver Name" (1000B) missing', "err_val": None, @@ -3707,7 +3707,7 @@ "cur_line": 16, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Billing/Pay-To Provider Hierarchical Level" (2000A) missing', "err_val": None, @@ -3746,7 +3746,7 @@ "cur_line": 20, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Table 1 - Header" (HEADER) missing', "err_val": None, @@ -3960,7 +3960,7 @@ "cur_line": 9, "errors": [ { - "err_cde": "5", + "err_cde": "SEG_5_segment_repeat_exceeded", "x12_code": "5", "err_str": "Segment PER exceeded max count. Found 3, should have 2", "err_val": None, @@ -4099,7 +4099,7 @@ "cur_line": 4, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Table 1 - Header" (HEADER) missing', "err_val": None, @@ -4116,7 +4116,7 @@ "cur_line": 4, "errors": [ { - "err_cde": "3", + "err_cde": "SEG_3_mandatory_loop_missing", "x12_code": "3", "err_str": 'Mandatory loop "Billing/Pay-To Provider Hierarchical Level" (2000A) missing', "err_val": None, diff --git a/pyx12/x12file.py b/pyx12/x12file.py index 97dda75b..35118d23 100644 --- a/pyx12/x12file.py +++ b/pyx12/x12file.py @@ -28,6 +28,15 @@ # Intrapackage imports import pyx12.errors import pyx12.segment +from pyx12.error_codes import ( + SEG_1_INVALID_SEG_ID, + SEG_1_LEADING_SPACE, + SEG_8_HL_COUNT_MISMATCH, + SEG_8_HL_INVALID_PARENT, + SEG_8_LX_COUNT_MISMATCH, + SEG_8_SEGMENT_EMPTY, + SEG_8_TRAILING_TERMINATORS, +) from pyx12.rawx12file import RawX12File # (kind, code, message, value, src_line) @@ -99,10 +108,10 @@ def _parse_segment(self, seg_data: pyx12.segment.Segment) -> None: """ if seg_data.is_empty(): err_str = f'Segment "{seg_data}" is empty' - self._seg_error("8", err_str, None, src_line=self.cur_line + 1) + self._seg_error(SEG_8_SEGMENT_EMPTY, err_str, None, src_line=self.cur_line + 1) if not seg_data.is_seg_id_valid(): err_str = f'Segment identifier "{seg_data.get_seg_id()}" is invalid' - self._seg_error("1", err_str, None, src_line=self.cur_line + 1) + self._seg_error(SEG_1_INVALID_SEG_ID, err_str, None, src_line=self.cur_line + 1) seg_id = seg_data.get_seg_id() if seg_id == "ISA": if len(seg_data) != 16: @@ -156,12 +165,12 @@ def _parse_segment(self, seg_data: pyx12.segment.Segment) -> None: # 'My HL count %i does not match your HL count %s' \ # % (self.hl_count, seg[1]) err_str = f"My HL count {self.hl_count:d} does not match your HL count {hl_count}" - self._seg_error("HL1", err_str) + self._seg_error(SEG_8_HL_COUNT_MISMATCH, err_str) if seg_data.get_value("HL02") != "": hl_parent = self._int(seg_data.get_value("HL02")) if hl_parent not in self.hl_stack: err_str = f"HL parent ({hl_parent}) is not a valid parent" - self._seg_error("HL2", err_str) + self._seg_error(SEG_8_HL_INVALID_PARENT, err_str) while self.hl_stack and hl_parent != self.hl_stack[-1]: del self.hl_stack[-1] else: @@ -180,7 +189,7 @@ def _parse_segment(self, seg_data: pyx12.segment.Segment) -> None: seg_data.get_value("LX01"), self.lx_count ) ) - self._seg_error("LX", err_str) + self._seg_error(SEG_8_LX_COUNT_MISMATCH, err_str) # count all regular segments if seg_id not in ("ISA", "IEA", "GS", "GE", "ST", "SE"): self.seg_count += 1 @@ -450,11 +459,13 @@ def __iter__(self) -> Iterator[pyx12.segment.Segment]: # We have not yet incremented cur_line if line.startswith(" "): err_str = "Segment contains a leading space" - self._seg_error("1", err_str, None, src_line=self.cur_line + 1) + self._seg_error(SEG_1_LEADING_SPACE, err_str, None, src_line=self.cur_line + 1) line = line.lstrip() if line[-1] == self.ele_term: err_str = "Segment contains trailing element terminators" - self._seg_error("SEG1", err_str, None, src_line=self.cur_line + 1) + self._seg_error( + SEG_8_TRAILING_TERMINATORS, err_str, None, src_line=self.cur_line + 1 + ) seg_data = pyx12.segment.Segment(line, self.seg_term, self.ele_term, self.subele_term) # type: ignore[arg-type] self._parse_segment(seg_data) yield seg_data