3
\$\begingroup\$

For a project, I'm using 3 columns to define teeth positions in a database. For these positions, I want to generate numbers according to this link.

Currently, I'm using a Teeth and Section enum, which has the numbers according to the link. The Section defines which range, and the Teeth defines the number.

class Section(Enum):
    """
    A enum of possible teeth sections

    UR = UPPER RIGHT
    UL = UPPER LEFT
    LR = LOWER RIGHT
    LL = LOWER LEFT
    """

    UL = 10
    UR = 20
    BL = 30
    BR = 40


class Teeth(Enum):
    """
    A clean enumeration of possible teeth variations
    """

    CENTRAL_INCISOR = 1
    LATERAL_INCISOR = 2
    CANINE = 3
    PREMOLAR_1ST = 4
    PREMOLAR_2ND = 5
    MOLAR_1ST = 6
    MOLAR_2ND = 7
    WISDOM = 8

The 3 columns teeth(from the Teeth enum), upper (bool) and left (bool). I currently have a simple if-statemenent.

def resolve_value(self):
    section: Section

    if self.upper:
        section = Section.UL if self.left else Section.UR
    else:
        section = Section.BL if self.left else Section.BR

    return section.value + self.teeth.value

Is there any way you guys would improve this? Keep in mind that it also goes the way around, teeth positions can be created with for example an input 48 which resolves to the bottom right wisdom's teeth.

The model class looks like this; where resolve_value is a method of:

class TeethPosition(Model):
    id = Column(Integer, primary_key=True)
    teeth = Column(Enum(Teeth), nullable=False)   
    upper = Column(Boolean, nullable=False)
    left = Column(Boolean, nullable=False)

    def __init__(self, teeth: Teeth, upper: bool, left: bool) -> None:
        super().__init__(teeth=teeth, upper=upper, left=left)
\$\endgroup\$
3
  • 1
    \$\begingroup\$ Can you show your whole program? \$\endgroup\$
    – Reinderien
    Jan 22 at 16:02
  • \$\begingroup\$ Also, posted the whole model class \$\endgroup\$
    – Bas
    Jan 22 at 16:07
  • \$\begingroup\$ The linked page doesn't make sense -- it claims teeth are numbered from 1-16 and 17-32, but the graphic shows teeth from 10-48. \$\endgroup\$ Jan 22 at 20:35

2 Answers 2

4
\$\begingroup\$

Keeping your current model, you can create an enum for all teeth <-> teeth codes dynamically.

class AllTeeth(Enum):
    _ignore_ = "section tooth AllTeeth"
    AllTeeth = vars()
    for section in Section:
        for tooth in Teeth:
            AllTeeth[f"{section.name}_{tooth.name}"] = section.value + tooth.value

This will let you look up teeth by code or by name:

>>> AllTeeth["UL_CANINE"]
<AllTeeth.UL_CANINE: 13>

>>> AllTeeth(48)
<AllTeeth.BR_WISDOM: 48>

If you then want to convert these into your current model structure, you can add methods (or properties) to get the required booleans from section:

@property
def is_upper(self):
    return self.value <= 20

@property
def is_left(self):
    return self.value % 20 == 10

And then add a method to AllTeeth to get the required info to make a model.

def as_row(self):
    tooth_index = self.value % 10
    section = Section(self.value - tooth_index)
    return Teeth(tooth_index), section.is_upper, section.is_left

Alternately, you could just store the enum in the database, and expose the 'upper', 'left' things through properties on the model (which would forward those requests onto the underlying enum). Of course, if you want to use those properties in SQL that would be a bad option.

As a note, going the other way you can use class methods to turn the model rows into enum lookups, e.g. doing this for Section:

@classmethod
def from_booleans(cls, is_upper, is_left):
    return cls(40 - 10 * is_left - 20 * is_upper)
\$\endgroup\$
2
  • \$\begingroup\$ Thankyoui! Looks good. Will the if logic keep the same? \$\endgroup\$
    – Bas
    Jan 22 at 18:41
  • \$\begingroup\$ Should do, this doesn't actually change any of your code, just adds a simpler interface :-) \$\endgroup\$ Jan 23 at 23:04
2
\$\begingroup\$

Ignoring for now that this is SQLAlchemy and just looking at the data representation itself: I find the Section class to be a little bit more difficult to use as a lookup than it should be. With no conditionals, you should be able to send your upper and left somewhere and get back a value. I suggest using a simple tuple-to-integer lookup dictionary:

from enum import Enum
from typing import NamedTuple


class ToothKind(Enum):
    """
    A clean enumeration of possible teeth variations
    """

    CENTRAL_INCISOR = 1
    LATERAL_INCISOR = 2
    CANINE = 3
    PREMOLAR_1ST = 4
    PREMOLAR_2ND = 5
    MOLAR_1ST = 6
    MOLAR_2ND = 7
    WISDOM = 8


OFFSETS = {
    # upper, left
    (True, False): 10,
    (True, True): 20,
    (False, True): 30,
    (False, False): 40,
}


class Tooth(NamedTuple):
    kind: ToothKind
    upper: bool
    left: bool

    @property
    def value(self) -> int:
        return OFFSETS[self.upper, self.left] + self.kind.value


def test() -> None:
    # https://www.orthodonticexprts.com/wp-content/uploads/2017/07/Teeth-Numbers.png

    assert Tooth(kind=ToothKind.CENTRAL_INCISOR, upper=True,  left=False).value == 11
    assert Tooth(kind=ToothKind.CANINE         , upper=False, left=False).value == 43
    assert Tooth(kind=ToothKind.MOLAR_2ND      , upper=True,  left=True ).value == 27
    assert Tooth(kind=ToothKind.LATERAL_INCISOR, upper=False, left=True ).value == 32
    assert Tooth(kind=ToothKind.MOLAR_1ST      , upper=False, left=False).value == 46


if __name__ == '__main__':
    test()
\$\endgroup\$
2
  • \$\begingroup\$ Enum for class ToothKind could use auto(). \$\endgroup\$
    – Kate
    Jan 22 at 20:05
  • 3
    \$\begingroup\$ @Kate I don't think that would be a very good idea - the values are actually meaningful in this case, determining the position value of the tooth overall; so I think it's best to be explicit \$\endgroup\$
    – Reinderien
    Jan 22 at 20:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.