48 Commits

Author SHA1 Message Date
4158bd36cb [Feature] Legacy API Layer (#80)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#80
2025-09-03 01:35:51 +12:00
4e02910c62 Add Link to Community in Navbar (#79)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#79
2025-09-02 08:28:37 +12:00
2babe7f7e2 [Feature] Scenario Creation (#78)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#78
2025-09-02 08:06:18 +12:00
7da3880a9b Log only unhandled or logged by 3rd party logger exceptions (#73)
Fixes #67

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#73
2025-09-01 00:16:59 +12:00
52931526c1 If Molecule contains a ~ load it as SMARTS (#71)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#71
2025-08-29 09:14:24 +12:00
dd7b28046c CSS Fix for Index Page (#69)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#69
2025-08-29 08:19:43 +12:00
8592cfae50 Change how List Pages are populated (#68)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#68
2025-08-29 08:09:57 +12:00
ec2b941a85 Fixes #60 (#66)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#66
2025-08-28 06:56:38 +12:00
02d84a9b29 Fix wrong Button Label in "Compound -> Edit" (#64)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#64
2025-08-28 06:32:42 +12:00
00d9188c0c Copy Objects between Packages (#59)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#59
2025-08-28 06:27:11 +12:00
13816ecaf3 Make URL a Field instead a property (#63)
This PR adds a new Field to all existing Models.
As its a data migrations the Migration is added.

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#63
2025-08-27 06:46:09 +12:00
6a4c8d96c3 Adjust Matomo Site ID (#57)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#57
2025-08-26 06:17:09 +12:00
97d0527565 Merge pull request 'fixed calls to create MLRelativeReasoning during bootstrap.py and test_model.py' (#56) from fix/mlrr_arguments into develop
Reviewed-on: enviPath/enviPy#56
2025-08-22 11:29:02 +12:00
b45c99f7d3 fixed calls to create MLRelativeReasoning during bootstrap.py and test_model.py 2025-08-22 11:28:01 +12:00
b95ec98a2f Added Shortcut to make Packages Public (#54)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#54
2025-08-22 07:31:08 +12:00
c79a1f2040 Fix "Impersonation" (#53)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#53
2025-08-22 07:01:49 +12:00
6e6b394289 Cleanup (#52)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#52
2025-08-22 06:36:22 +12:00
ec387cc12e Added UI elements to add/remove Scenarios to various objects (#51)
Fixes #23

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#51
2025-08-21 07:56:44 +12:00
a7637d046a External References (#50)
Fix for #24

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#50
2025-08-21 06:11:05 +12:00
fc8192fb0d Fix App Domain Bug when a Rule can be applied more than once (#49)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#49
2025-08-19 22:10:18 +12:00
c3c1d4f5cf App Domain Pathway Prediction (#47)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#47
2025-08-19 02:53:56 +12:00
3308d47071 Fix bond breaking (#46)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#46
2025-08-15 09:06:07 +12:00
1267ca8ace Enable App Domain Assessment on Model Page (#45)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#45
2025-08-12 09:02:11 +12:00
ec52b8872d Functional Group Calculation (#44)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#44
2025-08-11 09:07:07 +12:00
579cd519d0 Experimental App Domain (#43)
Backend App Domain done, Frontend missing

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#43
2025-08-08 20:52:21 +12:00
280ddc7205 Fixed UUID vs str comparison when getting User (#42)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#42
2025-07-31 09:11:52 +12:00
c9d6d8b024 Delete Stale Edges when removing a Node from a Pathway (#41)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#41
2025-07-31 07:50:50 +12:00
a1aebfa54d Model Building UI Flag (#39)
Fixes #8
Flag only disables UI Elements

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#39
2025-07-31 07:00:54 +12:00
79b4b1586c Download Pathway Functionality (#38)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#38
2025-07-31 01:30:16 +12:00
aec61151ce Correct Label assignment (#37)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#37
2025-07-31 00:43:28 +12:00
026189ccd9 First Issues / Improvements detected in Beta (#36)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#36
2025-07-24 09:57:50 +12:00
2c9f9038f3 Added Community Snippet (#35)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#35
2025-07-23 20:59:36 +12:00
2b8b6f286d Fix Pathway Creation (#34)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#34
2025-07-23 10:04:13 +12:00
2c4c9d95d9 Add Edge Template (#33)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#33
2025-07-23 07:50:39 +12:00
43c95e3da7 feature/enforce_login (#32)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#32
2025-07-23 07:27:57 +12:00
df896878f1 Basic System (#31)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#31
2025-07-23 06:47:07 +12:00
49e02ed97d feature/additional_information (#30)
Fixes #12

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#30
2025-07-19 08:10:40 +12:00
4fff78541b Implement Admin approval (#29)
This PR fixes #7

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#29
2025-07-19 06:42:50 +12:00
9323a9f7d7 Implement Compound CRUD (#22)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#22
2025-07-03 07:17:04 +12:00
4e58a1fad7 Implement functionality to assigne Licenses to Packages and show existing Licenses (#17)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#17
2025-07-03 06:28:49 +12:00
9950112311 Implemented Reaction Page (#5)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#5
2025-06-28 06:40:58 +12:00
6eb1d1bd65 Added Sentry (#4)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#4
2025-06-28 06:00:15 +12:00
bcd9451450 Implement basic Group handling (#3)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#3
2025-06-26 00:18:40 +12:00
844d0708c9 Granting/Updating Permission for Packages (#2)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#2
2025-06-25 08:49:58 +12:00
7c3bc69b38 Scenario Import + Gitea PR Test (#1)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#1
2025-06-24 23:56:45 +12:00
843e8e6f07 make use of prediction_settings() method to ensure that a default setting is used in case no setting is set 2025-06-24 11:33:58 +02:00
acdb62c08f added uv.lock to avoid dep issues 2025-06-24 09:07:32 +02:00
ded50edaa2 Current Dev State 2025-06-23 20:13:54 +02:00
22661 changed files with 4356675 additions and 174 deletions

19
.env-example Normal file
View File

@ -0,0 +1,19 @@
# settings.py
EP_DATA_DIR=
ALLOWED_HOSTS=
DEBUG=
LOG_LEVEL=
ENVIFORMER_PRESENT=
FLAG_CELERY_PRESENT=
SERVER_URL=
PLUGINS_ENABLED=
# DB
POSTGRES_SERVICE_NAME=
POSTGRES_DB=
POSTGRES_USER=
POSTGRES_PASSWORD=
POSTGRES_PORT=
# MAIL
EMAIL_HOST_USER=
EMAIL_HOST_PASSWORD=

179
.gitignore vendored
View File

@ -1,176 +1,7 @@
# ---> Python *.pyc
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3 db.sqlite3
db.sqlite3-journal .idea/
static/admin/
# Flask stuff: static/django_extensions/
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env .env
.venv debug.log
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc

1
.python-version Normal file
View File

@ -0,0 +1 @@
3.10

5
envipath/__init__.py Normal file
View File

@ -0,0 +1,5 @@
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app
__all__ = ('celery_app',)

14
envipath/api.py Normal file
View File

@ -0,0 +1,14 @@
from epdb.api import router as epdb_app_router
from epdb.legacy_api import router as epdb_legacy_app_router
from ninja import NinjaAPI
api = NinjaAPI()
from ninja import NinjaAPI
api_v1 = NinjaAPI(title="API V1 Docs", urls_namespace="api-v1")
api_legacy = NinjaAPI(title="Legacy API Docs", urls_namespace="api-legacy")
# Add routers
api_v1.add_router("/", epdb_app_router)
api_legacy.add_router("/", epdb_legacy_app_router)

16
envipath/asgi.py Normal file
View File

@ -0,0 +1,16 @@
"""
ASGI config for envipath project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'envipath.settings')
application = get_asgi_application()

27
envipath/celery.py Normal file
View File

@ -0,0 +1,27 @@
import os
from celery import Celery
from celery.signals import setup_logging
# Set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'envipath.settings')
app = Celery('envipath')
# Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys
# should have a `CELERY_` prefix.
app.config_from_object('django.conf:settings', namespace='CELERY')
@setup_logging.connect
def config_loggers(*args, **kwargs):
from logging.config import dictConfig
from django.conf import settings
dictConfig(settings.LOGGING)
# Load task modules from all registered Django apps.
app.autodiscover_tasks()

337
envipath/settings.py Normal file
View File

@ -0,0 +1,337 @@
"""
Django settings for envipath project.
Generated by 'django-admin startproject' using Django 4.2.17.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""
import os
from pathlib import Path
from dotenv import load_dotenv
from envipy_plugins import Classifier, Property, Descriptor
from sklearn.ensemble import RandomForestClassifier
from sklearn.tree import DecisionTreeClassifier
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
load_dotenv(BASE_DIR / '.env', override=False)
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '7!VTW`aZqg/UBLsM.P=m)2]lWqg>{+:xUgG1"WO@bCyaHR2Up8XW&g<*3.F4l2gi9c.E3}dHyA0D`&z?u#U%^7HYbj],eP"g_MS|3BNMD[mI>s#<i/%2ngZ~Oy+/w&@]'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.environ.get('DEBUG', 'False') == 'True'
ALLOWED_HOSTS = os.environ['ALLOWED_HOSTS'].split(',')
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 3rd party
'django_extensions',
# Custom
'epdb',
'migration',
]
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
if os.environ.get('REGISTRATION_MANDATORY', False) == 'True':
MIDDLEWARE.append('epdb.middleware.login_required_middleware.LoginRequiredMiddleware')
ROOT_URLCONF = 'envipath.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': (os.path.join(BASE_DIR, 'templates'),),
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'envipath.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"USER": os.environ['POSTGRES_USER'],
"NAME": os.environ['POSTGRES_DB'],
"PASSWORD": os.environ['POSTGRES_PASSWORD'],
"HOST": os.environ['POSTGRES_SERVICE_NAME'],
"PORT": os.environ['POSTGRES_PORT']
}
}
# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# EMAIL
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_USE_TLS = True
EMAIL_HOST = 'mail.gandi.net'
EMAIL_HOST_USER = os.environ['EMAIL_HOST_USER']
EMAIL_HOST_PASSWORD = os.environ['EMAIL_HOST_PASSWORD']
EMAIL_PORT = 587
AUTH_USER_MODEL = "epdb.User"
ADMIN_APPROVAL_REQUIRED = os.environ.get('ADMIN_APPROVAL_REQUIRED', 'False') == 'True'
# # SESAME
# SESAME_MAX_AGE = 300
# # TODO set to "home"
# LOGIN_REDIRECT_URL = "/"
LOGIN_URL = '/login/'
SERVER_URL = os.environ.get('SERVER_URL', 'http://localhost:8000')
CSRF_TRUSTED_ORIGINS = [SERVER_URL]
AMBIT_URL = 'http://localhost:9001'
DEFAULT_VALUES = {
'description': 'no description'
}
EP_DATA_DIR = os.environ['EP_DATA_DIR']
MODEL_DIR = os.path.join(EP_DATA_DIR, 'models')
if not os.path.exists(MODEL_DIR):
os.mkdir(MODEL_DIR)
STATIC_DIR = os.path.join(EP_DATA_DIR, 'static')
if not os.path.exists(STATIC_DIR):
os.mkdir(STATIC_DIR)
LOG_DIR = os.path.join(EP_DATA_DIR, 'log')
if not os.path.exists(LOG_DIR):
os.mkdir(LOG_DIR)
PLUGIN_DIR = os.path.join(EP_DATA_DIR, 'plugins')
if not os.path.exists(PLUGIN_DIR):
os.mkdir(PLUGIN_DIR)
# Set this as our static root dir
STATIC_ROOT = STATIC_DIR
STATIC_URL = '/static/'
# Where the sources are stored...
STATICFILES_DIRS = (
BASE_DIR / 'static',
)
FIXTURE_DIRS = (
BASE_DIR / 'fixtures',
)
# Logging
LOGGING = {
"version": 1,
"disable_existing_loggers": True,
"formatters": {
"simple": {
'format': '[%(asctime)s] %(levelname)s %(module)s - %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S',
},
},
"handlers": {
"console": {
"level": "INFO",
"class": "logging.StreamHandler",
"formatter": "simple",
},
"file": {
"level": "DEBUG", # Or higher
"class": "logging.FileHandler",
"filename": os.path.join(LOG_DIR, "debug.log"),
"formatter": "simple"
},
},
"loggers": {
# For everything under epdb/ loaded via getlogger(__name__)
"epdb": {
"handlers": ["file"], # "console",
"propagate": True,
"level": os.environ.get('LOG_LEVEL', 'INFO')
},
# For everything under envipath/ loaded via getlogger(__name__)
'envipath': {
'handlers': ['file', 'console'],
'propagate': True,
'level': os.environ.get('LOG_LEVEL', 'INFO')
},
# For everything under utilities/ loaded via getlogger(__name__)
'utilities': {
'handlers': ['file', 'console'],
'propagate': True,
'level': os.environ.get('LOG_LEVEL', 'INFO')
},
},
}
# Flags
ENVIFORMER_PRESENT = os.environ.get('ENVIFORMER_PRESENT', 'False') == 'True'
if ENVIFORMER_PRESENT:
print("Loading enviFormer")
device = os.environ.get('ENVIFORMER_DEVICE', 'cpu')
from enviformer import load
ENVIFORMER_INSTANCE = load(device=device)
print("loaded")
# If celery is not present set always eager to true which will cause delayed tasks to block until finished
FLAG_CELERY_PRESENT = os.environ.get('FLAG_CELERY_PRESENT', 'False') == 'True'
if not FLAG_CELERY_PRESENT:
CELERY_TASK_ALWAYS_EAGER = True
# Celery Configuration Options
CELERY_TIMEZONE = "Europe/Berlin"
# Celery Configuration
CELERY_BROKER_URL = 'redis://localhost:6379/0' # Use Redis as message broker
CELERY_RESULT_BACKEND = 'redis://localhost:6379/1'
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
MODEL_BUILDING_ENABLED = os.environ.get('MODEL_BUILDING_ENABLED', 'False') == 'True'
APPLICABILITY_DOMAIN_ENABLED = os.environ.get('APPLICABILITY_DOMAIN_ENABLED', 'False') == 'True'
DEFAULT_RF_MODEL_PARAMS = {
'base_clf': RandomForestClassifier(
n_estimators=100,
max_features='log2',
random_state=42,
criterion='entropy',
ccp_alpha=0.0,
max_depth=3,
min_samples_leaf=1
),
'num_chains': 10,
}
DEFAULT_MODEL_PARAMS = {
'base_clf': DecisionTreeClassifier(
criterion='entropy',
max_depth=3,
min_samples_split=5,
# min_samples_leaf=5,
max_features='sqrt',
# class_weight='balanced',
random_state=42
),
'num_chains': 10,
}
DEFAULT_MAX_NUMBER_OF_NODES = 30
DEFAULT_MAX_DEPTH = 5
DEFAULT_MODEL_THRESHOLD = 0.25
# Loading Plugins
PLUGINS_ENABLED = os.environ.get('PLUGINS_ENABLED', 'False') == 'True'
if PLUGINS_ENABLED:
from utilities.plugin import discover_plugins
CLASSIFIER_PLUGINS = discover_plugins(_cls=Classifier)
PROPERTY_PLUGINS = discover_plugins(_cls=Property)
DESCRIPTOR_PLUGINS = discover_plugins(_cls=Descriptor)
else:
CLASSIFIER_PLUGINS = {}
PROPERTY_PLUGINS = {}
DESCRIPTOR_PLUGINS = {}
SENTRY_ENABLED = os.environ.get('SENTRY_ENABLED', 'False') == 'True'
if SENTRY_ENABLED:
import sentry_sdk
def before_send(event, hint):
# Check if was a handled exception by one of our loggers
if event.get('logger'):
for log_path in LOGGING.get('loggers').keys():
if event['logger'].startswith(log_path):
return None
return event
sentry_sdk.init(
dsn=os.environ.get('SENTRY_DSN'),
# Add data like request headers and IP for users,
# see https://docs.sentry.io/platforms/python/data-management/data-collected/ for more info
send_default_pii=True,
environment=os.environ.get('SENTRY_ENVIRONMENT', 'development'),
before_send=before_send,
)
# compile into digestible flags
FLAGS = {
'MODEL_BUILDING': MODEL_BUILDING_ENABLED,
'CELERY': FLAG_CELERY_PRESENT,
'PLUGINS': PLUGINS_ENABLED,
'SENTRY': SENTRY_ENABLED,
'ENVIFORMER': ENVIFORMER_PRESENT,
'APPLICABILITY_DOMAIN': APPLICABILITY_DOMAIN_ENABLED,
}

28
envipath/urls.py Normal file
View File

@ -0,0 +1,28 @@
"""
URL configuration for envipath project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include, path
from .api import api_v1, api_legacy
urlpatterns = [
path("", include("epdb.urls")),
path("", include("migration.urls")),
path("admin/", admin.site.urls),
path("api/v1/", api_v1.urls),
path("api/legacy/", api_legacy.urls),
]

16
envipath/wsgi.py Normal file
View File

@ -0,0 +1,16 @@
"""
WSGI config for envipath project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'envipath.settings')
application = get_wsgi_application()

0
epdb/__init__.py Normal file
View File

105
epdb/admin.py Normal file
View File

@ -0,0 +1,105 @@
from django.contrib import admin
from .models import (
User,
UserPackagePermission,
Group,
GroupPackagePermission,
Package,
MLRelativeReasoning,
Compound,
CompoundStructure,
SimpleAmbitRule,
ParallelRule,
Reaction,
Pathway,
Node,
Edge,
Scenario,
Setting
)
class UserAdmin(admin.ModelAdmin):
pass
class UserPackagePermissionAdmin(admin.ModelAdmin):
pass
class GroupAdmin(admin.ModelAdmin):
pass
class GroupPackagePermissionAdmin(admin.ModelAdmin):
pass
class EPAdmin(admin.ModelAdmin):
search_fields = ['name', 'description']
class PackageAdmin(EPAdmin):
pass
class MLRelativeReasoningAdmin(EPAdmin):
pass
class CompoundAdmin(EPAdmin):
pass
class CompoundStructureAdmin(EPAdmin):
pass
class SimpleAmbitRuleAdmin(EPAdmin):
pass
class ParallelRuleAdmin(EPAdmin):
pass
class ReactionAdmin(EPAdmin):
pass
class PathwayAdmin(EPAdmin):
pass
class NodeAdmin(EPAdmin):
pass
class EdgeAdmin(EPAdmin):
pass
class ScenarioAdmin(EPAdmin):
pass
class SettingAdmin(EPAdmin):
pass
admin.site.register(User, UserAdmin)
admin.site.register(UserPackagePermission, UserPackagePermissionAdmin)
admin.site.register(Group, GroupAdmin)
admin.site.register(GroupPackagePermission, GroupPackagePermissionAdmin)
admin.site.register(Package, PackageAdmin)
admin.site.register(MLRelativeReasoning, MLRelativeReasoningAdmin)
admin.site.register(Compound, CompoundAdmin)
admin.site.register(CompoundStructure, CompoundStructureAdmin)
admin.site.register(SimpleAmbitRule, SimpleAmbitRuleAdmin)
admin.site.register(ParallelRule, ParallelRuleAdmin)
admin.site.register(Reaction, ReactionAdmin)
admin.site.register(Pathway, PathwayAdmin)
admin.site.register(Node, NodeAdmin)
admin.site.register(Edge, EdgeAdmin)
admin.site.register(Setting, SettingAdmin)
admin.site.register(Scenario, ScenarioAdmin)

108
epdb/api.py Normal file
View File

@ -0,0 +1,108 @@
from typing import List
from django.contrib.auth import get_user_model
from ninja import Router, Schema, Field
from ninja.errors import HttpError
from ninja.pagination import paginate
from ninja.security import HttpBearer
from .logic import PackageManager
from .models import User, Compound, APIToken
class BearerTokenAuth(HttpBearer):
def authenticate(self, request, token):
for token_obj in APIToken.objects.select_related("user").all():
if token_obj.check_token(token) and token_obj.is_valid():
return token_obj.user
raise HttpError(401, "Invalid or expired token")
def _anonymous_or_real(request):
if request.user.is_authenticated and not request.user.is_anonymous:
return request.user
return get_user_model().objects.get(username='anonymous')
router = Router(auth=BearerTokenAuth())
class UserSchema(Schema):
email: str
username: str
id: str = Field(None, alias="url")
class PackageIn(Schema):
name: str
description: str
class PackageOut(Schema):
id: str = Field(None, alias="url")
name: str
reviewed: bool
compound_links: str = None
@staticmethod
def resolve_compound_links(obj):
return f"{obj.url}/compound"
class Error(Schema):
message: str
class CompoundSchema(Schema):
name: str = Field(None, alias="name")
id: str = Field(None, alias="url")
smiles: str = Field(None, alias="default_structure.smiles")
reviewed: bool = Field(None, alias="package.reviewed")
@router.get("/user", response={200: List[UserSchema], 403: Error})
def get_users(request):
return User.objects.all()
@router.get("/package", response={200: List[PackageOut], 403: Error})
def get_packages(request):
return PackageManager.get_all_readable_packages(request.user, include_reviewed=True)
@router.post("/package", response=PackageOut)
def create_package(request, package: PackageIn):
user = request.auth
name = package.name.strip()
description = package.description.strip()
p = PackageManager.create_package(user, name, description=description)
return p
@router.get("/package/{uuid:package_uuid}", response={200: PackageOut, 403: Error})
def get_package(request, package_uuid):
try:
return PackageManager.get_package_by_id(request.auth, package_id=package_uuid)
except ValueError:
return 403, {'message': f'Getting Package with id {package_uuid} failed due to insufficient rights!'}
@router.get("/compound", response={200: List[CompoundSchema], 403: Error})
@paginate
def get_compounds(request):
qs = Compound.objects.none()
for p in PackageManager.get_reviewed_packages():
qs |= Compound.objects.filter(package=p)
return qs
@router.get("/package/{uuid:package_uuid}/compound", response={200: List[CompoundSchema], 403: Error})
@paginate
def get_package_compounds(request, package_uuid):
try:
p = PackageManager.get_package_by_id(request.auth, package_uuid)
return Compound.objects.filter(package=p)
except ValueError:
return 403, {
'message': f'Getting Compounds for Package with id {package_uuid} failed due to insufficient rights!'}

9
epdb/apps.py Normal file
View File

@ -0,0 +1,9 @@
from django.apps import AppConfig
class EPDBConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'epdb'
def ready(self):
import epdb.signals # noqa: F401

5
epdb/forms.py Normal file
View File

@ -0,0 +1,5 @@
from django import forms
class EmailLoginForm(forms.Form):
email = forms.EmailField()

739
epdb/legacy_api.py Normal file
View File

@ -0,0 +1,739 @@
from typing import List, Dict, Optional, Any
from django.contrib.auth import get_user_model
from django.http import HttpResponse
from ninja import Router, Schema, Field, Form
from utilities.chem import FormatConverter
from .logic import PackageManager
from .models import Compound, CompoundStructure, Package, User, UserPackagePermission, Rule, Reaction, Scenario, Pathway
def _anonymous_or_real(request):
if request.user.is_authenticated and not request.user.is_anonymous:
return request.user
return get_user_model().objects.get(username='anonymous')
# router = Router(auth=SessionAuth())
router = Router()
class Error(Schema):
message: str
class SimpleObject(Schema):
id: str = Field(None, alias="url")
name: str = Field(None, alias="name")
reviewStatus: bool = Field(None, alias="package.reviewed")
################
# Login/Logout #
################
class SimpleUser(Schema):
id: str = Field(None, alias="url")
identifier: str = 'user'
name: str = Field(None, alias='username')
email: str = Field(None, alias='email')
@router.post("/", response={200: SimpleUser, 403: Error})
def login(request, loginusername: Form[str], loginpassword: Form[str], hiddenMethod: Form[str]):
from django.contrib.auth import authenticate
from django.contrib.auth import login
email = User.objects.get(username=loginusername).email
user = authenticate(username=email, password=loginpassword)
if user:
login(request, user)
return user
else:
return 403, {'message': 'Invalid username or password'}
class SimpleGroup(Schema):
id: str
identifier: str = 'group'
name: str
###########
# Package #
###########
class SimplePackage(SimpleObject):
identifier: str = 'package'
reviewStatus: bool = Field(None, alias="reviewed")
class PackageSchema(Schema):
description: str = Field(None, alias="description")
id: str = Field(None, alias="url")
links: List[Dict[str, List[str | int]]] = Field([], alias="links")
name: str = Field(None, alias="name")
primaryGroup: Optional[SimpleGroup] = None
readers: List[Dict[str, str]] = Field([], alias="readers")
reviewComment: str = Field(None, alias="review_comment")
reviewStatus: str = Field(None, alias="review_status")
writers: List[Dict[str, str]] = Field([], alias="writers")
@staticmethod
def resolve_links(obj: Package):
return [
{
'Pathways': [
f'{obj.url}/pathway', obj.pathways.count()
]
}, {
'Rules': [
f'{obj.url}/rule', obj.rules.count()
]
}, {
'Compounds': [
f'{obj.url}/compound', obj.compounds.count()
]
}, {
'Reactions': [
f'{obj.url}/reaction', obj.reactions.count()
]
}, {
'Relative Reasoning': [
f'{obj.url}/relative-reasoning', obj.models.count()
]
}, {
'Scenarios': [
f'{obj.url}/scenario', obj.scenarios.count()
]
}
]
@staticmethod
def resolve_readers(obj: Package):
users = User.objects.filter(
id__in=UserPackagePermission.objects.filter(
package=obj,
permission=UserPackagePermission.READ[0]
).values_list('user', flat=True)
).distinct()
return [{u.id: u.name} for u in users]
@staticmethod
def resolve_writers(obj: Package):
users = User.objects.filter(
id__in=UserPackagePermission.objects.filter(
package=obj,
permission=UserPackagePermission.WRITE[0]
).values_list('user', flat=True)
).distinct()
return [{u.id: u.name} for u in users]
@staticmethod
def resolve_review_comment(obj):
return ""
@staticmethod
def resolve_review_status(obj):
return 'reviewed' if obj.reviewed else 'unreviewed'
class PackageWrapper(Schema):
package: List['PackageSchema']
@router.get("/package", response={200: PackageWrapper, 403: Error})
def get_packages(request):
return {'package': PackageManager.get_all_readable_packages(request.user, include_reviewed=True)}
@router.get("/package/{uuid:package_uuid}", response={200: PackageSchema, 403: Error})
def get_package(request, package_uuid):
try:
return PackageManager.get_package_by_id(request.user, package_uuid)
except ValueError:
return 403, {
'message': f'Getting Package with id {package_uuid} failed due to insufficient rights!'}
################################
# Compound / CompoundStructure #
################################
class SimpleCompound(SimpleObject):
identifier: str = 'compound'
class CompoundPathwayScenario(Schema):
scenarioId: str
scenarioName: str
scenarioType: str
class CompoundSchema(Schema):
aliases: List[str] = Field([], alias="aliases")
description: str = Field(None, alias="description")
externalReferences: Dict[str, List[str]] = Field(None, alias="external_references")
id: str = Field(None, alias="url")
halflifes: List[Dict[str, str]] = Field([], alias="halflifes")
identifier: str = 'compound'
imageSize: int = 600
name: str = Field(None, alias="name")
pathwayScenarios: List[CompoundPathwayScenario] = Field([], alias="pathway_scenarios")
pathways: List['SimplePathway'] = Field([], alias="related_pathways")
pubchemCompoundReferences: List[str] = Field([], alias="pubchem_compound_references")
reactions: List['SimpleReaction'] = Field([], alias="related_reactions")
reviewStatus: str = Field(False, alias="review_status")
scenarios: List['SimpleScenario'] = Field([], alias="scenarios")
structures: List['CompoundStructureSchema'] = []
@staticmethod
def resolve_review_status(obj: CompoundStructure):
return 'reviewed' if obj.package.reviewed else 'unreviewed'
@staticmethod
def resolve_external_references(obj: Compound):
# TODO
return {}
@staticmethod
def resolve_structures(obj: Compound):
return CompoundStructure.objects.filter(compound=obj)
@staticmethod
def resolve_halflifes(obj: Compound):
return []
@staticmethod
def resolve_pubchem_compound_references(obj: Compound):
return []
@staticmethod
def resolve_pathway_scenarios(obj: Compound):
return [
{
'scenarioId': 'https://envipath.org/package/5882df9c-dae1-4d80-a40e-db4724271456/scenario/cd8350cd-4249-4111-ba9f-4e2209338501',
'scenarioName': 'Fritz, R. & Brauner, A. (1989) - (00004)',
'scenarioType': 'Soil'
}
]
class CompoundWrapper(Schema):
compound: List['SimpleCompound']
class SimpleCompoundStructure(SimpleObject):
identifier: str = 'structure'
reviewStatus: bool = Field(None, alias="compound.package.reviewed")
class CompoundStructureSchema(Schema):
InChI: str = Field(None, alias="inchi")
aliases: List[str] = Field([], alias="aliases")
canonicalSmiles: str = Field(None, alias="canonical_smiles")
charge: int = Field(None, alias="charge")
description: str = Field(None, alias="description")
externalReferences: Dict[str, List[str]] = Field(None, alias="external_references")
formula: str = Field(None, alias="formula")
halflifes: List[Dict[str, str]] = Field([], alias="halflifes")
id: str = Field(None, alias="url")
identifier: str = 'structure'
imageSize: int = 600
inchikey: str = Field(None, alias="inchikey")
isDefaultStructure: bool = Field(None, alias="is_default_structure")
mass: float = Field(None, alias="mass")
name: str = Field(None, alias="name")
pathways: List['SimplePathway'] = Field([], alias="related_pathways")
pubchemCompoundReferences: List[str] = Field([], alias="pubchem_compound_references")
reactions: List['SimpleReaction'] = Field([], alias="related_reactions")
reviewStatus: str = Field(None, alias="review_status")
scenarios: List['SimpleScenario'] = Field([], alias="scenarios")
smiles: str = Field(None, alias="smiles")
@staticmethod
def resolve_review_status(obj: CompoundStructure):
return 'reviewed' if obj.compound.package.reviewed else 'unreviewed'
@staticmethod
def resolve_inchi(obj: CompoundStructure):
return FormatConverter.InChI(obj.smiles)
@staticmethod
def resolve_charge(obj: CompoundStructure):
print(obj.smiles)
print(FormatConverter.charge(obj.smiles))
return FormatConverter.charge(obj.smiles)
@staticmethod
def resolve_formula(obj: CompoundStructure):
return FormatConverter.formula(obj.smiles)
@staticmethod
def resolve_mass(obj: CompoundStructure):
return FormatConverter.mass(obj.smiles)
@staticmethod
def resolve_external_references(obj: CompoundStructure):
# TODO
return {}
@staticmethod
def resolve_halflifes(obj: CompoundStructure):
return []
@staticmethod
def resolve_pubchem_compound_references(obj: CompoundStructure):
return []
@staticmethod
def resolve_pathway_scenarios(obj: CompoundStructure):
return [
{
'scenarioId': 'https://envipath.org/package/5882df9c-dae1-4d80-a40e-db4724271456/scenario/cd8350cd-4249-4111-ba9f-4e2209338501',
'scenarioName': 'Fritz, R. & Brauner, A. (1989) - (00004)',
'scenarioType': 'Soil'
}
]
class CompoundStructureWrapper(Schema):
structure: List['SimpleCompoundStructure']
@router.get("/compound", response={200: CompoundWrapper, 403: Error})
def get_compounds(request):
qs = Compound.objects.none()
for p in PackageManager.get_reviewed_packages():
qs |= Compound.objects.filter(package=p)
return {'compound': qs}
@router.get("/package/{uuid:package_uuid}/compound", response={200: CompoundWrapper, 403: Error})
def get_package_compounds(request, package_uuid):
try:
p = PackageManager.get_package_by_id(request.user, package_uuid)
return {'compound': Compound.objects.filter(package=p).prefetch_related('package')}
except ValueError:
return 403, {
'message': f'Getting Compounds for Package with id {package_uuid} failed due to insufficient rights!'}
@router.get("/package/{uuid:package_uuid}/compound/{uuid:compound_uuid}", response={200: CompoundSchema, 403: Error})
def get_package_compound(request, package_uuid, compound_uuid):
try:
p = PackageManager.get_package_by_id(request.user, package_uuid)
return Compound.objects.get(package=p, uuid=compound_uuid)
except ValueError:
return 403, {
'message': f'Getting Compound with id {compound_uuid} failed due to insufficient rights!'}
@router.get("/package/{uuid:package_uuid}/compound/{uuid:compound_uuid}/structure",
response={200: CompoundStructureWrapper, 403: Error})
def get_package_compound_structures(request, package_uuid, compound_uuid):
try:
p = PackageManager.get_package_by_id(request.user, package_uuid)
return {'structure': Compound.objects.get(package=p, uuid=compound_uuid).structures.all()}
except ValueError:
return 403, {
'message': f'Getting CompoundStructures for Compound with id {compound_uuid} failed due to insufficient rights!'}
@router.get("/package/{uuid:package_uuid}/compound/{uuid:compound_uuid}/structure/{uuid:structure_uuid}",
response={200: CompoundStructureSchema, 403: Error})
def get_package_compound_structure(request, package_uuid, compound_uuid, structure_uuid):
try:
p = PackageManager.get_package_by_id(request.user, package_uuid)
return CompoundStructure.objects.get(uuid=structure_uuid,
compound=Compound.objects.get(package=p, uuid=compound_uuid))
except ValueError:
return 403, {
'message': f'Getting CompoundStructure with id {structure_uuid} failed due to insufficient rights!'}
#########
# Rules #
#########
class SimpleRule(SimpleObject):
identifier: str = 'rule'
@staticmethod
def resolve_url(obj: Rule):
return obj.url.replace('-ambit-', '-').replace('-rdkit-', '-')
class RuleWrapper(Schema):
rule: List['SimpleRule']
class SimpleRuleSchema(Schema):
aliases: List[str] = Field([], alias="aliases")
description: str = Field(None, alias="description")
ecNumbers: List[Dict[str, str]] = Field([], alias="ec_numbers")
engine: str = 'ambit'
id: str = Field(None, alias="url")
identifier: str = Field(None, alias="identifier")
isCompositeRule: bool = False
name: str = Field(None, alias="name")
pathways: List['SimplePathway'] = Field([], alias="related_pathways")
productFilterSmarts: str = Field("", alias="product_filter_smarts")
productSmarts: str = Field(None, alias="products_smarts")
reactantFilterSmarts: str = Field("", alias="reactant_filter_smarts")
reactantSmarts: str = Field(None, alias="reactants_smarts")
reactions: List['SimpleReaction'] = Field([], alias="related_reactions")
reviewStatus: str = Field(None, alias="review_status")
scenarios: List['SimpleScenario'] = Field([], alias="scenarios")
smirks: str = Field("", alias="smirks")
# TODO
transformations: str = Field("", alias="transformations")
@staticmethod
def resolve_url(obj: Rule):
return obj.url.replace('-ambit-', '-').replace('-rdkit-', '-')
@staticmethod
def resolve_identifier(obj: Rule):
if 'simple-rule' in obj.url:
return 'simple-rule'
if 'simple-ambit-rule' in obj.url:
return 'simple-rule'
elif 'parallel-rule' in obj.url:
return 'parallel-rule'
elif 'sequential-rule' in obj.url:
return 'sequential-rule'
else:
return None
@staticmethod
def resolve_review_status(obj: Rule):
return 'reviewed' if obj.package.reviewed else 'unreviewed'
@staticmethod
def resolve_product_filter_smarts(obj: Rule):
return obj.product_filter_smarts if obj.product_filter_smarts else ''
@staticmethod
def resolve_reactant_filter_smarts(obj: Rule):
return obj.reactant_filter_smarts if obj.reactant_filter_smarts else ''
class CompositeRuleSchema(Schema):
aliases: List[str] = Field([], alias="aliases")
description: str = Field(None, alias="description")
ecNumbers: List[Dict[str, str]] = Field([], alias="ec_numbers")
id: str = Field(None, alias="url")
identifier: str = Field(None, alias="identifier")
isCompositeRule: bool = True
name: str = Field(None, alias="name")
pathways: List['SimplePathway'] = Field([], alias="related_pathways")
productFilterSmarts: str = Field("", alias="product_filter_smarts")
reactantFilterSmarts: str = Field("", alias="reactant_filter_smarts")
reactions: List['SimpleReaction'] = Field([], alias="related_reactions")
reviewStatus: str = Field(None, alias="review_status")
scenarios: List['SimpleScenario'] = Field([], alias="scenarios")
simpleRules: List['SimpleRule'] = Field([], alias="simple_rules")
@staticmethod
def resolve_ec_numbers(obj: Rule):
return []
@staticmethod
def resolve_url(obj: Rule):
return obj.url.replace('-ambit-', '-').replace('-rdkit-', '-')
@staticmethod
def resolve_identifier(obj: Rule):
if 'simple-rule' in obj.url:
return 'simple-rule'
if 'simple-ambit-rule' in obj.url:
return 'simple-rule'
elif 'parallel-rule' in obj.url:
return 'parallel-rule'
elif 'sequential-rule' in obj.url:
return 'sequential-rule'
else:
return None
@staticmethod
def resolve_review_status(obj: Rule):
return 'reviewed' if obj.package.reviewed else 'unreviewed'
@staticmethod
def resolve_product_filter_smarts(obj: Rule):
return obj.product_filter_smarts if obj.product_filter_smarts else ''
@staticmethod
def resolve_reactant_filter_smarts(obj: Rule):
return obj.reactant_filter_smarts if obj.reactant_filter_smarts else ''
@router.get("/rule", response={200: RuleWrapper, 403: Error})
def get_rules(request):
qs = Rule.objects.none()
for p in PackageManager.get_reviewed_packages():
qs |= Rule.objects.filter(package=p)
return {'rule': qs}
@router.get("/package/{uuid:package_uuid}/rule", response={200: RuleWrapper, 403: Error})
def get_package_rules(request, package_uuid):
try:
p = PackageManager.get_package_by_id(request.user, package_uuid)
return {'rule': Rule.objects.filter(package=p).prefetch_related('package')}
except ValueError:
return 403, {
'message': f'Getting Rules for Package with id {package_uuid} failed due to insufficient rights!'}
@router.get("/package/{uuid:package_uuid}/rule/{uuid:rule_uuid}",
response={200: SimpleRuleSchema | CompositeRuleSchema, 403: Error})
def get_package_rule(request, package_uuid, rule_uuid):
return _get_package_rule(request, package_uuid, rule_uuid)
@router.get("/package/{uuid:package_uuid}/simple-rule/{uuid:rule_uuid}",
response={200: SimpleRuleSchema | CompositeRuleSchema, 403: Error})
def get_package_simple_rule(request, package_uuid, rule_uuid):
return _get_package_rule(request, package_uuid, rule_uuid)
@router.get("/package/{uuid:package_uuid}/parallel-rule/{uuid:rule_uuid}",
response={200: SimpleRuleSchema | CompositeRuleSchema, 403: Error})
def get_package_parallel_rule(request, package_uuid, rule_uuid):
return _get_package_rule(request, package_uuid, rule_uuid)
def _get_package_rule(request, package_uuid, rule_uuid):
try:
p = PackageManager.get_package_by_id(request.user, package_uuid)
return Rule.objects.get(package=p, uuid=rule_uuid)
except ValueError:
return 403, {
'message': f'Getting Rule with id {rule_uuid} failed due to insufficient rights!'}
# POST
@router.post("/package/{uuid:package_uuid}/rule/{uuid:rule_uuid}", response={200: str | Any, 403: Error})
def post_package_rule(request, package_uuid, rule_uuid, compound: Form[str] = None):
return _post_package_rule(request, package_uuid, rule_uuid, compound=compound)
@router.post("/package/{uuid:package_uuid}/simple-rule/{uuid:rule_uuid}", response={200: str | Any, 403: Error})
def post_package_simple_rule(request, package_uuid, rule_uuid, compound: Form[str] = None):
return _post_package_rule(request, package_uuid, rule_uuid, compound=compound)
@router.post("/package/{uuid:package_uuid}/parallel-rule/{uuid:rule_uuid}", response={200: str | Any, 403: Error})
def post_package_parallel_rule(request, package_uuid, rule_uuid, compound: Form[str] = None):
return _post_package_rule(request, package_uuid, rule_uuid, compound=compound)
def _post_package_rule(request, package_uuid, rule_uuid, compound: Form[str]):
try:
p = PackageManager.get_package_by_id(request.user, package_uuid)
r = Rule.objects.get(package=p, uuid=rule_uuid)
if compound is not None:
if not compound.split():
return 400, {'message': 'Compound is empty'}
product_sets = r.apply(compound)
res = []
for p_set in product_sets:
for product in p_set:
res.append(product)
return HttpResponse('\n'.join(res), content_type="text/plain")
return r
except ValueError:
return 403, {
'message': f'Getting Rule with id {rule_uuid} failed due to insufficient rights!'}
############
# Reaction #
############
class SimpleReaction(SimpleObject):
identifier: str = 'reaction'
class ReactionWrapper(Schema):
reaction: List['SimpleReaction']
class ReactionCompoundStructure(Schema):
compoundName: str = Field(None, alias="name")
id: str = Field(None, alias="url")
smiles: str = Field(None, alias="smiles")
class ReactionSchema(Schema):
aliases: List[str] = Field([], alias="aliases")
description: str = Field(None, alias="description")
ecNumbers: List[Dict[str, str]] = Field([], alias="ec_numbers")
educts: List['ReactionCompoundStructure'] = Field([], alias="educts")
id: str = Field(None, alias="url")
identifier: str = 'reaction'
medlineRefs: List[str] = Field([], alias="medline_references")
multistep: bool = Field(None, alias="multi_step")
name: str = Field(None, alias="name")
pathways: List['SimplePathway'] = Field([], alias="related_pathways")
products: List['ReactionCompoundStructure'] = Field([], alias="products")
references: List[Dict[str, List[str]]] = Field([], alias="references")
reviewStatus: str = Field(None, alias="review_status")
scenarios: List['SimpleScenario'] = Field([], alias="scenarios")
smirks: str = Field("", alias="smirks")
@staticmethod
def resolve_smirks(obj: Reaction):
return obj.smirks()
@staticmethod
def resolve_ec_numbers(obj: Reaction):
# TODO fetch via scenario EnzymeAI
return []
@staticmethod
def resolve_references(obj: Reaction):
# TODO
return []
@staticmethod
def resolve_medline_references(obj: Reaction):
# TODO
return []
@staticmethod
def resolve_review_status(obj: Rule):
return 'reviewed' if obj.package.reviewed else 'unreviewed'
@router.get("/reaction", response={200: ReactionWrapper, 403: Error})
def get_reactions(request):
qs = Reaction.objects.none()
for p in PackageManager.get_reviewed_packages():
qs |= Reaction.objects.filter(package=p)
return {'reaction': qs}
@router.get("/package/{uuid:package_uuid}/reaction", response={200: ReactionWrapper, 403: Error})
def get_package_reactions(request, package_uuid):
try:
p = PackageManager.get_package_by_id(request.user, package_uuid)
return {'reaction': Reaction.objects.filter(package=p).prefetch_related('package')}
except ValueError:
return 403, {
'message': f'Getting Reactions for Package with id {package_uuid} failed due to insufficient rights!'}
@router.get("/package/{uuid:package_uuid}/reaction/{uuid:reaction_uuid}", response={200: ReactionSchema, 403: Error})
def get_package_reaction(request, package_uuid, reaction_uuid):
try:
p = PackageManager.get_package_by_id(request.user, package_uuid)
return Reaction.objects.get(package=p, uuid=reaction_uuid)
except ValueError:
return 403, {
'message': f'Getting Reaction with id {reaction_uuid} failed due to insufficient rights!'}
############
# Scenario #
############
class SimpleScenario(SimpleObject):
identifier: str = 'scenario'
class ScenarioWrapper(Schema):
scenario: List['SimpleScenario']
class ScenarioSchema(Schema):
aliases: List[str] = Field([], alias="aliases")
collection: Dict['str', List[Dict[str, Any]]] = Field([], alias="collection")
collectionID: Optional[str] = None
description: str = Field(None, alias="description")
id: str = Field(None, alias="url")
identifier: str = 'scenario'
linkedTo: List[Dict[str, str]] = Field({}, alias="linked_to")
name: str = Field(None, alias="name")
pathways: List['SimplePathway'] = Field([], alias="related_pathways")
relatedScenarios: List[Dict[str, str]] = Field([], alias="related_scenarios")
reviewStatus: str = Field(None, alias="review_status")
scenarios: List['SimpleScenario'] = Field([], alias="scenarios")
type: str = Field(None, alias="scenario_type")
@staticmethod
def resolve_collection(obj: Scenario):
return obj.additional_information
@staticmethod
def resolve_review_status(obj: Rule):
return 'reviewed' if obj.package.reviewed else 'unreviewed'
@router.get("/scenario", response={200: ScenarioWrapper, 403: Error})
def get_scenarios(request):
qs = Scenario.objects.none()
for p in PackageManager.get_reviewed_packages():
qs |= Scenario.objects.filter(package=p)
return {'scenario': qs}
@router.get("/package/{uuid:package_uuid}/scenario", response={200: ScenarioWrapper, 403: Error})
def get_package_scenarios(request, package_uuid):
try:
p = PackageManager.get_package_by_id(request.user, package_uuid)
return {'scenario': Scenario.objects.filter(package=p).prefetch_related('package')}
except ValueError:
return 403, {
'message': f'Getting Scenarios for Package with id {package_uuid} failed due to insufficient rights!'}
@router.get("/package/{uuid:package_uuid}/scenario/{uuid:scenario_uuid}", response={200: ScenarioSchema, 403: Error})
def get_package_scenario(request, package_uuid, scenario_uuid):
try:
p = PackageManager.get_package_by_id(request.user, package_uuid)
return Scenario.objects.get(package=p, uuid=scenario_uuid)
except ValueError:
return 403, {
'message': f'Getting Scenario with id {scenario_uuid} failed due to insufficient rights!'}
###########
# Pathway #
###########
class SimplePathway(SimpleObject):
identifier: str = 'pathway'
class PathwayWrapper(Schema):
pathway: List['SimplePathway']
@router.get("/pathway", response={200: PathwayWrapper, 403: Error})
def get_pathways(request):
qs = Pathway.objects.none()
for p in PackageManager.get_reviewed_packages():
qs |= Pathway.objects.filter(package=p)
return {'pathway': qs}
@router.get("/package/{uuid:package_uuid}/pathway", response={200: PathwayWrapper, 403: Error})
def get_package_pathways(request, package_uuid):
try:
p = PackageManager.get_package_by_id(request.user, package_uuid)
return {'pathway': Pathway.objects.filter(package=p).prefetch_related('package')}
except ValueError:
return 403, {
'message': f'Getting Pathways for Package with id {package_uuid} failed due to insufficient rights!'}
# @router.get("/package/{uuid:package_uuid}/pathway/{uuid:pathway_uuid}", response={200: Pathway, 403: Error})
# def get_package_pathway(request, package_uuid, pathway_uuid):
# try:
# p = PackageManager.get_package_by_id(request.user, package_uuid)
# return Pathway.objects.get(package=p, uuid=pathway_uuid)
# except ValueError:
# return 403, {
# 'message': f'Getting Pathway with id {pathway_uuid} failed due to insufficient rights!'}

1392
epdb/logic.py Normal file

File diff suppressed because it is too large Load Diff

View File

View File

View File

@ -0,0 +1,205 @@
import json
from django.conf import settings as s
from django.core.management.base import BaseCommand
from django.db import transaction
from epdb.logic import UserManager, GroupManager, PackageManager, SettingManager
from epdb.models import UserSettingPermission, MLRelativeReasoning, EnviFormer, Permission, User, ExternalDatabase
class Command(BaseCommand):
def create_users(self):
if not User.objects.filter(email='anon@lorsba.ch').exists():
anon = UserManager.create_user("anonymous", "anon@lorsba.ch", "SuperSafe", is_active=True,
add_to_group=False, set_setting=False)
else:
anon = User.objects.get(email='anon@lorsba.ch')
if not User.objects.filter(email='admin@lorsba.ch').exists():
admin = UserManager.create_user("admin", "admin@lorsba.ch", "SuperSafe", is_active=True, add_to_group=False,
set_setting=False)
admin.is_staff = True
admin.is_superuser = True
admin.save()
else:
admin = User.objects.get(email='admin@lorsba.ch')
g = GroupManager.create_group(admin, 'enviPath Users', 'All enviPath Users')
g.public = True
g.save()
g.user_member.add(anon)
g.save()
anon.default_group = g
anon.save()
admin.default_group = g
admin.save()
if not User.objects.filter(email='jebus@lorsba.ch').exists():
jebus = UserManager.create_user("jebus", "jebus@lorsba.ch", "SuperSafe", is_active=True, add_to_group=False,
set_setting=False)
jebus.is_staff = True
jebus.is_superuser = True
jebus.save()
else:
jebus = User.objects.get(email='jebus@lorsba.ch')
g.user_member.add(jebus)
g.save()
jebus.default_group = g
jebus.save()
return anon, admin, g, jebus
def import_package(self, data, owner):
return PackageManager.import_package(data, owner, keep_ids=True, add_import_timestamp=False)
def create_default_setting(self, owner, packages):
s = SettingManager.create_setting(
owner,
name='Global Default Setting',
description='Global Default Setting containing BBD Rules and Max 30 Nodes and Max Depth of 8',
max_nodes=30,
max_depth=5,
rule_packages=packages,
model=None,
model_threshold=None
)
return s
def populate_common_external_databases(self):
"""
Helper function to populate common external databases.
This can be called from a Django management command.
"""
databases = [
{
'name': 'PubChem Compound',
'full_name': 'PubChem Compound Database',
'description': 'Chemical database of small organic molecules',
'base_url': 'https://pubchem.ncbi.nlm.nih.gov',
'url_pattern': 'https://pubchem.ncbi.nlm.nih.gov/compound/{id}'
},
{
'name': 'PubChem Substance',
'full_name': 'PubChem Substance Database',
'description': 'Database of chemical substances',
'base_url': 'https://pubchem.ncbi.nlm.nih.gov',
'url_pattern': 'https://pubchem.ncbi.nlm.nih.gov/substance/{id}'
},
{
'name': 'ChEBI',
'full_name': 'Chemical Entities of Biological Interest',
'description': 'Dictionary of molecular entities',
'base_url': 'https://www.ebi.ac.uk/chebi',
'url_pattern': 'https://www.ebi.ac.uk/chebi/searchId.do?chebiId=CHEBI:{id}'
},
{
'name': 'RHEA',
'full_name': 'RHEA Reaction Database',
'description': 'Comprehensive resource of biochemical reactions',
'base_url': 'https://www.rhea-db.org',
'url_pattern': 'https://www.rhea-db.org/rhea/{id}'
},
{
'name': 'CAS',
'full_name': 'Chemical Abstracts Service Registry',
'description': 'Registry of chemical substances',
'base_url': 'https://www.cas.org',
'url_pattern': None # CAS doesn't have a free public URL pattern
},
{
'name': 'KEGG Reaction',
'full_name': 'KEGG Reaction Database',
'description': 'Database of biochemical reactions',
'base_url': 'https://www.genome.jp',
'url_pattern': 'https://www.genome.jp/entry/reaction+{id}'
},
{
'name': 'MetaCyc',
'full_name': 'MetaCyc Metabolic Pathway Database',
'description': 'Database of metabolic pathways and enzymes',
'base_url': 'https://metacyc.org',
'url_pattern': None
},
{
'name': 'UniProt',
'full_name': 'MetaCyc Metabolic Pathway Database',
'description': 'UniProt is a freely accessible database of protein sequence and functional information',
'base_url': 'https://www.uniprot.org',
'url_pattern': 'https://www.uniprot.org/uniprotkb?query="{id}"'
}
]
for db_info in databases:
ExternalDatabase.objects.get_or_create(
name=db_info['name'],
defaults=db_info
)
@transaction.atomic
def handle(self, *args, **options):
# Create users
anon, admin, g, jebus = self.create_users()
# Import Packages
packages = [
'EAWAG-BBD.json',
'EAWAG-SOIL.json',
'EAWAG-SLUDGE.json',
'EAWAG-SEDIMENT.json',
]
mapping = {}
for p in packages:
print(f"Importing {p}...")
package_data = json.loads(open(s.BASE_DIR / 'fixtures' / 'packages' / '2025-07-18' / p).read())
imported_package = self.import_package(package_data, admin)
mapping[p.replace('.json', '')] = imported_package
setting = self.create_default_setting(admin, [mapping['EAWAG-BBD']])
setting.public = True
setting.save()
setting.make_global_default()
for u in [anon, jebus]:
u.default_setting = setting
u.save()
usp = UserSettingPermission()
usp.user = u
usp.setting = setting
usp.permission = Permission.READ[0]
usp.save()
# Create Model Package
pack = PackageManager.create_package(admin, "Public Prediction Models",
"Package to make Prediction Models publicly available")
pack.reviewed = True
pack.save()
# Create RR
ml_model = MLRelativeReasoning.create(
package=pack,
rule_packages=[mapping['EAWAG-BBD']],
data_packages=[mapping['EAWAG-BBD']],
eval_packages=[],
threshold=0.5,
name='ECC - BBD - T0.5',
description='ML Relative Reasoning',
)
ml_model.build_dataset()
ml_model.build_model()
# ml_model.evaluate_model()
# If available create EnviFormerModel
if s.ENVIFORMER_PRESENT:
enviFormer_model = EnviFormer.create(pack, 'EnviFormer - T0.5', 'EnviFormer Model with Threshold 0.5', 0.5)

View File

@ -0,0 +1,27 @@
from django.core.management.base import BaseCommand
from epdb.logic import PackageManager
from epdb.models import *
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument(
'--data',
type=str,
help='Path of the Package to import.',
required=True,
)
parser.add_argument(
'--owner',
type=str,
help='Username of the desired Owner.',
required=True,
)
@transaction.atomic
def handle(self, *args, **options):
owner = User.objects.get(username=options['owner'])
package_data = json.load(open(options['data']))
PackageManager.import_package(package_data, owner)

View File

@ -0,0 +1,50 @@
from django.apps import apps
from django.core.management.base import BaseCommand
from django.db.models import F, Value
from django.db.models.functions import Replace
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument(
'--old',
type=str,
help='Old Host, most likely https://envipath.org/',
required=True,
)
parser.add_argument(
'--new',
type=str,
help='New Host, most likely http://localhost:8000/',
required=True,
)
def handle(self, *args, **options):
MODELS = [
'User',
'Group',
'Package',
'Compound',
'CompoundStructure',
'Pathway',
'Edge',
'Node',
'Reaction',
'SimpleAmbitRule',
'SimpleRDKitRule',
'ParallelRule',
'SequentialRule',
'Scenario',
'Setting',
'MLRelativeReasoning',
'EnviFormer',
'ApplicabilityDomain',
]
for model in MODELS:
obj_cls = apps.get_model("epdb", model)
print(f"Localizing urls for {model}")
obj_cls.objects.update(
url=Replace(F('url'), Value(options['old']), Value(options['new']))
)

View File

View File

@ -0,0 +1,21 @@
from django.conf import settings
from django.shortcuts import redirect
from django.urls import reverse
class LoginRequiredMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.exempt_urls = [
reverse('login'),
reverse('logout'),
reverse('admin:login'),
reverse('admin:index'),
'/api/legacy/'
] + getattr(settings, 'LOGIN_EXEMPT_URLS', [])
def __call__(self, request):
if not request.user.is_authenticated:
path = request.path_info
if not any(path.startswith(url) for url in self.exempt_urls):
return redirect(settings.LOGIN_URL)
return self.get_response(request)

View File

@ -0,0 +1,594 @@
# Generated by Django 5.2.1 on 2025-07-22 20:58
import datetime
import django.contrib.auth.models
import django.contrib.auth.validators
import django.contrib.postgres.fields
import django.db.models.deletion
import django.utils.timezone
import model_utils.fields
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
('contenttypes', '0002_remove_content_type_name'),
]
operations = [
migrations.CreateModel(
name='Compound',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
('name', models.TextField(default='no name', verbose_name='Name')),
('description', models.TextField(default='no description', verbose_name='Descriptions')),
('kv', models.JSONField(blank=True, default=dict, null=True)),
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
],
),
migrations.CreateModel(
name='EPModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
('name', models.TextField(default='no name', verbose_name='Name')),
('description', models.TextField(default='no description', verbose_name='Descriptions')),
('kv', models.JSONField(blank=True, default=dict, null=True)),
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),
],
options={
'abstract': False,
'base_manager_name': 'objects',
},
),
migrations.CreateModel(
name='Permission',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('permission', models.CharField(choices=[('read', 'Read'), ('write', 'Write'), ('all', 'All')], max_length=32)),
],
),
migrations.CreateModel(
name='License',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('link', models.URLField(verbose_name='link')),
('image_link', models.URLField(verbose_name='Image link')),
],
),
migrations.CreateModel(
name='Rule',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
('name', models.TextField(default='no name', verbose_name='Name')),
('description', models.TextField(default='no description', verbose_name='Descriptions')),
('kv', models.JSONField(blank=True, default=dict, null=True)),
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
],
options={
'abstract': False,
'base_manager_name': 'objects',
},
),
migrations.CreateModel(
name='User',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('email', models.EmailField(max_length=254, unique=True)),
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
],
options={
'verbose_name': 'user',
'verbose_name_plural': 'users',
'abstract': False,
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name='APIToken',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('hashed_key', models.CharField(max_length=128, unique=True)),
('created', models.DateTimeField(auto_now_add=True)),
('expires_at', models.DateTimeField(blank=True, default=datetime.datetime(2025, 10, 20, 20, 58, 48, 351675, tzinfo=datetime.timezone.utc), null=True)),
('name', models.CharField(blank=True, help_text='Optional name for the token', max_length=100)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='CompoundStructure',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
('name', models.TextField(default='no name', verbose_name='Name')),
('description', models.TextField(default='no description', verbose_name='Descriptions')),
('kv', models.JSONField(blank=True, default=dict, null=True)),
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
('smiles', models.TextField(verbose_name='SMILES')),
('canonical_smiles', models.TextField(verbose_name='Canonical SMILES')),
('inchikey', models.TextField(max_length=27, verbose_name='InChIKey')),
('normalized_structure', models.BooleanField(default=False)),
('compound', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.compound')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='compound',
name='default_structure',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='compound_default_structure', to='epdb.compoundstructure', verbose_name='Default Structure'),
),
migrations.CreateModel(
name='Edge',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
('name', models.TextField(default='no name', verbose_name='Name')),
('description', models.TextField(default='no description', verbose_name='Descriptions')),
('kv', models.JSONField(blank=True, default=dict, null=True)),
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),
],
options={
'abstract': False,
'base_manager_name': 'objects',
},
),
migrations.CreateModel(
name='EnviFormer',
fields=[
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
('threshold', models.FloatField(default=0.5)),
],
options={
'abstract': False,
'base_manager_name': 'objects',
},
bases=('epdb.epmodel',),
),
migrations.CreateModel(
name='PluginModel',
fields=[
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
],
options={
'abstract': False,
'base_manager_name': 'objects',
},
bases=('epdb.epmodel',),
),
migrations.CreateModel(
name='RuleBaseRelativeReasoning',
fields=[
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
],
options={
'abstract': False,
'base_manager_name': 'objects',
},
bases=('epdb.epmodel',),
),
migrations.CreateModel(
name='Group',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
('name', models.TextField(verbose_name='Group name')),
('public', models.BooleanField(default=False, verbose_name='Public Group')),
('description', models.TextField(default='no description', verbose_name='Descriptions')),
('group_member', models.ManyToManyField(related_name='groups_in_group', to='epdb.group', verbose_name='Group member')),
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Group Owner')),
('user_member', models.ManyToManyField(related_name='users_in_group', to=settings.AUTH_USER_MODEL, verbose_name='User members')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='user',
name='default_group',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='default_group', to='epdb.group', verbose_name='Default Group'),
),
migrations.CreateModel(
name='Node',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
('name', models.TextField(default='no name', verbose_name='Name')),
('description', models.TextField(default='no description', verbose_name='Descriptions')),
('kv', models.JSONField(blank=True, default=dict, null=True)),
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
('depth', models.IntegerField(verbose_name='Node depth')),
('default_node_label', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='default_node_structure', to='epdb.compoundstructure', verbose_name='Default Node Label')),
('node_labels', models.ManyToManyField(related_name='node_structures', to='epdb.compoundstructure', verbose_name='All Node Labels')),
('out_edges', models.ManyToManyField(to='epdb.edge', verbose_name='Outgoing Edges')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='edge',
name='end_nodes',
field=models.ManyToManyField(related_name='edge_products', to='epdb.node', verbose_name='End Nodes'),
),
migrations.AddField(
model_name='edge',
name='start_nodes',
field=models.ManyToManyField(related_name='edge_educts', to='epdb.node', verbose_name='Start Nodes'),
),
migrations.CreateModel(
name='Package',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
('name', models.TextField(default='no name', verbose_name='Name')),
('description', models.TextField(default='no description', verbose_name='Descriptions')),
('kv', models.JSONField(blank=True, default=dict, null=True)),
('reviewed', models.BooleanField(default=False, verbose_name='Reviewstatus')),
('license', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.license', verbose_name='License')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='epmodel',
name='package',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package'),
),
migrations.AddField(
model_name='compound',
name='package',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package'),
),
migrations.AddField(
model_name='user',
name='default_package',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.package', verbose_name='Default Package'),
),
migrations.CreateModel(
name='SequentialRule',
fields=[
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')),
],
options={
'abstract': False,
'base_manager_name': 'objects',
},
bases=('epdb.rule',),
),
migrations.CreateModel(
name='SimpleRule',
fields=[
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')),
],
options={
'abstract': False,
'base_manager_name': 'objects',
},
bases=('epdb.rule',),
),
migrations.AddField(
model_name='rule',
name='package',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package'),
),
migrations.AddField(
model_name='rule',
name='polymorphic_ctype',
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'),
),
migrations.CreateModel(
name='Pathway',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
('name', models.TextField(default='no name', verbose_name='Name')),
('description', models.TextField(default='no description', verbose_name='Descriptions')),
('kv', models.JSONField(blank=True, default=dict, null=True)),
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='node',
name='pathway',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.pathway', verbose_name='belongs to'),
),
migrations.AddField(
model_name='edge',
name='pathway',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.pathway', verbose_name='belongs to'),
),
migrations.CreateModel(
name='Reaction',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
('name', models.TextField(default='no name', verbose_name='Name')),
('description', models.TextField(default='no description', verbose_name='Descriptions')),
('kv', models.JSONField(blank=True, default=dict, null=True)),
('aliases', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None, verbose_name='Aliases')),
('multi_step', models.BooleanField(verbose_name='Multistep Reaction')),
('medline_references', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), null=True, size=None, verbose_name='Medline References')),
('educts', models.ManyToManyField(related_name='reaction_educts', to='epdb.compoundstructure', verbose_name='Educts')),
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package')),
('products', models.ManyToManyField(related_name='reaction_products', to='epdb.compoundstructure', verbose_name='Products')),
('rules', models.ManyToManyField(related_name='reaction_rule', to='epdb.rule', verbose_name='Rule')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='edge',
name='edge_label',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.reaction', verbose_name='Edge label'),
),
migrations.CreateModel(
name='Scenario',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
('name', models.TextField(default='no name', verbose_name='Name')),
('description', models.TextField(default='no description', verbose_name='Descriptions')),
('kv', models.JSONField(blank=True, default=dict, null=True)),
('scenario_date', models.CharField(default='No date', max_length=256)),
('scenario_type', models.CharField(default='Not specified', max_length=256)),
('additional_information', models.JSONField(verbose_name='Additional Information')),
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Package')),
('parent', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='epdb.scenario')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='rule',
name='scenarios',
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
),
migrations.AddField(
model_name='reaction',
name='scenarios',
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
),
migrations.AddField(
model_name='pathway',
name='scenarios',
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
),
migrations.AddField(
model_name='node',
name='scenarios',
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
),
migrations.AddField(
model_name='edge',
name='scenarios',
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
),
migrations.AddField(
model_name='compoundstructure',
name='scenarios',
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
),
migrations.AddField(
model_name='compound',
name='scenarios',
field=models.ManyToManyField(to='epdb.scenario', verbose_name='Attached Scenarios'),
),
migrations.CreateModel(
name='Setting',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
('name', models.TextField(default='no name', verbose_name='Name')),
('description', models.TextField(default='no description', verbose_name='Descriptions')),
('kv', models.JSONField(blank=True, default=dict, null=True)),
('public', models.BooleanField(default=False)),
('global_default', models.BooleanField(default=False)),
('max_depth', models.IntegerField(default=5, verbose_name='Setting Max Depth')),
('max_nodes', models.IntegerField(default=30, verbose_name='Setting Max Number of Nodes')),
('model_threshold', models.FloatField(blank=True, default=0.25, null=True, verbose_name='Setting Model Threshold')),
('model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.epmodel', verbose_name='Setting EPModel')),
('rule_packages', models.ManyToManyField(blank=True, related_name='setting_rule_packages', to='epdb.package', verbose_name='Setting Rule Packages')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='pathway',
name='setting',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='epdb.setting', verbose_name='Setting'),
),
migrations.AddField(
model_name='user',
name='default_setting',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.setting', verbose_name='The users default settings'),
),
migrations.CreateModel(
name='MLRelativeReasoning',
fields=[
('epmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.epmodel')),
('threshold', models.FloatField(default=0.5)),
('model_status', models.CharField(choices=[('INITIAL', 'Initial'), ('INITIALIZING', 'Model is initializing.'), ('BUILDING', 'Model is building.'), ('BUILT_NOT_EVALUATED', 'Model is built and can be used for predictions, Model is not evaluated yet.'), ('EVALUATING', 'Model is evaluating'), ('FINISHED', 'Model has finished building and evaluation.'), ('ERROR', 'Model has failed.')], default='INITIAL')),
('eval_results', models.JSONField(blank=True, default=dict, null=True)),
('data_packages', models.ManyToManyField(related_name='data_packages', to='epdb.package', verbose_name='Data Packages')),
('eval_packages', models.ManyToManyField(related_name='eval_packages', to='epdb.package', verbose_name='Evaluation Packages')),
('rule_packages', models.ManyToManyField(related_name='rule_packages', to='epdb.package', verbose_name='Rule Packages')),
],
options={
'abstract': False,
'base_manager_name': 'objects',
},
bases=('epdb.epmodel',),
),
migrations.CreateModel(
name='ApplicabilityDomain',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID of this object')),
('name', models.TextField(default='no name', verbose_name='Name')),
('description', models.TextField(default='no description', verbose_name='Descriptions')),
('kv', models.JSONField(blank=True, default=dict, null=True)),
('num_neighbours', models.FloatField(default=5)),
('reliability_threshold', models.FloatField(default=0.5)),
('local_compatibilty_threshold', models.FloatField(default=0.5)),
('model', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.mlrelativereasoning')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='SimpleAmbitRule',
fields=[
('simplerule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.simplerule')),
('smirks', models.TextField(verbose_name='SMIRKS')),
('reactant_filter_smarts', models.TextField(null=True, verbose_name='Reactant Filter SMARTS')),
('product_filter_smarts', models.TextField(null=True, verbose_name='Product Filter SMARTS')),
],
options={
'abstract': False,
'base_manager_name': 'objects',
},
bases=('epdb.simplerule',),
),
migrations.CreateModel(
name='SimpleRDKitRule',
fields=[
('simplerule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.simplerule')),
('reaction_smarts', models.TextField(verbose_name='SMIRKS')),
],
options={
'abstract': False,
'base_manager_name': 'objects',
},
bases=('epdb.simplerule',),
),
migrations.CreateModel(
name='SequentialRuleOrdering',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('order_index', models.IntegerField()),
('sequential_rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.sequentialrule')),
('simple_rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.simplerule')),
],
),
migrations.AddField(
model_name='sequentialrule',
name='simple_rules',
field=models.ManyToManyField(through='epdb.SequentialRuleOrdering', to='epdb.simplerule', verbose_name='Simple rules'),
),
migrations.CreateModel(
name='ParallelRule',
fields=[
('rule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='epdb.rule')),
('simple_rules', models.ManyToManyField(to='epdb.simplerule', verbose_name='Simple rules')),
],
options={
'abstract': False,
'base_manager_name': 'objects',
},
bases=('epdb.rule',),
),
migrations.AlterUniqueTogether(
name='compound',
unique_together={('uuid', 'package')},
),
migrations.CreateModel(
name='GroupPackagePermission',
fields=[
('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')),
('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')),
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.group', verbose_name='Permission to')),
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Permission on')),
],
options={
'unique_together': {('package', 'group')},
},
bases=('epdb.permission',),
),
migrations.CreateModel(
name='UserPackagePermission',
fields=[
('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')),
('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')),
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.package', verbose_name='Permission on')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Permission to')),
],
options={
'unique_together': {('package', 'user')},
},
bases=('epdb.permission',),
),
migrations.CreateModel(
name='UserSettingPermission',
fields=[
('permission_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='epdb.permission')),
('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='UUID of this object')),
('setting', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.setting', verbose_name='Permission on')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Permission to')),
],
options={
'unique_together': {('setting', 'user')},
},
bases=('epdb.permission',),
),
]

View File

@ -0,0 +1,128 @@
# Generated by Django 5.2.1 on 2025-08-25 18:07
import django.db.models.deletion
import django.utils.timezone
import model_utils.fields
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('epdb', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='ExternalDatabase',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
('name', models.CharField(max_length=100, unique=True, verbose_name='Database Name')),
('full_name', models.CharField(blank=True, max_length=255, verbose_name='Full Database Name')),
('description', models.TextField(blank=True, verbose_name='Description')),
('base_url', models.URLField(blank=True, null=True, verbose_name='Base URL')),
('url_pattern', models.CharField(blank=True, help_text="URL pattern with {id} placeholder, e.g., 'https://pubchem.ncbi.nlm.nih.gov/compound/{id}'", max_length=500, verbose_name='URL Pattern')),
('is_active', models.BooleanField(default=True, verbose_name='Is Active')),
],
options={
'verbose_name': 'External Database',
'verbose_name_plural': 'External Databases',
'db_table': 'epdb_external_database',
'ordering': ['name'],
},
),
migrations.AlterModelOptions(
name='apitoken',
options={'ordering': ['-created'], 'verbose_name': 'API Token', 'verbose_name_plural': 'API Tokens'},
),
migrations.AlterModelOptions(
name='edge',
options={},
),
migrations.RemoveField(
model_name='edge',
name='polymorphic_ctype',
),
migrations.AddField(
model_name='apitoken',
name='is_active',
field=models.BooleanField(default=True, help_text='Whether this token is active'),
),
migrations.AddField(
model_name='apitoken',
name='modified',
field=model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified'),
),
migrations.AddField(
model_name='applicabilitydomain',
name='functional_groups',
field=models.JSONField(blank=True, default=dict, null=True),
),
migrations.AddField(
model_name='mlrelativereasoning',
name='app_domain',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='epdb.applicabilitydomain'),
),
migrations.AlterField(
model_name='apitoken',
name='created',
field=model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created'),
),
migrations.AlterField(
model_name='apitoken',
name='expires_at',
field=models.DateTimeField(blank=True, help_text='Token expiration time (null for no expiration)', null=True),
),
migrations.AlterField(
model_name='apitoken',
name='hashed_key',
field=models.CharField(help_text='SHA-256 hash of the token key', max_length=128, unique=True),
),
migrations.AlterField(
model_name='apitoken',
name='name',
field=models.CharField(help_text='Descriptive name for this token', max_length=100),
),
migrations.AlterField(
model_name='apitoken',
name='user',
field=models.ForeignKey(help_text='User who owns this token', on_delete=django.db.models.deletion.CASCADE, related_name='api_tokens', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='applicabilitydomain',
name='num_neighbours',
field=models.IntegerField(default=5),
),
migrations.AlterModelTable(
name='apitoken',
table='epdb_api_token',
),
migrations.CreateModel(
name='ExternalIdentifier',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
('object_id', models.IntegerField()),
('identifier_value', models.CharField(max_length=255, verbose_name='Identifier Value')),
('url', models.URLField(blank=True, null=True, verbose_name='Direct URL')),
('is_primary', models.BooleanField(default=False, help_text='Mark this as the primary identifier for this database', verbose_name='Is Primary')),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
('database', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='epdb.externaldatabase', verbose_name='External Database')),
],
options={
'verbose_name': 'External Identifier',
'verbose_name_plural': 'External Identifiers',
'db_table': 'epdb_external_identifier',
'indexes': [models.Index(fields=['content_type', 'object_id'], name='epdb_extern_content_b76813_idx'), models.Index(fields=['database', 'identifier_value'], name='epdb_extern_databas_486422_idx')],
'unique_together': {('content_type', 'object_id', 'database', 'identifier_value')},
},
),
]

View File

@ -0,0 +1,228 @@
# Generated by Django 5.2.1 on 2025-08-26 17:05
from django.db import migrations, models
def populate_url(apps, schema_editor):
MODELS = [
'User',
'Group',
'Package',
'Compound',
'CompoundStructure',
'Pathway',
'Edge',
'Node',
'Reaction',
'SimpleAmbitRule',
'SimpleRDKitRule',
'ParallelRule',
'SequentialRule',
'Scenario',
'Setting',
'MLRelativeReasoning',
'EnviFormer',
'ApplicabilityDomain',
]
for model in MODELS:
obj_cls = apps.get_model("epdb", model)
for obj in obj_cls.objects.all():
obj.url = assemble_url(obj)
if obj.url is None:
raise ValueError(f"Could not assemble url for {obj}")
obj.save()
def assemble_url(obj):
from django.conf import settings as s
match obj.__class__.__name__:
case 'User':
return '{}/user/{}'.format(s.SERVER_URL, obj.uuid)
case 'Group':
return '{}/group/{}'.format(s.SERVER_URL, obj.uuid)
case 'Package':
return '{}/package/{}'.format(s.SERVER_URL, obj.uuid)
case 'Compound':
return '{}/compound/{}'.format(obj.package.url, obj.uuid)
case 'CompoundStructure':
return '{}/structure/{}'.format(obj.compound.url, obj.uuid)
case 'SimpleAmbitRule':
return '{}/simple-ambit-rule/{}'.format(obj.package.url, obj.uuid)
case 'SimpleRDKitRule':
return '{}/simple-rdkit-rule/{}'.format(obj.package.url, obj.uuid)
case 'ParallelRule':
return '{}/parallel-rule/{}'.format(obj.package.url, obj.uuid)
case 'SequentialRule':
return '{}/sequential-rule/{}'.format(obj.compound.url, obj.uuid)
case 'Reaction':
return '{}/reaction/{}'.format(obj.package.url, obj.uuid)
case 'Pathway':
return '{}/pathway/{}'.format(obj.package.url, obj.uuid)
case 'Node':
return '{}/node/{}'.format(obj.pathway.url, obj.uuid)
case 'Edge':
return '{}/edge/{}'.format(obj.pathway.url, obj.uuid)
case 'MLRelativeReasoning':
return '{}/model/{}'.format(obj.package.url, obj.uuid)
case 'EnviFormer':
return '{}/model/{}'.format(obj.package.url, obj.uuid)
case 'ApplicabilityDomain':
return '{}/model/{}/applicability-domain/{}'.format(obj.model.package.url, obj.model.uuid, obj.uuid)
case 'Scenario':
return '{}/scenario/{}'.format(obj.package.url, obj.uuid)
case 'Setting':
return '{}/setting/{}'.format(s.SERVER_URL, obj.uuid)
case _:
raise ValueError(f"Unknown model {obj.__class__.__name__}")
class Migration(migrations.Migration):
dependencies = [
('epdb', '0002_externaldatabase_alter_apitoken_options_and_more'),
]
operations = [
migrations.AddField(
model_name='applicabilitydomain',
name='url',
field=models.TextField(null=True, unique=False, verbose_name='URL'),
),
migrations.AddField(
model_name='compound',
name='url',
field=models.TextField(null=True, unique=False, verbose_name='URL'),
),
migrations.AddField(
model_name='compoundstructure',
name='url',
field=models.TextField(null=True, unique=False, verbose_name='URL'),
),
migrations.AddField(
model_name='edge',
name='url',
field=models.TextField(null=True, unique=False, verbose_name='URL'),
),
migrations.AddField(
model_name='epmodel',
name='url',
field=models.TextField(null=True, unique=False, verbose_name='URL'),
),
migrations.AddField(
model_name='group',
name='url',
field=models.TextField(null=True, unique=False, verbose_name='URL'),
),
migrations.AddField(
model_name='node',
name='url',
field=models.TextField(null=True, unique=False, verbose_name='URL'),
),
migrations.AddField(
model_name='package',
name='url',
field=models.TextField(null=True, unique=False, verbose_name='URL'),
),
migrations.AddField(
model_name='pathway',
name='url',
field=models.TextField(null=True, unique=False, verbose_name='URL'),
),
migrations.AddField(
model_name='reaction',
name='url',
field=models.TextField(null=True, unique=False, verbose_name='URL'),
),
migrations.AddField(
model_name='rule',
name='url',
field=models.TextField(null=True, unique=False, verbose_name='URL'),
),
migrations.AddField(
model_name='scenario',
name='url',
field=models.TextField(null=True, unique=False, verbose_name='URL'),
),
migrations.AddField(
model_name='setting',
name='url',
field=models.TextField(null=True, unique=False, verbose_name='URL'),
),
migrations.AddField(
model_name='user',
name='url',
field=models.TextField(null=True, unique=False, verbose_name='URL'),
),
migrations.RunPython(populate_url, reverse_code=migrations.RunPython.noop),
migrations.AlterField(
model_name='applicabilitydomain',
name='url',
field=models.TextField(null=True, unique=True, verbose_name='URL'),
),
migrations.AlterField(
model_name='compound',
name='url',
field=models.TextField(null=True, unique=True, verbose_name='URL'),
),
migrations.AlterField(
model_name='compoundstructure',
name='url',
field=models.TextField(null=True, unique=True, verbose_name='URL'),
),
migrations.AlterField(
model_name='edge',
name='url',
field=models.TextField(null=True, unique=True, verbose_name='URL'),
),
migrations.AlterField(
model_name='epmodel',
name='url',
field=models.TextField(null=True, unique=True, verbose_name='URL'),
),
migrations.AlterField(
model_name='group',
name='url',
field=models.TextField(null=True, unique=True, verbose_name='URL'),
),
migrations.AlterField(
model_name='node',
name='url',
field=models.TextField(null=True, unique=True, verbose_name='URL'),
),
migrations.AlterField(
model_name='package',
name='url',
field=models.TextField(null=True, unique=True, verbose_name='URL'),
),
migrations.AlterField(
model_name='pathway',
name='url',
field=models.TextField(null=True, unique=True, verbose_name='URL'),
),
migrations.AlterField(
model_name='reaction',
name='url',
field=models.TextField(null=True, unique=True, verbose_name='URL'),
),
migrations.AlterField(
model_name='rule',
name='url',
field=models.TextField(null=True, unique=True, verbose_name='URL'),
),
migrations.AlterField(
model_name='scenario',
name='url',
field=models.TextField(null=True, unique=True, verbose_name='URL'),
),
migrations.AlterField(
model_name='setting',
name='url',
field=models.TextField(null=True, unique=True, verbose_name='URL'),
),
migrations.AlterField(
model_name='user',
name='url',
field=models.TextField(null=True, unique=True, verbose_name='URL'),
),
]

View File

2621
epdb/models.py Normal file

File diff suppressed because it is too large Load Diff

20
epdb/signals.py Normal file
View File

@ -0,0 +1,20 @@
from django.db import transaction
from django.db.models.signals import pre_delete
from django.dispatch import receiver
from epdb.models import Node, Edge
@receiver(pre_delete, sender=Node)
@transaction.atomic
def delete_orphan_edges(sender, instance, **kwargs):
# check if the node that is about to be deleted is the only start node
for edge in Edge.objects.filter(start_nodes=instance):
if edge.start_nodes.count() == 1:
edge.delete()
# same for end_nodes
for edge in Edge.objects.filter(end_nodes=instance):
# check if the node that is about to be deleted is the only start node
if edge.end_nodes.count() == 1:
edge.delete()

81
epdb/tasks.py Normal file
View File

@ -0,0 +1,81 @@
import logging
from typing import Optional
from celery.signals import worker_process_init
from celery import shared_task
from epdb.models import Pathway, Node, Edge, EPModel, Setting
from epdb.logic import SPathway
from utilities.chem import FormatConverter
logger = logging.getLogger(__name__)
@shared_task(queue='background')
def mul(a, b):
return a * b
@shared_task(queue='predict')
def predict_simple(model_pk: int, smiles: str):
mod = EPModel.objects.get(id=model_pk)
res = mod.predict(smiles)
return res
@shared_task(queue='background')
def send_registration_mail(user_pk: int):
pass
@shared_task(queue='model')
def build_model(model_pk: int):
mod = EPModel.objects.get(id=model_pk)
mod.build_dataset()
mod.build_model()
@shared_task(queue='model')
def evaluate_model(model_pk: int):
mod = EPModel.objects.get(id=model_pk)
mod.evaluate_model()
@shared_task(queue='predict')
def predict(pw_pk: int, pred_setting_pk: int, limit: Optional[int] = None, node_pk: Optional[int] = None) -> Pathway:
pw = Pathway.objects.get(id=pw_pk)
setting = Setting.objects.get(id=pred_setting_pk)
pw.kv.update(**{'status': 'running'})
pw.save()
try:
# regular prediction
if limit is not None:
spw = SPathway(prediction_setting=setting, persist=pw)
level = 0
while not spw.done:
spw.predict_step(from_depth=level)
level += 1
# break in case we are in incremental mode
if limit != -1:
if level >= limit:
break
elif node_pk is not None:
n = Node.objects.get(id=node_pk, pathway=pw)
spw = SPathway.from_pathway(pw)
spw.predict_step(from_node=n)
else:
raise ValueError("Neither limit nor node_pk given!")
except Exception as e:
pw.kv.update({'status': 'failed'})
pw.save()
raise e
pw.kv.update(**{'status': 'completed'})
pw.save()

View File

View File

@ -0,0 +1,7 @@
from django import template
register = template.Library()
@register.filter
def classname(obj):
return obj.__class__.__name__

3
epdb/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

80
epdb/urls.py Normal file
View File

@ -0,0 +1,80 @@
from django.urls import path, re_path
from . import views as v
# from sesame.views import LoginView
UUID = '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}'
urlpatterns = [
# Sesame
# path("login/", v.EmailLoginView.as_view(), name="email_login"),
# path("login/auth/", LoginView.as_view(), name="login"),
# Home
re_path(r'^$', v.index, name='index'),
re_path(r'^login', v.login, name='login'),
re_path(r'^logout', v.logout, name='logout'),
# Top level urls
re_path(r'^package$', v.packages, name='packages'),
re_path(r'^compound$', v.compounds, name='compounds'),
re_path(r'^rule$', v.rules, name='rules'),
re_path(r'^reaction$', v.reactions, name='reactions'),
re_path(r'^pathway$', v.pathways, name='pathways'),
re_path(r'^scenario$', v.scenarios, name='scenarios'),
re_path(r'^model$', v.models, name='model'),
re_path(r'^user$', v.users, name='users'),
re_path(r'^group$', v.groups, name='groups'),
re_path(r'^search$', v.search, name='search'),
# User Detail
re_path(rf'^user/(?P<user_uuid>{UUID})', v.user, name='user'),
# Group Detail
re_path(rf'^group/(?P<group_uuid>{UUID})$', v.group, name='group_detail'),
# "in package" urls
re_path(rf'^package/(?P<package_uuid>{UUID})$', v.package, name='package_detail'),
# Compound
re_path(rf'^package/(?P<package_uuid>{UUID})/compound$', v.package_compounds, name='package compound list'),
re_path(rf'^package/(?P<package_uuid>{UUID})/compound/(?P<compound_uuid>{UUID})$', v.package_compound, name='package compound detail'),
# Compound Structure
re_path(rf'^package/(?P<package_uuid>{UUID})/compound/(?P<compound_uuid>{UUID})/structure$', v.package_compound_structures, name='package compound structure list'),
re_path(rf'^package/(?P<package_uuid>{UUID})/compound/(?P<compound_uuid>{UUID})/structure/(?P<structure_uuid>{UUID})$', v.package_compound_structure, name='package compound structure detail'),
# Rule
re_path(rf'^package/(?P<package_uuid>{UUID})/rule$', v.package_rules, name='package rule list'),
re_path(rf'^package/(?P<package_uuid>{UUID})/rule/(?P<rule_uuid>{UUID})$', v.package_rule, name='package rule detail'),
re_path(rf'^package/(?P<package_uuid>{UUID})/simple-ambit-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'),
# Reaction
re_path(rf'^package/(?P<package_uuid>{UUID})/reaction$', v.package_reactions, name='package reaction list'),
re_path(rf'^package/(?P<package_uuid>{UUID})/reaction/(?P<reaction_uuid>{UUID})$', v.package_reaction, name='package reaction detail'),
# # Pathway
re_path(rf'^package/(?P<package_uuid>{UUID})/pathway$', v.package_pathways, name='package pathway list'),
re_path(rf'^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})$', v.package_pathway, name='package pathway detail'),
# Pathway Nodes
re_path(rf'^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/node$', v.package_pathway_nodes, name='package pathway node list'),
re_path(rf'^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/node/(?P<node_uuid>{UUID})$', v.package_pathway_node, name='package pathway node detail'),
# Pathway Edges
re_path(rf'^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/edge$', v.package_pathway_edges, name='package pathway edge list'),
re_path(rf'^package/(?P<package_uuid>{UUID})/pathway/(?P<pathway_uuid>{UUID})/edge/(?P<edge_uuid>{UUID})$', v.package_pathway_edge, name='package pathway edge detail'),
# Scenario
re_path(rf'^package/(?P<package_uuid>{UUID})/scenario$', v.package_scenarios, name='package scenario list'),
re_path(rf'^package/(?P<package_uuid>{UUID})/scenario/(?P<scenario_uuid>{UUID})$', v.package_scenario, name='package scenario detail'),
# Model
re_path(rf'^package/(?P<package_uuid>{UUID})/model$', v.package_models, name='package model list'),
re_path(rf'^package/(?P<package_uuid>{UUID})/model/(?P<model_uuid>{UUID})$', v.package_model,name='package model detail'),
re_path(r'^setting$', v.settings, name='settings'),
re_path(rf'^setting/(?P<setting_uuid>{UUID})', v.setting, name='setting'),
re_path(r'^indigo/info$', v.indigo, name='indigo_info'),
re_path(r'^indigo/aromatize$', v.aromatize, name='indigo_aromatize'),
re_path(r'^indigo/dearomatize$', v.dearomatize, name='indigo_dearomatize'),
re_path(r'^indigo/layout$', v.layout, name='indigo_layout'),
re_path(r'^depict$', v.depict, name='depict')
]

2215
epdb/views.py Normal file

File diff suppressed because it is too large Load Diff

439315
fixtures/EAWAG-BBD.json Normal file

File diff suppressed because it is too large Load Diff

125432
fixtures/EAWAG-SLUDGE.json Normal file

File diff suppressed because it is too large Load Diff

1572257
fixtures/EAWAG-SOIL.json Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

BIN
fixtures/bootstrap.json.gz Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

22
manage.py Executable file
View File

@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'envipath.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

0
migration/__init__.py Normal file
View File

3
migration/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
migration/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class MigrationConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'migration'

View File

3
migration/models.py Normal file
View File

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

3
migration/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

15
migration/urls.py Normal file
View File

@ -0,0 +1,15 @@
from django.urls import re_path
from . import views as v
UUID = '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}'
urlpatterns = [
re_path(rf'^migration$', v.migration, name='migration'),
re_path(rf'^migration/package/(?P<package_uuid>{UUID})/rule/(?P<rule_uuid>{UUID})$', v.migration_detail, name='migration detail'),
re_path(rf'^migration/package/(?P<package_uuid>{UUID})/simple-rule/(?P<rule_uuid>{UUID})$',v.migration_detail, name='migration detail'),
re_path(rf'^migration/package/(?P<package_uuid>{UUID})/simple-ambit-rule/(?P<rule_uuid>{UUID})$', v.migration_detail, name='migration detail'),
re_path(rf'^migration/package/(?P<package_uuid>{UUID})/simple-rdkit-rule/(?P<rule_uuid>{UUID})$', v.migration_detail, name='migration detail'),
re_path(rf'^migration/package/(?P<package_uuid>{UUID})/parallel-rule/(?P<rule_uuid>{UUID})$', v.migration_detail, name='migration detail'),
re_path(rf'^migration/package/(?P<package_uuid>{UUID})/sequential-rule/(?P<rule_uuid>{UUID})$', v.migration_detail, name='migration detail'),
]

199
migration/views.py Normal file
View File

@ -0,0 +1,199 @@
import gzip
import json
import os.path
from django.conf import settings as s
from django.shortcuts import render
from epdb.logic import PackageManager
from epdb.models import Rule
from epdb.views import get_base_context, _anonymous_or_real
from utilities.chem import FormatConverter
def migration(request):
if request.method == 'GET':
context = get_base_context(request)
if os.path.exists(s.BASE_DIR / 'fixtures' / 'migration_status_per_rule.json') and request.GET.get(
"force") is None:
migration_status = json.load(open(s.BASE_DIR / 'fixtures' / 'migration_status_per_rule.json'))
else:
data = json.load(gzip.open(s.BASE_DIR / 'fixtures' / 'ambit_rules.json.gz', 'rb'))
results = []
success = 0
error = 0
total = 0
num_keys = len(data.keys())
for i, bt_rule_name in enumerate(data.keys()):
print(f"{i + 1}/{num_keys}")
bt_rule = data[bt_rule_name]
smirks = bt_rule['smirks']
all_prods = set()
res = True
for comp, ambit_prod in zip(bt_rule['compounds'], bt_rule['products']):
products = FormatConverter.apply(comp['smiles'], smirks)
all_rdkit_prods = []
for ps in products:
for p in ps:
all_rdkit_prods.append(p)
all_rdkit_prods = list(set(all_rdkit_prods))
ambit_smiles, ambit_errors = FormatConverter.sanitize_smiles(ambit_prod)
rdkit_smiles, rdkit_errors = FormatConverter.sanitize_smiles(all_rdkit_prods)
for x in ambit_smiles:
all_prods.add(x)
# TODO mode "intersection"
# partial_res = (len(set(ambit_smiles).intersection(set(rdkit_smiles))) > 0) or (len(ambit_smiles) == 0)
# FAILED (failures=37)
# TODO mode = "full ambit"
# partial_res = len(set(ambit_smiles).intersection(set(rdkit_smiles))) == len(ambit_smiles)
# FAILED (failures=46)
# TODO mode = "equality"
partial_res = set(ambit_smiles) == set(rdkit_smiles)
# FAILED (failures=69)
res &= partial_res
results.append(
{
'name': bt_rule_name,
'id': bt_rule['id'].split('/')[-1],
'url': bt_rule['id'],
'status': res,
'detail_url': s.SERVER_URL + '/migration/' + bt_rule['id'].replace('https://envipath.org/', '')
}
)
if res:
success += 1
else:
error += 1
total += 1
results = sorted(results, key=lambda x: (x['status'], x['name']))
migration_status = {
'results': results,
'success': success,
'error': error,
'total': total
}
json.dump(migration_status, open(s.BASE_DIR / 'fixtures' / 'migration_status_per_rule.json', 'w'))
for r in migration_status['results']:
r['detail_url'] = r['detail_url'].replace('http://localhost:8000', s.SERVER_URL)
context.update(**migration_status)
return render(request, 'migration.html', context)
def migration_detail(request, package_uuid, rule_uuid):
current_user = _anonymous_or_real(request)
if request.method == 'GET':
context = get_base_context(request)
p = PackageManager.get_package_by_id(current_user, package_uuid)
rule = Rule.objects.get(package=p, uuid=rule_uuid)
bt_rule_name = rule.name
data = json.load(gzip.open(s.BASE_DIR / 'fixtures' / 'ambit_rules.json.gz', 'rb'))
bt_rule = data[bt_rule_name]
smirks = bt_rule['smirks']
results = []
res = True
all_prods = set()
for comp, ambit_prod in zip(bt_rule['compounds'], bt_rule['products']):
# if comp['smiles'] != 'CC1=C(C(=C(C=N1)CO)C=O)O':
# continue
products = FormatConverter.apply(comp['smiles'], smirks)
all_rdkit_prods = []
for ps in products:
for p in ps:
all_rdkit_prods.append(p)
all_rdkit_prods = list(set(all_rdkit_prods))
ambit_smiles, ambit_errors = FormatConverter.sanitize_smiles(ambit_prod)
rdkit_smiles, rdkit_errors = FormatConverter.sanitize_smiles(all_rdkit_prods)
for x in ambit_smiles:
all_prods.add(x)
# TODO mode "intersection"
# partial_res = (len(set(ambit_smiles).intersection(set(rdkit_smiles))) > 0) or (len(ambit_smiles) == 0)
# FAILED (failures=37)
# TODO mode = "full ambit"
# partial_res = len(set(ambit_smiles).intersection(set(rdkit_smiles))) == len(ambit_smiles)
# FAILED (failures=46)
# TODO mode = "equality"
partial_res = set(ambit_smiles) == set(rdkit_smiles)
# FAILED (failures=69)
#
if len(ambit_smiles) or len(rdkit_smiles):
temp = {
'url': comp['id'],
'id': comp['id'].split('/')[-1],
'name': comp['name'],
'initial_smiles': comp['smiles'],
'ambit_smiles': sorted(list(ambit_smiles)),
'rdkit_smiles': sorted(list(rdkit_smiles)),
'status': set(ambit_smiles) == set(rdkit_smiles),
}
if set(ambit_smiles) != set(rdkit_smiles):
detail = f"""
BT: {bt_rule_name}
SMIRKS: {bt_rule['smirks']}
Compound: {comp['smiles']}
Compound URL: {comp['id']}
Num ambit: {len(set(ambit_smiles))}
Num rdkit: {len(set(rdkit_smiles))}
Num Intersection A: {len(set(ambit_smiles).intersection(set(rdkit_smiles)))}
Num Intersection B: {len(set(rdkit_smiles).intersection(set(ambit_smiles)))}
Difference A: {set(ambit_smiles).difference(set(rdkit_smiles))}
Difference B: {set(rdkit_smiles).difference(set(ambit_smiles))}
ambit products: {ambit_smiles}
rdkit products: {rdkit_smiles}
ambit_errors: {ambit_errors}
rdkit_errors: {rdkit_errors}
"""
temp['detail'] = '\n'.join([x.strip() for x in detail.split('\n')])
# print(detail.strip())
results.append(temp)
res &= partial_res
results = sorted(results, key=lambda x: x['status'])
context['results'] = results
context['res'] = res
context['bt_rule_name'] = bt_rule_name
return render(request, 'migration_detail.html', context)

32
pyproject.toml Normal file
View File

@ -0,0 +1,32 @@
[project]
name = "envipy"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"celery>=5.5.2",
"django>=5.2.1",
"django-extensions>=4.1",
"django-model-utils>=5.0.0",
"django-ninja>=1.4.1",
"django-polymorphic>=4.1.0",
"enviformer",
"envipy-additional-information",
"envipy-plugins",
"epam-indigo>=1.30.1",
"gunicorn>=23.0.0",
"psycopg2-binary>=2.9.10",
"python-dotenv>=1.1.0",
"rdkit>=2025.3.2",
"redis>=6.1.0",
"requests>=2.32.3",
"scikit-learn>=1.6.1",
"sentry-sdk[django]>=2.32.0",
"setuptools>=80.8.0",
]
[tool.uv.sources]
enviformer = { git = "ssh://git@git.envipath.com/enviPath/enviformer.git", rev = "v0.1.0" }
envipy-plugins = { git = "ssh://git@git.envipath.com/enviPath/enviPy-plugins.git", rev = "v0.1.0" }
envipy-additional-information = { git = "ssh://git@git.envipath.com/enviPath/enviPy-additional-information.git", rev = "v0.1.4"}

168
static/css/c3.css Normal file
View File

@ -0,0 +1,168 @@
/*-- Chart --*/
.c3 svg {
font: 10px sans-serif;
-webkit-tap-highlight-color: transparent; }
.c3 path, .c3 line {
fill: none;
stroke: #000;
}
.c3 text {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none; }
.c3-legend-item-tile,
.c3-xgrid-focus,
.c3-ygrid,
.c3-event-rect,
.c3-bars path {
shape-rendering: crispEdges; }
.c3-chart-arc path {
stroke: #fff; }
.c3-chart-arc text {
fill: #fff;
font-size: 13px; }
/*-- Axis --*/
/*-- Grid --*/
.c3-grid line {
stroke: #aaa; }
.c3-grid text {
fill: #aaa; }
.c3-xgrid, .c3-ygrid {
stroke-dasharray: 3 3; }
/*-- Text on Chart --*/
.c3-text.c3-empty {
fill: #808080;
font-size: 2em; }
/*-- Line --*/
.c3-line {
stroke-width: 1px; }
/*-- Point --*/
.c3-circle._expanded_ {
stroke-width: 1px;
stroke: white; }
.c3-selected-circle {
fill: white;
stroke-width: 2px; }
/*-- Bar --*/
.c3-bar {
stroke-width: 0; }
.c3-bar._expanded_ {
fill-opacity: 0.75; }
/*-- Focus --*/
.c3-target.c3-focused {
opacity: 1; }
.c3-target.c3-focused path.c3-line, .c3-target.c3-focused path.c3-step {
stroke-width: 2px; }
.c3-target.c3-defocused {
opacity: 0.3 !important; }
/*-- Region --*/
.c3-region {
fill: steelblue;
fill-opacity: .1; }
/*-- Brush --*/
.c3-brush .extent {
fill-opacity: .1; }
/*-- Select - Drag --*/
/*-- Legend --*/
.c3-legend-item {
font-size: 12px; }
.c3-legend-item-hidden {
opacity: 0.15; }
.c3-legend-background {
opacity: 0.75;
fill: white;
stroke: lightgray;
stroke-width: 1; }
/*-- Title --*/
.c3-title {
font: 14px sans-serif; }
/*-- Tooltip --*/
.c3-tooltip-container {
z-index: 10; }
.c3-tooltip {
border-collapse: collapse;
border-spacing: 0;
background-color: #fff;
empty-cells: show;
-webkit-box-shadow: 7px 7px 12px -9px #777777;
-moz-box-shadow: 7px 7px 12px -9px #777777;
box-shadow: 7px 7px 12px -9px #777777;
opacity: 0.9; }
.c3-tooltip tr {
border: 1px solid #CCC; }
.c3-tooltip th {
background-color: #aaa;
font-size: 14px;
padding: 2px 5px;
text-align: left;
color: #FFF; }
.c3-tooltip td {
font-size: 13px;
padding: 3px 6px;
background-color: #fff;
border-left: 1px dotted #999; }
.c3-tooltip td > span {
display: inline-block;
width: 10px;
height: 10px;
margin-right: 6px; }
.c3-tooltip td.value {
text-align: right; }
/*-- Area --*/
.c3-area {
stroke-width: 0;
opacity: 0.2; }
/*-- Arc --*/
.c3-chart-arcs-title {
dominant-baseline: middle;
font-size: 1.3em; }
.c3-chart-arcs .c3-chart-arcs-background {
fill: #e0e0e0;
stroke: none; }
.c3-chart-arcs .c3-chart-arcs-gauge-unit {
fill: #000;
font-size: 16px; }
.c3-chart-arcs .c3-chart-arcs-gauge-max {
fill: #777; }
.c3-chart-arcs .c3-chart-arcs-gauge-min {
fill: #777; }
.c3-chart-arc .c3-gauge-value {
fill: #000;
/* font-size: 28px !important;*/ }

126
static/css/epp.css Normal file
View File

@ -0,0 +1,126 @@
.mini-submenu{
display:none;
background-color: rgba(0, 0, 0, 0);
border: 1px solid rgba(0, 0, 0, 0.9);
border-radius: 4px;
padding: 9px;
/*position: relative;*/
width: 42px;
}
.mini-submenu:hover{
cursor: pointer;
}
.mini-submenu .icon-bar {
border-radius: 1px;
display: block;
height: 2px;
width: 22px;
margin-top: 3px;
}
.mini-submenu .icon-bar {
background-color: #000;
}
#slide-submenu{
background: rgba(0, 0, 0, 0.45);
display: inline-block;
padding: 0 8px;
border-radius: 4px;
cursor: pointer;
}
#eductsdiv {
float:left;
}
#agentsdiv {
float:left;
}
#productsdiv {
float:left;
}
#actions {
float:left;
}
.modal.fade .modal-dialog {
overflow-y: auto;
max-height: 800px;
}
.modal-dialog-multiple {
position: relative;
overflow: visible;
width: 600px;
margin: 30px auto;
max-height: 800px;
}
.modal-element{
padding-top: 10px;
margin-top: 5px;
margin-bottom: 5px;
border-top: 1px solid #e5e5e5;
}
.modal-dialog-pps{
position: relative;
display: table;
margin-left: 35%;
margin-right: 35%;
overflow-y: auto;
overflow-x: auto;
width: 30%;
}
.modal-dialog-pps-big{
position: relative;
display: table;
margin-left: 25%;
margin-right: 25%;
overflow-y: auto;
overflow-x: auto;
width: 50%;
}
@media (max-width: 1220px) {
.navbar-header-framework {
float: none;
}
.navbar-left-framework,.navbar-right-framework {
float: none !important;
}
.navbar-toggle-framework {
display: block;
}
.navbar-collapse-framework {
border-top: 1px solid transparent;
box-shadow: inset 0 1px 0 rgba(255,255,255,0.1);
}
.navbar-fixed-top-framework {
top: 0;
border-width: 0 0 1px;
}
.navbar-collapse-framework.collapse {
display: none!important;
}
.navbar-nav-framework {
float: none!important;
margin-top: 7.5px;
}
.navbar-nav-framework>li {
float: none;
}
.navbar-nav-framework>li>a {
padding-top: 10px;
padding-bottom: 10px;
}
.collapse-framework.in{
display:block !important;
}
}

BIN
static/images/ealogo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

BIN
static/images/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

225
static/images/logo-long.svg Normal file
View File

@ -0,0 +1,225 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="314.98749"
height="28.8125"
id="svg3004"
xml:space="preserve"><metadata
id="metadata3010"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs3008" /><g
transform="matrix(1.25,0,0,-1.25,0,28.8125)"
id="g3012"><g
transform="scale(0.1,0.1)"
id="g3014"><path
d="m 957.473,175.816 0,-4.296 -18.453,0 0,-48.614 -5.04,0 0,48.614 -18.378,0 0,4.296 41.871,0"
id="path3016"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 969.695,175.816 0,-22.968 31.425,0 0,22.968 5.04,0 0,-52.91 -5.04,0 0,25.637 -31.425,0 0,-25.637 -5.039,0 0,52.91 5.039,0"
id="path3018"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1055.58,175.816 0,-4.296 -31.49,0 0,-19.122 29.49,0 0,-4.293 -29.49,0 0,-20.898 31.87,0 0,-4.301 -36.91,0 0,52.91 36.53,0"
id="path3020"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1124.58,175.816 0,-4.296 -31.5,0 0,-19.122 29.49,0 0,-4.293 -29.49,0 0,-20.898 31.87,0 0,-4.301 -36.91,0 0,52.91 36.54,0"
id="path3022"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1139.76,175.816 30.83,-44.757 0.15,0 0,44.757 5.04,0 0,-52.91 -5.64,0 -30.82,44.754 -0.15,0 0,-44.754 -5.04,0 0,52.91 5.63,0"
id="path3024"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1188.15,175.816 17.19,-47.355 0.15,0 17.06,47.355 5.34,0 -19.65,-52.91 -5.86,0 -19.56,52.91 5.33,0"
id="path3026"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1235.15,122.906 5.043,0 0,52.9102 -5.043,0 0,-52.9102 z"
id="path3028"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1277.3,150.691 c 1.54,0 2.99,0.243 4.38,0.711 1.39,0.469 2.59,1.145 3.63,2.036 1.04,0.886 1.86,1.968 2.48,3.222 0.63,1.258 0.92,2.703 0.92,4.336 0,3.262 -0.94,5.824 -2.81,7.703 -1.88,1.875 -4.74,2.821 -8.6,2.821 l -18.82,0 0,-20.829 18.82,0 z m 0.38,25.125 c 2.16,0 4.23,-0.269 6.19,-0.816 1.95,-0.543 3.65,-1.367 5.1,-2.48 1.46,-1.118 2.62,-2.54 3.49,-4.297 0.86,-1.758 1.29,-3.821 1.29,-6.192 0,-3.359 -0.86,-6.273 -2.6,-8.742 -1.72,-2.473 -4.29,-4.055 -7.69,-4.746 l 0,-0.145 c 1.73,-0.25 3.16,-0.703 4.29,-1.367 1.14,-0.668 2.06,-1.527 2.78,-2.558 0.72,-1.035 1.23,-2.239 1.56,-3.594 0.31,-1.363 0.53,-2.832 0.62,-4.41 0.05,-0.887 0.1,-1.977 0.16,-3.262 0.05,-1.285 0.15,-2.582 0.29,-3.891 0.15,-1.312 0.38,-2.543 0.71,-3.711 0.32,-1.156 0.74,-2.054 1.3,-2.699 l -5.56,0 c -0.29,0.496 -0.54,1.098 -0.7,1.809 -0.18,0.723 -0.31,1.461 -0.37,2.234 -0.08,0.762 -0.14,1.512 -0.2,2.254 -0.05,0.742 -0.1,1.387 -0.14,1.926 -0.09,1.875 -0.26,3.742 -0.49,5.598 -0.22,1.851 -0.69,3.503 -1.4,4.961 -0.72,1.46 -1.76,2.632 -3.11,3.523 -1.36,0.887 -3.23,1.281 -5.6,1.191 l -19.12,0 0,-23.496 -5.03,0 0,52.91 24.23,0"
id="path3030"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1308.46,140.879 c 0.77,-2.793 1.95,-5.289 3.56,-7.484 1.61,-2.2 3.66,-3.965 6.19,-5.301 2.52,-1.336 5.54,-2.004 9.04,-2.004 3.51,0 6.51,0.668 9,2.004 2.5,1.336 4.55,3.101 6.15,5.301 1.6,2.195 2.8,4.691 3.56,7.484 0.77,2.789 1.15,5.613 1.15,8.48 0,2.914 -0.38,5.758 -1.15,8.52 -0.76,2.769 -1.96,5.25 -3.56,7.449 -1.6,2.199 -3.65,3.969 -6.15,5.297 -2.49,1.336 -5.49,2.008 -9,2.008 -3.5,0 -6.52,-0.672 -9.04,-2.008 -2.53,-1.328 -4.58,-3.098 -6.19,-5.297 -1.61,-2.199 -2.79,-4.68 -3.56,-7.449 -0.75,-2.762 -1.15,-5.606 -1.15,-8.52 0,-2.867 0.4,-5.691 1.15,-8.48 z m -4.63,18.934 c 1.04,3.308 2.6,6.23 4.67,8.777 2.08,2.547 4.68,4.57 7.82,6.078 3.14,1.504 6.79,2.254 10.93,2.254 4.15,0 7.78,-0.75 10.89,-2.254 3.11,-1.508 5.71,-3.531 7.78,-6.078 2.08,-2.547 3.64,-5.469 4.67,-8.777 1.05,-3.313 1.56,-6.797 1.56,-10.454 0,-3.656 -0.51,-7.136 -1.56,-10.449 -1.03,-3.308 -2.59,-6.226 -4.67,-8.738 -2.07,-2.52 -4.67,-4.531 -7.78,-6.043 -3.11,-1.5 -6.74,-2.266 -10.89,-2.266 -4.14,0 -7.79,0.766 -10.93,2.266 -3.14,1.512 -5.74,3.523 -7.82,6.043 -2.07,2.512 -3.63,5.43 -4.67,8.738 -1.04,3.313 -1.54,6.793 -1.54,10.449 0,3.657 0.5,7.141 1.54,10.454"
id="path3032"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1367.77,175.816 30.84,-44.757 0.15,0 0,44.757 5.05,0 0,-52.91 -5.64,0 -30.83,44.754 -0.15,0 0,-44.754 -5.04,0 0,52.91 5.62,0"
id="path3034"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1423.89,175.816 18.3,-46.386 18.22,46.386 7.41,0 0,-52.91 -5.03,0 0,45.723 -0.15,0 -18.09,-45.723 -4.74,0 -18.15,45.723 -0.15,0 0,-45.723 -5.04,0 0,52.91 7.42,0"
id="path3036"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1517.1,175.816 0,-4.296 -31.48,0 0,-19.122 29.48,0 0,-4.293 -29.48,0 0,-20.898 31.86,0 0,-4.301 -36.9,0 0,52.91 36.52,0"
id="path3038"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1532.29,175.816 30.82,-44.757 0.14,0 0,44.757 5.03,0 0,-52.91 -5.62,0 -30.81,44.754 -0.15,0 0,-44.754 -5.03,0 0,52.91 5.62,0"
id="path3040"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1617.34,175.816 0,-4.296 -18.44,0 0,-48.614 -5.04,0 0,48.614 -18.38,0 0,4.296 41.86,0"
id="path3042"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1647.8,143.656 -10.22,27.121 -10.6,-27.121 20.82,0 z m -7.18,32.16 20.74,-52.91 -5.4,0 -6.46,16.449 -24.07,0 -6.38,-16.449 -5.33,0 21.26,52.91 5.64,0"
id="path3044"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1673.44,175.816 0,-48.609 29.65,0 0,-4.301 -34.68,0 0,52.91 5.03,0"
id="path3046"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1769.74,165.254 c -1.02,1.605 -2.25,2.953 -3.71,4.043 -1.46,1.082 -3.06,1.914 -4.81,2.48 -1.76,0.567 -3.6,0.856 -5.52,0.856 -3.51,0 -6.53,-0.672 -9.05,-2.008 -2.52,-1.328 -4.58,-3.098 -6.18,-5.297 -1.61,-2.199 -2.8,-4.68 -3.56,-7.449 -0.77,-2.762 -1.15,-5.606 -1.15,-8.52 0,-2.867 0.38,-5.691 1.15,-8.48 0.76,-2.793 1.95,-5.289 3.56,-7.484 1.6,-2.2 3.66,-3.965 6.18,-5.301 2.52,-1.336 5.54,-2.004 9.05,-2.004 2.46,0 4.69,0.449 6.66,1.336 1.98,0.89 3.68,2.101 5.12,3.633 1.43,1.523 2.59,3.324 3.48,5.371 0.89,2.05 1.45,4.261 1.71,6.633 l 5.03,0 c -0.35,-3.258 -1.11,-6.204 -2.3,-8.821 -1.18,-2.617 -2.7,-4.84 -4.59,-6.664 -1.88,-1.824 -4.08,-3.238 -6.63,-4.223 -2.54,-0.992 -5.37,-1.492 -8.48,-1.492 -4.17,0 -7.81,0.766 -10.93,2.266 -3.15,1.512 -5.76,3.523 -7.83,6.043 -2.07,2.512 -3.62,5.43 -4.65,8.738 -1.04,3.313 -1.57,6.793 -1.57,10.449 0,3.657 0.53,7.141 1.57,10.454 1.03,3.308 2.58,6.23 4.65,8.777 2.07,2.547 4.68,4.57 7.83,6.078 3.12,1.504 6.76,2.254 10.93,2.254 2.52,0 4.97,-0.371 7.38,-1.106 2.39,-0.738 4.56,-1.843 6.51,-3.296 1.95,-1.461 3.58,-3.254 4.89,-5.372 1.3,-2.125 2.13,-4.574 2.47,-7.335 l -5.04,0 c -0.43,2.019 -1.16,3.839 -2.17,5.441"
id="path3048"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1791.01,140.879 c 0.76,-2.793 1.95,-5.289 3.56,-7.484 1.6,-2.2 3.66,-3.965 6.18,-5.301 2.52,-1.336 5.53,-2.004 9.04,-2.004 3.51,0 6.5,0.668 9.01,2.004 2.49,1.336 4.54,3.101 6.14,5.301 1.61,2.195 2.79,4.691 3.57,7.484 0.75,2.789 1.14,5.613 1.14,8.48 0,2.914 -0.39,5.758 -1.14,8.52 -0.78,2.769 -1.96,5.25 -3.57,7.449 -1.6,2.199 -3.65,3.969 -6.14,5.297 -2.51,1.336 -5.5,2.008 -9.01,2.008 -3.51,0 -6.52,-0.672 -9.04,-2.008 -2.52,-1.328 -4.58,-3.098 -6.18,-5.297 -1.61,-2.199 -2.8,-4.68 -3.56,-7.449 -0.78,-2.762 -1.16,-5.606 -1.16,-8.52 0,-2.867 0.38,-5.691 1.16,-8.48 z m -4.64,18.934 c 1.04,3.308 2.6,6.23 4.67,8.777 2.08,2.547 4.68,4.57 7.82,6.078 3.13,1.504 6.77,2.254 10.93,2.254 4.15,0 7.78,-0.75 10.89,-2.254 3.11,-1.508 5.72,-3.531 7.79,-6.078 2.07,-2.547 3.62,-5.469 4.66,-8.777 1.04,-3.313 1.56,-6.797 1.56,-10.454 0,-3.656 -0.52,-7.136 -1.56,-10.449 -1.04,-3.308 -2.59,-6.226 -4.66,-8.738 -2.07,-2.52 -4.68,-4.531 -7.79,-6.043 -3.11,-1.5 -6.74,-2.266 -10.89,-2.266 -4.16,0 -7.8,0.766 -10.93,2.266 -3.14,1.512 -5.74,3.523 -7.82,6.043 -2.07,2.512 -3.63,5.43 -4.67,8.738 -1.04,3.313 -1.56,6.793 -1.56,10.449 0,3.657 0.52,7.141 1.56,10.454"
id="path3050"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1850.33,175.816 30.82,-44.757 0.15,0 0,44.757 5.04,0 0,-52.91 -5.64,0 -30.82,44.754 -0.15,0 0,-44.754 -5.04,0 0,52.91 5.64,0"
id="path3052"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1935.39,175.816 0,-4.296 -18.45,0 0,-48.614 -5.04,0 0,48.614 -18.37,0 0,4.296 41.86,0"
id="path3054"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1965.86,143.656 -10.23,27.121 -10.6,-27.121 20.83,0 z m -7.21,32.16 20.76,-52.91 -5.41,0 -6.44,16.449 -24.08,0 -6.38,-16.449 -5.33,0 21.26,52.91 5.62,0"
id="path3056"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1993.71,175.816 18.3,-46.386 18.24,46.386 7.41,0 0,-52.91 -5.04,0 0,45.723 -0.15,0 -18.09,-45.723 -4.73,0 -18.16,45.723 -0.14,0 0,-45.723 -5.05,0 0,52.91 7.41,0"
id="path3058"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 2050.78,122.906 5.0273,0 0,52.9102 -5.0273,0 0,-52.9102 z"
id="path3060"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 2074.63,175.816 30.83,-44.757 0.15,0 0,44.757 5.04,0 0,-52.91 -5.63,0 -30.83,44.754 -0.15,0 0,-44.754 -5.04,0 0,52.91 5.63,0"
id="path3062"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 2151.78,143.656 -10.24,27.121 -10.58,-27.121 20.82,0 z m -7.2,32.16 20.76,-52.91 -5.41,0 -6.45,16.449 -24.09,0 -6.36,-16.449 -5.34,0 21.27,52.91 5.62,0"
id="path3064"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 2177.93,175.816 30.82,-44.757 0.16,0 0,44.757 5.05,0 0,-52.91 -5.64,0 -30.84,44.754 -0.14,0 0,-44.754 -5.04,0 0,52.91 5.63,0"
id="path3066"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 2263.01,175.816 0,-4.296 -18.46,0 0,-48.614 -5.04,0 0,48.614 -18.38,0 0,4.296 41.88,0"
id="path3068"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 945.785,32.6602 c 1.875,0 3.656,0.1562 5.336,0.4804 1.676,0.3164 3.16,0.8985 4.445,1.7383 1.286,0.8477 2.301,1.9649 3.043,3.375 0.739,1.4102 1.11,3.1719 1.11,5.2969 0,3.4101 -1.203,5.9648 -3.602,7.668 -2.387,1.7031 -5.836,2.5585 -10.332,2.5585 l -17.344,0 0,-21.1171 17.344,0 z m 0,25.414 c 2.028,0 3.778,0.2344 5.262,0.711 1.48,0.4609 2.719,1.1015 3.711,1.9218 0.98,0.8125 1.726,1.7696 2.215,2.8555 0.5,1.082 0.742,2.2422 0.742,3.4766 0,6.6211 -3.977,9.9375 -11.93,9.9375 l -17.344,0 0,-18.9024 17.344,0 z m 0,23.1953 c 2.223,0 4.36,-0.2109 6.41,-0.6289 2.051,-0.4218 3.852,-1.1328 5.41,-2.1484 1.559,-1.0156 2.805,-2.3438 3.743,-4 0.937,-1.6563 1.406,-3.7227 1.406,-6.1914 0,-1.3867 -0.219,-2.7305 -0.668,-4.0391 -0.445,-1.3086 -1.074,-2.4961 -1.887,-3.5547 -0.808,-1.0625 -1.781,-1.9687 -2.89,-2.7031 -1.114,-0.7422 -2.36,-1.2617 -3.739,-1.5586 l 0,-0.1523 c 3.403,-0.4453 6.121,-1.8321 8.145,-4.1797 2.031,-2.3477 3.043,-5.25 3.043,-8.711 0,-0.8398 -0.074,-1.7851 -0.227,-2.8515 -0.144,-1.0625 -0.437,-2.1524 -0.886,-3.2617 -0.446,-1.1133 -1.086,-2.2149 -1.93,-3.2969 -0.836,-1.0859 -1.957,-2.0391 -3.363,-2.8516 -1.414,-0.8203 -3.141,-1.4883 -5.192,-2.0039 -2.051,-0.5195 -4.508,-0.7812 -7.375,-0.7812 l -22.379,0 0,52.914 22.379,0"
id="path3070"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 975.426,28.3555 5.04297,0 0,52.9141 -5.04297,0 0,-52.9141 z"
id="path3072"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 997.105,46.3281 c 0.762,-2.789 1.95,-5.2812 3.555,-7.4804 1.6,-2.1993 3.67,-3.9688 6.18,-5.2969 2.53,-1.3399 5.54,-2.0039 9.04,-2.0039 3.51,0 6.52,0.664 9.01,2.0039 2.5,1.3281 4.54,3.0976 6.15,5.2969 1.61,2.1992 2.79,4.6914 3.55,7.4804 0.77,2.793 1.16,5.6211 1.16,8.4844 0,2.918 -0.39,5.7578 -1.16,8.5273 -0.76,2.7618 -1.94,5.2461 -3.55,7.4454 -1.61,2.2031 -3.65,3.9648 -6.15,5.3007 -2.49,1.3321 -5.5,2 -9.01,2 -3.5,0 -6.51,-0.6679 -9.04,-2 -2.51,-1.3359 -4.58,-3.0976 -6.18,-5.3007 -1.605,-2.1993 -2.793,-4.6836 -3.555,-7.4454 -0.773,-2.7695 -1.152,-5.6093 -1.152,-8.5273 0,-2.8633 0.379,-5.6914 1.152,-8.4844 z m -4.632,18.9336 c 1.035,3.3125 2.59,6.2383 4.668,8.7813 2.074,2.5429 4.679,4.5742 7.819,6.0781 3.13,1.5078 6.77,2.2578 10.92,2.2578 4.15,0 7.79,-0.75 10.9,-2.2578 3.11,-1.5039 5.71,-3.5352 7.78,-6.0781 2.08,-2.543 3.63,-5.4688 4.67,-8.7813 1.04,-3.3047 1.56,-6.789 1.56,-10.4492 0,-3.6602 -0.52,-7.1406 -1.56,-10.4414 -1.04,-3.3164 -2.59,-6.2305 -4.67,-8.7461 -2.07,-2.5195 -4.67,-4.5312 -7.78,-6.0391 -3.11,-1.5039 -6.75,-2.2656 -10.9,-2.2656 -4.15,0 -7.79,0.7617 -10.92,2.2656 -3.14,1.5079 -5.745,3.5196 -7.819,6.0391 -2.078,2.5156 -3.633,5.4297 -4.668,8.7461 -1.035,3.3008 -1.559,6.7812 -1.559,10.4414 0,3.6602 0.524,7.1445 1.559,10.4492"
id="path3074"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1087.1,81.2695 0,-4.2929 -18.45,0 0,-48.6211 -5.04,0 0,48.6211 -18.38,0 0,4.2929 41.87,0"
id="path3076"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1118.15,56.1484 c 1.53,0 2.99,0.2383 4.37,0.7071 1.39,0.4687 2.6,1.1484 3.63,2.0351 1.04,0.8907 1.86,1.9688 2.49,3.2227 0.61,1.2617 0.93,2.7109 0.93,4.332 0,3.2656 -0.95,5.836 -2.82,7.7031 -1.88,1.8829 -4.75,2.8282 -8.6,2.8282 l -18.82,0 0,-20.8282 18.82,0 z m 0.37,25.1211 c 2.17,0 4.23,-0.2695 6.19,-0.8203 1.95,-0.539 3.65,-1.3633 5.11,-2.4765 1.46,-1.1133 2.62,-2.543 3.49,-4.3008 0.86,-1.7578 1.29,-3.8125 1.29,-6.1875 0,-3.3594 -0.86,-6.2696 -2.59,-8.7461 -1.73,-2.4688 -4.3,-4.0469 -7.71,-4.7383 l 0,-0.1484 c 1.73,-0.25 3.16,-0.7032 4.3,-1.3672 1.14,-0.668 2.06,-1.5235 2.78,-2.5586 0.71,-1.0391 1.23,-2.2344 1.55,-3.5977 0.33,-1.3593 0.54,-2.8281 0.63,-4.4062 0.05,-0.8867 0.1,-1.9766 0.15,-3.2656 0.05,-1.2852 0.15,-2.5782 0.3,-3.8868 0.15,-1.3125 0.38,-2.5468 0.71,-3.707 0.31,-1.1562 0.74,-2.0586 1.29,-2.707 l -5.56,0 c -0.29,0.5 -0.53,1.1015 -0.7,1.8203 -0.17,0.7187 -0.3,1.4531 -0.37,2.2265 -0.07,0.7618 -0.14,1.5118 -0.19,2.25 -0.04,0.7461 -0.1,1.3946 -0.15,1.9297 -0.1,1.875 -0.26,3.7461 -0.48,5.6016 -0.22,1.8555 -0.69,3.5 -1.41,4.9609 -0.71,1.4532 -1.75,2.6289 -3.11,3.5235 -1.36,0.8906 -3.22,1.2812 -5.59,1.1875 l -19.12,0 0,-23.5 -5.04,0 0,52.914 24.23,0"
id="path3078"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1174.25,49.1055 -10.23,27.125 -10.6,-27.125 20.83,0 z m -7.19,32.164 20.75,-52.914 -5.41,0 -6.45,16.457 -24.08,0 -6.38,-16.457 -5.33,0 21.27,52.914 5.63,0"
id="path3080"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1200.41,81.2695 30.83,-44.7617 0.15,0 0,44.7617 5.04,0 0,-52.914 -5.63,0 -30.84,44.7578 -0.15,0 0,-44.7578 -5.04,0 0,52.914 5.64,0"
id="path3082"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1252.87,38.9609 c 0.9,-1.8281 2.12,-3.289 3.67,-4.375 1.57,-1.0898 3.4,-1.8671 5.52,-2.3359 2.12,-0.4727 4.4,-0.7031 6.83,-0.7031 1.37,0 2.89,0.1914 4.52,0.5976 1.62,0.3907 3.14,1.0196 4.55,1.8828 1.42,0.8711 2.58,1.9727 3.52,3.336 0.94,1.3515 1.41,3.0039 1.41,4.9258 0,1.4843 -0.33,2.7695 -1.01,3.8593 -0.66,1.086 -1.53,1.9961 -2.58,2.7383 -1.07,0.7422 -2.24,1.3438 -3.53,1.8164 -1.28,0.4688 -2.55,0.8555 -3.78,1.1524 l -11.78,2.8789 c -1.54,0.4062 -3.02,0.8906 -4.49,1.4922 -1.44,0.5898 -2.72,1.375 -3.81,2.3672 -1.09,0.9843 -1.95,2.2031 -2.63,3.6289 -0.67,1.4336 -1,3.1875 -1,5.2617 0,1.289 0.25,2.7929 0.74,4.5234 0.49,1.7266 1.42,3.3594 2.79,4.8906 1.35,1.5274 3.21,2.8243 5.59,3.8907 2.37,1.0625 5.4,1.5898 9.1,1.5898 2.62,0 5.13,-0.3398 7.49,-1.0351 2.38,-0.6915 4.47,-1.7305 6.22,-3.1133 1.8,-1.3789 3.21,-3.0977 4.26,-5.1446 1.08,-2.0546 1.6,-4.4453 1.6,-7.1601 l -5.03,0 c -0.1,2.0351 -0.56,3.7969 -1.37,5.3047 -0.82,1.5039 -1.88,2.7617 -3.19,3.7773 -1.3,1.0117 -2.81,1.7852 -4.53,2.3008 -1.7,0.5195 -3.49,0.7773 -5.37,0.7773 -1.72,0 -3.39,-0.1914 -5,-0.5586 -1.61,-0.371 -3.01,-0.9609 -4.22,-1.7812 -1.21,-0.8164 -2.18,-1.8906 -2.93,-3.2227 -0.74,-1.332 -1.11,-2.9882 -1.11,-4.9648 0,-1.2305 0.22,-2.3086 0.64,-3.2227 0.42,-0.914 0.98,-1.6953 1.72,-2.332 0.75,-0.6484 1.61,-1.1601 2.56,-1.5547 0.98,-0.4023 1.99,-0.7226 3.08,-0.9687 l 12.9,-3.1875 c 1.88,-0.4883 3.64,-1.0938 5.3,-1.8164 1.65,-0.711 3.11,-1.6055 4.37,-2.6602 1.27,-1.0625 2.24,-2.3633 2.97,-3.8945 0.71,-1.5313 1.07,-3.3867 1.07,-5.5586 0,-0.5899 -0.07,-1.3828 -0.2,-2.3672 -0.11,-0.9922 -0.41,-2.0313 -0.87,-3.1523 -0.47,-1.1133 -1.14,-2.2344 -2,-3.3711 -0.87,-1.1368 -2.06,-2.1602 -3.56,-3.0782 -1.51,-0.9062 -3.37,-1.6562 -5.6,-2.2226 -2.22,-0.5586 -4.88,-0.8516 -8,-0.8516 -3.11,0 -6,0.3594 -8.68,1.0781 -2.65,0.7188 -4.94,1.8125 -6.81,3.2969 -1.88,1.4844 -3.32,3.3789 -4.34,5.707 -1,2.3204 -1.43,5.1055 -1.3,8.375 l 5.05,0 c -0.06,-2.7148 0.37,-4.9921 1.25,-6.8164"
id="path3084"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1331.72,81.2695 0,-4.2929 -28.53,0 0,-19.1211 25.35,0 0,-4.3008 -25.35,0 0,-25.1992 -5.04,0 0,52.914 33.57,0"
id="path3086"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1343.54,46.3281 c 0.78,-2.789 1.95,-5.2812 3.55,-7.4804 1.6,-2.1993 3.68,-3.9688 6.2,-5.2969 2.51,-1.3399 5.53,-2.0039 9.03,-2.0039 3.51,0 6.51,0.664 9.01,2.0039 2.5,1.3281 4.55,3.0976 6.15,5.2969 1.6,2.1992 2.78,4.6914 3.56,7.4804 0.76,2.793 1.15,5.6211 1.15,8.4844 0,2.918 -0.39,5.7578 -1.15,8.5273 -0.78,2.7618 -1.96,5.2461 -3.56,7.4454 -1.6,2.2031 -3.65,3.9648 -6.15,5.3007 -2.5,1.3321 -5.5,2 -9.01,2 -3.5,0 -6.52,-0.6679 -9.03,-2 -2.52,-1.3359 -4.6,-3.0976 -6.2,-5.3007 -1.6,-2.1993 -2.77,-4.6836 -3.55,-7.4454 -0.77,-2.7695 -1.14,-5.6093 -1.14,-8.5273 0,-2.8633 0.37,-5.6914 1.14,-8.4844 z m -4.63,18.9336 c 1.03,3.3125 2.59,6.2383 4.66,8.7813 2.09,2.5429 4.69,4.5742 7.82,6.0781 3.14,1.5078 6.79,2.2578 10.93,2.2578 4.15,0 7.8,-0.75 10.9,-2.2578 3.11,-1.5039 5.7,-3.5352 7.78,-6.0781 2.09,-2.543 3.63,-5.4688 4.66,-8.7813 1.04,-3.3047 1.57,-6.789 1.57,-10.4492 0,-3.6602 -0.53,-7.1406 -1.57,-10.4414 -1.03,-3.3164 -2.57,-6.2305 -4.66,-8.7461 -2.08,-2.5195 -4.67,-4.5312 -7.78,-6.0391 -3.1,-1.5039 -6.75,-2.2656 -10.9,-2.2656 -4.14,0 -7.79,0.7617 -10.93,2.2656 -3.13,1.5079 -5.73,3.5196 -7.82,6.0391 -2.07,2.5156 -3.63,5.4297 -4.66,8.7461 -1.04,3.3008 -1.56,6.7812 -1.56,10.4414 0,3.6602 0.52,7.1445 1.56,10.4492"
id="path3088"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1421.16,56.1484 c 1.54,0 3,0.2383 4.39,0.7071 1.37,0.4687 2.58,1.1484 3.62,2.0351 1.04,0.8907 1.87,1.9688 2.49,3.2227 0.61,1.2617 0.91,2.7109 0.91,4.332 0,3.2656 -0.93,5.836 -2.81,7.7031 -1.88,1.8829 -4.73,2.8282 -8.6,2.8282 l -18.83,0 0,-20.8282 18.83,0 z m 0.37,25.1211 c 2.18,0 4.24,-0.2695 6.18,-0.8203 1.96,-0.539 3.67,-1.3633 5.12,-2.4765 1.47,-1.1133 2.63,-2.543 3.49,-4.3008 0.86,-1.7578 1.3,-3.8125 1.3,-6.1875 0,-3.3594 -0.87,-6.2696 -2.6,-8.7461 -1.72,-2.4688 -4.3,-4.0469 -7.71,-4.7383 l 0,-0.1484 c 1.74,-0.25 3.17,-0.7032 4.3,-1.3672 1.13,-0.668 2.06,-1.5235 2.78,-2.5586 0.72,-1.0391 1.24,-2.2344 1.56,-3.5977 0.32,-1.3593 0.52,-2.8281 0.63,-4.4062 0.05,-0.8867 0.1,-1.9766 0.15,-3.2656 0.05,-1.2852 0.15,-2.5782 0.29,-3.8868 0.16,-1.3125 0.38,-2.5468 0.7,-3.707 0.33,-1.1562 0.76,-2.0586 1.3,-2.707 l -5.55,0 c -0.31,0.5 -0.54,1.1015 -0.71,1.8203 -0.17,0.7187 -0.29,1.4531 -0.37,2.2265 -0.08,0.7618 -0.13,1.5118 -0.18,2.25 -0.05,0.7461 -0.1,1.3946 -0.15,1.9297 -0.1,1.875 -0.25,3.7461 -0.48,5.6016 -0.22,1.8555 -0.7,3.5 -1.41,4.9609 -0.73,1.4532 -1.76,2.6289 -3.12,3.5235 -1.36,0.8906 -3.21,1.2812 -5.59,1.1875 l -19.13,0 0,-23.5 -5.03,0 0,52.914 24.23,0"
id="path3090"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1456.21,81.2695 18.3,-46.3906 18.24,46.3906 7.41,0 0,-52.914 -5.04,0 0,45.7265 -0.15,0 -18.08,-45.7265 -4.74,0 -18.17,45.7265 -0.13,0 0,-45.7265 -5.05,0 0,52.914 7.41,0"
id="path3092"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1541.21,49.1055 -10.22,27.125 -10.6,-27.125 20.82,0 z m -7.19,32.164 20.74,-52.914 -5.42,0 -6.42,16.457 -24.08,0 -6.38,-16.457 -5.33,0 21.27,52.914 5.62,0"
id="path3094"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1593,81.2695 0,-4.2929 -18.47,0 0,-48.6211 -5.04,0 0,48.6211 -18.37,0 0,4.2929 41.88,0"
id="path3096"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1600.55,28.3555 5.0391,0 0,52.9141 -5.0391,0 0,-52.9141 z"
id="path3098"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1622.23,46.3281 c 0.76,-2.789 1.94,-5.2812 3.55,-7.4804 1.6,-2.1993 3.67,-3.9688 6.19,-5.2969 2.52,-1.3399 5.53,-2.0039 9.04,-2.0039 3.5,0 6.5,0.664 9.01,2.0039 2.48,1.3281 4.54,3.0976 6.14,5.2969 1.61,2.1992 2.8,4.6914 3.56,7.4804 0.76,2.793 1.15,5.6211 1.15,8.4844 0,2.918 -0.39,5.7578 -1.15,8.5273 -0.76,2.7618 -1.95,5.2461 -3.56,7.4454 -1.6,2.2031 -3.66,3.9648 -6.14,5.3007 -2.51,1.3321 -5.51,2 -9.01,2 -3.51,0 -6.52,-0.6679 -9.04,-2 -2.52,-1.3359 -4.59,-3.0976 -6.19,-5.3007 -1.61,-2.1993 -2.79,-4.6836 -3.55,-7.4454 -0.77,-2.7695 -1.16,-5.6093 -1.16,-8.5273 0,-2.8633 0.39,-5.6914 1.16,-8.4844 z m -4.64,18.9336 c 1.03,3.3125 2.6,6.2383 4.68,8.7813 2.07,2.5429 4.67,4.5742 7.8,6.0781 3.14,1.5078 6.79,2.2578 10.94,2.2578 4.16,0 7.78,-0.75 10.88,-2.2578 3.13,-1.5039 5.72,-3.5352 7.8,-6.0781 2.07,-2.543 3.62,-5.4688 4.66,-8.7813 1.03,-3.3047 1.55,-6.789 1.55,-10.4492 0,-3.6602 -0.52,-7.1406 -1.55,-10.4414 -1.04,-3.3164 -2.59,-6.2305 -4.66,-8.7461 -2.08,-2.5195 -4.67,-4.5312 -7.8,-6.0391 -3.1,-1.5039 -6.72,-2.2656 -10.88,-2.2656 -4.15,0 -7.8,0.7617 -10.94,2.2656 -3.13,1.5079 -5.73,3.5196 -7.8,6.0391 -2.08,2.5156 -3.65,5.4297 -4.68,8.7461 -1.03,3.3008 -1.55,6.7812 -1.55,10.4414 0,3.6602 0.52,7.1445 1.55,10.4492"
id="path3100"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1681.55,81.2695 30.82,-44.7617 0.15,0 0,44.7617 5.04,0 0,-52.914 -5.64,0 -30.82,44.7578 -0.15,0 0,-44.7578 -5.03,0 0,52.914 5.63,0"
id="path3102"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1775.58,55.332 c 3.51,0 6.35,0.8946 8.52,2.6719 2.17,1.7774 3.26,4.4922 3.26,8.1484 0,3.6602 -1.09,6.3711 -3.26,8.1485 -2.17,1.7851 -5.01,2.6758 -8.52,2.6758 l -17.35,0 0,-21.6446 17.35,0 z m 1.12,25.9375 c 2.36,0 4.51,-0.3359 6.43,-0.9961 1.94,-0.6679 3.58,-1.6562 4.99,-2.9648 1.37,-1.3125 2.43,-2.9063 3.17,-4.7852 0.74,-1.875 1.11,-4.0039 1.11,-6.3711 0,-2.3671 -0.37,-4.4921 -1.11,-6.371 -0.74,-1.8829 -1.8,-3.4766 -3.17,-4.7852 -1.41,-1.3047 -3.05,-2.293 -4.99,-2.9609 -1.92,-0.6641 -4.07,-0.9961 -6.43,-0.9961 l -18.47,0 0,-22.6836 -5.04,0 0,52.914 23.51,0"
id="path3104"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1824.93,49.1055 -10.22,27.125 -10.6,-27.125 20.82,0 z m -7.19,32.164 20.76,-52.914 -5.41,0 -6.45,16.457 -24.09,0 -6.38,-16.457 -5.33,0 21.28,52.914 5.62,0"
id="path3106"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1876.73,81.2695 0,-4.2929 -18.45,0 0,-48.6211 -5.04,0 0,48.6211 -18.38,0 0,4.2929 41.87,0"
id="path3108"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1888.96,81.2695 0,-22.9687 31.41,0 0,22.9687 5.04,0 0,-52.914 -5.04,0 0,25.6445 -31.41,0 0,-25.6445 -5.04,0 0,52.914 5.04,0"
id="path3110"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 1938.38,81.2695 12.01,-46.3125 0.15,0 12.9,46.3125 6.3,0 12.96,-46.3125 0.15,0 12.07,46.3125 5.04,0 -14.59,-52.914 -5.34,0 -13.42,47.3515 -0.14,0 -13.34,-47.3515 -5.48,0 -14.67,52.914 5.4,0"
id="path3112"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 2034.73,49.1055 -10.24,27.125 -10.59,-27.125 20.83,0 z m -7.2,32.164 20.75,-52.914 -5.41,0 -6.44,16.457 -24.09,0 -6.37,-16.457 -5.34,0 21.27,52.914 5.63,0"
id="path3114"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 2043.84,81.2695 5.93,0 17.41,-26.8281 17.33,26.8281 6.01,0 -20.9,-31.125 0,-21.789 -5.04,0 0,21.789 -20.74,31.125"
id="path3116"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 2144.01,56.1484 c 1.55,0 3,0.2383 4.38,0.7071 1.39,0.4687 2.6,1.1484 3.63,2.0351 1.05,0.8907 1.88,1.9688 2.48,3.2227 0.62,1.2617 0.93,2.7109 0.93,4.332 0,3.2656 -0.94,5.836 -2.81,7.7031 -1.89,1.8829 -4.75,2.8282 -8.61,2.8282 l -18.81,0 0,-20.8282 18.81,0 z m 0.38,25.1211 c 2.18,0 4.23,-0.2695 6.19,-0.8203 1.95,-0.539 3.66,-1.3633 5.1,-2.4765 1.47,-1.1133 2.63,-2.543 3.5,-4.3008 0.86,-1.7578 1.29,-3.8125 1.29,-6.1875 0,-3.3594 -0.86,-6.2696 -2.59,-8.7461 -1.73,-2.4688 -4.3,-4.0469 -7.7,-4.7383 l 0,-0.1484 c 1.72,-0.25 3.15,-0.7032 4.28,-1.3672 1.15,-0.668 2.07,-1.5235 2.79,-2.5586 0.72,-1.0391 1.24,-2.2344 1.55,-3.5977 0.32,-1.3593 0.54,-2.8281 0.63,-4.4062 0.05,-0.8867 0.1,-1.9766 0.15,-3.2656 0.05,-1.2852 0.15,-2.5782 0.29,-3.8868 0.15,-1.3125 0.39,-2.5468 0.71,-3.707 0.33,-1.1562 0.76,-2.0586 1.3,-2.707 l -5.55,0 c -0.3,0.5 -0.54,1.1015 -0.71,1.8203 -0.17,0.7187 -0.3,1.4531 -0.38,2.2265 -0.07,0.7618 -0.13,1.5118 -0.18,2.25 -0.04,0.7461 -0.1,1.3946 -0.15,1.9297 -0.1,1.875 -0.26,3.7461 -0.49,5.6016 -0.22,1.8555 -0.68,3.5 -1.39,4.9609 -0.73,1.4532 -1.76,2.6289 -3.13,3.5235 -1.35,0.8906 -3.21,1.2812 -5.59,1.1875 l -19.11,0 0,-23.5 -5.04,0 0,52.914 24.23,0"
id="path3118"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 2208.34,81.2695 0,-4.2929 -31.49,0 0,-19.1211 29.49,0 0,-4.3008 -29.49,0 0,-20.8945 31.87,0 0,-4.3047 -36.91,0 0,52.914 36.53,0"
id="path3120"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 2221.6,38.9609 c 0.9,-1.8281 2.13,-3.289 3.66,-4.375 1.58,-1.0898 3.4,-1.8671 5.53,-2.3359 2.12,-0.4727 4.41,-0.7031 6.82,-0.7031 1.38,0 2.9,0.1914 4.53,0.5976 1.62,0.3907 3.13,1.0196 4.55,1.8828 1.41,0.8711 2.58,1.9727 3.52,3.336 0.94,1.3515 1.4,3.0039 1.4,4.9258 0,1.4843 -0.32,2.7695 -0.99,3.8593 -0.66,1.086 -1.55,1.9961 -2.59,2.7383 -1.06,0.7422 -2.24,1.3438 -3.53,1.8164 -1.28,0.4688 -2.54,0.8555 -3.78,1.1524 l -11.77,2.8789 c -1.55,0.4062 -3.03,0.8906 -4.5,1.4922 -1.45,0.5898 -2.73,1.375 -3.82,2.3672 -1.08,0.9843 -1.95,2.2031 -2.62,3.6289 -0.66,1.4336 -1,3.1875 -1,5.2617 0,1.289 0.24,2.7929 0.74,4.5234 0.5,1.7266 1.42,3.3594 2.78,4.8906 1.36,1.5274 3.23,2.8243 5.59,3.8907 2.38,1.0625 5.41,1.5898 9.12,1.5898 2.61,0 5.11,-0.3398 7.48,-1.0351 2.37,-0.6915 4.46,-1.7305 6.24,-3.1133 1.77,-1.3789 3.19,-3.0977 4.24,-5.1446 1.08,-2.0546 1.6,-4.4453 1.6,-7.1601 l -5.03,0 c -0.1,2.0351 -0.55,3.7969 -1.38,5.3047 -0.81,1.5039 -1.87,2.7617 -3.18,3.7773 -1.31,1.0117 -2.82,1.7852 -4.53,2.3008 -1.71,0.5195 -3.48,0.7773 -5.37,0.7773 -1.73,0 -3.4,-0.1914 -5.01,-0.5586 -1.6,-0.371 -3,-0.9609 -4.21,-1.7812 -1.21,-0.8164 -2.19,-1.8906 -2.94,-3.2227 -0.74,-1.332 -1.1,-2.9882 -1.1,-4.9648 0,-1.2305 0.21,-2.3086 0.63,-3.2227 0.43,-0.914 0.99,-1.6953 1.73,-2.332 0.75,-0.6484 1.62,-1.1601 2.56,-1.5547 0.97,-0.4023 1.99,-0.7226 3.08,-0.9687 l 12.9,-3.1875 c 1.87,-0.4883 3.64,-1.0938 5.3,-1.8164 1.65,-0.711 3.11,-1.6055 4.37,-2.6602 1.26,-1.0625 2.24,-2.3633 2.97,-3.8945 0.71,-1.5313 1.07,-3.3867 1.07,-5.5586 0,-0.5899 -0.07,-1.3828 -0.2,-2.3672 -0.11,-0.9922 -0.41,-2.0313 -0.87,-3.1523 -0.48,-1.1133 -1.15,-2.2344 -2.01,-3.3711 -0.86,-1.1368 -2.05,-2.1602 -3.55,-3.0782 -1.5,-0.9062 -3.37,-1.6562 -5.6,-2.2226 -2.22,-0.5586 -4.89,-0.8516 -8.01,-0.8516 -3.11,0 -5.99,0.3594 -8.67,1.0781 -2.66,0.7188 -4.94,1.8125 -6.8,3.2969 -1.89,1.4844 -3.33,3.3789 -4.36,5.707 -0.99,2.3204 -1.43,5.1055 -1.29,8.375 l 5.04,0 c -0.05,-2.7148 0.38,-4.9921 1.26,-6.8164"
id="path3122"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 2270.24,46.3281 c 0.79,-2.789 1.96,-5.2812 3.57,-7.4804 1.6,-2.1993 3.67,-3.9688 6.18,-5.2969 2.52,-1.3399 5.54,-2.0039 9.04,-2.0039 3.51,0 6.52,0.664 9.02,2.0039 2.49,1.3281 4.54,3.0976 6.15,5.2969 1.6,2.1992 2.77,4.6914 3.55,7.4804 0.77,2.793 1.15,5.6211 1.15,8.4844 0,2.918 -0.38,5.7578 -1.15,8.5273 -0.78,2.7618 -1.95,5.2461 -3.55,7.4454 -1.61,2.2031 -3.66,3.9648 -6.15,5.3007 -2.5,1.3321 -5.51,2 -9.02,2 -3.5,0 -6.52,-0.6679 -9.04,-2 -2.51,-1.3359 -4.58,-3.0976 -6.18,-5.3007 -1.61,-2.1993 -2.78,-4.6836 -3.57,-7.4454 -0.75,-2.7695 -1.13,-5.6093 -1.13,-8.5273 0,-2.8633 0.38,-5.6914 1.13,-8.4844 z m -4.62,18.9336 c 1.04,3.3125 2.59,6.2383 4.66,8.7813 2.09,2.5429 4.68,4.5742 7.83,6.0781 3.14,1.5078 6.78,2.2578 10.92,2.2578 4.15,0 7.8,-0.75 10.9,-2.2578 3.11,-1.5039 5.7,-3.5352 7.79,-6.0781 2.07,-2.543 3.63,-5.4688 4.66,-8.7813 1.04,-3.3047 1.56,-6.789 1.56,-10.4492 0,-3.6602 -0.52,-7.1406 -1.56,-10.4414 -1.03,-3.3164 -2.59,-6.2305 -4.66,-8.7461 -2.09,-2.5195 -4.68,-4.5312 -7.79,-6.0391 -3.1,-1.5039 -6.75,-2.2656 -10.9,-2.2656 -4.14,0 -7.78,0.7617 -10.92,2.2656 -3.15,1.5079 -5.74,3.5196 -7.83,6.0391 -2.07,2.5156 -3.62,5.4297 -4.66,8.7461 -1.04,3.3008 -1.56,6.7812 -1.56,10.4414 0,3.6602 0.52,7.1445 1.56,10.4492"
id="path3124"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 2328.53,81.2695 0,-32.7539 c 0,-3.0625 0.35,-5.664 1.03,-7.8242 0.69,-2.1406 1.71,-3.8984 3.05,-5.2578 1.33,-1.3555 2.97,-2.3477 4.88,-2.9648 1.93,-0.6172 4.11,-0.9219 6.52,-0.9219 2.47,0 4.67,0.3047 6.61,0.9219 1.93,0.6171 3.55,1.6093 4.88,2.9648 1.34,1.3594 2.35,3.1172 3.04,5.2578 0.69,2.1602 1.04,4.7617 1.04,7.8242 l 0,32.7539 5.04,0 0,-33.8672 c 0,-2.7148 -0.38,-5.2968 -1.14,-7.7382 -0.77,-2.4493 -1.99,-4.5899 -3.65,-6.418 -1.66,-1.8242 -3.76,-3.2695 -6.36,-4.332 -2.6,-1.0586 -5.74,-1.5938 -9.46,-1.5938 -3.65,0 -6.77,0.5352 -9.37,1.5938 -2.59,1.0625 -4.71,2.5078 -6.37,4.332 -1.66,1.8281 -2.87,3.9687 -3.63,6.418 -0.77,2.4414 -1.13,5.0234 -1.13,7.7382 l 0,33.8672 5.02,0"
id="path3126"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 2400.87,56.1484 c 1.51,0 2.99,0.2383 4.36,0.7071 1.39,0.4687 2.59,1.1484 3.63,2.0351 1.03,0.8907 1.86,1.9688 2.49,3.2227 0.62,1.2617 0.92,2.7109 0.92,4.332 0,3.2656 -0.94,5.836 -2.82,7.7031 -1.86,1.8829 -4.73,2.8282 -8.58,2.8282 l -18.83,0 0,-20.8282 18.83,0 z m 0.36,25.1211 c 2.18,0 4.23,-0.2695 6.2,-0.8203 1.94,-0.539 3.64,-1.3633 5.11,-2.4765 1.45,-1.1133 2.61,-2.543 3.47,-4.3008 0.88,-1.7578 1.29,-3.8125 1.29,-6.1875 0,-3.3594 -0.85,-6.2696 -2.58,-8.7461 -1.73,-2.4688 -4.29,-4.0469 -7.71,-4.7383 l 0,-0.1484 c 1.73,-0.25 3.17,-0.7032 4.31,-1.3672 1.11,-0.668 2.05,-1.5235 2.77,-2.5586 0.71,-1.0391 1.23,-2.2344 1.55,-3.5977 0.32,-1.3593 0.52,-2.8281 0.63,-4.4062 0.05,-0.8867 0.11,-1.9766 0.16,-3.2656 0.04,-1.2852 0.15,-2.5782 0.29,-3.8868 0.14,-1.3125 0.38,-2.5468 0.7,-3.707 0.31,-1.1562 0.75,-2.0586 1.3,-2.707 l -5.56,0 c -0.29,0.5 -0.53,1.1015 -0.71,1.8203 -0.16,0.7187 -0.29,1.4531 -0.36,2.2265 -0.09,0.7618 -0.14,1.5118 -0.19,2.25 -0.05,0.7461 -0.1,1.3946 -0.15,1.9297 -0.09,1.875 -0.27,3.7461 -0.47,5.6016 -0.23,1.8555 -0.69,3.5 -1.42,4.9609 -0.71,1.4532 -1.75,2.6289 -3.1,3.5235 -1.37,0.8906 -3.23,1.2812 -5.6,1.1875 l -19.12,0 0,-23.5 -5.04,0 0,52.914 24.23,0"
id="path3128"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 2465.16,70.7109 c -1.02,1.6094 -2.27,2.9493 -3.71,4.0391 -1.47,1.0859 -3.08,1.9141 -4.82,2.4844 -1.75,0.5625 -3.59,0.8515 -5.53,0.8515 -3.5,0 -6.51,-0.6679 -9.03,-2 -2.52,-1.3359 -4.6,-3.0976 -6.18,-5.3007 -1.62,-2.1993 -2.8,-4.6836 -3.58,-7.4454 -0.76,-2.7695 -1.14,-5.6093 -1.14,-8.5273 0,-2.8633 0.38,-5.6914 1.14,-8.4844 0.78,-2.789 1.96,-5.2812 3.58,-7.4804 1.58,-2.1993 3.66,-3.9688 6.18,-5.2969 2.52,-1.3399 5.53,-2.0039 9.03,-2.0039 2.47,0 4.7,0.4453 6.66,1.332 2,0.8906 3.7,2.1016 5.13,3.6289 1.44,1.5274 2.6,3.3281 3.48,5.3711 0.9,2.0508 1.46,4.2695 1.71,6.6367 l 5.04,0 c -0.35,-3.2578 -1.13,-6.1953 -2.3,-8.8203 -1.19,-2.6172 -2.72,-4.8359 -4.59,-6.668 -1.88,-1.8281 -4.09,-3.2304 -6.63,-4.2226 -2.56,-0.9844 -5.37,-1.4844 -8.5,-1.4844 -4.15,0 -7.79,0.7617 -10.93,2.2656 -3.13,1.5079 -5.74,3.5196 -7.83,6.0391 -2.05,2.5156 -3.62,5.4297 -4.65,8.7461 -1.04,3.3008 -1.56,6.7812 -1.56,10.4414 0,3.6602 0.52,7.1445 1.56,10.4492 1.03,3.3125 2.6,6.2383 4.65,8.7813 2.09,2.5429 4.7,4.5742 7.83,6.0781 3.14,1.5078 6.78,2.2578 10.93,2.2578 2.52,0 4.97,-0.3711 7.38,-1.1094 2.39,-0.7382 4.57,-1.8398 6.52,-3.2968 1.95,-1.461 3.57,-3.25 4.89,-5.3711 1.31,-2.1289 2.14,-4.5703 2.48,-7.3399 l -5.04,0 c -0.45,2.0274 -1.17,3.8399 -2.17,5.4492"
id="path3130"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 2519.59,81.2695 0,-4.2929 -31.5,0 0,-19.1211 29.5,0 0,-4.3008 -29.5,0 0,-20.8945 31.86,0 0,-4.3047 -36.9,0 0,52.914 36.54,0"
id="path3132"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 109.109,130.879 c -2.171,6.246 -5.242,11.781 -9.2145,16.601 -3.9765,4.825 -8.8007,8.7 -14.4765,11.637 -5.6758,2.938 -12.1133,4.395 -19.2969,4.395 -7.375,0 -13.9023,-1.457 -19.5781,-4.395 -5.6797,-2.937 -10.5039,-6.812 -14.4766,-11.637 -3.9726,-4.82 -7.1406,-10.41 -9.5078,-16.75 -2.3633,-6.332 -3.9258,-12.816 -4.6758,-19.433 l 94.7732,0 c -0.179,6.812 -1.371,13.348 -3.547,19.582 z M 20.5703,76.2539 c 1.8008,-6.9141 4.6875,-13.1055 8.6563,-18.5937 3.9765,-5.4883 8.9922,-10.0235 15.0429,-13.6133 6.0547,-3.6016 13.336,-5.3985 21.8516,-5.3985 13.0586,0 23.2734,3.4102 30.6484,10.2188 7.3755,6.8008 12.4885,15.8867 15.3205,27.2344 l 17.883,0 C 126.184,59.457 119.234,46.5898 109.109,37.5195 98.9922,28.4258 84.6602,23.8906 66.1211,23.8906 c -11.5352,0 -21.5234,2.043 -29.9336,6.1133 C 27.7578,34.0664 20.9023,39.6445 15.6094,46.7422 10.3125,53.8281 6.39063,62.0586 3.83203,71.4336 1.28125,80.7891 0,90.6719 0,101.086 c 0,9.641 1.28125,19.098 3.83203,28.371 2.5586,9.27 6.48047,17.551 11.77737,24.828 5.2929,7.285 12.1484,13.153 20.5781,17.606 8.4102,4.437 18.3984,6.664 29.9336,6.664 11.7266,0 21.7539,-2.375 30.0859,-7.102 8.32,-4.719 15.078,-10.922 20.285,-18.578 5.196,-7.66 8.938,-16.461 11.203,-26.395 2.278,-9.937 3.219,-20 2.84,-30.2222 l -112.6522,0 c 0,-6.4336 0.8867,-13.1055 2.6875,-20.0039"
id="path3134"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 157.859,174.297 0,-25.258 0.571,0 c 3.41,8.895 9.457,16.039 18.164,21.426 8.695,5.398 18.254,8.09 28.66,8.09 10.219,0 18.777,-1.325 25.68,-3.977 6.91,-2.648 12.441,-6.379 16.601,-11.203 4.16,-4.824 7.098,-10.738 8.797,-17.734 1.699,-7.008 2.555,-14.864 2.555,-23.555 l 0,-94.2188 -17.875,0 0,91.3708 c 0,6.246 -0.567,12.059 -1.703,17.461 -1.141,5.387 -3.121,10.067 -5.957,14.047 -2.844,3.969 -6.676,7.09 -11.5,9.359 -4.821,2.274 -10.829,3.407 -18.02,3.407 -7.191,0 -13.578,-1.278 -19.152,-3.828 -5.578,-2.555 -10.309,-6.055 -14.192,-10.5 -3.879,-4.442 -6.91,-9.743 -9.082,-15.895 -2.172,-6.156 -3.355,-12.82 -3.547,-20.004 l 0,-85.4178 -17.871,0 0,146.4298 17.871,0"
id="path3136"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 274.938,174.297 45.976,-128.5509 0.566,0 45.407,128.5509 18.449,0 -54.77,-146.4298 -19.019,0 -56.465,146.4298 19.856,0"
id="path3138"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 409.707,174.297 0,-146.4298 -17.875,0 0,146.4298 17.875,0 z m 0,56.187 0,-28.664 -17.875,0 0,28.664 17.875,0"
id="path3140"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 487.02,104.863 c 3.683,0 7.222,0.27 10.632,0.809 3.407,0.547 6.403,1.601 8.993,3.172 2.589,1.566 4.668,3.785 6.238,6.648 1.558,2.852 2.347,6.613 2.347,11.235 0,4.636 -0.789,8.39 -2.347,11.25 -1.57,2.859 -3.649,5.074 -6.238,6.64 -2.59,1.571 -5.586,2.629 -8.993,3.172 -3.41,0.547 -6.949,0.813 -10.632,0.813 l -24.934,0 0,-43.739 24.934,0 z m 8.793,68.68 c 9.128,0 16.898,-1.32 23.304,-3.988 6.406,-2.66 11.613,-6.16 15.633,-10.524 4.023,-4.359 6.961,-9.34 8.797,-14.922 1.84,-5.589 2.758,-11.379 2.758,-17.382 0,-5.852 -0.918,-11.614 -2.758,-17.27 -1.836,-5.652 -4.774,-10.664 -8.797,-15.0273 -4.02,-4.3633 -9.227,-7.8672 -15.633,-10.5234 -6.406,-2.6602 -14.176,-3.9844 -23.304,-3.9844 l -33.727,0 0,-52.336 -32.102,0 0,145.9571 65.829,0"
id="path3142"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 614.273,76.7539 c -1.835,-0.6211 -3.812,-1.1289 -5.929,-1.5351 -2.114,-0.4102 -4.324,-0.75 -6.641,-1.0196 -2.316,-0.2773 -4.637,-0.6211 -6.953,-1.0273 -2.176,-0.4102 -4.328,-0.9571 -6.437,-1.6328 -2.114,-0.6836 -3.958,-1.6016 -5.52,-2.7618 -1.566,-1.1562 -2.832,-2.625 -3.777,-4.3945 -0.957,-1.7773 -1.438,-4.0234 -1.438,-6.7461 0,-2.5937 0.481,-4.7695 1.438,-6.5429 0.945,-1.7696 2.246,-3.168 3.879,-4.1915 1.636,-1.0234 3.543,-1.7382 5.726,-2.1484 2.176,-0.4101 4.426,-0.6094 6.746,-0.6094 5.723,0 10.145,0.9532 13.285,2.8672 3.133,1.9024 5.45,4.1875 6.953,6.8438 1.497,2.6562 2.418,5.3476 2.758,8.0742 0.336,2.7226 0.516,4.9101 0.516,6.543 l 0,10.8398 c -1.234,-1.1055 -2.766,-1.9492 -4.606,-2.5586 z m -57.339,40.9841 c 3,4.496 6.812,8.106 11.449,10.832 4.637,2.723 9.844,4.672 15.637,5.828 5.789,1.157 11.617,1.735 17.48,1.735 5.316,0 10.691,-0.375 16.145,-1.125 5.457,-0.75 10.425,-2.211 14.921,-4.395 4.504,-2.175 8.18,-5.207 11.043,-9.093 2.86,-3.883 4.297,-9.032 4.297,-15.434 l 0,-54.9922 c 0,-4.7774 0.27,-9.336 0.817,-13.6954 0.539,-4.3632 1.496,-7.6328 2.859,-9.8125 l -29.437,0 c -0.543,1.6329 -0.989,3.3008 -1.329,5.0118 -0.343,1.6992 -0.582,3.4414 -0.718,5.2109 -4.633,-4.7734 -10.082,-8.1133 -16.348,-10.0156 -6.273,-1.9063 -12.676,-2.8672 -19.219,-2.8672 -5.043,0 -9.746,0.6172 -14.105,1.8398 -4.367,1.2266 -8.18,3.1367 -11.449,5.7305 -3.27,2.5859 -5.825,5.8594 -7.668,9.8125 -1.84,3.9492 -2.758,8.6523 -2.758,14.1016 0,5.9961 1.058,10.9375 3.168,14.8242 2.109,3.8789 4.836,6.9726 8.179,9.2969 3.34,2.3164 7.157,4.0585 11.446,5.2148 4.297,1.1562 8.617,2.0742 12.98,2.7539 4.364,0.6836 8.656,1.2266 12.879,1.6367 4.227,0.4102 7.973,1.0235 11.25,1.8438 3.266,0.8125 5.856,2.0117 7.762,3.582 1.91,1.5664 2.793,3.8477 2.664,6.8435 0,3.137 -0.516,5.617 -1.535,7.461 -1.028,1.844 -2.395,3.266 -4.09,4.293 -1.707,1.02 -3.68,1.699 -5.93,2.039 -2.25,0.344 -4.672,0.516 -7.258,0.516 -5.718,0 -10.222,-1.223 -13.488,-3.68 -3.277,-2.453 -5.183,-6.543 -5.726,-12.265 l -29.032,0 c 0.41,6.816 2.118,12.46 5.114,16.968"
id="path3144"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 720.172,133.277 0,-19.422 -21.254,0 0,-52.3355 c 0,-4.9023 0.812,-8.1718 2.449,-9.8047 1.637,-1.6406 4.903,-2.4609 9.817,-2.4609 1.632,0 3.195,0.0703 4.699,0.2031 1.496,0.1328 2.926,0.3438 4.289,0.6172 l 0,-22.4883 c -2.453,-0.414 -5.176,-0.6836 -8.176,-0.8203 -2.996,-0.1289 -5.926,-0.1992 -8.789,-0.1992 -4.5,0 -8.754,0.3047 -12.773,0.918 -4.024,0.6094 -7.563,1.8086 -10.629,3.5781 -3.071,1.7695 -5.489,4.293 -7.254,7.5586 -1.781,3.2773 -2.664,7.5703 -2.664,12.8828 l 0,62.3511 -17.578,0 0,19.422 17.578,0 0,31.684 29.031,0 0,-31.684 21.254,0"
id="path3146"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 755.918,173.543 0,-54.984 0.613,0 c 3.684,6.125 8.387,10.586 14.11,13.379 5.722,2.796 11.312,4.195 16.761,4.195 7.77,0 14.137,-1.059 19.114,-3.164 4.972,-2.114 8.894,-5.043 11.754,-8.793 2.863,-3.75 4.871,-8.317 6.031,-13.699 1.152,-5.383 1.734,-11.3481 1.734,-17.8832 l 0,-65.0079 -29.027,0 0,59.6954 c 0,8.7148 -1.363,15.2227 -4.086,19.5157 -2.731,4.293 -7.567,6.433 -14.516,6.433 -7.902,0 -13.633,-2.343 -17.172,-7.039 -3.543,-4.703 -5.316,-12.441 -5.316,-23.2144 l 0,-55.3907 -29.023,0 0,145.9571 29.023,0"
id="path3148"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 863.082,0 21.875,0 0,190.547 -21.875,0 0,-190.547 z"
id="path3150"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g></g></svg>

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="103.4625"
height="25.825001"
id="svg3227"
xml:space="preserve"><metadata
id="metadata3233"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs3231"><filter
x="0"
y="0"
width="1"
height="1"
color-interpolation-filters="sRGB"
id="filter3283"><feColorMatrix
id="feColorMatrix3285"
result="fbSourceGraphic"
values="1"
type="saturate" /><feColorMatrix
id="feColorMatrix3287"
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0 "
in="fbSourceGraphic" /></filter></defs><g
transform="matrix(1.25,0,0,-1.25,0,25.825)"
id="g3235"><g
transform="scale(0.1,0.1)"
id="g3237"
style="filter:url(#filter3283)"><path
d="m 109.105,106.992 c -2.167,6.25 -5.234,11.797 -9.2183,16.602 -3.9648,4.824 -8.789,8.711 -14.4726,11.64 -5.6719,2.95 -12.1094,4.395 -19.2969,4.395 -7.3711,0 -13.9063,-1.445 -19.5703,-4.395 -5.6836,-2.929 -10.5078,-6.816 -14.4805,-11.64 -3.9766,-4.805 -7.1406,-10.41 -9.5039,-16.739 -2.3633,-6.328 -3.9258,-12.812 -4.6875,-19.4331 l 94.785,0 c -0.183,6.8164 -1.375,13.3401 -3.555,19.5701 z M 20.5703,52.3828 c 1.7969,-6.914 4.6875,-13.1055 8.6524,-18.5937 3.9843,-5.4883 8.9961,-10.0391 15.039,-13.6133 6.0547,-3.6133 13.3399,-5.4102 21.8555,-5.4102 13.0664,0 23.2812,3.418 30.6445,10.2149 7.3833,6.8164 12.5003,15.8984 15.3323,27.2461 l 17.871,0 C 126.176,35.5859 119.234,22.7148 109.105,13.6328 98.9883,4.55078 84.6523,0.0195313 66.1172,0.0195313 54.5859,0.0195313 44.5938,2.05078 36.1875,6.13281 27.7578,10.1953 20.9023,15.7617 15.6094,22.8711 10.3164,29.9414 6.39063,38.1836 3.83203,47.5586 1.27344,56.9141 0.00390625,66.7969 0.00390625,77.207 c 0,9.6485 1.26953375,19.1016 3.82812375,28.379 2.5586,9.258 6.48437,17.539 11.77737,24.824 5.2929,7.285 12.1484,13.145 20.5781,17.598 8.4063,4.433 18.3984,6.66 29.9297,6.66 11.7305,0 21.7578,-2.363 30.0898,-7.09 8.32,-4.726 15.078,-10.918 20.281,-18.574 5.196,-7.676 8.946,-16.465 11.211,-26.406 2.266,-9.9417 3.215,-20.0003 2.832,-30.2152 l -112.656,0 c 0,-6.4453 0.8984,-13.1055 2.6953,-20"
id="path3239"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 157.855,150.41 0,-25.254 0.567,0 c 3.418,8.907 9.465,16.035 18.164,21.426 8.703,5.41 18.262,8.086 28.672,8.086 10.215,0 18.769,-1.309 25.676,-3.965 6.902,-2.656 12.441,-6.387 16.601,-11.211 4.16,-4.824 7.098,-10.742 8.797,-17.734 1.699,-7.012 2.559,-14.863 2.559,-23.5549 l 0,-94.21872 -17.879,0 0,91.36722 c 0,6.2504 -0.567,12.0704 -1.711,17.4614 -1.133,5.39 -3.113,10.078 -5.957,14.062 -2.832,3.965 -6.668,7.09 -11.492,9.355 -4.817,2.266 -10.832,3.399 -18.02,3.399 -7.187,0 -13.574,-1.27 -19.16,-3.828 -5.567,-2.559 -10.301,-6.055 -14.18,-10.488 -3.887,-4.454 -6.914,-9.747 -9.082,-15.899 -2.176,-6.152 -3.359,-12.832 -3.555,-19.9999 l 0,-85.42972 -17.871,0 0,146.42562 17.871,0"
id="path3241"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 274.938,150.41 45.976,-128.535 0.566,0 45.399,128.535 18.457,0 -54.766,-146.42562 -19.023,0 -56.465,146.42562 19.856,0"
id="path3243"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 409.711,150.41 0,-146.42562 -17.879,0 0,146.42562 17.879,0 z m 0,56.192 0,-28.653 -17.879,0 0,28.653 17.879,0"
id="path3245"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 488.297,80.9766 c 3.68,0 7.215,0.2734 10.633,0.8203 3.398,0.5469 6.398,1.6015 8.984,3.164 2.598,1.5625 4.668,3.7891 6.242,6.6602 1.563,2.8516 2.352,6.6016 2.352,11.2309 0,4.628 -0.789,8.378 -2.352,11.25 -1.574,2.851 -3.644,5.078 -6.242,6.64 -2.586,1.563 -5.586,2.617 -8.984,3.164 -3.418,0.547 -6.953,0.821 -10.633,0.821 l -24.934,0 0,-43.7504 24.934,0 z m 8.789,68.6914 c 9.129,0 16.902,-1.328 23.309,-3.984 6.406,-2.676 11.613,-6.172 15.625,-10.528 4.023,-4.355 6.964,-9.336 8.8,-14.922 1.844,-5.586 2.762,-11.386 2.762,-17.382 0,-5.8598 -0.918,-11.6215 -2.762,-17.2661 -1.836,-5.664 -4.777,-10.664 -8.8,-15.039 -4.012,-4.3555 -9.219,-7.8711 -15.625,-10.5274 -6.407,-2.6562 -14.18,-3.9843 -23.309,-3.9843 l -33.723,0 0,-52.32426 -32.109,0 0,145.95706 65.832,0"
id="path3247"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 615.551,52.8711 c -1.836,-0.625 -3.817,-1.1328 -5.938,-1.5234 -2.109,-0.4102 -4.316,-0.7618 -6.64,-1.0352 -2.313,-0.2734 -4.629,-0.6055 -6.953,-1.0156 -2.168,-0.4102 -4.325,-0.9571 -6.434,-1.6406 -2.109,-0.6836 -3.957,-1.6016 -5.52,-2.754 -1.562,-1.1523 -2.832,-2.6367 -3.777,-4.3945 -0.957,-1.7773 -1.437,-4.0234 -1.437,-6.7578 0,-2.5781 0.48,-4.7656 1.437,-6.543 0.945,-1.7578 2.246,-3.164 3.875,-4.1797 1.641,-1.0351 3.547,-1.7382 5.734,-2.1484 2.176,-0.4101 4.422,-0.6055 6.747,-0.6055 5.722,0 10.136,0.9375 13.281,2.8516 3.137,1.9141 5.449,4.1992 6.953,6.8555 1.496,2.6562 2.422,5.3515 2.754,8.0664 0.344,2.7344 0.519,4.9219 0.519,6.5429 l 0,10.8399 c -1.23,-1.0938 -2.765,-1.9531 -4.601,-2.5586 z m -57.344,40.9961 c 3,4.4922 6.816,8.1058 11.445,10.8208 4.641,2.734 9.844,4.667 15.645,5.839 5.781,1.153 11.613,1.719 17.48,1.719 5.313,0 10.684,-0.371 16.145,-1.113 5.457,-0.762 10.418,-2.207 14.922,-4.395 4.5,-2.187 8.172,-5.215 11.043,-9.1013 2.851,-3.8867 4.297,-9.0234 4.297,-15.4297 l 0,-55 c 0,-4.7656 0.265,-9.3359 0.812,-13.6914 0.535,-4.35544 1.492,-7.63669 2.859,-9.80466 l -29.433,0 c -0.547,1.62109 -0.996,3.30078 -1.328,5 -0.352,1.69926 -0.586,3.45706 -0.723,5.21486 C 616.742,9.16016 611.293,5.82031 605.023,3.90625 598.754,2.01172 592.348,1.05469 585.805,1.05469 c -5.039,0 -9.746,0.60547 -14.102,1.83594 -4.375,1.23046 -8.183,3.125 -11.453,5.72265 -3.273,2.59762 -5.82,5.85942 -7.668,9.82422 -1.836,3.9453 -2.754,8.6523 -2.754,14.1016 0,5.9961 1.055,10.9375 3.164,14.8242 2.11,3.8672 4.836,6.9726 8.184,9.2969 3.34,2.3046 7.148,4.0429 11.445,5.2148 4.297,1.1523 8.613,2.0703 12.981,2.7539 4.363,0.6836 8.652,1.2109 12.878,1.6211 4.219,0.4102 7.969,1.0352 11.25,1.8555 3.262,0.8008 5.852,2.0117 7.754,3.5742 1.914,1.5625 2.793,3.8476 2.668,6.8555 0,3.125 -0.519,5.6054 -1.535,7.4609 -1.023,1.8359 -2.39,3.2617 -4.09,4.2773 -1.711,1.0352 -3.683,1.6993 -5.929,2.0508 -2.246,0.3321 -4.668,0.5078 -7.254,0.5078 -5.723,0 -10.227,-1.2109 -13.489,-3.6718 -3.281,-2.461 -5.183,-6.543 -5.73,-12.2657 l -29.035,0 c 0.41,6.8164 2.121,12.461 5.117,16.9727"
id="path3249"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 721.449,109.395 0,-19.4145 -21.258,0 0,-52.3438 c 0,-4.9023 0.809,-8.164 2.45,-9.8047 1.64,-1.6406 4.902,-2.4609 9.816,-2.4609 1.629,0 3.191,0.0781 4.695,0.2148 1.504,0.1172 2.93,0.3321 4.297,0.6055 l 0,-22.48046 c -2.461,-0.41016 -5.176,-0.6836 -8.183,-0.82031 -2.989,-0.13672 -5.918,-0.19532 -8.789,-0.19532 -4.493,0 -8.75,0.29297 -12.774,0.91797 -4.023,0.60547 -7.558,1.79688 -10.625,3.57422 -3.066,1.75781 -5.488,4.2969 -7.254,7.5586 -1.777,3.2812 -2.668,7.5586 -2.668,12.8711 l 0,62.3633 -17.578,0 0,19.4145 17.578,0 0,31.679 29.035,0 0,-31.679 21.258,0"
id="path3251"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="m 757.621,149.668 0,-54.9805 0.606,0 c 3.691,6.1135 8.39,10.5855 14.113,13.3785 5.723,2.793 11.316,4.18 16.765,4.18 7.766,0 14.133,-1.055 19.102,-3.164 4.981,-2.109 8.906,-5.039 11.758,-8.789 2.871,-3.75 4.875,-8.3203 6.035,-13.6914 1.152,-5.3907 1.738,-11.3477 1.738,-17.8907 l 0,-64.99996 -29.031,0 0,59.68746 c 0,8.711 -1.359,15.2344 -4.094,19.5118 -2.722,4.2968 -7.558,6.4453 -14.511,6.4453 -7.899,0 -13.633,-2.3438 -17.168,-7.0508 -3.543,-4.6875 -5.313,-12.4414 -5.313,-23.2031 l 0,-55.39066 -29.023,0 0,145.95706 29.023,0"
id="path3253"
style="fill:#090c0d;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g></g></svg>

After

Width:  |  Height:  |  Size: 8.2 KiB

BIN
static/images/uoa.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

BIN
static/images/wait.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 814 B

1782
static/js/d3-pathway.js vendored Normal file

File diff suppressed because it is too large Load Diff

217
static/js/epp/epp.js Normal file
View File

@ -0,0 +1,217 @@
class Setting {
constructor(nameInputId, selectedPackagesId, relativeReasoningSelectId, cutoffInputId, evaluationTypeSelectId,
availableTSSelectId, formsId, tableId, summaryTableId) {
this.nameInputId = nameInputId;
this.selectedPackagesId = selectedPackagesId;
this.relativeReasoningSelectId = relativeReasoningSelectId;
this.cutoffInputId = cutoffInputId;
this.evaluationTypeSelectId = evaluationTypeSelectId;
this.availableTSSelectId = availableTSSelectId;
this.formsId = formsId;
this.tableId = tableId;
this.summaryTableId = summaryTableId;
// General settings
this.name = null;
this.selectedPackages = [];
// Relative Reasoning related
this.selectedRelativeReasoning = null;
this.cutoff = null;
this.evaluationType = null;
// parameters such as { "lowPH": 7, "highPH": 8 }
this.tsParams = {};
}
extractName() {
var tempName = $('#' + this.nameInputId).val()
if (tempName == '') {
console.log("Name was empty...");
return;
}
this.name = tempName;
}
extractSelectedPackages() {
var selPacks = $("#" + this.selectedPackagesId + " :selected");
var ref = this;
ref.selectedPackages = [];
selPacks.each(function () {
var obj = {}
obj['id'] = this.value;
obj['name'] = this.text;
ref.selectedPackages.push(obj);
});
}
extractRelativeReasoning() {
var tempRR = $('#' + this.relativeReasoningSelectId + " :selected").val()
if (tempRR == '') {
console.log("RR was empty...");
return;
}
var obj = {}
obj['id'] = $('#' + this.relativeReasoningSelectId + " :selected").val()
obj['name'] = $('#' + this.relativeReasoningSelectId + " :selected").text()
this.selectedRelativeReasoning = obj;
}
extractCutoff() {
var tempCutoff = $('#' + this.cutoffInputId).val()
if (tempCutoff == '') {
console.log("Cutoff was empty...");
return;
}
this.cutoff = tempCutoff;
}
extractEvaluationType() {
var tempEvaluationType = $('#' + this.evaluationTypeSelectId).val()
if (tempEvaluationType == '') {
console.log("EvaluationType was empty...");
return;
}
this.evaluationType = tempEvaluationType;
}
addTruncator() {
// triggered by "Add"
// will extract values and afterwards updates table + summary
var type = $("#" + this.availableTSSelectId + " :selected").val();
var text = $("#" + this.availableTSSelectId + " :selected").text();
var form = $("#" + type + "_form > :input")
// flag to check whether at least one input had a valid value
var addedValue = false;
// reference being used in each
var ref = this;
form.each(function() {
if(this.value == "" || this.value === "undefined"){
console.log(this);
console.log("Skipping " + this.name);
} else {
var obj = {}
obj[this.name] = this.value;
obj['text'] = text;
ref.tsParams[type] = obj
}
});
this.updateTable();
this.updateSummaryTable();
}
removeTruncator(rowId) {
var summary = rowId.startsWith("sum") ? true : false;
// plain key
var key = rowId.replace(summary ? "sum" : "trunc", "").replace("row", "");
console.log("Removing " + key);
// remove the rows
$("#trunc"+ key + "row").remove();
if($("#sum"+ key + "row").length > 0) {
$("#sum"+ key + "row").remove();
}
delete this.tsParams[key];
}
updateTable() {
// remove all children
$('#'+this.tableId + "Body").empty()
var innerHTML = "<tr>" +
"<td>Name</td>" +
"<td>Value</td>" +
"<td width='10%'>Action</td>" +
"</tr>";
for (var x in this.tsParams) {
var val = "";
for (var y in this.tsParams[x]){
if (y == 'text') {
continue;
}
val += this.tsParams[x][y]
}
innerHTML += "<tr id='trunc" + x + "row'>" +
"<td>" + this.tsParams[x]['text'] + "</td>" +
"<td>" + val + "</td>" +
"<td width='10%'>"+
"<button type='button' id='" + x + "button' class='form-control' onclick='s.removeTruncator(\"trunc" + x + "row\")'>Remove</button>" +
"</td>" +
"</tr>";
}
$('#'+this.tableId + "Body").append(innerHTML);
}
packageRows() {
var res = '';
for(var p in this.selectedPackages) {
var obj = this.selectedPackages[p];
res += "<tr>" +
"<td>Package</td>" +
"<td>" + obj['name'] + "</td>" +
"<td width='10%'>" +
// "<button type='button' id='relativereasoningbutton' class='form-control' onclick='s.removeTruncator(\"Relative Reasoning\")'>Remove</button>" +
"</td>" +
"</tr>";
}
return res;
}
modelRow() {
if(this.selectedRelativeReasoning == null) {
return '';
}
return "<tr>" +
"<td>Relative Reasoning</td>" +
"<td>" + this.selectedRelativeReasoning['name'] + " " + (this.evaluationType == "singleGen" ? "SG" : "MG") + " with t=" + this.cutoff + " </td>" +
"<td width='10%'>" +
// "<button type='button' id='relativereasoningbutton' class='form-control' onclick='s.removeTruncator(\"Relative Reasoning\")'>Remove</button>" +
"</td>" +
"</tr>";
}
updateSummaryTable() {
// remove all children
$('#'+this.summaryTableId + "Body").empty()
var innerHTML = "<tr>" +
"<td>Name</td>" +
"<td>Value</td>" +
"<td width='10%'>Action</td>" +
"</tr>";
innerHTML += this.packageRows();
innerHTML += this.modelRow();
// var innerHTML = ''
for (var x in this.tsParams) {
var val = "";
for (var y in this.tsParams[x]){
if (y == 'text') {
continue;
}
val += this.tsParams[x][y]
}
innerHTML += "<tr id='sum" + x + "row'>" +
"<td>" + this.tsParams[x]['text'] + "</td>" +
"<td>" + val + "</td>" +
"<td width='10%'>"+
"<button type='button' id='" + x + "button' class='form-control' onclick='s.removeTruncator(\"sum" + x + "row\")'>Remove</button>" +
"</td>" +
"</tr>";
}
$('#'+this.summaryTableId + "Body").append(innerHTML);
}
}

View File

@ -0,0 +1 @@
!function(t){"use strict";t.fn.modalSteps=function(a){var e=this,n=t.extend({btnCancelHtml:"Cancel",btnPreviousHtml:"Previous",btnNextHtml:"Next",btnLastStepHtml:"Complete",disableNextButton:!1,completeCallback:function(){},callbacks:{},getTitleAndStep:function(){}},a),d=function(t){return void 0!==t&&"function"==typeof t&&(t(),!0)};return e.on("show.bs.modal",function(){var a,l,s,i=e.find(".modal-footer"),o=i.find(".js-btn-step[data-orientation=cancel]"),p=i.find(".js-btn-step[data-orientation=previous]"),r=i.find(".js-btn-step[data-orientation=next]"),c=n.callbacks["*"],b=n.callbacks[1];n.disableNextButton&&r.attr("disabled","disabled"),p.attr("disabled","disabled"),function(){var t=n.callbacks["*"];if(void 0!==t&&"function"!=typeof t)throw"everyStepCallback is not a function! I need a function";if("function"!=typeof n.completeCallback)throw"completeCallback is not a function! I need a function";for(var a in n.callbacks)if(n.callbacks.hasOwnProperty(a)){var e=n.callbacks[a];if("*"!==a&&void 0!==e&&"function"!=typeof e)throw"Step "+a+" callback must be a function"}}(),d(c),d(b),o.html(n.btnCancelHtml),p.html(n.btnPreviousHtml),r.html(n.btnNextHtml),a=t("<input>").attr({type:"hidden",id:"actual-step",value:"1"}),e.find("#actual-step").remove(),e.append(a),e.find("[data-step=1]").removeClass("d-none"),r.attr("data-step",2),l=e.find("[data-step=1]").data("title"),s=t("<span>").addClass("label label-success").html(1),e.find(".js-title-step").append(s).append(" "+l),n.getTitleAndStep(a.attr("data-title"),1)}).on("hidden.bs.modal",function(){var t=e.find("#actual-step"),a=e.find(".js-btn-step[data-orientation=next]");e.find("[data-step]").not(e.find(".js-btn-step")).addClass("d-none"),t.not(e.find(".js-btn-step")).remove(),a.attr("data-step",1).html(n.btnNextHtml),e.find(".js-title-step").html("")}),e.find(".js-btn-step").on("click",function(){var a,l,s,i,o=t(this),p=e.find("#actual-step"),r=e.find(".js-btn-step[data-orientation=previous]"),c=e.find(".js-btn-step[data-orientation=next]"),b=e.find(".js-title-step"),f=o.data("orientation"),u=parseInt(p.val()),m=n.callbacks["*"];if(a=e.find("div[data-step]").length,"complete"===o.attr("data-step"))return n.completeCallback(),void e.modal("hide");if("next"===f)l=u+1,r.attr("data-step",u),p.val(l);else{if("previous"!==f)return void e.modal("hide");l=u-1,c.attr("data-step",u),r.attr("data-step",l-1),p.val(u-1)}parseInt(p.val())===a?c.attr("data-step","complete").html(n.btnLastStepHtml):c.attr("data-step",l).html(n.btnNextHtml),n.disableNextButton&&c.attr("disabled","disabled"),e.find("[data-step="+u+"]").not(e.find(".js-btn-step")).addClass("d-none"),e.find("[data-step="+l+"]").not(e.find(".js-btn-step")).removeClass("d-none"),parseInt(r.attr("data-step"))>0?r.removeAttr("disabled"):r.attr("disabled","disabled"),"previous"===f&&c.removeAttr("disabled"),(s=e.find("[data-step="+l+"]")).attr("data-unlock-continue")&&c.removeAttr("disabled"),i=s.attr("data-title");var v=t("<span>").addClass("label label-success").html(l);b.html(v).append(" "+i),n.getTitleAndStep(s.attr("data-title"),l);var h=n.callbacks[p.val()];d(m),d(h)}),this}}(jQuery);

191
static/js/epp/pathway.js Normal file
View File

@ -0,0 +1,191 @@
// drawarea
var svg;
// colors
var border = "thin solid lightgrey";
// nodes
// links
// tree
var compoundSize = 75; // = diameter of nodes of small compounds
var maxCompoundSize = function(){ return 1.5 * compoundSize }; // limit increased size of larger compounds
var maxCompoundPopupSize = function(){ return 5 * compoundSize };
var compoundImageMargin = 0.15; // compounds images are rectangles, reduce size to fit into nodes
var treeLayoutLinkDistance = function(){ return 2.5 * compoundSize };
var treeLayoutCharge = -1000; // compounds push each other apart
var treeLayoutPseudoCharge = treeLayoutCharge * 0.1; // relaxes forces on pseudo nodes
var treeLayoutLevelGravity = 0.5; // pulls compounds back to their depth level
var treeLayoutXCenterGravity = 0.03; // pulls compounds to x center
var treeLayoutLevels = function(){ return treeLayoutLinkDistance() }; // distance between depth levels
var treeLayoutXInitMargin = function(){ return treeLayoutLinkDistance() }; // initial x-distance between nodes of same depth
// Convenience method to check wether a given node is a leaf
function isLeaf(node) {
return node.children.length == 0;
}
function getMaxNodeDepth(graph) {
maxDepth = 0
graph.nodes.forEach(function(node, i) {
if (node.depth > maxDepth)
maxDepth = node.depth;
});
return maxDepth;
}
// adds a parents[] and a children[] array for each node set treeSize
function setChildrenAndParents(graph) {
// Init arrays on each node
graph.nodes.forEach(function(node, i) {
node.parents = []
node.children = []
});
// loop through links, determine source and target and populate
// their parent/children arrays
graph.links.forEach(function(link, j) {
source = graph.nodes[link.source];
target = graph.nodes[link.target];
source.children.push(target);
target.parents.push(source);
});
// Recursively traverses the tree to count the nodes that can be reached
function count(node) {
node.touch = true;
c = 1;
node.children.forEach(function(child, i) {
if (!child.touch) {
c += count(child);
}
});
node.count = false;
return c;
}
// Check how many nodes can be reached from each given node
// by traversing the children property
graph.nodes.forEach(function(node, i) {
// reset touch on all nodes
graph.nodes.forEach(function(n, i) {
n.touch = false;
});
// get count
node.downstreamNodeCount = count(node);
});
// determine siblings for each links
graph.links.forEach(function(link, j) {
p = graph.nodes[link.source].children.length;
c = graph.nodes[link.target].parents.length;
link.siblings = Math.max(p, c);
});
}
// sets all links to pseudo that are connected to a pseudo node
function setPseudoLinks(graph) {
graph.links.forEach(function(link, j) {
if (graph.nodes[link.source].pseudo || graph.nodes[link.target].pseudo) {
link.pseudo = true;
} else {
link.pseudo = false;
}
});
}
function redraw() {
svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
}
function reset() {
// remove all;
svg.selectAll("g.node").remove();
svg.selectAll("rect").remove();
svg.selectAll("image").remove();
svg.selectAll("line").remove();
svg.selectAll("marker").remove();
svg.selectAll("text").remove();
svg.selectAll("g").remove();
svg.selectAll("defs").remove();
//start(__graph);
}
function _drawPathway(elem, graph, options) {
width = document.getElementById(elem).offsetWidth;
height = width * 0.75;
svg = d3.select("#" + elem)
.append("svg")
.attr("width", "100%")
.attr("height", height)
.style("border", border)
.attr("inline", "block")
.attr("margin", "auto")
.attr("pointer-events", "all")
.append('svg:g')
.call(d3.behavior.zoom().on("zoom", redraw))
.attr("preserveAspectRatio", "xMidYMid meet")
.append('svg:g');
// Background rectangle to enable zoom function
// even cursor is not on a node or path
svg.append('svg:rect')
.attr('width', 9 * width)
.attr('height', 9 * height)
.attr('fill', 'white')
.attr("x", -4 * width)
.attr("y", -4 * height)
.attr("opacity", 0.0);
setChildrenAndParents(graph)
setPseudoLinks(graph)
var maxDepths = getMaxNodeDepth(graph);
// if(options.treeLayout) {
//
// } else {
//
// }
force = d3.layout.force()
.charge(function(d) {
if (d.pseudo) {
return treeLayoutPseudoCharge;
} else {
return treeLayoutCharge;
}
})
.gravity(0)
.linkDistance(function(d) {
dist = Math.abs(d.target.treeLevel - d.source.treeLevel) * treeLayoutLinkDistance();
if (dist == 0) {
// nodes with depth in brackets: A (0) -> B (1) + C (2)
// then there is a pseudo node that is on the same level as B
dist = 0.5 * treeLayoutLinkDistance();
}
return dist;
})
.linkStrength(function(d) {
return strength = 1 / d.siblings;
})
.size([width, height]);
force.nodes(graph.nodes).links(graph.links).start();
}
function drawPathway(elem, options) {
d3.json("", function(error, graph) {
if (graph) {
_drawPathway(elem, graph, options);
} else {
throw new Error("Graph not defined: " + error);
}
});
}

View File

@ -0,0 +1,228 @@
/* global jQuery */
(function($){
'use strict';
$.fn.modalSteps = function(options){
var $modal = this;
var settings = $.extend({
btnCancelHtml: 'Cancel',
btnPreviousHtml: 'Previous',
btnNextHtml: 'Next',
btnLastStepHtml: 'Complete',
disableNextButton: false,
completeCallback: function(){},
callbacks: {}
}, options);
var validCallbacks = function(){
var everyStepCallback = settings.callbacks['*'];
if (everyStepCallback !== undefined && typeof(everyStepCallback) !== 'function'){
throw 'everyStepCallback is not a function! I need a function';
}
if (typeof(settings.completeCallback) !== 'function') {
throw 'completeCallback is not a function! I need a function';
}
for(var step in settings.callbacks){
if (settings.callbacks.hasOwnProperty(step)){
var callback = settings.callbacks[step];
if (step !== '*' && callback !== undefined && typeof(callback) !== 'function'){
throw 'Step ' + step + ' callback must be a function';
}
}
}
};
var executeCallback = function(callback){
if (callback !== undefined && typeof(callback) === 'function'){
callback();
return true;
}
return false;
};
$modal
.on('show.bs.modal', function(){
var $modalFooter = $modal.find('.modal-footer'),
$btnCancel = $modalFooter.find('.js-btn-step[data-orientation=cancel]'),
$btnPrevious = $modalFooter.find('.js-btn-step[data-orientation=previous]'),
$btnNext = $modalFooter.find('.js-btn-step[data-orientation=next]'),
everyStepCallback = settings.callbacks['*'],
stepCallback = settings.callbacks['1'],
actualStep,
$actualStep,
titleStep,
$titleStepSpan,
nextStep;
if (settings.disableNextButton){
$btnNext.attr('disabled', 'disabled');
}
$btnPrevious.attr('disabled', 'disabled');
validCallbacks();
executeCallback(everyStepCallback);
executeCallback(stepCallback);
// Setting buttons
$btnCancel.html(settings.btnCancelHtml);
$btnPrevious.html(settings.btnPreviousHtml);
$btnNext.html(settings.btnNextHtml);
$actualStep = $('<input>').attr({
'type': 'hidden',
'id': 'actual-step',
'value': '1',
});
$modal.find('#actual-step').remove();
$modal.append($actualStep);
actualStep = 1;
nextStep = actualStep + 1;
$modal.find('[data-step=' + actualStep + ']').removeClass('hide');
$btnNext.attr('data-step', nextStep);
titleStep = $modal.find('[data-step=' + actualStep + ']').data('title');
$titleStepSpan = $('<span>')
.addClass('label label-success')
.html(actualStep);
$modal
.find('.js-title-step')
.append($titleStepSpan)
.append(' ' + titleStep);
})
.on('hidden.bs.modal', function(){
var $actualStep = $modal.find('#actual-step'),
$btnNext = $modal.find('.js-btn-step[data-orientation=next]');
$modal
.find('[data-step]')
.not($modal.find('.js-btn-step'))
.addClass('hide');
$actualStep
.not($modal.find('.js-btn-step'))
.remove();
$btnNext
.attr('data-step', 1)
.html(settings.btnNextHtml);
$modal.find('.js-title-step').html('');
});
$modal.find('.js-btn-step').on('click', function(){
var $btn = $(this),
$actualStep = $modal.find('#actual-step'),
$btnPrevious = $modal.find('.js-btn-step[data-orientation=previous]'),
$btnNext = $modal.find('.js-btn-step[data-orientation=next]'),
$title = $modal.find('.js-title-step'),
orientation = $btn.data('orientation'),
actualStep = parseInt($actualStep.val()),
everyStepCallback = settings.callbacks['*'],
steps,
nextStep,
$nextStep,
newTitle;
steps = $modal.find('div[data-step]').length;
// Callback on Complete
if ($btn.attr('data-step') === 'complete'){
settings.completeCallback();
$modal.modal('hide');
return;
}
// Check the orientation to make logical operations with actualStep/nextStep
if (orientation === 'next'){
nextStep = actualStep + 1;
$btnPrevious.attr('data-step', actualStep);
$actualStep.val(nextStep);
} else if (orientation === 'previous'){
nextStep = actualStep - 1;
$btnNext.attr('data-step', actualStep);
$btnPrevious.attr('data-step', nextStep - 1);
$actualStep.val(actualStep - 1);
} else {
$modal.modal('hide');
return;
}
if (parseInt($actualStep.val()) === steps){
$btnNext
.attr('data-step', 'complete')
.html(settings.btnLastStepHtml);
} else {
$btnNext
.attr('data-step', nextStep)
.html(settings.btnNextHtml);
}
if (settings.disableNextButton){
$btnNext.attr('disabled', 'disabled');
}
// Hide and Show steps
$modal
.find('[data-step=' + actualStep + ']')
.not($modal.find('.js-btn-step'))
.addClass('hide');
$modal
.find('[data-step=' + nextStep + ']')
.not($modal.find('.js-btn-step'))
.removeClass('hide');
// Just a check for the class of previous button
if (parseInt($btnPrevious.attr('data-step')) > 0 ){
$btnPrevious.removeAttr('disabled');
} else {
$btnPrevious.attr('disabled', 'disabled');
}
if (orientation === 'previous'){
$btnNext.removeAttr('disabled');
}
// Get the next step
$nextStep = $modal.find('[data-step=' + nextStep + ']');
// Verify if we need to unlock continue btn of the next step
if ($nextStep.attr('data-unlock-continue')){
$btnNext.removeAttr('disabled');
}
// Set the title of step
newTitle = $nextStep.attr('data-title');
var $titleStepSpan = $('<span>')
.addClass('label label-success')
.html(nextStep);
$title
.html($titleStepSpan)
.append(' ' + newTitle);
var stepCallback = settings.callbacks[$actualStep.val()];
executeCallback(everyStepCallback);
executeCallback(stepCallback);
});
return this;
};
}(jQuery));

View File

@ -0,0 +1,57 @@
## Prerequisites
Stable [Node.js](https://nodejs.org) version
## Build instructions
npm install
npm start
For production build:
npm run build
You could also build only the style with command
npm run style
## Indigo Service
Ketcher uses Indigo Service for server operations.
You can use `--api-path` parameter to start with it:
npm start -- --api-path=<server-url>
For production build:
npm run build -- --api-path=<server-url>
You can find the instruction for service installation
[here](http://lifescience.opensource.epam.com/indigo/service/index.html).
## Tests instructions
You can start tests for input/output `.mol`-files and render.
npm test
Tests are started for all structures in `test/fixtures` directory.
To start the tests separately:
npm run test-io
npm run test-render
#### Parameters
You can use following parameters to start the tests:
- `--fixtures` - for the choice of a specific directory with molecules
- `--headless` - for start of the browser in headless mode
```
npm run test-render -- --fixtures=fixtures/super --headless
```
If you have added new structures for testing to the `test/fixtures` directory
you have to generate `svg` from them for correct render-test with:
npm run generate-svg

184
static/js/ketcher2/LICENSE Normal file
View File

@ -0,0 +1,184 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2017 EPAM Systems
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,5 @@
Ketcher version 1 was released under GNU Affero General Public License v3.0
Ketcher version 2 was re-licensed under Apache License, Version 2.
Current version is distributed by the terms of the Apache License, Version 2.
which is included in the file LICENSE, found at the root of the Ketcher source tree.

19
static/js/ketcher2/NOTICE Normal file
View File

@ -0,0 +1,19 @@
Ketcher
Copyright (C) 2017 EPAM Systems
This product includes software developed at EPAM Systems, Inc.
In addition, this product contains dependencies on files licensed under:
The FreeBSD Documentation License https://www.freebsd.org/copyright/freebsd-doc-license.html
The MIT License https://opensource.org/licenses/MIT
X11 License http://www.xfree86.org/3.3.6/COPYRIGHT2.html
Academic Free License https://opensource.org/licenses/AFL-3.0
Apache License, Version 1.0 http://www.apache.org/licenses/LICENSE-1.0
Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0
The 2-Clause BSD License https://opensource.org/licenses/BSD-2-Clause
The 3-Clause BSD License https://opensource.org/licenses/BSD-3-Clause
ISC License (ISC) https://opensource.org/licenses/ISC
GNU Lesser General Public License version 2.1 https://opensource.org/licenses/LGPL-2.1
The Mozilla Public License https://opensource.org/licenses/MPL-1.0
Public Domain https://wiki.creativecommons.org/wiki/Public_domain
Unlicense http://unlicense.org/

View File

@ -0,0 +1,35 @@
# EPAM Ketcher projects
Copyright (c) 2017 EPAM Systems, Inc
Ketcher is an open-source web-based chemical structure editor incorporating high performance, good portability, light weight, and ability to easily integrate into a custom web-application. Ketcher is designed for chemists, laboratory scientists and technicians who draw structures and reactions.
## KEY FEATURES
* Fast 2D structure representation that satisfies common chemical drawing standards
* 3D structure visualization
* Draw and edit structures using major tools: Atom Tool, Bond Tool, and Template Tool
* Template library (including custom and user's templates)
* Add atom and bond basic properties and query features, add aliases and Generic groups
* Select, modify, and erase connected and unconnected atoms and bonds using Selection Tool, or using Shift key
* Simple Structure Clean up Tool (checks bonds length, angles and spatial arrangement of atoms) and Advanced Structure Clean up Tool (+ stereochemistry checking and structure layout)
* Aromatize/De-aromatize Tool
* Calculate CIP Descriptors Tool
* Structure Check Tool
* MW and Structure Parameters Calculate Tool
* Stereochemistry support during editing, loading, and saving chemical structures
* Storing history of actions, with the ability to rollback to previous state
* Ability to load and save structures and reactions in MDL Molfile or RXN file format, InChI String, ChemAxon Extended SMILES, ChemAxon Extended CML file formats
* Easy to use R-Group and S-Group tools (Generic, Multiple group, SRU polymer, peratom, Data S-Group)
* Reaction Tool (reaction generating, manual and automatic atom-to-atom mapping)
* Flip/Rotate Tool
* Zoom in/out, hotkeys, cut/copy/paste
* OCR - ability to recognize structures at pictures (image files) and reproduce them
* Copy and paste between different chemical editors
* Settings support (Rendering, Displaying, Debugging)
* Use of SVG to achieve best quality in-browser chemical structure rendering
* Languages: JavaScript with third-party libraries
## Build instructions
Please read [DEVNOTES.md](DEVNOTES.md) for details.
## License
Please read [LICENSE](LICENSE) and [NOTICE](NOTICE) for details.

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1014 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1,570 @@
**Ketcher** is a tool to draw molecular structures and chemical
reactions.
# Ketcher Overview
**Ketcher** is a tool to draw molecular structures and chemical
reactions. Ketcher operates in two modes, the Server mode with most
functions available and the client mode with limited functions
available.
**Ketcher** consists of the following elements:
![](main.png "Main window")
**Note** : Depending on the screen size, some tools on the _Tool
palette_ can be displayed in expanded or collapsed forms.
Using the _Tool palette_, you can
* draw and edit a molecule or reaction by clicking on and dragging
atoms, bonds, and other elements provided with the buttons on the
_Atoms_ toolbar and _Tool palette_;
* delete any element of the drawing (atom or bond) by clicking on it
with the Erase tool;
* delete the entire molecule or its fragment by a lasso,
rectangular, or fragment selection with the Erase tool;
* draw special structures (see the following sections);
* select the entire molecule or its fragment in one of the following
ways (click on the button to see the list of available options):
* in the expanded form
![](expanded.png "Expanded tool")
* in the collapsed form
![](collapsed.png "Collapsed tool")
To select one atom or bond, click Lasso or Rectangle Selection tool,
and then click the atom or bond.
To select the entire structure:
* Select the Fragment Selection tool and then click the object.
* Select the Lasso or Rectangle Selection tool, and then drag the
mouse to select the object.
* `Ctrl-click` with the Lasso or Rectangle Selection tool.
To select multiple atoms, bonds, structures, or other objects, do one
of the following:
* `Shift-click` with the Lasso or Rectangle Selection tool selects
some (connected or not) atoms/bonds.
* With the Lasso or Rectangle Selection tool click and drag the
mouse around the atoms, bonds, or structures that you want to
select.
**Note** : `Ctrl+Shift-click` with the Lasso or Rectangle Selection tool
selects several structures.
You can use the buttons of the _Main_ toolbar:
![](toolbar.png "Tolbar")
* **Clear Canvas** (1) button to start drawing a new molecule; this
command clears the drawing area;
* **Open…** (2) and **Save As…** (3) buttons to import a molecule
from a molecular file or save it to a supported molecular file
format;
* **Undo** / **Redo** (4), **Cut** (5), **Copy** (6), **Paste** (7),
**Zoom In** / **Out** (8), and **Scaling** (9) buttons to perform
the corresponding actions;
* **Layout** button (10) to change the position of the structure to
work with it with the most convenience;
* **Clean Up** button (11) to improve the appearance of the
structure by assigning them uniform bond lengths and angles.
* **Aromatize** / **Dearomatize** buttons (12) to mark aromatic
structures (to convert a structure to the Aromatic or Kekule
presentation);
* **Calculate CIP** button (13) to determine R/S and E/Z
configurations;
* **Check Structure** button (14) to check the following properties
of the structure:
![](check.png "Structure Ckeck")
* **Calculated Values** button (15) to display some properties of
the structure:
![](analyse.png "Calculated Values")
* **Recognize Molecule** button (16) to recognize a structure in the
image file and load it to the canvas;
* **3D Viewer** button (17) to open the structure in the
three-dimensional Viewer;
* **Settings** button (18) to make some settings for molecular
files:
![](settings.png "Settings")
* **Help** button (19) to view Help;
* **About** button (20) to display version and copyright information
of the program.
**Note** : **Layout,** **Clean Up,** **Aromatize** / **Dearomatize,**
**Calculate CIP,** **Check Structure,** **Calculated Values,**
**Recognize Molecule** and **3D View** buttons are active only in the
Server mode.
# 3D Viewer
The structure appears in a modal window after clicking on the **3D
Viewer** button:
![](miew.png "3D Viewer")
You can perform the following actions:
* Rotate the structure holding the left mouse button;
* Zoom In/Out the structure;
Ketcher Settings allow to change the appearance of the structure and background coloring.
"Lines" drawing method, "Bright" atom name coloring
method and "Light" background coloring are default.
# Drawing Atoms
To draw/edit atoms you can:
* select an atom in the Atoms toolbar and click inside the drawing
area;
* if the desired atom is absent in the toolbar, click on
the ![](periodic-table.png) button to invoke the Periodic Table and
click on the desired atom (available options: _Single_ selection
of a single atom, _List_ choose an atom from the list of selected
options (To allow one atom from a list of atoms of your choice at
that position), _Not List_ - exclude any atom on your list at that
position).
![](periodic-dialog.png "Periodic Table")
* add an atom to the existing molecule by selecting an atom in the
_Atoms_ toolbar, clicking on an atom in the molecule, and dragging
the cursor; the atom will be added with a single bond; vacant
valences will be filled with the corresponding number of hydrogen
atoms;
* change an atom by selecting an atom in the _Atoms_ toolbar and
clicking on the atom to be changed; in the case a wrong valence thus
appears the atom will be underlined in red;
* change an atom by clicking on an existing atom with the
_Selection_ tool and waiting for a couple of seconds for the text
box to appear; type another atom symbol in the text box:
![](inline-edit.png "Change Atom")
* change the charge of an atom by selecting the Charge Plus or
Charge Minus tool and clicking consecutively on an atom to
increase/decrease its charge
![](charge.png "Ions")
* change an atom or its properties by double-clicking on the atom to
invoke the Atom Properties dialog (the dialog also provides atom
query features):
![](atom-dialog.png "Atom Properties")
* click on the Periodic Table button, open the Extended table and
select a corresponding Generic group or Special Node:
![](periodic-dialog-ext.png "Generic Groups")
# Drawing Bonds
To draw/edit bonds you can:
* Click an arrow on the Bond tool ![](bond.png) in the Tools palette
to open the drop-down list with the following bond types:
![](bonds.png)
For the full screen format, the Bond tool from the Tools palette
splits into three: _Single Bond,__Single Up Bond,_ and _Any
Bond_,which include the corresponding bond types:
![](bond-types.png)
* select a bond type from the drop down list and click inside the
drawing area; a bond of the selected type will be drawn;
* click on an atom in the molecule; a bond of the selected type will
be added to the atom at the angle of 120 degrees;
* add a bond to the existing molecule by clicking on an atom in the
molecule and dragging the cursor; in this case you can set the angle
manually;
* change the bond type by clicking on it;
* use the Chain Tool ![](chain.png) to draw consecutive single
bonds;
* change a bond or its properties by double-clicking on the bond to
invoke the Bond Properties dialog:
![](bond-dialog.png "Bond Properties")
* clicking on a drawn stereo bond changes its direction.
* clicking with the Single Bond tool or Chain tool switches the bond type
cyclically: Single-Double-Triple-Single.
# Drawing R-Groups
Use the _R-Group_ toolbox ![](rgroup.png) to draw R-groups in Markush
structures:
![](rgroup-types.png)
Selecting the _R-Group_ _Label_ Tool and clicking on an atom in the
structure invokes the dialog to select the R-Group label for a current
atom position in the structure:
![](rgroup-dialog.png)
Selecting the R-Group label and clicking **OK** converts the structure
into a Markush structure with the selected R-Group label:
![](rgroup-example1.png)
**Note** : You can choose several R-Group labels simultaneously:
![](rgroup-example2.png)
Particular chemical fragments that may be substituted for a given
R-Group form a set of R-Group members. R-Group members can be any
structural fragment, including functional groups and single atoms or
atom lists.
To create a set of R-Group members:
1. Draw a structure to become an R-Group member.
2. Select the structure using the _R-Group Fragment Tool_ to invoke
the R-Group dialog; in this dialog select the label of the
R-Group to assign the fragment to.
3. Click on **OK** to convert the structure into an R-Group member.
An R-Group attachment point is the atom in an R-Group member fragment
that attaches the fragment to the initial Markush structure.
Selecting the _Attachment Point Tool_ and clicking on an atom in the
R-Group fragment converts this atom into an attachment point. If the
R-Group contains more than one attachment point, you can specify one
of them as primary and the other as secondary. You can select between
either the primary or secondary attachment point using the dialog that
appears after clicking on the atom:
![](attpoints-dialog.png)
If there are two attachment points on an R-Group member, there must be
two corresponding attachments (bonds) to the R-Group atom that has the
same R-Group label. Clicking on **OK** in the above dialog creates the
attachment point.
Schematically, the entire process of the R-Group member creation can
be presented as:
![](rgroup-example3.png)
![](rgroup-example4.png)
# R-Group Logic
**Ketcher** enables one to add logic when using R-Groups. To access
the R-Group logic:
1. Create an R-Group member fragment as described above.
2. Move the cursor over the entire fragment for the green frame to
appear, then click inside the fragment. The following dialog
appears:
![](rlogic-dialog.png)
3. Specify **Occurrence** to define how many of an R-Group
occurs. If an R-Group atom appears several times in the initial
structure, you will specify **Occurrence**"&gt;n", n
being the number of occurrences; if it appears once, you see
"R1 > 0".
4. Specify H at **unoccupied** R-Group sites ( **RestH** ): check or
clear the checkbox.
5. Specify the logical **Condition**. Use the R-Group condition **If
R(i) Then** to specify whether the presence of an R-Group is
dependent on the presence of another R-Group.
# Marking S-Groups
To mark S-Groups, use the _S-Group tool_ ![](sgroup.png) and the
following dialog that appears after selecting a fragment with this
tool:
![](sgroup-dialog.png "S-Group Dialog")
Available S-Group types:
_Generic_
Generic is a pair of brackets without any labels.
_Multiple group_
A Multiple group indicates a number of replications of a fragment or a part of a
structure in contracted form.
_SRU Polymer_
The Structural Repeating Unit (SRU) brackets enclose the structural
repeating of a polymer. You have three available patterns:
head-to-tail (the default), head-to-head, and either/unknown.
_Superatom_
An abbreviated structure (abbreviation) is all or part of a structure
(molecule or reaction component) that has been abbreviated to a text
label. Structures that you abbreviate keep their chemical
significance, but their underlying structure is hidden. The current
version can&#39;t display contracted structures but correctly
saves/reads them into/from files.
# Data S-Groups
The _Data S-Groups Tool_ ![](sdata.png) is a separate tool for
comfortable use with the accustomed set of descriptors (like Attached
Data in **Marvin** Editor).
You can attach data to an atom, a fragment, a single bond, or a
group. The defined set of _Names_ and _Values_ is introduced for each
type of selected elements:
![](sdata-dialog.png)
* Select the appropriate S-Group Field Name.
* Select or type the appropriate Field Value.
* Labels can be specified as Absolute, Relative or Attached.
# Changing Structure Display
Use the _Flip/Rotate_ tool ![](transform.png) to change the structure
display:
![](transform-types.png)
For the full screen format, the _Flip/Rotate_ tool is split into
separate buttons:
![](rotate.png)
_Rotate Tool_
This tool allows rotating objects.
* If some objects are selected, the tool rotates the selected objects.
* If no objects are selected, or all objects are selected, the tool rotates the whole canvas
* The default rotation step is 15 degrees.
* Press and hold the Ctrl key for more gradual continuous rotation with 1 degree rotation step
Select any bond on the structure and click Alt+H to rotate the structure so that the selected bond is placed horizontally.
Select any bond on the structure and click Alt+V to rotate the structure so that the selected bond is placed vertically.
_Flip Tool_
This tool flips the objects horizontally or vertically.
* If some objects are selected, the Horizontal Flip tool (or Alt+H) flips the selected objects horizontally
* If no objects are selected, or all objects are selected, the Horizontal Flip tool (or Alt+H) flips each structure horizontally
* If some objects are selected, the Vertical Flip tool (or Alt+V) flips the selected objects vertically
* If no objects are selected, or all objects are selected, the Vertical Flip tool (or Alt+V) flips each structure vertically
# Drawing Reactions
To draw/edit reactions you can
* draw reagents and products as described above;
* use options of the _Reaction Arrow Tool_ ![](reaction.png) to draw an
arrow and pluses in the reaction equation and map same atoms in
reagents and products.
![](reaction-types.png)
**Note** : Reaction Auto-Mapping Tool is available only in the Server
mode.
# Templates toolbar
You can add templates (rings or other predefined structures) to the
structure using the _Templates_ toolbar together with the _Custom
Templates_ button located at the bottom:
![](template.png)
To add a ring to the molecule, select a ring from the toolbar and
click inside the drawing area, or click on an atom or a bond in the
molecule.
Rules of using templates:
* Selecting a template and clicking on an atom in the existing
structure adds the template to the structure connected with a single
bond:
![](template-example1.png)
* Selecting a template and dragging the cursor from an atom in the
existing structure adds the template directly to this atom resulting
in the fused structure:
![](template-example2.png)
* Dragging the cursor from an atom in the existing structure results
in the single bond attachment if the cursor is dragged to more than
the bond length; otherwise the fused structure is drawn.
* Selecting a template and clicking on a bond in the existing
structure created a bond-to-bond fused structure:
![](template-example3.png)
* The bond in the initial structure is replaced with the bond in the
template.
* This procedure doesn&#39;t change the length of the bond in the
initial structure.
* Dragging the cursor relative to the initial bond applies the
template at the corresponding side of the bond.
**Note** : The added template will be fused by the default attachment
atom or bond preset in the program.
**Note** : User is able to define the attachment atom and bond by clicking
the Edit button for template structure.
The _Custom Templates_ button ![](template-lib.png)invokes the scrolling
list of templates available in the program; both built-in and created
by user:
![](template-dialog.png)
To create a user template:
* draw a structure.
* click the Save as button.
* click the Save to Templates button.
* enter a name and define the attachment atom and bond.
# Working with Files
Ketcher supports the following molecular formats that can be entered
either manually or from files:
* MDL Molfile or RXN file;
* Daylight SMILES (Server mode only);
* Daylight SMARTS (Server mode only);
* InChi string (Server mode only);
* CML file (Server mode only).
You can use the **Open…** and **Save As…** buttons of the _Main_
toolbar to import a molecule from a molecular file or save it to a
supported molecular file format. The _Open Structure_ dialog enables
one to either browse for a file (Server mode) or manually input, e.g.,
the Molfile ctable for the molecule to be imported:
![](open.png)
The _Save Structure_ dialog enables one to save the molecular file:
![](save.png)
**Note** : In the standalone version only mol/rxn are supported for
Open and mol/rxn/SMILES for Save.
# Hotkeys
You can use keyboard hotkeys (including Numeric keypad) for some
features/commands of the Editor. To display the hotkeys just place the
cursor over a toolbar button. If a hotkey is available for the button,
it will appear in brackets after the description of the button.
| Key | Action |
| --- | --- |
| `Esc` | Switching between the Lasso/Rectangle/Fragment Selection tools |
| `Del` | Delete the selected objects |
| `0` | Draw Any bond. |
| `1` | Single / Single Up / Single Down / Single Up/Down bond. Consecutive pressing switches between these types. |
| `2` | Double / Double Cis/Trans bond |
| `3` | Draw a triple bond. |
| `4` | Draw an aromatic bond. |
| `5` | Charge Plus/Charge Minus |
| `A` | Draw any atom |
| `H` | Draw a hydrogen |
| `C` | Draw a carbon |
| `N` | Draw a nitrogen |
| `O` | Draw an oxygen |
| `S` | Draw a sulfur |
| `F` | Draw a fluorine |
| `P` | Draw a phosphorus |
| `I` | Draw an iodine |
| `T` | Basic templates. Consecutive pressing switches between different templates |
| `Shift+t` | Open template library |
| `Alt+r` | Rotate tool |
| `Alt+v` | Flip vertically |
| `Alt+h` | Flip horizontally |
| `Ctrl+g` | S-Group tool / Data S-Group tool |
| `Ctrl+d` | Align and select all S-Group data
| `Ctrl+r` | Switching between the R-Group Label Tool/R-Group Fragment Tool/Attachment Point Tool |
| `Ctrl+Shift+r` | R-Group Fragment Tool |
| `Ctrl+Del` | Clear canvas |
| `Ctrl+o` | Open |
| `Ctrl+s` | Save As |
| `Ctrl+z` | Undo |
| `Ctrl+Shift+z` | Redo |
| `Ctrl+x` | Cut selected objects |
| `Ctrl+c` | Copy selected objects |
| `Ctrl+v` | Paste selected objects |
| `+` | Zoom In |
| `-` | Zoom Out |
| `Ctrl+l` | Layout |
| `Ctrl+Shift+l` | Clean Up |
| `Ctrl+p` | Calculate CIP |
| `?` | Help |
**Note** : Please, use `Ctrl+V` to paste the selected object in
Google Chrome and Mozilla Firefox browsers.
**Note 2** : Probably, you have forbidden access to the local storage.
If you are using IE10 or IE11 and didn't forbid access to local storage
intentionally, you can pay attention here: https://stackoverflow.com/a/20848924

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 887 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Some files were not shown because too many files have changed in this diff Show More