diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 7de35661..5e897309 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -41,7 +41,7 @@ jobs: EP_DATA_DIR: /opt/enviPy/ ALLOWED_HOSTS: 127.0.0.1,localhost DEBUG: True - LOG_LEVEL: DEBUG + LOG_LEVEL: INFO MODEL_BUILDING_ENABLED: True APPLICABILITY_DOMAIN_ENABLED: True ENVIFORMER_PRESENT: True diff --git a/epdb/migrations/0012_node_stereo_removed_pathway_predicted.py b/epdb/migrations/0012_node_stereo_removed_pathway_predicted.py new file mode 100644 index 00000000..648090d7 --- /dev/null +++ b/epdb/migrations/0012_node_stereo_removed_pathway_predicted.py @@ -0,0 +1,22 @@ +# Generated by Django 5.2.7 on 2025-12-02 13:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("epdb", "0011_auto_20251111_1413"), + ] + + operations = [ + migrations.AddField( + model_name="node", + name="stereo_removed", + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name="pathway", + name="predicted", + field=models.BooleanField(default=False), + ), + ] diff --git a/epdb/models.py b/epdb/models.py index 67a34829..cfac5d6a 100644 --- a/epdb/models.py +++ b/epdb/models.py @@ -1593,6 +1593,7 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin): setting = models.ForeignKey( "epdb.Setting", verbose_name="Setting", on_delete=models.CASCADE, null=True, blank=True ) + predicted = models.BooleanField(default=False, null=False) @property def root_nodes(self): @@ -1804,6 +1805,7 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin): smiles: str, name: Optional[str] = None, description: Optional[str] = None, + predicted: bool = False, ): pw = Pathway() pw.package = package @@ -1816,6 +1818,7 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin): pw.name = name if description is not None and description.strip() != "": pw.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip() + pw.predicted = predicted pw.save() try: @@ -1946,6 +1949,7 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin): ) out_edges = models.ManyToManyField("epdb.Edge", verbose_name="Outgoing Edges") depth = models.IntegerField(verbose_name="Node depth", null=False, blank=False) + stereo_removed = models.BooleanField(default=False, null=False) def _url(self): return "{}/node/{}".format(self.pathway.url, self.uuid) @@ -1955,6 +1959,7 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin): return { "depth": self.depth, + "stereo_removed": self.stereo_removed, "url": self.url, "node_label_id": self.default_node_label.url, "image": f"{self.url}?image=svg", @@ -1980,12 +1985,17 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin): name: Optional[str] = None, description: Optional[str] = None, ): + stereo_removed = False + if pathway.predicted and FormatConverter.has_stereo(smiles): + smiles = FormatConverter.standardize(smiles, remove_stereo=True) + stereo_removed = True c = Compound.create(pathway.package, smiles, name=name, description=description) if Node.objects.filter(pathway=pathway, default_node_label=c.default_structure).exists(): return Node.objects.get(pathway=pathway, default_node_label=c.default_structure) n = Node() + n.stereo_removed = stereo_removed n.pathway = pathway n.depth = depth diff --git a/epdb/views.py b/epdb/views.py index accce6f1..335743d5 100644 --- a/epdb/views.py +++ b/epdb/views.py @@ -961,9 +961,9 @@ def package_model(request, package_uuid, model_uuid): # Check if smiles is non empty and valid if smiles == "": return JsonResponse({"error": "Received empty SMILES"}, status=400) - + stereo = FormatConverter.has_stereo(smiles) try: - stand_smiles = FormatConverter.standardize(smiles) + stand_smiles = FormatConverter.standardize(smiles, remove_stereo=True) except ValueError: return JsonResponse({"error": f'"{smiles}" is not a valid SMILES'}, status=400) @@ -974,7 +974,7 @@ def package_model(request, package_uuid, model_uuid): current_user, predict_simple, current_model.pk, stand_smiles ) - res = [] + res = {"pred": [], "stereo": stereo} for pr in pred_res: if len(pr) > 0: @@ -983,7 +983,7 @@ def package_model(request, package_uuid, model_uuid): logger.debug(f"Checking {prod_set}") products.append(tuple([x for x in prod_set])) - res.append( + res["pred"].append( { "products": list(set(products)), "probability": pr.probability, @@ -1931,7 +1931,6 @@ def package_pathways(request, package_uuid): "Pathway prediction failed!", "Pathway prediction failed due to missing or empty SMILES", ) - try: stand_smiles = FormatConverter.standardize(smiles) except ValueError: @@ -1954,8 +1953,13 @@ def package_pathways(request, package_uuid): prediction_setting = SettingManager.get_setting_by_url(current_user, prediction_setting) else: prediction_setting = current_user.prediction_settings() - - pw = Pathway.create(current_package, stand_smiles, name=name, description=description) + pw = Pathway.create( + current_package, + stand_smiles, + name=name, + description=description, + predicted=pw_mode in {"predict", "incremental"}, + ) # set mode pw.kv.update({"mode": pw_mode}) diff --git a/fixtures/test_fixtures_incl_model.jsonl.gz b/fixtures/test_fixtures_incl_model.jsonl.gz index c09475ef..2f24cca5 100644 Binary files a/fixtures/test_fixtures_incl_model.jsonl.gz and b/fixtures/test_fixtures_incl_model.jsonl.gz differ diff --git a/static/js/pw.js b/static/js/pw.js index 8c7c49ba..cefabe1b 100644 --- a/static/js/pw.js +++ b/static/js/pw.js @@ -360,7 +360,11 @@ function draw(pathway, elem) { } function node_popup(n) { - popupContent = "" + n.name + "
"; + popupContent = ""; + if (n.stereo_removed) { + popupContent += "Removed stereochemistry for prediction"; + } + popupContent += "" + n.name + "
"; popupContent += "Depth " + n.depth + "
" if (appDomainViewEnabled) { diff --git a/templates/objects/model.html b/templates/objects/model.html index 7693e3df..84524ac8 100644 --- a/templates/objects/model.html +++ b/templates/objects/model.html @@ -117,7 +117,9 @@
-
Predict
+
+ Predict +
@@ -193,7 +195,13 @@ {# FIXME: This is a hack to get the precision recall curve data into the JavaScript code. #}