app: Nettoyage du modèle de données
This commit is contained in:
parent
5ad7ee0b55
commit
268289f1f7
|
@ -21,6 +21,11 @@
|
|||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"indent": [
|
||||
"error",
|
||||
|
@ -37,6 +42,13 @@
|
|||
"semi": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"no-irregular-whitespace": [
|
||||
"error",
|
||||
{
|
||||
"skipStrings": true,
|
||||
"skipTemplates": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ import {useAsync} from '../util.js';
|
|||
import {
|
||||
diseasesBySymptoms,
|
||||
exploreSymptoms
|
||||
} from '../data/fetch.js';
|
||||
} from '../data/mock';
|
||||
|
||||
const App = () =>
|
||||
{
|
||||
|
|
|
@ -1,9 +1,18 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Graph from './Graph.js';
|
||||
import {types} from '../data/mock.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 {nodes, edges} = useAsync({
|
||||
|
@ -19,7 +28,7 @@ const DiseaseGraph = ({terms, setTerms, results}) =>
|
|||
const render = 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 (
|
||||
<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;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import React, {useState, useRef, useEffect} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {TransitionGroup, CSSTransition} from 'react-transition-group';
|
||||
import Springy from 'springy';
|
||||
import {Relation} from '../data/model.js';
|
||||
|
||||
/**
|
||||
* É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;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
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 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;
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* @module
|
||||
* Fonctions permettant la recherche approximative de termes.
|
||||
*/
|
||||
|
||||
import * as diacritics from 'diacritics';
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,14 +3,7 @@
|
|||
* Jeu de données d’exemple.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Types de termes de la base.
|
||||
*/
|
||||
export const types = {
|
||||
disease: 'Maladie',
|
||||
symptom: 'Symptôme',
|
||||
sign: 'Signe',
|
||||
};
|
||||
import {termTypes} from '../model.js';
|
||||
|
||||
/**
|
||||
* Liste des termes de la base de données, contenant des maladies, des signes
|
||||
|
@ -21,7 +14,7 @@ export const terms = {
|
|||
id: 'Q2840',
|
||||
name: 'Grippe',
|
||||
alias: [],
|
||||
types: [types.disease],
|
||||
types: [termTypes.disease],
|
||||
weight: 0.000035,
|
||||
},
|
||||
Q154882: {
|
||||
|
@ -30,14 +23,14 @@ export const terms = {
|
|||
alias: [
|
||||
'Maladie des légionnaires',
|
||||
],
|
||||
types: [types.disease],
|
||||
types: [termTypes.disease],
|
||||
weight: 0.000015,
|
||||
},
|
||||
Q155098: {
|
||||
id: 'Q155098',
|
||||
name: 'Leptospirose',
|
||||
alias: [],
|
||||
types: [types.disease],
|
||||
types: [termTypes.disease],
|
||||
weight: 0.00001,
|
||||
},
|
||||
Q326663: {
|
||||
|
@ -53,14 +46,14 @@ export const terms = {
|
|||
'Maladie de Schneider',
|
||||
'Maladie de Kumlinge',
|
||||
],
|
||||
types: [types.disease],
|
||||
types: [termTypes.disease],
|
||||
weight: 0.000001,
|
||||
},
|
||||
Q133780: {
|
||||
id: 'Q133780',
|
||||
name: 'Peste',
|
||||
alias: [],
|
||||
types: [types.disease],
|
||||
types: [termTypes.disease],
|
||||
weight: 0.000032,
|
||||
},
|
||||
Q38933: {
|
||||
|
@ -69,7 +62,8 @@ export const terms = {
|
|||
alias: [
|
||||
'Pyrexie',
|
||||
],
|
||||
types: [types.symptom],
|
||||
types: [termTypes.symptom],
|
||||
weight: 0,
|
||||
},
|
||||
Q474959: {
|
||||
id: 'Q474959',
|
||||
|
@ -77,7 +71,8 @@ export const terms = {
|
|||
alias: [
|
||||
'Douleur musculaire',
|
||||
],
|
||||
types: [types.symptom],
|
||||
types: [termTypes.symptom],
|
||||
weight: 0,
|
||||
},
|
||||
Q86: {
|
||||
id: 'Q86',
|
||||
|
@ -85,7 +80,8 @@ export const terms = {
|
|||
alias: [
|
||||
'Mal de tête',
|
||||
],
|
||||
types: [types.sign],
|
||||
types: [termTypes.sign],
|
||||
weight: 0,
|
||||
},
|
||||
Q1115038: {
|
||||
id: 'Q1115038',
|
||||
|
@ -94,13 +90,15 @@ export const terms = {
|
|||
'Nez qui coule',
|
||||
'Écoulement nasal',
|
||||
],
|
||||
types: [types.symptom],
|
||||
types: [termTypes.symptom],
|
||||
weight: 0,
|
||||
},
|
||||
Q9690: {
|
||||
id: 'Q9690',
|
||||
name: 'Fatigue',
|
||||
alias: [],
|
||||
types: [types.symptom],
|
||||
types: [termTypes.symptom],
|
||||
weight: 0,
|
||||
},
|
||||
Q127076: {
|
||||
id: 'Q127076',
|
||||
|
@ -110,7 +108,8 @@ export const terms = {
|
|||
'Vomissage',
|
||||
'Vomir',
|
||||
],
|
||||
types: [types.symptom, types.sign],
|
||||
types: [termTypes.symptom, termTypes.sign],
|
||||
weight: 0,
|
||||
},
|
||||
Q178061: {
|
||||
id: 'Q178061',
|
||||
|
@ -118,7 +117,7 @@ export const terms = {
|
|||
alias: [
|
||||
'Insuffisance circulatoire aiguë',
|
||||
],
|
||||
types: [types.disease],
|
||||
types: [termTypes.disease],
|
||||
weight: 0.000038,
|
||||
},
|
||||
Q35805: {
|
||||
|
@ -128,7 +127,8 @@ export const terms = {
|
|||
'Tousse',
|
||||
'Toussote',
|
||||
],
|
||||
types: [types.symptom, types.sign],
|
||||
types: [termTypes.symptom, termTypes.sign],
|
||||
weight: 0,
|
||||
},
|
||||
Q647099: {
|
||||
id: 'Q647099',
|
||||
|
@ -136,13 +136,15 @@ export const terms = {
|
|||
alias: [
|
||||
'Expectoration sanglante',
|
||||
],
|
||||
types: [types.symptom],
|
||||
types: [termTypes.symptom],
|
||||
weight: 0,
|
||||
},
|
||||
Q653197: {
|
||||
id: 'Q653197',
|
||||
name: 'Rash',
|
||||
alias: [],
|
||||
types: [types.symptom, types.sign],
|
||||
types: [termTypes.symptom, termTypes.sign],
|
||||
weight: 0,
|
||||
},
|
||||
Q160796: {
|
||||
id: 'Q160796',
|
||||
|
@ -150,28 +152,29 @@ export const terms = {
|
|||
alias: [
|
||||
'Confusion mentale',
|
||||
],
|
||||
types: [types.disease],
|
||||
types: [termTypes.disease],
|
||||
weight: 0.000004,
|
||||
},
|
||||
Q186235: {
|
||||
id: 'Q186235',
|
||||
name: 'Myocardite',
|
||||
alias: [],
|
||||
types: [types.disease],
|
||||
types: [termTypes.disease],
|
||||
weight: 0.0000075,
|
||||
},
|
||||
Q476921: {
|
||||
id: 'Q476921',
|
||||
name: 'Insuffisance rénale',
|
||||
alias: [],
|
||||
types: [types.disease],
|
||||
types: [termTypes.disease],
|
||||
weight: 0.0000046,
|
||||
},
|
||||
Q281289: {
|
||||
id: 'Q281289',
|
||||
name: 'Photophobie',
|
||||
alias: [],
|
||||
types: [types.sign],
|
||||
types: [termTypes.sign],
|
||||
weight: 0,
|
||||
},
|
||||
Q159557: {
|
||||
id: 'Q159557',
|
||||
|
@ -181,7 +184,8 @@ export const terms = {
|
|||
'Coma végétatif',
|
||||
'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 »
|
||||
|
@ -14,7 +20,7 @@ export const diseasesBySymptoms = async query =>
|
|||
if (!query.length)
|
||||
{
|
||||
// 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
|
||||
{
|
||||
|
@ -32,7 +38,7 @@ export const diseasesBySymptoms = async query =>
|
|||
while (stack.length)
|
||||
{
|
||||
const current = stack.pop();
|
||||
const neighbors = mock.symptomOf.filter(
|
||||
const neighbors = data.symptomOf.filter(
|
||||
([from]) => from === current
|
||||
).map(
|
||||
([, to]) => to
|
||||
|
@ -65,9 +71,9 @@ export const diseasesBySymptoms = async query =>
|
|||
|
||||
// On ne garde que les maladies
|
||||
return allMatches.map(
|
||||
id => mock.terms[id]
|
||||
id => data.terms[id]
|
||||
).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)
|
||||
{
|
||||
const current = stack.pop();
|
||||
const neighbors = mock.hasSymptom.filter(
|
||||
const neighbors = data.hasSymptom.filter(
|
||||
([from]) => from === current
|
||||
).map(
|
||||
([, 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
|
||||
for (let [from, to] of mock.hasSymptom)
|
||||
for (let [from, to] of data.hasSymptom)
|
||||
{
|
||||
if (
|
||||
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
|
||||
* 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].
|
||||
* — sinon, focus = 0.
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
"diacritics": "^1.3.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-react": "^7.17.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-transition-group": "^4.3.0",
|
||||
|
|
Loading…
Reference in New Issue