[Feature] EnzymeLink Annotations (#152)

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#152
This commit is contained in:
2025-10-15 19:35:26 +13:00
parent ef697ac5f5
commit 386098b8a6
10 changed files with 352 additions and 27 deletions

View File

@ -26,6 +26,7 @@ from epdb.models import (
Compound,
Reaction,
CompoundStructure,
EnzymeLink,
)
from utilities.chem import FormatConverter
from utilities.misc import PackageImporter, PackageExporter
@ -617,6 +618,8 @@ class PackageManager(object):
parent_mapping = {}
# Mapping old scen_id to old_obj_id
scen_mapping = defaultdict(list)
# Enzymelink Mapping rule_id to enzymelink objects
enzyme_mapping = defaultdict(list)
# Store Scenarios
for scenario in data["scenarios"]:
@ -648,9 +651,7 @@ class PackageManager(object):
# Broken eP Data
if name == "initialmasssediment" and addinf_data == "missing data":
continue
# TODO Enzymes arent ready yet
if name == "enzyme":
if name == "columnheight" and addinf_data == "(2)-(2.5);(6)-(8)":
continue
try:
@ -740,6 +741,9 @@ class PackageManager(object):
for scen in rule["scenarios"]:
scen_mapping[scen["id"]].append(r)
for enzyme_link in rule.get("enzymeLinks", []):
enzyme_mapping[r.uuid].append(enzyme_link)
print("Par: ", len(par_rules))
print("Seq: ", len(seq_rules))
@ -757,6 +761,9 @@ class PackageManager(object):
for scen in par_rule["scenarios"]:
scen_mapping[scen["id"]].append(r)
for enzyme_link in par_rule.get("enzymeLinks", []):
enzyme_mapping[r.uuid].append(enzyme_link)
for simple_rule in par_rule["simpleRules"]:
if simple_rule["id"] in mapping:
r.simple_rules.add(SimpleRule.objects.get(uuid=mapping[simple_rule["id"]]))
@ -777,6 +784,9 @@ class PackageManager(object):
for scen in seq_rule["scenarios"]:
scen_mapping[scen["id"]].append(r)
for enzyme_link in seq_rule.get("enzymeLinks", []):
enzyme_mapping[r.uuid].append(enzyme_link)
for i, simple_rule in enumerate(seq_rule["simpleRules"]):
sro = SequentialRuleOrdering()
sro.simple_rule = simple_rule
@ -910,6 +920,39 @@ class PackageManager(object):
print("Scenarios linked...")
# Import Enzyme Links
for rule_uuid, enzyme_links in enzyme_mapping.items():
r = Rule.objects.get(uuid=rule_uuid)
for enzyme in enzyme_links:
e = EnzymeLink()
e.uuid = UUID(enzyme["id"].split("/")[-1]) if keep_ids else uuid4()
e.rule = r
e.name = enzyme["name"]
e.ec_number = enzyme["ecNumber"]
e.classification_level = enzyme["classificationLevel"]
e.linking_method = enzyme["linkingMethod"]
e.save()
for reaction in enzyme["reactionLinkEvidence"]:
reaction = Reaction.objects.get(uuid=mapping[reaction["id"]])
e.reaction_evidence.add(reaction)
for edge in enzyme["edgeLinkEvidence"]:
edge = Edge.objects.get(uuid=mapping[edge["id"]])
e.reaction_evidence.add(edge)
for evidence in enzyme["linkEvidence"]:
matches = re.findall(r">(R[0-9]+)<", evidence["evidence"])
if not matches or len(matches) != 1:
logger.warning(f"Could not find reaction id in {evidence['evidence']}")
continue
e.add_kegg_reaction_id(matches[0])
e.save()
print("Enzyme links imported...")
print("Import statistics:")
print("Package {} stored".format(pack.url))
print("Imported {} compounds".format(Compound.objects.filter(package=pack).count()))

View File

@ -41,6 +41,7 @@ class Command(BaseCommand):
"RuleBasedRelativeReasoning",
"EnviFormer",
"ApplicabilityDomain",
"EnzymeLink",
]
for model in MODELS:
obj_cls = apps.get_model("epdb", model)

View File

