Code Monkey home page Code Monkey logo

stupidedi's Introduction

Stupidedi

Build Status GitHub version Code Climate Inline docs

Screenshot

Stupidedi is a high-quality library for parsing, generating, validating, and manipulating ASC X12 EDI documents. Very roughly, it's jQuery for EDI.

For those unfamiliar with ASC X12 EDI, it is a data format used to encode common business documents like purchase orders, delivery notices, and health care claims. It is similar to XML in some ways, but precedes it by about 15 years; so if you think XML sucks, you will love to hate EDI.

Credits

What problem does it solve?

Transaction set specifications can be enormous, boring, and vague. Trading partners can demand strict adherence (often to their own unique interpretation of the specification) of the documents you generate. However, documents they generate themselves are often non-standard and require flexibility to parse them.

Stupidedi enables you to encode these transaction set specifications directly in Ruby. From these specifications, it will generate a parser to read incoming messages and a DSL to generate outgoing messages. This approach has a huge advantage over writing a parser from scratch, which can be error-prone and difficult to change.

Significant thought was put into the design of the library. Some of the features are described here.

Robust tokenization and parsing

Delimiters, line breaks, and out-of-band data between interchanges are handled correctly. While many trading partners follow common conventions, it only takes one unexpected deviation, like swapping the ":" and "~" delimiters, to render a hand-written parser broken.

Stupidedi handles many edge cases that can only be anticipated by reading carefully between the lines of the X12 documentation.

Instant feedback on error conditions

When generating EDI documents, validation is performed incrementally on each segment. This means the instant your client code violates the specification, an exception is thrown with a meaningful stack trace. Other libraries only perform validation after the entire document has been generated, while some don't perform validation at all.

Stupidedi performs extensive validation and ensures well-formedness. See the human readable documentation in doc/Generating.md for more details.

Encourages readable client code

Unlike other libraries, generating documents doesn't involve naming obscure identifiers from the specification (like C001, DE522 or LOOP2000), for elements of the grammar that don't actually appear in the output.

Like HAML or Builder::XmlMarkup, the DSL for generating documents closely matches terminology from the problem domain. You can see in the example below that code looks very similar to an EDI document. This makes it easy to understand, assuming a reasonable familiarity with EDI.

Efficient parsing and traversing

The parser is designed using immutable data structures, making it thread-safe for runtimes that can utilize multiple cores. In some cases, immutability places higher demand on garbage collection, this has been somewhat mitigated with careful optimization.

Note: Further optimizations are planned for late 2021. Until then, modestly large files can consume excessive memory to the point all CPU time is spent in garbage collection. In more than ten years since this library was put into production, this seems to rarely happen in practice (as X12 files are most often very small), but until these improvements are merged, it can be difficult to work around. See #65 for more discussion. The current work in progress is in the gh-65.3 branch.

Benchmark

segments 1.9.3 1.9.2 rbx-head jruby-1.6.6
1680 2107.90 2007.17 503.14 317.52
3360 2461.54 2420.75 731.71 477.07
6720 2677.29 2620.90 950.63 685.15
13440 2699.88 2663.50 1071.00 897.50
26880 2558.54 2510.51 1124.50 1112.67
53760 2254.94 2164.16 1039.81 1292.62

These benchmarks aren't scientific by any means. They were performed on a MacBook Pro, 2.2GHz Core i7 with 8GB RAM by reading the X222-HC837 fixture data files, in serial. The results show the parser runtime is O(n), linear in the size of the input, but the drop in throughput at 13K+ segments is likely due to memory allocation. The steady increase in throughput on JRuby and Rubinus is probably attributable optimizations performed by the JIT compiler.

Lastly, these results should approximate the performance of document generation with BuilderDsl, except BuilderdsL API should have less overhead, as it skips the tokenizer. On the other hand, BuilderDsl frequently queries the call stack to track provenance of elements in the parse tree. In common real-world use, custom application logic and database access are going to bottleneck performance, rather than Stupidedi.

Helps developers gain familiarity

Why not a commercial translator?

Commercial EDI translators solve a different set of problems. Many focus on translating between EDI and another data format, like XML, CSV, or a relational database. This isn't particularly productive, as you still have to unserialize the data to do anything with it.

What doesn't it solve?

It isn't a translator. It doesn't have bells and whistles, like the commercial EDI translators have, so it...

  • Doesn't convert to/from XML, CSV, etc
  • Doesn't transmit or receive files
  • Doesn't do encryption
  • Doesn't connect to your database
  • Doesn't transmit over a dial-up modem
  • Doesn't queue messages for delivery or receipt
  • Doesn't generate acknowledgements
  • Doesn't have a graphical interface

These features are orthogonal to the problem Stupidedi aims to solve, but they can certainly be implemented with other code taking advantage of Stupidedi.

Alternative libraries

Stupidedi is an opinionated library, and maybe you don't agree with it. Here are a few alternative libraries:

Examples

In addition to these brief examples, see sample code in the notes directory and the human-readable Markdown documentation in doc.

Utilities

Pretty print the syntax tree

$ ./bin/edi-pp spec/fixtures/X222-HC837/1-good.txt
...
        TableVal[Table 3 - Summary](
          SegmentVal[SE: Transaction Set Trailer](
            Nn.value[  E96: Number of Included Segments](45),
            AN.value[ E329: Transaction Set Control Number](0021)))),
      SegmentVal[GE: Functional Group Trailer](
        Nn.value[  E97: Number of Transaction Sets Included](1),
        Nn.value[  E28: Group Control Number](1))),
    SegmentVal[IEA: Interchange Control Trailer](
      Nn.value[  I16: Number of Included Functional Groups](1),
      Nn.value[  I12: Interchange Control Number](905))))
49 segments
49 segments
0.140 seconds

Perform validation on a file

$ ./bin/edi-ed spec/fixtures/X222-HC837/1-bad.txt
[AK905(file spec/fixtures/X222-HC837/1-bad.txt,
       line 16, column 4, is not an allowed value,
       ID.value[ E479: Functional Identifier Code](XX)),
 IK304(file spec/fixtures/X222-HC837/1-bad.txt,
       line 33, column 1,
       missing N4 segment, NM1),
 IK304(file spec/fixtures/X222-HC837/1-bad.txt,
       line 35, column 1,
       missing N4 segment, NM1)]
46 segments
0.177 seconds

Generating, Writing

X12 Writer

require "stupidedi"

# You can customize this to delegate to your own grammar definitions, if needed.
config = Stupidedi::Config.hipaa

b = Stupidedi::Parser::BuilderDsl.build(config)

# These methods perform error checking: number of elements, element types, min/max
# length requirements, conditionally required elements, valid segments, number of
# segment occurrences, number of loop occurrences, etc.
b.ISA "00", nil, "00", nil,
      "ZZ", "SUBMITTER ID",
      "ZZ", "RECEIVER ID",
      "990531", "1230", nil, "00501", "123456789", "1", "T", nil

# The API tracks the current position in the specification (e.g., the current loop,
# table, etc) to ensure well-formedness as each segment is generated.
b.GS "HC", "SENDER ID", "RECEIVER ID", "19990531", "1230", "1", "X", "005010X222"

# The `b.default` value can be used to generate the appropriate value if it can
# be unambigously inferred from the grammar.
b.ST "837", "1234", b.default
  # You can use string representations of data or standard Ruby data types, like Time.
  b.BHT "0019", "00", "X"*30, "19990531", Time.now.utc, "CH"
  b.NM1 b.default, "1", "PREMIER BILLING SERVICE", nil, nil, nil, nil, "46", "12EEER000TY"
  b.PER "IC", "JERRY THE CLOWN", "TE", "3056660000"

  b.NM1 "40", "2", "REPRICER JONES", nil, nil, nil, nil, "46", "66783JJT"
    b.HL "1", nil, "20", "1"

  b.NM1 "85", "2", "PREMIER BILLING SERVICE", nil, nil, nil, nil, "XX", "123234560"
    b.N3  "1234 SEAWAY ST"
    b.N4  "MIAMI", "FL", "331111234"
    b.REF "EI", "123667894"
    b.PER "IC", b.blank, "TE", "3056661111"

  b.NM1 "87", "2"
    b.N3 "2345 OCEAN BLVD"
    b.N4 "MIAMI", "FL", "33111"

