Moteur de règles
Le moteur de règles est le coeur d’Presage. Il évalue des conditions déclaratives par rapport au UserContext courant et retourne l’action appropriée.
Fonctionnement des règles
Section intitulée « Fonctionnement des règles »Chaque règle cible un point d’adaptation spécifique (identifié par adaptationId). Quand un composant résout ce point d’adaptation, le moteur :
- Filtre les règles par
adaptationId - Trie par
priority(la plus haute d’abord) - Evalue les conditions de chaque règle par rapport au
UserContext - Retourne l’action de la première règle correspondante
- Retourne
nullsi aucune règle ne correspond (le composant utilise sa variante par défaut)
const rule: AdaptationRule = { id: 'enterprise-advanced-dashboard', adaptationId: 'dashboard', priority: 10, conditions: { all: [ { field: 'traits.plan', operator: 'eq', value: 'enterprise' }, { field: 'signals.sessionCount', operator: 'gte', value: 10 }, ], }, action: { type: 'show', variantId: 'advanced' },}Les 14 opérateurs
Section intitulée « Les 14 opérateurs »| Opérateur | Description | Exemple |
|---|---|---|
eq | Egalité stricte (===) | { field: 'traits.role', operator: 'eq', value: 'admin' } |
neq | Inégalité stricte (!==) | { field: 'traits.plan', operator: 'neq', value: 'free' } |
Comparaison numérique
Section intitulée « Comparaison numérique »Tous les opérateurs numériques requièrent que la valeur du champ et la valeur de comparaison soient des nombres.
| Opérateur | Description | Exemple |
|---|---|---|
gt | Supérieur à | { field: 'signals.sessionCount', operator: 'gt', value: 5 } |
gte | Supérieur ou égal | { field: 'signals.totalEvents', operator: 'gte', value: 100 } |
lt | Inférieur à | { field: 'signals.daysSinceSignup', operator: 'lt', value: 7 } |
lte | Inférieur ou égal | { field: 'traits.companySize', operator: 'lte', value: 50 } |
Appartenance à un ensemble
Section intitulée « Appartenance à un ensemble »| Opérateur | Description | Exemple |
|---|---|---|
in | La valeur est dans le tableau fourni | { field: 'traits.plan', operator: 'in', value: ['pro', 'enterprise'] } |
notIn | La valeur n’est pas dans le tableau fourni | { field: 'traits.role', operator: 'notIn', value: ['viewer', 'guest'] } |
Chaîne de caractères et tableau
Section intitulée « Chaîne de caractères et tableau »| Opérateur | Description | Exemple |
|---|---|---|
contains | La chaîne contient la sous-chaîne, ou le tableau contient l’élément | { field: 'traits.company', operator: 'contains', value: 'Corp' } |
notContains | Inverse de contains | { field: 'traits.locale', operator: 'notContains', value: 'zh' } |
Existence
Section intitulée « Existence »| Opérateur | Description | Exemple |
|---|---|---|
exists | La valeur n’est ni undefined ni null | { field: 'traits.company', operator: 'exists', value: true } |
notExists | La valeur est undefined ou null | { field: 'traits.signupDate', operator: 'notExists', value: true } |
Intervalle
Section intitulée « Intervalle »| Opérateur | Description | Exemple |
|---|---|---|
between | Le nombre est dans [min, max] (inclus) | { field: 'signals.sessionCount', operator: 'between', value: [5, 20] } |
Correspondance de motif
Section intitulée « Correspondance de motif »| Opérateur | Description | Exemple |
|---|---|---|
matches | La chaîne correspond à une expression régulière | { field: 'traits.company', operator: 'matches', value: '^Acme.*' } |
Logique booléenne
Section intitulée « Logique booléenne »all — ET
Section intitulée « all — ET »Chaque condition du tableau doit correspondre :
conditions: { all: [ { field: 'traits.role', operator: 'eq', value: 'admin' }, { field: 'traits.plan', operator: 'eq', value: 'enterprise' }, { field: 'signals.sessionCount', operator: 'gte', value: 5 }, ],}any — OU
Section intitulée « any — OU »Au moins une condition doit correspondre :
conditions: { any: [ { field: 'maturity', operator: 'eq', value: 'new' }, { field: 'maturity', operator: 'eq', value: 'onboarding' }, ],}not — Négation
Section intitulée « not — Négation »Inverse le résultat d’une condition ou d’un groupe :
conditions: { not: { field: 'traits.plan', operator: 'eq', value: 'free' },}Conditions imbriquées
Section intitulée « Conditions imbriquées »Les groupes peuvent être imbriqués pour exprimer une logique complexe :
// (admin AND enterprise) OR (power user with 50+ sessions)conditions: { any: [ { all: [ { field: 'traits.role', operator: 'eq', value: 'admin' }, { field: 'traits.plan', operator: 'eq', value: 'enterprise' }, ], }, { all: [ { field: 'maturity', operator: 'eq', value: 'power' }, { field: 'signals.sessionCount', operator: 'gte', value: 50 }, ], }, ],}Ordonnancement par priorité
Section intitulée « Ordonnancement par priorité »Les règles sont évaluées de la priorité la plus haute à la plus basse. La première correspondance l’emporte.
const rules = [ { id: 'vip-override', adaptationId: 'dashboard', priority: 100, // Evaluated first conditions: { all: [{ field: 'traits.role', operator: 'eq', value: 'vip' }] }, action: { type: 'show', variantId: 'vip-dashboard' }, }, { id: 'enterprise-dashboard', adaptationId: 'dashboard', priority: 50, // Evaluated second conditions: { all: [{ field: 'traits.plan', operator: 'eq', value: 'enterprise' }] }, action: { type: 'show', variantId: 'advanced' }, }, { id: 'default-dashboard', adaptationId: 'dashboard', priority: 1, // Catch-all conditions: {}, // Empty group matches everything action: { type: 'show', variantId: 'standard' }, },]Chemins de champs
Section intitulée « Chemins de champs »Les champs utilisent la notation pointée pour accéder aux valeurs imbriquées dans le UserContext :
| Chemin | Résout vers |
|---|---|
maturity | context.maturity (une chaîne) |
traits.role | context.traits.role |
traits.companySize | context.traits.companySize |
signals.sessionCount | context.signals.sessionCount |
signals.featureUsage.export | context.signals.featureUsage.export |
signals.clickMap.nav-settings | context.signals.clickMap['nav-settings'] |
Types d’actions
Section intitulée « Types d’actions »Sélectionne une variante spécifique à rendre :
action: { type: 'show', variantId: 'guided-tour' }Masque entièrement le point d’adaptation :
action: { type: 'hide' }Réordonne les éléments (utilisé avec AdaptiveNav) :
action: { type: 'reorder', order: ['analytics', 'settings', 'home'] }Passe des props arbitraires au composant correspondant :
action: { type: 'modify', props: { showBetaBadge: true, maxItems: 10 } }Bonnes pratiques
Section intitulée « Bonnes pratiques »- Nommez les règles de manière descriptive —
new-user-guided-tourest plus clair querule-1 - Utilisez des écarts de priorité — Laissez de la marge entre les priorités (10, 20, 30) pour pouvoir insérer des règles plus tard sans renuméroter
- Gardez les conditions peu profondes — Les arbres booléens profondément imbriqués sont difficiles à déboguer ; préférez des structures plus plates
- Testez les règles en isolation — La classe
RulesEnginepeut être instanciée directement pour les tests unitaires - Préférez
inà plusieurseq—{ operator: 'in', value: ['pro', 'enterprise'] }est plus propre que deux conditionsany