Display higher order variables only, filterable by tag

Use Ramda.js
[data] Rename values to historique
Introduce the selectedVariable full screen view
pull/1/head
Mael Thomas 2016-07-25 21:58:11 +02:00
parent a62b1eedb0
commit 59a64d6573
24 changed files with 267 additions and 148 deletions

View File

@ -24,6 +24,7 @@ parser: babel-eslint
env:
browser: true
commonjs: true
es6: true
extends:
- eslint:recommended
- plugin:react/recommended

View File

@ -1,5 +1,11 @@
export const SELECT_TAG = 'SELECT_TAG'
export let SELECT_TAG = 'SELECT_TAG'
export function selectTag(tagName, tagValue) {
return {type: SELECT_TAG, tagName, tagValue}
}
export let SELECT_VARIABLE = 'SELECT_VARIABLE'
export function selectVariable(name) {
return {type: SELECT_VARIABLE, name}
}

View File

@ -0,0 +1,30 @@
import React, { Component } from 'react'
export default class SelectedVariable extends Component {
render() {
let {
variable: {
name,
first: {
description
},
tags
},
selectedTags
} = this.props
return (
<section id="selected-variable">
<h1>{name}</h1>
<p>{description}</p>
<ul>
{Object.keys(tags)
.filter(name => !selectedTags.find(([n]) => name == n))
.map(name =>
<li key={name}>
{name + ': ' + tags[name]}
</li>
)}
</ul>
</section>)
}
}

View File