b.HL "2", "1", "22", "0"
  b.SBR "S", "18", nil, nil, "12", nil, nil, nil, "MB"

  b.NM1 "IL", "1", "BACON", "KEVIN", nil, nil, nil, "MI", "222334444"
    b.N3  "236 N MAIN ST"
    b.N4  "MIAMI", "FL", "33413"
    b.DMG "D8", "19431022", "F"

b.machine.zipper.tap do |z|
  # The :component, and :repetition parameters can also be specified as elements
  # of the ISA segment, at `b.ISA(...)` above. When generating a document from
  # scratch, :segment and :element must be specified -- if you've parsed the doc
  # from a file, these params will default to whatever was used in the file, or
  # you can override them here.
  separators =
    Stupidedi::Reader::Separators.build :segment    => "~\n",
                                        :element    => "*",
                                        :component  => ":",
                                        :repetition => "^"

  # You can also serialize any subtree within the document (e.g., everything inside
  # some ST..SE transaction set, or a single loop. Here, z.root is the entire tree.
  w = Stupidedi::Writer::Default.new(z.root, separators)
  print w.write()
end

HTML writer

As shown above Stupidedi::Writer::Default will output data encoded in plain x12 format. While Stupidedi::Writer::Claredi will output a formatted HTML string.

Stupidedi::Writer::Claredi#write operates on StringIO.

b.machine.zipper.tap do |z|
  w = Stupidedi::Writer::Claredi.new(z.root)

  File.open('output.html', 'w') { |f| f.write w.write }
end

Json (Hash) Writer

Converting the tree to a JSON document is intentionally not included in the library. However this still may be implemented utilizing the stupidedi API.

Here is one of the possible ways to implement this.

The shown approach allows to define custom traversing logic for the nodes with ability to change hash keys and values to whatever is needed.

Please refer to this readme and these nodes implementation for more information.

Reading, Traversing

require "stupidedi"

config = Stupidedi::Config.hipaa
parser = Stupidedi::Parser.build(config)

input  = if RUBY_VERSION > "1.8"
           File.open("spec/fixtures/X221-HP835/1-good.txt", :encoding => "ISO-8859-1")
         else
           File.open("spec/fixtures/X221-HP835/1-good.txt")
         end

# Reader.build accepts IO (File), String, and DelegateInput
parser, result = parser.read(Stupidedi::Reader.build(input))

# Report fatal tokenizer failures
if result.fatal?
  result.explain{|reason| raise reason + " at #{result.position.inspect}" }
end

# Helper function: fetch an element from the current segment
def el(m, *ns, &block)
  if Stupidedi::Either === m
    m.tap{|m| el(m, *ns, &block) }
  else
    yield(*ns.map{|n| m.elementn(n).map(&:value).fetch })
  end
end

# Print some information
parser.first
  .flatmap{|m| m.find(:GS) }
  .flatmap{|m| m.find(:ST) }
  .tap do |m|
    el(m.find(:N1, "PR"), 2){|e| puts "Payer: #{e}" }
    el(m.find(:N1, "PE"), 2){|e| puts "Payee: #{e}" }
  end
  .flatmap{|m| m.find(:LX) }
  .flatmap{|m| m.find(:CLP) }
  .flatmap{|m| m.find(:NM1, "QC") }
  .tap{|m| el(m, 3, 4){|l,f| puts "Patient: #{l}, #{f}" }}

Testing

rake spec

stupidedi's People

Contributors

adriand avatar apreskill avatar austinmbrown avatar casconed avatar dependabot[bot] avatar irobayna avatar jakedmsnap avatar julienroger avatar kelnadim avatar kfalconer avatar kputnam avatar kyle-query avatar lanxx019 avatar michael-t-shrout avatar milieu avatar mrpasquini avatar mtwstudios avatar nickrivadeneira avatar nsmith avatar pepito2k avatar peretzp avatar petergoldstein avatar samueltc avatar serixscorpio avatar skaczor avatar snyk-bot avatar sofiengwin avatar tamagokun avatar vikaskedia avatar wling avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

stupidedi's Issues

Non-deterministic state

running edi-pp against the latest message I am defining (PS830), I am getting the following output

Non-deterministic state
0.122 seconds

Any ideas as of why?

How to specify a certain set of segments?

Here's a typical transaction set:

ST|214|000056421~
B10|0|0|UPSN~
L11|QVD|TN|O4~
L11|1Z75A3T3YT10727583|2I~
L11|68600 TL 182555|CR|SHPREF1/~
L11|68600 TL 182555|CR|PKGREF1/~
N1|SH|NA|25|75A3T3~
N1|BT||25|75A3T3|01~
LX|1~
AT7|X2|A2|||20130612|213727|LT~
L11|AH-THE ADDRESS HAS BEEN CORREC|RRS|. THE DELIVERY HAS BEEN RESCHE~
L11|DS-THE ADDRESS LABEL IS MISSIN|REC|R ILLEGIBLE~
MAN|CP|E1|ONTARIO HUB|CP|CA|US~
CD3|||E1~
CD3|||R4~
N1|UP|ABE LINCOLN~
N3|123 ANYWHERE AVE~
N4|SOMEWHERE|NY|33332|US~
SE|19|000056421~

What I'm ultimately trying to pull is that set of N1, N3 and N4 towards then end of the set.

But, there will occasionally be an additional set of those under the first occurrence of N1 (and before AT7).

So how would I differentiate those and select only those segments before AT7 or only those towards the end?

Does that make sense?

including gem causes infinite deprecation warnings

I find that including this as a gem causes the following error over and over on tests:

Passing options to #find is deprecated. Please build a scope and then call #find on it.

If I do require: false, then no problems, unless I require the library in a test.

Warn about re-assigning configuration

Configuration documentation should be reviewed (and tangentially, an explanation for the difference between a TransactionSetDef and Guide would be useful). It's likely someone may unknowingly do something like this:

x.register("004010", "SM", "204") { FortyTen::TransactionSetDefs::SM204 }
x.register("004010", "SM", "204") { Guides::FortyTen::SM204 }

... which probably has unintended consequences: the first configuration is overwritten. Printing a warning would be appropriate.

required element ISA02 is blank

I have the have the following definition data element summary definition:

M            ISA02             I02             Authorization Information     M  AN 10/10
Key Point:
Must be 10 spaces

it is a mandatory field but must contain 10 spaces. Any way to work around this?

I am trying to build the output file using:

config = Stupidedi::Config.contrib
    builder = Stupidedi::Builder::BuilderDsl.build(config)

    builder.ISA "00", "          ", "00", "          ",
      "01", "005548384",
      "01", "126444801",
      "990531", "1230", "U", "00200", "63373", "0", "P", ":"

cannot move to next after last node

While generating EDI 856 SH, I am getting this error. As far as I can tell this is a valid transition.

Here is my code

builder.DTM "011",req_date, req_time
  builder.HL "1", nil, "S"
  builder.MEA "PD", "G", weight_total, "LB", "0", "0"

