---
title: SAIF.Framework.Common.Files business rules
summary: Enumerated business rules with per-rule trigger, enforcement, and outcomes.
status: draft
owner: SAIF Doc Gen
last_reviewed: 2026-09-15
library: SAIF.Framework.Common.Files
reference_type: package
---

# SAIF.Framework.Common.Files business rules

This page captures the 62 business rules enforced by `SAIF.Framework.Common.Files`. All rules are enforced by this library's own code (no rule is enforced by an inherited BOF/NHibernate base class). Rules [BR-001](#br-001-default-configuration-required)–[BR-016](#br-016-readcustom-and-writecustom-force-layout-direction) govern the flat-file subsystem; [BR-017](#br-017-physical-line-breaks-are-not-significant)–[BR-037](#br-037-edi-interchange-written-as-a-single-line-overwriting-existing-file) govern EDI structural parsing/building/grouping; [BR-038](#br-038-isa-header-fixed-qualifiers-and-padding)–[BR-062](#br-062-procedure-composite-equality-excludes-description) govern individual typed segments and date conversion.

## BR-001 Default configuration required

| Field | Value |
| --- | --- |
| **Statement** | A record type must supply at least one file-format configuration before it can be read or written without an explicit format. |
| **Trigger** | `Read(path)` / `Write(path, data)` called with no resolvable configuration. |
| **Enforcement** | `FileReader<TRecord>.Read(string)`; `FileWriter<TRecord>.Write(string, IEnumerable<TRecord>)` |
| **Outcome** | `ApplicationException("No default configuration found.")` |
| **Source** | Common\SAIF.Common.Files\Core\FileReader.cs, Common\SAIF.Common.Files\Core\FileWriter.cs |

---

## BR-002 Single IsDefault configuration required among multiple

| Field | Value |
| --- | --- |
| **Statement** | When a record type declares more than one format attribute, exactly one must be marked `IsDefault` for the format-less overload to resolve. |
| **Trigger** | Multiple `FormatBase` attributes on `TRecord`. |
| **Enforcement** | `FileReader<TRecord>.Read(string)`; `FileWriter<TRecord>.Write(string, IEnumerable<TRecord>)` |
| **Outcome** | The `IsDefault` configuration is used; otherwise `ApplicationException`. |
| **Source** | Common\SAIF.Common.Files\Core\FileReader.cs, Common\SAIF.Common.Files\Core\FileWriter.cs |

---

## BR-003 Requested format must match a declared configuration

| Field | Value |
| --- | --- |
| **Statement** | A requested `FileFormats` value must match a declared configuration. |
| **Trigger** | `Read(path, format)` / `Write(path, data, format)`. |
| **Enforcement** | `FileReader<TRecord>.Read(string, FileFormats)`; `FileWriter<TRecord>.Write(string, IEnumerable<TRecord>, FileFormats)` |
| **Outcome** | `ApplicationException("No Configuration found for requested format.")` |
| **Source** | Common\SAIF.Common.Files\Core\FileReader.cs, Common\SAIF.Common.Files\Core\FileWriter.cs |

---

## BR-004 TrimAll trims field values on read

| Field | Value |
| --- | --- |
| **Statement** | Field values are whitespace-trimmed on read when the configuration sets `TrimAll`. |
| **Trigger** | Reading any file. |
| **Enforcement** | `FileReader<TRecord>.PrepareParser` (`parser.TrimWhiteSpace = configuration.TrimAll`) |
| **Outcome** | Leading/trailing whitespace removed from every field. |
| **Source** | Common\SAIF.Common.Files\Core\FileReader.cs |

---

## BR-005 Quoted-field handling is CSV-only

| Field | Value |
| --- | --- |
| **Statement** | Quoted-field handling is enabled only for the CSV format; other delimited formats treat quotes as literal characters. |
| **Trigger** | Reading a delimited file. |
| **Enforcement** | `FileReader<TRecord>.PrepareParser` (`HasFieldsEnclosedInQuotes = (Format == FileFormats.Csv)`) |
| **Outcome** | CSV honours `"value"` field enclosure; `Delimited`/`TabDelimited` do not. |
| **Source** | Common\SAIF.Common.Files\Core\FileReader.cs |

---

## BR-006 Header record skipped when declared

| Field | Value |
| --- | --- |
| **Statement** | When the configuration declares `HasHeaderRecord`, the first physical row of the file is a header and is not materialised as a data record. |
| **Trigger** | Reading a file whose configuration sets `HasHeaderRecord`. |
| **Enforcement** | `FileReader<TRecord>.Read(string, IFileConfiguration)` (`if (configuration.HasHeaderRecord && first)`) |
| **Outcome** | First row skipped. |
| **Source** | Common\SAIF.Common.Files\Core\FileReader.cs |

---

## BR-007 Record-build failures report layout id and row data

| Field | Value |
| --- | --- |
| **Statement** | Any failure to map a row to the record type is reported with the offending layout id and the raw row data. |
| **Trigger** | Exception thrown while building a record. |
| **Enforcement** | `FileReader<TRecord>.BuildRecord` catch block |
| **Outcome** | `ApplicationException("Configuration Layout Error: Unable to build data record. (<layoutId>) [Row data: <rowData>]", innerException)` |
| **Source** | Common\SAIF.Common.Files\Core\FileReader.cs |

---

## BR-008 Fixed-width values forced to layout length

