Skip to content

Commit

Permalink
Merge pull request #197 from betagouv/simplify-missing
Browse files Browse the repository at this point in the history
Améliorations des variables manquantes
  • Loading branch information
Morendil authored Apr 23, 2018
2 parents 0f58a56 + 7a347ce commit c711f3f
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 181 deletions.
47 changes: 30 additions & 17 deletions source/engine/evaluation.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import {
add,
max,
map,
pluck,
any,
equals,
reduce,
mergeWith,
chain,
length,
flatten,
uniq,
fromPairs,
keys,
values,
Expand All @@ -17,17 +23,20 @@ export let makeJsx = node =>
? node.jsx(node.nodeValue, node.explanation)
: node.jsx

export let collectNodeMissing = node =>
node.collectMissing ? node.collectMissing(node) : []
export let collectNodeMissing = node => node.missingVariables || {}

export let bonus = (missings, hasCondition=true) => hasCondition ? map(x=>x+.0001, missings || {}) : missings
export let mergeAllMissing = missings => reduce(mergeWith(add),{},map(collectNodeMissing,missings))
export let mergeMissing = (left, right) => mergeWith(add, left || {}, right || {})

export let evaluateNode = (cache, situationGate, parsedRules, node) =>
node.evaluate ? node.evaluate(cache, situationGate, parsedRules, node) : node

export let rewriteNode = (node, nodeValue, explanation, collectMissing) => ({
export let rewriteNode = (node, nodeValue, explanation, missingVariables) => ({
...node,
nodeValue,
collectMissing,
explanation
explanation,
missingVariables
})

export let evaluateArray = (reducer, start) => (
Expand All @@ -42,11 +51,12 @@ export let evaluateArray = (reducer, start) => (
values = pluck('nodeValue', explanation),
nodeValue = any(equals(null), values)
? null
: reduce(reducer, start, values)

let collectMissing = node =>
node.nodeValue == null ? chain(collectNodeMissing, node.explanation) : []
return rewriteNode(node, nodeValue, explanation, collectMissing)
: reduce(reducer, start, values),
missingVariables = node.nodeValue == null
? mergeAllMissing(explanation)
: {}
// console.log("".padStart(cache.parseLevel), missingVariables)
return rewriteNode(node, nodeValue, explanation, missingVariables)
}

export let evaluateArrayWithFilter = (evaluationFilter, reducer, start) => (
Expand All @@ -64,10 +74,12 @@ export let evaluateArrayWithFilter = (evaluationFilter, reducer, start) => (
values = pluck('nodeValue', explanation),
nodeValue = any(equals(null), values)
? null
: reduce(reducer, start, values)
: reduce(reducer, start, values),
missingVariables = node.nodeValue == null
? mergeAllMissing(explanation)
: {}

let collectMissing = node => chain(collectNodeMissing, node.explanation)
return rewriteNode(node, nodeValue, explanation, collectMissing)
return rewriteNode(node, nodeValue, explanation, missingVariables)
}

export let parseObject = (recurse, objectShape, value) => {
Expand All @@ -86,11 +98,12 @@ export let evaluateObject = (objectShape, effect) => (
node
) => {
let evaluateOne = child =>
evaluateNode(cache, situationGate, parsedRules, child),
collectMissing = node => chain(collectNodeMissing, values(node.explanation))
evaluateNode(cache, situationGate, parsedRules, child)

let transforms = map(k => [k, evaluateOne], keys(objectShape)),
explanation = evolve(fromPairs(transforms))(node.explanation),
nodeValue = effect(explanation)
return rewriteNode(node, nodeValue, explanation, collectMissing)
nodeValue = effect(explanation),
missingVariables = mergeAllMissing(values(explanation))
// console.log("".padStart(cache.parseLevel),map(node => length(flatten(collectNodeMissing(node))) ,explanation))
return rewriteNode(node, nodeValue, explanation, missingVariables)
}
12 changes: 5 additions & 7 deletions source/engine/generateQuestions.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {
chain,
flatten,
mergeAll,
pluck,
groupBy,
toPairs,
sort,
Expand All @@ -22,7 +24,6 @@ import SelectAtmp from 'Components/conversation/select/SelectTauxRisque'
import formValueTypes from 'Components/conversation/formValueTypes'

import { findRuleByDottedName, disambiguateRuleReference } from './rules'
import { collectNodeMissing } from './evaluation'

/*
COLLECTE DES VARIABLES MANQUANTES
Expand All @@ -39,13 +40,10 @@ import { collectNodeMissing } from './evaluation'
missingVariables: {variable: [objectives]}
*/

export let collectMissingVariables = targets => {
let missing = chain(collectNodeMissing, targets)
return groupBy(identity, missing)
}
export let collectMissingVariables = targets => mergeAll(pluck('missingVariables', targets))

export let getNextSteps = (situationGate, analysis) => {
let impact = ([, objectives]) => length(objectives)
let impact = ([, count]) => count

let missingVariables = collectMissingVariables(analysis.targets),
pairs = toPairs(missingVariables),
Expand Down
135 changes: 64 additions & 71 deletions source/engine/mecanisms.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import {
flatten,
reduce,
mergeWith,
mergeAll,
length,
objOf,
toPairs,
dissoc,
Expand All @@ -16,6 +21,7 @@ import {
evolve,
curry,
filter,
all,
pipe,
head,
isEmpty,
Expand All @@ -40,7 +46,10 @@ import {
evaluateArrayWithFilter,
evaluateObject,
parseObject,
collectNodeMissing
collectNodeMissing,
mergeAllMissing,
mergeMissing,
bonus
} from './evaluation'
import {
findRuleByName,
Expand Down Expand Up @@ -144,23 +153,18 @@ let devariate = (recurse, k, v) => {
}

let explanation = map(evaluateOne, node.explanation),
choice = find(node => node.condition.nodeValue, explanation),
candidates = filter(node => node.condition.nodeValue !== false, explanation),
satisfied = filter(node => node.condition.nodeValue, explanation),
choice = head(satisfied),
nodeValue = choice ? choice.nodeValue : null

let collectMissing = node => {
let choice = find(node => node.condition.nodeValue, node.explanation),
leftMissing = choice
? []
: uniq(
chain(collectNodeMissing, pluck('condition', node.explanation))
),
rightMissing = choice
? collectNodeMissing(choice)
: chain(collectNodeMissing, node.explanation)
return concat(leftMissing, rightMissing)
}
let leftMissing = choice
? {}
: mergeAllMissing(pluck('condition', explanation)),
rightMissing = mergeAllMissing(candidates),
missingVariables = mergeMissing(bonus(leftMissing), rightMissing)

return rewriteNode(node, nodeValue, explanation, collectMissing)
return rewriteNode(node, nodeValue, explanation, missingVariables)
}

// TODO - find an appropriate representation
Expand Down Expand Up @@ -221,11 +225,13 @@ export let mecanismOneOf = (recurse, k, v) => {
values = pluck('nodeValue', explanation),
nodeValue = any(equals(true), values)
? true
: any(equals(null), values) ? null : false
: any(equals(null), values) ? null : false,
// Unlike most other array merges of missing variables this is a "flat" merge
// because "one of these conditions" tend to be several tests of the same variable
// (e.g. contract type is one of x, y, z)
missingVariables = nodeValue == null ? reduce(mergeWith(max),{},map(collectNodeMissing,explanation)) : {}

let collectMissing = node =>
node.nodeValue == null ? chain(collectNodeMissing, node.explanation) : []
return rewriteNode(node, nodeValue, explanation, collectMissing)
return rewriteNode(node, nodeValue, explanation, missingVariables)
}

return {
Expand Down Expand Up @@ -265,11 +271,10 @@ export let mecanismAllOf = (recurse, k, v) => {
values = pluck('nodeValue', explanation),
nodeValue = any(equals(false), values)
? false // court-circuit
: any(equals(null), values) ? null : true
: any(equals(null), values) ? null : true,
missingVariables = nodeValue == null ? mergeAllMissing(explanation) : {}

let collectMissing = node =>
node.nodeValue == null ? chain(collectNodeMissing, node.explanation) : []
return rewriteNode(node, nodeValue, explanation, collectMissing)
return rewriteNode(node, nodeValue, explanation, missingVariables)
}

return {
Expand Down Expand Up @@ -302,27 +307,24 @@ export let mecanismNumericalSwitch = (recurse, k, v) => {
consequenceNode = mecanismNumericalSwitch(recurse, condition, consequence)

let evaluate = (cache, situationGate, parsedRules, node) => {
let collectMissing = node => {
let missingOnTheLeft = collectNodeMissing(node.explanation.condition),
investigate = node.explanation.condition.nodeValue !== false,
missingOnTheRight = investigate
? collectNodeMissing(node.explanation.consequence)
: []
return concat(missingOnTheLeft, missingOnTheRight)
}

let explanation = evolve(
{
condition: curry(evaluateNode)(cache, situationGate, parsedRules),
consequence: curry(evaluateNode)(cache, situationGate, parsedRules)
},
node.explanation
)
),
leftMissing = explanation.condition.missingVariables,
investigate = explanation.condition.nodeValue !== false,
rightMissing = investigate
? explanation.consequence.missingVariables
: {},
missingVariables = mergeMissing(bonus(leftMissing), rightMissing)

return {
...node,
collectMissing,
explanation,
missingVariables,
nodeValue: explanation.consequence.nodeValue,
condValue: explanation.condition.nodeValue
}
Expand Down Expand Up @@ -360,16 +362,13 @@ export let mecanismNumericalSwitch = (recurse, k, v) => {
getFirst('condValue') == null
? null
: // c'est un true, on renvoie la valeur de la conséquence
getFirst('nodeValue')

let collectMissing = node => {
let choice = find(node => node.condValue, node.explanation)
return choice
? collectNodeMissing(choice)
: chain(collectNodeMissing, node.explanation)
}
getFirst('nodeValue'),
choice = find(node => node.condValue, explanation),
missingVariables = choice
? choice.missingVariables
: mergeAllMissing(explanation)

return rewriteNode(node, nodeValue, explanation, collectMissing)
return rewriteNode(node, nodeValue, explanation, missingVariables)
}

let explanation = map(parseCondition, terms)
Expand Down Expand Up @@ -429,46 +428,38 @@ export let findInversion = (situationGate, rules, v, dottedName) => {
}
}

let doInversion = (situationGate, parsedRules, v, dottedName) => {
let doInversion = (oldCache, situationGate, parsedRules, v, dottedName) => {
let inversion = findInversion(situationGate, parsedRules, v, dottedName)

if (inversion.inversionChoiceNeeded)
return {
inversionMissingVariables: [dottedName],
missingVariables: {[dottedName]:1},
nodeValue: null
}
let { fixedObjectiveValue, fixedObjectiveRule } = inversion
let inversionCache = {}
let fx = x => {
inversionCache = {}
inversionCache = {parseLevel: oldCache.parseLevel+1, op:"<"}
return evaluateNode(
inversionCache, // with an empty cache
n => (dottedName === n ? x : situationGate(n)),
parsedRules,
fixedObjectiveRule
).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
if (fx(1000) == null)
return {
nodeValue: null,
inversionMissingVariables: collectNodeMissing(
evaluateNode(
{},
n => (dottedName === n ? 1000 : situationGate(n)),
parsedRules,
fixedObjectiveRule
)
)
}
let attempt = fx(1000)
if (attempt.nodeValue == null) {
return attempt
}

let tolerancePercentage = 0.00001,
// cette fonction détermine la racine d'une fonction sans faire trop d'itérations
nodeValue = uniroot(
x => fx(x) - fixedObjectiveValue,
x => fx(x).nodeValue - fixedObjectiveValue,
0,
1000000000,
tolerancePercentage * fixedObjectiveValue,
Expand All @@ -477,7 +468,7 @@ let doInversion = (situationGate, parsedRules, v, dottedName) => {

return {
nodeValue,
inversionMissingVariables: [],
missingVariables: {},
inversionCache
}
}
Expand All @@ -486,14 +477,15 @@ export let mecanismInversion = dottedName => (recurse, k, v) => {
let evaluate = (cache, situationGate, parsedRules, node) => {
let inversion =
// avoid the inversion loop !
situationGate(dottedName) == undefined &&
doInversion(situationGate, parsedRules, v, dottedName)
let collectMissing = () => inversion.inversionMissingVariables,
nodeValue = inversion.nodeValue
situationGate(dottedName) == undefined &&
doInversion(cache, situationGate, parsedRules, v, dottedName),
nodeValue = inversion.nodeValue,
missingVariables = inversion.missingVariables

let evaluatedNode = rewriteNode(node, nodeValue, null, collectMissing)
// rewrite the simulation cache with the definitive inversion values
let evaluatedNode = rewriteNode(node, nodeValue, null, missingVariables)

// TODO - we need this so that ResultsGrid will work, but it's
// just not right
toPairs(inversion.inversionCache).map(([k, v]) => (cache[k] = v))
return evaluatedNode
}
Expand Down Expand Up @@ -932,8 +924,7 @@ export let mecanismSelection = (recurse, k, v) => {
let explanation = recurse(v['cherche'])

let evaluate = (cache, situationGate, parsedRules, node) => {
let collectMissing = node => collectNodeMissing(node.explanation),
explanation = evaluateNode(
let explanation = evaluateNode(
cache,
situationGate,
parsedRules,
Expand Down Expand Up @@ -962,8 +953,10 @@ export let mecanismSelection = (recurse, k, v) => {
? sortedSubValues
? Number.parseFloat(last(sortedSubValues)[1]) / 100
: 0
: null
return rewriteNode(node, nodeValue, explanation, collectMissing)
: null,
missingVariables = explanation.missingVariables

return rewriteNode(node, nodeValue, explanation, missingVariables)
}

let SelectionView = buildSelectionView(dataTargetName)
Expand Down
2 changes: 0 additions & 2 deletions source/engine/traverse-common-functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,3 @@ export let val = node => node && node.nodeValue
export let undefOrTrue = val => val == undefined || val == true

export let anyNull = any(pipe(val, equals(null)))

export let applyOrEmpty = func => v => (v ? func(v) : [])
Loading

0 comments on commit c711f3f

Please sign in to comment.