call stack:

 stupidedi (1.2.2) lib/stupidedi/zipper/memoized_cursor.rb:44:in `next'
 - stupidedi (1.2.2) lib/stupidedi/zipper/abstract_cursor.rb:208:in `child'
 - stupidedi (1.2.2) lib/stupidedi/schema/syntax_note.rb:54:in `block in children'
 - stupidedi (1.2.2) lib/stupidedi/schema/syntax_note.rb:54:in `children'
 - stupidedi (1.2.2) lib/stupidedi/versions/functional_groups/002001/syntax_notes.rb:51:in `required'
 - stupidedi (1.2.2) lib/stupidedi/schema/syntax_note.rb:40:in `satisfied?'
 - stupidedi (1.2.2) lib/stupidedi/builder/builder_dsl.rb:174:in `block in critique'
 - stupidedi (1.2.2) lib/stupidedi/builder/builder_dsl.rb:171:in `critique'
 - stupidedi (1.2.2) lib/stupidedi/builder/builder_dsl.rb:48:in `block in segment!'
 - stupidedi (1.2.2) lib/stupidedi/builder/builder_dsl.rb:48:in `segment!'
 - stupidedi (1.2.2) lib/stupidedi/builder/builder_dsl.rb:93:in `method_missing'
 - app/models/edi856_sh2001.rb:201:in `create_edi'

The 4010 PO850 in contrib doesn't work

Some of the segments in the PO850 don't have enough elements to match their definitions. This results in an exception about not having the right number of elements. I'd like to fix it by adding some of them to it, but I would need the PDF that originally informed these definitions.

Type conversion to String in ID elements

For ID elements that are essentially numeric, like HL*03, it would be nice to allow numeric arguments like m.find(:HL, nil, nil, 20). Currently this throws an exception:

ArgumentError: 20 is not allowed in HL03

The workaround is to use strings for all ID elements: m.find(:HL, nil, nil, "20")

Medicare Claim to 837 EDI

Im getting this error when try to generate EDI:
Stupidedi::Exceptions::ParseError: unknown transaction set "HC" "837" "005010X223A1"

Code:

config = Stupidedi::Config.default

config.interchange.register("00501") {Stupidedi::Versions::Interchanges::FiveOhOne::InterchangeDef}
config.functional_group.register("005010") {Stupidedi::Versions::FunctionalGroups::FiftyTen::FunctionalGroupDef}
config.transaction_set.register("005010X223", "HC", "837") { Stupidedi::Guides::FiftyTen::X222A1::HC837I }
b = Stupidedi::Builder::BuilderDsl.build(config)

b = Stupidedi::Builder::BuilderDsl.build(config)

# These methods perform error checking: number of elements, element types, min/max
# length requirements, conditionally required elements, valid segments, number of
# segment occurrences, number of loop occurrences, etc.
b.ISA "00", nil, "00", nil,
      "ZZ", fed_tax_number,
      "ZZ", receiver_id,
      submission_date, submission_time, '^', "00501", "000000111", "1", "#{mode}", ':'

# The API tracks the current position in the specification (e.g., the current loop,
# table, etc) to ensure well-formedness as each segment is generated.
b.GS "HC", fed_tax_number, receiver_id, submission_date_century, submission_time, "1", "X", "005010X223A1"

# The `b.default` value can be used to generate the appropriate value if it can
# be unambigously inferred from the grammar.
b.ST "837", "1234", b.default
  # You can use string representations of data or standard Ruby data types, like Time.
  b.BHT "0019", "00", "0000000333"*3, submission_date_century, submission_time, "CH"
  #Loop:2010AA
  b.NM1 b.default, "1", submitter_name, nil, nil, nil, nil, "46", claim.agency_npi
  b.PER "IC", submitter_contact_name, "EM", submitter_contact_person_email, "TE", submitter_contact_person_phone_number

  b.NM1 "40", "2", receiver_name, nil, nil, nil, nil, "46", receiver_id
  b.HL "1", nil, "20", "1"

  b.PRV "BI", "PXC", provider_taxonomy_code

  b.NM1 "85", "2", submitter_name, nil, nil, nil, nil, "XX", billing_provider_npi_number
  b.N3  billing_provider_address1
  b.N4  billing_provider_city, billing_provider_state, billing_provider_zip_code

  b.REF "EI", fed_tax_number
  b.PER "IC", submitter_contact_name, "EM", submitter_contact_person_email, "TE", submitter_contact_person_phone_number


#loop2000B
b.HL "2", "1", "22", "0"
  b.SBR "P", individual_relationship_code, nil, nil, nil, nil, nil, nil, code_identifying_type_of_claim

  #Loop:2010BA
  b.NM1 "IL", "1", subscriber_last_name, subscriber_first_name, nil, nil, nil, "MI", subscriber_identifier
    b.N3  subscriber.street_address, subscriber_suite_number
    b.N4  subscriber.city, subscriber.state, subscriber.zip_code
    b.DMG "D8", claim.patient_dob_century_format, claim.patient_gender
    b.REF "SY", subscriber_ssn

#loop2010BB
b.NM1 "PR", "2", claim.payer_name, nil, nil, nil, nil, "PI", claim.provider_number
  b.N3 payer_address1
  b.N4 payer_city, payer_state, payer_zip_code


#loop2300
  b.CLM claim_id, claim.invoice_amount, nil, nil, b.composite("03", 'B', '2'), 'Y', 'A', 'Y','Y', 'P'
   #b.DTP "096", "TM", discharge_hour
    # b.DTP "434", "RD8", statement_covers_period
  # b.DTP "435", "DT", admission_date
  # b.CL1 nil, claim.src, claim.status_of_discharge
  #b.HI b.composite('BE', '61', nil, nil, claim.value_codes)



  #ICD9 Diagnosis Codes
  claim.icd_9.each do |icd|
    b.HI b.composite("BK","#{icd}")
  end

#loop2400 SERVICE LINE
claim.receivables.each_with_index do |receivable, index|
  b.LX "#{index+1}"
  # b.NM1 "IL", "1", claim.primary_physician_last_name, claim.primary_physician_first_name, claim.primary_physician_middle_initials, "XX", claim.primary_physician_npi
  #b.SV2 receivable.revenue_code, b.composite("HC","#{receivable.hcpcs_code}"), receivable.receivable_amount, "UN", receivable.service_units
  # b.DTP "472", "D8", receivable.receivable_date
end

  #loop2310A ATTENDING PROVIDER INFORMATION
   #b.NM1 "IL", "1", claim.primary_physician_last_name, claim.primary_physician_first_name, claim.primary_physician_middle_initials, "XX", claim.primary_physician_npi




b.machine.zipper.tap do |z|
  # The :component, and :repetition parameters can also be specified as elements
  # of the ISA segment, at `b.ISA(...)` above. When generating a document from
  # scratch, :segment and :element must be specified -- if you've parsed the doc
  # from a file, these params will default to whatever was used in the file, or
  # you can override them here.
  separators =
      Stupidedi::Reader::Separators.build :segment    => "~\n",
                                          :element    => "*",
                                          :component  => ":",
                                          :repetition => "^"

  # You can also serialize any subtree within the document (e.g., everything inside
  # some ST..SE transaction set, or a single loop. Here, z.root is the entire tree.
  w = Stupidedi::Writer::Default.new(z.root, separators)
  w.write($stdout)
end

Fails on valid file

Doing this:

input = File.open("file.x12", :encoding => "ISO-8859-1")
config = Stupidedi::Config.default
p, result = parser.read(Stupidedi::Reader.build(input))

Fails on this believed-good input:
ISA_00_ 00 _ZZ_PANALPINA 02_ANOC 160503_2119_U_00401_000000001_0_T
GS_SM_PANALPINA_ANOC_20160503_2119_1_X_004010
ST_204_000000001
B2ANOC715658442-01PP
B2A_00
L11_PALA_SCA
L11_TPE540871_HB
L11_69539112301_MB
G62_11_20160430
AT5_DEL
NTE
DISPATCHER: Diane Johnson, [email protected]
S5_1_LD_218.00_L_1_PC
G62_69_20160430_U_0800
G62_69_20160430_K_1000
NTE**SL8D# 8296498DOCK HIGH TRUCK NEEDED
N1_SF_EVA AIRWAYS
N3_BLDG.151 JFK AIRPORT
N4_Jamaica_NY_11430
G61_IC_ISF PAYS THRU EPIC_TE_718-2445500
L5_1_WAFERSTEPPER COMPONENT LT ME ADJ. TOOL SET Q'TY:
S5_2_UL_218.00_L_1_PC
G62_70_20160430_X_1400
N1_ST_ASML C/O DB SCHENKER
N3_6 OLD STONE BREAK ROAD hrs:7day
/week 0700-1900,dock high 50lbs
N4_Ballston Spa_NY_12020
G61_IC_Receiving_TE_518-256-9197
L3_218.00*G********_1_L
SE_26_000000001
GE_1_1
IEA_1_000000001

