diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0903ad6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +Capture.PNG +__pycache__ +scrap.py diff --git a/Bad_GEDCOM_test_data.ged b/Bad_GEDCOM_test_data.ged new file mode 100644 index 0000000..e3f8a64 --- /dev/null +++ b/Bad_GEDCOM_test_data.ged @@ -0,0 +1,573 @@ +0 HEAD +0 NOTE Fake Family Tree with good and bad inputs to test +0 @I1@ INDI +1 NAME Mark /Eff/ +1 SEX M +1 BIRT +2 DATE 8 FEB 1969 +1 DEAT +2 DATE 10 MAR 1980 +1 FAMS @F1@ +0 @I2@ INDI +1 NAME Jess /Eff/ +1 SEX F +1 BIRT +2 DATE 8 MAR 1967 +1 FAMS @F1@ +0 @I3@ INDI +1 NAME Troy /Johnson/ +1 SEX M +1 BIRT +2 DATE 9 FEB 1988 +1 DEAT +2 DATE 9 FEB 2000 +1 FAMS @F2@ +0 @I4@ INDI +1 NAME Sammy /Johnson/ +1 SEX M +1 BIRT +2 DATE 7 FEB 1988 +1 FAMS @F2@ +0 @I5@ INDI +1 NAME Joe /Shmoe/ +1 SEX M +1 BIRT +2 DATE 1 FEB 1976 +1 FAMS @F3@ +0 @I6@ INDI +1 NAME Mary /Shmoe/ +1 SEX F +1 BIRT +2 DATE 3 MAR 1977 +1 FAMS @F3@ +0 @I7@ INDI +1 NAME Jimmy /Shmoe/ +1 SEX M +1 BIRT +2 DATE 2 MAR 1998 +1 FAMC @F3@ +0 @I8@ INDI +1 NAME Sammy /Shmoe/ +1 SEX F +1 BIRT +2 DATE 8 APR 2007 +1 FAMC @F3@ +0 @I9@ INDI +1 NAME John /Old/ +1 SEX M +1 BIRT +2 DATE 8 APR 1007 +1 DEAT +2 DATE 10 JUL 2007 +1 FAMS @F4@ +0 @I10@ INDI +1 NAME Jackie /Old/ +1 SEX F +1 BIRT +2 DATE 1 JAN 1850 +1 FAMS @F4@ +0 @I11@ INDI +1 NAME Bobby /Bourne/ +1 SEX M +1 BIRT +2 DATE 10 MAR 2000 +1 FAMS @F5@ +0 @I12@ INDI +1 NAME Bella /Bourne/ +1 SEX F +1 BIRT +2 DATE 10 MAR 2001 +1 FAMS @F5@ +0 @I13@ INDI +1 NAME Johnny /Sway/ +1 SEX M +1 BIRT +2 DATE 10 MAR 1998 +1 FAMS @F6@ +0 @I14@ INDI +1 NAME Jenny /Sway/ +1 SEX F +1 BIRT +2 DATE 12 MAR 1990 +1 FAMS @F6@ +0 @I15@ INDI +1 NAME Matthew /Kennedy/ +1 SEX M +1 BIRT +2 DATE 10 MAR 1990 +1 FAMS @F7@ +0 @I16@ INDI +1 NAME Missy /Kennedy/ +1 SEX F +1 BIRT +2 DATE 10 MAR 2000 +1 FAMS @F7@ +0 @I17@ INDI +1 NAME Johnson /Deere/ +1 SEX M +1 BIRT +2 DATE 1 JAN 1990 +1 FAMS @F8@ +0 @I18@ INDI +1 NAME Emily /Deere/ +1 SEX F +1 BIRT +2 DATE 3 FEB 1991 +1 FAMS @F8@ +0 @I19@ INDI +1 NAME Rick /James/ +1 SEX M +1 BIRT +2 DATE 5 MAR 1978 +1 FAMS @F9@ +0 @I20@ INDI +1 NAME Linda /James/ +1 SEX F +1 BIRT +2 DATE 3 MAR 1977 +1 FAMS @F9@ +0 @I21@ INDI +1 NAME Future /Trunks/ +1 SEX M +1 BIRT +2 DATE 10 MAR 2020 +1 DEAT +2 DATE 15 MAR 2050 +1 FAMS @F10@ +0 @I22@ INDI +1 NAME Mai /Trunks/ +1 SEX F +1 BIRT +2 DATE 1 JAN 2022 +1 FAMS @F10@ +0 @I23@ INDI +1 NAME Matt /Smith/ +1 SEX M +1 BIRT +2 DATE 10 JAN 1960 +1 FAMS @F11@ +1 FAMS @F12@ +0 @I24@ INDI +1 NAME Jen /Smith/ +1 SEX F +1 BIRT +2 DATE 11 JAN 1960 +1 FAMS @F11@ +1 FAMS @F13@ +0 @I25@ INDI +1 NAME Max /John/ +1 SEX M +1 BIRT +2 DATE 12 JAN 1960 +1 FAMS @F13@ +0 @I26@ INDI +1 NAME Jess /Smith/ +1 SEX F +1 BIRT +2 DATE 13 JAN 1960 +1 FAMS @F12@ +0 @I27@ INDI +1 NAME Jessica /Old/ +1 SEX F +1 BIRT +2 DATE 20 JAN 1998 +1 FAMC @F4@ +0 @I28@ INDI +1 NAME Dad /Fif/ +1 SEX M +1 BIRT +2 DATE 1 JAN 1970 +1 FAMS @F14@ +0 @I29@ INDI +1 NAME Mom /Fif/ +1 SEX F +1 BIRT +2 DATE 1 JAN 1975 +1 FAMS @F14@ +0 @I30@ INDI +1 NAME One /Fif/ +1 SEX F +1 BIRT +2 DATE 1 JAN 2000 +1 FAMC @F14@ +0 @I31@ INDI +1 NAME Two /Fif/ +1 SEX F +1 BIRT +2 DATE 1 JAN 2000 +1 FAMC @F14@ +0 @I32@ INDI +1 NAME Three /Fif/ +1 SEX F +1 BIRT +2 DATE 1 JAN 2002 +1 FAMC @F14@ +0 @I33@ INDI +1 NAME Four /Fif/ +1 SEX M +1 BIRT +2 DATE 1 JAN 2002 +1 FAMC @F14@ +0 @I34@ INDI +1 NAME Five /Fif/ +1 SEX F +1 BIRT +2 DATE 1 JAN 2002 +1 FAMC @F14@ +0 @I35@ INDI +1 NAME Six /Fif/ +1 SEX F +1 BIRT +2 DATE 1 JAN 2002 +1 FAMC @F14@ +0 @I36@ INDI +1 NAME Seven /Fif/ +1 SEX M +1 BIRT +2 DATE 1 JAN 2002 +1 FAMC @F14@ +0 @I37@ INDI +1 NAME Eight /Fif/ +1 SEX M +1 BIRT +2 DATE 1 JAN 2002 +1 FAMC @F14@ +0 @I38@ INDI +1 NAME Nine /Fif/ +1 SEX F +1 BIRT +2 DATE 1 JAN 2004 +1 FAMC @F14@ +0 @I39@ INDI +1 NAME Ten /Fif/ +1 SEX M +1 BIRT +2 DATE 1 JAN 2004 +1 FAMC @F14@ +0 @I40@ INDI +1 NAME El /Fif/ +1 SEX F +1 BIRT +2 DATE 1 JAN 2004 +1 FAMC @F14 +0 @I41@ INDI +1 NAME Twelve /Fif/ +1 SEX M +1 BIRT +2 DATE 1 JAN 2004 +1 FAMC @F14@ +0 @I42@ INDI +1 NAME Thirteen /Fif/ +1 SEX M +1 BIRT +2 DATE 6 JAN 2006 +1 FAMC @F14@ +0 @I43@ INDI +1 NAME Fourteen /Fif/ +1 SEX M +1 BIRT +2 DATE 1 JAN 2006 +1 FAMC @F14@ +0 @I44@ INDI +1 NAME Fifteen /Fif/ +1 SEX M +1 BIRT +2 DATE 1 JAN 2006 +1 FAMC @F14@ +0 @I45@ INDI +1 NAME Johnny /James/ +1 SEX M +1 BIRT +2 DATE 1 JAN 1960 +1 DEAT +2 DATE 2 FEB 1998 +1 FAMS @F15@ +0 @I46@ INDI +1 NAME Jessica /Joseline/ +1 SEX F +1 BIRT +2 DATE 1 JAN 1962 +1 DEAT +2 DATE 1 JAN 2000 +1 FAMS @F15@ +0 @I47@ INDI +1 NAME Jimmy /James/ +1 SEX M +1 BIRT +2 DATE 20 DEC 1998 +1 FAMC @F15@ +0 @I48@ INDI +1 NAME Jacob /James/ +1 SEX M +1 BIRT +2 DATE 1 MAR 2003 +1 FAMC @F15@ +0 @I49@ INDI +1 NAME Jackie /James/ +1 SEX F +1 BIRT +2 DATE 3 FEB 1999 +1 FAMC @F15@ +0 @I50@ INDI +1 NAME Eric /Dove/ +1 SEX M +1 BIRT +2 DATE 20 JUN 1972 +1 FAMS @F16@ +0 @I51@ INDI +1 NAME Ann /Joene/ +1 SEX F +1 BIRT +2 DATE 30 JUL 1984 +1 FAMS @F16@ +0 @I52@ INDI +1 NAME Nick /Jackson/ +1 SEX M +1 BIRT +2 DATE 9 SEP 1994 +1 FAMS @F17@ +0 @I53@ INDI +1 NAME Marsha /Tolen/ +1 SEX F +1 BIRT +2 DATE 8 MAR 1986 +1 FAMS @F17@ +0 NOTE US03: the following individual has a birthday 1 day after date of death +0 @I54@ INDI +1 NAME James /Nicholas/ +1 SEX M +1 BIRT +2 DATE 27 FEB 1998 +1 DEAT +2 DATE 26 FEB 1998 +0 NOTE US03: the following individual has a birthday 5 years after date of death +0 @I55@ INDI +1 NAME Peter /Tosh/ +1 SEX M +1 BIRT +2 DATE 15 MAR 2005 +1 DEAT +2 DATE 15 MAR 2000 +0 NOTE US03: the following individual died the same day he was born, no error +0 @I56@ INDI +1 NAME Stevie /Wonder/ +1 SEX M +1 BIRT +2 DATE 10 FEB 2002 +1 DEAT +2 DATE 10 FEB 2002 +0 NOTE US16: The following 9 individuals are from the same extended family, where an individual marries their descendant +0 @I57@ INDI +1 NAME John /Leffe/ +1 SEX M +1 BIRT +2 DATE 8 MAR 1900 +1 FAMS @F18@ +0 @I58@ INDI +1 NAME Jane /Leffe/ +1 SEX F +1 BIRT +2 DATE 10 MAR 1900 +1 FAMS @F18@ +0 @I59@ INDI +1 NAME Lauren /Leffe/ +1 SEX F +1 BIRT +2 DATE 8 MAR 1921 +1 FAMC @F18@ +0 @I60@ INDI +1 NAME Bill /Leffe/ +1 SEX M +1 BIRT +2 DATE 8 JAN 1921 +1 FAMC @F18@ +1 FAMS @F19@ +0 @I61@ INDI +1 NAME Candice /Leffe/ +1 SEX F +1 BIRT +2 DATE 12 JAN 1921 +1 FAMS @F19@ +0 @I62@ INDI +1 NAME Jeff /Leffe/ +1 SEX M +1 BIRT +2 DATE 12 JAN 1941 +1 FAMC @F19@ +1 FAMS @F20@ +0 @I63@ INDI +1 NAME Emma /Leffe/ +1 SEX F +1 BIRT +2 DATE 10 JAN 1941 +1 FAMS @F20@ +0 @I64@ INDI +1 NAME Allen /Leffe/ +1 SEX M +1 BIRT +2 DATE 10 MAR 1961 +1 FAMC @F20@ +0 @I65@ INDI +1 NAME Ava /Leffe/ +1 SEX F +1 BIRT +2 DATE 1 JAN 1961 +1 FAMC @F20@ +1 FAMS @F21@ +0 NOTE -----------------Start of the Families------------------- +0 @F1@ FAM +1 HUSB @I1@ +1 WIFE @I2@ +1 MARR +2 DATE 2 APR 1993 +0 @F2@ FAM +1 HUSB @I3@ +1 WIFE @I4@ +1 MARR +2 DATE 9 MAR 2007 +1 DIV +2 DATE 10 JAN 2009 +0 NOTE Family that tests birth before marriage and after divorce +0 @F3@ FAM +1 HUSB @I5@ +1 WIFE @I6@ +1 MARR +2 DATE 4 APR 1999 +1 DIV +2 DATE 5 APR 2002 +0 NOTE Family that tests old people (greater than 150) +0 @F4@ FAM +1 HUSB @I9@ +1 WIFE @I10@ +1 MARR +2 DATE 1 JAN 1967 +0 NOTE Following families (F5,F6,F7) test birth of spouses before their marriage +0 NOTE Family that tests both spouses born after marriage +0 @F5@ FAM +1 HUSB @I11@ +1 WIFE @I12@ +1 MARR +2 DATE 2 APR 1999 +0 NOTE Family that tests husband born after marriage +0 @F6@ FAM +1 HUSB @I13@ +1 WIFE @I14@ +1 MARR +2 DATE 2 APR 1995 +0 NOTE Family that tests wife born after marriage +0 @F7@ FAM +1 HUSB @I15@ +1 WIFE @I16@ +1 MARR +2 DATE 2 APR 1995 +0 NOTE US04: Family that tests Marriage after Divorce +0 @F8@ FAM +1 HUSB @I17@ +1 WIFE @I18@ +1 MARR +2 DATE 1 FEB 2001 +1 DIV +2 DATE 1 FEB 2000 +0 NOTE US04: Family divorces same day as marriage +0 @F9@ FAM +1 HUSB @I17@ +1 WIFE @I18@ +1 MARR +2 DATE 2 MAR 1990 +1 DIV +2 DATE 2 MAR 1990 +0 NOTE US01: Marriage and divorce occur in future. Husb born and died in future. Wife died in future. +0 @F10@ FAM +1 HUSB @I21@ +1 WIFE @I22@ +1 MARR +2 DATE 15 MAR 2045 +1 DIV +2 DATE 3 JAN 2049 +0 NOTE US11: This makes two new bigamists to check for, Matt Smith and Jen Smith +0 @F11@ FAM +1 HUSB @I23@ +1 WIFE @I24@ +1 MARR +2 DATE 15 MAR 1982 +1 DIV +2 DATE 3 JAN 1990 +0 @F12@ FAM +1 HUSB @I23@ +1 WIFE @I26@ +1 MARR +2 DATE 16 MAR 1983 +0 @F13@ FAM +1 HUSB @I25@ +1 WIFE @I24@ +1 MARR +2 DATE 16 MAR 1983 +0 Note US15: Family with 15 children +0 @F14@ FAM +1 HUSB @I28@ +1 WIFE @I29@ +1 MARR +2 DATE 1 JAN 1998 +1 CHIL @I30@ +1 CHIL @I31@ +1 CHIL @I32@ +1 CHIL @I33@ +1 CHIL @I34@ +1 CHIL @I35@ +1 CHIL @I36@ +1 CHIL @I37@ +1 CHIL @I38@ +1 CHIL @I39@ +1 CHIL @I40@ +1 CHIL @I41@ +1 CHIL @I42@ +1 CHIL @I43@ +1 CHIL @I44@ +0 NOTE US09: This makes two births after death of parents Jimmy and Jacob +0 @F15@ FAM +1 HUSB @I45@ +1 WIFE @I46@ +1 MARR +2 DATE 20 JUL 1982 +1 CHIL @I47@ +1 CHIL @I48@ +1 CHIL @I49@ +0 NOTE US10: The following two families are married before 14 years old +0 @F16@ FAM +1 HUSB @I50@ +1 WIFE @I51@ +1 MARR +2 DATE 4 APR 1997 +0 @F17@ FAM +1 HUSB @I52@ +1 WIFE @I53@ +1 MARR +2 DATE 10 OCT 2003 +0 NOTE US16: The following 4 families contain descendants of each other, where Fam 19 contains a marriage between a individual and their descendant +0 @F18@ FAM +1 HUSB @I57@ +1 WIFE @I58@ +1 MARR +2 DATE 15 FEB 1920 +1 DIV +2 DATE 16 MAR 1935 +1 CHIL @I59@ +1 CHIL @I60@ +0 @F19@ FAM +1 HUSB @I60@ +1 WIFE @I61@ +1 MARR +2 DATE 20 FEB 1940 +1 CHIL @I62@ +0 @F20@ FAM +1 HUSB @I62@ +1 WIFE @I63@ +1 MARR +2 DATE 10 FEB 1960 +1 CHIL @I64@ +1 CHIL @I65@ +0 @F21@ FAM +1 HUSB @I57@ +1 WIFE @I65@ +1 MARR +2 DATE 11 FEB 1980 diff --git a/GEDCOM_FamilyTree.ged b/GEDCOM_FamilyTree.ged new file mode 100644 index 0000000..bc46294 --- /dev/null +++ b/GEDCOM_FamilyTree.ged @@ -0,0 +1,179 @@ +0 HEAD +0 NOTE Timothy Shine Family Tree +0 @I1@ INDI +1 NAME Timothy /Shine/ +1 SEX M +1 BIRT +2 DATE 6 FEB 1998 +1 FAMC @F1@ +0 @I2@ INDI +1 NAME Michael /Shine/ +1 SEX M +1 BIRT +2 DATE 8 MAR 1967 +1 FAMS @F1@ +1 FAMC @F2@ +0 @I3@ INDI +1 NAME Michelle /Adamo/ +1 SEX F +1 BIRT +2 DATE 18 SEP 1968 +1 FAMS @F1@ +1 FAMC @F3@ +0 @I4@ INDI +1 NAME Brendan /Shine/ +1 SEX M +1 BIRT +2 DATE 6 FEB 1998 +1 FAMC @F1@ +0 @I5@ INDI +1 NAME Michael /Shine/ +1 SEX M +1 BIRT +2 DATE 24 SEP 1934 +1 DEAT +2 DATE 17 JUL 1994 +1 FAMS @F2@ +0 @I6@ INDI +1 NAME Barbara /Amitin/ +1 SEX F +1 BIRT +2 DATE 30 NOV 1939 +1 FAMS @F2@ +1 FAMS @F4@ +0 @I7@ INDI +1 NAME Dorothy /Shine/ +1 SEX F +1 BIRT +2 DATE 8 APR 1963 +1 FAMC @F2@ +0 @I8@ INDI +1 NAME Brian /Shine/ +1 SEX M +1 BIRT +2 DATE 18 OCT 1974 +1 FAMC @F2@ +0 @I9@ INDI +1 NAME Salvador /Adamo/ +1 SEX M +1 BIRT +2 DATE 1 JAN 1946 +1 FAMS @F3@ +1 FAMS @F5@ +0 @I10@ INDI +1 NAME Kathryn /Baeli/ +1 SEX F +1 BIRT +2 DATE 17 DEC 1946 +1 FAMS @F3@ +0 @I11@ INDI +1 NAME Salvatore /Adamo/ +1 SEX M +1 BIRT +2 DATE 4 SEP 1984 +1 FAMC @F5@ +0 @I12@ INDI +1 NAME Annette /Pace/ +1 SEX F +1 BIRT +2 DATE 25 JUL 1954 +1 FAMS @F5@ +0 @I13@ INDI +1 NAME Theresa /Adamo/ +1 SEX F +1 BIRT +2 DATE 5 FEB 1966 +1 FAMS @F6@ +1 FAMC @F3@ +0 @I14@ INDI +1 NAME Elizabeth /Adamo/ +1 SEX F +1 BIRT +2 DATE 28 NOV 1972 +1 FAMS @F7@ +1 FAMC @F3@ +0 @I15@ INDI +1 NAME Marina /Adamo/ +1 SEX F +1 BIRT +2 DATE 5 JUN 1981 +1 FAMS @F8@ +1 FAMC @F5@ +0 @I16@ INDI +1 NAME Joseph /Pister/ +1 SEX M +1 BIRT +2 DATE 12 JAN 1963 +1 FAMS @F6@ +0 @I17@ INDI +1 NAME Michael /Morris/ +1 SEX M +1 BIRT +2 DATE 8 OCT 1980 +1 FAMS @F8@ +0 @I18@ INDI +1 NAME Michael /Vasile/ +1 SEX M +1 BIRT +2 DATE 18 NOV 1974 +1 FAMS @F7@ +0 @I19@ INDI +1 NAME Joe /Smith/ +1 SEX M +1 BIRT +2 DATE 1 JAN 1940 +1 FAMS @F4@ +0 @F1@ FAM +1 HUSB @I2@ +1 WIFE @I3@ +1 CHIL @I1@ +1 CHIL @I4@ +1 MARR +2 DATE 2 APR 1993 +0 @F2@ FAM +1 HUSB @I5@ +1 WIFE @I6@ +1 CHIL @I2@ +1 CHIL @I7@ +1 CHIL @I8@ +1 MARR +2 DATE 9 MAR 1950 +1 EVEN +0 @F3@ FAM +1 HUSB @I9@ +1 WIFE @I10@ +1 CHIL @I3@ +1 CHIL @I13@ +1 CHIL @I14@ +1 MARR +2 DATE 1 AUG 1965 +1 DIV +2 DATE 7 APR 1976 +0 @F4@ FAM +1 HUSB @I19@ +1 WIFE @I6@ +1 MARR +2 DATE 6 JUN 1997 +0 @F5@ FAM +1 HUSB @I9@ +1 WIFE @I12@ +1 CHIL @I11@ +1 CHIL @I15@ +1 MARR +2 DATE 8 OCT 1978 +0 @F6@ FAM +1 HUSB @I16@ +1 WIFE @I13@ +1 MARR +2 DATE 15 AUG 1988 +0 @F7@ FAM +1 HUSB @I18@ +1 WIFE @I14@ +1 MARR +2 DATE 8 OCT 2015 +0 @F8@ FAM +1 HUSB @I17@ +1 WIFE @I15@ +1 MARR +2 DATE 19 JUN 2012 +0 TRLR \ No newline at end of file diff --git a/GedcomProject.py b/GedcomProject.py new file mode 100644 index 0000000..065d642 --- /dev/null +++ b/GedcomProject.py @@ -0,0 +1,430 @@ +from prettytable import PrettyTable +import datetime +from collections import defaultdict +from copy import deepcopy +import os + +class AnalyzeGEDCOM: + """This class analyzes the GEDCOM file and sorts information into the family and individual classes respectively for analysis""" + def __init__(self, file_name, create_tables = True, print_errors = True): + self.file_name = file_name + self.family = dict() #dictionary with Key = FamID Value = Family class object + self.individuals = dict() #dictionary with Key = IndiID Value = Individual class object + self.fam_table = PrettyTable(field_names = ["ID", "Married", "Divorced", "Husband ID", "Husband Name", "Wife ID", "Wife Name", "Children"]) + self.indi_table = PrettyTable(field_names = ["ID", "Name", "Gender", "Birthday", "Age", "Alive", "Death", "Child", "Spouse"]) + self.analyze() + if create_tables: #allows to easily toggle the print of the pretty table on and off + self.create_pretty_tables() + self.all_errors = CheckForErrors(self.individuals, self.family, print_errors).all_errors + + def analyze(self): + """This method reads in each line and determines if a new family or individual need to be made, if not then it sends the line + to be analyzed further in analyze_info""" + indiv, fam, previous_line, current_type = "", "", [], 0 + read_GEDCOM_file = self.read_files(self.file_name, error_mess = "A misformatted line was found!", seperator = " " ) + for line in read_GEDCOM_file: #Reads each line from the generator + if line[0] == '0' and line[1] in ["HEAD", "TRLR", "NOTE"]: #These cases provide no information we need to analyze + continue + elif line[0] == '0' and line [2] == "INDI": + current_type = 1 #Marker used to ensure following lines are analyzed as individual + indiv = line[1].replace("@", "") #The GEDCOM file from online has @ID@ format, this replaces it + self.individuals[indiv] = Individual() #The instance of a Individual class object is created + continue + elif line[0] == '0' and line[2] == "FAM": + current_type = 2 #Marker used to ensure following lines are analyzed as family + fam = line[1].replace("@", "") + self.family[fam] = Family() #The instance of a Family class object is created + continue + if current_type in [1,2]: #No new Individual or Family was created, analyze line further + self.analyze_info(line, previous_line, indiv, fam, current_type) + previous_line = line + for indiv in self.individuals.values(): + indiv.update_age() + + + def analyze_info(self, line, previous_line, idn, fam, current_type): + """This analyzes each line's information and stores it in the appropriate place in the appropriate class""" + if len(line) == 2: + return #We only need this line when it becomes the previous line + else: #Populates the variables in the Individuals class and Family class + level, tag, arg = line + level = int(level) + if level == 1 and tag in ["NAME", "SEX", "FAMC", "FAMS", "HUSB", "WIFE", "CHIL"]: + arg = arg.replace("@", "") + if current_type == 1: #individual analysis + if tag == "FAMS": + self.individuals[idn].fams.add(arg) + elif tag == "NAME": + self.individuals[idn].name = arg + elif tag == "SEX": + self.individuals[idn].sex = arg + elif tag == "FAMC": + self.individuals[idn].famc = arg + elif current_type == 2: #family analysis + if tag == "HUSB": + self.family[fam].husb = arg + elif tag == "WIFE": + self.family[fam].wife = arg + elif tag == "CHIL": + self.family[fam].chil.add(arg) + elif level == 2 and tag == "DATE": #Handles dates for both INDI and FAM cases + p_level, p_tag = previous_line + arg = datetime.datetime.strptime(arg, "%d %b %Y").date() + if p_level == "1" and p_tag in ["BIRT", "DEAT", "MARR", "DIV"]: + if current_type == 1: #individual analysis + if p_tag == "BIRT": + self.individuals[idn].birt = arg + elif p_tag == "DEAT": + self.individuals[idn].deat = arg + elif current_type == 2: #family analysis + if p_tag == "MARR": + self.family[fam].marr = arg + elif p_tag == "DIV": + self.family[fam].div = arg + + + def create_pretty_tables(self): + """Populates the pretty tables with all necessary summary information""" + print("Individual Table") + for ID, ind in self.individuals.items(): + self.indi_table.add_row([ID, ind.name, ind.sex, ind.birt, ind.age, ind.alive, ind.deat, ind.famc, ind.fams]) + print(self.indi_table) + print("Family Table") + for ID, fam in self.family.items(): + self.fam_table.add_row([ID, fam.marr, fam.div, fam.husb, self.individuals[fam.husb].name, fam.wife, self.individuals[fam.wife].name, fam.chil]) + print(self.fam_table) + + def read_files(self, file_name, error_mess, seperator = "\t"): + """A generic read file generator to check bad file inputs and read line by line""" + try: + fp = open(file_name, 'r') + except FileNotFoundError: + raise FileNotFoundError ("Could not open {}".format(file_name)) + else: + with fp: + for line in fp: + l = line.strip().split(seperator, 2) + yield l + + +class Family: + """This stores all the pertinent information about a family""" + def __init__(self): + self.marr = None #date of marriage + self.div = None #date of divorce + self.husb = None #husband ID + self.wife = None #wife ID + self.chil = set() #set of children + + +class Individual: + """This class stores all the pertinent information about an individual""" + def __init__(self): + """This captures all the relevant information for an individual, it also instantiates null values in case information is incomplete""" + self.name = None + self.sex = None + self.birt = None + self.age = None + self.alive = True + self.deat = None + self.famc = None + self.fams = set() + + def update_age(self): + """Checks to see if INDI is dead, and finds their age""" + self.alive = self.deat == None + try: + if self.alive: + self.age = datetime.datetime.today().year - self.birt.year + else: + self.age = self.deat.year - self.birt.year + except AttributeError: + raise AttributeError("Improper records of birth/death, need proper birth/death date to calculate age") + +class CheckForErrors: + """This class runs through all the user stories and looks for possible errors in the GEDCOM data""" + def __init__(self, ind_dict, fam_dict, print_errors): + """This instantiates variables in this class to the dictionaries of families and individuals from + the AnalyzeGEDCOM class, it also calls all US methods while providing an option to print all errors""" + self.individuals = ind_dict + self.family = fam_dict + self.all_errors = list() + self.dates_before_curr() #US01 + self.indi_birth_before_marriage() #US02 + self.birth_before_death() #US03 + self.marr_before_div() #US04 + self.marr_div_before_death() #US05 & US06 + self.normal_age() #US07 + self.birth_before_marriage() #US08 + self.brith_before_death_of_parents() #US09 + self.spouses_too_young() #US10 + self.no_bigamy() #US11 + self.parents_too_old() #US12 + self.sibling_spacing() #US13 + self.too_many_births() + self.too_many_siblings() #US15 + self.no_marriage_to_descendants() + + if print_errors == True: + self.print_errors() + + def date_difference(self, d1, d2): + """Returns true if the difference between the two dates is positive: [d1 - d2]""" + return (d1 - d2).days + + def dates_before_curr(self): + """US01: Tests to ensure any dates do not occur after current date""" + for fam in self.family.values(): + marrDate=fam.marr + divDate=fam.div + if(marrDate>datetime.datetime.now().date()): + self.all_errors+=["US01: The marriage of {} and {} cannot occur after the current date.".format(self.individuals[fam.husb].name, self.individuals[fam.wife].name)] + if(divDate != None and divDate>datetime.datetime.now().date()): + self.all_errors+=["US01: The divorce of {} and {} cannot occur after the current date.".format(self.individuals[fam.husb].name, self.individuals[fam.wife].name)] + + for indi in self.individuals.values(): + birthday=indi.birt + deathDay=indi.deat + if(birthday>datetime.datetime.now().date()): + self.all_errors+=["US01: The birth of {} cannot occur after the current date.".format(indi.name)] + if(deathDay != None and deathDay>datetime.datetime.now().date()): + self.all_errors+=["US01: The death of {} cannot occur after the current date.".format(indi.name)] + + def indi_birth_before_marriage(self): + """US02: Tests to ensure a married individual was not born after their marriage""" + for fam in self.family.values(): + birth_husb = self.individuals[fam.husb].birt + birth_wife = self.individuals[fam.wife].birt + marr_date = fam.marr + + if(birth_husb>marr_date and birth_wife>marr_date): + self.all_errors += ["US02: {}'s birth can not occur after their date of marriage".format(self.individuals[fam.husb].name) + + " and " + "{}'s birth can not occur after their date of marriage".format(self.individuals[fam.wife].name)] + + elif(birth_husb>marr_date): + self.all_errors += ["US02: {}'s birth can not occur after their date of marriage".format(self.individuals[fam.husb].name)] + elif(birth_wife>marr_date): + self.all_errors += ["US02: {}'s birth can not occur after their date of marriage".format(self.individuals[fam.wife].name)] + + def birth_before_death(self): + """US03: Tests to ensure that birth occurs before the death of an individual""" + for person in self.individuals.values(): + if person.deat != None and self.date_difference(person.deat, person.birt) < 0: + self.all_errors += ["US03: {}'s death can not occur before their date of birth".format(person.name)] + + def marr_before_div(self): + """US04: Tests to ensure that marriage dates come before divorce dates""" + for fam in self.family.values(): + if fam.div != None and self.date_difference(fam.div, fam.marr) < 0: + self.all_errors += ["US04: {} and {}'s divorce can not occur before their date of marriage".format(self.individuals[fam.husb].name, self.individuals[fam.wife].name)] + + def marr_div_before_death(self): + """US05 & US06: This tests to make sure that no one was married or divorced after they died""" + for fam in self.family.values(): + deat_husb = self.individuals[fam.husb].deat + deat_wife = self.individuals[fam.wife].deat + marr_date, div_date = fam.marr, fam.div + check_husb_m, check_husb_d, check_wife_d, check_wife_m = 1, 1, 1, 1 #Let the if else statements assign these their proper values + if deat_husb == None and deat_wife == None: + break #We do not need to analyze further if both are alive + elif div_date == None: #We will now consider the case the two were still married when one/both spouse died + if deat_husb != None: + check_husb_m = (deat_husb - marr_date).days + else: + check_wife_m = (deat_wife - marr_date).days + elif div_date != None: #We will now consider they did divorce, we still have to check marriage again here + if deat_husb != None: + check_husb_m = (deat_husb - marr_date).days + check_husb_d = (deat_husb - div_date).days + else: + check_wife_m = (deat_wife - marr_date).days + check_wife_d = (deat_wife - div_date).days + if check_husb_m < 0 or check_wife_m < 0 or check_husb_d < 0 or check_wife_d < 0: + self.all_errors += ["US05 & US06: Either {} or {} were married or divorced after they died".format(self.individuals[fam.husb].name, self.individuals[fam.wife].name)] + + def normal_age(self): + """US07: Checks to make sure that the person's age is less than 150 years old""" + for individual in self.individuals.values(): + if individual.age == None: + print(individual.name) + if individual.age >= 150: + self.all_errors += ["US07: {}'s age calculated ({}) is over 150 years old".format(individual.name, individual.age)] + + def birth_before_marriage(self): + """US08: This checks to see if someone was born before the parents were married + or 9 months after divorce""" + for individual in self.individuals.values(): + birth_date = individual.birt #each individual birthday + if individual.famc != None: + marriage_date = self.family[individual.famc].marr #each family (that child is in) marraige date + divorce_date = self.family[individual.famc].div #divorce date of parents + if divorce_date != None: + diff_divorce_and_birth_date = (birth_date.year - divorce_date.year) * 12 + birth_date.month - divorce_date.month + if (birth_date - marriage_date).days <= 0: + self.all_errors += ["US08: {} was born before their parents were married".format(individual.name)] + elif divorce_date != None and diff_divorce_and_birth_date >= 9: + self.all_errors += ["US08: {} was born {} months after their parents were divorced".format(individual.name, diff_divorce_and_birth_date)] + + def brith_before_death_of_parents(self): + "US09: Checks to see if someone was born before their parent died" + for individual in self.individuals.values(): + birth_date = individual.birt #each individual birthday + if individual.famc != None: + fatherID = self.family[individual.famc].husb #father ID + motherID = self.family[individual.famc].wife #mother ID + father_death = self.individuals[fatherID].deat + mother_death = self.individuals[motherID].deat + if father_death != None: + father_difference = (birth_date.year - father_death.year) * 12 + birth_date.month - father_death.month + if father_difference >= 9: + self.all_errors += ["US09: {} was born {} months after father died".format(individual.name, father_difference)] + if mother_death != None: + mother_difference = (birth_date - mother_death).days + if mother_difference >= 0: + self.all_errors += ["US09: {} was born after mother died".format(individual.name)] + + def spouses_too_young(self): + """US10: Checks to make sure that each spouse of a family is older than 14 years old when + they get married""" + for individual in self.individuals.values(): + if len(individual.fams) > 0: + for family in individual.fams: + marriage_date = self.family[family].marr + marriage_difference = marriage_date.year - individual.birt.year + if marriage_difference <= 14: + self.all_errors += ["US10: {} was only {} years old when they got married".format(individual.name, marriage_difference)] + + def no_bigamy(self): + """US11: Tests to ensure marriage does not occur during marriage with someone else""" + for fam in self.family.values(): + if len(self.individuals[fam.husb].fams) <= 1 and len(self.individuals[fam.husb].fams): + continue #If they are only a spouse in one family no need to continue, same for not being a spouse + if len(self.individuals[fam.husb].fams) > 1: #checks if husb is a bigamist + count = 0 + for spouse in sorted(self.individuals[fam.husb].fams): #want to ensure the set is ordered + curr_marr_date = self.family[spouse].marr + curr_div_date = self.family[spouse].div + if count == 0: + prev_marr_date = self.family[spouse].marr + prev_div_date = self.family[spouse].div + count += 1 + continue + if prev_div_date == None: + self.add_errors_if_new("US11: {} is practing bigamy".format(self.individuals[fam.husb].name)) + elif (curr_marr_date - prev_marr_date).days > 0 and (curr_marr_date - prev_div_date).days < 0: + self.add_errors_if_new("US11: {} is practing bigamy".format(self.individuals[fam.husb].name)) + prev_marr_date = self.family[spouse].marr + prev_div_date = self.family[spouse].div + if len(self.individuals[fam.wife].fams) > 1: #checks if wife is a bigamist + count = 0 + for spouse in sorted(self.individuals[fam.wife].fams): + curr_marr_date = self.family[spouse].marr + curr_div_date = self.family[spouse].div + if count == 0: + prev_marr_date = self.family[spouse].marr + prev_div_date = self.family[spouse].div + count += 1 + continue + if prev_div_date == None: + self.add_errors_if_new("US11: {} is practing bigamy".format(self.individuals[fam.wife].name)) + elif (curr_marr_date - prev_marr_date).days > 0 and (curr_marr_date - prev_div_date).days < 0: + self.add_errors_if_new("US11: {} is practing bigamy".format(self.individuals[fam.wife].name)) + prev_marr_date = self.family[spouse].marr + prev_div_date = self.family[spouse].div + + def parents_too_old(self): + """US12: This method tests to ensure that parents in a family are not too old. + Mother should be less than 60 years older than children. + Father should be less than 80 years older than children.""" + for indi in self.individuals.values(): + if indi.famc == None: #No need to continue if they are not a child + continue + if self.individuals[self.family[indi.famc].husb].age > (indi.age + 80): #check the father + self.all_errors += ["US12: {} is over 80 years older than his child {}".format(self.individuals[self.family[indi.famc].husb].name, indi.name)] + if self.individuals[self.family[indi.famc].wife].age > (indi.age + 60): #check the mother + self.all_errors += ["US12: {} is over 60 years older than his child {}".format(self.individuals[self.family[indi.famc].wife].name, indi.name)] + + def sibling_spacing(self): + """US13: Makes sure that birth dates of siblings should be more than 8 months apart + or less than 2 days apart (twins may be born one day apart, e.g. 11:59 PM and 12:02 AM the following calendar day)""" + for fam in self.family.values(): + childIDLstCopy = deepcopy(list(fam.chil)) + childIDLstCopy.sort() #needs to be sorted since every time the program runs, the order of the children set changes + + for i in range(len(childIDLstCopy)): + for j in range(i + 1, len(childIDLstCopy)): + child1 = self.individuals[childIDLstCopy[i]] + child2 = self.individuals[childIDLstCopy[j]] + daysApart = abs(child1.birt.day - child2.birt.day) + monthsApart = abs(child1.birt.month - child2.birt.month) + if daysApart > 2 and monthsApart < 8 : + self.all_errors += ["US13: Siblings {} and {}'s births are only ".format(child1.name, child2.name) + str(daysApart) + " days apart"] + + + + def too_many_births(self): + """US14: Makes sure that no more than five siblings should be born at the same time""" + for fam in self.family.values(): + childIDLstCopy = deepcopy(list(fam.chil)) + childIDLstCopy.sort() #needs to be sorted since every time the program runs, the order of the children set changes + + birthDayDict = {} + for i in range(len(childIDLstCopy)): + child = self.individuals[childIDLstCopy[i]] + if child.birt not in birthDayDict: + birthDayDict[child.birt] = 1 + else: + birthDayDict[child.birt] = birthDayDict[child.birt] + 1 + for key in birthDayDict: + if birthDayDict[key] > 5: + familyName = str(self.individuals[fam.husb].name).split()[-1] + self.all_errors += ["US14: The {} family has more than five children born at the same time".format(familyName)] + + + def too_many_siblings(self): + """US15: Tests to ensure that there are fewer than 15 siblings in a family""" + for fam in self.family.values(): + if len(fam.chil)>=15: + familyName = str(self.individuals[fam.husb].name).split()[-1] + self.all_errors+=["US15: The {} family has 15 or more siblings".format(familyName)] + + def descendants_help(self, initial_indi, current_indi ): + """Recursive helper for US17""" + if(len(current_indi.fams)>0): + for fam in current_indi.fams: + if (self.individuals[self.family[fam].husb] == initial_indi or self.individuals[self.family[fam].wife] == initial_indi): + self.all_errors+=["US17: {} cannot be married to their descendant {}".format(initial_indi.name, current_indi.name)] + for child in self.family[fam].chil: + self.descendants_help(initial_indi,self.individuals[child]) + + def no_marriage_to_descendants(self): + """US17: Tests to ensure that individuals and their descendants do not marry each other""" + for person in self.individuals.values(): #Traverse all individuals and do a top down search of all descendants + if(len(person.fams)>0): + for fam in person.fams: + for child in self.family[fam].chil: + self.descendants_help(person,self.individuals[child]) + + def add_errors_if_new(self, error): + """This method is here to add errors to the error list if they do not occur, in order to ensure no duplicates. + Some user stories may flag duplicate errors and this method eliminates the issue.""" + if error not in self.all_errors: + self.all_errors += [error] + + def print_errors(self): + """After all error messages have been compiled into the list of errors the program prints them all out""" + if len(self.all_errors) == 0: + print("Congratulations this GEDCOM file has no known errors!") + else: + for error in self.all_errors: + print(error) + +def main(): + """This method runs the program""" + cwd = os.path.dirname(os.path.abspath(__file__)) #gets directory of the file + #file_name = cwd + r"\GEDCOM_FamilyTree.ged" + file_name = cwd + r"\Bad_GEDCOM_test_data.ged" + AnalyzeGEDCOM(file_name) + +if __name__ == '__main__': + main() diff --git a/Program Output.PNG b/Program Output.PNG new file mode 100644 index 0000000..066a278 Binary files /dev/null and b/Program Output.PNG differ diff --git a/Unit_Test_Proj.py b/Unit_Test_Proj.py new file mode 100644 index 0000000..79f3133 --- /dev/null +++ b/Unit_Test_Proj.py @@ -0,0 +1,149 @@ +import unittest +from GedcomProject import AnalyzeGEDCOM, Family, Individual, CheckForErrors +import datetime +import os + +class ProjectTest(unittest.TestCase): + """Tests that our GEDCOM parser is working properly""" + + def __init__(self, *args, **kwargs): + super(ProjectTest, self).__init__(*args, **kwargs) + cwd = os.path.dirname(os.path.abspath(__file__)) #gets directory of the file + file_name = cwd + "\Bad_GEDCOM_test_data.ged" + self.all_errors = AnalyzeGEDCOM(file_name, False, False).all_errors #done in this method so it only happens once + + def test_dates_before_curr(self): + """US01: Unit Test: to ensure that all dates occur before the current date""" + list_of_known_errors=["US01: The marriage of Future /Trunks/ and Mai /Trunks/ cannot occur after the current date.", + "US01: The divorce of Future /Trunks/ and Mai /Trunks/ cannot occur after the current date.", + "US01: The birth of Future /Trunks/ cannot occur after the current date.", + "US01: The birth of Mai /Trunks/ cannot occur after the current date.", + "US01: The death of Future /Trunks/ cannot occur after the current date."] + for error in list_of_known_errors: + self.assertIn(error, self.all_errors) + + def test_indi_birth_before_marriage(self): + """US02: Unit Test: to ensure that birth of an individual occurs before their marriage""" + list_of_known_errors = ["US02: Johnny /Sway/'s birth can not occur after their date of marriage", + "US02: Missy /Kennedy/'s birth can not occur after their date of marriage", + "US02: Bobby /Bourne/'s birth can not occur after their date of marriage and Bella /Bourne/'s birth can not occur after their date of marriage" ] + for error in list_of_known_errors: + self.assertIn(error, self.all_errors) + + def test_birth_before_death(self): + """US03: Unit Test: to ensure that birth occurs before the death of an individual""" + list_of_known_errors = ["US03: James /Nicholas/'s death can not occur before their date of birth", + "US03: Peter /Tosh/'s death can not occur before their date of birth"] + for error in list_of_known_errors: + self.assertIn(error, self.all_errors) + + def test_marr_before_div(self): + """US04: Unit Test: to ensure that marriage dates come before divorce dates""" + list_of_known_errors = [ + "US04: Johnson /Deere/ and Emily /Deere/'s divorce can not occur before their date of marriage"] + for error in list_of_known_errors: + self.assertIn(error, self.all_errors) + + def test_marr_div_before_death(self): + """US05 & US06: Tests that the marr_div_before_death method works properly, the list of known errors is manually hard coded. + It contains all of the errors we have intentionally put into the file and ensures the file catches them""" + list_of_known_errors = ["US05 & US06: Either Mark /Eff/ or Jess /Eff/ were married or divorced after they died", + "US05 & US06: Either Troy /Johnson/ or Sammy /Johnson/ were married or divorced after they died"] + for error in list_of_known_errors: + self.assertIn(error, self.all_errors) + + def test_normal_age(self): + """US07: Tests that the normal_age method works properly""" + list_of_known_errors = [ + "US07: John /Old/'s age calculated (1000) is over 150 years old", + "US07: Jackie /Old/'s age calculated (168) is over 150 years old"] + for error in list_of_known_errors: + self.assertIn(error, self.all_errors) + + def test_birth_before_marriage(self): + """US08: Tests to see if birth_before_marriage function is working properly + Will raise exceptions if birth is before marriage or 9 + months after the divorce of the parents""" + #Tests child is born 1 month before parents are married + list_of_known_errors = [ + "US08: Jimmy /Shmoe/ was born before their parents were married", + "US08: Sammy /Shmoe/ was born 60 months after their parents were divorced"] + #self.num_of_errors += len(list_of_known_errors) + for error in list_of_known_errors: + self.assertIn(error, self.all_errors) + + def test_birth_before_death_of_parents(self): + """US09: Test if someone was born before their parent died""" + list_of_known_errors = [ + "US09: Jimmy /James/ was born 10 months after father died", + "US09: Jacob /James/ was born 61 months after father died", + "US09: Jacob /James/ was born after mother died", + "US09: Jackie /James/ was born 12 months after father died"] + for error in list_of_known_errors: + self.assertIn(error, self.all_errors) + + def test_spouses_too_young(self): + """US10: Tests that the parents do not get married to each other when they are + younger than 14 years old""" + list_of_known_errors = [ + "US10: Bobby /Bourne/ was only -1 years old when they got married", + "US10: Ann /Joene/ was only 13 years old when they got married", + "US10: Nick /Jackson/ was only 9 years old when they got married"] + for error in list_of_known_errors: + self.assertIn(error, self.all_errors) + + def test_no_bigamy(self): + """US11: Tests to see if no_bigamy function is working properly, catches all bigamists""" + list_of_known_errors = ["US11: Matt /Smith/ is practing bigamy", "US11: Jen /Smith/ is practing bigamy"] + for error in list_of_known_errors: + self.assertIn(error, self.all_errors) + + def test_parents_too_old(self): + """US12: Tests that parents are not too old relative to their children, + Dad less than 80 years older and moter less than 60 years older""" + list_of_known_errors = ["US12: John /Old/ is over 80 years older than his child Jessica /Old/", + "US12: Jackie /Old/ is over 60 years older than his child Jessica /Old/"] + for error in list_of_known_errors: + self.assertIn(error, self.all_errors) + + def test_sibling_spacing(self): + """US13: Tests thatbirth dates of siblings should be more than 8 months apart or less than 2 days apart + (twins may be born one day apart, e.g. 11:59 PM and 12:02 AM the following calendar day)""" + list_of_known_errors = ["US13: Siblings One /Fif/ and Thirteen /Fif/'s births are only 5 days apart", + "US13: Siblings Two /Fif/ and Thirteen /Fif/'s births are only 5 days apart", + "US13: Siblings Three /Fif/ and Thirteen /Fif/'s births are only 5 days apart", + "US13: Siblings Four /Fif/ and Thirteen /Fif/'s births are only 5 days apart", + "US13: Siblings Five /Fif/ and Thirteen /Fif/'s births are only 5 days apart", + "US13: Siblings Six /Fif/ and Thirteen /Fif/'s births are only 5 days apart", + "US13: Siblings Seven /Fif/ and Thirteen /Fif/'s births are only 5 days apart", + "US13: Siblings Eight /Fif/ and Thirteen /Fif/'s births are only 5 days apart", + "US13: Siblings Nine /Fif/ and Thirteen /Fif/'s births are only 5 days apart", + "US13: Siblings Ten /Fif/ and Thirteen /Fif/'s births are only 5 days apart", + "US13: Siblings El /Fif/ and Thirteen /Fif/'s births are only 5 days apart", + "US13: Siblings Twelve /Fif/ and Thirteen /Fif/'s births are only 5 days apart", + "US13: Siblings Thirteen /Fif/ and Fourteen /Fif/'s births are only 5 days apart", + "US13: Siblings Thirteen /Fif/ and Fifteen /Fif/'s births are only 5 days apart", + "US13: Siblings Allen /Leffe/ and Ava /Leffe/'s births are only 9 days apart"] + for error in list_of_known_errors: + self.assertIn(error, self.all_errors) + + def test_too_many_births(self): + """US14: Tests that no more than five siblings should be born at the same time""" + list_of_known_errors = ["US14: The /Fif/ family has more than five children born at the same time"] + for error in list_of_known_errors: + self.assertIn(error, self.all_errors) + + def test_too_many_siblings(self): + """US15: Test: Makes sure too_many_siblings function works properly""" + list_of_known_errors = ["US15: The /Fif/ family has 15 or more siblings"] + for error in list_of_known_errors: + self.assertIn(error, self.all_errors) + + def test_no_marriage_to_descendents(self): + """US17: Test: Makes sure no_marriage_to_siblings finds all individiuals married to one fof their descendants""" + list_of_known_errors = ["US17: John /Leffe/ cannot be married to their descendant Ava /Leffe/"] + for error in list_of_known_errors: + self.assertIn(error, self.all_errors) + +if __name__ == '__main__': + unittest.main(exit=False, verbosity=2)