Commit eae9df74 authored by Florian Pagnoux's avatar Florian Pagnoux
Browse files

Merge branch 'parsed-xlsx'

parents b1d67bc7 4b92492a
......@@ -9,5 +9,6 @@
"test": {
"presets": [["next/babel", { "preset-env": { "modules": "commonjs" } }]]
}
}
},
"plugins": ['@babel/plugin-proposal-optional-chaining']
}
......@@ -7,7 +7,7 @@ it('should create a route towards a table', () => {
it('should handle a section containing tables', () => {
const desc = {
"children": {
"subparams": {
"rsa_montant": {"table": {description: "Montant" }},
"rsa_forfait_logement": {"table": {description: "Forfait logement"}},
}
......@@ -27,8 +27,8 @@ it('should handle a section containing tables', () => {
it('should handle a section containing sections', () => {
const desc = {
"children": {
"rsa": {"children": {}}
"subparams": {
"rsa": {"subparams": {}}
}
}
const routes = extractRoutes(desc, '/prestations')
......@@ -41,8 +41,8 @@ it('should handle a section containing sections', () => {
it('should handle a section containing subsections', () => {
const desc = {
"children": {
"contributions": {"children": {}}
"subparams": {
"contributions": {"subparams": {}}
}
}
const routes = extractRoutes(desc, '/prestations')
......@@ -52,3 +52,15 @@ it('should handle a section containing subsections', () => {
expect(routes[1].route).toEqual('/prestations/contributions')
expect(routes[1].page).toEqual('/section')
});
it('should handle a section containing a list', () => {
const desc = {
"subparams": [
{"table": {description: "Montant"}},
{"table": {description: "Forfait logement"}},
]
}
const routes = extractRoutes(desc, '/prestations')
expect(routes[0].route).toEqual('/prestations')
expect(routes[0].page).toEqual('/section')
});
......@@ -2,13 +2,13 @@ import {resolveSection} from '../../server/resolver'
it('should run', (done) => {
resolveSection({
"children": {
"subparams": {
"contributions": {
"subsection": "prelevements_sociaux.contributions"
"subsection": "prelevements_sociaux.contributions_sociales"
}
}
}).then(section => {
expect(section.children.contributions.children).toBeTruthy()
expect(section.subparams.contributions.subparams).toBeTruthy()
done()
})
});
......@@ -17,32 +17,87 @@ it('extractValues can extract dates from a simple parameter', () => {
}
expect(extractValues(parameter)).toEqual({
"prestations.minima_sociaux.rsa.montant_de_base_du_rsa": {
"2016-09-01": 535.17,
"2017-04-01": 536.78,
"2017-09-01": 545.48,
"2018-04-01": 550.93
"2016-09-01": {value: 535.17},
"2017-04-01": {value: 536.78},
"2017-09-01": {value: 545.48},
"2018-04-01": {value: 550.93}
}})
});
it('extractValues can extract dates from a scale', () => {
const data = extractValues(bareme)
expect(data['impot_revenu.bareme.2.thresold']).toEqual({"2002-01-01":"9500.0","2014-01-01":"26764.0","2015-01-01":"26791.0"})
expect(data['impot_revenu.bareme.2.thresold']).toEqual(
{
"2002-01-01": {value: 9500.0},
"2014-01-01": {value: 26764.0},
"2015-01-01": {value: 26791.0}
})
})
it('extractValues can extract dates from a parameter node', () => {
const data = extractValues(rsaForFaitLogement)
expect(data['prestations.minima_sociaux.rsa.forfait_logement.taux_1_personne']).toEqual({"2009-06-01": 0.12})
expect(data['prestations.minima_sociaux.rsa.forfait_logement.taux_2_personnes']).toEqual({"2009-06-01": 0.16})
expect(data['prestations.minima_sociaux.rsa.forfait_logement.taux_1_personne']).toEqual({"2009-06-01": {value: 0.12}})
expect(data['prestations.minima_sociaux.rsa.forfait_logement.taux_2_personnes']).toEqual({"2009-06-01": {value: 0.16}})
});
it('extractValues can extract metadata from a parameter node', () => {
const data = extractValues(rsaForFaitLogement)
expect(data['date_parution_jo']).toEqual({"2009-06-01": "2009-05-01"})
});
it('extractValues can extract dates from a custom parameter node', () => {
const data = extractValues(rsaMajoration)
expect(data['prestations.minima_sociaux.rsa.majoration_rsa.taux_deuxieme_personne']).toEqual({"2009-06-01": 0.5})
expect(data['prestations.minima_sociaux.rsa.majoration_rsa.taux_deuxieme_personne']).toEqual({"2009-06-01": {value: 0.5}})
});
it('extractValues can extract a unit', () => {
const parameter = {
"description": "Montant de base du RSA",
"id": "prestations.minima_sociaux.rsa.montant_de_base_du_rsa",
"values": {
"2016-09-01": 535.17,
"2017-04-01": 536.78,
},
"metadata": {
"unit": 'currency-EUR'
}
}
expect(extractValues(parameter)).toEqual({
"prestations.minima_sociaux.rsa.montant_de_base_du_rsa": {
"2016-09-01": {value: 535.17, unit: "currency-EUR"},
"2017-04-01": {value: 536.78, unit: "currency-EUR"},
}})
});
it('extractValues can extract a chaning unit', () => {
const parameter = {
"description": "Montant du PSS",
"id": "cotisations.pss",
"values": {
"2010-01-01": 3500,
"2002-01-01": 3000,
"1990-01-01": 20000,
"1985-01-01": 15000,
},
"metadata": {
"unit": {
"2002-01-01": 'currency-EUR',
"1985-01-01": 'currency-FRF'
}
}
}
expect(extractValues(parameter)).toEqual({
"cotisations.pss": {
"2010-01-01": {value: 3500, unit: "currency-EUR"},
"2002-01-01": {value: 3000, unit: "currency-EUR"},
"1990-01-01": {value: 20000, unit: "currency-FRF"},
"1985-01-01": {value: 15000, unit: "currency-FRF"},
}})
});
it('extractData can extract the data', () => {
const data = extractData(rsaMajoration)
expect(data).toHaveLength(3)
expect(data[1].date).toBe('2009-06-01')
expect(data[1]['prestations.minima_sociaux.rsa.majoration_parent_isole.femmes_enceintes']).toBe(1.284)
expect(data[1]['prestations.minima_sociaux.rsa.majoration_parent_isole.femmes_enceintes'].value).toBe(1.284)
});
{
"children": {
"subparams": {
"taux_1_personne": {
"description": "1 personne",
"id": "prestations.minima_sociaux.rsa.forfait_logement.taux_1_personne",
......@@ -24,5 +24,10 @@
}
},
"description": "Forfait logement (en % du montant forfaitaire du RSA)",
"id": "prestations.minima_sociaux.rsa.forfait_logement"
"id": "prestations.minima_sociaux.rsa.forfait_logement",
"metadata": {
"date_parution_jo": {
"2009-06-01": "2009-05-01"
}
}
}
import map from 'lodash.map'
import filter from 'lodash.filter'
import range from 'lodash.range'
import size from 'lodash.size'
import isString from 'lodash.isstring'
import sortBy from 'lodash.sortby'
import flow from 'lodash.flow'
import { FormattedDate, IntlProvider, addLocaleData, FormattedNumber} from 'react-intl'
import fr from 'react-intl/locale-data/fr'
......@@ -10,17 +13,27 @@ import Table from '../components/Table'
addLocaleData(fr)
const cellFormatter = {
'/1': props => props.value !== undefined && <FormattedNumber value={props.value} style="percent" maximumFractionDigits={3}/>,
'EUR': props => props.value !== undefined && <FormattedNumber value={props.value} style="currency" maximumFractionDigits={3} currency="EUR"/>,
function cellFormatter({value, metadata}) {
if ((! value && ! value !== 0) || ! metadata || ! metadata.unit) {
return value
}
if (metadata.unit == '/1') {
return <FormattedNumber value={value} style="percent" maximumFractionDigits={3}/>
}
if (metadata.unit.startsWith('currency')) {
const currency = metadata.unit.split('-')[1]
return <FormattedNumber value={value} style="currency" maximumFractionDigits={3} currency={currency}/>
}
return value
}
function buildSimpleColumn(parameter) {
const source = parameter.source.replace('openfisca_baremes_ipp/', '') // The structure of the repo is not the typical OF structure, so we monkey patch
return {
Header: <span className="edit-link">{parameter.description || parameter.id}<br/><a target="_blank" href={parameter.source}>Edit</a></span>,
Header: <span className="edit-link">{parameter.description || parameter.id}<br/><a target="_blank" href={source}>Edit</a></span>,
accessor: item => item[parameter.id],
id: parameter.id,
Cell: cellFormatter[parameter.unit]
Cell: cellFormatter
}
}
......@@ -52,14 +65,37 @@ function buildColumn(parameter) {
if (parameter.brackets) {
return buildScaleColumn(parameter)
}
if (parameter.children) {
if (parameter.subparams) {
return {
Header: parameter.description || parameter.id,
columns: map(parameter.children, buildColumn)
columns: flow([
x => map(x, (subParam, name) => Object.assign({}, subParam, {name})),
x => sortBy(x, subParam => parameter?.metadata?.order?.indexOf(subParam.name)),
x => map(x, buildColumn),
x => x.concat(buildMetaDataColumns(parameter))
])(parameter.subparams)
}
}
}
function buildMetaDataColumns(parameter) {
const metadata = {
reference: {title: 'Références législatives', width: 1.8},
date_parution_jo: {title: 'Parution au JO', width: 0.7},
notes: {title: 'Notes', width: 2},
}
return flow([
x => filter(x, fieldName => parameter.metadata[fieldName]),
x => map(x, fieldName => ({
Header: metadata[fieldName].title,
accessor: item => item[fieldName],
id: fieldName,
width: metadata[fieldName].width
}))
])(Object.keys(metadata))
}
const ParameterTable = ({parameter}) => {
const data = extractData(parameter)
const dateColumn = {
......@@ -71,10 +107,13 @@ const ParameterTable = ({parameter}) => {
const columns = [dateColumn, buildColumn(parameter)]
return (
<IntlProvider locale="fr">
<Table
columns={columns}
data={data}
/>
<div>
<Table
columns={columns}
data={data}
/>
<p className="table-doc">{parameter.documentation}</p>
</div>
</IntlProvider>
)
}
......
......@@ -2,6 +2,7 @@ import sum from 'lodash.sum'
import max from 'lodash.max'
import map from 'lodash.map'
import range from 'lodash.range'
import isPlainObject from 'lodash.isplainobject'
// Data and colmuns Preprocessing
......@@ -58,7 +59,7 @@ function renderHeader(columns, index) {
key={index2}
colSpan={column.colSpan}
rowSpan={column.rowSpan || 1}
style={{flex:`${column.colSpan * 100} 0 auto`, width:`${column.colSpan * 100}px`}}
style={{flex: `${column.width || 1} 0 auto`, 'width': `${(column.width || 1) * 100}px`}}
>
{column.Header}
</th>
......@@ -68,17 +69,28 @@ function renderHeader(columns, index) {
function renderDatum(datum, column) {
const value = column.accessor(datum)
if (! isPlainObject(value)) {
if (column.Cell) {
return <column.Cell value={value}/>
}
return value
}
if (column.Cell) {
return <column.Cell value={value}/>
return <column.Cell value={value.value} metadata={value}/>
}
return value
return value.value
}
function renderData(data, dataColumns) {
return data.map((datum, index) => {
return <tr key={index}>
{dataColumns.map((column, index2) => {
return <td key={index2} style={{flex: '100 0 auto', width:'100px'}}>
return <td
key={index2}
colSpan={column.colSpan}
style={{flex: `${column.width || 1} 0 auto`, 'width': `100px`}}
>
<span>{renderDatum(datum, column)}</span>
</td>
})}
......
......@@ -10,10 +10,10 @@ Considérons le fichier de configuration suivant:
```YAML
title: Prestations sociales
children:
subparams:
rsa:
title: Revenu de solidarité active
children:
subparams:
rsa_montant:
table: prestations.minima_sociaux.rsa.montant_de_base_du_rsa
rsa_forfait_logement:
......@@ -30,7 +30,7 @@ Ce fichier génére la section suivante:
Notes:
- Le `title` de premier niveau définit le titre de la section.
- L'attribut `children` permet de définir des sous-sections, de manière arborescente.
- L'attribut `subparams` permet de définir des sous-sections, de manière arborescente.
- Le mot clé `table` permet de référencer une table de paramètre. Voir [Configuration d'une table](#configuration-dune-table).
- Il est possible de générer automatiquement le contenu d'une sous-section. Voir [Génération d'une sous-section](#génération-dune-sous-section).
......@@ -61,7 +61,7 @@ Note:
### Table sur-mesure
Il est possible de définir de manière fine le contenu d'une table.
Il est possible de définir de manière fine le contenu d'une table.
Par exemple:
......@@ -87,11 +87,11 @@ Note:
## Génération d'une sous-section
Les sous-sections d'une page peuvent être générés en se basant sur l'arbre des paramètres OpenFisca. Par exemple,
Les sous-sections d'une page peuvent être générés en se basant sur l'arbre des paramètres OpenFisca. Par exemple,
```YAML
title: Prélèvements sociaux
children:
subparams:
plafond_securite_sociale:
table: cotsoc.gen.plafond_securite_sociale
contributions:
......@@ -105,13 +105,13 @@ La sous-section aura pour titre la description du noeud paramètre OpenFisca, et
### Paramètre `depth`
Il est possible de générer des sous-sections du sommaire de plus grande profondeur.
Il est possible de générer des sous-sections du sommaire de plus grande profondeur.
Si l'on modifie légèrement l'exemple précédent pour y ajouter un paramètre `depth` valant `1`:
```YAML
title: Prélèvements sociaux
children:
subparams:
plafond_securite_sociale:
table: cotsoc.gen.plafond_securite_sociale
contributions:
......@@ -126,5 +126,5 @@ On obtient la page suivante:
Le méchanisme est le même que celui décrit à la partie précédente, avec deux différences totables:
- Le sommaire descend "plus en profondeur" dans le noeud de paramètre
- Inversement, les tables sont cette fois générées à partir des "petits-enfants" du noeud, et sont donc moins complexes.
Il est possible de spécifier un `depth` > 1 pour un sommaire plus profondd. Par défault, `depth` vaut `0`.
Il est possible de spécifier un `depth` > 1 pour un sommaire plus profond. Par défault, `depth` vaut `0`.
const withCSS = require('@zeit/next-css')
const loader = require('./server/loader')
const keyBy = require('lodash.keyby')
const webpack = require('webpack')
const { parsed: localEnv } = require('dotenv').config()
module.exports = withCSS({
cssModules: false,
......@@ -11,6 +13,7 @@ module.exports = withCSS({
use: 'js-yaml-loader',
},
)
config.plugins.push(new webpack.EnvironmentPlugin(localEnv))
return config
},
exportPathMap: () => {
......@@ -21,5 +24,6 @@ module.exports = withCSS({
keyBy(routes, 'route')
)
})
}
},
assetPrefix: (localEnv && localEnv.BASENAME) || ""
})
import Document, { Head, Main, NextScript } from 'next/document'
const basename = process.env.BASENAME || ''
export default class MyDocument extends Document {
render() {
return (
<html>
<Head>
<link rel="stylesheet" href="https://www.ipp.eu/wp-content/themes/ipp/assets/css/global.min.css" />
<link rel="stylesheet" href="/_next/static/style.css" />
<link rel="stylesheet" href={`${basename}/_next/static/style.css`} />
</Head>
<body>
<Main />
......
......@@ -9,10 +9,19 @@ const Index = () => (
<ul>
<li><a href="./prelevements-sociaux">Prélèvements sociaux</a></li>
<li><a href="./impot-revenu">Impôt sur le revenu</a></li>
<li><a href="./taxation-capital">Taxation du capital</a></li>
<li><a href="./taxation-indirecte">Taxation indirecte</a></li>
</ul>
<h4>Transferts et prestations</h4>
<ul>
<li><a href="./prestations-sociales">Prestations sociales</a></li>
<li><a href="./chomage">Chômage</a></li>
<li><a href="./retraite">Retraite</a></li>
</ul>
<h4>Réglementations</h4>
<ul>
<li><a href="./marche-travail">Marché du travail</a></li>
<li><a href="./tarifs-energie">Tarifs réglementés de lénergie</a></li>
</ul>
</div>
</Layout>
......
import Layout from '../components/Layout'
import { withRouter } from 'next/router'
import map from 'lodash.map'
import isArray from 'lodash.isarray'
import includes from 'lodash.includes'
import flow from 'lodash.flow';
import sortBy from 'lodash.sortby';
function renderSubParams(item, key, path) {
const shouldSort = ! isArray(item.subparams) // A specific order has been explicitly defined in the conf
const subParams = flow([
x => map(x, (subParam, name) => Object.assign({}, subParam, {name})),
x => shouldSort ? sortBy(x, subParam => item?.metadata?.order.indexOf(subParam.name)) : x
])(item.subparams)
return map(subParams, subParam => {
if (item.exclude && includes(item.exclude, subParam.name)) {
return
}
return renderItem(subParam, subParam.name, `${path}${key}/`)
})
}
function renderItem(item, key, path) {
if (item.children) {
if (item.subparams && item.flat) {
return renderSubParams(item, key, path)
}
if (item.subparams) {
return <li key={key}>{item.title || item.description || item.id}
<ol>{map(item.children, (child, childKey) => renderItem(child, childKey, `${path}${key}/`))}</ol>
<ol>{renderSubParams(item, key, path)}</ol>
</li>
}
if (item.table) {
......@@ -16,12 +38,18 @@ function renderItem(item, key, path) {
const Section = (props) => {
const section = props.router.query
const path = props.router.asPath.endsWith('/') ? props.router.asPath : props.router.asPath + '/'
const basename = process.env.BASENAME || ''
const subParams = flow([
x => map(x, (subParam, name) => Object.assign({}, subParam, {name})),
x => sortBy(x, subParam => section?.metadata?.order.indexOf(subParam.name))
])(section.subparams)
return <Layout>
<h1 className="box"><span>{section.title}</span></h1>
<div className="entry-content text">
<h4>Sommaire</h4>
<ol>
{map(section.children, (child, key) => renderItem(child, key, path))}
{map(subParams, subParam => renderItem(subParam, subParam.name, `${basename}${path}`))}
</ol>
</div>
</Layout>
......
......@@ -29,13 +29,13 @@ function extractRoutes(desc, path) {
query: desc.table,
}]
}
if (desc.children) {
if (desc.subparams) {
const sectionRoute = {
route: path,
page: '/section',
query: desc
}
return [sectionRoute].concat(flatten(map(desc.children, (child, key) => {
return [sectionRoute].concat(flatten(map(desc.subparams, (child, key) => {
return extractRoutes(child, `${path}/${key}`)
})))
}
......
......@@ -3,6 +3,7 @@
const fetch = require('isomorphic-unfetch')
const isString = require('lodash.isstring')
const mapValues = require('lodash.mapvalues')
const isArray = require('lodash.isarray')
const Promise = require('bluebird')
async function resolveTable(tableDesc) {
......@@ -14,15 +15,15 @@ async function resolveTable(tableDesc) {
async function resolveCustomTable(tableDesc) {
const tableWithResolvedChildren = await Promise.props(mapValues(tableDesc, (value) => resolveTable(value)))
const children = mapValues(tableWithResolvedChildren, (child, description) => {
const subparams = mapValues(tableWithResolvedChildren, (child, description) => {
return Object.assign({}, child, {description})
})
return {children}
return {subparams}