@ -494,6 +494,20 @@ class ChemicalIdentifierMixin(ExternalIdentifierMixin):
return self.get_external_identifier("CAS")
class KEGGIdentifierMixin(ExternalIdentifierMixin):
@property
def kegg_reaction_links(self):
return self.get_external_identifier("KEGG Reaction")
def add_kegg_reaction_id(self, kegg_id):
return self.add_external_identifier(
"KEGG Reaction", kegg_id, f"https://www.genome.jp/entry/{kegg_id}"
)
class Meta:
abstract = True
class ReactionIdentifierMixin(ExternalIdentifierMixin):
class Meta:
abstract = True
@ -1014,6 +1028,26 @@ class CompoundStructure(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdenti
return self.compound.default_structure == self
class EnzymeLink(EnviPathModel, KEGGIdentifierMixin):
rule = models.ForeignKey("Rule", on_delete=models.CASCADE, db_index=True)
ec_number = models.TextField(blank=False, null=False, verbose_name="EC Number")
classification_level = models.IntegerField(
blank=False, null=False, verbose_name="Classification Level"
)
linking_method = models.TextField(blank=False, null=False, verbose_name="Linking Method")
reaction_evidence = models.ManyToManyField("epdb.Reaction")
edge_evidence = models.ManyToManyField("epdb.Edge")
external_identifiers = GenericRelation("ExternalIdentifier")
def _url(self):
return "{}/enzymelink/{}".format(self.rule.url, self.uuid)
def get_group(self) -> str:
return ".".join(self.ec_number.split(".")[:3]) + ".-"
class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
package = models.ForeignKey(
"epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
@ -1095,6 +1129,18 @@ class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
return new_rule
def enzymelinks(self):
return self.enzymelink_set.all()
def get_grouped_enzymelinks(self):
res = defaultdict(list)
for el in self.enzymelinks():
key = ".".join(el.ec_number.split(".")[:3]) + ".-"
res[key].append(el)
return dict(res)
class SimpleRule(Rule):
pass
@ -1437,6 +1483,16 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin
id__in=Edge.objects.filter(edge_label=self).values("pathway_id")
).order_by("name")
def get_related_enzymes(self):
res = []
edges = Edge.objects.filter(edge_label=self)
for e in edges:
for scen in e.scenarios.all():
for ai in scen.additional_information.keys():
if ai == "Enzyme":
res.extend(scen.additional_information[ai])
return res
class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
package = models.ForeignKey(

View File

@ -1,5 +1,5 @@
from django.urls import path, re_path
from django.contrib.auth import views as auth_views
from django.urls import path, re_path
from . import views as v
@ -88,20 +88,36 @@ urlpatterns = [
v.package_rule,
name="package rule detail",
),
re_path(
rf"^package/(?P<package_uuid>{UUID})/simple-rdkit-rule/(?P<rule_uuid>{UUID})$",
v.package_rule,
name="package rule detail",
),
# re_path(
# rf"^package/(?P<package_uuid>{UUID})/simple-rdkit-rule/(?P<rule_uuid>{UUID})$",
# v.package_rule,
# name="package rule detail",
# ),
re_path(
rf"^package/(?P<package_uuid>{UUID})/parallel-rule/(?P<rule_uuid>{UUID})$",
v.package_rule,
name="package rule detail",
),
# re_path(
# rf"^package/(?P<package_uuid>{UUID})/sequential-rule/(?P<rule_uuid>{UUID})$",
# v.package_rule,
# name="package rule detail",
# ),
# EnzymeLinks
re_path(
rf"^package/(?P<package_uuid>{UUID})/sequential-rule/(?P<rule_uuid>{UUID})$",
v.package_rule,
name="package rule detail",
rf"^package/(?P<package_uuid>{UUID})/rule/(?P<rule_uuid>{UUID})/enzymelink/(?P<enzymelink_uuid>{UUID})$",
v.package_rule_enzymelink,
name="package rule enzymelink detail",
),
re_path(
rf"^package/(?P<package_uuid>{UUID})/simple-ambit-rule/(?P<rule_uuid>{UUID})/enzymelink/(?P<enzymelink_uuid>{UUID})$",
v.package_rule_enzymelink,
name="package rule enzymelink detail",
),
re_path(
rf"^package/(?P<package_uuid>{UUID})/parallel-rule/(?P<rule_uuid>{UUID})/enzymelink/(?P<enzymelink_uuid>{UUID})$",
v.package_rule_enzymelink,
name="package rule enzymelink detail",
),
# Reaction
re_path(

View File

@ -46,6 +46,7 @@ from .models import (
Edge,
ExternalDatabase,
ExternalIdentifier,
EnzymeLink,
)
logger = logging.getLogger(__name__)
@ -1528,6 +1529,32 @@ def package_rule(request, package_uuid, rule_uuid):
return HttpResponseNotAllowed(["GET", "POST"])
@package_permission_required()
def package_rule_enzymelink(request, package_uuid, rule_uuid, enzymelink_uuid):
current_user = _anonymous_or_real(request)
current_package = PackageManager.get_package_by_id(current_user, package_uuid)
current_rule = Rule.objects.get(package=current_package, uuid=rule_uuid)
current_enzymelink = EnzymeLink.objects.get(rule=current_rule, uuid=enzymelink_uuid)
if request.method == "GET":
context = get_base_context(request)
context["title"] = f"enviPath - {current_package.name} - {current_rule.name}"
context["meta"]["current_package"] = current_package
context["object_type"] = "enzyme"
context["breadcrumbs"] = breadcrumbs(
current_package, "rule", current_rule, "enzymelink", current_enzymelink
)
context["enzymelink"] = current_enzymelink
context["current_object"] = current_enzymelink
return render(request, "objects/enzymelink.html", context)
return HttpResponseNotAllowed(["GET"])
@package_permission_required()
def package_reactions(request, package_uuid):
current_user = _anonymous_or_real(request)