✅ ajoute les tests de generateQuestion (et corrige le moteur pour qu'ils passent à nouveau)
parent
6ff886053d
commit
309c63872f
|
@ -135,24 +135,49 @@ let fillVariableNode = (rules, rule, filter) => parseResult => {
|
|||
let variable = findRuleByDottedName(parsedRules, dottedName),
|
||||
variableHasFormula = variable.formule != null,
|
||||
variableHasCond = variable['applicable si'] != null || variable['non applicable si'] != null,
|
||||
situationValue = evaluateVariable(situation, dottedName, variable),
|
||||
needsEvaluation = (variableHasCond || variableHasFormula) && situationValue == null,
|
||||
parsedRule = needsEvaluation
|
||||
? evaluateNode(cache, situation, parsedRules, variable)
|
||||
: variable,
|
||||
situationValue = evaluateVariable(situation, dottedName, variable),
|
||||
needsEvaluation = situationValue == null && (variableHasCond || variableHasFormula),
|
||||
// evaluateVariable renvoit la valeur déduite de la situation courante renseignée par l'utilisateur
|
||||
explanation = parsedRule,
|
||||
nodeValue =
|
||||
situationValue != null
|
||||
? situationValue // cette variable a été directement renseignée
|
||||
: variableHasCond || variableHasFormula
|
||||
? parsedRule.nodeValue // la valeur du calcul fait foi
|
||||
: null, // elle restera donc nulle
|
||||
missingVariables = nodeValue != null ? {} :
|
||||
{
|
||||
...(needsEvaluation ? parsedRule.missingVariables : {}),
|
||||
...(!situationValue ? { [dottedName]: 1 } : {}),
|
||||
}
|
||||
explanation = needsEvaluation
|
||||
? evaluateNode(cache, situation, parsedRules, variable)
|
||||
: variable
|
||||
|
||||
let nodeValue;
|
||||
let missingVariables;
|
||||
|
||||
// SITUATION 1 : La variable est directement renseignée
|
||||
if (situationValue != null) {
|
||||
nodeValue = situationValue;
|
||||
missingVariables = {};
|
||||
}
|
||||
// SITUATION 2 : La variable est calculée
|
||||
if (situationValue == null && variableHasFormula) {
|
||||
nodeValue = explanation.nodeValue;
|
||||
missingVariables = explanation.missingVariables;
|
||||
}
|
||||
// SITUATION 3 : La variable est une question sans condition dont la valeur n'a pas été renseignée
|
||||
if(situationValue == null && !variableHasFormula && !variableHasCond) {
|
||||
nodeValue = null
|
||||
missingVariables = { [dottedName]: 1}
|
||||
}
|
||||
// SITUATION 4 : La variable est une question avec conditions
|
||||
if(situationValue == null && !variableHasFormula && variableHasCond) {
|
||||
// SITUATION 4.1 : La condition est connue et vrai
|
||||
if (explanation.isApplicable) {
|
||||
nodeValue = explanation.nodeValue,
|
||||
missingVariables = { [dottedName]: 1}
|
||||
}
|
||||
// SITUATION 4.2 : La condition est connue et fausse
|
||||
if (explanation.isApplicable === false) {
|
||||
nodeValue = explanation.nodeValue
|
||||
missingVariables = {}
|
||||
}
|
||||
// SITUATION 4.3 : La condition n'est pas connue
|
||||
if (explanation.isApplicable == null) {
|
||||
nodeValue = null
|
||||
missingVariables = explanation.missingVariables
|
||||
}
|
||||
}
|
||||
|
||||
cache[cacheName] = rewriteNode(
|
||||
node,
|
||||
|
|
|
@ -0,0 +1,428 @@
|
|||
import { expect } from 'chai'
|
||||
import {
|
||||
collectMissingVariables,
|
||||
getNextSteps
|
||||
} from '../source/engine/generateQuestions'
|
||||
import { enrichRule, rules as realRules } from '../source/engine/rules'
|
||||
import { analyse, parseAll } from '../source/engine/traverse'
|
||||
|
||||
let stateSelector = () => null
|
||||
|
||||
describe('collectMissingVariables', function() {
|
||||
it('should identify missing variables', function() {
|
||||
let rawRules = [
|
||||
{
|
||||
nom: 'startHere',
|
||||
formule: 2,
|
||||
'non applicable si': 'sum . evt . ko',
|
||||
espace: 'sum'
|
||||
},
|
||||
{
|
||||
nom: 'evt',
|
||||
espace: 'sum',
|
||||
formule: { 'une possibilité': ['ko'] },
|
||||
titre: 'Truc',
|
||||
question: '?'
|
||||
},
|
||||
{ nom: 'ko', espace: 'sum . evt' }
|
||||
],
|
||||
rules = parseAll(rawRules.map(enrichRule)),
|
||||
analysis = analyse(rules, 'startHere')(stateSelector),
|
||||
result = collectMissingVariables(analysis.targets)
|
||||
|
||||
expect(result).to.include('sum . evt . ko')
|
||||
})
|
||||
|
||||
it('should identify missing variables mentioned in expressions', function() {
|
||||
let rawRules = [
|
||||
{
|
||||
nom: 'startHere',
|
||||
formule: 2,
|
||||
'non applicable si': 'evt . nyet > evt . nope',
|
||||
espace: 'sum'
|
||||
},
|
||||
{ nom: 'nope', espace: 'sum . evt' },
|
||||
{ nom: 'nyet', espace: 'sum . evt' }
|
||||
],
|
||||
rules = parseAll(rawRules.map(enrichRule)),
|
||||
analysis = analyse(rules, 'startHere')(stateSelector),
|
||||
result = collectMissingVariables(analysis.targets)
|
||||
|
||||
expect(result).to.include('sum . evt . nyet')
|
||||
expect(result).to.include('sum . evt . nope')
|
||||
})
|
||||
|
||||
it('should ignore missing variables in the formula if not applicable', function() {
|
||||
let rawRules = [
|
||||
{
|
||||
nom: 'startHere',
|
||||
formule: 'trois',
|
||||
'non applicable si': '3 > 2',
|
||||
espace: 'sum'
|
||||
},
|
||||
{ nom: 'trois', espace: 'sum' }
|
||||
],
|
||||
rules = parseAll(rawRules.map(enrichRule)),
|
||||
analysis = analyse(rules, 'startHere')(stateSelector),
|
||||
result = collectMissingVariables(analysis.targets)
|
||||
|
||||
expect(result).to.be.empty
|
||||
})
|
||||
|
||||
it('should not report missing variables when "one of these" short-circuits', function() {
|
||||
let rawRules = [
|
||||
{
|
||||
nom: 'startHere',
|
||||
formule: 'trois',
|
||||
'non applicable si': {
|
||||
'une de ces conditions': ['3 > 2', 'trois']
|
||||
},
|
||||
espace: 'sum'
|
||||
},
|
||||
{ nom: 'trois', espace: 'sum' }
|
||||
],
|
||||
rules = parseAll(rawRules.map(enrichRule)),
|
||||
analysis = analyse(rules, 'startHere')(stateSelector),
|
||||
result = collectMissingVariables(analysis.targets)
|
||||
|
||||
expect(result).to.be.empty
|
||||
})
|
||||
|
||||
it('should report "une possibilité" as a missing variable even though it has a formula', function() {
|
||||
let rawRules = [
|
||||
{ nom: 'startHere', formule: 'trois', espace: 'top' },
|
||||
{
|
||||
nom: 'trois',
|
||||
formule: { 'une possibilité': ['ko'] },
|
||||
espace: 'top'
|
||||
}
|
||||
],
|
||||
rules = parseAll(rawRules.map(enrichRule)),
|
||||
analysis = analyse(rules, 'startHere')(stateSelector),
|
||||
result = collectMissingVariables(analysis.targets)
|
||||
|
||||
expect(result).to.include('top . trois')
|
||||
})
|
||||
|
||||
it('should not report missing variables when "une possibilité" is inapplicable', function() {
|
||||
let rawRules = [
|
||||
{ nom: 'startHere', formule: 'trois', espace: 'top' },
|
||||
{
|
||||
nom: 'trois',
|
||||
formule: { 'une possibilité': ['ko'] },
|
||||
'non applicable si': 1,
|
||||
espace: 'top'
|
||||
}
|
||||
],
|
||||
rules = parseAll(rawRules.map(enrichRule)),
|
||||
analysis = analyse(rules, 'startHere')(stateSelector),
|
||||
result = collectMissingVariables(analysis.targets)
|
||||
|
||||
expect(result).to.be.empty
|
||||
null
|
||||
})
|
||||
|
||||
it('should not report missing variables when "une possibilité" was answered', function() {
|
||||
let mySelector = name => ({ 'top . trois': 'ko' }[name])
|
||||
|
||||
let rawRules = [
|
||||
{ nom: 'startHere', formule: 'trois', espace: 'top' },
|
||||
{
|
||||
nom: 'trois',
|
||||
formule: { 'une possibilité': ['ko'] },
|
||||
espace: 'top'
|
||||
}
|
||||
],
|
||||
rules = parseAll(rawRules.map(enrichRule)),
|
||||
analysis = analyse(rules, 'startHere')(mySelector),
|
||||
result = collectMissingVariables(analysis.targets)
|
||||
|
||||
expect(result).to.be.empty
|
||||
})
|
||||
|
||||
it('should report missing variables in switch statements', function() {
|
||||
let rawRules = [
|
||||
{
|
||||
nom: 'startHere',
|
||||
formule: {
|
||||
'aiguillage numérique': {
|
||||
'11 > dix': '1000%',
|
||||
'3 > dix': '1100%',
|
||||
'1 > dix': '1200%'
|
||||
}
|
||||
},
|
||||
espace: 'top'
|
||||
},
|
||||
{ nom: 'dix', espace: 'top' }
|
||||
],
|
||||
rules = parseAll(rawRules.map(enrichRule)),
|
||||
analysis = analyse(rules, 'startHere')(stateSelector),
|
||||
result = collectMissingVariables(analysis.targets)
|
||||
|
||||
expect(result).to.include('top . dix')
|
||||
})
|
||||
|
||||
it('should report missing variables in variations', function() {
|
||||
let rawRules = [
|
||||
{
|
||||
nom: 'startHere',
|
||||
formule: { somme: ['variations'] },
|
||||
espace: 'top'
|
||||
},
|
||||
{
|
||||
nom: 'variations',
|
||||
espace: 'top',
|
||||
formule: {
|
||||
barème: {
|
||||
assiette: 2008,
|
||||
variations: [
|
||||
{
|
||||
si: 'dix',
|
||||
'multiplicateur des tranches': 'deux',
|
||||
tranches: [
|
||||
{ 'en-dessous de': 1, taux: 0.1 },
|
||||
{ de: 1, à: 2, taux: 'trois' },
|
||||
{ 'au-dessus de': 2, taux: 10 }
|
||||
]
|
||||
},
|
||||
{
|
||||
si: '3 > 4',
|
||||
'multiplicateur des tranches': 'quatre',
|
||||
tranches: [
|
||||
{ 'en-dessous de': 1, taux: 0.1 },
|
||||
{ de: 1, à: 2, taux: 1.8 },
|
||||
{ 'au-dessus de': 2, taux: 10 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{ nom: 'dix', espace: 'top' },
|
||||
{ nom: 'deux', espace: 'top' },
|
||||
{ nom: 'trois', espace: 'top' },
|
||||
{ nom: 'quatre', espace: 'top' }
|
||||
],
|
||||
rules = parseAll(rawRules.map(enrichRule)),
|
||||
analysis = analyse(rules, 'startHere')(stateSelector),
|
||||
result = collectMissingVariables(analysis.targets)
|
||||
|
||||
expect(result).to.include('top . dix')
|
||||
expect(result).to.include('top . deux')
|
||||
expect(result).not.to.include('top . quatre')
|
||||
// TODO
|
||||
// expect(result).to.include('top . trois')
|
||||
})
|
||||
|
||||
it('should not report missing variables in irrelevant variations', function() {
|
||||
let rawRules = [
|
||||
{
|
||||
nom: 'startHere',
|
||||
formule: { somme: ['variations'] },
|
||||
espace: 'top'
|
||||
},
|
||||
{
|
||||
nom: 'variations',
|
||||
espace: 'top',
|
||||
formule: {
|
||||
barème: {
|
||||
assiette: 2008,
|
||||
'multiplicateur des tranches': 1000,
|
||||
variations: [
|
||||
{
|
||||
si: 'dix',
|
||||
tranches: [
|
||||
{ 'en-dessous de': 1, taux: 0.1 },
|
||||
{ de: 1, à: 2, taux: 'deux' },
|
||||
{ 'au-dessus de': 2, taux: 10 }
|
||||
]
|
||||
},
|
||||
{
|
||||
si: '3 > 2',
|
||||
tranches: [
|
||||
{ 'en-dessous de': 1, taux: 0.1 },
|
||||
{ de: 1, à: 2, taux: 1.8 },
|
||||
{ 'au-dessus de': 2, taux: 10 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{ nom: 'dix', espace: 'top' },
|
||||
{ nom: 'deux', espace: 'top' }
|
||||
],
|
||||
rules = parseAll(rawRules.map(enrichRule)),
|
||||
analysis = analyse(rules, 'startHere')(stateSelector),
|
||||
result = collectMissingVariables(analysis.targets)
|
||||
|
||||
expect(result).to.be.empty
|
||||
})
|
||||
|
||||
it('should not report missing variables in switch for consequences of false conditions', function() {
|
||||
let rawRules = [
|
||||
{
|
||||
nom: 'startHere',
|
||||
formule: {
|
||||
'aiguillage numérique': {
|
||||
'8 > 10': '1000%',
|
||||
'1 > 2': 'dix'
|
||||
}
|
||||
},
|
||||
espace: 'top'
|
||||
},
|
||||
{ nom: 'dix', espace: 'top' }
|
||||
],
|
||||
rules = parseAll(rawRules.map(enrichRule)),
|
||||
analysis = analyse(rules, 'startHere')(stateSelector),
|
||||
result = collectMissingVariables(analysis.targets)
|
||||
|
||||
expect(result).to.be.empty
|
||||
})
|
||||
|
||||
it('should report missing variables in consequence when its condition is unresolved', function() {
|
||||
let rawRules = [
|
||||
{
|
||||
nom: 'startHere',
|
||||
formule: {
|
||||
'aiguillage numérique': {
|
||||
'10 > 11': '1000%',
|
||||
'3 > dix': {
|
||||
douze: '560%',
|
||||
'1 > 2': '75015%'
|
||||
}
|
||||
}
|
||||
},
|
||||
espace: 'top'
|
||||
},
|
||||
{ nom: 'douze', espace: 'top' },
|
||||
{ nom: 'dix', espace: 'top' }
|
||||
],
|
||||
rules = parseAll(rawRules.map(enrichRule)),
|
||||
analysis = analyse(rules, 'startHere')(stateSelector),
|
||||
result = collectMissingVariables(analysis.targets)
|
||||
|
||||
expect(result).to.include('top . dix')
|
||||
expect(result).to.include('top . douze')
|
||||
})
|
||||
|
||||
it('should not report missing variables when a switch short-circuits', function() {
|
||||
let rawRules = [
|
||||
{
|
||||
nom: 'startHere',
|
||||
formule: {
|
||||
'aiguillage numérique': {
|
||||
'11 > 10': '1000%',
|
||||
'3 > dix': '1100%',
|
||||
'1 > dix': '1200%'
|
||||
}
|
||||
},
|
||||
espace: 'top'
|
||||
},
|
||||
{ nom: 'dix', espace: 'top' }
|
||||
],
|
||||
rules = parseAll(rawRules.map(enrichRule)),
|
||||
analysis = analyse(rules, 'startHere')(stateSelector),
|
||||
result = collectMissingVariables(analysis.targets)
|
||||
|
||||
expect(result).to.be.empty
|
||||
})
|
||||
})
|
||||
|
||||
describe('nextSteps', function() {
|
||||
it('should generate questions', function() {
|
||||
let rawRules = [
|
||||
{ nom: 'sum', formule: { somme: [2, 'deux'] }, espace: 'top' },
|
||||
{
|
||||
nom: 'deux',
|
||||
formule: 2,
|
||||
'non applicable si': "top . sum . evt = 'ko'",
|
||||
espace: 'top'
|
||||
},
|
||||
{
|
||||
nom: 'evt',
|
||||
espace: 'top . sum',
|
||||
formule: { 'une possibilité': ['ko'] },
|
||||
titre: 'Truc',
|
||||
question: '?'
|
||||
},
|
||||
{ nom: 'ko', espace: 'top . sum . evt' }
|
||||
],
|
||||
rules = parseAll(rawRules.map(enrichRule)),
|
||||
analysis = analyse(rules, 'sum')(stateSelector),
|
||||
result = collectMissingVariables(analysis.targets)
|
||||
|
||||
expect(result).to.have.lengthOf(1)
|
||||
expect(result[0]).to.equal('top . sum . evt')
|
||||
})
|
||||
|
||||
it('should generate questions from the real rules, experimental version', function() {
|
||||
let stateSelector = name =>
|
||||
({
|
||||
'contrat salarié . type de contrat': 'CDI',
|
||||
'entreprise . effectif': '50'
|
||||
}[name])
|
||||
|
||||
let rules = parseAll(realRules.map(enrichRule)),
|
||||
analysis = analyse(rules, 'salaire')(stateSelector),
|
||||
result = collectMissingVariables(analysis.targets)
|
||||
|
||||
expect(result[0]).to.equal('contrat salarié . temps partiel')
|
||||
})
|
||||
|
||||
it('should ask "motif CDD" if "CDD" applies', function() {
|
||||
let stateSelector = name =>
|
||||
({
|
||||
'contrat salarié . type de contrat': 'CDD',
|
||||
'contrat salarié . salaire . brut de base': '2300'
|
||||
}[name])
|
||||
|
||||
let rules = parseAll(realRules.map(enrichRule)),
|
||||
analysis = analyse(rules, 'contrat salarié . salaire . net')(
|
||||
stateSelector
|
||||
),
|
||||
result = collectMissingVariables(analysis.targets)
|
||||
|
||||
expect(result).to.include('contrat salarié . CDD . motif')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getNextSteps', function() {
|
||||
it('should give priority to questions that advance most targets', function() {
|
||||
let missingVariablesByTarget = {
|
||||
chargé: {
|
||||
effectif: 34.01,
|
||||
cadre: 30
|
||||
},
|
||||
net: {
|
||||
cadre: 10.1
|
||||
},
|
||||
aides: {
|
||||
effectif: 32.0,
|
||||
cadre: 10
|
||||
}
|
||||
}
|
||||
|
||||
let result = getNextSteps(missingVariablesByTarget)
|
||||
|
||||
expect(result[0]).to.equal('cadre')
|
||||
})
|
||||
|
||||
it('should give priority to questions by total weight when advancing the same target count', function() {
|
||||
let missingVariablesByTarget = {
|
||||
chargé: {
|
||||
effectif: 24.01,
|
||||
cadre: 30
|
||||
},
|
||||
net: {
|
||||
effectif: 24.01,
|
||||
cadre: 10.1
|
||||
},
|
||||
aides: {}
|
||||
}
|
||||
|
||||
let result = getNextSteps(missingVariablesByTarget)
|
||||
|
||||
expect(result[0]).to.equal('effectif')
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue