diff --git a/.gitignore b/.gitignore
index 2e8693ae7ebaf8b45dff4715595586d1e30d1579..ba0ae100d1e02b07aecb5bd25e0a2df7f6ae594b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,8 @@
 venv
 ippisite/docs/build
 ippisite/docs/source/_autosummary
+ippisite/docs/source/ippidb
+ippisite/docs/source/ippisite
 /ippidb_backend/PrepareFingerPrints/nbproject/private/
 /ippidb_backend/PrepareFingerPrints/build/
 /ippidb_backend/PrepareFingerPrints/dist/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 34b597969b7b0ac464bc046798e0438184c6e44f..82872695029da72e33d139c968035b242c04faaf 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -62,6 +62,8 @@ test-centos7:
   - coverage html
   - pip3.6 install sphinx sphinx-argparse sphinxcontrib.bibtex sphinx_rtd_theme
   - cd docs
+  - sphinx-apidoc -o source/ippidb/ ../ippidb
+  - sphinx-apidoc -o source/ippisite/ ../ippisite
   - make html
   artifacts:
     paths:
diff --git a/ansible/deploy.yaml b/ansible/deploy.yaml
index 346a6d21bfc032a9ef37451742a3e7b04ea133f9..0ad054c5fc31acc086b9fcd894894fc766cb7807 100644
--- a/ansible/deploy.yaml
+++ b/ansible/deploy.yaml
@@ -12,7 +12,11 @@
     - name: Create celery user
       user: name=celery-{{ http_port }} groups={{ deploy_user_name }} append=yes state=present createhome=yes 
       become: true
-      register: newuser  
+      register: newuser
+    - name: Set the path to {{ deploy_user_name }} home to executable by group so that celery user can cd into it
+      file:
+        path: /home/{{ deploy_user_name }}
+        mode: g+x
     # Install basic non-virtualenv requirements
     #
     - name: install git
diff --git a/ippisite/ippidb/__init__.py b/ippisite/ippidb/__init__.py
index 97a23c61955f8cfbf3bd596b47b1399bf56e1fd8..dff61f605675a68647da71213dc19e685c56cb94 100644
--- a/ippisite/ippidb/__init__.py
+++ b/ippisite/ippidb/__init__.py
@@ -9,6 +9,7 @@ This is the main ippidb module
     forms
     gx
     models
+    tasks
     tests
     urls
     utils
diff --git a/ippisite/ippidb/forms.py b/ippisite/ippidb/forms.py
index bc2fcefa07673239eff1a0eb3e9f356cc262ae77..9eb60d3529aa9b2e68f2b69b97f578779210840b 100644
--- a/ippisite/ippidb/forms.py
+++ b/ippisite/ippidb/forms.py
@@ -1403,7 +1403,7 @@ class CompoundPKResultForm(ModelForm):
         exclude = ("compound", "test_pk_description")
         widgets = {
             "tolerated": forms.Select(
-                choices=((True, "True"), (False, "False"), (None, "Null"))
+                choices=((True, "True"), (False, "False"), (None, "Unknown"))
             )
         }
 
@@ -1716,15 +1716,15 @@ class TestsForm(forms.Form):
     pkRes_tolerated = forms.ChoiceField(
         label="Tolerated", widget=forms.TextInput(), required=False
     )
-    pkRes_auc = forms.IntegerField(label="AUC", initial=1, required=False)
-    pkRes_clearance = forms.DecimalField(label="Clearance", required=False)
-    pkRes_cmax = forms.DecimalField(label="C max", required=False)
+    pkRes_auc_av = forms.BooleanField(label="AUC available", initial=1, required=False)
+    pkRes_clearance_av = forms.BooleanField(label="Clearance available", required=False)
+    pkRes_cmax_av = forms.BooleanField(label="Maximal concentration available", required=False)
     pkRes_oral_bioavailability = forms.IntegerField(
         label="Oral bio-availability", initial=1, required=False
     )
     pkRes_t_demi = forms.IntegerField(label="T 1/2", initial=1, required=False)
     pkRes_t_max = forms.IntegerField(label="T max", initial=1, required=False)
-    pkRes_voldistribution = forms.DecimalField(label="Voldistribution", required=False)
+    pkRes_voldistribution_av = forms.BooleanField(label="Volume distribution (Vd) available", required=False)
 
 
 UNIT_CONCENTRATION = (("micro", "µM"), ("nano", "nM"), ("pico", "pM"))