| Field | Value |
| --- | --- |
| **Statement** | Fixed-width output values are forced to the layout length: longer values are truncated, shorter values are padded on the pad side implied by the alignment (left-aligned with a space by default), and a zero length yields an empty value. |
| **Trigger** | Writing a `FileFormats.Fixed` record or header. |
| **Enforcement** | `FileWriter<TRecord>.AlignValue`; defaults in `FileRecordLayout` constructor (`AlignLeft = true`, pads `' '`) |
| **Outcome** | Every fixed field occupies exactly `Length` characters. |
| **Source** | Common\SAIF.Common.Files\Core\FileWriter.cs, Common\SAIF.Common.Files\Core\Configurations\FileRecordLayout.cs |

---

## BR-009 Header field must have a property or title

| Field | Value |
| --- | --- |
| **Statement** | A header field must have either a bound property or a non-blank `Title`. |
| **Trigger** | Writing a header record. |
| **Enforcement** | `FileWriter<TRecord>.BuildHeaderRecord` |
| **Outcome** | `ApplicationException("Configuration Layout Error: No Property or Title defined for Header. (<displayId>)")` |
| **Source** | Common\SAIF.Common.Files\Core\FileWriter.cs |

---

## BR-010 Writable layout must resolve to a property

| Field | Value |
| --- | --- |
| **Statement** | A writable layout must resolve to a property, either directly (`Property`) or by `Name`, unless a custom writer is supplied. |
| **Trigger** | Writing a data record. |
| **Enforcement** | `FileWriter<TRecord>.GetPropertyValue` |
| **Outcome** | `ApplicationException("Configuration Layout Error: No Property or Name assigned to set value. (<displayId>)")` |
| **Source** | Common\SAIF.Common.Files\Core\FileWriter.cs |

---

## BR-011 Record-derived types round-trip the raw line

| Field | Value |
| --- | --- |
| **Statement** | A record type deriving from `Record` round-trips the raw line: the whole line is stored in `Value` on read and written back verbatim on write, bypassing layout mapping. |
| **Trigger** | `TRecord` derives from `Core.Record`. |
| **Enforcement** | `FileReader<TRecord>.BuildRecord` (binds `Value`/`Index`); `FileWriter<TRecord>.BuildRecord` (returns `Value`) |
| **Outcome** | Raw-line pass-through. |
| **Source** | Common\SAIF.Common.Files\Core\FileReader.cs, Common\SAIF.Common.Files\Core\FileWriter.cs, Common\SAIF.Common.Files\Core\Record.cs |

---

## BR-012 Delimited layouts use 1-based element ordinals

| Field | Value |
| --- | --- |
| **Statement** | Delimited layouts address fields by 1-based element ordinal; a custom reader runs before, and a custom read converter instead of, the default `Convert.ChangeType`. |
| **Trigger** | Reading each readable layout. |
| **Enforcement** | `FileReader<TRecord>.BuildRecord` (`fields[lo.Element - 1]`), `ConvertAndSetValue` |
| **Outcome** | Value converted to the property type and assigned. |
| **Source** | Common\SAIF.Common.Files\Core\FileReader.cs |

---

## BR-013 TabDelimitedRecord always splits on tab

| Field | Value |
| --- | --- |
| **Statement** | `TabDelimitedRecord` always splits on a tab character, regardless of the delimiter requested by the caller. |
| **Trigger** | `FileReader.ReadDelimited(path, delimiter)` or direct construction. |
| **Enforcement** | `TabDelimitedRecord` constructors (`Delimiter = "\t"` in both) |
| **Outcome** | Non-tab delimiters passed to `ReadDelimited` are ignored for field splitting. |
| **Source** | Common\SAIF.Common.Files\Core\TabDelimitedRecord.cs |

---

## BR-014 No trailing delimiter in delimited output

| Field | Value |
| --- | --- |
| **Statement** | In delimited output the delimiter separates fields and is never emitted after the final field; fixed-width output emits no delimiter at all. |
| **Trigger** | Writing a record or header. |
| **Enforcement** | `FileWriter<TRecord>.BuildRecord`, `BuildHeaderRecord` (`if (lo != layouts.Last() && Format != Fixed)`) |
| **Outcome** | No trailing delimiter. |
| **Source** | Common\SAIF.Common.Files\Core\FileWriter.cs |

---

## BR-015 Output value resolution order

| Field | Value |
| --- | --- |
| **Statement** | Output value resolution order is: custom writer, then property value, then custom write converter, then the layout format string, then `ToString()`. |
| **Trigger** | Writing each writable layout. |
| **Enforcement** | `FileWriter<TRecord>.GetPropertyValue` |
| **Outcome** | Formatted string value. |
| **Source** | Common\SAIF.Common.Files\Core\FileWriter.cs |

---

## BR-016 ReadCustom and WriteCustom force layout direction

| Field | Value |
| --- | --- |
| **Statement** | A layout configured with `ReadCustom` becomes read-only and one configured with `WriteCustom` becomes write-only. |
| **Trigger** | Fluent configuration. |
| **Enforcement** | `FileLayoutBuilder.ReadCustom`, `WriteCustom` (also `ReadOnly()`, `WriteOnly()`) |
| **Outcome** | Layout excluded from the opposite direction's layout set. |
| **Source** | Common\SAIF.Common.Files\Core\Configurations\FileLayoutBuilder.cs |

---

## BR-017 Physical line breaks are not significant

| Field | Value |
| --- | --- |
| **Statement** | Physical line breaks in an EDI file are not significant; the whole file is treated as one continuous segment stream. |
| **Trigger** | Reading an EDI file. |
| **Enforcement** | `EdiFileReader.Read` (trims and concatenates all lines before parsing) |
| **Outcome** | Wrapped or single-line files parse identically. |
| **Source** | Common\SAIF.Common.Files\Edi\Core\Readers\EdiFileReader.cs |

