🔥 Supprime Ramda
parent
3d2021ca21
commit
7f08675e1a
|
@ -83,7 +83,6 @@
|
|||
"modele-social": "workspace:^",
|
||||
"publicodes": "=1.0.0-beta.40",
|
||||
"publicodes-react": "=1.0.0-beta.40",
|
||||
"ramda": "^0.27.0",
|
||||
"react": "^17.0.0",
|
||||
"react-color": "^2.14.0",
|
||||
"react-dom": "^17.0.0",
|
||||
|
@ -118,7 +117,6 @@
|
|||
"@storybook/builder-vite": "^0.1.23",
|
||||
"@storybook/react": "^6.5.0-alpha.49",
|
||||
"@storybook/testing-library": "^0.0.9",
|
||||
"@types/ramda": "^0.26.43",
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-color": "^3.0.1",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { readFileSync, writeFileSync } from 'fs'
|
||||
import { assocPath } from 'ramda'
|
||||
import yaml from 'yaml'
|
||||
import {
|
||||
fetchTranslation,
|
||||
|
@ -36,3 +35,15 @@ import {
|
|||
yaml.stringify(originalKeys, { sortMapEntries: true })
|
||||
)
|
||||
})()
|
||||
|
||||
function assocPath(path, val, obj) {
|
||||
if (path.length === 0) return val
|
||||
|
||||
const key = path[0]
|
||||
|
||||
if (path.length >= 2) {
|
||||
val = assocPath(path.slice(1), val, obj?.[key] ?? {})
|
||||
}
|
||||
|
||||
return { ...obj, [key]: val }
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import 'dotenv/config.js'
|
||||
import { readFileSync } from 'fs'
|
||||
import 'isomorphic-fetch'
|
||||
import { stringify } from 'querystring'
|
||||
import { equals, mergeAll, path as _path, pick } from 'ramda'
|
||||
import yaml from 'yaml'
|
||||
import rules from '../../../modele-social/dist/index.js'
|
||||
|
||||
|
@ -27,68 +25,66 @@ export function getRulesMissingTranslations() {
|
|||
)
|
||||
|
||||
let missingTranslations = []
|
||||
let resolved = Object.entries(rules)
|
||||
.map(([dottedName, rule]) => [
|
||||
dottedName,
|
||||
!rule || !rule.titre // && utils.ruleWithDedicatedDocumentationPage(rule))
|
||||
? { ...rule, titre: dottedName.split(' . ').slice(-1)[0] }
|
||||
: rule,
|
||||
])
|
||||
.map(([dottedName, rule]) => ({
|
||||
[dottedName]: mergeAll(
|
||||
Object.entries(rule)
|
||||
.filter(([, v]) => !!v)
|
||||
.map(([k, v]) => {
|
||||
let attrToTranslate = attributesToTranslate.find(equals(k))
|
||||
if (!attrToTranslate) return {}
|
||||
let enTrad = attrToTranslate + '.en',
|
||||
frTrad = attrToTranslate + '.fr'
|
||||
let resolved = Object.fromEntries(
|
||||
Object.entries(rules)
|
||||
.map(([dottedName, rule]) => [
|
||||
dottedName,
|
||||
!rule || !rule.titre // && utils.ruleWithDedicatedDocumentationPage(rule))
|
||||
? { ...rule, titre: dottedName.split(' . ').slice(-1)[0] }
|
||||
: rule,
|
||||
])
|
||||
.map(([dottedName, rule]) => [
|
||||
dottedName,
|
||||
Object.fromEntries(
|
||||
Object.entries(rule)
|
||||
.filter(([, v]) => !!v)
|
||||
.map(([k, v]) => {
|
||||
let attrToTranslate = attributesToTranslate.find(
|
||||
(attr) => attr === k
|
||||
)
|
||||
if (!attrToTranslate) return []
|
||||
let enTrad = attrToTranslate + '.en'
|
||||
let frTrad = attrToTranslate + '.fr'
|
||||
|
||||
let currentTranslation = currentExternalization[dottedName]
|
||||
let currentTranslation = currentExternalization[dottedName]
|
||||
|
||||
if ('suggestions' === attrToTranslate) {
|
||||
return Object.keys(v).reduce((acc, suggestion) => {
|
||||
const enTrad = `suggestions.${suggestion}.en`
|
||||
const frTrad = `suggestions.${suggestion}.fr`
|
||||
if (
|
||||
currentTranslation &&
|
||||
currentTranslation[enTrad] &&
|
||||
currentTranslation[frTrad] === suggestion
|
||||
) {
|
||||
return {
|
||||
...acc,
|
||||
[frTrad]: currentTranslation[frTrad],
|
||||
[enTrad]: currentTranslation[enTrad],
|
||||
if ('suggestions' === attrToTranslate) {
|
||||
return Object.keys(v).reduce((acc, suggestion) => {
|
||||
const enTrad = `suggestions.${suggestion}.en`
|
||||
const frTrad = `suggestions.${suggestion}.fr`
|
||||
if (
|
||||
currentTranslation?.[enTrad] &&
|
||||
currentTranslation?.[frTrad] === suggestion
|
||||
) {
|
||||
return [
|
||||
...acc,
|
||||
[frTrad, currentTranslation[frTrad]],
|
||||
[enTrad, currentTranslation[enTrad]],
|
||||
]
|
||||
}
|
||||
}
|
||||
missingTranslations.push([dottedName, enTrad, suggestion])
|
||||
return {
|
||||
...acc,
|
||||
[frTrad]: suggestion,
|
||||
}
|
||||
}, {})
|
||||
}
|
||||
|
||||
// Check if a human traduction exists already for this attribute and if
|
||||
// it does need to be updated
|
||||
if (
|
||||
currentTranslation &&
|
||||
currentTranslation[enTrad] &&
|
||||
currentTranslation[frTrad] === v
|
||||
)
|
||||
return {
|
||||
[enTrad]: currentTranslation[enTrad],
|
||||
[frTrad]: v,
|
||||
missingTranslations.push([dottedName, enTrad, suggestion])
|
||||
return [...acc, [frTrad, suggestion]]
|
||||
}, [])
|
||||
}
|
||||
|
||||
missingTranslations.push([dottedName, enTrad, v])
|
||||
return {
|
||||
[frTrad]: v,
|
||||
}
|
||||
})
|
||||
),
|
||||
}))
|
||||
resolved = mergeAll(resolved)
|
||||
// Check if a human traduction exists already for this attribute and if
|
||||
// it does need to be updated
|
||||
if (
|
||||
currentTranslation &&
|
||||
currentTranslation[enTrad] &&
|
||||
currentTranslation[frTrad] === v
|
||||
)
|
||||
return [
|
||||
[enTrad, currentTranslation[enTrad]],
|
||||
[frTrad, v],
|
||||
]
|
||||
missingTranslations.push([dottedName, enTrad, v])
|
||||
return [[frTrad, v]]
|
||||
})
|
||||
.flat()
|
||||
),
|
||||
])
|
||||
)
|
||||
return [missingTranslations, resolved]
|
||||
}
|
||||
|
||||
|
@ -105,26 +101,32 @@ export const getUiMissingTranslations = () => {
|
|||
return false
|
||||
}
|
||||
const keys = key.split(/(?<=[A-zÀ-ü0-9])\.(?=[A-zÀ-ü0-9])/)
|
||||
|
||||
const isNewKey = !_path(keys, translatedKeys)
|
||||
const isInvalidatedKey = _path(keys, originalKeys) !== valueInSource
|
||||
const pathReducer = (currentSelection, subPath) =>
|
||||
currentSelection?.[subPath]
|
||||
const isNewKey = !keys.reduce(pathReducer, translatedKeys)
|
||||
const isInvalidatedKey =
|
||||
keys.reduce(pathReducer, originalKeys) !== valueInSource
|
||||
|
||||
return isNewKey || isInvalidatedKey
|
||||
}, staticKeys)
|
||||
.map(([key]) => key)
|
||||
|
||||
return pick(missingTranslations, staticKeys)
|
||||
return Object.fromEntries(
|
||||
Object.entries(staticKeys).filter(([key]) =>
|
||||
missingTranslations.includes(key)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export const fetchTranslation = async (text) => {
|
||||
const response = await fetch(
|
||||
`https://api.deepl.com/v2/translate?${stringify({
|
||||
`https://api.deepl.com/v2/translate?${new URLSearchParams({
|
||||
text,
|
||||
auth_key: process.env.DEEPL_API_SECRET,
|
||||
tag_handling: 'xml',
|
||||
source_lang: 'FR',
|
||||
target_lang: 'EN',
|
||||
})}`
|
||||
}).toString()}`
|
||||
)
|
||||
if (response.status !== 200) {
|
||||
console.error(`❌ Deepl return status ${response.status} for:\n\t${text}\n`)
|
||||
|
|
|
@ -2,8 +2,6 @@ import { goToQuestion } from '@/actions/actions'
|
|||
import { Spacing } from '@/design-system/layout'
|
||||
import { Link } from '@/design-system/typography/link'
|
||||
import { SmallBody } from '@/design-system/typography/paragraphs'
|
||||
import { DottedName } from 'modele-social'
|
||||
import { contains, filter, pipe, reject, toPairs } from 'ramda'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { RootState } from '@/reducers/rootReducer'
|
||||
|
@ -26,11 +24,10 @@ export default function QuickLinks() {
|
|||
if (!quickLinks) {
|
||||
return <Spacing sm />
|
||||
}
|
||||
const links = pipe(
|
||||
reject((dottedName: DottedName) => contains(dottedName, quickLinksToHide)),
|
||||
filter((dottedName: DottedName) => contains(dottedName, nextSteps)),
|
||||
toPairs
|
||||
)(quickLinks)
|
||||
const links = Object.entries(quickLinks).filter(
|
||||
([, dottedName]) =>
|
||||
nextSteps.includes(dottedName) && !quickLinksToHide.includes(dottedName)
|
||||
)
|
||||
|
||||
if (links.length < 1) {
|
||||
return <Spacing lg />
|
||||
|
|
|
@ -6,7 +6,6 @@ import InfoBulle from '@/design-system/InfoBulle'
|
|||
import { H3 } from '@/design-system/typography/heading'
|
||||
import { ExtractFromSimuData } from '@/pages/Simulateurs/metadata'
|
||||
import { MetadataSrc } from '@/pages/Simulateurs/metadata-src'
|
||||
import { path } from 'ramda'
|
||||
import { useContext } from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { Hit } from 'react-instantsearch-core'
|
||||
|
@ -69,12 +68,12 @@ export const SimulatorHits = connectHits<
|
|||
<Grid item key={hit.objectID} xs={12} lg={6}>
|
||||
<SimulateurCardHit
|
||||
hit={hit}
|
||||
path={
|
||||
path(
|
||||
hit.pathId.split('.'),
|
||||
sitePaths
|
||||
) as ExtractFromSimuData<'path'>
|
||||
}
|
||||
path={hit.pathId
|
||||
.split('.')
|
||||
.reduce<ExtractFromSimuData<'path'>>(
|
||||
(acc, curr) => acc[curr as any],
|
||||
sitePaths as any
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
)
|
||||
|
|
|
@ -1,29 +1,3 @@
|
|||
import { DottedName } from 'modele-social'
|
||||
import {
|
||||
add,
|
||||
countBy,
|
||||
descend,
|
||||
difference,
|
||||
equals,
|
||||
flatten,
|
||||
head,
|
||||
identity,
|
||||
keys,
|
||||
last,
|
||||
length,
|
||||
map,
|
||||
mergeWith,
|
||||
pair,
|
||||
pipe,
|
||||
reduce,
|
||||
sortBy,
|
||||
sortWith,
|
||||
takeWhile,
|
||||
toPairs,
|
||||
zipWith,
|
||||
} from 'ramda'
|
||||
import { useContext, useMemo } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Simulation, SimulationConfig } from '@/reducers/rootReducer'
|
||||
import {
|
||||
answeredQuestionsSelector,
|
||||
|
@ -32,6 +6,9 @@ import {
|
|||
objectifsSelector,
|
||||
situationSelector,
|
||||
} from '@/selectors/simulationSelectors'
|
||||
import { DottedName } from 'modele-social'
|
||||
import { useContext, useMemo } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { EngineContext } from './EngineContext'
|
||||
|
||||
type MissingVariables = Partial<Record<DottedName, number>>
|
||||
|
@ -39,46 +16,72 @@ type MissingVariables = Partial<Record<DottedName, number>>
|
|||
export function getNextSteps(
|
||||
missingVariables: Array<MissingVariables>
|
||||
): Array<DottedName> {
|
||||
const byCount = ([, [count]]: [unknown, [number]]) => count
|
||||
const byScore = ([, [, score]]: [unknown, [unknown, number]]) => score
|
||||
const missingByTotalScore = reduce<MissingVariables, MissingVariables>(
|
||||
mergeWith(add),
|
||||
{},
|
||||
missingVariables
|
||||
const missingByTotalScore = missingVariables.reduce<Record<string, number>>(
|
||||
(acc, mv) => ({
|
||||
...acc,
|
||||
...Object.fromEntries(
|
||||
Object.entries(mv).map(([name, score]) => [
|
||||
name,
|
||||
(acc[name] || 0) + score,
|
||||
])
|
||||
),
|
||||
}),
|
||||
{}
|
||||
)
|
||||
|
||||
const innerKeys = flatten(map(keys, missingVariables))
|
||||
const innerKeys = missingVariables.map((mv) => Object.keys(mv)).flat()
|
||||
const missingByTargetsAdvanced = Object.fromEntries(
|
||||
Object.entries(countBy(identity, innerKeys)).map(
|
||||
Object.entries(
|
||||
innerKeys.reduce<Record<string, number>>(
|
||||
(counters, key) => ({
|
||||
...counters,
|
||||
[key]: (counters[key] ?? 0) + 1,
|
||||
}),
|
||||
{}
|
||||
)
|
||||
).map(
|
||||
// Give higher score to top level questions
|
||||
([name, score]) => [name, score + Math.max(0, 4 - name.split('.').length)]
|
||||
)
|
||||
)
|
||||
|
||||
const missingByCompound = mergeWith(
|
||||
pair,
|
||||
missingByTargetsAdvanced,
|
||||
missingByTotalScore
|
||||
)
|
||||
const pairs = toPairs<number>(missingByCompound)
|
||||
const sortedPairs = sortWith(
|
||||
[descend(byCount), descend(byScore) as any],
|
||||
pairs
|
||||
const missingByCompoundEntries = [
|
||||
...new Set([
|
||||
...Object.keys(missingByTargetsAdvanced),
|
||||
...Object.keys(missingByTotalScore),
|
||||
]),
|
||||
].map((name): [string, { score: number; count: number }] => [
|
||||
name,
|
||||
{
|
||||
count: missingByTargetsAdvanced[name] ?? 0,
|
||||
score: missingByTotalScore[name] ?? 0,
|
||||
},
|
||||
])
|
||||
|
||||
const sortedEntries = missingByCompoundEntries.sort(
|
||||
([, scoresA], [, scoresB]) => {
|
||||
if (scoresA.count === scoresB.count) {
|
||||
return scoresB.score - scoresA.score
|
||||
} else {
|
||||
return scoresB.count - scoresA.count
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return map(head, sortedPairs) as any
|
||||
return sortedEntries.map(([name]) => name) as Array<DottedName>
|
||||
}
|
||||
|
||||
// Max : 1
|
||||
// Min -> 0
|
||||
const questionDifference = (rule1 = '', rule2 = '') =>
|
||||
1 /
|
||||
(1 +
|
||||
pipe(
|
||||
zipWith(equals),
|
||||
takeWhile(Boolean),
|
||||
length
|
||||
)(rule1.split(' . '), rule2.split(' . ')))
|
||||
const questionDifference = (ruleA = '', ruleB = '') => {
|
||||
if (ruleA === ruleB) {
|
||||
return 0
|
||||
}
|
||||
const partsA = ruleA.split(' . ')
|
||||
const partsB = ruleB.split(' . ')
|
||||
|
||||
return 1 / (1 + partsA.findIndex((val, i) => partsB?.[i] !== val))
|
||||
}
|
||||
|
||||
export function getNextQuestions(
|
||||
missingVariables: Array<MissingVariables>,
|
||||
|
@ -93,17 +96,22 @@ export function getNextQuestions(
|
|||
"à l'affiche": displayed = {},
|
||||
} = questionConfig
|
||||
|
||||
let nextSteps = difference(
|
||||
[...Object.values(displayed), ...getNextSteps(missingVariables)],
|
||||
answeredQuestions
|
||||
)
|
||||
nextSteps = nextSteps.filter(
|
||||
(step) =>
|
||||
(!whitelist.length || whitelist.some((name) => step.startsWith(name))) &&
|
||||
(!blacklist.length || !blacklist.some((name) => step === name))
|
||||
)
|
||||
const nextSteps = [
|
||||
...new Set([
|
||||
...Object.values(displayed),
|
||||
...getNextSteps(missingVariables),
|
||||
]),
|
||||
]
|
||||
.filter((name) => !answeredQuestions.includes(name))
|
||||
.filter(
|
||||
(step) =>
|
||||
(!whitelist.length ||
|
||||
whitelist.some((name) => step.startsWith(name))) &&
|
||||
(!blacklist.length || !blacklist.some((name) => step === name))
|
||||
)
|
||||
|
||||
const lastStep = answeredQuestions[answeredQuestions.length - 1]
|
||||
|
||||
const lastStep = last(answeredQuestions)
|
||||
// L'ajout de la réponse permet de traiter les questions dont la réponse est
|
||||
// "une possibilité", exemple "contrat salarié . cdd"
|
||||
const lastStepWithAnswer =
|
||||
|
@ -114,7 +122,7 @@ export function getNextQuestions(
|
|||
.trim() as DottedName)
|
||||
: lastStep
|
||||
|
||||
return sortBy((question) => {
|
||||
const score = (question: string) => {
|
||||
const indexList =
|
||||
whitelist.findIndex((name) => question.startsWith(name)) + 1
|
||||
const indexNotPriority =
|
||||
|
@ -122,7 +130,9 @@ export function getNextQuestions(
|
|||
const differenceCoeff = questionDifference(question, lastStepWithAnswer)
|
||||
|
||||
return indexList + indexNotPriority + differenceCoeff
|
||||
}, nextSteps)
|
||||
}
|
||||
|
||||
return nextSteps.sort((a, b) => score(a) - score(b))
|
||||
}
|
||||
|
||||
export const useNextQuestions = function (): Array<DottedName> {
|
||||
|
|
|
@ -26,9 +26,12 @@ export const SatisfactionStyle: [
|
|||
function toPercentage(data: Record<string, number>): Record<string, number> {
|
||||
const total = Object.values(data).reduce((a, b: number) => a + b, 0)
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(data).map(([key, value]) => [key, (100 * value) / total])
|
||||
)
|
||||
return {
|
||||
...Object.fromEntries(
|
||||
Object.entries(data).map(([key, value]) => [key, (100 * value) / total])
|
||||
),
|
||||
total,
|
||||
}
|
||||
}
|
||||
|
||||
type SatisfactionChartProps = {
|
||||
|
@ -47,29 +50,23 @@ export default function SatisfactionChart({ data }: SatisfactionChartProps) {
|
|||
.filter((d) => Object.values(d.nombre).reduce((a, b) => a + b, 0))
|
||||
|
||||
return (
|
||||
<>
|
||||
<ResponsiveContainer width="100%" height={400}>
|
||||
<BarChart data={flattenData}>
|
||||
<XAxis dataKey="date" tickFormatter={formatMonth} />
|
||||
<Tooltip content={CustomTooltip} />
|
||||
{SatisfactionStyle.map(([level, { emoji, color }]) => (
|
||||
<Bar
|
||||
key={level}
|
||||
dataKey={level}
|
||||
stackId="1"
|
||||
fill={color}
|
||||
maxBarSize={50}
|
||||
>
|
||||
<LabelList
|
||||
dataKey={level}
|
||||
content={() => emoji}
|
||||
position="left"
|
||||
/>
|
||||
</Bar>
|
||||
))}
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</>
|
||||
<ResponsiveContainer width="100%" height={400}>
|
||||
<BarChart data={flattenData}>
|
||||
<XAxis dataKey="date" tickFormatter={formatMonth} />
|
||||
<Tooltip content={CustomTooltip} />
|
||||
{SatisfactionStyle.map(([level, { emoji, color }]) => (
|
||||
<Bar
|
||||
key={level}
|
||||
dataKey={level}
|
||||
stackId="1"
|
||||
fill={color}
|
||||
maxBarSize={50}
|
||||
>
|
||||
<LabelList dataKey={level} content={() => emoji} position="left" />
|
||||
</Bar>
|
||||
))}
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -8,13 +8,12 @@ import { Item, Select } from '@/design-system/field/Select'
|
|||
import { Spacing } from '@/design-system/layout'
|
||||
import { H2, H3 } from '@/design-system/typography/heading'
|
||||
import { formatValue } from 'publicodes'
|
||||
import { add, groupBy, mapObjIndexed, mergeWith } from 'ramda'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { useHistory, useLocation } from 'react-router-dom'
|
||||
import { toAtString } from '../../ATInternetTracking'
|
||||
import statsJson from '@/data/stats.json'
|
||||
import { debounce } from '../../utils'
|
||||
import { debounce, groupBy } from '../../utils'
|
||||
import { SimulateurCard } from '../Simulateurs/Home'
|
||||
import useSimulatorsData, { SimulatorData } from '../Simulateurs/metadata'
|
||||
import Chart, { Data, isDataStacked } from './Chart'
|
||||
|
@ -48,20 +47,24 @@ const isPAM = (name: string | undefined) =>
|
|||
const filterByChapter2 = (pages: Pageish[], chapter2: Chapter2 | '') => {
|
||||
return Object.entries(
|
||||
groupBy(
|
||||
(p) => ('date' in p ? p.date : p.month),
|
||||
pages.filter(
|
||||
(p) =>
|
||||
!chapter2 ||
|
||||
((!('page' in p) || p.page !== 'accueil_pamc') &&
|
||||
(p.page_chapter2 === chapter2 ||
|
||||
(chapter2 === 'PAM' && isPAM(p.page_chapter3))))
|
||||
)
|
||||
),
|
||||
(p) => ('date' in p ? p.date : p.month)
|
||||
)
|
||||
).map(([date, values]) => ({
|
||||
date,
|
||||
nombre: mapObjIndexed(
|
||||
(v: Array<{ nombre: number }>) => v.map((v) => v.nombre).reduce(add),
|
||||
groupBy((x) => ('page' in x ? x.page : x.click), values)
|
||||
nombre: Object.fromEntries(
|
||||
Object.entries(
|
||||
groupBy(values, (x) => ('page' in x ? x.page : x.click))
|
||||
).map(([key, values]) => [
|
||||
key,
|
||||
values.reduce((sum, value) => sum + value.nombre, 0),
|
||||
])
|
||||
),
|
||||
}))
|
||||
}
|
||||
|
@ -69,16 +72,19 @@ const filterByChapter2 = (pages: Pageish[], chapter2: Chapter2 | '') => {
|
|||
function groupByDate(data: Pageish[]) {
|
||||
return Object.entries(
|
||||
groupBy(
|
||||
(p) => ('date' in p ? p.date : p.month),
|
||||
data.filter((d) => 'page' in d && d.page === 'accueil')
|
||||
data.filter((d) => 'page' in d && d.page === 'accueil'),
|
||||
(p) => ('date' in p ? p.date : p.month)
|
||||
)
|
||||
).map(([date, values]) => ({
|
||||
date,
|
||||
nombre: Object.fromEntries(
|
||||
Object.entries(
|
||||
groupBy((x) => x.page_chapter1 + ' / ' + x.page_chapter2, values)
|
||||
groupBy(values, (x) => x.page_chapter1 + ' / ' + x.page_chapter2)
|
||||
)
|
||||
.map(([k, v]) => [k, v.map((v) => v.nombre).reduce(add, 0)] as const)
|
||||
.map(
|
||||
([k, v]) =>
|
||||
[k, v.map((v) => v.nombre).reduce((a, b) => a + b, 0)] as const
|
||||
)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, 7)
|
||||
),
|
||||
|
@ -89,8 +95,19 @@ const computeTotals = (
|
|||
data: Data<number> | Data<Record<string, number>>
|
||||
): number | Record<string, number> => {
|
||||
return isDataStacked(data)
|
||||
? data.map((d) => d.nombre).reduce(mergeWith(add), {})
|
||||
: data.map((d) => d.nombre).reduce(add, 0)
|
||||
? data
|
||||
.map((d) => d.nombre)
|
||||
.reduce(
|
||||
(acc, record) =>
|
||||
[...Object.entries(acc), ...Object.entries(record)].reduce(
|
||||
(merge, [key, value]) => {
|
||||
return { ...merge, [key]: (acc[key] ?? 0) + value }
|
||||
},
|
||||
{}
|
||||
),
|
||||
{}
|
||||
)
|
||||
: data.map((d) => d.nombre).reduce((a, b) => a + b, 0)
|
||||
}
|
||||
|
||||
interface BrushStartEndIndex {
|
||||
|
|
|
@ -16,7 +16,6 @@ import { evaluateQuestion, getMeta } from '@/utils'
|
|||
import { useSSRSafeId } from '@react-aria/ssr'
|
||||
import { DottedName } from 'modele-social'
|
||||
import { RuleNode } from 'publicodes'
|
||||
import { isEmpty } from 'ramda'
|
||||
import { useCallback, useContext } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import styled from 'styled-components'
|
||||
|
@ -135,7 +134,7 @@ export function SimpleField({
|
|||
required={meta.requis === 'oui'}
|
||||
missing={
|
||||
evaluation.nodeValue === undefined ||
|
||||
!isEmpty(evaluation.missingVariables)
|
||||
Object.keys(evaluation.missingVariables).length > 0
|
||||
}
|
||||
onChange={dispatchValue}
|
||||
showSuggestions={showSuggestions}
|
||||
|
|
|
@ -7,7 +7,6 @@ import { Strong } from '@/design-system/typography'
|
|||
import { H3 } from '@/design-system/typography/heading'
|
||||
import { Body, SmallBody } from '@/design-system/typography/paragraphs'
|
||||
import { useOrdinal } from '@/hooks/useOrdinal'
|
||||
import { isEmpty } from 'ramda'
|
||||
import { useCallback } from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { useDispatch } from 'react-redux'
|
||||
|
@ -28,7 +27,7 @@ export default function ModeAccompagnement() {
|
|||
|
||||
const dispatch = useDispatch()
|
||||
const imposition = engine.evaluate('entreprise . imposition')
|
||||
if (isSelected && !isEmpty(imposition.missingVariables)) {
|
||||
if (isSelected && Object.keys(imposition.missingVariables).length > 0) {
|
||||
dispatch(
|
||||
updateSituation(
|
||||
'entreprise . imposition',
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { useEngine } from '@/components/utils/EngineContext'
|
||||
import { DottedName } from 'modele-social'
|
||||
import { RuleNode } from 'publicodes'
|
||||
import { isEmpty } from 'ramda'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
export function useProgress(objectifs: DottedName[]): number {
|
||||
|
@ -13,8 +12,8 @@ export function useProgress(objectifs: DottedName[]): number {
|
|||
const objectifsApplicables = evaluatedObjectifs.filter(
|
||||
(objectif) => objectif.nodeValue !== null
|
||||
)
|
||||
const objectifsRemplis = objectifsApplicables.filter((objectif) =>
|
||||
isEmpty(objectif.missingVariables)
|
||||
const objectifsRemplis = objectifsApplicables.filter(
|
||||
(objectif) => Object.keys(objectif.missingVariables).length === 0
|
||||
)
|
||||
|
||||
if (!objectifsApplicables.length) {
|
||||
|
|
|
@ -1,17 +1,4 @@
|
|||
import { SitePathsContext } from '@/components/utils/SitePathsContext'
|
||||
import {
|
||||
add,
|
||||
any,
|
||||
countBy,
|
||||
difference,
|
||||
flatten,
|
||||
isNil,
|
||||
keys,
|
||||
map,
|
||||
mergeAll,
|
||||
mergeWith,
|
||||
sortBy,
|
||||
} from 'ramda'
|
||||
import { useContext } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { RootState } from '@/reducers/rootReducer'
|
||||
|
@ -92,39 +79,49 @@ const LEGAL_STATUS_DETAILS = {
|
|||
|
||||
export type LegalStatus = keyof typeof LEGAL_STATUS_DETAILS
|
||||
type Question = keyof LegalStatusRequirements
|
||||
type Answers = LegalStatusRequirements
|
||||
|
||||
const QUESTION_LIST: Array<Question> = keys(
|
||||
mergeAll(flatten(Object.values(LEGAL_STATUS_DETAILS)))
|
||||
)
|
||||
const QUESTION_LIST: Array<Question> = [
|
||||
'soleProprietorship',
|
||||
'directorStatus',
|
||||
'minorityDirector',
|
||||
'multipleAssociates',
|
||||
'autoEntrepreneur',
|
||||
]
|
||||
|
||||
const isCompatibleStatusWith =
|
||||
(answers: any) =>
|
||||
(statusRequirements: LegalStatusRequirements): boolean => {
|
||||
const stringify = map((x) => (!isNil(x) ? JSON.stringify(x) : x))
|
||||
const answerCompatibility = Object.values(
|
||||
mergeWith(
|
||||
(answer, statusValue) =>
|
||||
isNil(answer) || isNil(statusValue) || answer === statusValue,
|
||||
stringify(statusRequirements as any),
|
||||
stringify(answers)
|
||||
function isCompatibleStatusWith(
|
||||
answers: Answers,
|
||||
statusRequirements: LegalStatusRequirements
|
||||
): boolean {
|
||||
return Object.entries(statusRequirements).reduce<boolean>(
|
||||
(isCompatible, [question, statusValue]) => {
|
||||
const answer = answers[question as Question]
|
||||
|
||||
return (
|
||||
isCompatible &&
|
||||
(answer == null ||
|
||||
statusValue == null ||
|
||||
JSON.stringify(answer) === JSON.stringify(statusValue))
|
||||
)
|
||||
)
|
||||
const isCompatibleStatus = answerCompatibility.every((x) => x !== false)
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
return isCompatibleStatus
|
||||
}
|
||||
const possibleStatus = (
|
||||
answers: Array<LegalStatusRequirements> | LegalStatusRequirements
|
||||
): Record<LegalStatus, boolean> =>
|
||||
map(
|
||||
(statusRequirements) =>
|
||||
const possibleStatus = (answers: Answers): Record<LegalStatus, boolean> =>
|
||||
Object.fromEntries(
|
||||
Object.entries(LEGAL_STATUS_DETAILS).map(([key, statusRequirements]) => [
|
||||
key,
|
||||
Array.isArray(statusRequirements)
|
||||
? any(isCompatibleStatusWith(answers as any), statusRequirements)
|
||||
: isCompatibleStatusWith(answers as any)(
|
||||
? !!statusRequirements.some((requirement) =>
|
||||
isCompatibleStatusWith(answers, requirement)
|
||||
)
|
||||
: isCompatibleStatusWith(
|
||||
answers,
|
||||
statusRequirements as LegalStatusRequirements
|
||||
),
|
||||
LEGAL_STATUS_DETAILS
|
||||
)
|
||||
])
|
||||
) as Record<LegalStatus, boolean>
|
||||
|
||||
export const possibleStatusSelector = (state: {
|
||||
choixStatutJuridique: State
|
||||
|
@ -136,9 +133,14 @@ export const nextQuestionSelector = (state: RootState): Question | null => {
|
|||
const questionAnswered = Object.keys(
|
||||
legalStatusRequirements
|
||||
) as Array<Question>
|
||||
const possibleStatusList = flatten(
|
||||
Object.values(LEGAL_STATUS_DETAILS)
|
||||
).filter(isCompatibleStatusWith(legalStatusRequirements) as any)
|
||||
const possibleStatusList = Object.values(LEGAL_STATUS_DETAILS)
|
||||
.flat()
|
||||
.filter((requirement) =>
|
||||
isCompatibleStatusWith(legalStatusRequirements, requirement as any)
|
||||
)
|
||||
|
||||
const difference = <T>(l1: Array<T>, l2: Array<T>): Array<T> =>
|
||||
l1.filter((x) => !l2.includes(x))
|
||||
|
||||
const unansweredQuestions = difference(QUESTION_LIST, questionAnswered)
|
||||
const shannonEntropyByQuestion = unansweredQuestions.map(
|
||||
|
@ -146,24 +148,31 @@ export const nextQuestionSelector = (state: RootState): Question | null => {
|
|||
const answerPopulation = Object.values(possibleStatusList).map(
|
||||
(status: any) => status[question]
|
||||
)
|
||||
const frequencyOfAnswers = Object.values(
|
||||
countBy(
|
||||
(x) => x,
|
||||
answerPopulation.filter((x) => x !== undefined)
|
||||
)
|
||||
|
||||
const frequencyOfAnswers = Object.values<number>(
|
||||
answerPopulation
|
||||
.filter((x) => x !== undefined)
|
||||
.reduce(
|
||||
(counters: Record<string, number>, i) => ({
|
||||
...counters,
|
||||
[i]: (counters?.[i] ?? 0) + 1,
|
||||
}),
|
||||
{}
|
||||
)
|
||||
).map((numOccurrence) => numOccurrence / answerPopulation.length)
|
||||
const shannonEntropy = -frequencyOfAnswers
|
||||
.map((p) => p * Math.log2(p))
|
||||
.reduce(add, 0)
|
||||
.reduce((a, b) => a + b, 0)
|
||||
|
||||
return [question, shannonEntropy]
|
||||
}
|
||||
)
|
||||
|
||||
const sortedPossibleNextQuestions = sortBy(
|
||||
([, entropy]) => -entropy,
|
||||
shannonEntropyByQuestion.filter(([, entropy]) => entropy !== 0)
|
||||
).map(([question]) => question)
|
||||
const sortedPossibleNextQuestions = shannonEntropyByQuestion
|
||||
.filter(([, entropy]) => entropy !== 0)
|
||||
.sort(([, entropy1], [, entropy2]) => entropy2 - entropy1)
|
||||
.map(([question]) => question)
|
||||
|
||||
if (sortedPossibleNextQuestions.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
|
|
@ -82,6 +82,24 @@ export function omit<T, K extends keyof T>(obj: T, key: K): Omit<T, K> {
|
|||
return returnObject
|
||||
}
|
||||
|
||||
// TODO: This is will be included in the ES spec soon. Remove our custom
|
||||
// implementation and rely on browser native support and polyfill when it is
|
||||
// available.
|
||||
// https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Global_Objects/Array/groupBy
|
||||
// https://caniuse.com/?search=groupby
|
||||
export function groupBy<E, G extends string>(
|
||||
arr: Array<E>,
|
||||
callback: (elm: E, index: number, array: Array<E>) => G
|
||||
): Record<G, Array<E>> {
|
||||
return arr.reduce((result, item, currentIndex) => {
|
||||
const key = callback(item, currentIndex, arr)
|
||||
result[key] = result[key] || []
|
||||
result[key].push(item)
|
||||
|
||||
return result
|
||||
}, {} as Record<G, Array<E>>)
|
||||
}
|
||||
|
||||
export function isIterable<T>(obj: unknown): obj is Iterable<T> {
|
||||
return Symbol.iterator in Object(obj)
|
||||
}
|
||||
|
|
20
yarn.lock
20
yarn.lock
|
@ -6065,15 +6065,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/ramda@npm:^0.26.43":
|
||||
version: 0.26.44
|
||||
resolution: "@types/ramda@npm:0.26.44"
|
||||
dependencies:
|
||||
ts-toolbelt: ^6.3.3
|
||||
checksum: 5dbb5dfb0311d4c777ee4d65df4c553a5af4bde06148c149d70fffdfe2498fe3709d892e681f4bdba907662e74ed7d7a09359d13b489f0797edc20719de45a10
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/react-color@npm:^3.0.1":
|
||||
version: 3.0.6
|
||||
resolution: "@types/react-color@npm:3.0.6"
|
||||
|
@ -16803,7 +16794,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ramda@npm:^0.27.0, ramda@npm:^0.27.1":
|
||||
"ramda@npm:^0.27.1":
|
||||
version: 0.27.2
|
||||
resolution: "ramda@npm:0.27.2"
|
||||
checksum: 28d6735dd1eea1a796c56cf6111f3673c6105bbd736e521cdd7826c46a18eeff337c2dba4668f6eed990d539b9961fd6db19aa46ccc1530ba67a396c0a9f580d
|
||||
|
@ -18473,7 +18464,6 @@ __metadata:
|
|||
"@storybook/builder-vite": ^0.1.23
|
||||
"@storybook/react": ^6.5.0-alpha.49
|
||||
"@storybook/testing-library": ^0.0.9
|
||||
"@types/ramda": ^0.26.43
|
||||
"@types/react": ^17.0.0
|
||||
"@types/react-color": ^3.0.1
|
||||
"@types/react-dom": ^17.0.9
|
||||
|
@ -18500,7 +18490,6 @@ __metadata:
|
|||
modele-social: "workspace:^"
|
||||
publicodes: =1.0.0-beta.40
|
||||
publicodes-react: =1.0.0-beta.40
|
||||
ramda: ^0.27.0
|
||||
react: ^17.0.0
|
||||
react-color: ^2.14.0
|
||||
react-dom: ^17.0.0
|
||||
|
@ -19691,13 +19680,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ts-toolbelt@npm:^6.3.3":
|
||||
version: 6.15.5
|
||||
resolution: "ts-toolbelt@npm:6.15.5"
|
||||
checksum: 24ad00cfd9ce735c76c873a9b1347eac475b94e39ebbdf100c9019dce88dd5f4babed52884cf82bb456a38c28edd0099ab6f704b84b2e5e034852b618472c1f3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tsconfig-paths@npm:^3.14.1":
|
||||
version: 3.14.1
|
||||
resolution: "tsconfig-paths@npm:3.14.1"
|
||||
|
|
Loading…
Reference in New Issue