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.
Hence, the SingleField class supports all
these functional features, making a number of field-scanner
classes such as SexCodeField much easier to
write.
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 to None.
OLD_TRANSLATOR = None
To write a field-scanner class for a single-column field:
Inherit from SingleField.
Declare a class attribute VALID_CODES
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
the Python 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
translated to F (female). The .maketrans() function does not translate any
codes other than the ones you specify.
Define the usual .scanField() and .flatten() methods required by the FieldItem interface.
Your derived class's .scanField() method
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 "age
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
scan object.
#-- 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
variable 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, “BaseEncounter.copyDitto(): Implement
single-column ditto”.
#-- 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 VALID_CODES string.
#-- 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
the instance.
#-- 5 --
# [ encounter.(fieldName) := a new fieldClass instance
# whose value is (updated) ]
setattr(encounter, fieldName,
fieldClass(encounter, updated))