Skip to content

Latest commit

 

History

History
387 lines (310 loc) · 14.9 KB

DESIGN.md

File metadata and controls

387 lines (310 loc) · 14.9 KB

LinuxForHealth x12 Design Overview

LinuxForHealth x12 parses and validates ASC X12 health care transactions. At the core of the x12 application is its data model. The data model aligns with the ASC X12 specifications and supports its primary concepts including fields, segments, loops, and transactions.

ASC X12 Overview

Segments, loops, and transaction sets are concepts which provide higher levels of grouping within the ASC X12 specification. Segments are the lowest order of grouping, while transaction sets provide the highest level.

Segments consist of fields. Loops consist of segments. Transaction sets consist of loops.

ASC X12 Fields and Data Types

A field represents a single data attribute with a scalar type. The table below illustrates how LinuxForHealth x12 maps ASC X12 data types to Python's type system.

ASC X12 Data Type Python Type
Numeric int
Decimal Decimal
ID str
String str
Date datetime.date
Time datetime.time
Binary byte[]

ASC X12 Segments

Segments group a collection of fields into a logical record. For example, the NM1 segment is a grouping of fields used to identify an "entity's" name.

NM1*PR*2*PAYER C*****PI*12345~

ASC X12 Loops

Loops are an organizational grouping within the specification that are not expressed within the X12 message payload. The specification uses loops to build a higher level record, such as a subscriber or dependent. This higher level grouping typically adds additional validation constraints to each segment. A segment in one loop typically utilizes different field constraints than it does in another.

For example, the HL segment's hierarchical level code field, or HL03, is used within the Eligibility (270) transaction to specify the beginning of a new hierarchical record. The permitted values for the HL03 field differ based on which loop the segment is in.

  • The Information Source Loop/Loop 2000A sets HL03 to 20
  • The Information Receiver Loop/Loop 2000B sets HL03 to 21
  • The Subscriber Loop/Loop 2000C sets HL03 to 22
  • The Dependent Loop/Loop 2000C sets HL03 to 23

Finally, the segments below represent an "Information Receiver" loop within the Eligibility (270) transaction. The loop boundaries are inferred using the transaction set segments.

HL*2*1*21*1~
NM1*1P*1*DOE*JOHN****XX*1467857193~
REF*4A*000111222~
N3*123 MAIN ST.*SUITE 42~
N4*SAN MATEO*CA*94401~

ASC X12 Transaction Set

The transaction set is the highest level of grouping, and represents a single unit of work. The following X12 message is a single transaction set.

ST*270*0001*005010X279A1~
BHT*0022*13*10001234*20131031*1147~
HL*1**20*1~
NM1*PR*2*PAYER C*****PI*12345~
HL*2*1*21*1~
NM1*1P*1*DOE*JOHN****XX*1467857193~
REF*4A*000111222~
N3*123 MAIN ST.*SUITE 42~
N4*SAN MATEO*CA*94401~
HL*3*2*22*0~
TRN*1*930000000000*9800000004*PD~
NM1*IL*1*DOE*JOHN****MI*00000000001~
REF*6P*0123456789~
DMG*D8*19700101~
DTP*291*D8*20131031~
EQ*1~
SE*17*0001~

ASC X12 Control Envelope

The x12 specification requires transaction sets to be nestled within an "envelope" for transmission. In this case the "envelope" is simply additional segments which contain metadata regarding the message transmission and recipients. The ISA, GS, GE. and IEA segments are the control segments used to contain the message. The ISA and IEA are the opening control header and footer segments, while the GS and GE segments serve as the functional group header and footer statements.

In general terms, the ISA and IEA segments identify the parties involved in the message exchange. The GS and GE segments identify the transaction type within the message payload. The control segments also support checksums (of sorts), security metadata, and timestamps.

ASC X12 File Format

The X12 file format is a simple text based delimited format which dates back to 1979. The initial segment in a X12 payload, the ISA segment, is a fixed length segment used to define the delimiters used in the transmission.

ISA*03*9876543210*01*9876543210*30*000000005      *30*12345          *131031*1147*^*00501*000000907*1*T*:~
Field Type Position (Zero Based) General Industry Default
element separator 3 *
repetition character 82 ^
component separator 104 :
segment terminator 105 ~

LinuxForHealth x12 Data Model

The LinuxForHealth x12 data models extend pydantic 's BaseModel implementation.

