Showing posts with label group. Show all posts
Showing posts with label group. Show all posts

Friday, April 17, 2009

Maintain index variables for the segment groups

How often do you find that you need to index the repeating segment groups in your extended rules. I hardly ever do a map where this is not needed. You know, you're down at the detail level and you have to selectively map the N1's or the REF's. So you go and write the piece of code and then the compiler says that additional indices are needed to represent this data, and it's a pain to dig up all those index values.

Say in our 850 example, you're down in the N1 group (2/350), child of PO1 (2/010) and you need to iterate through your REF segment (2/390). It happens.

Even if you have prudently cleaned up the segment and group id's, you still have this nuisance of trying to do this:

$2390_REF[index(1)][index(2)][idx_REF].#0128 ...

Those index values can get confusing and can lead to performance questions if they are needed frequently. While the code may look simple enough, it is not clearly obvious from the code what the segment group hierarchy really is. Furthermore, there is a temptation to copy such code from another section in which the indices may not apply and thus produce questionable results.

The convention I follow is to maintain a running group level, grplvl, and an index. I want to avoid hardcoding the group level and keep it relative to its parent group.
We would like to capture those values for their scope and hold them in variables., and we would like these variables to follow a fixed convention.

This is where our document_grplvl = 0 comes into play. A group at the document level is a child of the document. Let us take a look at the entry to the header level N1 group (1/310).

In the group's onBegin() rule I define a standard preamble:

integer this_N1_grplvl, this_N1;

this_N1_grplvl = document_grplvl + inc;
this_N1 = index(this_N1_grplvl);

Now, anytime in my group that I need the current index, I do not need to use the index function and run the risk of using the wrong index number. The variable this_N1 will have the value that I need.

If I had reason in there to reference the REF segment (1/350), then my code is simply like this:

qual = $1350_REF[this_N1][idx_REF].#0128;

This code is also very portable. If I am going to work with the detail level N1 (2/350), the preamble is copied with only a simple change:

integer this_N1_grplvl, this_N1;

this_N1_grplvl = this_PO1_grplvl + inc;
this_N1 = index(this_N1_grplvl);

Now you see, it is taken for granted that the PO1 group (2/010) had its preamble in place. Since this N1 group is a child of PO1, we simply increment off PO1's grplvl.

Another case: say we need to flag that the bill-to and ship-to addresses were actually present in the header, and then report an error at document.onEnd() if one was missing. Or consider a real-life case I had in which the ship-to and ship-from were optional in themselves, but if either one was used they were both required. Also, if carrier was present, then ship-from and ship-to were both required. We could use booleans to flag that the N1 entity was present, but since the segment groups are 1-based, I like to keep a variable capturing the group that had it, and 0 (zero) if it is not present. This way if I want to use it at document.onEnd(), I can.

What I do for this is at the document.onBegin() rule, I define index capture variables, like this:

integer the_N1_CA, the_N1_SF, the_N1_ST;

the_N1_CA = zero;
the_N1_SF = zero;
the_N1_ST = zero;
Now when I am in the N1 group, I can then do my test. There is a little gotcha here in that if you are going to be testing for validation, you must capture the value at the field level. Such a rule is really in proper context in the group.onEnd() rule, since it concerns the overall group. But in the validation pass on the map, field data isn't always visible in the onEnd() rule, and so errors are falsely reported.

What I do for this is in the N1 group's preamble, I add another variable called this_N1_entity.

string[3] this_N1_entity;

this_N1_entity = "";

In N1's 0098 element, all I have is this_N1_entity = #0098; Now I can use that variable in my onEnd() rule safely. I can also use it further down, as the current N1 entity may influence which REF's I am interested in capturing. So now, to tie this back to our the_N1_... variables, our N1 group's onEnd() may look like this now:

if this_N1_entity = "CA" then begin
the_N1_CA = this_N1;
end
else if this_N1_entity = "SF" then begin
the_N1_SF = this_N1;
end
else if this_N1_entity = "ST" then begin
the_N1_ST = this_N1;
end

This code will always remember the last occurrence of any of these entities. But if we wanted to capture only the first occurrence and ignore others, we can do that too, very easily, as shown with a modified CA test:

if this_N1_entity = "CA" then begin
if the_N1_CA = zero then begin
// capture it
end
else begin
// call this an error or ignore it
end
end

The test at the document.onEnd() is now greatly simplified:

if the_N1_CA <> zero then begin
if the_N1_SF = zero | the_N1_ST = zero then begin
// CA present, but SF or ST is missing
end
end
else if the_N1_SF <> zero & the_N1_ST = zero then begin
// SF present without ST
end
else if the_N1_SF = zero & the_N1_ST <> zero then begin
// ST present without SF
end
By keeping proper indices for the group hierarchy, it greatly simplifies your group array index codes whenever you need to reference them. The extended rules retain very high portability. They can be copied from one group to another with only simple and well-represented changes.