diff --git a/ippisite/ippidb/migrations/0058_auto_20200430_1915.py b/ippisite/ippidb/migrations/0058_auto_20200430_1915.py
new file mode 100644
index 0000000000000000000000000000000000000000..bab8608b9f863131d362a79e311ea42a92215c47
--- /dev/null
+++ b/ippisite/ippidb/migrations/0058_auto_20200430_1915.py
@@ -0,0 +1,47 @@
+# Generated by Django 2.2.1 on 2020-04-30 19:15
+
+from django.db import migrations, models
+
+def fill_pkresult_avail_values(apps, schema_editor):
+    CompoundPKResult = apps.get_model("ippidb", "CompoundPKResult")
+    for cpr in CompoundPKResult.objects.all():
+        cpr.auc_av = cpr.auc is not None
+        cpr.clearance_av = cpr.clearance is not None
+        cpr.c_max_av = cpr.c_max is not None
+        cpr.voldistribution_av = cpr.voldistribution is not None
+        cpr.save()
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('ippidb', '0057_auto_20200430_0740'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='compoundpkresult',
+            name='auc_av',
+            field=models.BooleanField(default=False, verbose_name='Area under curve available'),
+            preserve_default=False,
+        ),
+        migrations.AddField(
+            model_name='compoundpkresult',
+            name='c_max_av',
+            field=models.BooleanField(default=False, verbose_name='Maximal concentration available'),
+            preserve_default=False,
+        ),
+        migrations.AddField(
+            model_name='compoundpkresult',
+            name='clearance_av',
+            field=models.BooleanField(default=False, verbose_name='Clearance available'),
+            preserve_default=False,
+        ),
+        migrations.AddField(
+            model_name='compoundpkresult',
+            name='voldistribution_av',
+            field=models.BooleanField(default=False, verbose_name='Volume distribution (Vd)'),
+            preserve_default=False,
+        ),
+        migrations.RunPython(fill_pkresult_avail_values),
+    ]
diff --git a/ippisite/ippidb/migrations/0059_auto_20200430_1926.py b/ippisite/ippidb/migrations/0059_auto_20200430_1926.py
new file mode 100644
index 0000000000000000000000000000000000000000..7b4d5920b8a85d6a4c79caa521dac749d83cb540
--- /dev/null
+++ b/ippisite/ippidb/migrations/0059_auto_20200430_1926.py
@@ -0,0 +1,29 @@
+# Generated by Django 2.2.1 on 2020-04-30 19:26
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('ippidb', '0058_auto_20200430_1915'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='compoundpkresult',
+            name='auc',
+        ),
+        migrations.RemoveField(
+            model_name='compoundpkresult',
+            name='c_max',
+        ),
+        migrations.RemoveField(
+            model_name='compoundpkresult',
+            name='clearance',
+        ),
+        migrations.RemoveField(
+            model_name='compoundpkresult',
+            name='voldistribution',
+        ),
+    ]
diff --git a/ippisite/ippidb/models.py b/ippisite/ippidb/models.py
index d21088023c367e939bdbf670a923358ed8794d45..55710940e48825b799daadcc87fc1e0de41f06b8 100644
--- a/ippisite/ippidb/models.py
+++ b/ippisite/ippidb/models.py
@@ -35,8 +35,8 @@ from .ws import (
 
 class AutoFillableModel(models.Model):
     """
-    AutoFillableModel makes it possible to automatically fill model fields from
-    external sources in the autofill() method
+    AutoFillableModel abstract model to enable automated model fields
+    filling from external sources in the autofill() method.
     The save method allows to either include autofill or not. in autofill kwarg
     is set to True, save() will first call autofill(), otherwise it won't
     """
@@ -60,19 +60,21 @@ class AutoFillableModel(models.Model):
 
     def autofill_post_save(self):
         """
-        method called after the save is done, usefull for setting m2m
-        relations
-        :return:
+        Method called automatically after the save is done,
+        usefull for setting m2m relations
         """
         pass
 
     def is_autofill_done(self):
+        """
+        test whether or not the model has been already autofilled
+        """
         return True
 
 
 class Bibliography(AutoFillableModel):
     """
-    Bibliography references
+    Bibliography reference
     (publications or patents)
     """
 
@@ -127,10 +129,15 @@ class Bibliography(AutoFillableModel):
         return self.source == "PM" or self.source == "DO"
 
     def get_external_url(self):
+        """
+        Get URL to the publication
+        """
         if self.source == "PM":
-            return "https://www.ncbi.nlm.nih.gov/pubmed/" + str(self.id_source)
-        if self.source == "DO":
-            return "https://doi.org/" + str(self.id_source)
+            return f"https://www.ncbi.nlm.nih.gov/pubmed/{self.id_source}"
+        elif self.source == "DO":
+            return f"https://doi.org/{self.id_source}"
+        elif self.source == "PT":
+            return f"https://patentscope.wipo.int/search/en/detail.jsf?docId={self.id_source}"
 
     @staticmethod
     def validate_source_id(id_source, source):
@@ -536,6 +543,11 @@ class PpiComplex(models.Model):
 
 
 class CompoundsManager(models.Manager):
+    """
+    Model manager for the `Compound` class
+    provides selections to `validated` or `user-visible` compounds
+    """
+
     def for_user(self, current_user):
         """
         Get compounds visible to a given user
@@ -869,7 +881,9 @@ class Compound(AutoFillableModel):
         # ]
 
     def compute_drugbank_compound_similarity(self):
-        """ compute Tanimoto similarity to existing DrugBank compounds """
+        """
+        Compute Tanimoto similarity to existing DrugBank compounds
+        """
         self.save()
         # fingerprints to compute drugbank similarities are in settings module, default FP2
         fingerprinter = FingerPrinter(getattr(settings, "DRUGBANK_FINGERPRINTS", "FP2"))
@@ -894,14 +908,14 @@ class Compound(AutoFillableModel):
     @property
     def biblio_refs(self):
         """
-        return all RefCompoundBiblio related to this compound
+        Return all RefCompoundBiblio related to this compound
         """
         return RefCompoundBiblio.objects.filter(compound=self)
 
     @property
     def pfam_ids(self):
         """
-        return all PFAM ids for the domain of the proteins of the bound
+        Return all PFAM ids for the domain of the proteins of the bound
         complexes in the PPIs this compound has an action on
         """
         pfam_ids = set()
@@ -913,6 +927,9 @@ class Compound(AutoFillableModel):
 
     @property
     def best_pXC50_compound_activity_result(self):
+        """
+        Return the best pXC50 activity
+        """
         best_pXC50_activity = self.best_activity
         if best_pXC50_activity is None:
             return None
@@ -925,7 +942,7 @@ class Compound(AutoFillableModel):
     @property
     def bioch_tests_count(self):
         """
-        return the number of associated biochemical tests
+        Return the number of associated biochemical tests
         """
         return (
             self.compoundactivityresult_set.all()
@@ -936,7 +953,7 @@ class Compound(AutoFillableModel):
     @property
     def cell_tests_count(self):
         """
-        return the number of associated cell tests
+        Return the number of associated cell tests
         """
         return (
             self.compoundactivityresult_set.all()
@@ -946,9 +963,16 @@ class Compound(AutoFillableModel):
 
     @property
     def sorted_similar_drugbank_compounds(self):
+        """
+        Return the similar Drugbank compounds,
+        sorted by decreasing similarity
+        """
         return self.drugbankcompoundtanimoto_set.order_by("-tanimoto")
 
     def is_validated(self):
+        """
+        Return the compound validation status
+        """
         # if compound is not linked to any CompoundAction this is
         # because it was dereferenced because of duplication by
         # `replace_compound_references`
@@ -962,21 +986,38 @@ class Compound(AutoFillableModel):
     is_validated.boolean = True
 
     def autofill(self):
+        """
+        Finalize the computation of the Compound
+        by computing InChi, InChiKey and Drugbank similarity
+        """
         # compute InChi and InChiKey
         self.inchi = smi2inchi(self.canonical_smile)
         self.inchikey = smi2inchikey(self.canonical_smile)
         self.compute_drugbank_compound_similarity()
 
     def compute_fsp3(self):
+        """
+        Compute FSP3 from CSP3 and number of carbons
+        """
         self.fsp3 = self.nb_csp3 / self.nb_c
 
     def __str__(self):
+        """
+        String representation
+        """
         return "Compound #{}".format(self.id)
 
     def get_absolute_url(self):
+        """
+        Get absolute URL to the Compound page
+        """
         return reverse("compound_card", kwargs={"pk": self.pk})
 
     def clean(self):
+        """
+        Perform additional checks:
+        - check common name for the Compound is unique
+        """
         if (
             self.common_name is not None
             and self.common_name != ""
@@ -987,11 +1028,14 @@ class Compound(AutoFillableModel):
             self.add_error("common_name", "A compound with this name already exists")
 
     def get_jobs(self):
+        """
+        Retrieve the jobs for the compound
+        """
         return CompoundJob.objects.filter(compound=self)
 
     def replace_compound_references(self, replacing_compound):
         """
-        replace the references to a given compound in the data with
+        Replace the references to a given compound in the data with
         references to another new compound. used to deal with
         duplicates in the database
         """
@@ -1262,29 +1306,15 @@ class CompoundPKResult(models.Model):
     compound = models.ForeignKey(Compound, models.CASCADE)
     test_pk_description = models.ForeignKey(TestPKDescription, models.CASCADE)
     tolerated = models.NullBooleanField("Tolerated", null=True)
-    auc = models.IntegerField("Area under curve (ng.mL-1.hr)", blank=True, null=True)
-    clearance = models.DecimalField(
-        "Clearance (mL/hr)", max_digits=7, decimal_places=3, blank=True, null=True
-    )
-    c_max = models.DecimalField(
-        "Maximal concentration (ng/mL)",
-        max_digits=7,
-        decimal_places=3,
-        blank=True,
-        null=True,
-    )
     oral_bioavailability = models.IntegerField(
         "Oral Bioavailability (%F)", blank=True, null=True
     )
     t_demi = models.IntegerField("t½ (mn)", blank=True, null=True)
     t_max = models.IntegerField("tmax (mn)", blank=True, null=True)
-    voldistribution = models.DecimalField(
-        "Volume distribution (Vd)",
-        max_digits=5,
-        decimal_places=2,
-        blank=True,
-        null=True,
-    )
+    auc_av = models.BooleanField("Area under curve available")
+    clearance_av = models.BooleanField("Clearance available")
+    c_max_av = models.BooleanField("Maximal concentration available")
+    voldistribution_av = models.BooleanField("Volume distribution (Vd) available")
 
     class Meta:
         unique_together = (("compound", "test_pk_description"),)
diff --git a/ippisite/ippidb/static/js/ippidb-slides.js b/ippisite/ippidb/static/js/ippidb-slides.js
new file mode 100644
index 0000000000000000000000000000000000000000..02572fa3458f28e14fe44dc4c3c2a343ff54fe91
--- /dev/null
+++ b/ippisite/ippidb/static/js/ippidb-slides.js
@@ -0,0 +1,64 @@
+var slideIndex = 1;
+
+var myTimer;
+
+var slideshowContainer;
+
+window.addEventListener("load", function () {
+    showSlides(slideIndex);
+    myTimer = setInterval(function () { plusSlides(1) }, 5000);
+
+    //COMMENT OUT THE LINE BELOW TO KEEP ARROWS PART OF MOUSEENTER PAUSE/RESUME
+    slideshowContainer = document.getElementsByClassName('slideshow-inner')[0];
+
+    //UNCOMMENT OUT THE LINE BELOW TO KEEP ARROWS PART OF MOUSEENTER PAUSE/RESUME
+    // slideshowContainer = document.getElementsByClassName('slideshow-container')[0];
+
+    slideshowContainer.addEventListener('mouseenter', pause)
+    slideshowContainer.addEventListener('mouseleave', resume)
+})
+
+// NEXT AND PREVIOUS CONTROL
+function plusSlides(n) {
+    clearInterval(myTimer);
+    if (n < 0) {
+        showSlides(slideIndex -= 1);
+    } else {
+        showSlides(slideIndex += 1);
+    }
+
+    //COMMENT OUT THE LINES BELOW TO KEEP ARROWS PART OF MOUSEENTER PAUSE/RESUME
+
+    if (n === -1) {
+        myTimer = setInterval(function () { plusSlides(n + 2) }, 5000);
+    } else {
+        myTimer = setInterval(function () { plusSlides(n + 1) }, 5000);
+    }
+}
+
+//Controls the current slide and resets interval if needed
+function currentSlide(n) {
+    clearInterval(myTimer);
+    myTimer = setInterval(function () { plusSlides(n + 1) }, 5000);
+    showSlides(slideIndex = n);
+}
+
+function showSlides(n) {
+    var i;
+    var slides = document.getElementsByClassName("mySlides");
+    if (n > slides.length) { slideIndex = 1 }
+    if (n < 1) { slideIndex = slides.length }
+    for (i = 0; i < slides.length; i++) {
+        slides[i].style.display = "none";
+    }
+    slides[slideIndex - 1].style.display = "block";
+}
+
+pause = () => {
+    clearInterval(myTimer);
+}
+
+resume = () => {
+    clearInterval(myTimer);
+    myTimer = setInterval(function () { plusSlides(slideIndex) }, 5000);
+}
\ No newline at end of file
diff --git a/ippisite/ippidb/static/js/ippidb.js b/ippisite/ippidb/static/js/ippidb.js
index 2b443c06002198d9b502192811649ee43e1c7d9e..e45274297d1d1f185e24d3072a34b26bdcc8a3b6 100644
--- a/ippisite/ippidb/static/js/ippidb.js
+++ b/ippisite/ippidb/static/js/ippidb.js
@@ -226,71 +226,6 @@ $(document).ready(function () {
     });
 });
 
-var slideIndex = 1;
-
-var myTimer;
-
-var slideshowContainer;
-
-window.addEventListener("load", function () {
-    showSlides(slideIndex);
-    myTimer = setInterval(function () { plusSlides(1) }, 5000);
-
-    //COMMENT OUT THE LINE BELOW TO KEEP ARROWS PART OF MOUSEENTER PAUSE/RESUME
-    slideshowContainer = document.getElementsByClassName('slideshow-inner')[0];
-
-    //UNCOMMENT OUT THE LINE BELOW TO KEEP ARROWS PART OF MOUSEENTER PAUSE/RESUME
-    // slideshowContainer = document.getElementsByClassName('slideshow-container')[0];
-
-    slideshowContainer.addEventListener('mouseenter', pause)
-    slideshowContainer.addEventListener('mouseleave', resume)
-})
-
-// NEXT AND PREVIOUS CONTROL
-function plusSlides(n) {
-    clearInterval(myTimer);
-    if (n < 0) {
-        showSlides(slideIndex -= 1);
-    } else {
-        showSlides(slideIndex += 1);
-    }
-
-    //COMMENT OUT THE LINES BELOW TO KEEP ARROWS PART OF MOUSEENTER PAUSE/RESUME
-
-    if (n === -1) {
-        myTimer = setInterval(function () { plusSlides(n + 2) }, 5000);
-    } else {
-        myTimer = setInterval(function () { plusSlides(n + 1) }, 5000);
-    }
-}
-
-//Controls the current slide and resets interval if needed
-function currentSlide(n) {
-    clearInterval(myTimer);
-    myTimer = setInterval(function () { plusSlides(n + 1) }, 5000);
-    showSlides(slideIndex = n);
-}
-
-function showSlides(n) {
-    var i;
-    var slides = document.getElementsByClassName("mySlides");
-    if (n > slides.length) { slideIndex = 1 }
-    if (n < 1) { slideIndex = slides.length }
-    for (i = 0; i < slides.length; i++) {
-        slides[i].style.display = "none";
-    }
-    slides[slideIndex - 1].style.display = "block";
-}
-
-pause = () => {
-    clearInterval(myTimer);
-}
-
-resume = () => {
-    clearInterval(myTimer);
-    myTimer = setInterval(function () { plusSlides(slideIndex) }, 5000);
-}
-
 var multiselectTypeAhead = function (idSearch, selection, onSelect) {
     var bh = new Bloodhound({
         datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
diff --git a/ippisite/ippidb/tasks.py b/ippisite/ippidb/tasks.py
index 3d2f1dbb46ddc117135043ed5c897832bd4ff411..0e090768cc7c960e5782a5be93e93d383548c9c2 100644
--- a/ippisite/ippidb/tasks.py
+++ b/ippisite/ippidb/tasks.py
@@ -4,10 +4,10 @@ import tempfile
 import io
 import base64
 import itertools
-import time
-import random
+from typing import Callable, List
 
 from django.db import Error
+from django.db.models import QuerySet
 from celery import task, states, chain, group
 from ippisite.decorator import MonitorTask
 import matplotlib.pyplot as plt
@@ -40,14 +40,36 @@ from .gx import GalaxyCompoundPropertiesRunner
 plt.switch_backend("Agg")
 
 
-def dec(decimal_places):
+def dec(decimal_places: int) -> Callable:
+    """
+    generate a function to return a number rounded
+    to a specific precision
+
+    :param decimal_places: precision for the value returned by the function
+    :type decimal_places: int
+    :return: rounding function
+    :rtype: function
+    """
+
     def func(number):
         return round(float(number), decimal_places)
 
     return func
 
 
-def compute_compound_properties(compound_ids, task=None):
+def compute_compound_properties(
+    compound_ids: List[int], task: MonitorTask = None
+) -> List[int]:
+    """
+    compute the properties for a list of compounds
+
+    :param compound_ids: The list of compound IDs to be processed
+    :type compound_ids: List[int]
+    :param task: The task to which some logging can be sent
+    :type task: MonitorTask
+    :return: List of processed compound IDs
+    :rtype: List[int]
+    """
     compounds = Compound.objects.filter(id__in=compound_ids)
     runner = GalaxyCompoundPropertiesRunner()
     smiles_dict = {}
@@ -160,13 +182,28 @@ def compute_compound_properties(compound_ids, task=None):
     return output_ids
 
 
-def compute_drugbank_similarity(qs):
+def compute_drugbank_similarity(qs: QuerySet):
+    """
+    compute the Tanimoto similarity with Drugbank entries
+    for each compound of a queryset
+
+    :param qs: queryset over the compounds to be processed
+    :type qs: QuerySet
+    """
+
     for c in qs:
         c.save(autofill=True)
-    pass
 
 
-def validate(compound_ids):
+def validate(compound_ids: List[int]) -> List[int]:
+    """
+    validate a list of compounds
+
+    :param compound_ids: The list of compound IDs to validate
+    :type compound_ids: List[int]
+    :return: List of validated compound IDs
+    :rtype: List[int]
+    """
     compounds = Compound.objects.filter(id__in=compound_ids)
     for c in compounds:
         for ca in c.compoundaction_set.all():
@@ -185,7 +222,7 @@ def validate(compound_ids):
 
 def generate_protein_domain_complex_groups():
     """
-    Generation Protein Domain Complex groups
+    Generate Protein Domain Complex groups
     for all validated contributions
     and pre-contribution PPIs
     """
@@ -215,6 +252,10 @@ def generate_protein_domain_complex_groups():
 
 
 def generate_le_lle_plot():
+    """
+    Generate the LE-LLE plot
+    for all validated compounds
+    """
     print("Generating the LE vs. LLE biplot...")
     le_lle_data = []
     LeLleBiplotData.objects.all().delete()
@@ -242,6 +283,10 @@ def generate_le_lle_plot():
 
 
 def plot_circle():
+    """
+    Generate a circle for the PCA correlation circle
+    (utility function for `generate_pca_plot` function)
+    """
     theta = np.linspace(0, 2 * np.pi, 100)
     r = np.sqrt(1.0)
     x1 = r * np.cos(theta)
@@ -250,6 +295,10 @@ def plot_circle():
 
 
 def generate_pca_plot():
+    """
+    Generate the PCA plot
+    for all validated compounds
+    """
     print("Generating the PCA biplot...")
     pca_data = []
     features = [
@@ -376,21 +425,17 @@ def generate_pca_plot():
 
 
 @task(base=MonitorTask, bind=True)
-def launch_test_command_caching(self):
+def run_compute_compound_properties(self: MonitorTask, compound_id: int) -> int:
     """
-    Test task of IppidbTask
+    task "run method" to compute the properties for a compound
+
+    :param self: the task the function is binded to as a method
+    :type self: MonitorTask
+    :param compound_id: the ID of the compound
+    :type compound_id: int
+    :return: the ID of the compound
+    :rtype: int
     """
-    self.write(std_out="Before first sleep, state={}".format(self.state))
-    time.sleep(30)
-    self.update_state(state=states.STARTED)
-    self.write(std_out="After first sleep, state={}".format(self.state))
-    num = random.random()
-    if num > 0.5:
-        raise Exception("ERROR: {} is greater than 0.5".format(num))
-
-
-@task(base=MonitorTask, bind=True)
-def run_compute_compound_properties(self, compound_id):
     self.update_state(state=states.STARTED)
     cj = CompoundJob()
     cj.compound = Compound.objects.get(id=compound_id)
@@ -403,7 +448,19 @@ def run_compute_compound_properties(self, compound_id):
 
 
 @task(base=MonitorTask, bind=True)
-def run_update_compound_cached_properties(self, compound_ids=None):
+def run_update_compound_cached_properties(
+    self: MonitorTask, compound_ids: List[int] = None
+) -> List[int]:
+    """
+    task "run method" to cache the properties for a list of compounds
+
+    :param self: the task the function is binded to as a method
+    :type self: MonitorTask
+    :param compound_ids: the list of compound IDs
+    :type compound_id: List[int]
+    :return: the list of compound IDs
+    :rtype: List[int]
+    """
     if compound_ids:
         qs = Compound.objects.filter(id__in=compound_ids)
     else:
@@ -420,7 +477,19 @@ def run_update_compound_cached_properties(self, compound_ids=None):
 
 
 @task(base=MonitorTask, bind=True)
-def run_compute_drugbank_similarity(self, compound_ids=None):
+def run_compute_drugbank_similarity(
+    self: MonitorTask, compound_ids: List[int] = None
+) -> List[int]:
+    """
+    task "run method" to compute the drugbank similarity for a list of compounds
+
+    :param self: the task the function is binded to as a method
+    :type self: MonitorTask
+    :param compound_ids: the list of compound IDs
+    :type compound_id: List[int]
+    :return: the list of compound IDs
+    :rtype: List[int]
+    """
     if compound_ids:
         qs = Compound.objects.filter(id__in=compound_ids)
     else:
@@ -437,7 +506,19 @@ def run_compute_drugbank_similarity(self, compound_ids=None):
 
 
 @task(base=MonitorTask, bind=True)
-def run_validate(self, compound_ids):
+def run_validate(
+    self: MonitorTask, compound_ids: List[int] = None
+) -> List[int]:
+    """
+    task "run method" to validate a list of compounds
+
+    :param self: the task the function is binded to as a method
+    :type self: MonitorTask
+    :param compound_ids: the list of compound IDs
+    :type compound_id: List[int]
+    :return: the list of compound IDs
+    :rtype: List[int]
+    """
     self.update_state(state=states.STARTED)
     self.write(std_out=f"Starting validation of compounds {compound_ids}")
     validate(compound_ids)
@@ -446,7 +527,13 @@ def run_validate(self, compound_ids):
 
 
 @task(base=MonitorTask, bind=True)
-def run_le_lle_plot(self):
+def run_le_lle_plot(self: MonitorTask):
+    """
+    task "run method" to generate the LE-LLE plot
+
+    :param self: the task the function is binded to as a method
+    :type self: MonitorTask
+    """
     self.update_state(state=states.STARTED)
     self.write(std_out=f"Starting computing LE-LLE plot")
     generate_le_lle_plot()
@@ -454,14 +541,23 @@ def run_le_lle_plot(self):
 
 
 @task(base=MonitorTask, bind=True)
-def run_pca_plot(self):
+def run_pca_plot(self: MonitorTask):
+    """
+    task "run method" to generate the PCA plot
+
+    :param self: the task the function is binded to as a method
+    :type self: MonitorTask
+    """
     self.update_state(state=states.STARTED)
     self.write(std_out=f"Starting computing PCA plot")
     generate_pca_plot()
     self.write(std_out=f"Finished computing PCA plot")
 
 
-def launch_validate_contributions(contribution_ids):
+def launch_validate_contributions(contribution_ids: List[int]):
+    """
+    Launch the task to validate the contributions
+    """
     contribution_jobs = []
     for cont in Contribution.objects.filter(id__in=contribution_ids):
         compound_ids = [
@@ -491,14 +587,14 @@ def launch_validate_contributions(contribution_ids):
 
 def launch_update_compound_cached_properties():
     """
-    This task will launch the caching of properties on all compounds
+    Launch the task to cache the properties on all compounds
     """
     run_update_compound_cached_properties.delay()
 
 
 def launch_plots_computing():
     """
-    This task will perform the computing of LE-LLE and PCA plots.
+    Launch the task to perform the computing of LE-LLE and PCA plots.
     """
     workflow = chain(run_le_lle_plot.si(), run_pca_plot.si())
     workflow.delay()
diff --git a/ippisite/ippidb/templates/compound_db_links.html b/ippisite/ippidb/templates/compound_db_links.html
index 10723bd5591252c613cdbf135ba9c32627a302a9..c67f97fff9646a543cb18e35c055f524a1755414 100644
--- a/ippisite/ippidb/templates/compound_db_links.html
+++ b/ippisite/ippidb/templates/compound_db_links.html
@@ -1,22 +1,27 @@
-{%if compound.pubchem_id or compound.chembl_id or compound.chemspider_id %}
+{%if compound.pubchem_id or compound.chembl_id or compound.chemspider_id or compound.ligand_id %}
 <table class="table mb-0">
   <tbody>
     <tr class="row">
-      <td scope="col" class="col-4">
+      <td scope="col" class="col-3">
         {% if compound.pubchem_id %}
         <a href="https://pubchem.ncbi.nlm.nih.gov/compound/{{ compound.pubchem_id }}" target="_blank"><img src="/static/images/Other/Pubchemlogo.png" style="height: 1.5em;" title="PubChem ID: {{ compound.pubchem_id }}" /></a>
         {% endif %}
       </td>
-      <td scope="col" class="col-4">
+      <td scope="col" class="col-3">
         {% if compound.chembl_id %}
         <a href="https://www.ebi.ac.uk/chembldb/compound/inspect/{{ compound.chembl_id }}" target="_blank"><img src="/static/images/Other/Chembl_logo.png" style="height: 1.5em;" title="ChEMBL ID: {{ compound.pubchem_id }}" /></a>
         {% endif %}
       </td>
-      <td scope="col" class="col-4">
+      <td scope="col" class="col-3">
         {% if compound.chemspider_id %}
         <a href="http://www.chemspider.com/Chemical-Structure.{{ compound.chemspider_id}}.html" target="_blank"><img src="/static/images/Other/ChemSpider_Logo.png" style="height: 1.5em;" title="ChemSpider ID: {{ compound.chemspider_id }}" /></a>
         {% endif %}
       </td>
+      <td scope="col" class="col-3">
+        {% if compound.ligand_id %}
+        <a href="https://www.rcsb.org/ligand/{{ compound.ligand_id}}" target="_blank">{{compound.ligand_id}}</a>
+        {% endif %}
+      </td>
     </tr>
   </tbody>
 </table>
diff --git a/ippisite/ippidb/templates/compound_l_item.html b/ippisite/ippidb/templates/compound_l_item.html
index 208ad5071d114f0520975b6465b9322584b48e64..7f55c823b813cdc19805eeb7dde7e73bcc478d9e 100644
--- a/ippisite/ippidb/templates/compound_l_item.html
+++ b/ippisite/ippidb/templates/compound_l_item.html
@@ -27,7 +27,7 @@
       <li class="list-group-item">InChiKey: {{ compound.inchikey }}</li>
       {% endif %}
     </ul>
-    {%if compound.pubchem_id or compound.chembl_id or compound.chemspider_id %}
+    {%if compound.pubchem_id or compound.chembl_id or compound.chemspider_id or compound.ligand_id %}
     <h4 class="pt-2 compound_list_title">External links</h4>
     {% include "compound_db_links.html" %}
     {% endif %}
diff --git a/ippisite/ippidb/templates/index.html b/ippisite/ippidb/templates/index.html
index 970e4e9aab8068b87948fe68154b4b923439fff3..664931798fa7e308432c9ff8a1ef885d72d7ac30 100644
--- a/ippisite/ippidb/templates/index.html
+++ b/ippisite/ippidb/templates/index.html
@@ -1,5 +1,9 @@
 {% extends "base.html" %}
 
+{% block extra_js %}
+<script src="/static/js/ippidb-slides.js" type="text/javascript"></script>
+{% endblock %}
+
 
 {% block title %}inhibitors of Protein-Protein Interaction Database{% endblock %}
 
diff --git a/ippisite/ippidb/tests/test_with_all_tests.yaml b/ippisite/ippidb/tests/test_with_all_tests.yaml
index 47145d44df7bbb564d4a7c9ac0d7d5ba058d624e..73f7d142669596f3dbaa32968616253a733a9cc2 100644
--- a/ippisite/ippidb/tests/test_with_all_tests.yaml
+++ b/ippisite/ippidb/tests/test_with_all_tests.yaml
@@ -52,15 +52,15 @@ pharmacokinetic: true
 pharmacokinetic_tests:
 - administration_mode: IV
   compound_pk_results:
-  - auc: '1'
-    c_max: '5.2'
-    clearance: '1.2'
+  - auc_av: True
+    c_max_av: True
+    clearance_av: True
     compound_name: toto
     oral_bioavailability: '22'
     t_demi: '5'
     t_max: '5'
     tolerated: 'True'
-    voldistribution: '5.5'
+    voldistribution_av: True
   concentration: '1.2'
   dose: '3.5'
   dose_interval: '2'
diff --git a/ippisite/ippidb/tests/test_with_pk_test.yaml b/ippisite/ippidb/tests/test_with_pk_test.yaml
index e6fa88bd11611a9a41f65b95b20a623a9dd91b9e..60136f0575032511a6bf534735ce72bb99f60af6 100644
--- a/ippisite/ippidb/tests/test_with_pk_test.yaml
+++ b/ippisite/ippidb/tests/test_with_pk_test.yaml
@@ -43,15 +43,15 @@ pharmacokinetic: true
 pharmacokinetic_tests:
 - administration_mode: IV
   compound_pk_results:
-  - auc: '1'
-    c_max: '5.2'
-    clearance: '1.2'
+  - auc_av: False
+    c_max_av: True
+    clearance_av: '1.2'
     compound_name: toto
     oral_bioavailability: '22'
     t_demi: '5'
     t_max: '5'
     tolerated: 'True'
-    voldistribution: '5.5'
+    voldistribution_av: '5.5'
   dose: '3.5'
   organism: 1
   test_name: test
diff --git a/ippisite/ippidb/tests/tests_contribute_e2e.py b/ippisite/ippidb/tests/tests_contribute_e2e.py
index f14b7da0360377da15a3bf2964e218fce153ac9b..d5d2877f34c23c64ad042d8bb78b75c0bd9be894 100644
--- a/ippisite/ippidb/tests/tests_contribute_e2e.py
+++ b/ippisite/ippidb/tests/tests_contribute_e2e.py
@@ -467,15 +467,15 @@ class ContributionE2ETestCase(TestCase):
                     data[
                         f"{idx}-compoundpkresult_set-pk-results-{nidx}-tolerated"
                     ] = result["tolerated"]
-                    data[f"{idx}-compoundpkresult_set-pk-results-{nidx}-auc"] = result[
-                        "auc"
+                    data[f"{idx}-compoundpkresult_set-pk-results-{nidx}-auc_av"] = result[
+                        "auc_av"
                     ]
                     data[
-                        f"{idx}-compoundpkresult_set-pk-results-{nidx}-clearance"
-                    ] = result["clearance"]
+                        f"{idx}-compoundpkresult_set-pk-results-{nidx}-clearance_av"
+                    ] = result["clearance_av"]
                     data[
-                        f"{idx}-compoundpkresult_set-pk-results-{nidx}-c_max"
-                    ] = result["c_max"]
+                        f"{idx}-compoundpkresult_set-pk-results-{nidx}-c_max_av"
+                    ] = result["c_max_av"]
                     data[
                         f"{idx}-compoundpkresult_set-pk-results-{nidx}-oral_bioavailability"
                     ] = result["oral_bioavailability"]
@@ -486,8 +486,8 @@ class ContributionE2ETestCase(TestCase):
                         f"{idx}-compoundpkresult_set-pk-results-{nidx}-t_max"
                     ] = result["t_max"]
                     data[
-                        f"{idx}-compoundpkresult_set-pk-results-{nidx}-voldistribution"
-                    ] = result["voldistribution"]
+                        f"{idx}-compoundpkresult_set-pk-results-{nidx}-voldistribution_av"
+                    ] = result["voldistribution_av"]
                     data[
                         f"{idx}-compoundpkresult_set-pk-results-{nidx}-compound_name"
                     ] = result["compound_name"]
diff --git a/ippisite/ippidb/urls.py b/ippisite/ippidb/urls.py
index 7946abc0b36c64aa1ae44a16b3b06f18a23ecf25..2b1d413d7f09d6a307b41984a04b62089e927860 100644
--- a/ippisite/ippidb/urls.py
+++ b/ippisite/ippidb/urls.py
@@ -4,10 +4,41 @@ iPPI-DB URLs routing module
 
 from django.conf.urls import include, url
 from django.conf import settings
+from django.contrib.sitemaps.views import sitemap
+from django.contrib.sitemaps import Sitemap
+from django.urls import path
+from django.urls import reverse
 
 from . import views
 
 
+class IppiDbStaticSitemap(Sitemap):
+    changefreq = "never"
+    priority = 1
+
+    def items(self):
+        return [
+            "index",
+            "general",
+            "pharmacology",
+            "le_lle",
+            "physicochemistry",
+            "pca",
+            "compound_list",
+        ]
+
+    def location(self, item):
+        return reverse(item)
+
+    def lastmod(self, obj):
+        return None
+
+
+sitemaps = {
+    "static": IppiDbStaticSitemap,
+    "compounds": views.site.IppiDbCompoundSitemap,
+}
+
 urlpatterns = [
     url(r"^$", views.index, name="index"),
     url(r"^about-general/$", views.about_general, name="general"),
@@ -65,6 +96,12 @@ urlpatterns = [
     url(r"^utils/smi2iupac$", views.convert_smi2iupac, name="smi2iupac"),
     url(r"^utils/iupac2smi$", views.convert_iupac2smi, name="iupac2smi"),
     url(r"^utils/getoutputjob$", views.get_output_job, name="getoutputjob"),
+    path(
+        "sitemap.xml",
+        sitemap,
+        {"sitemaps": sitemaps},
+        name="django.contrib.sitemaps.views.sitemap",
+    ),
 ]
 
 if settings.DEBUG:
diff --git a/ippisite/ippidb/utils.py b/ippisite/ippidb/utils.py
index dd72f6b1b59cc72e1d62567b72c0d0fd44dbf249..b931863521a387c128a6496eb2a172aa35af0717 100644
--- a/ippisite/ippidb/utils.py
+++ b/ippisite/ippidb/utils.py
@@ -11,12 +11,30 @@ except ImportError:
     import openbabel as ob
 
 
-def mol2smi(mol_string):
+def mol2smi(mol_string: str) -> str:
+    """
+    Convert a compound structure from MOL to SMILES format
+    using open babel
+
+    :param mol_string: structure for the compound in MOL format
+    :type mol_string: str
+    :return: structure for the compound in SMILES format
+    :rtype: str
+    """
     m = pybel.readstring("mol", mol_string)
     return m.write(format="smi").strip()
 
 
-def smi2mol(smi_string):
+def smi2mol(smi_string: str) -> str:
+    """
+    Convert a compound structure from SMILES to MOL format
+    using open babel
+
+    :param smi_string: structure for the compound in SMILES format
+    :type smi_string: str
+    :return: structure for the compound in MOL format
+    :rtype: str
+    """
     m = pybel.readstring("smi", smi_string)
     # generate 2D coordinates for MarvinJS layout
     # NB: the line below should be replaced as soon as the new version of openbabel
@@ -31,7 +49,16 @@ smi2inchi_conv = ob.OBConversion()
 smi2inchi_conv.SetInAndOutFormats("smi", "inchi")
 
 
-def smi2inchi(smi_string):
+def smi2inchi(smi_string: str) -> str:
+    """
+    Convert a compound structure from SMILES to InChi format
+    using open babel
+
+    :param smi_string: structure for the compound in SMILES format
+    :type smi_string: str
+    :return: structure for the compound in InChi format
+    :rtype: str
+    """
     mol = ob.OBMol()
     smi2inchi_conv.ReadString(mol, smi_string)
     return smi2inchi_conv.WriteString(mol).strip()
@@ -42,7 +69,16 @@ smi2inchikey_conv.SetInAndOutFormats("smi", "inchi")
 smi2inchikey_conv.SetOptions("K", smi2inchikey_conv.OUTOPTIONS)
 
 
-def smi2inchikey(smi_string):
+def smi2inchikey(smi_string: str) -> str:
+    """
+    Convert a compound structure from SMILES to InChiKey format
+    using open babel
+
+    :param smi_string: structure for the compound in SMILES format
+    :type smi_string: str
+    :return: structure for the compound in InChiKey format
+    :rtype: str
+    """
     mol = ob.OBMol()
     smi2inchikey_conv.ReadString(mol, smi_string)
     return smi2inchikey_conv.WriteString(mol).strip()
@@ -52,7 +88,16 @@ smi2sdf_conv = ob.OBConversion()
 smi2sdf_conv.SetInAndOutFormats("smi", "sdf")
 
 
-def smi2sdf(smi_dict):
+def smi2sdf(smi_dict: dict) -> str:
+    """
+    Convert a series of compound structures in SMILES to an SDF format
+    using open babel
+
+    :param smi_dict: structure for the compound in SMILES format
+    :type smi_dict: dict
+    :return: structure for the compound in InChiKey format
+    :rtype: str
+    """
     sdf_string = ""
     for id, smiles in smi_dict.items():
         mol = ob.OBMol()
@@ -63,37 +108,67 @@ def smi2sdf(smi_dict):
 
 
 class FingerPrinter(object):
-    def __init__(self, name="FP4"):
+    def __init__(self, name: str = "FP4"):
+        """
+        :param name: name of the FingerPrint type to use, defaults to FP4
+        :type name: str
+        """
         self.fingerprinter = ob.OBFingerprint.FindFingerprint(name)
         self._smiles_parser = ob.OBConversion()
         self._smiles_parser.SetInFormat("smi")
 
-    def parse_smiles(self, smiles):
-        "parse a SMILES into a molecule"
+    def parse_smiles(self, smiles: str) -> ob.OBMol:
+        """
+        Parse a SMILES into a molecule
+
+        :param smiles: compound SMILES
+        :type smiles: str
+        :return: compound openbabel object
+        :rtype: ob.OBMol
+        """
         mol = ob.OBMol()
         self._smiles_parser.ReadString(mol, smiles)
         return mol
 
-    def fp(self, smiles):
-        "generate a fingerprint from a SMILES string"
+    def fp(self, smiles: str) -> ob.vectorUnsignedInt:
+        """
+        Generate a fingerprint from a SMILES string
+
+        :param smiles: compound SMILES
+        :type smiles: str
+        :return: fingerprint
+        :rtype: ob.vectorUnsignedInt
+        """
         fp = ob.vectorUnsignedInt()
         self.fingerprinter.GetFingerprint(self.parse_smiles(smiles), fp)
         return fp
 
-    def fp_dict(self, smiles_dict):
+    def fp_dict(self, smiles_dict: str) -> dict:
         """
-        generate a dict of {compound id: fingerprint} from a dict of
+        Generate a dict of {compound id: fingerprint} from a dict of
             {compound id: fingerprint}
+
+        :param smiles_dict: dictionary of compound SMILES
+        :type smiles_dict: dict
+        :return: the corresponding {compound id: fingerprint} dictionary
+        :rtype: dict
         """
         return {
             compound_id: self.fp(smiles_entry)
             for compound_id, smiles_entry in smiles_dict.items()
         }
 
-    def tanimoto_fps(self, smiles_query, fp_dict):
+    def tanimoto_fps(self, smiles_query: str, fp_dict: dict) -> dict:
         """
-        perform a tanimoto similarity search using a smiles query string
+        Perform a tanimoto similarity search using a smiles query string
         on a dict of {compound id: fingerprint}
+
+        :param smiles_query: dictionary of compound SMILES
+        :type smiles_query: str
+        :param fp_dict: a {compound id: fingerprint} dictionary
+        :type fp_dict: dict
+        :return: the {compound id: tanimoto value} dictionary for this query
+        :rtype: dict
         """
         fp_query = self.fp(smiles_query)
         return {
@@ -101,10 +176,17 @@ class FingerPrinter(object):
             for compound_id, fp_entry in fp_dict.items()
         }
 
-    def tanimoto_smiles(self, query_smiles, smiles_dict):
+    def tanimoto_smiles(self, query_smiles: str, smiles_dict: dict) -> dict:
         """
-        perform a tanimoto similarity search using a smiles query on a
+        Perform a tanimoto similarity search using a smiles query on a
         dict of {compound id: SMILES}
+
+        :param query_smiles: dictionary of compound SMILES
+        :type query_smiles: str
+        :param smiles_dict: a {compound id: SMILES} dictionary
+        :param smiles_dict: dict
+        :return: the {compound id: tanimoto value} dictionary for this query
+        :rtype: dict
         """
         fp_dict = self.fp_dict(smiles_dict)
         return self.tanimoto_fps(query_smiles, fp_dict)
diff --git a/ippisite/ippidb/views/__init__.py b/ippisite/ippidb/views/__init__.py
index af0cb27ee9b02c64288941267454a1f27054cc35..ee84ea5538f65e15e6e45044532b680708ef5ecf 100644
--- a/ippisite/ippidb/views/__init__.py
+++ b/ippisite/ippidb/views/__init__.py
@@ -21,6 +21,8 @@ from .compound_query import (
     convert_smi2iupac,
     convert_iupac2smi,
 )
+from .site import IppiDbCompoundSitemap
+
 from .tasks import get_output_job
 from .about import (
     about_general,
@@ -87,4 +89,5 @@ __all__ = [
     about_pharmacology,
     about_physicochemistry,
     get_output_job,
+    IppiDbCompoundSitemap,
 ]
diff --git a/ippisite/ippidb/views/site.py b/ippisite/ippidb/views/site.py
new file mode 100644
index 0000000000000000000000000000000000000000..9878db475863b99fae7ca6f66e020db51f9d347f
--- /dev/null
+++ b/ippisite/ippidb/views/site.py
@@ -0,0 +1,13 @@
+from django.contrib.sitemaps import Sitemap
+from ippidb.models import Compound
+
+
+class IppiDbCompoundSitemap(Sitemap):
+    changefreq = "never"
+    priority = 0.5
+
+    def items(self):
+        return Compound.objects.validated()
+
+    def lastmod(self, obj):
+        return None
diff --git a/ippisite/ippidb/ws.py b/ippisite/ippidb/ws.py
index 190c4ef13c30151ffb7e1f11fc3a67570ede063c..6ea39ea3da1c5bdf9663fef33e427d62c08ab35d 100644
--- a/ippisite/ippidb/ws.py
+++ b/ippisite/ippidb/ws.py
@@ -1,10 +1,10 @@
 """
 iPPI-DB web-service client utility functions
 """
+from typing import List
 
 import xml.etree.ElementTree as ET
 from urllib import parse as urllib_parse
-
 import requests
 from bioservices.eutils import EUtils
 from bioservices.uniprot import UniProt
@@ -13,7 +13,20 @@ from requests.exceptions import ContentDecodingError
 
 
 class EntryNotFoundError(Exception):
-    def __init__(self, entry_id, status_code=None, msg=None):
+    """
+    Exception raised if an entry is not found using on of the
+    Web Service functions of this module
+    """
+
+    def __init__(self, entry_id: str, status_code: str = None, msg: str = None):
+        """
+        :param entry_id: ID of the entry not found
+        :type entry_id: str
+        :param status_code: status code sent back by the web service
+        :type status_code: str
+        :param msg: message sent back by the web service
+        :type msg: str
+        """
         self.entry_id = entry_id
         _msg = f"{entry_id} not found."
         if status_code is not None:
@@ -24,25 +37,41 @@ class EntryNotFoundError(Exception):
 
 
 class BibliographicalEntryNotFound(EntryNotFoundError):
+    """
+    Exception raised if a Bibliography entry is not found
+    """
+
     pass
 
 
 class PubMedEntryNotFound(BibliographicalEntryNotFound):
+    """
+    Exception raised if a PubMed entry is not found
+    """
+
     def __str__(self):
         return f"Bibliographical data not found for PMID {self.entry_id}"
 
 
 class PatentNotFound(BibliographicalEntryNotFound):
+    """
+    Exception raised if a Patent entry is not found
+    """
+
     def __str__(self):
         return f"Bibliographical data not found for Patent {self.entry_id}"
 
 
 class DOIEntryNotFound(BibliographicalEntryNotFound):
+    """
+    Exception raised if a DOI entry is not found
+    """
+
     def __str__(self):
         return f"Bibliographical data not found for DOI {self.entry_id}"
 
 
-def get_pubmed_info(pmid):
+def get_pubmed_info(pmid: int) -> dict:
     """
     Retrieve information about a publication from  NCBI PubMed
 
@@ -77,7 +106,7 @@ def get_pubmed_info(pmid):
     }
 
 
-def get_google_patent_info(patent_number):
+def get_google_patent_info(patent_number: str) -> dict:
     """
     Retrieve information about a patent parsing Dublin Core info in the Google HTML
 
@@ -110,12 +139,12 @@ def get_google_patent_info(patent_number):
     }
 
 
-def get_doi_info(doi):
+def get_doi_info(doi: str) -> dict:
     """
     Retrieve information about a publication from DOI web services
 
     :param doi: DOI
-    :type pmid: str
+    :type doi: str
     :return: publication metadata (title, journal name, publication year, authors list).
     :rtype: dict
     """
@@ -176,7 +205,7 @@ def get_doi_info(doi):
     }
 
 
-def get_uniprot_info(uniprot_id):
+def get_uniprot_info(uniprot_id: str) -> dict:
     """
     Retrieve information about a protein from the Uniprot database
 
@@ -278,7 +307,7 @@ def get_uniprot_info(uniprot_id):
     }
 
 
-def get_go_info(go_id):
+def get_go_info(go_id: str) -> dict:
     """
     Retrieve information about a GO term using the EBI OLS web service
 
@@ -296,7 +325,7 @@ def get_go_info(go_id):
     return {"label": label}
 
 
-def get_taxonomy_info(taxonomy_id):
+def get_taxonomy_info(taxonomy_id: str) -> dict:
     """
     Retrieve information about a taxon using the NCBI Entrez web services
 
