"""Family members' roles with respect to the proband."""
import logging
from collections import defaultdict
from dae.pedigrees.family import Family, Person
from dae.variants.attributes import Role, Sex, Status
logger = logging.getLogger(__name__)
[docs]
class Mating:
"""Class to represent a mating unit."""
def __init__(self, mom_id: str, dad_id: str | None) -> None:
self.mom_id = mom_id
self.dad_id = dad_id
self.mating_id = Mating.build_id(mom_id, dad_id)
self.children: set[str] = set()
[docs]
@staticmethod
def build_id(mom_id: str | None, dad_id: str | None) -> str:
return f"{mom_id},{dad_id}"
[docs]
@staticmethod
def parents_id(person: Person) -> str:
assert person.mom_id is None or isinstance(person.mom_id, str), person
assert person.dad_id is None or isinstance(person.dad_id, str), person
return Mating.build_id(person.mom_id, person.dad_id)
def __repr__(self) -> str:
return f"({self.mating_id}> Mom: {self.mom_id}, Dad: {self.dad_id})"
[docs]
class FamilyRoleBuilder: # pylint: disable=too-few-public-methods
"""Build roles of family members."""
def __init__(self, family: Family) -> None:
self.family = family
self.family_matings = self._build_family_matings()
self.members_matings = self._build_members_matings()
[docs]
def build_roles(self) -> None:
"""Build roles of all family members."""
proband = self._get_family_proband()
if proband is None:
self._assign_unknown_roles()
return
assert proband is not None
self._set_person_role(proband, Role.prb)
self._assign_roles_children(proband)
self._assign_roles_mates(proband)
self._assign_roles_parents(proband)
self._assign_roles_siblings(proband)
self._assign_roles_paternal(proband)
self._assign_roles_maternal(proband)
self._assign_roles_step_parents_and_half_siblings(proband)
self._assign_unknown_roles()
@classmethod
def _set_person_role(cls, person: Person, role: Role) -> None:
assert isinstance(person, Person)
assert isinstance(role, Role)
if person.role is None or person.role == Role.unknown:
if role != person.role:
logger.info(
"changing role for %s from %s to %s",
person, person.role, role)
# pylint: disable=protected-access
person._role = role
person._attributes["role"] = role
def _get_family_proband(self) -> Person | None:
probands = self.family.get_members_with_roles([Role.prb])
if len(probands) > 0:
return probands[0]
for person in self.family.full_members:
is_proband = person.get_attr("proband", False)
# assert isinstance(is_proband, bool), is_proband
if is_proband:
return person
affected = self.family.get_members_with_statuses([Status.affected])
affected = [p for p in affected if p.has_parent()]
if len(affected) > 0:
return affected[0]
return None
def _build_family_matings(self) -> dict[str, Mating]:
matings = {}
for person_id, person in self.family.persons.items():
if person.has_parent():
parents_mating_id = Mating.parents_id(person)
if parents_mating_id not in matings:
parents = Mating(person.mom_id, person.dad_id)
matings[parents_mating_id] = parents
parents_mating = matings.get(parents_mating_id)
assert parents_mating is not None
parents_mating.children.add(person_id)
return matings
def _build_members_matings(self) -> dict[str, set[str]]:
members_matings = defaultdict(set)
for mating_id, mating in self.family_matings.items():
if mating.mom_id is not None:
members_matings[mating.mom_id].add(mating_id)
if mating.dad_id is not None:
members_matings[mating.dad_id].add(mating_id)
return members_matings
def _assign_roles_children(self, proband: Person) -> None:
for mating_id in self.members_matings[proband.person_id]:
mating = self.family_matings[mating_id]
for child_id in mating.children:
child = self.family.persons[child_id]
self._set_person_role(child, Role.child)
def _assign_roles_mates(self, proband: Person) -> None:
for mating_id in self.members_matings[proband.person_id]:
mating = self.family_matings[mating_id]
if (
mating.dad_id is not None
and mating.dad_id != proband.person_id
):
person = self.family.persons[mating.dad_id]
self._set_person_role(person, Role.spouse)
elif mating.mom_id is not None and \
mating.mom_id != proband.person_id:
person = self.family.persons[mating.mom_id]
self._set_person_role(person, Role.spouse)
def _assign_roles_parents(self, proband: Person) -> None:
if not proband.has_parent():
return
if proband.mom is not None:
self._set_person_role(proband.mom, Role.mom)
if proband.dad is not None:
self._set_person_role(proband.dad, Role.dad)
def _assign_roles_siblings(self, proband: Person) -> None:
if not proband.has_parent():
return
parents_mating = self.family_matings[Mating.parents_id(proband)]
for person_id in parents_mating.children:
if person_id != proband.person_id:
person = self.family.persons[person_id]
self._set_person_role(person, Role.sib)
def _assign_roles_paternal(self, proband: Person) -> None:
if proband.dad is None or not proband.dad.has_parent():
return
dad = proband.dad
if dad.dad is not None:
self._set_person_role(dad.dad, Role.paternal_grandfather)
if dad.mom is not None:
self._set_person_role(dad.mom, Role.paternal_grandmother)
grandparents_mating_id = Mating.parents_id(dad)
grandparents_mating = self.family_matings[grandparents_mating_id]
for person_id in grandparents_mating.children:
person = self.family.persons[person_id]
if person.role is not None and person.role != Role.unknown:
continue
if person.sex == Sex.M:
self._set_person_role(person, Role.paternal_uncle)
if person.sex == Sex.F:
self._set_person_role(person, Role.paternal_aunt)
for person_mating_id in self.members_matings[person.person_id]:
person_mating = self.family_matings[person_mating_id]
for cousin_id in person_mating.children:
cousin = self.family.persons[cousin_id]
self._set_person_role(cousin, Role.paternal_cousin)
def _assign_roles_maternal(self, proband: Person) -> None:
if proband.mom is None or not proband.mom.has_parent():
return
mom = proband.mom
if mom.dad is not None:
self._set_person_role(mom.dad, Role.maternal_grandfather)
if mom.mom is not None:
self._set_person_role(mom.mom, Role.maternal_grandmother)
grandparents_mating_id = Mating.parents_id(mom)
grandparents_mating = self.family_matings[grandparents_mating_id]
for person_id in grandparents_mating.children:
person = self.family.persons[person_id]
if person.role is not None and person.role != Role.unknown:
continue
if person.sex == Sex.M:
self._set_person_role(person, Role.maternal_uncle)
if person.sex == Sex.F:
self._set_person_role(person, Role.maternal_aunt)
for person_mating_id in self.members_matings[person.person_id]:
person_mating = self.family_matings[person_mating_id]
for cousin_id in person_mating.children:
cousin = self.family.persons[cousin_id]
self._set_person_role(cousin, Role.maternal_cousin)
def _find_parent_matings(self, parent: Person) -> list[Mating]:
matings = []
for mating in self.family_matings.values():
if parent.sex == Sex.male:
if mating.dad_id == parent.person_id:
matings.append(mating)
else:
if mating.mom_id == parent.person_id:
matings.append(mating)
return matings
def _assign_roles_step_parents_and_half_siblings(
self, proband: Person,
) -> None:
if proband.mom is None or proband.dad is None:
return
if proband.mom is not None:
mom_mates = filter(
lambda x: x.dad_id != proband.dad.person_id, # type: ignore
self._find_parent_matings(proband.mom),
)
for mating in mom_mates:
if mating.dad_id is None:
continue
step_dad = self.family.persons[mating.dad_id]
self._set_person_role(step_dad, Role.step_dad)
maternal_halfsiblings_ids = mating.children
for halfsibling_id in maternal_halfsiblings_ids:
halfsibling = self.family.persons[halfsibling_id]
self._set_person_role(
halfsibling, Role.maternal_half_sibling,
)
if proband.dad is not None:
dad_mates = filter(
lambda x: x.mom_id != proband.mom.person_id, # type: ignore
self._find_parent_matings(proband.dad),
)
for mating in dad_mates:
if mating.mom_id is None:
continue
step_mom = self.family.persons[mating.mom_id]
self._set_person_role(step_mom, Role.step_mom)
paternal_halfsiblings_ids = mating.children
for halfsibling_id in paternal_halfsiblings_ids:
halfsibling = self.family.persons[halfsibling_id]
self._set_person_role(
halfsibling, Role.paternal_half_sibling,
)
def _assign_unknown_roles(self) -> None:
for person in self.family.persons.values():
if person.role is None:
self._set_person_role(person, Role.unknown)