Skip to content

Commit 802e375

Browse files
committed
Add API to get details of ancestors of a node
Something like this probably exists already in order to populate the "where am I" popup bar, but I have reimplemented something for Chris Haughton and Tiziana
1 parent 34e56ef commit 802e375

File tree

1 file changed

+154
-0
lines changed

1 file changed

+154
-0
lines changed

controllers/API.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,7 +814,161 @@ 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+
)))
817841

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_id = {}
877+
ret = dict(
878+
step=step,
879+
date_MYA=date,
880+
n_spp=n_leaves,
881+
name=row.name,
882+
)
883+
if include_otts:
884+
ret["input_ott"] = ott
885+
886+
data_by_ott = {ott: ret}
887+
data_by_name = {}
888+
889+
while row.parent >= 0:
890+
step += 1
891+
rows = db(db.ordered_nodes.id == row.parent).select(
892+
db.ordered_nodes.id,
893+
db.ordered_nodes.parent,
894+
db.ordered_nodes.name,
895+
db.ordered_nodes.ott,
896+
db.ordered_nodes.age,
897+
db.ordered_nodes.leaf_lft,
898+
db.ordered_nodes.leaf_rgt,
899+
)
900+
if not rows:
901+
break
902+
row = rows.first()
903+
n_leaves = row.leaf_rgt - row.leaf_lft + 1
904+
row_data = dict(
905+
step=step,
906+
date_MYA=None if row.age is None else round(row.age, 1),
907+
n_spp=n_leaves,
908+
tracked_branch_n_spp=prev_n_leaves,
909+
)
910+
if prev_leaf_lft == row.leaf_lft:
911+
# the tracked branch is the left-hand branch from this node
912+
data_by_sib_id[row.id + prev_n_leaves] = row_data
913+
else:
914+
# the tracked branch is the right-hand branch from this node
915+
data_by_sib_id[row.id + 1] = row_data
916+
917+
row_data["name"] = row.name if row.name and not row.name.endswith("_") else None
918+
row_data["vernacular"] = None
919+
if include_otts:
920+
row_data["ott"] = row.ott
921+
row_data["sib_branches"]={"n_spp": n_leaves - prev_n_leaves}
922+
923+
prev_n_leaves = n_leaves
924+
ancestors.append(row_data)
925+
if row.ott is not None:
926+
data_by_ott[row.ott] = row_data
927+
elif row.name is not None:
928+
data_by_name[row.name] = row_data
929+
930+
# Get data on the siblings
931+
# TODO - what if a sib is a leaf?
932+
if data_by_sib_id:
933+
rows = db(db.ordered_nodes.id.belongs(data_by_sib_id)).select(
934+
db.ordered_nodes.id, db.ordered_nodes.ott, db.ordered_nodes.name)
935+
if rows:
936+
for row in rows:
937+
parent_data = data_by_sib_id[row.id]
938+
# if name ends in _ it's not a correct scientific name, but a OZ nickname
939+
if row.name and not row.name.endswith("_"):
940+
parent_data["sib_branches"]["name"] = row.name
941+
if row.ott is not None:
942+
data_by_ott[row.ott] = parent_data["sib_branches"]
943+
elif row.name is not None:
944+
data_by_name[row.name] = parent_data["sib_branches"]
945+
946+
947+
for ott, vernacular in OZfunc.get_common_names(
948+
data_by_ott.keys(),
949+
return_nulls=False,
950+
prefer_short_name=False,
951+
include_unpreferred=False,
952+
return_all=False,
953+
lang=lang,
954+
).items():
955+
data_by_ott[ott]["vernacular"] = vernacular
956+
957+
for name, vernacular in OZfunc.get_common_names(
958+
data_by_name.keys(),
959+
OTT=False,
960+
return_nulls=False,
961+
prefer_short_name=False,
962+
include_unpreferred=False,
963+
return_all=False,
964+
lang=lang,
965+
).items():
966+
data_by_name[name]["vernacular"] = vernacular
967+
ret["ancestors"] = ancestors
968+
969+
return ret
970+
971+
818972
#PRIVATE FUNCTIONS
819973

820974

0 commit comments

Comments
 (0)