app: Nettoyage du modèle de données

This commit is contained in:
Mattéo Delabre 2019-12-04 18:54:44 -05:00
parent 5ad7ee0b55
commit 268289f1f7
Signed by: matteo
GPG Key ID: AE3FBD02DC583ABB
11 changed files with 175 additions and 42 deletions

View File

@ -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
}
]
}
}
}

View File

@ -5,7 +5,7 @@ import {useAsync} from '../util.js';
import {
diseasesBySymptoms,
exploreSymptoms
} from '../data/fetch.js';
} from '../data/mock';
const App = () =>
{

View File

@ -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 lutilisateur.
* @prop setTerms Fonction de rappel pour ajouter de nouveaux termes de
* recherche.
* @prop results Maladies correspondant à la recherche de lutilisateur.
*/
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;

View File

@ -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 dattributs 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;

View File

@ -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;

View File

@ -1,3 +1,8 @@
/**
* @module
* Fonctions permettant la recherche approximative de termes.
*/
import * as diacritics from 'diacritics';
/**

View File

@ -3,14 +3,7 @@
* Jeu de données dexemple.
*/
/**
* 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,
},
};

View File

@ -1,4 +1,10 @@
import * as mock from './mock.js';
/**
* @module
* Fonctions de requêtage des données dexemple.
*/
import {termTypes} from '../model.js';
import * as data from './data.js';
/**
* Recherche lensemble 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)

72
app/src/data/model.js Normal file
View File

@ -0,0 +1,72 @@
/**
* @module
* Définit le modèle de données utilisé par lapplication.
*/
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.`
);
}
};

View File

@ -28,7 +28,7 @@ export const useAsync = (initial, func, ...args) =>
/**
* Crée un état composé dune liste et dun élément ayant le focus dans cette
* liste. À la modification de la liste ou de lindice de lélément ayant le
* focus, la contrainte suivante est imposée:
* focus, la contrainte suivante est imposée :
*
* si la liste nest pas vide, focus [0, taille de la liste].
* sinon, focus = 0.

View File

@ -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",