---

## BR-018 Segments delimited by tilde empty segments discarded

| Field | Value |
| --- | --- |
| **Statement** | Segments are delimited by `~`, and empty segments are discarded. |
| **Trigger** | Parsing EDI text. |
| **Enforcement** | `EdiSegmentParser.Parse` (`Split(SegmentDelimiter, RemoveEmptyEntries)`); delimiter in `EdiRecordBase` / `Delimiters.Segment` |
| **Outcome** | One `EdiSegment` per non-empty token. |
| **Source** | Common\SAIF.Common.Files\Edi\Core\Parsers\EdiSegmentParser.cs, Common\SAIF.Common.Files\Edi\EdiRecordBase.cs |

---

## BR-019 Colon-containing elements become composite elements

| Field | Value |
| --- | --- |
| **Statement** | A data element containing `:` is treated as a composite element and split into sub-elements. |
| **Trigger** | Parsing a segment's elements. |
| **Enforcement** | `EdiDataElementParser.Parse` (`if (item.Contains(":"))`) → `EdiCompositeDataElementParser.Parse` |
| **Outcome** | `EdiCompositeDataElement` with child `EdiDataElement`s. |
| **Source** | Common\SAIF.Common.Files\Edi\Core\Parsers\EdiDataElementParser.cs, Common\SAIF.Common.Files\Edi\Core\Parsers\EdiCompositeDataElementParser.cs |

---

## BR-020 First element is the segment identifier

| Field | Value |
| --- | --- |
| **Statement** | The first data element of a segment is element 0 and is the segment identifier. |
| **Trigger** | Parsing or building a segment. |
| **Enforcement** | `EdiDataElementParser.Parse` (`indexValue = first ? 0 : IndexingSequence.NextValue`); `EdiSegmentParser.Parse` (`SegmentId = GetChildValue(0)`); `SegmentBuilder(string)` |
| **Outcome** | `SegmentId` populated from element 0. |
| **Source** | Common\SAIF.Common.Files\Edi\Core\Parsers\EdiDataElementParser.cs, Common\SAIF.Common.Files\Edi\Core\Parsers\EdiSegmentParser.cs, Common\SAIF.Common.Files\Edi\Core\Builders\SegmentBuilder.cs |

---

## BR-021 Segment construction requires a non-null element list

| Field | Value |
| --- | --- |
| **Statement** | Segment construction requires its inputs: a non-null element list, and (for `Build(IList<>)`) an element at index 0 to supply the segment id. |
| **Trigger** | `SegmentBuilder.Build(IList<IEdiDataElement>)`. |
| **Enforcement** | `SegmentBuilder.Build(IList<IEdiDataElement>)` via `Enforce.ArgumentNotNull` |
| **Outcome** | Argument exception when null (Origin: `SAIF.Framework.Common`, `Enforce`). |
| **Source** | Common\SAIF.Common.Files\Edi\Core\Builders\SegmentBuilder.cs |

---

## BR-022 Only one identifying element at index 0

| Field | Value |
| --- | --- |
| **Statement** | A segment may contain only one identifying element at index 0. |
| **Trigger** | Adding a second element with index 0. |
| **Enforcement** | `SegmentBuilder.Add(int, string)` and `Add(IEdiDataElement)` |
| **Outcome** | `ApplicationException("Segment already contains an identifying Element")` |
| **Source** | Common\SAIF.Common.Files\Edi\Core\Builders\SegmentBuilder.cs |

---

## BR-023 Segment id must not be blank

| Field | Value |
| --- | --- |
| **Statement** | A segment id must be supplied and must not be blank. |
| **Trigger** | `new SegmentBuilder(segmentId)`. |
| **Enforcement** | `SegmentBuilder(string)` via `Enforce.ArgumentNotNullOrWhiteSpace` |
| **Outcome** | Argument exception when null/blank (Origin: `SAIF.Framework.Common`, `Enforce`). |
| **Source** | Common\SAIF.Common.Files\Edi\Core\Builders\SegmentBuilder.cs |

---

## BR-024 Null element values are omitted

| Field | Value |
| --- | --- |
| **Statement** | Null element values are omitted from a segment rather than written as empty elements. |
| **Trigger** | Adding an element with a null value. |
| **Enforcement** | `SegmentBuilder.Add(int, string)`, `Add(IEdiDataElement)`; `EdiCompositeDataElement.SetChildElement` (null removes an existing child) |
| **Outcome** | Optional elements absent from output. |
| **Source** | Common\SAIF.Common.Files\Edi\Core\Builders\SegmentBuilder.cs, Common\SAIF.Common.Files\Edi\EdiCompositeDataElement.cs |

---

## BR-025 Composite sub-elements require a composite parent

| Field | Value |
| --- | --- |
| **Statement** | A composite sub-element can only be added to an element that is itself composite. |
| **Trigger** | `Add(elementIndex, compositeIndex, value)` where the parent exists but is a simple data element. |
| **Enforcement** | `SegmentBuilder.Add(int, int, string)` |
| **Outcome** | `ApplicationException("Element at index {0} is not a composite element")` |
| **Source** | Common\SAIF.Common.Files\Edi\Core\Builders\SegmentBuilder.cs |

---

## BR-026 Composite element values joined with colon

