@@ -814,7 +814,174 @@ def otts2identifiers():
814814 headers = colname_map ,
815815 ids = ret )
816816
817+ def ascend_ancestors ():
818+ """
819+ Used to return details of all the ancestors of a particular node, in particular
820+ - number of million years of each ancestor
821+ - the total number of species that are descendants on each side of the bifurcation
822+ - the name of the period/era/eon
823+ - a name (if it exists) for the ancestor
824+
825+ NB: finding the name for the other descendant group is a lot more tricky!
826+
827+ """
828+ session .forget (response )
829+ lang = request .vars .lang or request .env .http_accept_language or 'en'
830+ response .headers ["Access-Control-Allow-Origin" ] = '*'
831+
832+ if "." not in request .env .path_info .split ('/' )[2 ]:
833+ request .extension = "json"
834+ response .view = request .controller + "/" + request .function + "." + request .extension
835+
836+ if request .vars .key is None :
837+ redirect (URL ('error' , vars = dict (
838+ code = 400 ,
839+ text = "Please use an API key (use 0 for the public API key)"
840+ )))
841+
842+ try :
843+ ott = int (request .vars .ott )
844+ except :
845+ redirect (URL ('error' , vars = dict (code = 400 , text = "Invalid OTT" )))
846+
847+ include_otts = request .vars .include_otts
848+ rows = db (db .ordered_leaves .ott == ott ).select (
849+ db .ordered_leaves .id ,
850+ db .ordered_leaves .parent ,
851+ db .ordered_leaves .name ,
852+ db .ordered_leaves .extinction_date ,
853+ )
854+ if rows :
855+ row = rows .first ()
856+ date = row .extinction_date or 0
857+ prev_n_leaves = n_leaves = 1
858+ prev_leaf_lft = row .id
859+ else :
860+ rows = db (db .ordered_nodes .ott == ott ).select (
861+ db .ordered_nodes .parent ,
862+ db .ordered_nodes .name ,
863+ db .ordered_nodes .age ,
864+ db .ordered_nodes .leaf_lft ,
865+ db .ordered_nodes .leaf_rgt ,
866+ )
867+ if not rows :
868+ redirect (URL ('error' , vars = dict (code = 400 , text = "No matching OTT found" )))
869+ row = rows .first ()
870+ date = None if row .age is None else round (row .age , 1 )
871+ prev_n_leaves = n_leaves = row .leaf_rgt - row .leaf_lft + 1
872+ prev_leaf_lft = row .leaf_lft
873+
874+ step = 0
875+ ancestors = []
876+ data_by_sib_node_id = {}
877+ data_by_sib_leaf_id = {}
878+ ret = dict (
879+ step = step ,
880+ date_MYA = date ,
881+ n_spp = n_leaves ,
882+ name = row .name ,
883+ )
884+ if include_otts :
885+ ret ["input_ott" ] = ott
886+
887+ data_by_ott = {ott : ret }
888+ data_by_name = {}
889+
890+ while row .parent >= 0 :
891+ step += 1
892+ rows = db (db .ordered_nodes .id == row .parent ).select (
893+ db .ordered_nodes .id ,
894+ db .ordered_nodes .parent ,
895+ db .ordered_nodes .name ,
896+ db .ordered_nodes .ott ,
897+ db .ordered_nodes .age ,
898+ db .ordered_nodes .leaf_lft ,
899+ db .ordered_nodes .leaf_rgt ,
900+ )
901+ if not rows :
902+ break
903+ row = rows .first ()
904+ n_leaves = row .leaf_rgt - row .leaf_lft + 1
905+ row_data = dict (
906+ step = step ,
907+ date_MYA = None if row .age is None else round (row .age , 1 ),
908+ n_spp = n_leaves ,
909+ tracked_branch_n_spp = prev_n_leaves ,
910+ )
911+ sib_n_leaves = n_leaves - prev_n_leaves
912+ if prev_leaf_lft == row .leaf_lft :
913+ # the tracked branch is the left-hand branch from this node
914+ if sib_n_leaves > 1 :
915+ data_by_sib_node_id [row .id + prev_n_leaves ] = row_data
916+ else :
917+ data_by_sib_leaf_id [row .leaf_rgt ] = row_data
918+
919+ else :
920+ # the tracked branch is the right-hand branch from this node
921+ if sib_n_leaves > 1 :
922+ data_by_sib_node_id [row .id + 1 ] = row_data
923+ else :
924+ data_by_sib_leaf_id [row .leaf_lft ] = row_data
925+
926+
927+ row_data ["name" ] = row .name if row .name and not row .name .endswith ("_" ) else None
928+ row_data ["vernacular" ] = None
929+ if include_otts :
930+ row_data ["ott" ] = row .ott
931+ row_data ["sib_branches" ]= {"n_spp" : sib_n_leaves }
932+
933+ prev_n_leaves = n_leaves
934+ ancestors .append (row_data )
935+ if row .ott is not None :
936+ data_by_ott [row .ott ] = row_data
937+ elif row .name is not None :
938+ data_by_name [row .name ] = row_data
939+
940+ # Get data on the siblings
941+ for sib_data_dict , db_table in zip (
942+ (data_by_sib_node_id , data_by_sib_leaf_id ),
943+ (db .ordered_nodes , db .ordered_leaves )
944+ ):
945+ if len (sib_data_dict ) > 0 :
946+ rows = db (db_table .id .belongs (sib_data_dict .keys ())).select (
947+ db_table .id , db_table .ott , db_table .name )
948+ if rows :
949+ for row in rows :
950+ parent_data = sib_data_dict [row .id ]
951+ # if name ends in _ it's not the scientific name, but a OZ nickname
952+ if row .name and not row .name .endswith ("_" ):
953+ parent_data ["sib_branches" ]["name" ] = row .name
954+ if row .ott is not None :
955+ data_by_ott [row .ott ] = parent_data ["sib_branches" ]
956+ elif row .name is not None :
957+ data_by_name [row .name ] = parent_data ["sib_branches" ]
958+
817959
960+ for ott , vernacular in OZfunc .get_common_names (
961+ data_by_ott .keys (),
962+ return_nulls = False ,
963+ prefer_short_name = False ,
964+ include_unpreferred = False ,
965+ return_all = False ,
966+ lang = lang ,
967+ ).items ():
968+ data_by_ott [ott ]["vernacular" ] = vernacular
969+
970+ for name , vernacular in OZfunc .get_common_names (
971+ data_by_name .keys (),
972+ OTT = False ,
973+ return_nulls = False ,
974+ prefer_short_name = False ,
975+ include_unpreferred = False ,
976+ return_all = False ,
977+ lang = lang ,
978+ ).items ():
979+ data_by_name [name ]["vernacular" ] = vernacular
980+ ret ["ancestors" ] = ancestors
981+
982+ return ret
983+
984+
818985#PRIVATE FUNCTIONS
819986
820987
0 commit comments