@@ -311,7 +340,7 @@ def get_taxonomy_info(taxonomy_id):
     return {"scientific_name": scientific_name}
 
 
-def get_pfam_info(pfam_acc):
+def get_pfam_info(pfam_acc: str) -> dict:
     """
     Retrieve information about a protein family using the PFAM web service
 
@@ -329,14 +358,14 @@ def get_pfam_info(pfam_acc):
     return {"id": pfam_id, "description": description}
 
 
-def get_pdb_uniprot_mapping(pdb_id):
+def get_pdb_uniprot_mapping(pdb_id: str) -> List[str]:
     """
     Retrieve PDB to uniprot mappings using the PDBe web service
 
     :param pdb_id: PDB ID
     :type pdb_id: str
     :return: Uniprot IDs
-    :rtype: list
+    :rtype: List[str]
     """
     pdb_id = pdb_id.lower()
     resp = requests.get(
@@ -354,7 +383,7 @@ def get_pdb_uniprot_mapping(pdb_id):
     return uniprot_ids
 
 
-def get_pdb_pfam_mapping(pdb_id):
+def get_pdb_pfam_mapping(pdb_id: str) -> dict:
     """
     Retrieve PDB to pfam mappings using the PDBe web service
 
@@ -378,7 +407,7 @@ def get_pdb_pfam_mapping(pdb_id):
     return all_pfam
 
 
-def pdb_entry_exists(pdb_id):
+def pdb_entry_exists(pdb_id: str) -> bool:
     """
     Test if a PDB entry exists using EBI web services
 
@@ -387,7 +416,6 @@ def pdb_entry_exists(pdb_id):
     :return: True/False wether the PDB entry exists
     :rtype: bool
     """
-    """ """
     resp = requests.get(
         "https://www.ebi.ac.uk/pdbe/api/pdb/entry/summary/{}".format(pdb_id.lower())
     )
@@ -398,11 +426,29 @@ def pdb_entry_exists(pdb_id):
         return True
 
 
-def convert_iupac_to_smiles(iupac):
+def convert_iupac_to_smiles(iupac: str) -> str:
+    """
+    Convert a IUPAC name to SMILES format
+    using the Opsin web service
+
+    :param iupac: IUPAC name for the compound
+    :type iupac: str
+    :return: the SMILES
+    :rtype: str
+    """
     return convert_iupac_to_smiles_and_inchi(iupac)["smiles"]
 
 
-def convert_iupac_to_smiles_and_inchi(iupac):
+def convert_iupac_to_smiles_and_inchi(iupac: str) -> dict:
+    """
+    Convert a IUPAC name to SMILES and INCHI formats
+    using the Opsin web service
+
+    :param iupac: IUPAC name for the compound
+    :type iupac: str
+    :return: dictionary containing the SMILES and INCHI
+    :rtype: dict
+    """
     if iupac is None:
         raise EntryNotFoundError(iupac, 422)
     resp = requests.get(
@@ -416,7 +462,16 @@ def convert_iupac_to_smiles_and_inchi(iupac):
     return ret
 
 
-def convert_smiles_to_iupac(smiles):
+def convert_smiles_to_iupac(smiles: str) -> str:
+    """
+    Convert a compound SMILES to IUPAC name
+    using the NIH Cactus Web Service
+
+    :param smiles: IUPAC name for the compound
+    :type smiles: str
+    :return: the IUPAC name
+    :rtype: str
+    """
     return requests.get(
         f"https://cactus.nci.nih.gov/chemical/structure/{smiles}/iupac_name"
     ).text
diff --git a/ippisite/ippisite/admin.py b/ippisite/ippisite/admin.py
index dd1a9292a8e817bcec381181cb8a0af904697662..08b377b4d99afaf9a2b913780f939c91113b9a5d 100644
--- a/ippisite/ippisite/admin.py
+++ b/ippisite/ippisite/admin.py
@@ -6,7 +6,6 @@ from ippidb.tasks import (
     launch_update_compound_cached_properties,
     run_compute_drugbank_similarity,
     launch_plots_computing,
-    launch_test_command_caching,
 )
 from django.shortcuts import render
 from ippidb.models import Job
@@ -37,7 +36,6 @@ class IppidbAdmin(admin.AdminSite):
                 "launch_plots_computing/",
                 self.admin_view(self.launch_plots_computing_view),
             ),