| Field | Value |
| --- | --- |
| **Statement** | Composite element values are joined with `:`, the trailing `:` is removed, and the composite is terminated with `*`. |
| **Trigger** | Serialising a composite element. |
| **Enforcement** | `EdiRecordBuilder.BuildCompositeDataElementValue`; `EdiCompositeDataElement.SetChildElement` / `ToString` |
| **Outcome** | Canonical composite text. |
| **Source** | Common\SAIF.Common.Files\Edi\Core\Builders\EdiRecordBuilder.cs, Common\SAIF.Common.Files\Edi\EdiCompositeDataElement.cs |

---

## BR-027 Skipped element ordinals preserved with placeholders

| Field | Value |
| --- | --- |
| **Statement** | Skipped element ordinals are preserved by emitting `*` placeholders, and a trailing element delimiter is removed from the finished segment. |
| **Trigger** | Serialising a segment whose elements are non-contiguous. |
| **Enforcement** | `EdiRecordBuilder.BuildSegmentRecord` |
| **Outcome** | Element positions remain X12-correct. |
| **Source** | Common\SAIF.Common.Files\Edi\Core\Builders\EdiRecordBuilder.cs |

---

## BR-028 Simple data element terminated by asterisk

| Field | Value |
| --- | --- |
| **Statement** | A simple data element is terminated by `*`. |
| **Trigger** | Serialising a data element. |
| **Enforcement** | `EdiRecordBuilder.BuildDataElementValue`; `EdiDataElement.ToString` |
| **Outcome** | `value*` |
| **Source** | Common\SAIF.Common.Files\Edi\Core\Builders\EdiRecordBuilder.cs, Common\SAIF.Common.Files\Edi\EdiDataElement.cs |

---

## BR-029 First ISA and IEA are the interchange header and trailer

| Field | Value |
| --- | --- |
| **Statement** | The first `ISA` segment is the interchange header and the first `IEA` segment is the interchange trailer. |
| **Trigger** | `RefreshGroupings`. |
| **Enforcement** | `EdiFile.RefreshGroupings` (`FirstOrDefault(x => x.SegmentId == "ISA" / "IEA")`, then `IsaSegment.Parse` / `IeaSegment.Parse`) |
| **Outcome** | `EdiFile.Header` / `EdiFile.Trailer` populated. |
| **Source** | Common\SAIF.Common.Files\Edi\EdiFile.cs |

---

## BR-030 Functional groups built only when all four control segments present

| Field | Value |
| --- | --- |
| **Statement** | The functional-group hierarchy is built only when the interchange contains all four control segments `ISA`, `IEA`, `GS` and `GE`. |
| **Trigger** | `RefreshGroupings`. |
| **Enforcement** | `EdiFile.RefreshGroupings` |
| **Outcome** | Incomplete interchanges expose segments but no eagerly-built group tree. |
| **Source** | Common\SAIF.Common.Files\Edi\EdiFile.cs |

---

## BR-031 Every added segment receives the next sequential index

| Field | Value |
| --- | --- |
| **Statement** | Every segment added to a file receives the next sequential index, and index order defines document order. |
| **Trigger** | `EdiFile.Add(segment)`. |
| **Enforcement** | `EdiFile.Add` (`segment.Index = IndexingSequence.NextValue`; Origin of `Sequence`: `SAIF.Framework.Common`) |
| **Outcome** | Deterministic ordering for grouping and writing. |
| **Source** | Common\SAIF.Common.Files\Edi\EdiFile.cs |

---

## BR-032 Adding a control segment re-derives envelope groupings

| Field | Value |
| --- | --- |
| **Statement** | Adding any of the control segments `ISA`, `IEA`, `GS`, `GE` re-derives the envelope groupings. |
| **Trigger** | `EdiFile.Add` with a control segment id. |
| **Enforcement** | `EdiFile.Add` (`segment.SegmentId.IsIn("ISA","IEA","GS","GE")`; Origin of `IsIn`: `SAIF.Framework.Common`) |
| **Outcome** | Header/trailer/groups refreshed. |
| **Source** | Common\SAIF.Common.Files\Edi\EdiFile.cs |

---

## BR-033 Control segments excluded from member Segments collections

| Field | Value |
| --- | --- |
| **Statement** | Control segments are excluded from the member `Segments` collection of the group they delimit: `ISA`/`IEA`/`GS`/`GE` from a functional group, `ST`/`SE` from a transaction set, `HL`/`ST`/`SE` from a hierarchical level, and `BHT`/`HL`/`ST`/`SE` from a hierarchical transaction. |
| **Trigger** | Building groups. |
| **Enforcement** | `EdiGroupParser.CreateFunctionalGroups`, `CreateTransactionSets`, `CreateHierarchialTransactions` |
| **Outcome** | Group `Segments` contain business segments only. |
| **Source** | Common\SAIF.Common.Files\Edi\Core\Parsers\EdiGroupParser.cs |

---

## BR-034 Group keys derived from control-number elements

| Field | Value |
| --- | --- |
| **Statement** | A functional group is keyed by `GS` element 6, a transaction set by `ST` element 2, and a hierarchical transaction/level by element 1 of `BHT`/`HL`. |
| **Trigger** | Building groups. |
| **Enforcement** | `EdiGroupParser.ParseSegmentGroup` calls in `CreateFunctionalGroups` (6), `CreateTransactionSets` (2), `CreateHierarchialTransactions` (1) |
| **Outcome** | `ISegmentGroup.Key` populated from the control number. |
| **Source** | Common\SAIF.Common.Files\Edi\Core\Parsers\EdiGroupParser.cs |

