Next / Previous / Contents / Shipman's homepage

74. class BaseEncounter: General output record

This class is the abstract base class for output encounter records. Here is the interface:
# - - - - -   c l a s s   B a s e E n c o u n t e r   - - - - -

class BaseEncounter:
    '''Abstract base class for output encounter records.

          [ compiler is a BaseCompiler object ->
              return a new BaseEncounter object with that compiler ]
        .compiler:     [ as passed to constructor, read-only ]
        .captureCode:  [ CaptureCodeField, or None ]
          [ return self as a string containing a flat-file record ]
        BaseEncounter.scanLine(compiler, scan):  # Static method
          [ (compiler is a BaseCompiler object) and
            (scan is a Scan object) ->
              if the line in scan is a valid encounter record
              in the context of compiler ->
                scan  :=  scan advanced no farther than end of line
                return a new BaseEncounter object representing
                that line
              else -> 
                Log()  +:=  error message
                raise SyntaxError ]

74.1. Design notes for the BaseEncounter class

The BaseEncounter object, and classes derived from it, are the workhorse data structures for representing most of the data on the banding sheet.

In practice, an instance may have many fields, one for each possible field on the banding sheet. However, the exact set of fields will be different for each protocol. For example, the MAPS 2006 protocol does not allow for color bands, but MAWS 2004 protocol does.

Also, some instances may have only a few attributes. For example, the representation of a destroyed band will contain very little other than the band number.

So what we want is an object that holds a set of named values. So far that sounds like a Python dictionary. However, we want more than just a dictionary; we want to be able to add methods such as .flatten() to the object to translate it into flat-file form.

It is quite straightforward to use regular Python attributes to store the fields of the encounter record. One possibility is to have the constructor create all the fields that may be used, and set each unused field to None; fields actually used in the input record will be copied over those values as they are parsed, using the Python function setattr(object, name). Then the .flatten() method will fill in a blank field of the appropriate size for each field that never had a value stored into it.

However, there is a way to avoid this process of setting each field to None initially. Python allows a class to define a special method called .__getattr__() that is called whenever the user uses getattr() or otherwise references an undefined attribute. We can simply define that method so that any attempt to retrieve an undefined attribute will return None.

For details of see Customizing attribute access in the Python online documentation.

So, to sum up, here is the life cycle of a BaseEncounter object:

  1. The caller passes a BaseCompiler instance (with the context of the record) and a Scan instance (with the raw encounter record) to the class method BaseEncounter.scanLine(). That method starts out with a BaseCompiler instance with only a few attributes filled in.

  2. As field values are recognized in the encounter record, their values are stored in attributes of the BaseEncounter record using Python's setattr() function. For example, if we have an encounter record enc and an age code as an AgeCodeField object named age, we store the value like this:

        setattr(enc, 'ageCode', age)

  3. Assuming the encounter record is valid, the BaseEncounter.scanLine() method returns the instance to the caller.

  4. The caller uses the .flatten()method on that instance to convert the content into a flat file record represented as a single string.

    The .flatten() method uses a class attribute called .OUT_FIELD_LIST to do the flattening. This is a list of tuples (class, fieldName), in output order, where the class is the name of a class that has a static method named .flatten(), and fieldName is the name of the attribute in the BaseEncounter instance. For each pair in OUT_FIELD_LIST, we extract the value of the given fieldName using getattr(), so that we get either the stored value, or None if the field was never stored.

    The .flatten() methods in those classes take an argument that is either an instance of the class, or the value None. If it is an instance, the .flatten() method should format that instance's value as a fixed-length format string. If the argument is None, the .flatten() method should return a string of blanks of the same size.

Therefore, the definitive list of field names for each derived variant of the BaseEncounter class will be in its class variable .OUT_FIELD_LIST. Each bit of field-processing logic should use the field name from that list to store its result.