(sequenced-notebook)=

# Le Notebook séquencé

Un **notebook séquencé** est un notebook **Python** dont le contenu s'affiche
petit à petit pour l'élève en fonction de **cellules d'arrêt** définies
par l'enseignant.

Les cellules d'arrêt peuvent être de type `markdown` ou de type `code`.
Pour poursuivre l'affichage du notebook après une cellule d'arrêt de type `markdown`,
il suffit de l'exécuter. C'est un simple point d'arrêt qui indique une étape dans
la consigne. En revanche, si la cellule est de type `code` l'élève devra réussir
un processus de validation pour pouvoir continuer. Ceci permet à l'enseignant de
séquencer son activité et à l'élève d'obtenir un retour sur son travail en autonomie.

<video src="../_static/sequenced-notebook.mp4" controls="controls" style="width:100%;"></video>

## Principe des cellules d'arrêt

Pour déclarer un point d'arrêt sur une cellule, il suffit de lui ajouter le tag `breakpoint`.

![Taguer une cellule pour définir un point d'arrêt](/_static/validation-cells-tags.png)

Plusieurs contraintes s'appliquent alors aux cellules d'arrêt :

- on ne peut pas ajouter de cellule après une cellule d'arrêt non validée (ni même «coller après»)
- on ne peut pas déplacer une cellule en dessous d'une cellule d'arrêt non validée
- on ne peut pas supprimer/diviser/couper/fusionner une cellule d'arrêt.

Ainsi, pour que l'on avance dans un notebook séquencé, plusieurs conditions doivent être réunies :

- la cellule qui demande d'avancer est une cellule d'arrêt (i.e. elle est de type `code` ou `markdown`
  et possède le tag `breakpoint`)
- c'est la dernière cellule visible
- dans le cas d'une cellule `markdown`, c'est l'exécution (rendu) qui déclenche l'avancement
- si c'est une cellule de `code`, c'est le succès de la validation Python qui déclenche l'avancement.

## Activation du mode séquencé pour l'élève

Pour déclarer que le notebook d'une activité est à jouer en mode séquencé, il
faut cocher cette option dans les propriétés de l'activité sur Capytale.

![Activation du notebook séquencé dans Capytale](/_static/capytale-activate-sequenced.png)

```{tip}
L'option **Markdown en lecture seul** peut avantageusement être utilisée dans un
notebook séquencé pour éviter aux élèves de **casser** les cellules de consigne
ou d'en afficher le code `markdown`.
```

```{note}
Lorsque l'enseignant édite son activité, le notebook n'est bien sûr pas joué en mode
séquencé. Ce n'est que lorsque l'élève clonera l'activité de l'enseignant (à l'aide du code
de partage) que le notebook sera joué en mode séquencé. De la même manière, pour éviter à
l'enseignant de valider les cellules une à une, la lecture de l'activité d'un élève par
l'enseignant ne sera pas séquencée.
```

## Mise en place du processus de validation par l'enseignant

Les cellules d'arrêt de type `code` nécessitent l'utilisation du module
{mod}`capytale.autoeval`.
Reportez-vous à la {ref}`documentation détaillée sur le sujet <auto-evaluation>`.

```{note}
Un objet de validation appelé dans une cellule d'arrêt avancera automatiquement le
notebook en cas de succès.
```

```{warning}
Il est nécessaire de définir un objet de validation par cellule validée.
En effet, deux cellules de validation ne peuvent pas partager le même objet.
```

Par exemple, pour valider le notebook de la vidéo en haut de cette page, on a écrit :

```python
from capytale.autoeval import (
    ValidateVariables,
    ValidateFunction,
    ValidateFunctionPretty,
)
from itertools import product


test_calcul_1 = ValidateVariables({"calcul_1": 1.2 / (2 + 3.5)})
test_calcul_2 = ValidateVariables({"calcul_2": ((1 + 2**2 + 3) / 7) ** 2})

valeurs = range(0, 100)
test_perimetre_carre = ValidateFunction(
    "perimetre_carre", valeurs, target_values=[4 * x for x in valeurs]
)

test_valeur_absolue = ValidateFunctionPretty(
    "valeur_absolue", range(-20, 21), valid_function=abs
)

valeurs = range(-(10**4), 10**4)
test_bissextile = ValidateFunction(
    "bissextile", valeurs, target_values=[a % 4 == 0 and a % 400 != 0 for a in valeurs]
)

test_xor = ValidateFunctionPretty(
    "xor",
    product([True, False], repeat=2),
    valid_function=lambda x, y: x != y,
)
```

## Utilisation avancée

### Valider la cellule d'import

Pour forcer les élèves à valider la cellule d'import, c'est-à-dire la cellule
contenant

```python
from _validation import *
```

il peut être intéressant
d'y placer un point d'arrêt avec une validation directe (en plus de la déclarer
non-éditable et non-supprimable dans les métadonnées du notebook).

Pour cela, l'utilisation d'un simple objet de la classe {class}`~capytale.autoeval.Validate`
est suffisant :

```python
# contenu du fichier _validation.py
from capytale.autoeval import Validate

cellule_import = Validate()
# c'est cet appel qui aura pour effet de valider la cellule d'import
cellule_import()

# autres objets de validation
...
```

### Plusieurs validations par cellule

Il est tout à fait possible d'utiliser plusieurs validations par cellules.

Pour cela, on peut utiliser le paramètre `ignore_breakpoint=True` dans les constructeurs
des classes de validation. Ceci aura pour effet de ne pas valider le point d'arrêt
en cas de succès. Il suffit alors qu'un seul objet n'ai pas reçu ce paramètre
pour que la cellule soit validée.

Malheureusement, ce mécanisme présente un défaut important. Il ne permet pas de
s'assurer que l'ensemble des tests sont un succès pour valider la cellule.
Pour cela, il vaut mieux utiliser {ref}`la réunion de validations <auto-evaluation-validate-all>`.

### Définir sa propre classe de validation

Ce sujet est traité dans {ref}`la section concernant l'auto-évaluation <auto-evaluation-custom>`
mais il y a quelques aspects spécifiques au contexte du notebook séquencé.

Pour qu'une cellule d'arrêt soit validée, il faut que l'appel de l'objet
de validation ait retourné `True` pour indiquer qu'il faut avancer dans
la séquence.

Par exemple, imaginons que l'enseignant souhaite autoriser la poursuite
du notebook qu'après un certain temps passé depuis le chargement de la page.
Il pourra s'y prendre comme ceci.

```python
from capytale.autoeval import validationclass
import time
import sys


@validationclass
class LitLaConsigne:
    def __init__(self):
        super().__init__()
        self._start = time.time()

    def __call__(self):
        # au moins 5 minutes se sont écoulées
        if time.time() - self._start > 5 * 60:
            return True
        else:
            print("As-tu vraiment pris le temps de lire la consigne ?", file=sys.stderr)
            return False


continuer = LitLaConsigne()
```

![Utilisation de la classe LitLaConsigne](/_static/validation-litlaconsigne.png)

<!-- ```{important} -->
<!-- Pour éviter aux élèves de pouvoir avancer dans le notebook sans avoir vraiment -->
<!-- validé leur travail, l'utilisation de la méthode `_success` est réservée à la classe -->
<!-- elle-même grâce à un mécanisme de privatisation (car en Python, les attributs ne sont -->
<!-- pas vraiment privés). -->

<!-- ![Appel à _success impossible en dehors de la classe](/_static/validation-cannot-call-success.png) -->
<!-- ``` -->
