Moteur d’inférence
Très souvent si vous devez mettre au point un système de tarification, il y a des règles spécifiques au domaine de votre application et un tas de données à lier pour obtenir votre prix final.
Vous vous retrouvez alors devant un problème, comment calculer un prix à partir de ces règles et de ces données ? Et surtout comment avoir du code maintenable, sûr (parce que c’est une partie importante de votre application, celle qui paye votre salaire) et surtout évolutif (parce que vous et moi savons que c’est le genre de chose qui n’est jamais gravé dans le marbre).
Une bonne solution semble être d’utiliser un moteur d’inférence, plutôt qu’un tas de spaghetti code :
Un moteur d’inférence est un système expert qui permet de conduire des raisonnements logiques et de dériver des conclusions à partir d’une base de faits et d’une base de règles.
Si on en revient à notre problème de tarification, nous avons :
- une base de règles : nos règles de tarification.
- une base de faits : nos données.
- des conclusions : notre prix.
Il existe quelques moteurs d’inférence, mais on va choisir d’utiliser Pyke, parce que c’est en Python et que ça tombe plutôt bien.
Pour illustrer l’utilisation de Pyke, nous allons utiliser un cas très simple qui s’inspire de son utilisation chez e-loue.
Des locataires louent des objets un certain temps à un prix fixé par chaque propriétaire. Chaque propriétaire peut définir un prix par jour et/ou un prix par semaine, selon la durée de la location et les prix existants vous devez trouver quel prix s’applique.
Nous allons commencer par installer Pyke :
$ pip install pyke
La première étape, ainsi que le plus difficile consiste à créer notre base de fait. Pyke s’attend à trouver vos règles dans un fichier dont l’extension est .krb. Nous allons appeler notre base “pricing.krb”.
“Le prix par semaine s’applique quand la durée dépasse une semaine et qu’un prix hebdomadaire existe” s’exprime ainsi :
week_package
use packages(weekly, $delta)
when
packages.package('weekly')
check $delta.days >= 7
J’utilise ici le chaînage arrière de Pyke pour exprimer cette règle.
On doit d’abord déclarer le nom de la règle, ici : week_package.
Le reste peut-être lu de cette façon : “Quand un prix hebdomadaire existe et que la durée dépasse 7 jours” correspond a la partie “when”.
Quand ces faits sont vrais alors on utilise le forfait hebdomadaire, ce qui correspond à la partie “use”.
Pour ceux qui veulent comprendre plus en profondeur ou plus rapidement la syntaxe des règles de Pyke, dirigez-vous vers la documentation adéquate, n’oubliez pas de faire un détour vers la partie Pattern Matching qui sera essentielle à votre compréhension.
Vous devriez pouvoir facilement construire vous même la deuxième règle : “Le prix par jour s’applique quand la durée dépasse un jour et est inférieure à une semaine s’il existe un prix journalier” :
day_package
use packages(daily, $delta)
when
packages.package('daily')
check $delta.days >= 1
check $delta.days < 7
On souhaite aussi pouvoir appliquer un prix journalier même si la durée dépasse une semaine si et seulement s’il n’existe pas de prix hebdomadaire, pour ça on va utiliser la directive notany :
day_package_for_week
use packages(daily, $delta)
when
packages.package('daily')
check $delta.days >= 7
notany
packages.package('weekly')
Nous avons créé notre base de règles, faisons appel maintenant à Pyke pour charger celle-ci :
from pyke import knowledge_engine
def which_package(packages, duration):
engine = knowledge_engine.engine((__file__, '.rules'))
engine.activate('pricing')
Nous devons maintenant charger notre base de faits, c’est à dire les forfaits qui s’appliquent pour l’objet :
packages = ['daily', 'weekly']
for package in packages:
engine.assert_('packages', 'package', (package,))
Vous retrouvez, ici, la syntaxe que nous avons utiliser dans nos règles “packages.package” pour faire référence à l’existence d’un forfait.
Il est maintenant possible de prouver si un forfait s’applique à l’objet par rapport à notre durée de location :
try:
vals, plans = engine.prove_1_goal('pricing.packages($type, $delta)', delta=duration)
print vals['type']
except knowledge_engine.CanNotProve:
print "No package applies"
Pour cela, on cherche a prouver un seul “goal”, vous retrouvez la similitude de nommage du nom du goal avec notre base de règles, sauf qu’ici on fourni delta et on cherche le type de forfait.
Au final, on peut vérifier que notre moteur d’inférence fait bien son travail :
>>> which_package(['daily', 'weekly'], datetime.timedelta(days=4))
'daily'
>>> which_package(['daily', 'weekly'], datetime.timedelta(days=8))
'weekly'
>>> which_package(['daily'], datetime.timedelta(days=9))
'daily'
>>> which_package(['weekly'], datetime.timedelta(days=1))
'No package applies'
Vous voila initié à Pyke, vous pouvez essayer d’autres durées ou ajouter de nouveaux forfaits plus complexes. N’oubliez pas que vous pouvez importer des modules python dans vos règles, comme suit :
bc_extras
import calendar
Vous pouvez aussi retrouver le code de cet exemple sur github ainsi qu’une version commentée.