---

## BR-035 Hierarchical levels nested inside hierarchical transaction

| Field | Value |
| --- | --- |
| **Statement** | Hierarchical levels (`HL`) are nested inside the hierarchical transaction (`BHT`) of their transaction set. |
| **Trigger** | Building a transaction set. |
| **Enforcement** | `EdiGroupParser.CreateHierarchialTransactions` |
| **Outcome** | `HierarchialTransaction.Levels` populated. |
| **Source** | Common\SAIF.Common.Files\Edi\Core\Parsers\EdiGroupParser.cs |

---

## BR-036 Segment group ownership determined by position

| Field | Value |
| --- | --- |
| **Statement** | A segment group owns every segment from its own index up to (but excluding) the next occurrence of the break segment, or the end of the parent list when there is no next occurrence. |
| **Trigger** | `ParseSegmentGroup`. |
| **Enforcement** | `EdiGroupParser.ParseSegmentGroup` + `GetNext` + `MoveSegmentsToSegmentGroup` |
| **Outcome** | Segment membership determined by position. |
| **Source** | Common\SAIF.Common.Files\Edi\Core\Parsers\EdiGroupParser.cs |

---

## BR-037 EDI interchange written as a single line overwriting existing file

| Field | Value |
| --- | --- |
| **Statement** | An EDI interchange is written as a single line of `~`-terminated segments, overwriting any existing file. |
| **Trigger** | Writing an EDI file. |
| **Enforcement** | `EdiFileWriter.Write` (`File.WriteAllText`) |
| **Outcome** | Single-line output file. |
| **Source** | Common\SAIF.Common.Files\Edi\Core\Writers\EdiFileWriter.cs |

---

## BR-038 ISA header fixed qualifiers and padding

| Field | Value |
| --- | --- |
| **Statement** | The ISA header is emitted with fixed authorization/security qualifiers `00`, ten-space authorization and security fields, `ZZ` interchange id qualifiers, sender and receiver ids right-padded to 15 characters, a `yyMMdd`/`HHmm` date-time, and a 9-digit zero-padded control number. |
| **Trigger** | Building an ISA segment. |
| **Enforcement** | `IsaSegment.Build` |
| **Outcome** | Fixed-width X12 interchange header. |
| **Source** | Common\SAIF.Common.Files\Edi\Segments\IsaSegment.cs |

---

## BR-039 ISA acknowledgement request flag

| Field | Value |
| --- | --- |
| **Statement** | The ISA acknowledgement request flag is `1` when requested and `0` otherwise, and is read back as `true` only for `1`. |
| **Trigger** | Building/filling an ISA segment. |
| **Enforcement** | `IsaSegment.Build` / `IsaSegment.Fill` (element 14) |
| **Outcome** | `AcknowledgementRequest` round-trips. |
| **Source** | Common\SAIF.Common.Files\Edi\Segments\IsaSegment.cs |

---

## BR-040 GS header responsible agency code and batch date-time

| Field | Value |
| --- | --- |
| **Statement** | The GS header emits the responsible agency code `X` at element 7 and a `yyyyMMdd`/`HHmm` batch date-time; the version control number is read from element 8. |
| **Trigger** | Building/filling a GS segment. |
| **Enforcement** | `GsSegment.Build` / `GsSegment.Fill` |
| **Outcome** | X12-compliant functional group header. |
| **Source** | Common\SAIF.Common.Files\Edi\Segments\GsSegment.cs |

---

## BR-041 ST and SE control numbers zero-padded to four digits

| Field | Value |
| --- | --- |
| **Statement** | `ST` and `SE` transaction-set control numbers are zero-padded to at least four digits, preserving longer values. |
| **Trigger** | Building an ST or SE segment. |
| **Enforcement** | `StSegment.Build`, `SeSegment.Build` (`len = ControlNumber.ToString().Length; if (len < 4) len = 4;`) |
| **Outcome** | Control numbers match between ST and SE. |
| **Source** | Common\SAIF.Common.Files\Edi\Segments\StSegment.cs, Common\SAIF.Common.Files\Edi\Segments\SeSegment.cs |

---

## BR-042 AK2 control number zero-padded to four digits

| Field | Value |
| --- | --- |
| **Statement** | The `AK2` transaction-set control number is zero-padded to four digits. |
| **Trigger** | Building an AK2 segment. |
| **Enforcement** | `Ak2Segment.Build` (`PadLeft(4, '0')`) |
| **Outcome** | Acknowledgement references the original control number format. |
| **Source** | Common\SAIF.Common.Files\Edi\Segments\Ak2Segment.cs |

---

## BR-043 IEA control number emitted as nine digits

| Field | Value |
| --- | --- |
| **Statement** | The `IEA` interchange control number is emitted as exactly nine digits. |
| **Trigger** | Building an IEA segment. |
| **Enforcement** | `IeaSegment.Build` (`ToString("D9")`) |
| **Outcome** | Matches the ISA control number format ([BR-038](#br-038-isa-header-fixed-qualifiers-and-padding)). |
| **Source** | Common\SAIF.Common.Files\Edi\Segments\IeaSegment.cs |

---

## BR-044 BHT original vs reissue flag

| Field | Value |
| --- | --- |
| **Statement** | A BHT transaction is flagged `00` when it is an original submission and `08` otherwise; on read, only `00` sets `IsOriginal`. |
| **Trigger** | Building/filling a BHT segment. |
| **Enforcement** | `BhtSegment.Build` / `BhtSegment.Fill` (element 2) |
| **Outcome** | Original vs. reissue distinction preserved. |
| **Source** | Common\SAIF.Common.Files\Edi\Segments\BhtSegment.cs |

---

## BR-045 DTP format defaults

| Field | Value |
| --- | --- |
| **Statement** | When no DTP format is supplied, the format defaults to `D8` for a single date and `RD8` for a date range; format `TM` writes a time value instead of a date. |
| **Trigger** | Building a DTP segment. |
| **Enforcement** | `DtpSegment.Build` |
| **Outcome** | Correct X12 date/time format qualifier. |
| **Source** | Common\SAIF.Common.Files\Edi\Segments\DtpSegment.cs |

---

## BR-046 DTP values interpreted by format qualifier

| Field | Value |
| --- | --- |
| **Statement** | DTP values are interpreted by their format qualifier: `RD8` splits a start/end range on `-`, `D8` is a single date, `DT` is date + time in one string, and `TM` yields `DateTime.MinValue` with the raw time retained as a string. |
| **Trigger** | Filling a DTP segment. |
| **Enforcement** | `DtpSegment.Fill` switch |
| **Outcome** | Format-appropriate parsing. |
| **Source** | Common\SAIF.Common.Files\Edi\Segments\DtpSegment.cs |

---

## BR-047 NM1 person vs organization name layout

| Field | Value |
| --- | --- |
| **Statement** | An NM1 entity is a person when entity type is `1`: person names emit last, first and optional middle/prefix/suffix; all other entity types emit a single organization name. |
| **Trigger** | Building/filling an NM1 segment. |
| **Enforcement** | `Nm1Segment.Build` / `Nm1Segment.Fill` (`IsForPerson = (EntityType == "1")`) |
| **Outcome** | Person vs. organization name layout. |
| **Source** | Common\SAIF.Common.Files\Edi\Segments\Nm1Segment.cs |

---

## BR-048 NM1 complete name composition

| Field | Value |
| --- | --- |
| **Statement** | A person's display name is composed as `Last + Suffix + ", " + First + " " + Middle`; an organization's display name is the organization name. |
| **Trigger** | Filling an NM1 segment. |
| **Enforcement** | `Nm1Segment.Fill` (`CompleteName`) |
| **Outcome** | Derived `CompleteName`. |
| **Source** | Common\SAIF.Common.Files\Edi\Segments\Nm1Segment.cs |

---

## BR-049 N4 postal code split into ZIP and extension

| Field | Value |
| --- | --- |
| **Statement** | A postal code longer than five characters is split into a five-digit ZIP and the remainder as the ZIP extension. |
| **Trigger** | Filling an N4 segment. |
| **Enforcement** | `N4Segment.Fill` (element 3) |
| **Outcome** | `ZipCode` / `ZipCodeExtension` separated; written back concatenated in `Build`. |
| **Source** | Common\SAIF.Common.Files\Edi\Segments\N4Segment.cs |

---

## BR-050 CTX amounts scaled to cents CAS amounts unscaled

| Field | Value |
| --- | --- |
| **Statement** | CTX monetary amounts are exchanged in cents: written as `truncate(amount × 100)` and read back divided by 100. CAS amounts are exchanged unscaled. |
| **Trigger** | Building/filling a CTX or CAS segment. |
| **Enforcement** | `CtxSegment.Build` / `CtxSegment.Fill`; `CasSegment.Build` / `CasSegment.Fill` |
| **Outcome** | Scaled vs. unscaled amounts. |
| **Source** | Common\SAIF.Common.Files\Edi\Segments\CtxSegment.cs, Common\SAIF.Common.Files\Edi\Segments\CasSegment.cs |

---

## BR-051 SVC adjudication suppression rules

| Field | Value |
| --- | --- |
| **Statement** | In an SVC adjudication segment: the revenue code is suppressed when the adjudicated procedure qualifier is `NU`; the adjudicated quantity is omitted when it equals 1 and defaults to 1 when absent; the billed procedure is emitted only when it differs from the adjudicated procedure; and the billed quantity is emitted only when it differs from the adjudicated quantity, defaulting to it when absent. |
| **Trigger** | Building/filling an SVC segment. |
| **Enforcement** | `SvcSegment.Build` / `SvcSegment.Fill` (comparison via `CompositeProcedureCodeElement.Equals`) |
| **Outcome** | Minimal, non-redundant SVC output. |
| **Source** | Common\SAIF.Common.Files\Edi\Segments\SvcSegment.cs, Common\SAIF.Common.Files\Edi\Segments\CompositeProcedureCodeElement.cs |

---

## BR-052 HL child level indicator

| Field | Value |
| --- | --- |
| **Statement** | An HL level reports child levels via element 4: an explicit child code is written when supplied, otherwise `1`/`0` derived from the child-exists flag; on read, `ChildExists` is true only when the child code is `1`. |
| **Trigger** | Building/filling an HL segment. |
| **Enforcement** | `HlSegment.Build` / `HlSegment.Fill` |
| **Outcome** | Hierarchy child indicator. |
| **Source** | Common\SAIF.Common.Files\Edi\Segments\HlSegment.cs |

---

## BR-053 IK5 acknowledgement or reject response codes

| Field | Value |
| --- | --- |
| **Statement** | A functional acknowledgement reports `A` to accept and `R` to reject. |
| **Trigger** | `Ik5Segment.Acknowledgement()` / `Reject()`. |
| **Enforcement** | `Ik5Segment.Acknowledgement`, `Ik5Segment.Reject` |
| **Outcome** | IK5 segment with response code `A` or `R`. |
| **Source** | Common\SAIF.Common.Files\Edi\Segments\Ik5Segment.cs |

---

## BR-054 DMG HI SV1 SV2 SV3 are inbound-only segments

| Field | Value |
| --- | --- |
| **Statement** | The DMG, HI, SV1, SV2 and SV3 segments are inbound-only and cannot be serialised. |
| **Trigger** | Calling `Build()` on those types. |
| **Enforcement** | `DmgSegment.Build`, `HiSegment.Build`, `Sv1Segment.Build`, `Sv2Segment.Build`, `Sv3Segment.Build` |
| **Outcome** | `NotImplementedException` |
| **Source** | Common\SAIF.Common.Files\Edi\Segments\DmgSegment.cs, Common\SAIF.Common.Files\Edi\Segments\HiSegment.cs, Common\SAIF.Common.Files\Edi\Segments\Sv1Segment.cs, Common\SAIF.Common.Files\Edi\Segments\Sv2Segment.cs, Common\SAIF.Common.Files\Edi\Segments\Sv3Segment.cs |

---

## BR-055 Generated EDI file name pattern

| Field | Value |
| --- | --- |
| **Statement** | Generated EDI file names follow `{yyyyMMddHHmmss}_SAIF_{P\|T}_{fileType}.txt`, where `P` marks production and `T` marks test. |
| **Trigger** | `NameBuilder.GetFileName(fileType, isProd)`. |
| **Enforcement** | `NameBuilder.GetFileName` |
| **Outcome** | Environment-tagged, timestamped file name. |
| **Source** | Common\SAIF.Common.Files\Edi\NameBuilder.cs |

---

## BR-056 Two-digit EDI years interpreted as 21st century

| Field | Value |
| --- | --- |
| **Statement** | Two-digit EDI years are always interpreted as 21st-century dates (prefixed with `20`); no 20th-century handling exists. |
| **Trigger** | Converting any EDI date. |
| **Enforcement** | `DateMaker.MakeDateTime` (documented in the method's `<comments>` block) |
| **Outcome** | `20yy` dates. |
| **Source** | Common\SAIF.Common.Files\Edi\Core\DateMaker.cs |

---

## BR-057 EDI time value right-padded before conversion

| Field | Value |
| --- | --- |
| **Statement** | An EDI time value is right-padded with zeros to `HHmmss` before conversion, so partial times default their missing components to zero. |
| **Trigger** | Converting an EDI date-time. |
| **Enforcement** | `DateMaker.MakeDateTime` (`time.PadRight(6, '0')`) |
| **Outcome** | Deterministic time component. |
| **Source** | Common\SAIF.Common.Files\Edi\Core\DateMaker.cs |

---

## BR-058 EDI date range format

| Field | Value |
| --- | --- |
| **Statement** | An EDI date range is expressed as `yyyyMMdd-yyyyMMdd` with `-` as the separator; a single date omits the separator and the second date. |
| **Trigger** | Writing/reading a date range. |
| **Enforcement** | `DateMaker.EdiFileDateString`, `DateMaker.ExtractDateRange` (`DateSeperator = '-'`, `DateFormat = "yyyyMMdd"`) |
| **Outcome** | `D8` / `RD8` compatible values. |
| **Source** | Common\SAIF.Common.Files\Edi\Core\DateMaker.cs |

---

## BR-059 Optional segment elements written only when populated

| Field | Value |
| --- | --- |
| **Statement** | Optional segment elements are written only when populated: `ST03` version reference, `TRN03`/`TRN04`, `CLP05` patient responsibility and `CLP09` frequency type, `NM105`–`NM109`, and `CAS`/`CTX` reason-code triplets with an empty reason code. |
| **Trigger** | Building those segments. |
| **Enforcement** | `StSegment.Build`, `TrnSegment.Build`, `ClpSegment.Build`, `Nm1Segment.Build`, `CasSegment.Build`, `CtxSegment.Build` (guards use `IsNullOrEmpty`/`IsNull`; Origin: `SAIF.Framework.Common` string/generic extensions) |
| **Outcome** | Absent optional elements rather than empty ones. |
| **Source** | Common\SAIF.Common.Files\Edi\Segments\StSegment.cs, Common\SAIF.Common.Files\Edi\Segments\TrnSegment.cs, Common\SAIF.Common.Files\Edi\Segments\ClpSegment.cs, Common\SAIF.Common.Files\Edi\Segments\Nm1Segment.cs, Common\SAIF.Common.Files\Edi\Segments\CasSegment.cs, Common\SAIF.Common.Files\Edi\Segments\CtxSegment.cs |

---

## BR-060 SV1 emergency indicator conditions

| Field | Value |
| --- | --- |
| **Statement** | An SV1 service line is flagged as an emergency only when element 9 is exactly `Y`, and only when the segment actually carries more than eight elements. |
| **Trigger** | Filling an SV1 segment. |
| **Enforcement** | `Sv1Segment.Fill` |
| **Outcome** | `EmergencyIndicator` set. |
| **Source** | Common\SAIF.Common.Files\Edi\Segments\Sv1Segment.cs |

---

## BR-061 CAS and CTX adjustment triplets

| Field | Value |
| --- | --- |
| **Statement** | CAS and CTX adjustments are repeating triplets of (reason code, amount, quantity) starting at element 2; the count is derived as `floor((elementCount + 1) / 3)` and the quantity position is deliberately not implemented. |
| **Trigger** | Building/filling a CAS or CTX segment. |
| **Enforcement** | `CasSegment.Build`/`Fill`, `CtxSegment.Build`/`Fill` |
| **Outcome** | Up to six adjustment triplets per segment*; quantity always ignored. |
| **Source** | Common\SAIF.Common.Files\Edi\Segments\CasSegment.cs, Common\SAIF.Common.Files\Edi\Segments\CtxSegment.cs |

---

## BR-062 Procedure composite equality excludes description

| Field | Value |
| --- | --- |
| **Statement** | A service procedure code composite carries qualifier, code and up to four modifier codes; two composites are considered equal when qualifier, code and all four modifiers match (description is excluded from the comparison). |
| **Trigger** | Comparing or building a procedure composite. |
| **Enforcement** | `CompositeProcedureCodeElement.BuildElement`, `Equals`; `SvSegmentBase.BuildElement`, `FillServiceCode` |
| **Outcome** | Equality drives SVC billed-vs-adjudicated suppression ([BR-051](#br-051-svc-adjudication-suppression-rules)). |
| **Source** | Common\SAIF.Common.Files\Edi\Segments\CompositeProcedureCodeElement.cs, Common\SAIF.Common.Files\Edi\Segments\SvSegmentBase.cs |

---

## Notes
- Rule [BR-061](#br-061-cas-and-ctx-adjustment-triplets) is marked inferred (`*`) because the "up to six triplets" figure is derived from the element-count formula rather than an explicit constant in source.
- No rule on this page is enforced by an inherited BOF/NHibernate base class; all enforcement is local to `SAIF.Framework.Common.Files`.

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Dependencies](dependencies.md)
- [Operations and processes](operations-processes.md)
- [API reference](api/index.md)

> **Note:** Statements marked with `*` were inferred from code structure, naming
> conventions, or contextual evidence where direct confirmation was not available
> in source artifacts.

## Sources
- Common\SAIF.Common.Files\Core\FileReader.cs
- Common\SAIF.Common.Files\Core\FileWriter.cs
- Common\SAIF.Common.Files\Core\TabDelimitedRecord.cs
- Common\SAIF.Common.Files\Core\Record.cs
- Common\SAIF.Common.Files\Core\Configurations\FileRecordLayout.cs
- Common\SAIF.Common.Files\Core\Configurations\FileLayoutBuilder.cs
- Common\SAIF.Common.Files\Edi\Core\Readers\EdiFileReader.cs
- Common\SAIF.Common.Files\Edi\Core\Writers\EdiFileWriter.cs
- Common\SAIF.Common.Files\Edi\Core\Parsers\EdiSegmentParser.cs
- Common\SAIF.Common.Files\Edi\Core\Parsers\EdiDataElementParser.cs
- Common\SAIF.Common.Files\Edi\Core\Parsers\EdiCompositeDataElementParser.cs
- Common\SAIF.Common.Files\Edi\Core\Parsers\EdiGroupParser.cs
- Common\SAIF.Common.Files\Edi\Core\Builders\SegmentBuilder.cs
- Common\SAIF.Common.Files\Edi\Core\Builders\EdiRecordBuilder.cs
- Common\SAIF.Common.Files\Edi\Core\DateMaker.cs
- Common\SAIF.Common.Files\Edi\EdiFile.cs
- Common\SAIF.Common.Files\Edi\EdiCompositeDataElement.cs
- Common\SAIF.Common.Files\Edi\EdiDataElement.cs
- Common\SAIF.Common.Files\Edi\Segments\IsaSegment.cs
- Common\SAIF.Common.Files\Edi\Segments\GsSegment.cs
- Common\SAIF.Common.Files\Edi\Segments\StSegment.cs
- Common\SAIF.Common.Files\Edi\Segments\SeSegment.cs
- Common\SAIF.Common.Files\Edi\Segments\Ak2Segment.cs
- Common\SAIF.Common.Files\Edi\Segments\IeaSegment.cs
- Common\SAIF.Common.Files\Edi\Segments\BhtSegment.cs
- Common\SAIF.Common.Files\Edi\Segments\DtpSegment.cs
- Common\SAIF.Common.Files\Edi\Segments\Nm1Segment.cs
- Common\SAIF.Common.Files\Edi\Segments\N4Segment.cs
- Common\SAIF.Common.Files\Edi\Segments\CtxSegment.cs
- Common\SAIF.Common.Files\Edi\Segments\CasSegment.cs
- Common\SAIF.Common.Files\Edi\Segments\SvcSegment.cs
- Common\SAIF.Common.Files\Edi\Segments\CompositeProcedureCodeElement.cs
- Common\SAIF.Common.Files\Edi\Segments\HlSegment.cs
- Common\SAIF.Common.Files\Edi\Segments\Ik5Segment.cs
- Common\SAIF.Common.Files\Edi\Segments\DmgSegment.cs
- Common\SAIF.Common.Files\Edi\Segments\HiSegment.cs
- Common\SAIF.Common.Files\Edi\Segments\Sv1Segment.cs
- Common\SAIF.Common.Files\Edi\Segments\Sv2Segment.cs
- Common\SAIF.Common.Files\Edi\Segments\Sv3Segment.cs
- Common\SAIF.Common.Files\Edi\NameBuilder.cs
- Common\SAIF.Common.Files\Edi\Segments\TrnSegment.cs
- Common\SAIF.Common.Files\Edi\Segments\ClpSegment.cs
- Common\SAIF.Common.Files\Edi\Segments\SvSegmentBase.cs