-            path("launch_test_command/", self.admin_view(self.launch_test_command),),
             path("tasklog/", self.admin_view(self.tasklog), name="tasklog"),
         ]
         return my_urls + urls
@@ -63,15 +61,6 @@ class IppidbAdmin(admin.AdminSite):
             messages.add_message(request, messages.INFO, "Please specify a jobid")
             return redirect("/admin/ippidb/job")
 
-    def launch_test_command(self, request):
-        """
-        This view launches the task to test jobs
-        """
-        task = launch_test_command_caching.delay()
-        print(task.state)
-        messages.add_message(request, messages.INFO, "Test job launched")
-        return redirect("/admin/ippidb/job")
-
     def launch_compound_properties_caching_view(self, request):
         """
         This view launches the task to perform, for all already validated compounds,
diff --git a/ippisite/ippisite/settings.py b/ippisite/ippisite/settings.py
index 4756dbcbf554c7a04bce04ed64763e5ea5671574..68cfb4e4351220a4b8425f6a413346ef8b649a71 100644
--- a/ippisite/ippisite/settings.py
+++ b/ippisite/ippisite/settings.py
@@ -42,6 +42,7 @@ INSTALLED_APPS = [
     "django.contrib.sessions",
     "django.contrib.messages",
     "django.contrib.staticfiles",
+    "django.contrib.sitemaps",
     "django_extensions",
     "django_celery_results",
     "crispy_forms",
diff --git a/ippisite/ippisite/settings.template.py b/ippisite/ippisite/settings.template.py
index f971d198766ae090eaa5165b86ed3cbde0808f02..7fa61aba155000c3723e2187c31dad72131c7270 100644
--- a/ippisite/ippisite/settings.template.py
+++ b/ippisite/ippisite/settings.template.py
@@ -44,6 +44,7 @@ INSTALLED_APPS = [
     "django.contrib.sessions",
     "django.contrib.messages",
     "django.contrib.staticfiles",
+    "django.contrib.sitemaps",
     "django_extensions",
     "django_celery_results",
     "crispy_forms",
@@ -86,6 +87,7 @@ TEMPLATES = [
                 "django.contrib.auth.context_processors.auth",
                 "django.contrib.messages.context_processors.messages",
                 "ippidb.views.marvinjs",
+                "ippidb.views.google_analytics",
                 "live_settings.context_processors.processors",
             ]
         },
diff --git a/ippisite/templates/admin/index.html b/ippisite/templates/admin/index.html
index d147b65995fe69eebececa685c1c9d0e2edd2adc..7db55abf631e8e2409e7556e2ecf6f2880c08dfb 100644
--- a/ippisite/templates/admin/index.html
+++ b/ippisite/templates/admin/index.html
@@ -58,13 +58,7 @@
               style="display:block">{% csrf_token %}
             <input type="submit" value="Plots generation" name="_save"/>
         </form>
-        <hr/>
-        <form method="POST" action="/admin/launch_test_command/"
-              style="display:block">{% csrf_token %}
-            <input type="submit" value="test command" name="_save"/>
-        </form>
-        <hr/>
-        
+        <hr/>        
     </div>