Rails 4.1 ActiveSupport MultiJSON

I am trying to add stupidedi to my project.

After adding stupidedi, immediately upon logging into my application (I am using Devise) I receive the following error,

ArgumentError - In Rails 4.1, ActiveSupport::JSON.decode no longer accepts an options hash for MultiJSON. MultiJSON reached its end of life and has been removed.

I saw what looked like a similar issue here,
#67

I removed term-ansicolor and tins from the dependencies. I also tried adding the multi_json gem to the stupidedi gem dependencies. Neither approach fixed the error.

Here is what I could find regarding Rails 4.1 and multijson
http://guides.rorlab.org/4_1_release_notes.html

Thank you for your help with this issue, let me know if you would like any additional information.

Floating Fields?

@kputnam Did you ever heard of Usage: Floating?

I have come across a more accurate specs for one of the messages I implemented and realized a segment I struggle with happens to be Floating.

It appears floating means that it can appear anywhere. Any thoughts on how to handle this?

uninitialized constant Stupidedi::Builder::BuilderDsl::Set (NameError)

I was playing around with getting the example builder working in ruby verions 1.9.3 and ruby 2.0.0. However, I kept running the error that is listed in the title. From everything I gathered, the ruby "Set" class was not being included properly.

I was able to include the Set object in either of the following locations and building the sample EDI worked.

  • Sample script file with the builder
  • /lib/stupidedi/builder/builder_dsl
require 'set'

I could put in a pull request for this but I am also unsure if/where you would want the set require to be. Also, I don't know if it's intended for the Set object to be required in the application that interacts with stupidedi. Thoughts?

"can't modify frozen String" from float_val.rb while generating 837 output

Using ruby 2.3.1 and latest stupidedi (1.2.9), I get the following error when trying to generate 837 output. Not sure exactly which field is triggering it, but it's some numeric one based on the stack trace. The code there (in float_val.rb) does look a bit suspicious with regards to frozen strings.

"can't modify frozen String"

stupidedi (1.2.9) lib/stupidedi/versions/functional_groups/005010/element_types/float_val.rb:295:in `to_x12'

Support for reading UTF-8 strings

It seems that stupidedi does not support UTF-8 chacaters when reading out an EDI. I get:

ArgumentError: universe does not contain element "ใ‚"

Digging a bit I found that Stupidedi::Reader::C_BYTES, defined here, only includes ASCII characters and crashes here when other characters are used.

I believe this should be fixable simply by changing the definition of C_BYTES but wondering if there are other considerations that would prevent this from being possible.

HC837i support

Hi,

I need to create HC837p as well as HC837i health claims.
I already have everything for the 837p, but I don't see any code for validating X223HC837P.

Is there any 'easy' way to validate that standard ?
If not, can you guide me a little bit on what should I modify/add to get it to work ? I have the standard documentation and I am willing to make the effort to add the support to the gem but It would help having a little guide on it.

Thanks,
Gonzalo.

Multiple constraints on find not working as expected

I have the following segment...

L11|68600 TL 182555|CR|SHPREF1/~

I'm trying to pull the 68600 TL 182555 reference using the following:

el(m.find(:L11, nil, nil, 'SHPREF1'), 1){|e| puts "Reference: #{e}" }

But that comes up empty.

If I do m.find(:L11, nil, 'CR'), it works. But that isn't a unique identifier, so I need to use SHPREF1.

Is this a bug or am I doing something wrong?

While traversing the document, elements cant be found using find method or element method

I tried traversing a HB271 document but it doesn't generate expected results. Can you just give a small demo as in how to traverse a document. I tried it multiple times with failure.
I tried positioning the cursor at first and could find ISA and GS segment, but nothing from there after and received message saying that segment is unreachable from current state.

Rails4 delegate issue

back trace when I tried to start rails console:
/Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/bundler/gems/stupidedi-67bade054f9e/lib/ruby/module.rb:71:in class_eval': /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/actionpack-4.0.2/lib/abstract_controller/layouts.rb:212: syntax error, unexpected '.' (SyntaxError) class._layout_conditions(*args, &block) ^ from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/bundler/gems/stupidedi-67bade054f9e/lib/ruby/module.rb:71:inblock in delegate'
from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/bundler/gems/stupidedi-67bade054f9e/lib/ruby/module.rb:63:in each' from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/bundler/gems/stupidedi-67bade054f9e/lib/ruby/module.rb:63:indelegate'
from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/actionpack-4.0.2/lib/abstract_controller/layouts.rb:212:in <module:Layouts>' from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/actionpack-4.0.2/lib/abstract_controller/layouts.rb:200:inmodule:AbstractController'
from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/actionpack-4.0.2/lib/abstract_controller/layouts.rb:3:in <top (required)>' from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/actionpack-4.0.2/lib/action_controller/base.rb:203:inclass:Base'
from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/actionpack-4.0.2/lib/action_controller/base.rb:163:in <module:ActionController>' from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/actionpack-4.0.2/lib/action_controller/base.rb:4:in<top (required)>'
from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/rails-observers-0.1.2/lib/rails/observers/action_controller/caching.rb:10:in <module:Caching>' from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/rails-observers-0.1.2/lib/rails/observers/action_controller/caching.rb:2:inmodule:ActionController'
from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/rails-observers-0.1.2/lib/rails/observers/action_controller/caching.rb:1:in <top (required)>' from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/activesupport-4.0.2/lib/active_support/dependencies.rb:229:inrequire'
from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/activesupport-4.0.2/lib/active_support/dependencies.rb:229:in block in require' from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/activesupport-4.0.2/lib/active_support/dependencies.rb:214:inload_dependency'
from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/activesupport-4.0.2/lib/active_support/dependencies.rb:229:in require' from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/bundler/gems/audited-1db8f77a2aed/lib/audited/sweeper.rb:2:in<top (required)>'
from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/activesupport-4.0.2/lib/active_support/dependencies.rb:229:in require' from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/activesupport-4.0.2/lib/active_support/dependencies.rb:229:inblock in require'
from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/activesupport-4.0.2/lib/active_support/dependencies.rb:214:in load_dependency' from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/activesupport-4.0.2/lib/active_support/dependencies.rb:229:inrequire'
from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/bundler/gems/audited-1db8f77a2aed/lib/audited/adapters/active_record.rb:15:in <top (required)>' from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/activesupport-4.0.2/lib/active_support/dependencies.rb:229:inrequire'
from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/activesupport-4.0.2/lib/active_support/dependencies.rb:229:in block in require' from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/activesupport-4.0.2/lib/active_support/dependencies.rb:214:inload_dependency'
from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/activesupport-4.0.2/lib/active_support/dependencies.rb:229:in require' from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/bundler/gems/audited-1db8f77a2aed/lib/audited-activerecord.rb:2:in<top (required)>'
from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/bundler-1.3.1/lib/bundler/runtime.rb:72:in require' from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/bundler-1.3.1/lib/bundler/runtime.rb:72:inblock (2 levels) in require'
from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/bundler-1.3.1/lib/bundler/runtime.rb:70:in each' from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/bundler-1.3.1/lib/bundler/runtime.rb:70:inblock in require'
from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/bundler-1.3.1/lib/bundler/runtime.rb:59:in each' from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/bundler-1.3.1/lib/bundler/runtime.rb:59:inrequire'
from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/bundler-1.3.1/lib/bundler.rb:132:in require' from /Users/K/dev/code/mom/qt_mcwhiskers/config/application.rb:12:in<top (required)>'
from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/railties-4.0.2/lib/rails/commands.rb:60:in require' from /Users/K/.rvm/gems/ruby-2.0.0-p353@momcorp/gems/railties-4.0.2/lib/rails/commands.rb:60:in<top (required)>'
from bin/rails:4:in require' from bin/rails:4:in

'

Json output

Hey,

For the sake of argument is there a way in which I can get json output of the transformed document.

Unable to create 210: 'unknown transaction set "IM" "210" "004010"'

I'm trying to create a 210. When I try to create the ST, I get the above error.

This is quite the grammar engine you've built here. AFAICT, this is happening because we're missing a suitable entry in lib/stupidedi/config/contrib/004010/guides.

I've done:
`builder.ISA "00", nil, "00", nil, "02", "ANOC", "ZZ", "PANALPINA", "160611", "1457", "U", "00400", "1", "0", "T", ">"

