app: Nettoyage du modèle de données
This commit is contained in:
parent
5ad7ee0b55
commit
268289f1f7
|
@ -21,6 +21,11 @@
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"react"
|
"react"
|
||||||
],
|
],
|
||||||
|
"settings": {
|
||||||
|
"react": {
|
||||||
|
"version": "detect"
|
||||||
|
}
|
||||||
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"indent": [
|
"indent": [
|
||||||
"error",
|
"error",
|
||||||
|
@ -37,6 +42,13 @@
|
||||||
"semi": [
|
"semi": [
|
||||||
"error",
|
"error",
|
||||||
"always"
|
"always"
|
||||||
|
],
|
||||||
|
"no-irregular-whitespace": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"skipStrings": true,
|
||||||
|
"skipTemplates": true
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -5,7 +5,7 @@ import {useAsync} from '../util.js';
|
||||||
import {
|
import {
|
||||||
diseasesBySymptoms,
|
diseasesBySymptoms,
|
||||||
exploreSymptoms
|
exploreSymptoms
|
||||||
} from '../data/fetch.js';
|
} from '../data/mock';
|
||||||
|
|
||||||
const App = () =>
|
const App = () =>
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import Graph from './Graph.js';
|
import Graph from './Graph.js';
|
||||||
import {types} from '../data/mock.js';
|
|
||||||
import {useAsync} from '../util.js';
|
import {useAsync} from '../util.js';
|
||||||
import {symptomsSubgraph} from '../data/fetch.js';
|
import {Term, termTypes} from '../data/model.js';
|
||||||
|
import {symptomsSubgraph} from '../data/mock';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Graphe de maladies et symptômes.
|
||||||
|
*
|
||||||
|
* @prop terms Ensemble de symptômes recherchés par l’utilisateur.
|
||||||
|
* @prop setTerms Fonction de rappel pour ajouter de nouveaux termes de
|
||||||
|
* recherche.
|
||||||
|
* @prop results Maladies correspondant à la recherche de l’utilisateur.
|
||||||
|
*/
|
||||||
const DiseaseGraph = ({terms, setTerms, results}) =>
|
const DiseaseGraph = ({terms, setTerms, results}) =>
|
||||||
{
|
{
|
||||||
const {nodes, edges} = useAsync({
|
const {nodes, edges} = useAsync({
|
||||||
|
@ -19,7 +28,7 @@ const DiseaseGraph = ({terms, setTerms, results}) =>
|
||||||
const render = id =>
|
const render = id =>
|
||||||
{
|
{
|
||||||
const isTerm = terms.some(({id: termId}) => termId === id);
|
const isTerm = terms.some(({id: termId}) => termId === id);
|
||||||
const isDisease = nodes[id].types.includes(types.disease);
|
const isDisease = nodes[id].types.includes(termTypes.disease);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={[
|
<span className={[
|
||||||
|
@ -69,4 +78,10 @@ const DiseaseGraph = ({terms, setTerms, results}) =>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
DiseaseGraph.propTypes = {
|
||||||
|
terms: PropTypes.arrayOf(Term).isRequired,
|
||||||
|
setTerms: PropTypes.func.isRequired,
|
||||||
|
results: PropTypes.arrayOf(Term).isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
export default DiseaseGraph;
|
export default DiseaseGraph;
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React, {useState, useRef, useEffect} from 'react';
|
import React, {useState, useRef, useEffect} from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import {TransitionGroup, CSSTransition} from 'react-transition-group';
|
import {TransitionGroup, CSSTransition} from 'react-transition-group';
|
||||||
import Springy from 'springy';
|
import Springy from 'springy';
|
||||||
|
import {Relation} from '../data/model.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Échappe une valeur utilisée dans un sélecteur d’attributs CSS.
|
* Échappe une valeur utilisée dans un sélecteur d’attributs CSS.
|
||||||
|
@ -343,4 +345,12 @@ const Graph = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Graph.propTypes = {
|
||||||
|
nodes: PropTypes.arrayOf(PropTypes.any).isRequired,
|
||||||
|
edges: PropTypes.arrayOf(Relation).isRequired,
|
||||||
|
emptyMessage: PropTypes.string.isRequired,
|
||||||
|
render: PropTypes.func.isRequired,
|
||||||
|
onNodeClick: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
export default Graph;
|
export default Graph;
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import React, {useState, useRef} from 'react';
|
import React, {useState, useRef} from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import {Term} from '../data/model.js';
|
||||||
import * as fuzzy from '../data/fuzzy.js';
|
import * as fuzzy from '../data/fuzzy.js';
|
||||||
import * as util from '../util.js';
|
import * as util from '../util.js';
|
||||||
|
|
||||||
|
@ -196,4 +198,10 @@ const TermInput = ({terms, availableTerms, setTerms}) =>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TermInput.propTypes = {
|
||||||
|
terms: PropTypes.arrayOf(Term).isRequired,
|
||||||
|
availableTerms: PropTypes.arrayOf(Term).isRequired,
|
||||||
|
setTerms: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
export default TermInput;
|
export default TermInput;
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
/**
|
||||||
|
* @module
|
||||||
|
* Fonctions permettant la recherche approximative de termes.
|
||||||
|
*/
|
||||||
|
|
||||||
import * as diacritics from 'diacritics';
|
import * as diacritics from 'diacritics';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,14 +3,7 @@
|
||||||
* Jeu de données d’exemple.
|
* Jeu de données d’exemple.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
import {termTypes} from '../model.js';
|
||||||
* Types de termes de la base.
|
|
||||||
*/
|
|
||||||
export const types = {
|
|
||||||
disease: 'Maladie',
|
|
||||||
symptom: 'Symptôme',
|
|
||||||
sign: 'Signe',
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Liste des termes de la base de données, contenant des maladies, des signes
|
* Liste des termes de la base de données, contenant des maladies, des signes
|
||||||
|
@ -21,7 +14,7 @@ export const terms = {
|
||||||
id: 'Q2840',
|
id: 'Q2840',
|
||||||
name: 'Grippe',
|
name: 'Grippe',
|
||||||
alias: [],
|
alias: [],
|
||||||
types: [types.disease],
|
types: [termTypes.disease],
|
||||||
weight: 0.000035,
|
weight: 0.000035,
|
||||||
},
|
},
|
||||||
Q154882: {
|
Q154882: {
|
||||||
|
@ -30,14 +23,14 @@ export const terms = {
|
||||||
alias: [
|
alias: [
|
||||||
'Maladie des légionnaires',
|
'Maladie des légionnaires',
|
||||||
],
|
],
|
||||||
types: [types.disease],
|
types: [termTypes.disease],
|
||||||
weight: 0.000015,
|
weight: 0.000015,
|
||||||
},
|
},
|
||||||
Q155098: {
|
Q155098: {
|
||||||
id: 'Q155098',
|
id: 'Q155098',
|
||||||
name: 'Leptospirose',
|
name: 'Leptospirose',
|
||||||
alias: [],
|
alias: [],
|
||||||
types: [types.disease],
|
types: [termTypes.disease],
|
||||||
weight: 0.00001,
|
weight: 0.00001,
|
||||||
},
|
},
|
||||||
Q326663: {
|
Q326663: {
|
||||||
|
@ -53,14 +46,14 @@ export const terms = {
|
||||||
'Maladie de Schneider',
|
'Maladie de Schneider',
|
||||||
'Maladie de Kumlinge',
|
'Maladie de Kumlinge',
|
||||||
],
|
],
|
||||||
types: [types.disease],
|
types: [termTypes.disease],
|
||||||
weight: 0.000001,
|
weight: 0.000001,
|
||||||
},
|
},
|
||||||
Q133780: {
|
Q133780: {
|
||||||
id: 'Q133780',
|
id: 'Q133780',
|
||||||
name: 'Peste',
|
name: 'Peste',
|
||||||
alias: [],
|
alias: [],
|
||||||
types: [types.disease],
|
types: [termTypes.disease],
|
||||||
weight: 0.000032,
|
weight: 0.000032,
|
||||||
},
|
},
|
||||||
Q38933: {
|
Q38933: {
|
||||||
|
@ -69,7 +62,8 @@ export const terms = {
|
||||||
alias: [
|
alias: [
|
||||||
'Pyrexie',
|
'Pyrexie',
|
||||||
],
|
],
|
||||||
types: [types.symptom],
|
types: [termTypes.symptom],
|
||||||
|
weight: 0,
|
||||||
},
|
},
|
||||||
Q474959: {
|
Q474959: {
|
||||||
id: 'Q474959',
|
id: 'Q474959',
|
||||||
|
@ -77,7 +71,8 @@ export const terms = {
|
||||||
alias: [
|
alias: [
|
||||||
'Douleur musculaire',
|
'Douleur musculaire',
|
||||||
],
|
],
|
||||||
types: [types.symptom],
|
types: [termTypes.symptom],
|
||||||
|
weight: 0,
|
||||||
},
|
},
|
||||||
Q86: {
|
Q86: {
|
||||||
id: 'Q86',
|
id: 'Q86',
|
||||||
|
@ -85,7 +80,8 @@ export const terms = {
|
||||||
alias: [
|
alias: [
|
||||||
'Mal de tête',
|
'Mal de tête',
|
||||||
],
|
],
|
||||||
types: [types.sign],
|
types: [termTypes.sign],
|
||||||
|
weight: 0,
|
||||||
},
|
},
|
||||||
Q1115038: {
|
Q1115038: {
|
||||||
id: 'Q1115038',
|
id: 'Q1115038',
|
||||||
|
@ -94,13 +90,15 @@ export const terms = {
|
||||||
'Nez qui coule',
|
'Nez qui coule',
|
||||||
'Écoulement nasal',
|
'Écoulement nasal',
|
||||||
],
|
],
|
||||||
types: [types.symptom],
|
types: [termTypes.symptom],
|
||||||
|
weight: 0,
|
||||||
},
|
},
|
||||||
Q9690: {
|
Q9690: {
|
||||||
id: 'Q9690',
|
id: 'Q9690',
|
||||||
name: 'Fatigue',
|
name: 'Fatigue',
|
||||||
alias: [],
|
alias: [],
|
||||||
types: [types.symptom],
|
types: [termTypes.symptom],
|
||||||
|
weight: 0,
|
||||||
},
|
},
|
||||||
Q127076: {
|
Q127076: {
|
||||||
id: 'Q127076',
|
id: 'Q127076',
|
||||||
|
@ -110,7 +108,8 @@ export const terms = {
|
||||||
'Vomissage',
|
'Vomissage',
|
||||||
'Vomir',
|
'Vomir',
|
||||||
],
|
],
|
||||||
types: [types.symptom, types.sign],
|
types: [termTypes.symptom, termTypes.sign],
|
||||||
|
weight: 0,
|
||||||
},
|
},
|
||||||
Q178061: {
|
Q178061: {
|
||||||
id: 'Q178061',
|
id: 'Q178061',
|
||||||
|
@ -118,7 +117,7 @@ export const terms = {
|
||||||
alias: [
|
alias: [
|
||||||
'Insuffisance circulatoire aiguë',
|
'Insuffisance circulatoire aiguë',
|
||||||
],
|
],
|
||||||
types: [types.disease],
|
types: [termTypes.disease],
|
||||||
weight: 0.000038,
|
weight: 0.000038,
|
||||||
},
|
},
|
||||||
Q35805: {
|
Q35805: {
|
||||||
|
@ -128,7 +127,8 @@ export const terms = {
|
||||||
'Tousse',
|
'Tousse',
|
||||||
'Toussote',
|
'Toussote',
|
||||||
],
|
],
|
||||||
types: [types.symptom, types.sign],
|
types: [termTypes.symptom, termTypes.sign],
|
||||||
|
weight: 0,
|
||||||
},
|
},
|
||||||
Q647099: {
|
Q647099: {
|
||||||
id: 'Q647099',
|
id: 'Q647099',
|
||||||
|
@ -136,13 +136,15 @@ export const terms = {
|
||||||
alias: [
|
alias: [
|
||||||
'Expectoration sanglante',
|
'Expectoration sanglante',
|
||||||
],
|
],
|
||||||
types: [types.symptom],
|
types: [termTypes.symptom],
|
||||||
|
weight: 0,
|
||||||
},
|
},
|
||||||
Q653197: {
|
Q653197: {
|
||||||
id: 'Q653197',
|
id: 'Q653197',
|
||||||
name: 'Rash',
|
name: 'Rash',
|
||||||
alias: [],
|
alias: [],
|
||||||
types: [types.symptom, types.sign],
|
types: [termTypes.symptom, termTypes.sign],
|
||||||
|
weight: 0,
|
||||||
},
|
},
|
||||||
Q160796: {
|
Q160796: {
|
||||||
id: 'Q160796',
|
id: 'Q160796',
|
||||||
|
@ -150,28 +152,29 @@ export const terms = {
|
||||||
alias: [
|
alias: [
|
||||||
'Confusion mentale',
|
'Confusion mentale',
|
||||||
],
|
],
|
||||||
types: [types.disease],
|
types: [termTypes.disease],
|
||||||
weight: 0.000004,
|
weight: 0.000004,
|
||||||
},
|
},
|
||||||
Q186235: {
|
Q186235: {
|
||||||
id: 'Q186235',
|
id: 'Q186235',
|
||||||
name: 'Myocardite',
|
name: 'Myocardite',
|
||||||
alias: [],
|
alias: [],
|
||||||
types: [types.disease],
|
types: [termTypes.disease],
|
||||||
weight: 0.0000075,
|
weight: 0.0000075,
|
||||||
},
|
},
|
||||||
Q476921: {
|
Q476921: {
|
||||||
id: 'Q476921',
|
id: 'Q476921',
|
||||||
name: 'Insuffisance rénale',
|
name: 'Insuffisance rénale',
|
||||||
alias: [],
|
alias: [],
|
||||||
types: [types.disease],
|
types: [termTypes.disease],
|
||||||
weight: 0.0000046,
|
weight: 0.0000046,
|
||||||
},
|
},
|
||||||
Q281289: {
|
Q281289: {
|
||||||
id: 'Q281289',
|
id: 'Q281289',
|
||||||
name: 'Photophobie',
|
name: 'Photophobie',
|
||||||
alias: [],
|
alias: [],
|
||||||
types: [types.sign],
|
types: [termTypes.sign],
|
||||||
|
weight: 0,
|
||||||
},
|
},
|
||||||
Q159557: {
|
Q159557: {
|
||||||
id: 'Q159557',
|
id: 'Q159557',
|
||||||
|
@ -181,7 +184,8 @@ export const terms = {
|
||||||
'Coma végétatif',
|
'Coma végétatif',
|
||||||
'Perte de connaissance',
|
'Perte de connaissance',
|
||||||
],
|
],
|
||||||
types: [types.sign],
|
types: [termTypes.sign],
|
||||||
|
weight: 0,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
import * as mock from './mock.js';
|
/**
|
||||||
|
* @module
|
||||||
|
* Fonctions de requêtage des données d’exemple.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {termTypes} from '../model.js';
|
||||||
|
import * as data from './data.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recherche l’ensemble des maladies liées par une relation « a pour symptôme »
|
* Recherche l’ensemble des maladies liées par une relation « a pour symptôme »
|
||||||
|
@ -14,7 +20,7 @@ export const diseasesBySymptoms = async query =>
|
||||||
if (!query.length)
|
if (!query.length)
|
||||||
{
|
{
|
||||||
// Si aucun terme dans la requête, tout correspond
|
// Si aucun terme dans la requête, tout correspond
|
||||||
allMatches = Object.values(mock.terms).map(({id}) => id);
|
allMatches = Object.values(data.terms).map(({id}) => id);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -32,7 +38,7 @@ export const diseasesBySymptoms = async query =>
|
||||||
while (stack.length)
|
while (stack.length)
|
||||||
{
|
{
|
||||||
const current = stack.pop();
|
const current = stack.pop();
|
||||||
const neighbors = mock.symptomOf.filter(
|
const neighbors = data.symptomOf.filter(
|
||||||
([from]) => from === current
|
([from]) => from === current
|
||||||
).map(
|
).map(
|
||||||
([, to]) => to
|
([, to]) => to
|
||||||
|
@ -65,9 +71,9 @@ export const diseasesBySymptoms = async query =>
|
||||||
|
|
||||||
// On ne garde que les maladies
|
// On ne garde que les maladies
|
||||||
return allMatches.map(
|
return allMatches.map(
|
||||||
id => mock.terms[id]
|
id => data.terms[id]
|
||||||
).filter(
|
).filter(
|
||||||
term => term.types.includes(mock.types.disease)
|
term => term.types.includes(termTypes.disease)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -91,7 +97,7 @@ export const exploreSymptoms = async terms =>
|
||||||
while (stack.length)
|
while (stack.length)
|
||||||
{
|
{
|
||||||
const current = stack.pop();
|
const current = stack.pop();
|
||||||
const neighbors = mock.hasSymptom.filter(
|
const neighbors = data.hasSymptom.filter(
|
||||||
([from]) => from === current
|
([from]) => from === current
|
||||||
).map(
|
).map(
|
||||||
([, to]) => to
|
([, to]) => to
|
||||||
|
@ -107,7 +113,7 @@ export const exploreSymptoms = async terms =>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return selected.map(id => mock.terms[id]);
|
return selected.map(id => data.terms[id]);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -131,7 +137,7 @@ export const symptomsSubgraph = async terms =>
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sélection des arêtes liant les nœuds sélectionnés
|
// Sélection des arêtes liant les nœuds sélectionnés
|
||||||
for (let [from, to] of mock.hasSymptom)
|
for (let [from, to] of data.hasSymptom)
|
||||||
{
|
{
|
||||||
if (
|
if (
|
||||||
termsIds.includes(from)
|
termsIds.includes(from)
|
|
@ -0,0 +1,72 @@
|
||||||
|
/**
|
||||||
|
* @module
|
||||||
|
* Définit le modèle de données utilisé par l’application.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types de termes existants.
|
||||||
|
*/
|
||||||
|
export const termTypes = {
|
||||||
|
disease: 'Maladie',
|
||||||
|
symptom: 'Symptôme',
|
||||||
|
sign: 'Signe',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type de terme.
|
||||||
|
*/
|
||||||
|
export const TermType = PropTypes.oneOf(Object.values(termTypes));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terme.
|
||||||
|
*
|
||||||
|
* Peut être une maladie, un symptôme ou un signe.
|
||||||
|
*/
|
||||||
|
export const Term = PropTypes.exact({
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
alias: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||||
|
types: PropTypes.arrayOf(TermType).isRequired,
|
||||||
|
weight: PropTypes.number.isRequired,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relation entre deux termes.
|
||||||
|
*/
|
||||||
|
export const Relation = (props, propName, componentName) =>
|
||||||
|
{
|
||||||
|
if (!(propName in props))
|
||||||
|
{
|
||||||
|
return new Error(
|
||||||
|
`Missing ${propName} in props supplied to ${componentName}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = props[propName];
|
||||||
|
|
||||||
|
if (!Array.isArray(value))
|
||||||
|
{
|
||||||
|
return new Error(
|
||||||
|
`Relation ${propName} must be an array.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.length !== 2)
|
||||||
|
{
|
||||||
|
return new Error(
|
||||||
|
`Relation ${propName} must contain exactly two entries.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof value[0] !== 'string'
|
||||||
|
|| typeof value[1] !== 'string'
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return new Error(
|
||||||
|
`Entries of relation ${propName} must be string IDs.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
|
@ -28,7 +28,7 @@ export const useAsync = (initial, func, ...args) =>
|
||||||
/**
|
/**
|
||||||
* Crée un état composé d’une liste et d’un élément ayant le focus dans cette
|
* Crée un état composé d’une liste et d’un élément ayant le focus dans cette
|
||||||
* liste. À la modification de la liste ou de l’indice de l’élément ayant le
|
* liste. À la modification de la liste ou de l’indice de l’élément ayant le
|
||||||
* focus, la contrainte suivante est imposée :
|
* focus, la contrainte suivante est imposée :
|
||||||
*
|
*
|
||||||
* — si la liste n’est pas vide, focus ∈ [0, taille de la liste].
|
* — si la liste n’est pas vide, focus ∈ [0, taille de la liste].
|
||||||
* — sinon, focus = 0.
|
* — sinon, focus = 0.
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
"diacritics": "^1.3.0",
|
"diacritics": "^1.3.0",
|
||||||
"eslint": "^6.7.2",
|
"eslint": "^6.7.2",
|
||||||
"eslint-plugin-react": "^7.17.0",
|
"eslint-plugin-react": "^7.17.0",
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
"react": "^16.12.0",
|
"react": "^16.12.0",
|
||||||
"react-dom": "^16.12.0",
|
"react-dom": "^16.12.0",
|
||||||
"react-transition-group": "^4.3.0",
|
"react-transition-group": "^4.3.0",
|
||||||
|
|
Loading…
Reference in New Issue