⚙️ Adapation du moteur à la simulation multiple

pull/138/head
mama 2017-11-07 19:46:40 +01:00
parent 73ed97d647
commit 92fed2c520
13 changed files with 181 additions and 204 deletions

View File

@ -8,13 +8,13 @@ import { withRouter } from 'react-router'
import './Results.css'
import {clearDict} from 'Engine/traverse'
import {encodeRuleName} from 'Engine/rules'
import {getObjectives} from 'Engine/generateQuestions'
import RuleValueVignette from './rule/RuleValueVignette'
@withRouter
@connect(
state => ({
analysedSituation: state.analysedSituation,
analysis: state.analysis,
targetName: state.targetName,
conversationStarted: !R.isEmpty(state.form),
conversationFirstAnswer: R.path(['form', 'conversation', 'values'])(state),
situationGate: state.situationGate
@ -23,37 +23,39 @@ import RuleValueVignette from './rule/RuleValueVignette'
export default class Results extends Component {
componentDidMount(){
setTimeout(() =>
this.props.setElementHeight(this.el.offsetHeight)
, 1)
this.el && this.props.setElementHeight(this.el.offsetHeight)
, 1)
}
render() {
let {
analysedSituation,
analysis,
targetName,
conversationStarted,
conversationFirstAnswer: showResults,
situationGate,
location
} = this.props
let explanation = R.has('root', analysedSituation) && clearDict() && getObjectives(situationGate, analysedSituation.root, analysedSituation.parsedRules)
if (!analysis) return null
if (!explanation) return null
let {targets} = analysis
clearDict() // pourquoi ??
let onRulePage = R.contains('/regle/')(location.pathname)
return (
<section ref={el => this.el = el} id="results" className={classNames({show: showResults})}>
{onRulePage && conversationStarted ?
<div id ="results-actions">
<Link id="toSimulation" to={"/simu/" + encodeRuleName(analysedSituation.root.name)}>
<Link id="toSimulation" to={'/simu/' + encodeRuleName(targetName)}>
<i className="fa fa-arrow-circle-left" aria-hidden="true"></i>Reprendre la simulation
</Link>
</div>
: <div id="results-titles">
<h2><i className="fa fa-calculator" aria-hidden="true"></i>{explanation.length == 1 ? 'Votre résultat' : 'Vos résultats'}<span>·</span><small>Cliquez pour comprendre chaque calcul</small></h2>
<h2><i className="fa fa-calculator" aria-hidden="true"></i>{targets.length == 1 ? 'Votre résultat' : 'Vos résultats'}<span>·</span><small>Cliquez pour comprendre chaque calcul</small></h2>
</div>
}
<ul>
{explanation.map( rule => <li key={rule.nom}>
{targets.map( rule => <li key={rule.nom}>
<RuleValueVignette {...rule} conversationStarted={conversationStarted} />
</li>)}
</ul>

View File

@ -24,11 +24,10 @@ import ReactPiwik from './Tracker'
foldedSteps: state.foldedSteps,
extraSteps: state.extraSteps,
themeColours: state.themeColours,
analysedSituation: state.analysedSituation,
situationGate: state.situationGate,
}),
dispatch => ({
startConversation: rootVariable => dispatch({type: START_CONVERSATION, rootVariable}),
startConversation: targetName => dispatch({type: START_CONVERSATION, targetName}),
resetForm: () => dispatch(reset('conversation'))
})
)
@ -56,7 +55,8 @@ export default class extends Component {
this.props.startConversation(name)
}
render(){
if (!this.rule.formule) return <Redirect to={"/regle/" + this.name}/>
if (!this.rule.formule && !R.path(['simulateur', 'objectifs'], this.rule))
return <Redirect to={'/regle/' + this.name} />
let
{started} = this.state,

View File

@ -5,7 +5,7 @@ import {
rules,
disambiguateRuleReference
} from "Engine/rules.js"
import { analyseSituation } from "Engine/traverse"
import { analyse } from "Engine/traverse"
import "./Examples.css"
export default class Examples extends Component {
@ -23,10 +23,10 @@ export default class Examples extends Component {
R.fromPairs
)(ex.situation)
let runExemple = analyseSituation(rules, rule.name)(
let runExemple = analyse(rules, rule.name)(
v => exempleSituation[v]
),
exempleValue = runExemple.nodeValue
exempleValue = runExemple.targets[0].nodeValue
return {
...ex,

View File

@ -3,7 +3,7 @@ import { connect } from "react-redux"
import R from "ramda"
import "./Rule.css"
import { rules, decodeRuleName, nameLeaf } from "Engine/rules.js"
import { analyseSituation } from "Engine/traverse"
import { analyse } from "Engine/traverse"
import { START_CONVERSATION } from "../../actions"
import possiblesDestinataires from "Règles/ressources/destinataires/destinataires.yaml"
import { capitalise0 } from "../../utils"
@ -19,8 +19,8 @@ import {createMarkdownDiv} from 'Engine/marked'
form: state.form
}),
dispatch => ({
startConversation: rootVariable =>
dispatch({ type: START_CONVERSATION, rootVariable })
startConversation: targetName =>
dispatch({ type: START_CONVERSATION, targetName })
})
)
export default class Rule extends Component {
@ -36,9 +36,9 @@ export default class Rule extends Component {
}
}
setRule(name) {
this.rule = analyseSituation(rules, nameLeaf(decodeRuleName(name)))(
this.rule = analyse(rules, nameLeaf(decodeRuleName(name)))(
this.props.situationGate
)
).targets[0]
}
componentWillMount() {
let { match: { params: { name } } } = this.props

View File

@ -8,9 +8,9 @@ import Select from 'Components/conversation/select/Select'
import SelectAtmp from 'Components/conversation/select/SelectTauxRisque'
import formValueTypes from 'Components/conversation/formValueTypes'
import {findInversion} from './traverse'
import {findRuleByDottedName} from './rules'
import {collectNodeMissing, evaluateNode} from './evaluation'
import {collectNodeMissing} from './evaluation'
/*
COLLECTE DES VARIABLES MANQUANTES
@ -27,33 +27,8 @@ import {collectNodeMissing, evaluateNode} from './evaluation'
missingVariables: {variable: [objectives]}
*/
// On peut travailler sur une somme, les objectifs sont alors les variables de cette somme.
// Ou sur une variable unique ayant une formule ou une conodition 'non applicable si', elle est elle-même le seul objectif
export let getObjectives = (situationGate, root, parsedRules) => {
let formuleType = R.path(["formule", "explanation", "name"])(root)
let targets = formuleType == "somme"
? R.pluck(
"dottedName",
R.path(["formule", "explanation", "explanation"])(root)
)
: (root.formule || root['non applicable si'] || root['applicable si']) ? [root.dottedName] : null,
names = targets ? R.reject(R.isNil)(targets) : []
let inversion = findInversion(situationGate, parsedRules, root)
if (inversion){
return [evaluateNode(situationGate, parsedRules, inversion.fixedObjectiveRule)]
}
let findAndEvaluate = name => evaluateNode(situationGate,parsedRules,findRuleByDottedName(parsedRules,name))
return R.map(findAndEvaluate,names)
}
export let collectMissingVariables = (groupMethod='groupByMissingVariable') => (situationGate, {root, parsedRules}) => {
return R.pipe(
R.curry(getObjectives)(situationGate),
export let collectMissingVariables = targets =>
R.pipe(
R.chain( v =>
R.pipe(
collectNodeMissing,
@ -62,17 +37,17 @@ export let collectMissingVariables = (groupMethod='groupByMissingVariable') => (
)(v)
),
//groupBy missing variable but remove mv from value, it's now in the key
R.groupBy(groupMethod == 'groupByMissingVariable' ? R.last : R.head),
R.map(R.map(groupMethod == 'groupByMissingVariable' ? R.head : R.last))
R.groupBy(R.last),
R.map(R.map(R.head))
// below is a hand implementation of above... function composition can be nice sometimes :')
// R.reduce( (memo, [mv, dependencyOf]) => ({...memo, [mv]: [...(memo[mv] || []), dependencyOf] }), {})
)(root, parsedRules)
}
)(targets)
export let nextSteps = (situationGate, flatRules, analysedSituation) => {
export let nextSteps = (situationGate, flatRules, analysis) => {
let impact = ([variable, objectives]) => R.length(objectives)
let missingVariables = collectMissingVariables('groupByMissingVariable')(situationGate, analysedSituation),
let missingVariables = collectMissingVariables(analysis.targets),
pairs = R.toPairs(missingVariables),
sortedPairs = R.sort(R.descend(impact), pairs)

View File

@ -143,7 +143,7 @@ let fillVariableNode = (rules, rule) => parseResult => {
variablePartialName = fragments.join(' . '),
dottedName = disambiguateRuleReference(rules, rule, variablePartialName)
let jsx = (nodeValue) => (
let jsx = nodeValue => (
<Leaf classes="variable" name={fragments.join(' . ')} value={nodeValue} />
)
@ -353,7 +353,8 @@ let treat = (rules, rule) => rawNode => {
let mecanisms = R.intersection(R.keys(rawNode), R.keys(knownMecanisms))
if (mecanisms.length != 1) {
console.log( // eslint-disable-line no-console
console.log(
// eslint-disable-line no-console
'Erreur : On ne devrait reconnaître que un et un seul mécanisme dans cet objet',
mecanisms,
rawNode
@ -424,17 +425,16 @@ export let findInversion = (situationGate, rules, rule) => {
}
export let treatRuleRoot = (rules, rule) => {
let evaluate = (situationGate, parsedRules, r) => {
let inversion = findInversion(situationGate, parsedRules, r)
if (inversion) {
let {fixedObjectiveValue, fixedObjectiveRule} = inversion
let
fx = x => evaluateNode(
n => (r.name === n || n === 'sys.filter') ? x : situationGate(n), //TODO pourquoi doit-on nous préoccuper de sys.filter ?
parsedRules,
fixedObjectiveRule
).nodeValue,
let { fixedObjectiveValue, fixedObjectiveRule } = inversion
let fx = x =>
evaluateNode(
n => (r.name === n || n === 'sys.filter' ? x : situationGate(n)), //TODO pourquoi doit-on nous préoccuper de sys.filter ?
parsedRules,
fixedObjectiveRule
).nodeValue,
tolerancePercentage = 0.00001,
// cette fonction détermine la racine d'une fonction sans faire trop d'itérations
nodeValue = uniroot(
@ -445,18 +445,23 @@ export let treatRuleRoot = (rules, rule) => {
100
)
// si fx renvoie null pour une valeur numérique standard, disons 1000, on peut
// considérer que l'inversion est impossible du fait de variables manquantes
// TODO fx peut être null pour certains x, et valide pour d'autres : on peut implémenter ici le court-circuit
return fx(1000) == null ? {
...r,
nodeValue: null,
inversionMissingVariables: collectNodeMissing(evaluateNode(
n => (r.name === n || n === 'sys.filter') ? 1000 : situationGate(n), //TODO pourquoi doit-on nous préoccuper de sys.filter ?
parsedRules,
fixedObjectiveRule
))
} : {...r, nodeValue}
// si fx renvoie null pour une valeur numérique standard, disons 1000, on peut
// considérer que l'inversion est impossible du fait de variables manquantes
// TODO fx peut être null pour certains x, et valide pour d'autres : on peut implémenter ici le court-circuit
return fx(1000) == null
? {
...r,
nodeValue: null,
inversionMissingVariables: collectNodeMissing(
evaluateNode(
n =>
r.name === n || n === 'sys.filter' ? 1000 : situationGate(n), //TODO pourquoi doit-on nous préoccuper de sys.filter ?
parsedRules,
fixedObjectiveRule
)
)
}
: { ...r, nodeValue }
}
let evolveRule = R.curry(evaluateNode)(situationGate, parsedRules),
@ -485,7 +490,7 @@ export let treatRuleRoot = (rules, rule) => {
return { ...evaluated, nodeValue, isApplicable }
}
let collectMissing = (rule) => {
let collectMissing = rule => {
let {
formule,
isApplicable,
@ -498,8 +503,7 @@ export let treatRuleRoot = (rules, rule) => {
return inversionMissingVariables
}
let
condMissing =
let condMissing =
val(notApplicable) === true
? []
: val(applicable) === false
@ -599,12 +603,20 @@ let evolveCond = (name, rule, rules) => value => {
}
}
export let analyseSituation = (rules, rootVariable) => situationGate => {
let { root } = analyseTopDown(rules, rootVariable)(situationGate)
return root
export let getTargets = (target, rules) => {
let multiSimulation = R.path(['simulateur', 'objectifs'])(target)
let targets = multiSimulation
? // On a un simulateur qui définit une liste d'objectifs
multiSimulation
.map(n => disambiguateRuleReference(rules, target, n))
.map(n => findRuleByDottedName(rules, n))
: // Sinon on est dans le cas d'une simple variable d'objectif
[target]
return targets
}
export let analyseTopDown = (rules, rootVariable) => situationGate => {
export let analyse = (rules, targetName) => situationGate => {
clearDict()
let /*
La fonction treatRuleRoot va descendre l'arbre de la règle `rule` et produire un AST, un objet contenant d'autres objets contenant d'autres objets...
@ -618,15 +630,17 @@ export let analyseTopDown = (rules, rootVariable) => situationGate => {
parsedRules = R.map(treatOne, rules),
// TODO: we should really make use of namespaces at this level, in particular
// setRule in Rule.js needs to get smarter and pass dottedName
rootRule = findRuleByName(parsedRules, rootVariable),
parsedTarget = findRuleByName(parsedRules, targetName),
/*
Ce n'est que dans cette nouvelle étape que l'arbre est vraiment évalué.
Auparavant, l'évaluation était faite lors de la construction de l'AST.
*/
root = evaluateNode(situationGate, parsedRules, rootRule)
targets = getTargets(parsedTarget, parsedRules).map(t =>
evaluateNode(situationGate, parsedRules, t)
)
return {
root,
targets,
parsedRules
}
}

View File

@ -9,7 +9,7 @@ import {nextSteps, makeQuestion} from 'Engine/generateQuestions'
import computeThemeColours from 'Components/themeColours'
import { STEP_ACTION, START_CONVERSATION, EXPLAIN_VARIABLE, CHANGE_THEME_COLOUR} from './actions'
import {analyseTopDown} from 'Engine/traverse'
import {analyse} from 'Engine/traverse'
import ReactPiwik from 'Components/Tracker';
@ -39,9 +39,9 @@ export let reduceSteps = (tracker, flatRules, answerSource) => (state, action) =
if (![START_CONVERSATION, STEP_ACTION].includes(action.type))
return state
let rootVariable = action.type == START_CONVERSATION ? action.rootVariable : state.analysedSituation.root.name
let targetName = action.type == START_CONVERSATION ? action.targetName : state.targetName
let sim = findRuleByName(flatRules, rootVariable),
let sim = findRuleByName(flatRules, targetName),
// Hard assumptions cannot be changed, they are used to specialise a simulator
// before the user sees the first question
hardAssumptions = R.pathOr({},['simulateur','hypothèses'],sim),
@ -51,18 +51,19 @@ export let reduceSteps = (tracker, flatRules, answerSource) => (state, action) =
completeSituation = assume(intermediateSituation,softAssumptions)
let situationGate = completeSituation(state),
analysedSituation = analyseTopDown(flatRules,rootVariable)(situationGate)
analysis = analyse(flatRules, targetName)(situationGate)
let newState = {
...state,
analysedSituation,
targetName,
analysis,
situationGate: situationGate,
extraSteps: [],
explainedVariable: null
}
if (action.type == START_CONVERSATION) {
let next = nextSteps(situationGate, flatRules, newState.analysedSituation)
let next = nextSteps(situationGate, flatRules, newState.analysis)
return {
...newState,
@ -74,7 +75,7 @@ export let reduceSteps = (tracker, flatRules, answerSource) => (state, action) =
tracker.push(['trackEvent', 'answer', action.step+": "+situationGate(action.step)]);
let foldedSteps = [...state.foldedSteps, state.currentQuestion],
next = nextSteps(situationGate, flatRules, newState.analysedSituation),
next = nextSteps(situationGate, flatRules, newState.analysis),
assumptionsMade = !R.isEmpty(softAssumptions),
done = next.length == 0
@ -82,10 +83,10 @@ export let reduceSteps = (tracker, flatRules, answerSource) => (state, action) =
// where the answers were previously given default reasonable assumptions
if (done && assumptionsMade) {
let newSituation = intermediateSituation(state),
reanalyse = analyseTopDown(flatRules,rootVariable)(newSituation),
extraSteps = nextSteps(newSituation, flatRules, reanalyse)
reanalysis = analyse(flatRules, targetName)(newSituation),
extraSteps = nextSteps(newSituation, flatRules, reanalysis)
tracker.push(['trackEvent', 'done', 'extra questions: '+extraSteps.length]);
tracker.push(['trackEvent', 'done', 'extra questions: '+extraSteps.length])
return {
...newState,
@ -154,7 +155,9 @@ export default reduceReducers(
extraSteps: (steps = []) => steps,
currentQuestion: (state = null) => state,
analysedSituation: (state = []) => state,
analysis: (state = null) => state,
targetName: (state = null) => state,
situationGate: (state = name => null) => state,
refine: (state = false) => state,

View File

@ -1,27 +1,12 @@
import R from 'ramda'
import {expect} from 'chai'
import {rules as realRules, enrichRule} from '../source/engine/rules'
import {analyseTopDown} from '../source/engine/traverse'
import {nextSteps, collectMissingVariables, getObjectives} from '../source/engine/generateQuestions'
import {analyse} from '../source/engine/traverse'
import {nextSteps, collectMissingVariables} from '../source/engine/generateQuestions'
let stateSelector = (name) => null
describe('getObjectives', function() {
it('should derive objectives from the root rule', 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 = rawRules.map(enrichRule),
{root, parsedRules} = analyseTopDown(rules,"startHere")(stateSelector),
result = getObjectives(stateSelector, root, parsedRules)
expect(result).to.have.lengthOf(1)
expect(result[0]).to.have.property('name','startHere')
});
});
describe('collectMissingVariables', function() {
@ -31,8 +16,8 @@ describe('collectMissingVariables', function() {
{nom: "evt", espace: "sum", formule: {"une possibilité":["ko"]}, titre: "Truc", question:"?"},
{nom: "ko", espace: "sum . evt"}],
rules = rawRules.map(enrichRule),
situation = analyseTopDown(rules,"startHere")(stateSelector),
result = collectMissingVariables()(stateSelector,situation)
analysis = analyse(rules,"startHere")(stateSelector),
result = collectMissingVariables(analysis.targets)
expect(result).to.have.property('sum . evt . ko')
});
@ -42,8 +27,8 @@ describe('collectMissingVariables', function() {
{nom: "nope", espace: "sum . evt"},
{nom: "nyet", espace: "sum . evt"}],
rules = rawRules.map(enrichRule),
situation = analyseTopDown(rules,"startHere")(stateSelector),
result = collectMissingVariables()(stateSelector,situation)
analysis = analyse(rules,"startHere")(stateSelector),
result = collectMissingVariables(analysis.targets)
expect(result).to.have.property('sum . evt . nyet')
expect(result).to.have.property('sum . evt . nope')
@ -54,8 +39,8 @@ describe('collectMissingVariables', function() {
{nom: "startHere", formule: "trois", "non applicable si" : "3 > 2", espace: "sum"},
{nom: "trois", espace: "sum"}],
rules = rawRules.map(enrichRule),
situation = analyseTopDown(rules,"startHere")(stateSelector),
result = collectMissingVariables()(stateSelector,situation)
analysis = analyse(rules,"startHere")(stateSelector),
result = collectMissingVariables(analysis.targets)
expect(result).to.deep.equal({})
});
@ -65,8 +50,8 @@ describe('collectMissingVariables', function() {
{nom: "startHere", formule: "trois", "non applicable si" : {"une de ces conditions": ["3 > 2", "trois"]}, espace: "sum"},
{nom: "trois", espace: "sum"}],
rules = rawRules.map(enrichRule),
situation = analyseTopDown(rules,"startHere")(stateSelector),
result = collectMissingVariables()(stateSelector,situation)
analysis = analyse(rules,"startHere")(stateSelector),
result = collectMissingVariables(analysis.targets)
expect(result).to.deep.equal({})
});
@ -76,8 +61,8 @@ describe('collectMissingVariables', function() {
{nom: "startHere", formule: "trois", espace: "top"},
{nom: "trois", formule: {"une possibilité":["ko"]}, espace: "top"}],
rules = rawRules.map(enrichRule),
situation = analyseTopDown(rules,"startHere")(stateSelector),
result = collectMissingVariables()(stateSelector,situation)
analysis = analyse(rules,"startHere")(stateSelector),
result = collectMissingVariables(analysis.targets)
expect(result).to.have.property('top . trois')
});
@ -87,8 +72,8 @@ describe('collectMissingVariables', function() {
{nom: "startHere", formule: "trois", espace: "top"},
{nom: "trois", formule: {"une possibilité":["ko"]}, "non applicable si": 1, espace: "top"}],
rules = rawRules.map(enrichRule),
situation = analyseTopDown(rules,"startHere")(stateSelector),
result = collectMissingVariables()(stateSelector,situation)
analysis = analyse(rules,"startHere")(stateSelector),
result = collectMissingVariables(analysis.targets)
expect(result).to.deep.equal({})
});
@ -100,8 +85,8 @@ describe('collectMissingVariables', function() {
{nom: "startHere", formule: "trois", espace: "top"},
{nom: "trois", formule: {"une possibilité":["ko"]}, espace: "top"}],
rules = rawRules.map(enrichRule),
situation = analyseTopDown(rules,"startHere")(mySelector),
result = collectMissingVariables()(mySelector,situation)
analysis = analyse(rules,"startHere")(mySelector),
result = collectMissingVariables(analysis.targets)
expect(result).to.deep.equal({})
});
@ -115,8 +100,8 @@ describe('collectMissingVariables', function() {
}}, espace: "top"},
{nom: "dix", espace: "top"}],
rules = rawRules.map(enrichRule),
situation = analyseTopDown(rules,"startHere")(stateSelector),
result = collectMissingVariables()(stateSelector,situation)
analysis = analyse(rules,"startHere")(stateSelector),
result = collectMissingVariables(analysis.targets)
expect(result).to.have.property('top . dix')
});
@ -135,8 +120,8 @@ describe('collectMissingVariables', function() {
{nom: "dix", espace: "top"},
{nom: "deux", espace: "top"}],
rules = rawRules.map(enrichRule),
situation = analyseTopDown(rules,"startHere")(stateSelector),
result = collectMissingVariables()(stateSelector,situation)
analysis = analyse(rules,"startHere")(stateSelector),
result = collectMissingVariables(analysis.targets)
expect(result).to.have.property('top . dix')
// expect(result).to.have.property('top . deux') - this is a TODO
@ -156,8 +141,8 @@ describe('collectMissingVariables', function() {
{nom: "dix", espace: "top"},
{nom: "deux", espace: "top"}],
rules = rawRules.map(enrichRule),
situation = analyseTopDown(rules,"startHere")(stateSelector),
result = collectMissingVariables()(stateSelector,situation)
analysis = analyse(rules,"startHere")(stateSelector),
result = collectMissingVariables(analysis.targets)
expect(result).to.deep.equal({})
});
@ -170,8 +155,8 @@ describe('collectMissingVariables', function() {
}}, espace: "top"},
{nom: "dix", espace: "top"}],
rules = rawRules.map(enrichRule),
situation = analyseTopDown(rules,"startHere")(stateSelector),
result = collectMissingVariables()(stateSelector,situation)
analysis = analyse(rules,"startHere")(stateSelector),
result = collectMissingVariables(analysis.targets)
expect(result).to.deep.equal({})
});
@ -193,8 +178,8 @@ describe('collectMissingVariables', function() {
{ nom: "dix", espace: "top" }
],
rules = rawRules.map(enrichRule),
situation = analyseTopDown(rules, "startHere")(stateSelector),
result = collectMissingVariables()(stateSelector, situation);
analysis = analyse(rules, "startHere")(stateSelector),
result = collectMissingVariables(analysis.targets)
expect(result).to.have.property('top . dix')
@ -210,8 +195,8 @@ describe('collectMissingVariables', function() {
}}, espace: "top"},
{nom: "dix", espace: "top"}],
rules = rawRules.map(enrichRule),
situation = analyseTopDown(rules,"startHere")(stateSelector),
result = collectMissingVariables()(stateSelector,situation)
analysis = analyse(rules,"startHere")(stateSelector),
result = collectMissingVariables(analysis.targets)
expect(result).to.deep.equal({})
});
@ -227,8 +212,8 @@ describe('nextSteps', function() {
{nom: "evt", espace: "top . sum", formule: {"une possibilité":["ko"]}, titre: "Truc", question:"?"},
{nom: "ko", espace: "top . sum . evt"}],
rules = rawRules.map(enrichRule),
situation = analyseTopDown(rules,"sum")(stateSelector),
result = nextSteps(stateSelector, rules, situation)
analysis = analyse(rules,"sum")(stateSelector),
result = nextSteps(stateSelector, rules, analysis)
expect(result).to.have.lengthOf(1)
expect(result[0]).to.equal("top . sum . evt")
@ -236,10 +221,9 @@ describe('nextSteps', function() {
it('should generate questions from the real rules', function() {
let rules = realRules.map(enrichRule),
situation = analyseTopDown(rules,"surcoût CDD")(stateSelector),
objectives = getObjectives(stateSelector, situation.root, situation.parsedRules),
missing = collectMissingVariables()(stateSelector,situation),
result = nextSteps(stateSelector, rules, situation)
analysis = analyse(rules,"surcoût CDD")(stateSelector),
missing = collectMissingVariables(analysis.targets),
result = nextSteps(stateSelector, rules, analysis)
// expect(objectives).to.have.lengthOf(4)
@ -268,10 +252,9 @@ describe('nextSteps', function() {
let stateSelector = (name) => ({"contrat salarié . type de contrat":"CDI","entreprise . effectif":"50"})[name]
let rules = realRules.map(enrichRule),
situation = analyseTopDown(rules,"Salaire")(stateSelector),
objectives = getObjectives(stateSelector, situation.root, situation.parsedRules),
missing = collectMissingVariables()(stateSelector,situation),
result = nextSteps(stateSelector, rules, situation)
analysis = analyse(rules,"Salaire")(stateSelector),
missing = collectMissingVariables(analysis.targets),
result = nextSteps(stateSelector, rules, analysis)
expect(result[0]).to.equal("contrat salarié . salaire de base")
expect(result[1]).to.equal("contrat salarié . temps partiel")

View File

@ -1,6 +1,6 @@
import { expect } from "chai"
import { enrichRule } from "../source/engine/rules"
import { analyseTopDown, analyseSituation } from "../source/engine/traverse"
import { analyse } from "../source/engine/traverse"
import { collectMissingVariables } from "../source/engine/generateQuestions"
import yaml from "js-yaml"
import dedent from "dedent-js"
@ -23,9 +23,9 @@ describe("inversions", () => {
format: euro
`,
rules = yaml.safeLoad(rawRules).map(enrichRule),
analysis = analyseSituation(rules, "net")(stateSelector)
analysis = analyse(rules, "net")(stateSelector)
expect(analysis.nodeValue).to.be.closeTo(1771, 0.001)
expect(analysis.targets[0].nodeValue).to.be.closeTo(1771, 0.001)
})
*/
@ -46,9 +46,9 @@ describe("inversions", () => {
- net
`,
rules = yaml.safeLoad(rawRules).map(enrichRule),
analysis = analyseSituation(rules, "brut")(stateSelector)
analysis = analyse(rules, "brut")(stateSelector)
expect(analysis.nodeValue).to.be.closeTo(2000 / (77 / 100), 0.0001 * 2000)
expect(analysis.targets[0].nodeValue).to.be.closeTo(2000 / (77 / 100), 0.0001 * 2000)
})
it("should handle inversions with missing variables", () => {
@ -71,10 +71,10 @@ describe("inversions", () => {
`,
rules = yaml.safeLoad(rawRules).map(enrichRule),
stateSelector = name => ({ net: 2000 }[name]),
analysis = analyseTopDown(rules, "brut")(stateSelector),
missing = collectMissingVariables()(stateSelector, analysis)
analysis = analyse(rules, "brut")(stateSelector),
missing = collectMissingVariables(analysis.targets)
expect(analysis.root.nodeValue).to.be.null
expect(analysis.targets[0].nodeValue).to.be.null
expect(missing).to.have.key("cadre")
})
})

View File

@ -6,7 +6,7 @@
import {expect} from 'chai'
import {enrichRule} from '../source/engine/rules'
import {analyseTopDown} from '../source/engine/traverse'
import {analyse} from '../source/engine/traverse'
import {collectMissingVariables} from '../source/engine/generateQuestions'
import testSuites from './load-mecanism-tests'
import R from 'ramda'
@ -24,15 +24,16 @@ describe('Mécanismes', () =>
let rules = suite.map(enrichRule),
state = situation || {},
stateSelector = name => state[name],
analysis = analyseTopDown(rules, nom)(stateSelector),
missing = collectMissingVariables()(stateSelector,analysis)
analysis = analyse(rules, nom)(stateSelector),
missing = collectMissingVariables(analysis.targets),
target = analysis.targets[0]
// console.log('JSON.stringify(analysis', JSON.stringify(analysis))
if (isFloat(valeur)) {
expect(analysis.root.nodeValue).to.be.closeTo(valeur,0.001)
expect(target.nodeValue).to.be.closeTo(valeur,0.001)
}
else if (valeur !== undefined) {
expect(analysis.root)
expect(target)
.to.have.property(
'nodeValue',
valeur

View File

@ -2,7 +2,6 @@ import R from 'ramda'
import {expect} from 'chai'
import {rules as realRules, enrichRule} from '../source/engine/rules'
import {analyseSituation, analyseTopDown} from '../source/engine/traverse'
import {collectMissingVariables, getObjectives} from '../source/engine/generateQuestions'
import {reduceSteps} from '../source/reducers'
@ -22,10 +21,10 @@ describe('fold', function() {
{nom: "bb", question: "?", titre: "b", espace: "top"}],
rules = rawRules.map(enrichRule),
reducer = reduceSteps(tracker, rules, stateSelector),
action = {type:'START_CONVERSATION', rootVariable: 'startHere'},
action = {type:'START_CONVERSATION', targetName: 'startHere'},
// situation = analyseTopDown(rules,"startHere")(stateSelector({})),
// objectives = getObjectives(stateSelector({}), situation.root, situation.parsedRules),
// missing = collectMissingVariables()(stateSelector({}),situation),
// missing = collectMissingVariables(stateSelector({}),situation),
result = reducer({},action)
expect(result).to.have.property('currentQuestion')
@ -48,7 +47,7 @@ describe('fold', function() {
rules = rawRules.map(enrichRule),
reducer = reduceSteps(tracker, rules, stateSelector)
var step1 = reducer({},{type:'START_CONVERSATION', rootVariable: 'startHere'})
var step1 = reducer({},{type:'START_CONVERSATION', targetName: 'startHere'})
fakeState['top . aa'] = 1
var step2 = reducer(step1,{type:'STEP_ACTION', name: 'fold', step: 'top . aa'})
fakeState['top . bb'] = 1
@ -82,7 +81,7 @@ describe('fold', function() {
rules = rawRules.map(enrichRule),
reducer = reduceSteps(tracker, rules, stateSelector)
var step1 = reducer({},{type:'START_CONVERSATION', rootVariable: 'startHere'})
var step1 = reducer({},{type:'START_CONVERSATION', targetName: 'startHere'})
fakeState['top . aa'] = 1
var step2 = reducer(step1,{type:'STEP_ACTION', name: 'fold', step: 'top . aa'})
@ -116,7 +115,7 @@ describe('fold', function() {
rules = rawRules.map(enrichRule),
reducer = reduceSteps(tracker, rules, stateSelector)
var step1 = reducer({},{type:'START_CONVERSATION', rootVariable: 'startHere'})
var step1 = reducer({},{type:'START_CONVERSATION', targetName: 'startHere'})
fakeState['top . aa'] = 1
var step2 = reducer(step1,{type:'STEP_ACTION', name: 'fold', step: 'top . aa'})
var step3 = reducer(step2,{type:'STEP_ACTION', name: 'unfold', step: 'top . bb'})

View File

@ -1,7 +1,6 @@
import R from 'ramda'
import {expect} from 'chai'
import {rules, enrichRule, findVariantsAndRecords} from '../source/engine/rules'
import {analyseSituation, analyseTopDown} from '../source/engine/traverse'
let stateSelector = (state, name) => null

View File

@ -1,33 +1,34 @@
import {expect} from 'chai'
import {rules as realRules, enrichRule} from '../source/engine/rules'
import {analyseSituation, analyseTopDown} from '../source/engine/traverse'
import {analyse, getTargets} from '../source/engine/traverse'
let stateSelector = (state, name) => null
describe('analyseSituation', function() {
describe('analyse', function() {
it('should directly return simple numerical values', function() {
let rule = {name: "startHere", formule: 3269}
let rules = [rule]
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',3269)
expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',3269)
});
it('should compute expressions combining constants', function() {
let rule = {name: "startHere", formule: "32 + 69"}
let rules = [rule]
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',101)
expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',101)
});
});
describe('analyseSituation on raw rules', function() {
describe('analyse on raw rules', function() {
it('should handle direct referencing of a variable', function() {
let rawRules = [
{nom: "startHere", formule: "dix", espace: "top"},
{nom: "dix", formule: 10, espace: "top"}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',10)
expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',10)
});
it('should handle expressions referencing other rules', function() {
@ -35,7 +36,7 @@ describe('analyseSituation on raw rules', function() {
{nom: "startHere", formule: "3259 + dix", espace: "top"},
{nom: "dix", formule: 10, espace: "top"}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',3269)
expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',3269)
});
it('should handle applicability conditions', function() {
@ -44,7 +45,7 @@ describe('analyseSituation on raw rules', function() {
{nom: "dix", formule: 10, espace: "top", "non applicable si" : "vrai"},
{nom: "vrai", formule: "2 > 1", espace: "top"}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',3259)
expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',3259)
});
it('should handle comparisons', function() {
@ -52,7 +53,7 @@ describe('analyseSituation on raw rules', function() {
{nom: "startHere", formule: "3259 > dix", espace: "top"},
{nom: "dix", formule: 10, espace: "top"}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',true)
expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',true)
});
/* TODO: make this pass
@ -62,26 +63,26 @@ describe('analyseSituation on raw rules', function() {
{nom: "dix", formule: 10, espace: "top", "non applicable si" : "vrai"},
{nom: "vrai", formule: "1", espace: "top"}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',3259)
expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',3259)
});
*/
});
describe('analyseSituation with mecanisms', function() {
describe('analyse with mecanisms', function() {
it('should handle n-way "or"', function() {
let rawRules = [
{nom: "startHere", formule: {"une de ces conditions": ["1 > 2", "1 > 0", "0 > 2"]}}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',true)
expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',true)
});
it('should handle n-way "and"', function() {
let rawRules = [
{nom: "startHere", formule: {"toutes ces conditions": ["1 > 2", "1 > 0", "0 > 2"]}}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',false)
expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',false)
});
it('should handle switch statements', function() {
@ -93,14 +94,14 @@ describe('analyseSituation with mecanisms', function() {
}}, espace: "top"},
{nom: "dix", formule: 10, espace: "top"}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',11)
expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',11)
});
it('should handle percentages', function() {
let rawRules = [
{nom: "startHere", formule: "35%", espace: "top"}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',0.35)
expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',0.35)
});
it('should handle sums', function() {
@ -108,14 +109,14 @@ describe('analyseSituation with mecanisms', function() {
{nom: "startHere", formule: {"somme": [3200, "dix", 9]}},
{nom: "dix", formule: 10}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',3219)
expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',3219)
});
it('should handle multiplications', function() {
let rawRules = [
{nom: "startHere", formule: {"multiplication": {assiette:3259, plafond:3200, facteur:1, taux:1.5}}}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',4800)
expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',4800)
});
it('should handle components in multiplication', function() {
@ -124,7 +125,7 @@ describe('analyseSituation with mecanisms', function() {
composantes: [{taux:0.7}, {taux:0.8}]
}}}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',4800)
expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',4800)
});
it('should apply a ceiling to the sum of components', function() {
@ -133,7 +134,7 @@ describe('analyseSituation with mecanisms', function() {
composantes: [{taux:0.7}, {taux:0.8}]
}}}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',4800)
expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',4800)
});
it('should handle progressive scales', function() {
@ -144,7 +145,7 @@ describe('analyseSituation with mecanisms', function() {
"tranches":[{"en-dessous de":1, taux: 0.1},{de:1, "à": 2, taux: 1.2}, ,{"au-dessus de":2, taux: 10}]
}}}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',100+1200+80)
expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',100+1200+80)
});
it('should handle progressive scales with components', function() {
@ -158,7 +159,7 @@ describe('analyseSituation with mecanisms', function() {
]
}}}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',100+1200+80)
expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',100+1200+80)
});
it('should handle progressive scales with variations', function() {
@ -172,14 +173,14 @@ describe('analyseSituation with mecanisms', function() {
]
}}}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',100+1800+80)
expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',100+1800+80)
});
it('should handle max', function() {
let rawRules = [
{nom: "startHere", formule: {"le maximum de": [3200, 60, 9]}}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',3200)
expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',3200)
});
it('should handle complements', function() {
@ -187,7 +188,7 @@ describe('analyseSituation with mecanisms', function() {
{nom: "startHere", formule: {complément: {cible: "dix", montant: 93}}, espace: "top"},
{nom: "dix", formule: 17, espace: "top"}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',93-17)
expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',93-17)
});
it('should handle components in complements', function() {
@ -197,7 +198,7 @@ describe('analyseSituation with mecanisms', function() {
}}, espace: "top"},
{nom: "dix", formule: 17, espace: "top"}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',2*(93-17))
expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',2*(93-17))
});
it('should handle filtering on components', function() {
@ -216,7 +217,7 @@ describe('analyseSituation with mecanisms', function() {
]
}}}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',50+400+40)
expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',50+400+40)
});
it('should compute consistent values', function() {
@ -236,8 +237,8 @@ describe('analyseSituation with mecanisms', function() {
]
}}}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"orHere")(stateSelector)).to.have.property('nodeValue',100+1200+80)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',100+1200+80)
expect(analyse(rules,"orHere")(stateSelector).targets[0]).to.have.property('nodeValue',100+1200+80)
expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',100+1200+80)
});
it('should handle selection', function() {
@ -255,7 +256,7 @@ describe('analyseSituation with mecanisms', function() {
données: 'taux_versement_transport'},
{espace: "top", nom: "code postal", format: "nombre"}],
rules = rawRules.map(rule => enrichRule(rule,data))
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',0.02)
expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue',0.02)
});
it('should handle failed selections', function() {
@ -273,7 +274,7 @@ describe('analyseSituation with mecanisms', function() {
données: 'taux_versement_transport'},
{espace: "top", nom: "code postal", format: "nombre"}],
rules = rawRules.map(rule => enrichRule(rule,data))
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue', 0)
expect(analyse(rules,"startHere")(stateSelector).targets[0]).to.have.property('nodeValue', 0)
});
});