FACTUM

Worked examples · Logistics

EDI invoices and the check digit.

A freight invoice sent as an X12 210 (Motor Carrier Freight Details and Invoice) is read against the demurrage and detention checklist: its structure, its totals, and the check digit of the container it bills for.

The problem

The Federal Maritime Commission's rule on demurrage and detention billing (46 CFR part 541, effective May 28, 2024) lists the information an invoice must contain. Under § 541.5, an invoice that omits required information eliminates the billed party's obligation to pay the charge.

A container number carries its own check digit, which validates the transmission of the owner code and serial number (ISO 6346). A valid digit shows the number is internally consistent. It does not show that the container exists, or that it is the right one.

Invoices travel as EDI as well as on paper, and the same questions apply to both.

Recorded outputThree certify calls of constructed X12 invoices with fictitious parties: one well formed, one with a single wrong check digit, one whose total disagrees with its own line charges. Each was derived from the engine's own test files, with the equipment owner prefix changed to a fictitious one. No AI engine was configured.

Three invoices

POST /api/tenants/{tenant}/certify?reportType=fmc-demurrage-detention-invoice-completeness&filename={file} · Content-Type: application/edi-x12

Well formed

no findingscheck digit validPBLU4471824

16 segments read under the public X12 layer, version 004010.

One digit of the container number

check digit invalidPBLU4471825
The container number, against its own check digitfindingequipment_check_digit_disagrees
Check how this was answered
this result says this document states its own equipment check digit and the standard's computation disagrees with it. ISO 6346 check digit FAILED: the standard's modulus-11 weighted sum over PBLU447182 gives 4, and the identifier as read ends in 5. The two disagree, so this reading is refused rather than corrected - a check digit says that a reading is wrong, never what the right one is. The three elements are stated separately by the format and every one of them ships verbatim as its own fact; nothing here is composed into a value and nothing is corrected.
cites dteb310a004010_Version_1.pdf N7 Equipment Details - N701 Equipment Initial (DE 206), N702 Equipment Number (DE 207, AN 1/10), N718 Equipment Number Check Digit (DE 761). The check itself is ISO 6346:1995 Annex A, computed by lib/checkDigit.ts.
produced by …/edi/relation-finding@2
Source edi-210-09-mustfail-check-digit-pblu.edi
N7*PBLU*447182****************5
edi-210-09-mustfail-check-digit-pblu.edi, the N7 segment

16 segments read under the public X12 layer, version 004010.

A total that does not add up

check digit validPBLU4471824
The grand total, against its own line chargesfindingtotals_disagree
Check how this was answered
this result says the stated grand total disagrees with the sum of this transaction set's own line charges. 137500 + 25000 = 162500, and the document states 175000; the difference is 12500. Both figures are in the representation the elements declare (implied decimal places are RECORDED and never applied), and NEITHER IS ALTERED: the total and every line charge ship verbatim and this finding sits beside them.
cites dteb310a004010_Version_1.pdf p.91 - L3 Semantic Note 1 "L305 is the total charges" and the L305 element note "Enter the grand total charge of all line items."; the addend is L104 Charge (data element 58) in the L1 Rate and Charges loop.
produced by …/edi/relation-finding@2
Source edi-210-08-mustfail-totals-disagree-pblu.edi
L1*1*275.00*DT*137500****DEM
edi-210-08-mustfail-totals-disagree-pblu.edi, byte 477
L1*2*250.00*FC*25000****DET
edi-210-08-mustfail-totals-disagree-pblu.edi, byte 507
L3*22000*G***175000
edi-210-08-mustfail-totals-disagree-pblu.edi, byte 534

16 segments read under the public X12 layer, version 004010.

A finding is a statement about the document, set in neutral ink under its own name. Each certificate still verifies: the document is wrong, the certificate is not.

What the checklist can answer from EDI

The same checklist reads paper and EDI. From a 210, it locates 2 of its 18 data points, each at its byte position. 2 are refused as ambiguous, because the set carries more than one candidate. 14 are absent, with the code no_fact_answers_this_point: the set does not carry them.

That is stated, not hidden. On EDI the checklist's value is the structure, the totals and the check digit. The paper invoice is read in full on the demonstration page.

resolved2gap14refused2
the bill of lading numberresolvedPBLU2026034771
Check how this was answered
data point dp-bol-number
read under Bill of Lading Number / Value
Source byte 255
N9*BM*PBLU2026034771
edi-210-01-wellformed-pblu.edi, byte 255, 14 bytes
the total amount dueresolved162500
Check how this was answered
data point dp-total-amount-due
read under Net Amount Due / Value
Source byte 213
B3**DDINV2026040601**CC**20260406*162500**20260401*011*OCXY*20260401
edi-210-01-wellformed-pblu.edi, byte 213, 6 bytes
the invoice daterefusedambiguous_data_point
Check how this was answered
data point dp-invoice-date
code ambiguous_data_point
this result says the invoice date: ambiguous_data_point
remedy declare
declaration needed ReportType.dataPoints[].subject|field
Source refused
No span. The set carries more than one candidate, and the engine does not choose.
the daily raterefusedambiguous_data_point
Check how this was answered
data point dp-daily-rate
code ambiguous_data_point
this result says the daily rate: ambiguous_data_point
remedy declare
declaration needed ReportType.dataPoints[].subject|field
Source refused
No span. The set carries more than one candidate, and the engine does not choose.

Boundary

CERTIFIED: this report states which of the fields this checklist asks for were LOCATED on the invoice submitted, and names each field that was not. A field reported as not located is reported as missing from this invoice, without a hedge: where 46 CFR part 541 requires a field, it requires it on the invoice itself, and 46 CFR § 541.5 makes the omission of a required field dispositive.

NOT ATTESTED That every field this checklist asks for is required of this particular invoice is not attested; some apply only to an export, or only to an import. What an omission decides about the charges billed is not attested, and whether they are owed is not determined here. That a located value is correct is not attested; it is certified as the invoice states it.

Check it without Factum

The certificate is published as it was returned. Download it with the key and the Factum Verifier, and run the command. It needs Node 18 or later and nothing else.

Verifiedexit 0

The files: wellformed.json, public-key.txt, edi-210-01-wellformed-pblu.edi, edi-210-09-mustfail-check-digit-pblu.edi, edi-210-08-mustfail-totals-disagree-pblu.edi, SHA256SUMS.

The key is a development key minted for this recording. A valid signature says the holder of this key signed these bytes.

Recorded outputThe verifier's output on this certificate (exit code 0).
node factum-verify.mjs wellformed.json public-key.txt
VERIFIED
  keyId            vd-2b223ab58835e187
  canonical bytes  75344
  barcode          … (the signed barcode, 478 characters, printed in full when you run it)
  surface          FULL - the body carries no surface block, so every claim the run captured is on it

  CERTIFIED BY THIS CHECK: this document was signed by the holder of the private half of the
  key you supplied, over the canonical form named in its signature block, and has not been
  altered by one byte since.
  NOT CERTIFIED BY THIS CHECK: that the key you supplied is the operator's. That comes from
  WHERE YOU OBTAINED THE KEY and from nothing in this file - a valid signature under the wrong
  key is exactly what a forgery looks like.

The certificate is published as it was returned. Download it with the key and the Factum Verifier, and run the command. It needs Node 18 or later and nothing else.

Verifiedexit 0

The files: check-digit.json, public-key.txt.

Recorded outputThe verifier's output on this certificate (exit code 0).
node factum-verify.mjs check-digit.json public-key.txt
VERIFIED
  keyId            vd-2b223ab58835e187
  canonical bytes  79026
  barcode          … (the signed barcode, 488 characters, printed in full when you run it)
  surface          FULL - the body carries no surface block, so every claim the run captured is on it

  CERTIFIED BY THIS CHECK: this document was signed by the holder of the private half of the
  key you supplied, over the canonical form named in its signature block, and has not been
  altered by one byte since.
  NOT CERTIFIED BY THIS CHECK: that the key you supplied is the operator's. That comes from
  WHERE YOU OBTAINED THE KEY and from nothing in this file - a valid signature under the wrong
  key is exactly what a forgery looks like.

The certificate is published as it was returned. Download it with the key and the Factum Verifier, and run the command. It needs Node 18 or later and nothing else.

Verifiedexit 0

The files: totals.json, public-key.txt.

Recorded outputThe verifier's output on this certificate (exit code 0).
node factum-verify.mjs totals.json public-key.txt
VERIFIED
  keyId            vd-2b223ab58835e187
  canonical bytes  78223
  barcode          … (the signed barcode, 492 characters, printed in full when you run it)
  surface          FULL - the body carries no surface block, so every claim the run captured is on it

  CERTIFIED BY THIS CHECK: this document was signed by the holder of the private half of the
  key you supplied, over the canonical form named in its signature block, and has not been
  altered by one byte since.
  NOT CERTIFIED BY THIS CHECK: that the key you supplied is the operator's. That comes from
  WHERE YOU OBTAINED THE KEY and from nothing in this file - a valid signature under the wrong
  key is exactly what a forgery looks like.