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