Quite a number of fields in the encounter record share several functional characteristics:
They occupy a single column on the banding sheet.
The character in this column may be any of a specific set of codes.
The single-column ditto convention applies. (This class can be used for fields that don't support single-column ditto, on the assumption that the bander won't use them there, and the data entry operator won't put them in.)
Examples of such fields include the age code, skull ossification, and all the micro-aging fields.
In addition, in two fields (the age code and the sex code),
we also allow any of a set of obsolete codes. For example,
in the Olden Days, they used
4 for male and
5 for female.
SingleField class supports all
these functional features, making a number of field-scanner
classes such as
SexCodeField much easier to
Here is the interface:
# - - - - - c l a s s S i n g l e F i e l d - - - class SingleField(FieldItem): '''Generic field-scanner class for single-column fields. Exports, other than those inherited: VALID_CODES: [ a string of all valid codes, uppercased ] OLD_TRANSLATOR: [ if this field does not support a set of obsolete codes -> None else -> a translation table in the form produced by string.maketrans() that translates obsolete codes to their current equivalents ] .encounter: [ as passed to constructor, read-only ] .value: [ as passed to constructor, read-only ] SingleField.scanField(encounter, scan, fieldName, desc): # Static [ (encounter is a BaseEncounter object) and (scan is a Scan object) and (fieldName is an attribute name as a string) and (desc is an external description of the field) and if scan starts with a valid field -> scan := scan advanced past that field encounter.(fieldName) := an object representing that field else -> scan := scan advanced no further than end of line Log() +:= error message(s) raise SyntaxError ] SingleField.flatten(object): [ if object is None -> return ' ' else -> return object.value ] '''
We set the default value of the class attribute
OLD_TRANSLATOR = None
To write a field-scanner class for a single-column field:
Declare a class attribute
containing a string that has all the valid code values
in it, in uppercase.
If the field can be blank, include a space character in this string. If a nonblank code is required, omit the space character.
If the field supports translation of an obsolete set of
codes, define a class attribute
OLD_TRANSLATOR that translates old codes to new. You will need to import
string module and use its
.maketrans() function. Here, for example, is the
declaration that translates the old sex codes to the new:
VALID_CODES = "UMFX" OLD_TRANSLATOR = string.maketrans("045", "UMF")
In this example, old sex code
5 will be
F (female). The
.maketrans() function does not translate any
codes other than the ones you specify.
Define the usual
.flatten() methods required by the
Your derived class's
will probably want to use the
SingleField.scanField() class method. The calling
sequence of this method includes one additional argument, a
desc string that is used in error message to
identify what kind of field is involved. For example, for
the age code field, you might pass the string
code" to this argument.
This static method scans a single-column field. The caller
must supply all the arguments required by the
FieldItem.scanField() interface, plus an additional
desc argument that describes the field in
English for error-reporting purposes.
# - - - S i n g l e F i e l d . s c a n F i e l d - - - @staticmethod def scanField(encounter, scan, fieldName, fieldClass, desc): '''Scan a single-column field. '''
The first step is to get the raw field value from the
#-- 1 -- # [ if the line in scan is not at end of line -> # scan := scan advanced 1 # rawText := next character from scan, uppercased # else -> # Log() +:= error message # raise SyntaxError ] try: rawText = scan.move(1).upper() except IndexError: scan.syntax("Expecting a %s field." % desc)
Next we check for single-column ditto, and set the
dittoFree to the dittoed value if
there is one, or to
rawText if ditto isn't
used. This step is the reason we need to use a class
method rather than a static method: we need the derived
class so we can pass it to the
.copyDitto() method. See Section 74.22, “
#-- 2 -- # [ if (rawText is DITTO_CHAR) and it validly dittoes a # character from encounter.compiler.oldEncounter -> # dittoFree := the dittoed character # else if (rawText is DITTO_CHAR) not validly used -> # Log() +:= error message # raise SyntaxError # else -> # dittoFree := rawText ] dittoFree = encounter.copyDitto(rawText, fieldClass, fieldName, scan)
If the derived class has defined an
OLD_TRANSLATOR attribute, we next use that
translator to replace an obsolete code with the current
version, if any.
#-- 3 -- # [ if fieldClass.OLD_TRANSLATOR is None -> # updated := dittoFree # else -> # updated := dittoFree with any old codes from # fieldClass.OLD_TRANSLATOR replaced by their # current equivalents ] if fieldClass.OLD_TRANSLATOR is not None: updated = dittoFree.translate(fieldClass.OLD_TRANSLATOR) else: updated = dittoFree
Next we check that this value is in the
#-- 4 -- # [ if updated is not in fieldClass.VALID_CODES -> # Log() +:= error message # raise SyntaxError ] # else -> I ] if updated not in fieldClass.VALID_CODES: scan.syntax("Code '%s' is not one of the valid " "%s codes (%s)" % (updated, fieldName, fieldClass.VALID_CODES))
We assume that derived classes will use the actual
field value as the
.value attribute stored in
#-- 5 -- # [ encounter.(fieldName) := a new fieldClass instance # whose value is (updated) ] setattr(encounter, fieldName, fieldClass(encounter, updated))