builder.GS "IM", "ANOC", "PANALPINA", "20160611", "1457", "1", "X", "004010"

builder.ST "210", "0001"`

at which point I get the error. I'd be grateful to know if this is a bug or I'm doing something wrong.

Many thanks.

834 fails on parsing

https://github.com/kputnam/stupidedi/blob/master/spec/fixtures/X220-BE834/3-good.txt
Fails on parsing 834 data:

TransmissionVal(
  InterchangeVal[00501](
    SegmentVal[ISA: Interchange Control Header](
      ID.value[  I01: Authorization Information Qualifier](00: No Authorization Information Present (No Meaningful Information in I02)),
      AN.value[  I02: Authorization Information](..........),
      ID.value[  I03: Security Information Qualifier](01: Password),
      AN.value[  I04: Security Information](SECRET....),
      ID.value[  I05: Interchange ID Qualifier](ZZ: Mutually Defined),
      AN.value[  I06: Interchange Sender ID](SUBMITTERS.ID..),
      ID.value[  I05: Interchange ID Qualifier](ZZ: Mutually Defined),
      AN.value[  I07: Interchange Receiver ID](RECEIVERS.ID...),
      DT.value[  I08: Interchange Date](XX03-01-01),
      TM.value[  I09: Interchange Time](12:53:ss),
      SeparatorElementVal.value[I65](^),
      ID.value[  I11: Interchange Control Version Number](00501: Standards Approved for Publication by ASC X12 Procedures Review Board through October 2003),
      Nn.value[  I12: Interchange Control Number](905),
      ID.value[  I13: Acknowledgment Requested](1: Interchange Acknowledgment Requested (TA1)),
      ID.value[  I14: Interchange Usage Indicator](T: Test Data),
      SeparatorElementVal.value[I15](:)), 
    FunctionalGroupVal[005010](
      SegmentVal[GS: Functional Group Header](
        ID.value[ E479: Functional Identifier Code](BE: Benefit Enrollment and Maintenance),
        AN.value[ E142: Application's Sender Code](SENDER CODE),
        AN.value[ E124: Application Receiver's Code](RECEIVER CODE),
        DT.value[ E373: Date](1999-12-31),
        TM.value[ E337: Time](08:02:ss),
        Nn.value[  E28: Group Control Number](1),
        ID.value[ E455: Responsible Agency Code](X: Accredited Standards Committee X12),
        AN.value[ E480: Version / Release / Identifier Code](005010X220)), 
      InvalidEnvelopeVal(
        unknown transaction set "BE" "834" "005010X220",
        InvalidSegmentVal[ST],
        InvalidSegmentVal[BGN],
        InvalidSegmentVal[N1],
        InvalidSegmentVal[N1],
        InvalidSegmentVal[INS],
        InvalidSegmentVal[REF],
        InvalidSegmentVal[REF],
        InvalidSegmentVal[DTP],
        InvalidSegmentVal[NM1],
        InvalidSegmentVal[PER],
        InvalidSegmentVal[N3],
        InvalidSegmentVal[N4],
        InvalidSegmentVal[DMG],
        InvalidSegmentVal[HD],
        InvalidSegmentVal[DTP],
        InvalidSegmentVal[LX],
        InvalidSegmentVal[NM1],
        InvalidSegmentVal[SE]), 
      SegmentVal[GE: Functional Group Trailer](
        Nn.value[  E97: Number of Transaction Sets Included](1),
        Nn.value[  E28: Group Control Number](1))), 
    SegmentVal[IEA: Interchange Control Trailer](
      Nn.value[  I16: Number of Included Functional Groups](1),
      Nn.value[  I12: Interchange Control Number](905))))
22 segments
0.139 seconds

Looks as if it does not like the transaction set BE. Ive also run this over some "valid" 834 files in my company with the same result, getting stuck in set BE.

Any ideas?

830 PO v2001 - BFR segment cannot be reached from the current state

trying to parse something that loos like this:

SA*00*          *00*          *01*054481205      *01*126564801      *140801*0623*U*00200*000008573*0*P*:!
GS*PO*054481305*126224801*140801*0623*8573*X*002001!
ST*830*8573!
BFR*04**140801*DL*A*140801*140807*140801**CP!

I am able to iterate through all segments but for some reason, can't do it to BFR, instead of using .iterate I have to use something along these lines:

edi_data.first.flatmap do |isa|
      isa.iterate(:GS) do |gs|
        gs.find(:ST).flatmap{|x| x.find(:BFR) }
        .tap do |bfr|
          puts "#{bfr.element(1).map(&:node).fetch(0)}"
        end 
...

which works but I am confused as to why .iterate does not find that segment. I get following message Stupidedi::Exceptions::ParseError - BFR segment cannot be reached from the current state:

I think I might have introduced a bug while adding this particular message. Note: using edi-pp and edi-ed, both behave the data properly as expected.

Any thoughts as to why st.iterate(:BFR) might not behave as expected?

SH856 - Parsing Specific Loop / Navigating Document

I am having a hard time understanding exactly how to navigate with the API.

There are multiple Loop definitions for HL (one for the shipment, and one for each line item).

I am able to extract information for the first HL loop, but not the second one.

Here is the sample file:

ISA*00*          *00*          *ZZ*941714834VANP  *ISA Prefix*ISA Identifier *141001*1224*U*00401*410011224*0*P*>~
GS*SH*9417148340022*GSIdentifier*20141001*1224*8*X*004010~
ST*856*0007~
BSN*00*478157*20141001~
DTM*011*20141001~
HL*1**S~
TD5**2*FDEP*J~
REF*L2*1412105525~
N1*ST*COMPANY NAME~
N3*100 INFORMATION SUPER HIGHWAY*SUITE 66~
N4*BEVERLY HILLS*CA*90210~
HL*2*1*I~
LIN*1*BP*077775136920~

SN1**1*EA~
PRF*1412105525~
REF*WY*testwaybill95642634~
HL*3*2*X~
REF*BG*CLB1140010001~
SDQ*EA**ZZ*1~
SE*18*0007~
GE*1*8~
IEA*1*410011224~

Ruby Code:

            parser.first
              .flatmap{|m| m.sequence(:GS, :ST, :BSN, :DTM, :HL) }
              .tap do |m|
                el(m.find(:TD5), 3){|e| puts "Carrier SCAC: #{e}" }
              end
              .tap do |m|
                el(m.find(:REF), 2){|e| puts "OrderID: #{e}" }
              end
              .flatmap {|m| m.sequence(:HL) }
              .tap do |m|
                el(m.find(:REF), 1){|e| puts "WayBill: #{e}" }
              end

Output:

Carrier SCAC: FDEP
OrderID: 1412105525
Stupidedi::Exceptions::ParseError: REF segment cannot be reached from the current state

For more requirements, I'd only need the Carrier, OrderID, and a single WayBill for each document if that will make a difference.

edi-pp reports unknown transaction set "PS" "830" "004010" when using ruby 2.2.0

Sample EDI files that parsed correctly prior to ruby2.2.0 are failing now

InvalidEnvelopeVal(
        unknown transaction set "PS" "830" "004010",
        InvalidSegmentVal[ST],
        InvalidSegmentVal[BFR],
        InvalidSegmentVal[N1],
        InvalidSegmentVal[N1],
        InvalidSegmentVal[LIN],
        InvalidSegmentVal[UIT],
        InvalidSegmentVal[PID],
        InvalidSegmentVal[N1],
        InvalidSegmentVal[SDP],
        InvalidSegmentVal[FST],
        InvalidSegmentVal[FST],
        InvalidSegmentVal[FST],
        InvalidSegmentVal[FST],
        InvalidSegmentVal[FST],
        InvalidSegmentVal[FST],
        InvalidSegmentVal[CTT],
        InvalidSegmentVal[SE]),

214: E66, E706 elements declared mandatory

I'm trying to create a 214 (Carrier Shipment Status Notification). I'm trying to create the N1 element,

b.N1 "SH", "SILVERADO VINEYARDS C/O DFS", "1"

but I'm being told that N103 and N104 elements are required.

Stupidedi::Exceptions::ParseError: value is too short in element N103

This would appear to be in functional_groups/004010/segment_defs/N1.rb.

I'm a little new to this EDI business, but from specifications I read (eg http://www.ryder.com/supply-chain/~/media/Ryder/Files/SupplyChain/EDIspecs/carrier_214.ashx) that these elements should not be marked as required.

I believe the correct code would be:

          N1  = s::SegmentDef.build(:N1, "Name",
            "To identify a party by type of organization, name, and code",
            e::E98  .simple_use(r::Mandatory,  s::RepeatCount.bounded(1)),
            e::E93  .simple_use(r::Mandatory, s::RepeatCount.bounded(1)),
            e::E66  .simple_use(r::Optional, s::RepeatCount.bounded(1)),
            e::E67  .simple_use(r::Optional, s::RepeatCount.bounded(1)),
            e::E706 .simple_use(r::Optional,   s::RepeatCount.bounded(1)),
            e::E98  .simple_use(r::Optional,   s::RepeatCount.bounded(1)),

Many thanks for this project, and for your help.

Memory/performance

Hi there,

I've been doing some benchmarking and am puzzled by the memory footprint i'm seeing. Do you expect MRI to use up 2.5+gb of RAM to parse (not pp) a 9mb SC832 doc, with run times well over 4min?

Seems a little... excessive. Is there a trick I'm missing?

Problem parsing - ideas needed

When I first implemented message RA 820 v3010, I was getting lots of non-deterministic problems and with your guidance we collapse some loops and items and both tools: edi-pp and edi-ed parse the data correctly.

My problem is that if I manually traverse the tree, I get to a loop of N1 items which belong to a particular segment and the parser parses all the N1 not just the ones that belong to the parent.

Here is my code:

      edi_data.first.flatmap do |isa|
        isa.iterate(:GS) do |gs|
          gs.iterate(:ST) do |st|
            gs.find(:ST).flatmap{|x| x.find(:BPS) }
            .tap do |bps|
            end
            gs.find(:ST).flatmap{|x| x.find(:REF,'R2') }
            .tap do |ref|
            end
            gs.find(:ST).flatmap{|x| x.find(:DTM) }
            .tap do |dtm|
            end
            gs.find(:ST).flatmap{|x| x.find(:N1,'SU') }
            .tap do |n1|
            end
            gs.find(:ST).flatmap{|x| x.find(:LS,'A') }
            .tap do |ls|
              ls.iterate(:N1, 'HH') do |n1|
                puts "N1 HH #{n1.element(2).map(&:node).fetch(0)}"
                n1.find(:N1, 'HH').flatmap{|x| x.find(:RMT,'IV') }
                .tap do |rmt|
                 @seller_invoice_number  = "#{rmt.element(2).map(&:node).fetch(0)}"
                  @total_gross_amount     = "#{rmt.element(3).map(&:node).fetch(0)}"
                  puts "RMT IV [$#{@total_gross_amount}] #{@seller_invoice_number}"
                  puts "==========================="
             end
                  n1.iterate(:N1, 'LI') do |n11|
                    puts "N1 LI #{n11.element(2).map(&:node).fetch(0)}"
                  end

The problem is on the last item N1 LI which it iterates through all the LI on the file, not the ones just belonging to the parent N1

Iterating ISA segments doesn't work

When we're positioned on an ISA segment, calling find(:ISA) never finds the next ISA segment. This may be due to a regression caused by #7, if we're ignoring

Instruction[ISA](pop: 1, drop: 0, push: InterchangeState)

when searching with #find because

Instruction[ISA](pop: 2, drop: 0, push: TransmissionState)

is favored, since it pops more nesting levels. This is one case were we actually don't create a new parent container, since TransmissionVal is the root and shouldn't have siblings. The fix might require a special case in ConstraintTable.

hash.present? conflict between rails 4.2.4 and tins 1.x

Recently when I attempted to upgrade to use rails 4.2.4, along with stupidedi, I came across a method definition conflict.
stupidedi depends on term-ansicolor, which depends on tins. Tins add present? method to Hash.
rails 4.2.4 also add present? method to Hash in its active support core extensions.
The semantics of the above two methods are different.
Does stupidedi depend on term-ansicolor only because of being able to color the terminal output? If so, is there some way I can turn it off and remove that dependency? or other suggestions? Thanks.

Extra space on TD5 segment

While generating a SH856, I am getting an extra space on TD5 on the second field (E66)

Example:

`TD5B2 XYZZM``

