From 260b32afaccf247bce7362bdc1027ff543dd71e4 Mon Sep 17 00:00:00 2001 From: Tyler Date: Fri, 19 Jun 2026 15:31:06 -0600 Subject: [PATCH] feat(backend): add CycloneParseError and CycloneValidationError --- backend/src/cyclone/parsers/exceptions.py | 28 +++++++++++++++++++++++ backend/tests/test_exceptions.py | 21 +++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 backend/src/cyclone/parsers/exceptions.py create mode 100644 backend/tests/test_exceptions.py diff --git a/backend/src/cyclone/parsers/exceptions.py b/backend/src/cyclone/parsers/exceptions.py new file mode 100644 index 0000000..ae44aa5 --- /dev/null +++ b/backend/src/cyclone/parsers/exceptions.py @@ -0,0 +1,28 @@ +"""Exception types raised by the parser. + +These are reserved for unrecoverable, file-level failures (bad envelope, no +ISA segment, malformed delimiters). Per-claim validation issues are not +exceptions — they are accumulated into ``ValidationReport``. +""" + +from __future__ import annotations + + +class CycloneParseError(Exception): + """Raised when the file itself cannot be parsed (bad envelope, etc.).""" + + def __init__(self, message: str, *, segment_index: int | None = None) -> None: + self.segment_index = segment_index + suffix = f" (segment {segment_index})" if segment_index is not None else "" + super().__init__(f"{message}{suffix}") + + +class CycloneValidationError(Exception): + """Raised by the CLI for envelope-level validation failures (no claims found).""" + + def __init__(self, message: str, *, rule_id: str | None = None, claim_id: str | None = None) -> None: + self.rule_id = rule_id + self.claim_id = claim_id + prefix = f"[{rule_id}] " if rule_id else "" + claim = f" (claim {claim_id})" if claim_id else "" + super().__init__(f"{prefix}{message}{claim}") diff --git a/backend/tests/test_exceptions.py b/backend/tests/test_exceptions.py new file mode 100644 index 0000000..05bea1b --- /dev/null +++ b/backend/tests/test_exceptions.py @@ -0,0 +1,21 @@ +import pytest +from cyclone.parsers.exceptions import CycloneParseError, CycloneValidationError + + +def test_parse_error_carries_segment_index(): + err = CycloneParseError("bad CLM", segment_index=42) + assert err.segment_index == 42 + assert "bad CLM" in str(err) + assert "segment 42" in str(err) + + +def test_validation_error_carries_rule_id(): + err = CycloneValidationError("NPI bad", rule_id="R020_npi_format", claim_id="C1") + assert err.rule_id == "R020_npi_format" + assert err.claim_id == "C1" + assert "[R020_npi_format]" in str(err) + + +def test_parse_error_inherits_from_exception(): + assert issubclass(CycloneParseError, Exception) + assert issubclass(CycloneValidationError, Exception)