@ -8,7 +8,7 @@ export default class TagNavigation extends React.Component {
<section id="tag-navigation">
{selectedTags.length > 0 &&
<ul id="selected">
{selectedTags.map(([name, value]) => <li>
{selectedTags.map(([name, value]) => <li key={name}>
{name + ': ' + value}
</li>)
}

View File

@ -1,6 +1,8 @@
import React from 'react'
import SelectedVariable from './SelectedVariable'
import colors from './variable-colors.yaml'
import R from 'ramda'
function convertHex(hex,opacity){
let r = parseInt(hex.substring(0,2), 16),
@ -11,29 +13,36 @@ function convertHex(hex,opacity){
return result
}
const Variable = ({variable: {variable, tags}, selectedTags, variableColors}) =>
<li className="variable" style={{background: convertHex(variableColors[variable], .15)}}>
<h3>{variable}</h3>
<ul>
{Object.keys(tags)
.filter(name => !selectedTags.find(([n]) => name == n))
.map(name =>
<li key={name}>
{name + ': ' + tags[name]}
</li>
)}
</ul>
const Variable = ({color, name, selectVariable}) =>
<li
className="variable" style={{background: convertHex(color, .15)}}
onClick={() => selectVariable(name)} >
<h3>{name}</h3>
</li>
export default class Variables extends React.Component {
render(){
let {variables, selectedTags} = this.props,
variableSet =
variables.reduce((set, {variable}) => set.add(variable), new Set()), // get unique variable names
variableColors = [...variableSet].reduce((correspondance, v, i) => Object.assign(correspondance, {[v]: colors[i]}), {})
let {variables, selectedTags, selectedVariable, selectVariable} = this.props
console.log('variables prop in <Variables', variables)
// let
// variableSet =
// variables.reduce((set, {variable}) => set.add(variable), new Set()), // get unique variable names
// variableColors = [...variableSet].reduce((correspondance, v, i) => Object.assign(correspondance, {[v]: colors[i]}), {})
console.log('selectedVariable',selectedVariable)
if (selectedVariable != null)
return <SelectedVariable
variable={R.find(R.propEq('name', selectedVariable))(variables)}
selectedTags={selectedTags}
/>
return <ul id="variables">
{variables.map((v, i) => <Variable variableColors={variableColors} key={i} variable={v} selectedTags={selectedTags}/>)}
{variables.map((v, i) =>
<Variable key={i}
color={colors[i]} name={v.name}
selectVariable={selectVariable}
/>
)}
</ul>
}
}

View File

@ -6,7 +6,7 @@ body {
h1 {
text-align: center;
font-weight: 200;
font-size: 200%;
font-size: 215%;
}
ul {
@ -63,15 +63,35 @@ li {
}
.variable {
width: 13em;
width: 6em;
display: inline-block;
margin-bottom: 1em;
margin-right: 1em;
padding: .6em;
vertical-align: top;
height: 13em;
padding: 1em 2em;
text-align: center;
text-transform: capitalize;
}
.variable ul {
padding-left: .6em
}
#selected-variable {
width: 70%;
display: inline-block;
position: absolute;
}
#selected-variable h1 {
text-transform: capitalize;
font-size: 180%;
}
#selected-variable p {
width: 50%;
margin: 0 auto;
border: 1px solid #ddd;
border-left: 2px solid #ddd;
padding: 1em 2em;
}

View File

@ -1,21 +1,22 @@
import React from 'react'
import {connect} from 'react-redux'
import {getVariables} from '../selectors'
import TagNavigation from '../components/TagNavigation'
import Variables from '../components/Variables'
import * as actions from '../actions'
import {bindActionCreators} from 'redux'
import {getTagsToSelect} from '../selectors'
import {tagsToSelectSelector, variablesSelector} from '../selectors'
class Explorer extends React.Component {
render() {
let {variables, selectedTags, tagsToSelect, actions: {selectTag}} = this.props
let {variables, selectedTags, selectedVariable, tagsToSelect, actions: {selectTag, selectVariable}} = this.props
return (
<div>
<h1>Liste des prélèvements sociaux sur les salaires en France</h1>
<h1>Les prélèvements sociaux sur les salaires en France</h1>
<TagNavigation selectedTags={selectedTags} tagsToSelect={tagsToSelect} selectTag={selectTag}/>
<Variables variables={variables} selectedTags={selectedTags} />
<Variables variables={variables}
selectedTags={selectedTags} selectedVariable={selectedVariable}
selectVariable={selectVariable}/>
</div>
)
}
@ -24,9 +25,10 @@ class Explorer extends React.Component {
const mapStateToProps = state => (
{
variables: getVariables(state),
selectedTags: state.selectedTags,
tagsToSelect: getTagsToSelect(state)
tagsToSelect: tagsToSelectSelector(state),
variables: variablesSelector(state),
selectedVariable: state.selectedVariable
}
)

106
model.js
View File

@ -1,52 +1,68 @@
import parameters from './load-parameters'
import deepAssign from 'deep-assign'
let
groupedByVariableName = parameters
.filter(p => p && p.variable)
.reduce((acc, p) => {
let variableName = p.variable
if (acc[variableName])
acc[variableName].push(p)
else
acc[variableName] = [p]
return acc
}, {}),
conflictingTags = (tags1, tags2) =>
Object.keys(tags1).reduce((conflicts, k) => {
if (typeof tags2[k] != 'undefined' && tags2[k] !== tags1[k])
conflicts.push(k)
return conflicts
}, []),
finalVariables =
Object.keys(groupedByVariableName)
.reduce((list, name) => {
let items = groupedByVariableName[name]
/* Les items sont des fragments de variables.
Les premiers fragments vont être fusionnés dans les suivants,
sauf s'il introduit un écrasement d'un tag */
let newItems = items.slice(1).reduce((mergedItems, item) => {
let mergedItem = mergedItems.reduce((final, itemBefore) => {
let oups = conflictingTags(itemBefore.tags, item.tags)
//console.log('conflicts for ', itemBefore.tags, item.tags)
return oups.length ? item : deepAssign({}, item, itemBefore)
},
item)
mergedItems.push(mergedItem)
return mergedItems
},
[items[0]])
import R from 'ramda'
let groupedItemsByVariable = R.groupBy(R.prop('variable'))(parameters),
higherOrderVariables = R.pipe(
R.keys,
R.map(R.pipe(
R.propEq('variable'),
R.flip(R.find)(parameters)
))
)(groupedItemsByVariable),
hasHistoryProp = R.pipe(JSON.stringify, R.contains('"historique":')),
itemHasHistoricProp = (item, prop) => R.has(prop)(item) && hasHistoryProp(item[prop]),
itemIsCalculable = item =>
itemHasHistoricProp(item, 'linear') || itemHasHistoricProp(item, 'marginalRateTaxScale'),
return [
/* Gardons seulement les variables ayant une
implémentation : capable de faire un calcul */
...newItems.filter(i => JSON.stringify(i).indexOf('values') + 1),
...list]
}, [])
tagsConflict = (tags1, tags2) =>
R.compose(
R.any(R.identity),
R.values,
R.mapObjIndexed((tagValue, tag) => tags2[tag] != undefined && tags2[tag] !== tagValue)
)(tags1),
export default finalVariables
mergedItemsByVariable =
R.mapObjIndexed((variableItems, name) => {
/* Les items sont des fragments de variables.
Les premiers fragments vont être fusionnés dans les suivants,
sauf s'ils provoquent l'écrasement d'un tag */
let mergedVariableItems = R.tail(variableItems) // Le premier ne peut être étendu
.reduce((mergedItems, item) =>
[ ...mergedItems,
mergedItems.reduce((final, higherLevelItem) => {
let oups = tagsConflict(higherLevelItem.tags, item.tags)
return oups ? item : deepAssign({}, item, higherLevelItem)
}, item)
], R.of(R.head(variableItems)))
return {
name,
// La variable de haut niveau, contenant la plupart du temps une description, etc.
first: R.head(variableItems),
// Tous les tags qui peuvent être trouvés dans les items de cette variable
tags: R.pipe(
R.pluck('tags'),
R.map(R.map(R.of)),
R.reduce(R.mergeWith(R.union), {})
)(variableItems),
// Gardons seulement les variables ayant une implémentation : capable de faire un calcul
calculable: R.filter(itemIsCalculable)(mergedVariableItems)
}}
)(groupedItemsByVariable)
let calculableItems =
R.pipe(
R.values,
R.pluck('calculable'),
R.unnest
)(mergedItemsByVariable),
mergedItems = R.values(mergedItemsByVariable)
export {
groupedItemsByVariable,
calculableItems,
mergedItems
}

View File

@ -14,6 +14,7 @@
"babel-runtime": "^6.6.1",
"classnames": "^2.2.1",
"deep-assign": "^2.0.0",
"ramda": "^0.21.0",
"react": "^15.0.1",
"react-dom": "^15.0.1",
"react-hot-loader": "3.0.0-beta.1",

View File

@ -1,4 +1,4 @@
- variable: chomage
- variable: majorationchomage #Devrait être chomage
alias: majoration chomage
description: Majoration des contributions patronales dassurance chômage
complément: oui

View File

@ -22,11 +22,11 @@
marginalRateTaxScale:
- threshold: 0
rate:
values:
historique:
2001-04-01: .008
- threshold: 1
rate:
values:
historique:
2001-04-01: .009
- threshold: 3
rate: 0
@ -40,11 +40,11 @@
marginalRateTaxScale:
- threshold: 0
rate:
values:
historique:
2001-04-01: .008
- threshold: 1
rate:
values:
historique:
2001-04-01: .009
- threshold: 3
rate: 0
@ -58,15 +58,15 @@
marginalRateTaxScale:
- threshold: 0
rate:
values:
historique:
2001-04-01: .008
- threshold: 1
rate:
values:
historique:
2001-04-01: .009
- threshold: 4
rate:
values:
historique:
2016-01-01: .009
2001-04-01: 0
- threshold: 8
@ -81,15 +81,15 @@
marginalRateTaxScale:
- threshold: 0
rate:
values:
historique:
2001-04-01: .012
- threshold: 1
rate:
values:
historique:
2001-04-01: .013
- threshold: 4
rate:
values:
historique:
2016-01-01: .013
2001-04-01: 0
- threshold: 8

View File

@ -23,7 +23,7 @@
rate: 0
- threshold: 1
rate:
values:
historique:
2016-01-01: .1275
2015-01-01: .1275
2014-01-01: .1268
@ -46,7 +46,7 @@
rate: 0
- threshold: 1
rate:
values:
historique:
2016-01-01: .078
2015-01-01: .078
2014-01-01: .0775

View File

@ -9,7 +9,7 @@
linear:
base: assiette_cotisations_sociales
limit: 4 * plafond_securite_sociale
values:
historique:
2017-01-01: 0.0025
2016-01-01: 0.0025
2011-04-01: 0.003

View File

@ -17,13 +17,13 @@
marginalRateTaxScale:
- threshold: 0
rate:
values:
historique:
2016-01-01: 0.00036
2011-01-01: 0.00036
1993-07-01: 0
- threshold: 0
rate:
values:
historique:
2016-01-01: 0.00036
1993-07-01: 0.00036
- threshold: 4
@ -35,13 +35,13 @@
marginalRateTaxScale:
- threshold: 0
rate:
values:
historique:
2016-01-01: 0.00024
2011-01-01: 0.00024
1993-07-01: 0
- threshold: 0
rate:
values:
historique:
2016-01-01: 0.00024
1993-07-01: 0.00024
- threshold: 4

View File

@ -1,5 +1,5 @@
- variable: ARRCO
description: |
description: >
Cotisation de retraite complémentaire pour tous les salariés du secteur privé,
(pour l'Association pour le Régime de Retraite Complémentaire des salariés)
référence: http://www.agirc-arrco.fr/l-agirc-et-larrco/chiffres-cles
@ -20,7 +20,7 @@
marginalRateTaxScale:
- threshold: 0
rate:
values:
historique:
2016-01-01: .0465
2015-01-01: .0465
2014-01-01: .0458
@ -40,7 +40,7 @@
marginalRateTaxScale:
- threshold: 0
rate:
values:
historique:
2016-01-01: .031
2015-01-01: .031
2014-01-01: .0305
@ -60,7 +60,7 @@
marginalRateTaxScale:
- threshold: 0
rate:
values:
historique:
2016-01-01: .0465
2015-01-01: .0465
2014-01-01: .0458
@ -71,7 +71,7 @@
1993-07-01: .02952
- threshold: 1
rate:
values:
historique:
2016-01-01: .1215
2015-01-01: .1215
2014-01-01: .1208
@ -90,7 +90,7 @@
marginalRateTaxScale:
- threshold: 0
rate:
values:
historique:
2016-01-01: .031
2015-01-01: .031
2014-01-01: .0305
@ -101,7 +101,7 @@
1993-01-01: .01968
- threshold: 1
rate:
values:
historique:
2016-01-01: .081
2015-01-01: .081
2014-01-01: .0805

View File

@ -1,19 +1,26 @@
- variable: chomage
description: Cotisation dassurance chômage
tags:
- plafonnée
- branche: chomage
recouvreur: URSSAF
destinataire: Pôle emploi
plafonnée: oui
branche: chomage
collecteur: URSSAF
destinataire: Pôle emploi
# Devrait être :
# - plafonnée
# - branche: chomage
# collecteur: URSSAF
# destinataire: Pôle emploi
conditions:
assimilé salarié: non
linear:
base: assiette cotisations sociales
limit: 4 * plafond sécurité sociale
- variable: chomage
tags:
dû par: employeur
linear:
values:
historique:
2015-01-01: 0.04
2007-01-01: 0.04
2006-01-01: 0.0404
@ -31,7 +38,7 @@
tags:
dû par: salarié
linear:
values:
historique:
2015-01-01: .024
2007-01-01: .024
2006-01-01: .0244

View File

@ -7,6 +7,6 @@
linear:
base: assiette_cotisations_sociales
values:
historique:
2015-01-01: .003
2004-07-01: .003

View File

@ -9,7 +9,7 @@
tags:
dû par: employeur
linear:
values:
historique:
2016-01-01: .0022
2001-01-01: .0022
2000-01-01: .0017
@ -21,7 +21,7 @@
tags:
dû par: salarié
linear:
values:
historique:
2016-01-01: .0013
2001-01-01: .0013
2000-01-01: .0011

View File

@ -8,7 +8,7 @@
tags:
dû par: employeur
linear:
values:
historique:
2016-01-01: 0.04
2007-01-01: 0.04
2006-01-01: 0.0404
@ -26,7 +26,7 @@
tags:
dû par: salarié
linear:
values:
historique:
2016-01-01: .024
2007-01-01: .024
2006-01-01: .0244
@ -56,7 +56,7 @@
linear:
# base: selon cette source, la base est l'assiette de la CSG : https://baseircantec.retraites.fr/cotisations-assurance-maladie-alsace-moselle.html
# information non retrouvée ailleurs
values:
historique:
2012-01-01: 1.5
2008-01-01: 1.6
2007-07-01: 1.7
@ -75,7 +75,7 @@
- régime Alsace-Moselle
- régime: agricole
linear:
values:
historique:
2014-01-01: 1.1
2011-07-01: 1.2
2008-07-01: 1.3

View File

@ -14,7 +14,7 @@
base: assiette_cotisations_sociales
var:
- condition: "régime = agricole"
values:
historique:
2015-01-01: 0.0045
1992-01-01: 0.0045

View File

@ -14,7 +14,7 @@
# TODO introduire la répartition entre part régionale, quota d'app., hors quota
# https://www.service-public.fr/professionnels-entreprises/vosdroits/F22574
linear:
values:
historique:
2017-01-01: 0.0068
2014-01-01: 0.0068
1993-07-01: 0.005
@ -23,7 +23,7 @@
tags:
- régime Alsace-Moselle
linear:
values:
historique:
2017-01-01: 0.0044
@ -35,30 +35,30 @@
linear:
var:
- condition: "(ratio_alternants < .01) & (effectif_entreprise < 2000)"
values:
historique:
2016-01-01: .005
2013-01-01: .004
- condition: "(ratio_alternants < .01) & (effectif_entreprise >= 2000)"
values:
historique:
2016-01-01: .006
2014-01-01: .006
2013-01-01: .005
- condition: "ratio_alternants < .02"
values:
historique:
2016-01-01: .002
2015-01-01: .002
2013-01-01: .001
- condition: "ratio_alternants < .03"
values:
historique:
2016-01-01: .002
2015-01-01: .002
2013-01-01: .001
- condition: "ratio_alternants < .04"
values:
historique:
2016-01-01: .0005
2015-01-01: .0005
- condition: "ratio_alternants < .05"
values:
historique:
2016-01-01: .0005
2015-01-01: 0
@ -68,7 +68,7 @@
- obsolète
- apprentissage: contribution additionnelle
linear:
values:
historique:
2017-01-01: 0
2014-01-01: 0
2006-01-01: 0.0018

View File

@ -1,10 +1,11 @@
- variable: vieillesse
description: Cotisation au régime de retraite de base des salariés.
tags:
branche: retraite
type de retraite: de base
recouvreur: URSSAF
collecteur: URSSAF
destinataire: CNAV
# CTP: 100
linear:
base: assiette_cotisations_sociales
@ -13,7 +14,7 @@
dû par: salarié
plafonnée: non
linear:
values:
historique:
2018-01-01: .004
2017-01-01: .004
2016-01-01: .0035
@ -26,7 +27,7 @@
dû par: employeur
plafonnée: non
linear:
values:
historique:
2018-01-01: 0.019
2017-01-01: 0.019
2016-01-01: 0.0185
@ -45,7 +46,7 @@
dû par: salarié
plafonnée: oui
linear:
values:
historique:
2017-01-01: .0690
2016-01-01: .0690
2015-01-01: .0685
@ -59,7 +60,7 @@
dû par: employeur
plafonnée: oui
linear:
values:
historique:
2017-01-01: .0855
2016-01-01: .0855
2015-01-01: .085

View File

@ -1,6 +1,6 @@
import { combineReducers } from 'redux'
import { SELECT_TAG } from './actions'
import { SELECT_TAG, SELECT_VARIABLE} from './actions'
function selectedTags(state = [], {type, tagName, tagValue}) {
switch (type) {
@ -11,8 +11,18 @@ function selectedTags(state = [], {type, tagName, tagValue}) {
}
}
function selectedVariable(state = null, {type, name}) {
switch (type) {
case SELECT_VARIABLE:
return name
default:
return state
}
}
export default combineReducers({
selectedTags
selectedTags,
selectedVariable
})

View File

@ -1,6 +1,22 @@
import { createSelector } from 'reselect'
import finalVariables from './model'
import {calculableItems, higherOrderVariables, mergedItems} from './model'
import R from 'ramda'
let
// variableHasTagValue = variable => ([tag, value]) => console.log('tv', variable.tags, tag, value),
variableHasTagValue = variable => ([osef, [tag, value]]) => R.pathEq(['tags', tag], value)(variable),
variableHasSelectedTags = variable => R.compose(
R.all(variableHasTagValue(variable)),
R.toPairs
),
filterVariables = variables => tags => R.filter(item => variableHasSelectedTags(item)(tags))(variables)
export const finalVariablesSelector = createSelector(
[state => state.selectedTags],
filterVariables(calculableItems)
)
/* Tag names, values, and number of variables per tag */
const unorderedTagStats = finalVariables =>
finalVariables
.reduce((stats, variable) => {
@ -19,23 +35,23 @@ const unorderedTagStats = finalVariables =>
.reduce((acc, n) => ([...acc, {name: n, ...stats[n]}]), [])
.sort((a, b) => b.number - a.number)
export const getVariables = createSelector(
[state => state.selectedTags],
tags =>
finalVariables.filter(variable =>
//This variable must be tagged as described in the selected tags array
tags == null ? true : tags.reduce((result, [k, v]) => result && variable.tags && variable.tags[k] === v, true)
)
)
const getTagStats = createSelector(
[getVariables],
let tagStatsSelector = createSelector(
[finalVariablesSelector],
variables => tagStats(unorderedTagStats(variables))
)
export const getTagsToSelect = createSelector(
[getTagStats, state => state.selectedTags],
(availableTags, selectedTags) =>
export let tagsToSelectSelector = createSelector(
[state => state.selectedTags, tagStatsSelector],
(selectedTags, availableTags) =>
availableTags.filter(t => !selectedTags.find(([name]) => t.name === name))
)
export let variablesSelector = createSelector(
[state => state.selectedTags],
selectedTags => R.filter(
({tags}) =>
R.all(
([tag, value]) => R.contains(value, tags[tag]),
)(selectedTags)
)(mergedItems)
)