TD5 is defined as:

          TD5 = s::SegmentDef.build(:TD5, "Carrier Details (Routing Sequence/Transit Time)",
            "To specify the carrier and sequence of routing and provide transit time information",
            e::E133.simple_use(r::Optional,  s::RepeatCount.bounded(1)),
            e::E66 .simple_use(r::Mandatory,  s::RepeatCount.bounded(1)),
            e::E67 .simple_use(r::Mandatory,  s::RepeatCount.bounded(1)),
            e::E91 .simple_use(r::Relational, s::RepeatCount.bounded(1)))

and

E66 is defined as:

E66   = t::ID.new(:E66  , "Identification Code Qualifier"        , 1, 2,
            s::CodeList.build(
              "1"  => "D-U-N-S Number, Dun & Bradstreet",
              "2"  => "Standard Carrier Alpha Code (SCAC)",
...

Any ideas?

composite elements - error parsing

I got the following composite element definition

          C001  = Schema::CompositeElementDef.build(:C001,
            "Composite Unit of Measure",
            "To identify a composite unit of measure",
            E355.component_use(r::Mandatory, s::RepeatCount.bounded(1)),
            E0  .component_use(r::Optional),
            E0  .component_use(r::Optional),
            E355.component_use(r::Optional,  s::RepeatCount.bounded(1)),
            E0  .component_use(r::Optional),
            E649.component_use(r::Optional,  s::RepeatCount.bounded(1)))

the data trying to parse is

UIT*PC>>>LB>>17.5*61.95

I am getting this exception

/usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/ruby/try.rb:20:in `try': undefined method `>' for #<Stupidedi::Schema::ComponentElementUse:0x3                    ffe442be9cc ...> (NoMethodError)
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/versions/functional_groups/004010/element_types/float_val.rb:16:in `ini                    tialize'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/versions/functional_groups/004010/element_types.rb:42:in `new'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/versions/functional_groups/004010/element_types.rb:42:in `copy'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/element_use.rb:139:in `initialize'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/element_use.rb:145:in `new'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/element_use.rb:145:in `copy'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/element_def.rb:95:in `block in initialize'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/element_def.rb:95:in `map'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/element_def.rb:95:in `initialize'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/element_def.rb:101:in `new'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/element_def.rb:101:in `copy'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/element_use.rb:205:in `initialize'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/element_use.rb:211:in `new'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/element_use.rb:211:in `copy'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/segment_def.rb:35:in `block in initialize'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/segment_def.rb:35:in `map'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/segment_def.rb:35:in `initialize'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/segment_def.rb:41:in `new'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/segment_def.rb:41:in `copy'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/segment_use.rb:43:in `initialize'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/segment_use.rb:49:in `new'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/segment_use.rb:49:in `copy'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/loop_def.rb:40:in `block in initialize'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/loop_def.rb:40:in `map'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/loop_def.rb:40:in `initialize'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/loop_def.rb:48:in `new'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/loop_def.rb:48:in `copy'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/table_def.rb:31:in `block in initialize'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/table_def.rb:31:in `map'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/table_def.rb:31:in `initialize'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/table_def.rb:38:in `new'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/table_def.rb:38:in `copy'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/transaction_set_def.rb:23:in `block in initialize'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/transaction_set_def.rb:23:in `map'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/transaction_set_def.rb:23:in `initialize'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/transaction_set_def.rb:83:in `new'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/schema/transaction_set_def.rb:83:in `build'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/contrib/004010/transaction_set_defs/PS830.rb:9:in `<module:TransactionS                    etDefs>'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/contrib/004010/transaction_set_defs/PS830.rb:4:in `<module:FortyTen>'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/contrib/004010/transaction_set_defs/PS830.rb:3:in `<module:Contrib>'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/contrib/004010/transaction_set_defs/PS830.rb:2:in `<module:Stupidedi>'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/contrib/004010/transaction_set_defs/PS830.rb:1:in `<top (required)>'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/config.rb:125:in `block (3 levels) in contrib'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/config/transaction_set_config.rb:67:in `call'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/config/transaction_set_config.rb:67:in `at'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/builder/states/transaction_set_state.rb:76:in `push'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/builder/generation.rb:82:in `block (2 levels) in insert'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/builder/generation.rb:36:in `each'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/builder/generation.rb:36:in `block in insert'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/builder/generation.rb:27:in `each'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/builder/generation.rb:27:in `insert'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/builder/generation.rb:14:in `block in read'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/reader/result.rb:102:in `call'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/reader/result.rb:102:in `deconstruct'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/either.rb:102:in `flatmap'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/lib/stupidedi/builder/generation.rb:12:in `read'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/bin/edi-pp:25:in `block (2 levels) in <top (required)>'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/bin/edi-pp:24:in `open'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/bin/edi-pp:24:in `block in <top (required)>'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/bin/edi-pp:18:in `each'
        from /usr/local/lib/ruby/gems/2.0.0/gems/stupidedi-1.1.0/bin/edi-pp:18:in `<top (required)>'
        from /usr/local/bin/edi-pp:23:in `load'
        from /usr/local/bin/edi-pp:23:in `<main>'

I believe I have things setup properly on my end.

Any thoughts?

Example file fails parser

The example file from the example fails with a InvalidEnvelopeVal:

./bin/edi-pp spec/fixtures/X222-HC837/1-good.txt


TransmissionVal(
  InterchangeVal[00501](
    SegmentVal[ISA: Interchange Control Header](
      ID.value[  I01: Authorization Information Qualifier](00: No Authorization Information Present (No Meaningful Information in I02)),
      AN.value[  I02: Authorization Information](..........),
      ID.value[  I03: Security Information Qualifier](01: Password),
      AN.value[  I04: Security Information](SECRET....),
      ID.value[  I05: Interchange ID Qualifier](ZZ: Mutually Defined),
      AN.value[  I06: Interchange Sender ID](SUBMITTERS.ID..),
      ID.value[  I05: Interchange ID Qualifier](ZZ: Mutually Defined),
      AN.value[  I07: Interchange Receiver ID](RECEIVERS.ID...),
      DT.value[  I08: Interchange Date](XX03-01-01),
      TM.value[  I09: Interchange Time](12:53:ss),
      SeparatorElementVal.value[I65](^),
      ID.value[  I11: Interchange Control Version Number](00501: Standards Approved for Publication by ASC X12 Procedures Review Board through October 2003),
      Nn.value[  I12: Interchange Control Number](905),
      ID.value[  I13: Acknowledgment Requested](1: Interchange Acknowledgment Requested (TA1)),
      ID.value[  I14: Interchange Usage Indicator](T: Test Data),
      SeparatorElementVal.value[I15](:)), 
    FunctionalGroupVal[005010](
      SegmentVal[GS: Functional Group Header](
        ID.value[ E479: Functional Identifier Code](HC: Health Care Claim),
        AN.value[ E142: Application's Sender Code](SENDER CODE),
        AN.value[ E124: Application Receiver's Code](RECEIVER CODE),
        DT.value[ E373: Date](1999-12-31),
        TM.value[ E337: Time](08:02:ss),
        Nn.value[  E28: Group Control Number](1),
        ID.value[ E455: Responsible Agency Code](X: Accredited Standards Committee X12),
        AN.value[ E480: Version / Release / Identifier Code](005010X222)), 
      InvalidEnvelopeVal(
        unknown transaction set "HC" "837" "005010X222",
        InvalidSegmentVal[ST],
        InvalidSegmentVal[BHT],
        InvalidSegmentVal[NM1],
        InvalidSegmentVal[PER],
        InvalidSegmentVal[NM1],
        InvalidSegmentVal[HL],
        InvalidSegmentVal[PRV],
        InvalidSegmentVal[NM1],
        InvalidSegmentVal[N3],
        InvalidSegmentVal[N4],
        InvalidSegmentVal[REF],
        InvalidSegmentVal[NM1],
        InvalidSegmentVal[N3],
        InvalidSegmentVal[N4],
        InvalidSegmentVal[HL],
        InvalidSegmentVal[SBR],
        InvalidSegmentVal[NM1],
        InvalidSegmentVal[N3],
        InvalidSegmentVal[N4],
        InvalidSegmentVal[DMG],
        InvalidSegmentVal[NM1],
        InvalidSegmentVal[N4],
        InvalidSegmentVal[REF],
        InvalidSegmentVal[HL],
        InvalidSegmentVal[PAT],
        InvalidSegmentVal[NM1],
        InvalidSegmentVal[N3],
        InvalidSegmentVal[N4],
        InvalidSegmentVal[DMG],
        InvalidSegmentVal[CLM],
        InvalidSegmentVal[REF],
        InvalidSegmentVal[HI],
        InvalidSegmentVal[LX],
        InvalidSegmentVal[SV1],
        InvalidSegmentVal[DTP],
        InvalidSegmentVal[LX],
        InvalidSegmentVal[SV1],
        InvalidSegmentVal[DTP],
        InvalidSegmentVal[LX],
        InvalidSegmentVal[SV1],
        InvalidSegmentVal[DTP],
        InvalidSegmentVal[LX],
        InvalidSegmentVal[SV1],
        InvalidSegmentVal[DTP],
        InvalidSegmentVal[SE]), 
      SegmentVal[GE: Functional Group Trailer](
        Nn.value[  E97: Number of Transaction Sets Included](1),
        Nn.value[  E28: Group Control Number](1))), 
    SegmentVal[IEA: Interchange Control Trailer](
      Nn.value[  I16: Number of Included Functional Groups](1),
      Nn.value[  I12: Interchange Control Number](905))))
49 segments
0.156 seconds

"non-deterministic machine state" when generating 837 2310E/2010F

Hi, I'm trying to convert a (horrible) hand-generated EDI "generator" to use stupidedi. Slowly figuring things out, but ran into this issue (and a few others, but separate tickets for those).

When trying to generate the 837 output, I'm getting:

"non-deterministic machine state: NM1 Rendering Provider Name, NM1 Service Facility Location Name, NM1 Ambulance Pick-up Location, NM1 Ambulance Drop-off Location"

This happens if I have both 2310E (Ambulance Pickup Location) and 2310F (Ambulance Dropoff Location) defined, does not happen with only one of them. So the presence of both is confusing the parser, even though they should be unambigious.

The relevant code snippet is:

        config = Stupidedi::Config.hipaa
        b = Stupidedi::Builder::BuilderDsl.build(config)
...
              b.CLM claim.original_id,
                    claim.claim_amount,
                    nil, nil,
                    b.composite(parameters.place_of_service_code, 'B', '1'),
                    'Y', 'A', 'Y', 'Y'

              b.DTP '454', 'D8',
                  claim.claim_date

              b.HI b.composite(*(claim.procedure_code.split ':'))

# 2310E
              b.NM1 'PW', '2'
              b.N3 claim.pickup_address
              b.N4 claim.pickup_city, claim.pickup_state, claim.pickup_postcode

# 2310F, HAVING THIS SEGMENT CAUSES THE ERROR
              b.NM1 '42', '2'
              b.N3 claim.dropoff_address
              b.N4 claim.dropoff_city, claim.dropoff_state, claim.dropoff_postcode

              b.LX '1'

              b.SV1 b.composite('HC', claim.hcpcs_code),
                    claim.claim_amount,
                    'UN', '1', nil, nil, b.composite('1')

              b.DTP '472', 'D8',
                    claim.claim_date
...

Any ideas on how I could get around this? Everything else is now working fine, but the client wants to have both the pickup and the dropoff addresses included per claim.

Generator example returns parse error

I am looking to use this gem to generate 837 professional claim file. ButI can't get the example provided for generating claims to work. The following error is return when trying to generate the ST data segment:

Stupidedi::Exceptions::ParseError: unknown transaction set "HC" "837" "005010X222"

Do I have to set some config settings for the transaction header?

writing document fails (no method error "present?")

Trying to generate an EDI document using the sample code from the documentation:

b.machine.zipper.tap do |z|
  # The :component, and :repetition parameters can also be specified as elements
  # of the ISA segment, at `b.ISA(...)` above. When generating a document from
  # scratch, :segment and :element must be specified -- if you've parsed the doc
  # from a file, these params will default to whatever was used in the file, or
  # you can override them here.
  separators =
    Stupidedi::Reader::Separators.build :segment    => "~\n",
                                        :element    => "*",
                                        :component  => ":",
                                        :repetition => "^"

  # You can also serialize any subtree within the document (e.g., everything inside
  # some ST..SE transaction set, or a single loop. Here, z.root is the entire tree.
  w = Stupidedi::Writer::Default.new(z.root, separators)
  w.write($stdout)
end

Getting the following no method error:

/Users/mkruk/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stupidedi-1.2.6/lib/stupidedi/reader/separators.rb:53:in `select': undefined method `present?' for ":":String (NoMethodError)
Did you mean?  prepend
    from /Users/mkruk/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stupidedi-1.2.6/lib/stupidedi/reader/separators.rb:53:in `characters'
    from /Users/mkruk/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stupidedi-1.2.6/lib/stupidedi/writer/default.rb:19:in `write'
    from script/generate_997.rb:59:in `block in <main>'
    from /Users/mkruk/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stupidedi-1.2.6/lib/stupidedi/either.rb:152:in `deconstruct'
    from /Users/mkruk/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stupidedi-1.2.6/lib/stupidedi/either.rb:127:in `tap'
    from script/generate_997.rb:44:in `<main>'

Thought that it was my ruby version, upgraded to 2.3.0, but still no luck. It seems like maybe this is an issue with refinements? Any help would be appreciated.

Iterate error: block did not return an instance of Either

I'm using the following to iterate over segments:

a.flatmap do |isa|
  isa.iterate(:GS) do |gs|
    gs.iterate(:ST) do |st|
      st.tap do |m|
         el(m.find(:L11, nil, '2I'), 1){|e| puts "Tracking Number: #{e}" }
      end
    end
  end
end

That works well, except after it's successfully iterated through all of the ST's, it throws this error:

TypeError: block did not return an instance of Either
   from /bundler/gems/stupidedi-499b645a6675/lib/stupidedi/either.rb:107:in `flatmap'
   from (irb):107
   from /gems/railties-3.2.13/lib/rails/commands/console.rb:47:in `start'
   from /gems/railties-3.2.13/lib/rails/commands/console.rb:8:in `start'
   from /gems/railties-3.2.13/lib/rails/commands.rb:41:in `<top (required)>'
   from script/rails:6:in `require'
   from script/rails:6:in `<main>'

Segment cannot be reached from the current state

I'm getting this error:

Stupidedi::Exceptions::ParseError: AT7 segment cannot be reached from the current state

When I run this:

a.flatmap{|m| m.sequence(:GS, :ST) }.tap do |m|
  m.find(:AT7)
end

Here's an example transaction set:

ST|214|000056391~
B10|0|0|UPSN~
L11|QVD|TN|O4~
L11|1Z6VX489YN03535798|2I~
N1|SH|NA|25|6VX489~
N1|BT||25|6VX489|01~
LX|1~
AT7|X8|AJ|||20130612|232947|LT~
AT7|||AB|BG|20130612~
L11|2U-UPS WILL ATTEMPT DELIVERY|REC|KAGE WILL NOT BE TRANSFERRED T~
MAN|CP|E1|OKC EDMOND|CP|OK|US~
CD3|||R4~
N1|UP|NA~
SE|14|000056391~

If I run that find on other elements (like L11 or N1) it works fine, but AT7 and MAN throw that error.

Leverage ruby 2.3.0 frozen string literal pragma?

@kputnam Now that ruby 2.3.0 has been released I am wondering if we should try experimenting with the new frozen string literal pragma? In theory sounds like stupidedi could benefit from it, i.e: less memory usage and faster processing. Thoughts?

Strict SegmentDef validations

Check that any element marked Relational actually has a corresponding SyntaxRule and that each SyntaxRule uses valid segments indexes.

Incorrect variable use in exception block?

In file \stupidedi\lib\stupidedi\guides\004010\guide_builder.rb

line 83

              unless e_requirement.forbidden?
                unless e_arguments.length == u.definition.component_uses.length
                  raise Exceptions::InvalidSchemaError,
                    "composite element #{u.definition.id} at #{segment_def.id}" <<
                    "#{element_index} has #{u.definition.element_uses.length}" <<
                    " component elements but #{e_arguments.length} were given"
                end

variable ....element_uses.length should be ...component_uses.length

right now I get the following exception when hitting the InvalidSchemaError exception:

guide_builder.rb:83:in `block in Segment': undefined method `element_uses' for #<Stupidedi::Schema::CompositeElementDef:0x3fb26d5df2a0 ...> (NoMethodError)

stupidedi does not add end segments

In the example for generating an EDI file on README.md, the declarations of the end segments are omitted. I assumed that this was because stupidedi would generate them for me. However, when I copy that example into a ruby script and then change the definition of config to the following:

config = Stupidedi::Config.default
config.interchange.register("00501") {Stupidedi::Versions::Interchanges::FiveOhOne::InterchangeDef}
config.functional_group.register("005010") {Stupidedi::Versions::FunctionalGroups::FiftyTen::FunctionalGroupDef}
config.transaction_set.register("005010X222", "HC", "837") {Stupidedi::Guides::FiftyTen::X222::HC837P }
b = Stupidedi::Builder::BuilderDsl.build(config)

I have to change this definition or else it doesn't work at all. Anyway, when I do this, the output I get is missing the SE, GE, and IEA segments.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.