The LinuxForHealth x12 data model version aligns with X12 versioning. The 5010 version is current, with 4010 provided for historical use. Each version utilizes an identical module structure. Using the v5010 version as an example, models used include:

  • Base models define the transaction set and its loop structures.
  • Segment models the base segments used within the transaction loops.

The contents of a transaction package include:

transaction-package.png

  • loops.py - Loop data model implementations.
  • parsing.py - Parsing functions used to create loop records within the transactional data model.
  • segments.py - Segment subclasses used to enforce loop context validation constraints.
  • transaction_set.py - The transaction set model.

Data Model Hierarchy

X12Segment is the base model class for X12 segments. It includes a x12 method which transforms the segment to a valid x12 string.

    def x12(self) -> str:
        """
        :return: the X12 representation of the model instance
        """

        x12_values = []
        for k, v in self.dict(exclude={"delimiters"}).items():
            if isinstance(v, str):
                x12_values.append(v)
            elif isinstance(v, list):
                x12_values.append(self._process_multivalue_field(k, v))
            elif isinstance(v, datetime.datetime):
                x12_values.append(v.strftime("%Y%m%d%H%M"))
            elif isinstance(v, datetime.date):
                x12_values.append(v.strftime("%Y%m%d"))
            elif isinstance(v, datetime.time):
                x12_values.append(v.strftime("%H%M"))
            elif isinstance(v, Decimal):
                x12_values.append("{:.2f}".format(v))
            elif v is None:
                x12_values.append("")
            else:
                x12_values.append(str(v))

        x12_str = self.delimiters.element_separator.join(x12_values).rstrip(
            self.delimiters.element_separator
        )
        return x12_str + self.delimiters.segment_terminator

The X12SegmentGroup model is the base model used to model a X12 loop or transaction set. The X12SegmentGroup also includes a x12 method which generates valid x12 from its contained segments.

    def x12(self, use_new_lines=True) -> str:
        """
        :return: Generates a X12 representation of the loop using its segments.
        """
        x12_segments: List[str] = []
        fields = [f for f in self.__fields__.values() if hasattr(f.type_, "x12")]

        for f in fields:
            field_instance = getattr(self, f.name)

            if field_instance is None:
                continue
            elif isinstance(field_instance, list):
                for item in field_instance:
                    if isinstance(item, X12Segment):
                        x12_segments.append(item.x12())
                    else:
                        x12_segments.append(item.x12(use_new_lines=use_new_lines))
            else:
                if isinstance(field_instance, X12Segment):
                    x12_segments.append(field_instance.x12())
                else:
                    x12_segments.append(field_instance.x12(use_new_lines=use_new_lines))

        join_char: str = "\n" if use_new_lines else ""
        return join_char.join(x12_segments)

Data Model Validation

The LinuxForHealth X12 Data Model supports broad and granular validations expressed at the transaction set, loop, segment, and field level with Pydantic's @root_validator and @validator functions.

Pydantic's post validation behavior is used for each validation process to streamline implementations. An additional benefit of post validation behavior is that Pydantic will continue to execute validators rather than halting on the first exception found.

Field Level Validations

Field level validations constrain values based on the underlying data type using Pydantic's Field model or constrained types.

String field validations may enforce min and max field lengths and pattern conformance (regular expressions).

class HlSegment(X12Segment):
    """
    Defines a hierarchical organization used to relate one grouping of segments to another
    Example:
        HL*3*2*22*1~
    """

    segment_name: X12SegmentName = X12SegmentName.HL
    hierarchical_id_number: str = Field(min_length=1, max_length=12)
    hierarchical_parent_id_number: str = Field(min_length=1, max_length=12)
    hierarchical_level_code: str = Field(min_length=1, max_length=2)
    hierarchical_child_code: str = Field(min_length=1, max_length=1, regex="^0|1$")

Numeric field validations may enforce value ranges or utilize Pydantic constrained types such as PositiveInt.

class IeaSegment(X12Segment):
    """
    Defines the interchange footer and is an EDI control segment.
    Example:
        IEA*1*000000907~
    """

    segment_name: X12SegmentName = X12SegmentName.IEA
    number_of_included_functional_groups: PositiveInt
    interchange_control_number: str = Field(min_length=9, max_length=9)

Code-table fields are expressed as string based enumerations within a X12Segment model. Code tables which are not specific to a given transaction set or loop are defined within the x12.<version>.segments module. Transaction-specific code tables are defined within the x12.<version>.x12_<transaction code>_<version identifier> module as "overrides".

class Loop2100RefSegment(RefSegment):
    """
    Conveys additional Subscriber or Dependent identification data.
    """

    class ReferenceIdentificationQualifier(str, Enum):
        """
        Code values for REF01
        """

        PLAN_NUMBER = "18"
        GROUP_POLICY_NUMBER = "1L"
        MEMBER_IDENTIFICATION_NUMBER = "1W"
        CASE_NUMBER = "3H"
        GROUP_NUMBER = "6P"
        CONTRACT_NUMBER = "CT"
        MEDICAL_RECORD_IDENTIFICATION_NUMBER = "EA"
        PATIENT_ACCOUNT_NUMBER = "EJ"
        HEALTH_INSURANCE_CLAIM_NUMBER = "F6"
        IDENTIFICATION_CARD_SERIAL_NUMBER = "GH"
        IDENTITY_CARD_NUMBER = "HJ"
        INSURANCE_POLICY_NUMBER = "IG"
        PLAN_NETWORK_IDENTIFICATION_NUMBER = "N6"
        MEDICARE_RECIPIENT_IDENTIFICATION_NUMBER = "NQ"
        SOCIAL_SECURITY_NUMBER = "SY"
        AGENCY_CLAIM_NUMBER = "Y4"

    reference_identification_qualifier: ReferenceIdentificationQualifier

Segment Level Validations

Segment level validations are implemented within a X12Segment model using either Pydantic's @root_validator or @validator functions. The @root_validator provides access to all fields within the model, while the @validator may only access fields "up to" the target field.

The following example, the Nm1Segment, utilizes both validator types.

class Nm1Segment(X12Segment):
    """
    Entity Name and Identification Number
    Example:
        NM1*PR*2*PAYER C*****PI*12345~
    """

    class EntityQualifierCode(str, Enum):
        """
        NM1.02 Entity Qualifier Code to specify the entity type
        """

        PERSON = "1"
        NON_PERSON = "2"

    segment_name: X12SegmentName = X12SegmentName.NM1
    entity_identifier_code: str = Field(min_length=2, max_length=3)
    entity_type_qualifier: EntityQualifierCode
    name_last_or_organization_name: str = Field(min_length=1, max_length=60)
    name_first: Optional[str] = Field(min_length=0, max_length=35)
    name_middle: Optional[str] = Field(min_length=0, max_length=25)
    name_prefix: Optional[str]
    name_suffix: Optional[str] = Field(min_length=0, max_length=10)
    identification_code_qualifier: Optional[str] = Field(min_length=1, max_length=2)
    identification_code: Optional[str] = Field(min_length=2, max_length=80)
    # NM110 - NM112 are not used

    @root_validator
    def validate_identification_codes(cls, values):
        """
        Validates that both an identification code and qualifier are provided if one or the other is present

        :param values: The raw, unvalidated transaction data.
        """
        id_fields: Tuple = values.get("identification_code_qualifier"), values.get(
            "identification_code"
        )

        if any(id_fields) and not all(id_fields):
            raise ValueError(
                "Identification code usage requires the code qualifier and code value"
            )

        return values

    @validator("name_first", "name_middle", "name_prefix", "name_suffix")
    def validate_organization_name_fields(cls, field_value, values):
        """
        Validates that person name fields are not used within an organizational record context.

        :param field_value: The person name field (first, middle, prefix, suffix) values
        :param values: The previously validated field names and values
        """
        entity_type = values["entity_type_qualifier"]

        if cls.EntityQualifierCode.NON_PERSON.value == entity_type:
            if field_value:
                raise ValueError(
                    "Invalid field usage for Organization/Non-Person Entity"
                )
        return field_value

Reusable Validators with Loop and Transaction Set Models

The x12.validators module contains common validation functions which are not specific to a transaction set. These validations are "wired" to the model using explicit syntax rather than decorator functions.

class Loop2100D(X12SegmentGroup):
    """
    Loop 2100D - Dependent Name
    """

    nm1_segment: Loop2100DNm1Segment
    ref_segment: Optional[List[Loop2100RefSegment]] = Field(min_items=0, max_items=9)
    n3_segment: Optional[N3Segment]
    n4_segment: Optional[N4Segment]
    prv_segment: Optional[PrvSegment]
    dmg_segment: Optional[DmgSegment]
    ins_segment: Optional[Loop2100DInsSegment]
    hi_segment: Optional[HiSegment]
    dtp_segment: Optional[Loop2100DtpSegment]
    loop_2110d: Loop2110D

    _validate_ref_segments = root_validator(allow_reuse=True)(
        validate_duplicate_ref_codes
    )

For additional information on validator implementations, please refer to the Pydantic documentation.