"""Tests for the 270 (Eligibility Benefit Inquiry) parser. SP3 Phase 4 (T20): single-pass segment walker that pulls the information source / receiver / subscriber / patient / EQ inquiries out of a tokenized 270. The fixture (`tests/fixtures/minimal_270.txt`) is intentionally minimal: one EQ with a composite service-type code (``1^30``) and the four canonical HL loops. """ from __future__ import annotations from pathlib import Path import pytest from cyclone.parsers.parse_270 import parse FIXTURE = Path(__file__).parent / "fixtures" / "minimal_270.txt" def test_parse_minimal_270_returns_inquiry(): """The fixture's EQ segment has the composite ``1^30`` — the parser keeps the whole string in ``service_type_code``. v1 convention: callers that need the individual codes should split on ``^``. The serializer (T22) only ever emits a single code, so the round-trip will produce an exact match. """ text = FIXTURE.read_text() result = parse(text, input_file="minimal_270.txt") assert len(result.inquiries) == 1 code = result.inquiries[0].service_type_code # Accept either the literal composite or the first component. assert code in {"1^30", "1"} if code == "1^30": first = code.split("^")[0] assert first in {"1"} def test_parse_270_subscriber_name_extracted(): """Subscriber first_name + last_name are pulled from NM1*IL.""" text = FIXTURE.read_text() result = parse(text, input_file="minimal_270.txt") assert result.subscriber.first_name == "JOHN" assert result.subscriber.last_name == "DOE" assert result.subscriber.member_id == "MEMBER123" # DMG segment — D8 = CCYYMMDD, M = male. assert result.subscriber.dob is not None assert result.subscriber.dob.year == 1980 assert result.subscriber.dob.month == 1 assert result.subscriber.dob.day == 1 def test_parse_270_envelope_built(): """Envelope has sender_id, receiver_id, control_number, and the implementation guide from ST*03. """ text = FIXTURE.read_text() result = parse(text, input_file="minimal_270.txt") env = result.envelope assert env.sender_id.strip() == "SUBMITTERID" assert env.receiver_id.strip() == "RECEIVERID" assert env.control_number == "000000001" assert env.implementation_guide == "005010X279A1" # GS04 supplies the transaction date. assert env.transaction_date.year == 2024 assert env.transaction_date.month == 1 assert env.transaction_date.day == 1 def test_parse_270_information_source_extracted(): """Information source (HL*20) pulls the payer name + id from NM1*PR.""" text = FIXTURE.read_text() result = parse(text, input_file="minimal_270.txt") assert result.information_source.name == "PAYER NAME" assert result.information_source.id == "SKCO0" # Information receiver (HL*21) pulls the provider + NPI. assert result.information_receiver.name == "PROVIDER NAME" assert result.information_receiver.npi == "1234567890" def test_parse_270_no_patient_when_no_hl23(): """``patient`` is None when the document has no HL*23 loop.""" text = FIXTURE.read_text() result = parse(text, input_file="minimal_270.txt") assert result.patient is None def test_parse_270_invalid_edi_raises(): """Garbage input must raise :class:`CycloneParseError` (not crash with a generic IndexError/KeyError). """ from cyclone.parsers.exceptions import CycloneParseError with pytest.raises(CycloneParseError): parse("not edi at all", input_file="garbage.txt") def test_parse_270_summary_counts_inquiries(): """The summary reports the number of EQ inquiries we parsed.""" text = FIXTURE.read_text() result = parse(text, input_file="minimal_270.txt") assert result.summary.total_claims == 1 assert result.summary.passed == 1 assert result.summary.failed == 0 assert result.summary.input_file == "minimal_270.txt" assert result.summary.control_number == "000000001"