Thursday, April 16, 2009

Segment and Group Identifiers for EDIFACT

This article is further to my earlier article on Segment and Group Identifiers for X12. In this article I discuss how I apply this technique to EDIFACT.

With EDIFACT there are no tables of Header, Detail, Summary. Even though the transaction set (called Message in EDIFACT) still conceptually has that structure, the segments are numbered sequentially straight through. They use 4-digit numbers, so we just use the same methodology. The segment groups each have their own unique 4-digit sequence number, distinct from the segments (so nice!), and are in themselves sequentially numbered, making for a nice little naming convention we can put in the group descriptions.

Looking at the D.00A ORDERS as an example, I would identify the first few segments with descriptions like this:

0020_BGM  Beginning of Message
0030_DTM Date/Time/Period
0040_PAI Payment Instructions
0050_ALI Additional Information
0060_IMD Item Description
0070_FTX Free Text
0080_GIR Related Identification Numbers
0090_RFF SG1: RFF-DTM
0100_RFF Reference
0110_DTM Date/Time/Period
...
Notice the 0090, which is the first segment group. I always put SG1, SG2, ... into the description followed by the segment id range as is normally found in the EDIFACT documentation. Also, I don't worry about the _grp suffix, since it has a unique number anyway. I only added the _grp in X12 because I needed it to make the identifier unique. It is perfectly appropriate to be consistent and name it 0090_RFF_grp if you prefer.

Once again, this naming convention always pays off with much-more readable, and less error-prone extended rules, and a much more maintainble map for the client.

Wednesday, April 15, 2009

Clean up the Segment and Group Identifiers

This step I find invaluable when creating a new map. An hour or so spent up front to clean up the identifiers will make the rest of development much easier, make error reports and debugging much easier, and improve readability down the road. I have never seen this done on any maps other than those which I wrote, and I really wish others would follow such a standard. When this step is avoided, extended rules can remain brutally cryptic, and when done, become so much cleaner to work with. In this post, I discuss the technique I always use.

When you create a new map, the EDI template is imported from the standard.mdb MS Access file. The field and record names in the map are generated at load time from a query on this database. By default, the field names are all modeled after the element identifiers and the records after the segment identifiers. The repeating segment groups have "loop ids" assigned arbitrarily from the Sterling file, and do not relate to the actual segment positions.

This creates a few problems as the map editor requires record and group identifiers to be unique, and it strives to make the field identifiers also unique across the board. Using the segment id for the record id is fine at the beginning, say with BEG, CUR, REF, PER, etc. But once these segments occur again, such as inside a group, the map editor then has to uniquify the name with something like REF:2, DTM:4 and so on. This leads to confusion as this :2 and :4 are not much more than random numbers.

This leads to even further confusion with the groups. The SAC group might be given a name like 1000_SAC and the AMT group might be 2000_AMT. These numbers are meaningless to the transaction.

What I always do is to include the true segment position with the segment id. This makes it genuinely unique, and very self-documenting.

Take for example the good old 850 from 004010. As we know, X12 divides the documents into tables of segments, typically Header, Detail, and Summary, numbered as 1-2-3. In 004010, the segment positions have 3 digits. So I simply make 4-digit numbers. In such an 850, I will rename the segment records to something like this:

1020_BEG
1040_CUR
1050_REF
1060_PER
...
The group presents a slight problem since X12 does not assign a separate number to the group itself, as EDIFACT does. In our 850 example, the first group is SAC at 1/120, so I just append _grp to the group id, like this:

1120_SAC_grp
1120_SAC
1125_CUR

Notice how very nicely now we have the 1125_CUR totally distinct from the 1040_CUR. Furthermore, the number distinctly identifies the segment's true position within the transaction set. Now your group references within your extended rules.

Let's say at your document.onEnd() rule you have a reason to iterate through your header SAC groups. The code for the extended rule is self-documenting:

integer max_SAC, idx;
string[4] qual;

max_SAC = count($1120_SAC[*]);
idx = one;
while idx <= max_SAC do begin
qual = $1120_SAC[idx].#1300;
if qual = "G830" then begin
...
end
idx = idx + inc;
end

From this code, it is obvious to the reader which SAC group is being searched here.

As an additional practice, I usually replace the segment descriptions with mixed-case, as all upper-case is awkward to read, and often gets clipped in the map editor.

It takes about an hour to go through a new map and correct all of the segment and group names. I always update them all, even if I am not going t use the segment. I find this hour is always very well-spent, and an hour spent here will save me many hours of coding and debugging time. To make such changes later would require fixing all of the extended rules, which might then be incorrectly updated leading to more bugs.

While this initial clean up may seem to be tedious or redundant, it is an effort that always pays off.