From 833fa9328f5c05c4a115c6379a6c8e7a00e8b231 Mon Sep 17 00:00:00 2001 From: YaphetKG <45075777+YaphetKG@users.noreply.github.com> Date: Fri, 4 Mar 2022 10:02:53 -0500 Subject: [PATCH] Release 0.4 (#21) * Merging Griffin's changes (#118) * Adds support for concept identifiers in auto completion. Fixes small messup with setError message dialog in autocompletion * Adds query string parameter q/query to specify a TranQL query. Adds URL with query param to import/export interface for sharing. Fixes a couple css problems. * Adds barebones interactive shell implementation. Overhauls find tool in favor of a Elasticlunr prototype * Heavily extends on the functionality of the interactive shell. Adds intermediary prototype to shell for two-way communication. Adds basic functionality to intermediary, such as importing external modules and reading promises synchronously. * Adds rudimentary multiline capability in shell thorugh the usage of \n. Adds some functionality to the shell intermediary. * Adds an editor mode to the interactive shell for use with more complex programs * Adds program tabs to script editor * Minor changes to shell * Adds resizing to overflowing tabs in script editor. Adds python syntax highlighting to script editor * Basic (semifunctional) elasticlunr find tool searching * Adds ability to use custom functions in where clause + unit test * Ensures proper parsing of functions and lists + extends relevant units tests * Adds named/keyword arguments to functions + adds corresponding unit test * UDFs added .Refactor CustomFuntion (now CustomFunctions) * Refactoring TranQL pyodide into module * UDF format modified to be more expressive. Major refactor of App modals. A couple other things I'm forgetting * Added promise utility to TranQLApp. Added graph operations to KnowledgeGraph. Pyodide loading and stuff does not occur until the shell is opened * Quick fix to TranQLApp->render * Adds variable support to lists. Fixes bug in expand_nodes where if a concept had multiple nodes and its first node was a variable, it would only generate questions for the variable * Adds ability to use vars as function arguments * Fixes commit to wrong branch * Adds promise wrapper to pyodide_module * adds (semi-functoinal) manifest for pip and reverts change to tranql_ast expand_nodes for now * Removes interactive shell/pyodide * Changes find tool to expand tokens for better results * Lots of changes to find tool * adds highlighting to find tool * Remove context menu & add hover styles to find tool * Adds github pages? * test pages * Adds more to pages * Hopefully fixes pages theme * Adds things to GitHub pages * Adds example notebook directly into pages * Adds UDFs info/comments & more functions * Adds example docs to GitHub Pages. Adds __init__.py to tranql which never got committed * Adds function docs to GitHub Pages * Fixes error modal typo * Adds history viewer prototype. Might be better to just sort cache viewer by timestamp. * Adds ONTO sibling function * Adds comment for failing unit test documenting a known bug which the test aims to prevent * Changes expand_nodes to fix bug/unit test (#122). * Set theme jekyll-theme-time-machine * Adds documentation on how to add a function in udfs.py * Set theme jekyll-theme-cayman * Adds documentation on how to add a function in udfs.py. Changes jekyll theme in config back to just-the-docs * Fixes some issues with the find tool. Comments out the history viewer. Co-authored-by: frostyfan109 Co-authored-by: frostyfan109 Co-authored-by: frostyfan109 <31631417+frostyfan109@users.noreply.github.com> * when using schema the expand nodes function fails to set starting nodes, and hence causing all graph grabbing, this was overlooked in initial merge. * Udfs functions for parents and sibilings looking up wrong keys fix * Error modal not closing properly fix * docker image version bump for new dev image * Update for planner, to fail iff no results can be found after trying all capable KPs. * docker version bump * Varnish docker image to slim debian version * Trapi updates (#122) * Backplane overhaul to enable centrizied of configuration and conversion between trapi versions * Backplane overhaul to enable centrizied of configuration and conversion between trapi versions * Addition of roger, update tranql frontend to talk to backplane always to ensure trapi configs are universal. Updating tests to those changes * docker image version bump * gnbr and indigo * Tests were that were using local server to use mock servers * icees update to latest bl lookup changes * update tests to match biolink:PascalConceptType in tests * Trapi 1.0 (#126) * Trapi 1.0 overhaul * Trapi 1.0 schema update. * update unit test files to Trapi 1.0 * Dependency corrections * Web prefix (#129) * adding web_prefix conf for web api * adding web_prefix conf for web api * minor version added * Convert renciorg refs to helxplatform refs. * Web prefix (#133) * adding web_prefix conf for web api * adding web_prefix conf for web api * minor version added * bumping plater version * Add UI conversion from TRAPI 1.0 to TRAPI 0.9. * Web prefix for UI (#139) * ui web prefix jinja way. and adds reasoner to redis results. * changes window.origin to window.href as root location of tranql to play well with prefixed proxy deployments. * Parameterizes docker file to support building a branch for CI/CD. Allows npm to build index file as a jinja template to web_prefix for static assets is allowed when serving from flask. (This has no effect on local development) * Update Dockerfile * Parameterizes docker file to support building a branch for CI/CD. (#145) - tranql app image should base off of tranql-base image that is build for a specific branch. In CI these two images are built in seq, and tranql-base for develop will checkout develop , - by default tranql-base is built for master and tranql-app will build off of tranql-base:latest. * Configure message not being called filters out important nodes. (#147) * skip and limit additions for cypher queries * plater lib version bump * test for skip and limit options on redis * fixes test , upgrades plater * strips / from main url (#154) * Adding db to schema so it shows up on UI auto complete * Refactoring merge endpoint. Using config for faster parsing instead of a new tranql object * Modify schema factory sending tranql config so its able to resolve redis password * Parsing tweaks for 1. Suggesting concepts only base on context, i.e considering start and end nodes , 2. sometimes numeric values cause errors , so tree.toString ensures that doesn't happen * merge for trapi 1.0 * test overhaul for merge and schema supporting redis * Score as summation of publication counts on each edge * score test disable * Support for max connections * fixing api test for score * Adding Makefile, Jenkinsfile, consolidated Docker, tranql src dir * Makefile and Jenkinsfile * Starting to organize & cleanup tests * Makefile * Test cleanup * Bumping version * Makefile * Testing jenkins file * Temp changing jenkins control flow * Update Dockerfile * Structural changes for simplified deployment * Jenkins and Makefile * using first message query graph fixed, now questions are also merged. * root question as query graph for merged answers when using schema on from clause . Redis timeout placed * schema to updated dynamically after redis is populated, and some tests * test for empty schema tweak * import at top * plater version bump * when grabbing trapi option, if tranql query parser does not set it, set to empty dict * Fixing merge conflicts * bump plater version * requirements to fix tests * Removing travis file * - Update requirements.txt , just the required libs - Dockerfile , need to override some conf on the chart so doing install makes dir location hard. for instance we mount tranql/conf/schema.yaml with redis server info, locating that in site-packages is hard * upgrade bmt concept model, and tests to remove chemical substance * upgrade bmt concept model, and tests to remove chemical substance (#6) Co-authored-by: Yaphetkg * Updates schema.yaml to be redis dependent. Adds make commands for local development * Make schema.yaml path configurable via env var * Feature/local development (#7) * upgrade bmt concept model, and tests to remove chemical substance * Updates schema.yaml to be redis dependent. Adds make commands for local development * Make schema.yaml path configurable via env var Co-authored-by: Yaphetkg * Updates number of major web dependencies. Notes a bug in App.js Upgrades React, react-force-graph, react-bootstrap, react-codemirror2, classnames, & qs. * Updates numerous web dependencies Upgrades react-filepond/filepond, react-icons, react-split-pane, recharts, & json5 * Removes a few unused dependencies * Adds boilerplate jest testing and sample test Fills/mocks unsupported features required by the TranQL app in jsdom. Adds a basic test that should never fail as an example. * Replaces domjs with Puppeteer testing Replaces domjs jest testing in feature/dependency-updates with end-to-end testing using Puppeteer driver. Adds two temporary tests for prototyping * Small change to App in order ot make it easier to resolve API url outside of React (i.e. in unit tests * Downgrades react-force-graph to fix dependency conflict Resolves bezier-js dependency conflict between CRA and react-force-graph. See issue #282 in react-force-graph. * Undo react-force-graph downgrade beacuse it only causes more errors * Adds schema testing, request mocking, test setup/env * Adds graph testing * Polishes aspects of schema/graph unit tests and tidies things up * Fixes non-mocked tests. Verifies that non-mocked tests work correctly. * Adds web tests to makefile * Updates API/backplane to use Flask-RESTX * Updates graph unit test to throughly check node/edge existence in Three graph * Adds legend unit test * Adds simple autocompletion test and slightly restructures tests to use a globally-specified website URL * Adds more comprehensive request mocking utility and fixes autocomplete test * Restructures autocomplete unit test so that it can test multiple types of completions. Adds an autocomplete tests for context-aware select prediction. Polishes mocking system * Fixes a bug that made it so that mocking didn't work properly in autocompletion test * Slight syntactic change to testUtil * Remove test block that was accidentally left in * Modularizes and refactors various parts of App Moves lengthy autocompletion logic into its own file. Moves main toolbar initialization data into AppToolbar under Toolbar.js. Moves settings modal into SettingsModal.js. Moves example queries used by help modal into static file. * Moves bulky table viewer initialization into AppTableViewer under TableViewer.js * Fixes error in Makefile test.npm * Makes sure Jenkins installs package.json before running tests * setupTests.js will load environment variables when cnofiguring test vars. Jenkins sets web testing env variables. make test.npm runs tests a single time (without watching). * Fixes environment directive under test stage * Fixes Jenkinsfile environment directive? * Wrap after all in try block to see if it fixes Jenkins build * Removes web build from web test installation * Stops jenkins from building production build. Hopefully fixes puppeteer running in Jenkins. * Hopefully fixes Jenkins puppeteer browser launch * Jenkins list chrome dpeendnecies * Another change to see if browser is being properly initialized * Hopefully locates chromium executable on Jenkins * Adds find_chromium to makefile * Adds test_puppeteer to get error information on browser installation in Jenkins * fixes typo in test_puppeteer * Fix bug in test_puppeteer * Should fix Jenkins Puppeteer by installing chrome dependencies * Should fix Puppeteer problem in Jenkins * Fixes improper quotes/apostrophes in Jenkinsfile * Adds test_puppeteer back for additional debugging * Adds env variable/option to disable chromium sandboxing * Tests will launch webserver prior to launching Puppeteer * Fixes merge conflict? * Jenkinks should now launch webserver in background * Changes wget to curl in Jenkinsfile. Adds brief comment of what it's doing. * Remove null output of curl * Adds wget back * Kill webserver after testing is complete * Changes lsof to npx kill-port since lsof is not installed on the build * Fix bug that stopped app from being served on root path * Adds embeddable functionality with qs arg "embed" and adds try catch around getReasonerURLs Query string arg ?embed=true or just ?embed will now has special functionality for embeddeding. This changes how the app renders, as well as specific behaviors such as disabling local storage/Dexie caching. This also adds a try catch block around the getReasonerURLs method which would previously throw an error if the API was down/unavailable. * Adds empty state to embedded graph when a query returns no results. Removes window.localStorage when embedded so that the embedded App cannot make localStorage modifications * Adds EmbedMode enum that allows embedding to either be simple (just the graph) or more complete (with banner, run button, and codemirror. * More changes to how embedding works. Adds debounced query auto-execution on simple embedding (without run button). * Replaces old Robokop answer viewer with gamma-viewer-web component. Adds answer viewer into embedded page as well. * Moves embedded tranql rendering to inside a hook. Changes the way answer viewer works inside of embedded version (component instead of modal now). * Fixes repo-wide dependency conflict bug * Fixes Dockerfile and Makefile to be up to date with develop This branch branches off from feature/local-development accidentally, so some test-related files are out of date * Remove extraneous console log & bump debounce on codemirror query from 250ms -> 1000ms. * Fixes styling on Gamma Viewer inside Bootstrap modal * Adds check for ?embed=false * Update _version.py (#12) * add heap size for build (#13) Co-authored-by: Yaphetkg * add publish to (#14) * Fixes tranqlURL parsing on production env (#15) * Where statement autocomplete, TranQLIncompleteParser unit tests, and curie-to-English-name codemirror tooltips (#16) * Updates grammar to parse incomplete where clauses * Fixes strange behavior of quotedString in grammar * Adds v1 functionality to where autocompletion. * Adds tooltips for autocompleted curies in where values * Curies typed into the codemirror will now automatically be resolved into their English identifiers in the background and shown as tooltips * Fixes bugs caused by not handling stale autocomplete calls. * Fix whitespace bug with spaces in autocompletion * Updates make test.python to use src/tranql rather than locally-installed pip tranql package. Adds test for parsing where clause for autocompletion * Adds extensive unit testing for all of the TranQLIncompleteParser * Changes node norm resolution into a single request instead of one for each curie. * Bump allowed results from name-resolutions from 50 -> 250. Properly catch node-norm fetch in case of failure within codemirror onChange method Co-authored-by: Griffin Roupe Co-authored-by: Griffin Roupe Co-authored-by: Griffin Roupe Co-authored-by: Griffin Roupe * Update _version.py * Update _version.py Using numbers instead due to PEP 440 mandate * HeLx-UI/TranQL Integration Iteration 2 and Autocompletion Improvements (#17) * Fix minor bug in automatic English curie name resolution * Relocate codemirror update logic into the _updateCode method. Fix small bug where English curie name resolution didn't work on the initial query loaded with ?query param * Quick fix to minor bug in debounce query execution caused by moving update logic to * Updates embedded TranQL window to only use 100vh (no overflow) instead of header + 100vh graph. * Re-enables navigation tools on embedded mode now that embedded window does not have overflow * Add query param ?answer_viewer for use when embedded param is true, automatically showing the answer viewer instead of the force graph in embedded mode * Removes ?answer_viewer for ?use_last_view. Adds embeddedLocalStorage for explicitly using localStorage while embedded. Adds ?use_last_view, which will show answer viewer/force graph as the default view when embedded, depending on which was last used * Fixes significant bug with _translateGraph that caused numerous issues with the UI. Fixes bug that was previously noted in a comment. Caused the Show Graph button to display the schema if no actual graph had been loaded yet. Also created a bug in which a race condition could cause the schemaMessage to be misconfigured and subsequently break the answer viewer component. * Delete spurious logs added in debugging for previous commit * Adds sorting for concept->concept suggestions based on edge count between concepts * Add sorting of predicate suggestions in autocomplete based on edge scoring. Removes the redundant concept hints on predicate suggestions. Note that edge scoring is not yet implemented in tranql_schema.py, so this sorting will not be apparent. * Adds prototype scoring mechanism to edges provided by redis reasoner * Updates the way that scoring data is obtained in decorate_schema * Update plater graph interface mock to have an empty field. * Fix typo in tranql_schema * Adds rudimentary localStorage cache for curie-to-english-name resolution * Update requirements.txt Updating plater version for summary fix Co-authored-by: Griffin Roupe Co-authored-by: YaphetKG <45075777+YaphetKG@users.noreply.github.com> * Update _version.py * New autocomplete changes (#18) * Fix minor bug in automatic English curie name resolution * Relocate codemirror update logic into the _updateCode method. Fix small bug where English curie name resolution didn't work on the initial query loaded with ?query param * Quick fix to minor bug in debounce query execution caused by moving update logic to * Updates embedded TranQL window to only use 100vh (no overflow) instead of header + 100vh graph. * Re-enables navigation tools on embedded mode now that embedded window does not have overflow * Add query param ?answer_viewer for use when embedded param is true, automatically showing the answer viewer instead of the force graph in embedded mode * Removes ?answer_viewer for ?use_last_view. Adds embeddedLocalStorage for explicitly using localStorage while embedded. Adds ?use_last_view, which will show answer viewer/force graph as the default view when embedded, depending on which was last used * Fixes significant bug with _translateGraph that caused numerous issues with the UI. Fixes bug that was previously noted in a comment. Caused the Show Graph button to display the schema if no actual graph had been loaded yet. Also created a bug in which a race condition could cause the schemaMessage to be misconfigured and subsequently break the answer viewer component. * Delete spurious logs added in debugging for previous commit * Adds sorting for concept->concept suggestions based on edge count between concepts * Add sorting of predicate suggestions in autocomplete based on edge scoring. Removes the redundant concept hints on predicate suggestions. Note that edge scoring is not yet implemented in tranql_schema.py, so this sorting will not be apparent. * Adds prototype scoring mechanism to edges provided by redis reasoner * Updates the way that scoring data is obtained in decorate_schema * Update plater graph interface mock to have an empty field. * Fix typo in tranql_schema * Adds rudimentary localStorage cache for curie-to-english-name resolution * Memoize _resolveIdentifiersFromConcept (full caching functionality for where clause/English name resolution. Note that currently the cached objects are extremely large (memoizing a single value of _resolveIdentifiersFromConcept could potentially cache as upwards of 200kB in localStorage) * Cut down on storage used in caching _resolveIdentifiersFromConcept/Curie. Fix results returned from caching of aforementioned. Add abort controller to name-resolution for expired requests. Remove superfluous values in results from _resolveIdentifiersFromConcept/Curie to cut down on cache size. Limit the number of results cached per query to number that will actually be displayed .Change caching on _resolveIdentifiersFromConcept to return precise results from args. Add abort controller to name-resolution-sri calls for stale autocomplete calls. * Fix issue with caching where overlapping results from different search terms weren't properly handled * Gracefully exit autocomplete on failure rather than displaying an error message. * Remove import of async-retry Co-authored-by: Griffin Roupe Co-authored-by: Griffin Roupe Co-authored-by: Griffin Roupe * use latest plater with connection fix (#19) * use latest plater with connection fix * remove redis graph dep Co-authored-by: Yaphetkg * Update _version.py version bump * Bmt version fix (#20) * bump bmt to fixed version * add version for test image * revert version * fix bmt and flask deps * linkml and linkml related things pinned * Update _version.py * Update _version.py Co-authored-by: frostyfan109 Co-authored-by: frostyfan109 Co-authored-by: frostyfan109 <31631417+frostyfan109@users.noreply.github.com> Co-authored-by: yaphetkg Co-authored-by: Chuck Bennett Co-authored-by: cnbennett3 <53792237+cnbennett3@users.noreply.github.com> Co-authored-by: Steven Co-authored-by: Carl Schreep Co-authored-by: Yaphetkg Co-authored-by: Carl Schreep Co-authored-by: Griffin Roupe Co-authored-by: Griffin Roupe Co-authored-by: Griffin Roupe Co-authored-by: Griffin Roupe Co-authored-by: Griffin Roupe Co-authored-by: Griffin Roupe Co-authored-by: Griffin Roupe Co-authored-by: Griffin Roupe Co-authored-by: Griffin Roupe Co-authored-by: Griffin Roupe Co-authored-by: Griffin Roupe Co-authored-by: Griffin Roupe Co-authored-by: Griffin Roupe Co-authored-by: Griffin Roupe --- Jenkinsfile | 65 +- Makefile | 10 +- package.json | 7 + requirements.txt | 18 +- src/tranql/_version.py | 2 +- src/tranql/api.py | 47 +- src/tranql/backplane/api/NDEX_api.py | 4 +- src/tranql/backplane/api/automat_api.py | 14 +- .../backplane/api/biolink_walker_api.py | 4 +- src/tranql/backplane/api/gamma_api.py | 12 +- src/tranql/backplane/api/icees_api.py | 6 +- src/tranql/backplane/api/indigo_api.py | 4 +- src/tranql/backplane/api/roger_api.py | 8 +- src/tranql/backplane/api/rtx_query.py | 4 +- src/tranql/backplane/api/standard_api.py | 6 +- src/tranql/backplane/server.py | 2 +- src/tranql/grammar.py | 15 +- src/tranql/tranql_schema.py | 67 +- src/tranql/util.py | 10 + src/tranql/web/package.json | 56 +- src/tranql/web/setupEnv.js | 4 + src/tranql/web/src/App.css | 23 + src/tranql/web/src/App.js | 1625 +- src/tranql/web/src/App.test.js | 9 - src/tranql/web/src/GammaViewer.js | 36 + src/tranql/web/src/SettingsModal.js | 183 + src/tranql/web/src/TableViewer.js | 70 +- src/tranql/web/src/Toolbar.js | 73 + src/tranql/web/src/TranQLEmbedded.js | 98 + src/tranql/web/src/__tests__/App.test.js | 337 + .../web/src/__tests__/mock/mock_graph.js | 13528 ++++++++++++++++ ...se_incomplete_forward_select_complete.json | 1 + .../mock_parse_incomplete_single_select.js | 1 + .../__tests__/mock/mock_reasoner_urls.json | 1 + .../web/src/__tests__/mock/mock_schema.json | 388 + src/tranql/web/src/autocomplete.js | 645 + .../text-hover.css | 24 + .../text-hover.js | 198 + src/tranql/web/src/setupTests.js | 38 + .../src/static/app_data/example_queries.js | 108 + src/tranql/web/src/testUtil.js | 167 + tests/test_tranql.py | 176 +- 42 files changed, 16839 insertions(+), 1255 deletions(-) create mode 100644 package.json create mode 100644 src/tranql/web/setupEnv.js delete mode 100644 src/tranql/web/src/App.test.js create mode 100644 src/tranql/web/src/GammaViewer.js create mode 100644 src/tranql/web/src/SettingsModal.js create mode 100644 src/tranql/web/src/TranQLEmbedded.js create mode 100644 src/tranql/web/src/__tests__/App.test.js create mode 100644 src/tranql/web/src/__tests__/mock/mock_graph.js create mode 100644 src/tranql/web/src/__tests__/mock/mock_parse_incomplete_forward_select_complete.json create mode 100644 src/tranql/web/src/__tests__/mock/mock_parse_incomplete_single_select.js create mode 100644 src/tranql/web/src/__tests__/mock/mock_reasoner_urls.json create mode 100644 src/tranql/web/src/__tests__/mock/mock_schema.json create mode 100644 src/tranql/web/src/autocomplete.js create mode 100644 src/tranql/web/src/codemirror-tooltip-extension/text-hover.css create mode 100644 src/tranql/web/src/codemirror-tooltip-extension/text-hover.js create mode 100644 src/tranql/web/src/setupTests.js create mode 100644 src/tranql/web/src/static/app_data/example_queries.js create mode 100644 src/tranql/web/src/testUtil.js diff --git a/Jenkinsfile b/Jenkinsfile index ec1b6b7..1fbd46e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -2,25 +2,59 @@ pipeline { agent { kubernetes { cloud 'kubernetes' - label 'agent-docker' - defaultContainer 'agent-docker' + yaml ''' + apiVersion: v1 + kind: Pod + spec: + containers: + - name: agent-docker + image: helxplatform/agent-docker:latest + command: + - cat + tty: true + volumeMounts: + - name: dockersock + mountPath: "/var/run/docker.sock" + volumes: + - name: dockersock + hostPath: + path: /var/run/docker.sock + ''' } } stages { stage('Install') { steps { - sh ''' - python3 -m pip install --user --upgrade pip - python3 -m pip install --user -r requirements.txt - python3 -m pip install --user . - ''' + container('agent-docker') { + sh ''' + python3 -m pip install --user --upgrade pip + python3 -m pip install --user -r requirements.txt + python3 -m pip install --user . + make install.npm_nobuild + ''' + } } } stage('Test') { + environment { + MOCKING=true + BROWSER_MODE = "HEADLESS" + SANDBOX=false + CI=true + } steps { - sh ''' - make test - ''' + container('agent-docker') { + // Run the webserver in the background. + // Use curl to wait until the webserver is serving localhost:3000 and redirect stout and stderr to null device. + // Run Puppeteer and Python tests once React app is ready to be used. + // Kill react-scripts processes listening on port 3000. There's probably a better way to kill react-scripts, but it works. + sh ''' + make run.web & + wget --retry-connrefused --tries=120 --waitretry=1 -q http://localhost:3000 -O /dev/null + make test + npx kill-port 3000 + ''' + } } } stage('Publish') { @@ -31,10 +65,13 @@ pipeline { DOCKERHUB_CREDS = credentials('rencibuild_dockerhub_machine_user') } steps { - sh ''' - echo $DOCKERHUB_CREDS_PSW | docker login -u $DOCKERHUB_CREDS_USR --password-stdin - make build - ''' + container('agent-docker') { + sh ''' + echo $DOCKERHUB_CREDS_PSW | docker login -u $DOCKERHUB_CREDS_USR --password-stdin + make build + make publish + ''' + } } } } diff --git a/Makefile b/Makefile index 2423815..ca0fcd7 100644 --- a/Makefile +++ b/Makefile @@ -35,17 +35,20 @@ install.python: install.npm: cd src/tranql/web; npm install; npm run build +install.npm_nobuild: + cd src/tranql/web; npm install; + #install: Install application install: install.python install.npm #test.python: Run all python tests test.python: #${PYTHON} -m pytest --doctest-modules src - ${PYTHON} -m pytest tests + PYTHONPATH=${PWD}/src ${PYTHON} -m pytest tests #test.npm: Run all NPM tests test.npm: - echo "test NPM" + cd src/tranql/web; npm test -- --watchAll=false #test: Run all tests test: test.python test.npm @@ -71,4 +74,7 @@ download: #run.local: seeds redis with data, builds web page and runs tranql docker container run.local: download docker-compose up -d + cd src/tranql/web; npm start; + +run.web: cd src/tranql/web; npm start; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..d1169ee --- /dev/null +++ b/package.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "@codemirror/tooltip": "^0.19.6", + "@uiw/react-codemirror": "^4.2.3", + "codemirror": "^5.63.3" + } +} diff --git a/requirements.txt b/requirements.txt index f510ace..a03f5d8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,8 +3,10 @@ coverage==4.5.3 flasgger==0.9.2 flask==1.0.2 flask-cors==3.0.7 -flask-restful==0.3.7 +flask-restx==0.5.1 gunicorn==19.9.0 +MarkupSafe==2.0.1 +itsdangerous==2.0.1 jinja2==2.10 jsonpath-rw==1.4.0 networkx~=2.5.0 @@ -12,14 +14,16 @@ openapi==1.1.0 pytest==5.4.1 pytest-cov==2.7.1 python-coveralls==2.9.2 -python-dateutil==2.8.0 +python-dateutil==2.8.1 pyyaml==5.1 -redisgraph==2.2.4 requests==2.25.1 requests-cache==0.4.13 requests-mock==1.5.2 -PLATER-GRAPH==1.9.8 -bmt==0.7.2 -git+https://github.com/ranking-agent/reasoner.git +git+https://github.com/helxplatform/Plater.git@v2.0.0 +bmt==0.8.2 +linkml==1.1.17 +linkml-dataops==0.1.0 +linkml-runtime==1.1.21 +reasoner-transpiler==1.7.1 git+https://github.com/TranslatorSRI/reasoner-pydantic@v1.0.0#egg=reasoner-pydantic -git+https://github.com/TranslatorSRI/reasoner-converter@1.2.4#egg=reasoner-converter \ No newline at end of file +git+https://github.com/TranslatorSRI/reasoner-converter@1.2.4#egg=reasoner-converter diff --git a/src/tranql/_version.py b/src/tranql/_version.py index 493f741..6a9beea 100644 --- a/src/tranql/_version.py +++ b/src/tranql/_version.py @@ -1 +1 @@ -__version__ = "0.3.0" +__version__ = "0.4.0" diff --git a/src/tranql/api.py b/src/tranql/api.py index 835e1d4..536b9c7 100644 --- a/src/tranql/api.py +++ b/src/tranql/api.py @@ -14,7 +14,7 @@ from flasgger import Swagger from flask import Flask, request, abort, Response, send_from_directory, render_template, make_response from flask_cors import CORS -from flask_restful import Api, Resource +from flask_restx import Api as BaseApi, Resource from tranql.concept import ConceptModel from tranql.exception import TranQLException @@ -36,6 +36,15 @@ app = Flask(__name__, template_folder=template_folder, static_folder=static_folder) +# Due to a bug with Flask-RESTPlus/RESTX, even when doc generation is disabled on the root path, you still can't serve to it. +# This is a workaround to manually override the portion that causes the problem. +class Api(BaseApi): + def _register_doc(self, app_or_blueprint): + pass + @property + def base_path(self): + return "" + api = Api(app) CORS(app) @@ -213,10 +222,6 @@ def get(self): class DecorateKG(StandardAPIResource): """ Exposes an endpoint that allows for the decoration of a KGS 0.1.0 knowledge graph with TranQL's decorate method. """ - - def __init__(self): - super().__init__() - def post(self): """ Decorate a Knowledge Graph @@ -283,10 +288,6 @@ def post(self): class MergeMessages(StandardAPIResource): """ Exposes an endpoint that allows for the merging of an arbitrary amount of messages """ - - def __init__(self): - super().__init__() - def post(self): """ Merge Messages @@ -387,10 +388,6 @@ def post(self): class TranQLQuery(StandardAPIResource): """ TranQL Resource. """ - - def __init__(self): - super().__init__() - def post(self): """ Query TranQL @@ -470,10 +467,6 @@ def post(self): class AnnotateGraph(StandardAPIResource): """ Request the message object to be annotated by the backplane and return the annotated message """ - - def __init__(self): - super().__init__() - def post(self): """ Annotate Graph @@ -528,10 +521,6 @@ def post(self): class SchemaGraph(StandardAPIResource): """ Graph of schema to display to the client """ - - def __init__(self): - super().__init__() - def get(self): """ TranQL Schema @@ -582,10 +571,6 @@ def get(self): class ModelConceptsQuery(StandardAPIResource): """ Query model concepts. """ - - def __init__(self): - super().__init__() - def post(self): """ Biolink Model Concepts @@ -616,10 +601,6 @@ def post(self): class ModelRelationsQuery(StandardAPIResource): """ Query model relations. """ - - def __init__(self): - super().__init__() - def post(self): """ Biolink Model Relations @@ -650,10 +631,6 @@ def post(self): class ReasonerURLs(StandardAPIResource): """ Returns the URLs corresponding to `reasoner` properties. """ - - def __init__(self): - super().__init__() - def get(self): """ Retrieve Reasoner URLs @@ -675,10 +652,6 @@ def get(self): class ParseIncomplete(StandardAPIResource): """ Tokenizes an incomplete query and returns the result """ - - def __init__(self): - super().__init__() - def parse(self, parser, query): if isinstance(query, str): parsed = parser.tokenize(query) diff --git a/src/tranql/backplane/api/NDEX_api.py b/src/tranql/backplane/api/NDEX_api.py index 5bc0ea4..53b0e56 100644 --- a/src/tranql/backplane/api/NDEX_api.py +++ b/src/tranql/backplane/api/NDEX_api.py @@ -10,8 +10,8 @@ class PublishToNDEx(StandardAPIResource): """ Publish a graph to NDEx. """ - def __init__(self): - super().__init__() + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) def post(self): """ diff --git a/src/tranql/backplane/api/automat_api.py b/src/tranql/backplane/api/automat_api.py index 80638df..1a3f7f1 100644 --- a/src/tranql/backplane/api/automat_api.py +++ b/src/tranql/backplane/api/automat_api.py @@ -13,8 +13,8 @@ class AutomatResource(StandardAPIResource): - def __init__(self): - super().__init__() + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.url = config.get("AUTOMAT_URL") def get_kp_reasoner_api(self, kp_tag): @@ -25,9 +25,6 @@ def get_kp_schema_api(self, kp_tag): class AutomatSchema(AutomatResource): - def __init__(self): - super().__init__() - def get(self, kp_tag): """ Automat Schema @@ -79,9 +76,6 @@ def get(self, kp_tag): class AutomatQuery(AutomatResource): """ Generic graph query to Gamma. """ - def __init__(self): - super().__init__() - def post(self, kp_tag): """ Automat query @@ -159,10 +153,6 @@ def post(self, kp_tag): class AutomatRegistry(AutomatResource): - - def __init__(self): - super().__init__() - def get(self): """ Automat query diff --git a/src/tranql/backplane/api/biolink_walker_api.py b/src/tranql/backplane/api/biolink_walker_api.py index 993bbb1..8bb265d 100644 --- a/src/tranql/backplane/api/biolink_walker_api.py +++ b/src/tranql/backplane/api/biolink_walker_api.py @@ -10,8 +10,8 @@ class BiolinkModelWalkerService(StandardAPIResource): """ Biolink Model Walk Resource. """ - def __init__(self): - super().__init__() + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) def post(self): """ diff --git a/src/tranql/backplane/api/gamma_api.py b/src/tranql/backplane/api/gamma_api.py index 5b17a30..84e0683 100644 --- a/src/tranql/backplane/api/gamma_api.py +++ b/src/tranql/backplane/api/gamma_api.py @@ -15,8 +15,8 @@ class GammaResource(StandardAPIResource): - def __init__(self): - super().__init__() + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.robokop_url = config.get("GAMMA_URL") self.view_post_url = f'{self.robokop_url}/api/simple/view/' self.quick_url = f'{self.robokop_url}/api/simple/quick/?rebuild=false&output_format=MESSAGE&max_connectivity=0&max_results=300' @@ -26,8 +26,8 @@ def view_url(self, uid): class GammaSchema(GammaResource): - def __init__(self): - super().__init__() + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) def get(self): """Robokop schema @@ -132,8 +132,8 @@ def post(self): class PublishToGamma(GammaResource): """ Publish a graph to Gamma. """ - def __init__(self): - super().__init__() + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) def post(self): """ diff --git a/src/tranql/backplane/api/icees_api.py b/src/tranql/backplane/api/icees_api.py index e9c6013..38efa2d 100644 --- a/src/tranql/backplane/api/icees_api.py +++ b/src/tranql/backplane/api/icees_api.py @@ -29,7 +29,8 @@ def __init__( class ICEESSchema(StandardAPIResource): - def __init__(self): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.version_to_url_map = { "icees": config.get("ICEES_URL").rstrip('/') + "/knowledge_graph/schema", "icees3_and_epr": config.get("ICEES3_AND_EPR_URL").rstrip('/') + "/knowledge_graph/schema" @@ -106,7 +107,8 @@ def get(self): class ICEESClusterQuery(StandardAPIResource): """ ICEES Resource. """ - def __init__(self): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.version_to_url_map = { "icees": config.get("ICEES3_AND_EPR_URL").rstrip('/') + "/knowledge_graph", "icees3_and_epr": config.get("ICEES3_AND_EPR_URL").rstrip('/') + "/knowledge_graph" diff --git a/src/tranql/backplane/api/indigo_api.py b/src/tranql/backplane/api/indigo_api.py index 68b6705..d501c18 100644 --- a/src/tranql/backplane/api/indigo_api.py +++ b/src/tranql/backplane/api/indigo_api.py @@ -3,8 +3,8 @@ from tranql.config import config class IndigoQuery(StandardAPIResource): - def __init__(self): - super().__init__() + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.base_url = config.get("INDIGO_URL") self.query_url = f'{self.base_url}/reasoner/api/v1/query' diff --git a/src/tranql/backplane/api/roger_api.py b/src/tranql/backplane/api/roger_api.py index 66b25f8..025ceff 100644 --- a/src/tranql/backplane/api/roger_api.py +++ b/src/tranql/backplane/api/roger_api.py @@ -13,8 +13,8 @@ class RogerResource(StandardAPIResource): - def __init__(self): - super().__init__() + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.url = config.get('ROGER_URL') @@ -26,8 +26,8 @@ def get_kp_schema_api(self): class RogerSchema(RogerResource): - def __init__(self): - super().__init__() + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) def get(self): """ diff --git a/src/tranql/backplane/api/rtx_query.py b/src/tranql/backplane/api/rtx_query.py index ae2a072..8493a51 100644 --- a/src/tranql/backplane/api/rtx_query.py +++ b/src/tranql/backplane/api/rtx_query.py @@ -6,8 +6,8 @@ class RtxSchema(StandardAPIResource): - def __init__(self): - super().__init__() + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.base_url = config.get("RTX_URL") self.predicates_url = self.base_url + '/beta/api/rtx/v1/predicates' diff --git a/src/tranql/backplane/api/standard_api.py b/src/tranql/backplane/api/standard_api.py index 633892f..7895589 100644 --- a/src/tranql/backplane/api/standard_api.py +++ b/src/tranql/backplane/api/standard_api.py @@ -1,11 +1,7 @@ from flask import Flask, request, Response, jsonify -from flask_restful import Api, Resource, abort +from flask_restx import Api, Resource, abort class StandardAPIResource(Resource): - - def __init__(self): - super().__init__() - @staticmethod def validate(request, definition, no_abort=False): if not isinstance(request, dict): diff --git a/src/tranql/backplane/server.py b/src/tranql/backplane/server.py index a75e86e..daf6972 100644 --- a/src/tranql/backplane/server.py +++ b/src/tranql/backplane/server.py @@ -6,7 +6,7 @@ import os import yaml from flask import Flask -from flask_restful import Api +from flask_restx import Api from flasgger import Swagger from flask_cors import CORS from tranql.backplane.api.automat_api import AutomatQuery, AutomatSchema, AutomatRegistry diff --git a/src/tranql/grammar.py b/src/tranql/grammar.py index a507644..e6ab916 100644 --- a/src/tranql/grammar.py +++ b/src/tranql/grammar.py @@ -70,6 +70,7 @@ concept_or_var_list = Group(LBRACK.suppress() + delimitedList(concept_value | ident) + RBRACK.suppress()) # need to add support for alg expressions columnRval = function_body | realNum | intNum | quotedString.addParseAction(removeQuotes) | columnName | concept_or_var_list + whereCondition = Group( ( columnName + binop + columnRval ) | # ( columnName + in_ + concept_value_list) | @@ -140,11 +141,23 @@ # In a group just so that it is consistent with an actual table which is stored in a list. openTable = Group(delimitedList(tableNameList | Group((Literal('"') | Literal("'")) + Regex('.*')))) +# Right now, there will only be support for autocompletion of strings (that's really the only place where it makes sense). +incomplete_string = Group(delimitedList((Literal('"') | Literal("'")) + Regex('.*'))) + +incomplete_where_expression = Forward() +incomplete_where_condition = Group( + ( columnName + binop + (quotedString | incomplete_string) ) | + ( columnName + binop) | + columnName | + ( "(" + incomplete_where_expression + ")" ) +) +incomplete_where_expression << incomplete_where_condition + ZeroOrMore( ( and_ | or_ ) + incomplete_where_condition ) + statement <<= ( Group( Group(SELECT + incomplete_question_graph_expression)("concepts") + Suppress(optWhite) + Optional(Group(FROM + (openTable | Empty()))) + Suppress(optWhite) + - Optional(Group(WHERE + whereExpression("where"))) + Suppress(optWhite) + + Optional(Group(WHERE + (incomplete_where_expression("where") | Empty()))) + Suppress(optWhite) + Optional(Group(SET + setExpression("set")))("select") ) | diff --git a/src/tranql/tranql_schema.py b/src/tranql/tranql_schema.py index dfae8ea..e70e929 100644 --- a/src/tranql/tranql_schema.py +++ b/src/tranql/tranql_schema.py @@ -9,7 +9,11 @@ import time import threading from PLATER.services.util.graph_adapter import GraphInterface -from tranql.util import snake_case +from tranql.util import snake_case, title_case +import logging + +logger = logging.getLogger(__name__) + class NetworkxGraph: def __init__(self): @@ -25,14 +29,19 @@ def get_node (self, identifier, properties=None): nodes = self.net.nodes(data=True) filtered = [i for i in nodes if i[0] == identifier] return filtered[0] if len(filtered) > 0 else None - def get_edge (self, start, end, properties=None): - result = None - for e in self.net.edges: - #print (f"----- {start} {end} | {e[0]} {e[2]}") - if e[0] == start and e[1] == end: - result = e - break - return result + """ Returns an edge from the graph or None. Note that the edge attr dict returned is mutable. + :param predicate: If None, will return the first edge found between start->end if one exists. + :return: (start, predicate, end, attr_data) | None + """ + def get_edge (self, start, end, predicate=None, properties=None): + try: + edges = self.net[start][end] + if predicate is None: + predicate = list(edges.keys())[0] + edge_data = edges[predicate] + return (start, predicate, end, edge_data) + except: + return None def get_nodes (self,**kwargs): return self.net.nodes(**kwargs) def get_edges (self,**kwargs): @@ -161,7 +170,7 @@ def _create_graph_interface(service_name, redis_conf, tranql_config): **redis_connection_details ) - def _get_adatpter(self, name): + def _get_adapter(self, name): if name not in RedisAdapter.registry_adapters: raise ValueError(f"Redis backend with name {name} not registered.") return RedisAdapter.registry_adapters.get(name) @@ -174,7 +183,7 @@ def set_adapter(self, name, redis_config, tranql_config): ) def get_schema(self, name): - gi: GraphInterface = self._get_adatpter(name) + gi: GraphInterface = self._get_adapter(name) schema = gi.get_schema(force_update=True) return schema @@ -337,6 +346,42 @@ def add_layer (self, layer, name=None): for link in links: #print (f" {source_name}->{target_type} [{link}]") self.schema_graph.add_edge (source_name, link, target_type, {"reasoner":[name]}) + self.decorate_schema(layer, name) + + """ + Manually perform scoring on the Redis reasoner, since it supports it. If more reasoners support it, + this should obviously be changed to be done dynamically on each reasoner and should probably be generalized + to attribute decoration rather than just scoring. + """ + def decorate_schema(self, layer, name=None): + if name != "redis": + return + + def toBiolink(concept): + return "biolink:" + title_case(concept) + + redis_adapter = RedisAdapter() + adapter = redis_adapter._get_adapter(name) + schema_summary = adapter.summary + for source_name, targets_list in layer.items (): + source_node = self.get_node (node_id=source_name) + biolink_source_name = toBiolink(source_name) + if not biolink_source_name in schema_summary: continue + for target_type, links in targets_list.items (): + target_node = self.get_node (node_id=target_type) + biolink_target_type = toBiolink(target_type) + if not biolink_target_type in schema_summary[biolink_source_name]: continue + edge_summary = schema_summary[biolink_source_name][biolink_target_type] + total_count = sum(edge_summary.values()) + if isinstance(links, str): + links = [links] + for link in links: + biolink_link = "biolink:" + link + if not biolink_link in edge_summary: continue + (_, _, _, edge_data) = self.schema_graph.get_edge (source_name, target_type, link) + individual_count = edge_summary[biolink_link] + edge_data["score"] = individual_count / total_count + def get_edge (self, plan, source_name, source_type, target_name, target_type, predicate, edge_direction): diff --git a/src/tranql/util.py b/src/tranql/util.py index 70cacf6..02cf75d 100644 --- a/src/tranql/util.py +++ b/src/tranql/util.py @@ -408,6 +408,16 @@ def deep_merge(source, destination, no_list_repeat=True): return destination +def camel_case(arg: str): + """Convert snake_case string to camelCase""" + split = arg.split("_") + return split[0] + ''.join([i.title() for i in split[1:]]) + +def title_case(arg: str): + """Convert snake_case string to TitleCase""" + split = arg.split("_") + return ''.join([i.title() for i in arg.split("_")]) + def snake_case(arg: str): """Convert string to snake_case. Non-alphanumeric characters are replaced with _. diff --git a/src/tranql/web/package.json b/src/tranql/web/package.json index 21e47c8..6df289f 100644 --- a/src/tranql/web/package.json +++ b/src/tranql/web/package.json @@ -3,59 +3,60 @@ "version": "0.1.0", "private": true, "dependencies": { + "@emotion/core": "^10.1.1", "@material-ui/core": "^3.9.2", "@material-ui/lab": "^3.0.0-alpha.30", - "abortcontroller-polyfill": "^1.3.0", + "@popperjs/core": "^2.8.0", + "abortcontroller-polyfill": "^1.7.3", "bootstrap": "^4.3.1", - "classnames": "^2.2.6", + "classnames": "^2.3.1", + "codemirror": "^5.62.3", "create-react-class": "^15.6.3", "dexie": "^2.0.4", "dexie-observable": "^1.0.0-beta.5", "elasticlunr": "^0.9.5", "es-to-primitive": "^1.2.1", "file-saver": "^2.0.2", - "filepond": "^4.4.11", + "filepond": "^4.29.1", "filepond-plugin-file-validate-type": "^1.2.4", + "gamma-viewer-web": "^0.1.1", "js-yaml": "^3.13.1", - "json5": "^2.1.0", + "json5": "^2.2.0", "jsonpath": "^1.0.2", "mime-types": "^2.1.24", "object-sizeof": "^1.4.0", - "qs": "^6.7.0", - "rc-slider": "^8.6.7", - "react": "^16.8.4", - "react-bootstrap": "^1.0.0-beta.6", + "puppeteer": "^10.2.0", + "qs": "^6.10.1", + "rc-slider": "^9.7.2", + "react": "^17.0.2", + "react-bootstrap": "^1.6.3", "react-bootstrap-typeahead": "^3.4.6", - "react-codemirror": "^1.0.0", - "react-codemirror2": "^6.0.0", + "react-codemirror2": "^7.2.1", "react-confirm-alert": "^2.4.1", "react-contexify": "^4.0.2", "react-dom": "^16.8.4", "react-edit-inline2": "^1.0.2", - "react-filepond": "^7.0.1", - "react-force-graph": "^1.29.5", - "react-icons": "^3.7.0", + "react-filepond": "^7.1.1", + "react-force-graph": "^1.41.3", + "react-icons": "^4.2.0", "react-json-tree": "^0.11.2", - "react-json-view": "^1.19.1", - "react-modal": "^3.8.1", "react-notifications": "^1.4.3", "react-scripts": "^2.1.8", - "react-spinner": "^0.2.7", + "react-sizeme": "^3.0.2", "react-spinners": "^0.5.3", - "react-split-pane": "^0.1.87", + "react-split-pane": "^0.1.92", "react-table": "^6.9.2", "react-tooltip": "^3.10.0", "react-vis-force": "^0.3.1", "reactstrap": "^7.1.0", - "recharts": "^1.6.2", + "recharts": "^2.1.2", "storybook": "^1.0.0", - "uninstall": "0.0.0", - "uuid": "^3.3.2" + "uuid": "^3.4.0" }, "scripts": { "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", + "build": "react-scripts --max-old-space-size=4096 build", + "test": "react-scripts test --transformIgnorePatterns \"/node_modules/(?!d3-force-graph)/\" --verbose true --testPathIgnorePatterns src/__tests__/mock", "eject": "react-scripts eject", "storybook": "start-storybook -p 6006", "build-storybook": "build-storybook" @@ -63,6 +64,7 @@ "eslintConfig": { "extends": "react-app" }, + "jest": {}, "browserslist": [ ">0.2%", "not dead", @@ -71,6 +73,14 @@ ], "devDependencies": { "@kadira/storybook": "^2.21.0", - "@rescripts/cli": "0.0.11" + "@rescripts/cli": "0.0.11", + "@testing-library/jest-dom": "^5.14.1", + "@testing-library/react": "^12.1.0", + "babel-core": "^7.0.0-bridge.0", + "fake-indexeddb": "^3.1.3", + "jest-canvas-mock": "^2.3.1", + "jest-webgl-canvas-mock": "^0.2.3", + "webgl-mock": "^0.1.7", + "xmldom": "^0.6.0" } } diff --git a/src/tranql/web/setupEnv.js b/src/tranql/web/setupEnv.js new file mode 100644 index 0000000..8d3bef2 --- /dev/null +++ b/src/tranql/web/setupEnv.js @@ -0,0 +1,4 @@ +// Initialize env variables +global.args = { + mocking: process.env.mocking || false +}; \ No newline at end of file diff --git a/src/tranql/web/src/App.css b/src/tranql/web/src/App.css index 2198128..6021074 100644 --- a/src/tranql/web/src/App.css +++ b/src/tranql/web/src/App.css @@ -714,6 +714,29 @@ body { left:-1.25px; background-color:rgb(215,215,215); } + +/* Embedded style rules */ +.App.embedded .react-codemirror2 { + border: 2px solid #f0f0f0; +} +.App.embedded .CodeMirror { + padding: 0; + height: 100% !important; +} +.App.embedded .CodeMirror-scroll { + height: 100% !important; + max-height: 30vh; +} + +/* Gamma Viewer rules */ +body.modal-open > *[role="tooltip"] { + /* This is attached to the MuiPaper popup/tooltips used in the answer viewer for filtering. + * Since the Gamma Viewer can appear in a Bootstrap modal, and these popups are positioned absolutely relative to the root, + * we need to bump up the z-index to ensure they appear above the modal. + */ + z-index: 2000 !important; +} + @-ms-keyframes spin { 100% { -moz-transform: rotate(360deg); } } @-o-keyframes spin { 100% { -moz-transform: rotate(360deg); } } @-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } } diff --git a/src/tranql/web/src/App.js b/src/tranql/web/src/App.js index 8dd2a20..a9b624a 100644 --- a/src/tranql/web/src/App.js +++ b/src/tranql/web/src/App.js @@ -14,7 +14,7 @@ import { IoIosArrowDropupCircle, IoIosArrowDropdownCircle, IoIosSwap, IoMdBrowse import { FaCog, FaDatabase, FaQuestionCircle, FaSearch, FaHighlighter, FaEye, FaSpinner, FaMousePointer, FaTimes, FaFolderOpen, FaFileImport, FaFileExport, - FaArrowsAlt, FaTrash, FaPlayCircle, FaTable, FaCopy, FaPython + FaArrowsAlt, FaTrash, FaPlayCircle, FaTable, FaCopy, FaPython, FaFrown } from 'react-icons/fa'; // import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'; import 'react-confirm-alert/src/react-confirm-alert.css'; @@ -25,27 +25,30 @@ import DefaultTooltipContent from 'recharts/lib/component/DefaultTooltipContent' import ReactTooltip from 'react-tooltip'; import { NotificationContainer , NotificationManager } from 'react-notifications'; import 'react-notifications/lib/notifications.css'; -import { Range } from 'rc-slider'; import { GridLoader } from 'react-spinners'; import SplitPane from 'react-split-pane'; import Cache from './Cache.js'; +import TranQLEmbedded, { EmbedMode } from './TranQLEmbedded.js'; import AnswerViewer from './AnswerViewer.js'; +import GammaViewer from './GammaViewer.js'; import QueriesModal from './QueriesModal.js'; import HistoryViewer from './HistoryViewer.js'; import BrowseNodeInterface from './BrowseNodeInterface.js'; import Legend from './Legend.js'; -import TableViewer from './TableViewer.js'; +import { AppTableViewer } from './TableViewer.js'; import HelpModal, { ToolbarHelpModal } from './HelpModal.js'; import ImportExportModal from './ImportExportModal.js'; +import SettingsModal from './SettingsModal.js'; import confirmAlert from './confirmAlert.js'; import highlightTypes from './highlightTypes.js'; -import { shadeColor, adjustTitle, hydrateState, formatBytes } from './Util.js'; -import { Toolbar, Tool, /*ToolGroup*/ } from './Toolbar.js'; +import { shadeColor, adjustTitle, hydrateState, formatBytes, debounce } from './Util.js'; +import { AppToolbar, Tool, /*ToolGroup*/ } from './Toolbar.js'; import LinkExaminer from './LinkExaminer.js'; // import FindTool from './FindTool.js'; import FindTool2 from './FindTool2.js'; import Message from './Message.js'; import Chain from './Chain.js'; +import autoComplete, { TooltipExtension } from './autocomplete.js'; import ContextMenu from './ContextMenu.js'; import GraphSerializer from './GraphSerializer.js'; import { RenderInit, RenderSchemaInit, IdFilter, LegendFilter, LinkFilter, NodeFilter, ReasonerFilter, SourceDatabaseFilter, CurvatureAdjuster } from './Render.js'; @@ -54,10 +57,13 @@ import 'rc-slider/assets/index.css'; import "react-table/react-table.css"; import 'bootstrap/dist/css/bootstrap.min.css'; import { Controlled as CodeMirror } from 'react-codemirror2'; +import { getCurieFromCMToken } from './codemirror-tooltip-extension/text-hover.js'; import 'codemirror/mode/sql/sql'; import 'codemirror/addon/hint/show-hint.css'; // without this css hints won't show import './App.css'; import 'abortcontroller-polyfill/dist/polyfill-patch-fetch.js'; + +/* Setup codemirror */ require('create-react-class'); require('codemirror/addon/hint/show-hint'); require('codemirror/addon/hint/sql-hint'); @@ -90,16 +96,11 @@ class App extends Component { constructor(props) { /* Create state elements and initialize configuration. */ super(props); - if(process.env.NODE_ENV === 'development') { - this.tranqlURL = "http://localhost:8001"; - } - if(process.env.NODE_ENV === 'production') { - // behind proxy this would treat the path used to load index.html as root - this.tranqlURL = window.location.href.endsWith('/') ? window.location.href.substring(0, window.location.href.length - 1 ) : window.location.href.length ; - } //this.tranqlURL = window.location.origin; //this.tranqlURL = "http://localhost:8001"; // dev only this.robokop_url = "https://robokop.renci.org"; + this.nameResolutionURL = "https://name-resolution-sri.renci.org"; + this.nodeNormalizationURL = "https://nodenormalization-sri.renci.org/1.2"; this._contextMenuId = "contextMenuId"; // Query editor support. @@ -107,26 +108,32 @@ class App extends Component { this._getModelConcepts = this._getModelConcepts.bind (this); this._getModelRelations = this._getModelRelations.bind (this); this._getReasonerURLs = this._getReasonerURLs.bind (this); - this._codeAutoComplete = this._codeAutoComplete.bind(this); this._updateCode = this._updateCode.bind (this); this._executeQuery = this._executeQuery.bind(this); + // For usage where the query is auto-executed on change (e.g., when embedded). + this._debouncedExecuteQuery = debounce(this._executeQuery, 1000); this._abortQuery = this._abortQuery.bind(this); this._configureMessage = this._configureMessage.bind (this); this._translateGraph = this._translateGraph.bind (this); + // Specifically for autocomplete + this._codeAutoComplete = autoComplete.bind(this); + this._resolveIdentifiersFromConcept = this._resolveIdentifiersFromConcept.bind(this); // Toolbar this._setNavMode = this._setNavMode.bind(this); this._setSelectMode = this._setSelectMode.bind(this); - this._getTools = this._getTools.bind(this); - this._getButtons = this._getButtons.bind(this); - this._setHighlightTypesMode = this._setHighlightTypesMode.bind(this); this._highlightType = this._highlightType.bind(this); this.__highlightTypes = highlightTypes.bind(this); this._setConnectionExaminerActive = this._setConnectionExaminerActive.bind(this); + // Non-visualization components + this._renderCodemirror = this._renderCodemirror.bind (this); + this._renderBanner = this._renderBanner.bind (this); + this._renderAnswerViewer = this._renderAnswerViewer.bind (this); + // The visualization this._renderForceGraph = this._renderForceGraph.bind (this); this._renderForceGraph2D = this._renderForceGraph2D.bind (this); @@ -175,7 +182,6 @@ class App extends Component { // Settings management this._handleUpdateSettings = this._handleUpdateSettings.bind (this); this._toggleCheckbox = this._toggleCheckbox.bind (this); - this._renderCheckboxes = this._renderCheckboxes.bind (this); this._hydrateState = hydrateState.bind (this); this._handleQueryString = this._handleQueryString.bind (this); @@ -222,6 +228,14 @@ class App extends Component { // Cache graphs locally using IndexedDB web component. this._cache = new Cache (); + /** + * Specifies the way in which the embedded app should render (specified by the query string argument "embed"). + * + * Related: the property `embedded` is functionally equivalent to `this.embedMode !== EmbedMode.NONE`, which abstracts + * away from the embed mode functionality. + */ + this.embedMode = EmbedMode.NONE; + // Configure initial state. this.state = { code : `select chemical_substance->gene->disease @@ -275,12 +289,13 @@ class App extends Component { // Set up CodeMirror settings. codeMirrorOptions : { lineNumbers: true, - mode: 'text/x-pgsql', //'text/x-pgsql', + mode: 'text/x-mysql', //'text/x-pgsql', tabSize: 2, readOnly: false, extraKeys: { 'Ctrl-Space': this._codeAutoComplete - } + }, + textHover: true }, showCodeMirror : true, @@ -383,118 +398,16 @@ class App extends Component { activeModal : null, - exampleQueries : [ - { - title: 'Protein-Metabolite Interaction', - query: -`-- What proteins are targetted by the metabolite KEGG:C00017? - -set metabolite = "KEGG:C00017" - -select metabolite->protein - from "/graph/rtx" - where metabolite=$metabolite - -` - }, - { - title: 'Chemical substances target genes that target asthma', - query: -`-- Which chemical substances target genes that target asthma? -select chemical_substance->gene->disease - from "/graph/gamma/quick" - where disease="asthma" -` - }, - { - title: 'Usage of predicates to narrow results', - query: -`-- Which chemical substances decrease activity of genes that contribute to asthma? -select chemical_substance-[decreases_activity_of]->gene-[contributes_to]->disease - from "/graph/gamma/quick" - where disease="asthma" -` - }, - { - title: 'Phenotypic Feature-Disease Association', - query: -`-- What diseases are associated with the phenotypic feature HP:0005978? - -select phenotypic_feature->disease - from "/graph/rtx" - where phenotypic_feature="HP:0005978" -` - }, - { - title: 'Drug-Disease Pair', - query: -`-- --- Produce clinial outcome pathways for this drug disease pair. --- - -set drug = 'PUBCHEM:2083' -set disease = 'MONDO:0004979' - -select chemical_substance->gene->anatomical_entity->phenotypic_feature<-disease - from '/graph/gamma/quick' - where chemical_substance = $drug - and disease = $disease` - }, - { - title: 'Drug Targets Gene', - query: -`-- --- What drug targets some gene? --- - -set target_gene = 'HGNC:6871' --mapk1 -select chemical_substance->gene - from '/graph/gamma/quick' - where gene = $target_gene` - }, - { - title: 'Tissue-Disease Association', - query: -`-- --- What tissue types are associated with [disease]? --- -set disease = 'asthma' -select disease->anatomical_feature->cell - from '/graph/gamma/quick' - where disease = $disease -` - }, - { - title: 'Workflow 5 v3', - query: -`-- --- Workflow 5 --- --- Modules 1-4: Chemical Exposures by Clinical Clusters --- For ICEES cohorts, eg, defined by differential population --- density, which chemicals are associated with these --- cohorts with a p_value lower than some threshold? --- --- Modules 5-*: Knowledge Graph Phenotypic Associations --- For chemicals produced by steps 1-4, what phenotypes are --- associated with exposure to these chemicals? --- - -SELECT population_of_individual_organisms->chemical_substance->gene->biological_process_or_activity<-phenotypic_feature - FROM "/schema" - WHERE icees.table = 'patient' - AND icees.year = 2010 - AND icees.cohort_features.AgeStudyStart = '0-2' - AND icees.feature.EstResidentialDensity < 1 - AND icees.maximum_p_value = 1 - AND chemical_substance !=~ '^(SCTID.*|rxcui.*|CAS.*|SMILES.*|umlscui.*)$' - AND icees.regex = "(MONDO|HP):.*""` - } - ] + exampleQueries : require("./static/app_data/example_queries.js"), //showAnswerViewer : true + // showAnswerViewerOnLoad: false + useLastUsedView: false }; + // This is a cache that stores results from `this._resolveIdentifiersFromConcept` for use in codemirror tooltips. + this._autocompleteResolvedIdentifiers = {}; + /** * We want to reset the interval if user highlights again. Stores `id`:`interval` Structure was too complicated so it is now separated into two objects. */ @@ -531,6 +444,10 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ // Fetch controllers this._queryController = new window.AbortController(); this._autoCompleteController = new window.AbortController(); + this._nameResController = new window.AbortController(); + // This is a tracking symbol used to detect if a new autocomplete call has been opened. + // It it used to prevent stale autocompletion calls from writing to the codemirror state. + this._autoCompleteInstance = Symbol(); this._OVERLAY_X = 0; this._OVERLAY_Y = 0; @@ -542,12 +459,16 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ // Promises this.schemaPromise = new Promise(()=>{}); } + get embedded() { + return this.embedMode !== EmbedMode.NONE; + } /** * Updates the queries contained within the cache viewer modal. * */ _updateCacheViewer () { const updateQueryTitle = (query, queryTitle) => { + if (!this._cachedQueriesModal.current) return; query.data.title = queryTitle; this._cache.db.cache.update(query.id,query); /* Gets a bit messy here but since it's directly modifying a property of the object, and these objects are all formatted and stored in the ref's state, we have to work around it a bit. */ @@ -626,558 +547,49 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ this.setState({ code: newCode }); - } - /** - * Callback for handling autocompletion within the query editor. - * - * @param {object} cm - The CodeMirror object. - * @private - */ - _codeAutoComplete () { - // https://github.com/JedWatson/react-codemirror/issues/52 - var codeMirror = this._codemirror; - - // hint options for specific plugin & general show-hint - // 'tables' is sql-hint specific - // 'disableKeywords' is also sql-hint specific, and undocumented but referenced in sql-hint plugin - // Other general hint config, like 'completeSingle' and 'completeOnSingleClick' - // should be specified here and will be honored - - // Shallow copy it. - const pos = Object.assign({}, codeMirror.getCursor()); - const untrimmedPos = codeMirror.getCursor(); - const textToCursorPositionUntrimmed = codeMirror.getRange({ line : 0, ch : 0 }, { line : pos.line, ch : pos.ch }); - const textToCursorPosition = textToCursorPositionUntrimmed.trimRight(); - const entireText = codeMirror.getValue(); - - // const splitLines = textToCursorPosition.split(/\r\n|\r|\n/); - // // Adjust the position after trimming to be on the correct line. - // pos.line = splitLines.length - 1; - // // Adjust the position after trimming to be on the correct char. - // pos.ch = splitLines[splitLines.length-1].length; - - const setHint = function(options, noResultsTip) { - if (typeof noResultsTip === 'undefined') noResultsTip = true; - if (noResultsTip && options.length === 0) { - options.push({ - text: String(''), - displayText:'No valid results' - }); - } - const hintOptions = { - // tables: tables, - hint: function() { - return { - from: pos, - to: untrimmedPos, - list: options.map((option) => { - // Process custom options - `replaceText` - if (option.hasOwnProperty('replaceText')) { - let replaceText = option.replaceText; - let from = option.hasOwnProperty('from') ? option.from : pos; - let to = option.hasOwnProperty('to') ? option.to : untrimmedPos; - - option.from = { line : from.line, ch : from.ch - replaceText.length }; - option.to = { line : to.line, ch : to.ch}; - - if (replaceText.length > 0) { - const trimmedLines = textToCursorPositionUntrimmed.trimRight().split(/\r\n|\r|\n/); - const lastLine = trimmedLines[trimmedLines.length-1]; - option.from.line = trimmedLines.length - 1; - option.from.ch = lastLine.length - replaceText.length; - } - - - delete option.replaceText; - } - - return option; - }) - }; - }, - disableKeywords: true, - completeSingle: false, - completeOnSingleClick: false - }; - - codeMirror.showHint(hintOptions); - // codeMirror.state.completionActive.pick = () => { - // codeMirror.showHint({ - // hint: function() { - // return { - // from: pos, - // to: pos, - // list: [{ - // text: String(''), - // displayText: 'foobar', - // className: 'testing' - // }] - // }; - // }, - // disableKeywords: true, - // completeSingle: false, - // }); - // } - } - - const setError = (resultText, status, errors, resultOptions) => { - if (typeof resultOptions === "undefined") resultOptions = {}; - codeMirror.showHint({ - hint: function() { - return { - from: pos, - to: pos, - list: [{ - text: String(''), - displayText: resultText, - className: 'autocomplete-result-error', - ...resultOptions, - }] - }; - }, - disableKeywords: true, - completeSingle: false, - }); - if (typeof status !== "undefined" && typeof errors !== "undefined") { - codeMirror.state.completionActive.pick = () => { - this._handleMessageDialog (status, errors); - } - } - } - - const setLoading = function(loading) { - if (loading) { - // text property has to be String('') because when it is '' (falsey) it refuses to display it. - codeMirror.showHint({ - hint: function() { - return { - from: pos, - to: pos, - list: [{ - text: String(''), - displayText: 'Loading', - className: 'loading-animation' - }] - }; - }, - disableKeywords: true, - completeSingle: false, - }); - } - else { - codeMirror.closeHint(); - } - } /** - * TODO: - * could try to see if its possible to have two select menus for predicates that also show concepts from the predicates - * would look something like this image, when, for example, you pressed the right arrow or left clicked or something on a predicate: - * https://i.imgur.com/LBsdrcq.png - * could somehow see if there's a way to have predicate suggestion work properly when there's a concept already following the predicate - * Ex: 'select foo-[what_can_I_put_here?]->baz' - * Would involve sending more of the query instead of cutting it off at cursor. - * Then would somehow have to backtrack and locate which token the cursor's position translates to. + * Perform editor-related tasks upon query updating. + * Perform this on the callback (updated) state, so that the codemirror state is updated. + * */ - - this._autoCompleteController.abort(); - this._autoCompleteController = new window.AbortController(); - - setLoading(true); - - fetch(this.tranqlURL + '/tranql/parse_incomplete', { - signal: this._autoCompleteController.signal, - method: "POST", - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify([textToCursorPositionUntrimmed, entireText]) - }).then(res => res.json()) - .then(async (parsedTree) => { - setLoading(false) - - if (parsedTree.errors) { - // this._handleMessageDialog (parsedTree.status, parsedTree.errors); - setError("Failed to parse", parsedTree.status, parsedTree.errors); - } - else { - setLoading(true); - await this.schemaPromise; - setLoading(false); - const graph = this.state.schemaMessage.knowledge_graph; - - // Recursviely removes any tokens that are linebreaks from a parsed tree. - const stripLinebreaks = function(tree) { - if (Array.isArray(tree)) { - return tree.filter((token) => stripLinebreaks(token)); - } - else { - return tree.toString().match(/\r\n|\r|\n/) === null; - } - } - - const incompleteTree = parsedTree[0]; - const completeTree = parsedTree[1]; - - // Filter whitespace from the statements - const block = incompleteTree[incompleteTree.length-1].map((statement) => { - return stripLinebreaks(statement); - }); - const completeBlock = completeTree[completeTree.length-1].map((statement) => { - return stripLinebreaks(statement); - }); - - const lastStatement = block[block.length-1]; - const lastStatementComplete = completeBlock[block.length-1]; - - const statementType = lastStatement[0]; - - setLoading(true); - const fromOptions = await this.reasonerURLs; - setLoading(false); - - fromOptions["/schema"] = "/schema"; - - const whereOptions = [ - 'testing', - 'foobar' - ]; - - const concept_arrows = [ - '->', - '<-' - ]; - - const all_arrows = [ - '->', - '<-', - '-[', - '<-[' - ]; - - const arrow_to_pred_arrow = (arrow) => { - return { - '->' : [ - '-[', - '', - ']->' - ], - '<-' : [ - '<-[', - '', - ']-' - ] - }[arrow]; - } - - const arrowToEmptyPredicate = (arrow) => { - return arrow_to_pred_arrow(arrow); - } - - const isBackwardsPredicate = (predicate) => { - return predicate[0] === '<-['; - } - - const toForwardPredicate = (predicate) => { - predicate[0] = '-['; - predicate[2] = ']->'; - return predicate; - } - - const completePredicate = (predicate) => { - if (isBackwardsPredicate (predicate)) { - predicate[2] = arrow_to_pred_arrow("<-")[2]; - } - else { - predicate[2] = arrow_to_pred_arrow("->")[2]; - } - return predicate; - } - - const concept = (old_concept) => { - // Concept identifiers aren't actually parsed by the lexer, but rather the ast in Query::add. - // This just copies the methods that the ast uses to parse concept identifiers. - if (old_concept.indexOf(":") !== -1) { - const split = old_concept.split(":"); - if (split.length - 1 > 1) { - throw new Error(`Invalid concept identifier "${old_concept}"`); - } - const [name, type_name] = split; - return type_name; - } - else { - return old_concept; - } - } - - const lastToken = lastStatement[lastStatement.length-1]; - const secondLastToken = lastStatement[lastStatement.length-2]; - const thirdLastToken = lastStatement[lastStatement.length-3]; - - console.log(statementType, lastStatement, lastToken); - - // Try/catch the entirety of the logic - try { - if (statementType === 'select') { - let validConcepts; - if (lastToken === "-") { - // Arrow suggestion - // "select foo-" - validConcepts = all_arrows.map((arrow) => { - return { - displayText: arrow, - text: arrow, - replaceText: "-" - }; - }); - } - else if (Array.isArray(lastToken) && lastToken.length < 3) { - // If the last token is an array and not length 3 then it is an incomplete predicate. - // "select foo-[" or "select foo-[bar" - let currentPredicate = completePredicate([ - lastToken[0], - lastToken[1] !== undefined ? lastToken[1] : "" - ]); - let previousConcept = concept(secondLastToken); - // May be undefined if there is no next concept - let has_no_next_concept = (lastStatementComplete.length - lastStatement.length) == 0; - let nextConcept = has_no_next_concept? undefined : concept(lastStatementComplete[lastStatement.length]) ; - // See https://github.com/frostyfan109/tranql/issues/117 for why this approach doesn't work - - - - const backwards = isBackwardsPredicate (currentPredicate); - - console.log ([previousConcept, currentPredicate, nextConcept]); - - // Should replace this method with reduce - - const allEdges = graph.edges.filter((edge) => { - if (backwards) { - return edge.target_id === previousConcept && - (nextConcept === undefined || edge.source_id === nextConcept) && - edge.type.startsWith(currentPredicate[1]); - } - else { - return ( - edge.source_id === previousConcept && - (nextConcept === undefined || edge.target_id === nextConcept) && - edge.type.startsWith(currentPredicate[1]) - ); - } - }); - const uniqueEdgeMap = {}; - allEdges.forEach((edge) => { - if (!uniqueEdgeMap.hasOwnProperty(edge.type)) { - uniqueEdgeMap[edge.type] = edge; - } - }); - const uniqueEdges = Object.values(uniqueEdgeMap); - validConcepts = uniqueEdges.map((edge) => { - const replaceText = currentPredicate[1]; - // const actualText = type + currentPredicate[2]; - const conceptHint = " (" + (backwards ? edge.source_id : edge.target_id) + ")"; - const actualText = edge.type; - const displayText = edge.type + conceptHint; - return { - displayText: displayText, - text: actualText, - replaceText : replaceText - }; - }); - } - else { - // Otherwise, we are handling autocompletion of a concept. - let currentConcept = ""; - let predicate = null; - let previousConcept = null; - - if (lastToken === statementType) { - // "select" - } - else if (secondLastToken === statementType) { - // "select foo" - currentConcept = concept(lastToken); - } - else if (concept_arrows.includes(lastToken) || Array.isArray(lastToken)) { - // "select foo->" or "select foo-[bar]->" - predicate = lastToken; - previousConcept = concept(secondLastToken); - } - else { - previousConcept = concept(thirdLastToken); - predicate = secondLastToken; - currentConcept = concept(lastToken); - } - - - if (predicate === null) { - // Predicate will only be null if there are no arrows, and therefore the previousConcept is also null. - // Single concept - just "select" or "select foo" where the concept is either "" or "foo" - validConcepts = graph.nodes.filter((node) => node.type.startsWith(currentConcept)).map(node => node.type); - } - else { - // If there is a predicate, we have to factor in the previous concept, the predicate, and the current concept. - if (!Array.isArray(predicate)) { - // We want to assign an empty predicate - predicate = arrowToEmptyPredicate (predicate); - } - - const backwards = isBackwardsPredicate (predicate); - - console.log ([previousConcept, predicate, currentConcept]); - // Concepts could be named like select f1:foo->f2:bar - // we need to split them and grab the actual types - let previousConceptSplit = previousConcept.split(':'); - let currentConceptSplit = currentConcept.split(':'); - previousConcept = previousConceptSplit[previousConceptSplit.length - 1]; - currentConcept = currentConceptSplit[currentConceptSplit.length - 1]; - validConcepts = graph.edges.filter((edge) => { - if (backwards) { - return ( - edge.source_id.startsWith(currentConcept) && - edge.target_id === previousConcept && - (predicate[1] === "" || edge.type === predicate[1]) - ); - } - else { - return ( - edge.source_id === previousConcept && - edge.target_id.startsWith(currentConcept) && - (predicate[1] === "" || edge.type === predicate[1]) - ); - } - }).map((edge) => { - if (backwards) { - return edge.source_id; - } - else { - return edge.target_id - } - }) - } - validConcepts = validConcepts.unique().map((concept) => { - return { - displayText: concept, - text: concept, - replaceText: currentConcept - }; - }); - } - setHint(validConcepts); - - } - else if (statementType === 'from') { - let currentReasonerArray = lastStatement[1]; - let startingQuote = ""; - if (currentReasonerArray === undefined) { - // Adds an apostrophe to the start of the string if it doesn't have one ("from") - startingQuote = "'"; - currentReasonerArray = [[ - "'", - "" - ]]; - } - const endingQuote = currentReasonerArray[currentReasonerArray.length - 1][0]; - const currentReasoner = currentReasonerArray[currentReasonerArray.length - 1][1]; - // The select statement must be the first statement in the block, but thorough just in case. - // We also want to filter out whitespace that would be detected as a token. - const selectStatement = block.filter((statement) => statement[0] === "select")[0].filter((token) => { - return typeof token !== "string" || token.match(/\s/) === null; - }); - // Don't want the first token ("select") - const tokens = selectStatement.slice(1); - - let validReasoners = []; - - Object.keys(fromOptions).forEach((reasoner) => { - let valid = true; - if (tokens.length === 1) { - // Handles if there's only one concept ("select foo") - const currentConcept = concept(tokens[0]); - graph.nodes.filter((node) => node.type.startsWith(currentConcept)).forEach(node => node.reasoner.forEach((reasoner) => { - !validReasoners.includes(reasoner) && validReasoners.push(reasoner); - })); - } - else { - for (let i=0;i { - if (backwards) { - return ( - edge.source_id.startsWith(currentConcept) && - edge.target_id === previousConcept && - (predicate[1] === "" || edge.type === predicate[1]) && - (reasoner === "/schema" || edge.reasoner.includes(reasoner)) - ); - } - else { - return ( - edge.source_id === previousConcept && - edge.target_id.startsWith(currentConcept) && - (predicate[1] === "" || edge.type === predicate[1]) && - (reasoner === "/schema" || edge.reasoner.includes(reasoner)) - ); - } - }).length > 0; - if (!isTransitionValid) { - valid = false; - break; - } - } - if (valid) { - validReasoners.push(reasoner); - } - } - }); - - const validReasonerValues = validReasoners.map((reasoner) => { - return fromOptions[reasoner]; - }).filter((reasonerValue) => { - return reasonerValue.startsWith(currentReasoner); - }).map((reasonerValue) => { - return { - displayText: reasonerValue, - text: startingQuote + reasonerValue, - // text: startingQuote + reasonerValue + endingQuote, - replaceText: currentReasoner - }; - }); - - setHint(validReasonerValues); - } - else if (statementType === 'where') { - - } - } - catch (e) { - setError('Failed to parse', 'Failed to parse', [{message: e.message, details: e.stack}]); + this.setState({}, () => { + const editor = this._codemirror; + if (!editor) return; + if (editor.state.completionActive) { + this._codeAutoComplete(); + } + /* Traverse through each token in the editor and perform identifier resolution on it if it's a string representing a curie */ + const lines = editor.lineCount(); + for (let lineNumber=0; lineNumber {}, false); } } - }) - .catch((error) => { - if (error.name !== "AbortError") { - setError('Error', 'Error', [{message: error.message, details: error.stack}]); - } - }); + } + }); + /** + * Note that the debounced query execution in embedded mode still occurs in the codemirror's `onChange` callback. + * This is because initially loading the ?query param into the graph should execute *immediately*, not on a debounce, + * (and there are a couple other issues other than the immediate execution that arise from having it here related the ?query loading). + */ } + /** * Sets the active force graph * * @private */ _setSchemaViewerActive (active) { - this._browseNodeInterface.current.hide(); - this._linkExaminer.current.hide(); + if (this._browseNodeInterface.current) this._browseNodeInterface.current.hide(); + if (this._linkExaminer.current) this._linkExaminer.current.hide(); this._closeObjectViewer(); // Don't set state, thereby reloading the graph, if the schema viewer isn't enabled @@ -1636,7 +1048,7 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ node["id"] = id; if (node["category"] !== undefined && node["category"] !== null) { const catArr = node["category"]; - console.log(`CATEGORY=[${catArr}]`); + // console.log(`CATEGORY=[${catArr}]`); let typeArr = catArr.map(this._categoryToType); node["type"] = typeArr; } @@ -1674,7 +1086,7 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ }; message.knowledge_graph.edges = newLinkArray; }; - this._configureMessage (message,false,false); + this._configureMessage (message,false,schema); this.setState({},() => { if (typeof noRenderChain === "undefined") noRenderChain = false; if (typeof schema === "undefined") schema = this.state.schemaViewerActive && this.state.schemaViewerEnabled; @@ -1720,6 +1132,7 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ if (result !== undefined) { console.log("Got schema from cache"); let msg = result.data; + console.log(JSON.stringify(msg)); const prevMsg = this.state.message; const prevRecord = this.state.record; this._configureMessage(msg,false,true); @@ -1804,18 +1217,185 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ } ) } + /** + * Query name-resolution-sri to resolve ontological identifiers from concept names. + * For example, go from "asthma" -> ["MONDO:XXX", "MONDO:YYY", "DOID:ZZZ"] + * + * As a side-effect, it caches these values in `state.autocompleteResolvedIdentifiers` + * for use in autocomplete tooltips. + * + * @param {string} conceptValue - The value of the concept, e.g., "asthma" or "asth". If this param is a less than two characters, the method will return an empty object. + * @param {string} conceptType - The concept itself, e.g., "disease" or "chemical_substance" + * @param {number} [resultLimit=50] - Limit the number of identifiers that will be returned from a concept value. The actual number of results returned by the method will be equal to or less than this after type filtratiion. + * @param {number} [returnLimit=50] - Limit the final number of results returned and cached by the function. By specifying the actual number of results that are required to be returned, it helps cut down on storage usage in caching. + * + * @throws {TypeError} Failure to fetch from APIs. Should be handled properly by caller. + * @private + */ + async _resolveIdentifiersFromConcept(conceptValue, conceptType, resultLimit=50, returnLimit=50, ignoreCache=false) { + this._nameResController.abort(); + this._nameResController = new window.AbortController(); + + if (!ignoreCache) { + // Find any cahced results that have the same type as `conceptType` and have a label that starts with `conceptValue`. + const results = Object.entries(this._autocompleteResolvedIdentifiers).filter(([curie, data]) => { + return data._searchMetadata.some(([cachedConceptValue, cachedConceptType]) => cachedConceptValue === conceptValue && cachedConceptType === conceptType); + return data._searchConceptType === conceptType && data._searchConceptValue === conceptValue; + return ( + // data._searchConceptType && data._searchConceptType.startsWith(conceptType) && + // data._searchConceptValue && data._searchConceptValue.startsWith(conceptValue) && + data.type.some((type) => this._categoryToType(type) === conceptType) && ( + data.preferredLabel.startsWith(conceptValue) || + data.otherLabels.some((label) => label.startsWith(conceptValue)) + ) + ); + }); + if (results.length > 0) return Object.fromEntries(results); + } + // Short strings are not well-supported by the name resolution API, so return no results. + if (conceptValue.length < 3) return {}; + const args = { + limit: resultLimit, + // offset: resultOffset, + string: conceptValue + }; + const res = await fetch(this.nameResolutionURL + '/lookup?' + qs.stringify(args), { + method: "POST", + signal: this._nameResController.signal + }); + /* + interface NameResolutions { + [curie: string]: string[] + } + Where the strings in the array are names, i.e. {'MONDO:XXX': ["Asthma", "Asthmas", "Bronchial asthma", ...], ... } + */ + const nameResolutions = await res.json(); + // Now that we have name resolutions, we need to go through and only take the ones that are the same type as conceptType. + // For example, the conceptValue "a" could yield both "Asthma" (disease) and "Atrazine" (chemical_substance), but we only want diseases. + const filtered = {}; + // The qs library does not seem to support the same type of qs list serialization as is expected by node norm, but URLSearchParams does. + const nodeNormArgs = new URLSearchParams(); + Object.keys(nameResolutions).forEach((curie) => nodeNormArgs.append("curie", curie)); + // Not entirely sure what this argument does, but it sounds like it merges results which we might not want. Probably inconsequential either way. + nodeNormArgs.append("conflate", false); + const nodeNormResults = await (await fetch( + this.nodeNormalizationURL + '/get_normalized_nodes?' + nodeNormArgs.toString() + )).json(); + for (let i=0; i this._categoryToType(type) === conceptType); + const includedCuries = Object.keys(filtered); + const includedEquivalentIdentifiers = result["equivalent_identifiers"].filter(({ identifier }) => includedCuries.includes(identifier)); + if (includedEquivalentIdentifiers.length > 0) { + includedEquivalentIdentifiers.forEach((equivIdent) => { + }); + } + else if (isSameType) filtered[curie] = { + // Node normalization returns its "preferred" label for the curie + preferredLabel: result["id"]["label"], + // Node normalization returns its "preferred" curie for the term + preferredCurie: result["id"]["identifier"], + score: 1, + // Name resolutions also returns a whole bunch of synonyms for the same curie + /* Uses up too much space when caching */ + // otherLabels: nameResolutions[curie], + // equivalentIdentifiers: result["equivalent_identifiers"], + type: result["type"], + _searchMetadata: [[conceptValue, conceptType]], + }; + } + } + this._updateResolvedIdentifiers(filtered, returnLimit); + return Object.fromEntries(Object.entries(filtered).slice(0, returnLimit)); + } + /** + * Very similar to `_resolveIdentifiersFromConcept`, except that is designed for use with a curie instead of a name & biolink type. + * This is made to be used so that whenever a user types a curie directly into the query, it can automatically resolve the English name for them. + * + * @param {string} curie - A curie, e.g. "MONDO:0005240" + * @param {boolean=false} ignoreCache - When false, this method returns previously cached results for `curie`. + * + */ + async _resolveIdentifiersFromCurie(curie, ignoreCache=false) { + /** If caching is enabled and the curie results are cached, return the cached results */ + if (!ignoreCache && this._autocompleteResolvedIdentifiers[curie]) { + const results = {}; + results[curie] = this._autocompleteResolvedIdentifiers[curie]; + return results; + } + + const nodeNormResult = await (await fetch( + this.nodeNormalizationURL + '/get_normalized_nodes?' + qs.stringify({ curie }) + )).json(); + const result = nodeNormResult[curie]; + + const results = {}; + if (result !== null) { + results[curie] = { + preferredLabel: result["id"]["label"], + preferredCurie: result["id"]["identifier"], + score: 1, + // Not really necessary to query name-resolution for this method's use case. + /* Too much space when caching */ + // otherLabels: [result["id"]["label"]], + // equivalentIdentifiers: result["equivalent_identifiers"], + type: result["type"], + _searchMetadata: [] + }; + } + this._updateResolvedIdentifiers(results, -1); + return results; + } + /** + * Caching mechanism used by _resolveIdentifiersFromConcept and _resolveIdentifiersFromCurie + * + * @param {Object} results - Results returned from these methods. + * @param {Number} cacheLimit - Limit the number of results cached (where the highest scored results will be cached). Negative to disable. + */ + _updateResolvedIdentifiers(results, cacheLimit=10) { + // Only cache `cacheLimit` results, since each result can take up upwards of a kB to store in localStorage, + // and each autocomplete can potentially result in hundreds of results being cached when no limit is set. + if (cacheLimit >= 0) { + results = Object.fromEntries(Object.entries(results).sort(([_, result1], [__, result2]) => result2.score - result1.score).slice(0, cacheLimit)); + } + // Add results to the cache. + Object.entries(results).forEach(([curie, result]) => { + const storedResult = this._autocompleteResolvedIdentifiers[curie]; + if (storedResult) { + // Update result (newer data) with metadata from old result. + console.log("Updating result for ", curie); + result._searchMetadata = result._searchMetadata.concat(storedResult._searchMetadata); + } + console.log("Caching result for", curie) + // Store newest data in cache. + this._autocompleteResolvedIdentifiers[curie] = result; + }); + localStorage.setItem('autocompleteIdentifiers', JSON.stringify(this._autocompleteResolvedIdentifiers)); + // Update codemirror tooltips with new cached results. + this._codemirror.state.resolvedIdentifiers = this._autocompleteResolvedIdentifiers; + } /** * Get the valid options in the `from` clause and their respective `reasoner` values * * @private */ async _getReasonerURLs () { - const res = await fetch(this.tranqlURL + '/tranql/reasonerURLs', { - method: "GET", - }) - const reasonerURLs = await res.json(); + try { + const res = await fetch(this.tranqlURL + '/tranql/reasonerURLs', { + method: "GET", + }) + const reasonerURLs = await res.json(); - return reasonerURLs; + return reasonerURLs; + } catch { + // Fetch failed. + return []; + } } /** * Get the concept model and stores as state. @@ -2052,6 +1632,20 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ * @private */ _updateFg () { + if (this.fg) { + // Performing actions such as dragging a node will reset fg.controls().enabled back to true automatically, + // thus it's necessary to hook into the TrackballControls and make sure that they're immediately re-disabled. + // *Currently disabled always.* + if (this.embedded && false) { + const controls = this.fg.controls(); + controls.enabled = false; + const _update = controls.update.bind(controls); + controls.update = (...args) => { + _update(...args); + if (controls.enabled === true) controls.enabled = false; + } + } + } // let graph = this.state.schemaViewerEnabled && this.state.schemaViewerActive ? this.state.schema : this.state.graph; } /** @@ -2102,33 +1696,6 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ this._translateGraph(newMessage,false,false); }); } - - // If the selected node/link is hidden we want to deselect it. - // this.setState({},() => { - // if (this.state.selectedNode !== null) { - // if (this.state.selectedNode.hasOwnProperty('node') && newMessage.graph.nodes.filter((node) => node.id === this.state.selectedNode.node.id).length === 0) { - // delete this.state.selectedNode.node; - // this._updateDimensions(); - // } - // if ( - // this.state.selectedNode.hasOwnProperty('link') && - // ( - // newMessage.graph.nodes.filter((node) => ( - // node.id === this.state.selectedNode.link.source_id || - // node.id === this.state.selectedNode.link.target_id - // )).length < 2 || - // newMessage.graph.links.filter((link) => ( - // link.origin.source_id === this.state.selectedNode.link.source_id && - // link.origin.target_id === this.state.selectedNode.link.target_id && - // JSON.stringify(link.origin.type) === JSON.stringify(this.state.selectedNode.link.type) - // )).length === 0 - // ) - // ) { - // delete this.state.selectedNode.link; - // this._updateDimensions(); - // } - // } - // }) } /** * Handle a click on a graph node. @@ -2170,9 +1737,87 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ _fgAdjustCharge (charge) { if (this.fg) { this.fg.d3Force ('charge').strength(charge); - this.fg.refresh (); + if (this.fg.refresh) this.fg.refresh (); } } + /** + * Render the Answer Viewer modal. + * + */ + _renderAnswerViewer (customProps) { + return ( + this._setActiveModal(null)} + {...customProps}/> + ); + } + /** + * Renders the App banner/header element. + * + */ + _renderBanner() { + return ( +
+
+

TranQL

+ + + { + !this.state.toolbarEnabled && + + } + { + !this.state.loading ? ( + + ) : ( + + ) + } +
+ this._setActiveModal("SettingsModal")} /> + +
+
+
+ ); + } + /** + * Renders the codemirror query element. + * + * @private + */ + _renderCodemirror() { + return ( + {this._codemirror = editor;}} + className="query-code" + value={this.state.code} + onBeforeChange={(editor, data, code) => this._updateCode(code)} + onChange={(editor) => { + if (this.embedded) this._debouncedExecuteQuery(); + }} + options={this.state.codeMirrorOptions} + autoFocus={true} /> + ); + } /** * Render the force directed graph in either 2D or 3D rendering modes. * @param {Object} data - Data containing nodes and links that is used to render the force graph @@ -2188,6 +1833,8 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ height:this.state.graphHeight, linkAutoColorBy:"type", nodeAutoColorBy:"type", + linkDirectionalArrowLength: 3.5, + linkDirectionalArrowRelPos: 1, d3AlphaDecay:0.2, strokeWidth:10, linkWidth:2, @@ -2275,77 +1922,12 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ _renderForceGraphVR (data, props) { return } - /** - * Render the toolbar buttons - * - * @private - */ - _getButtons() { - return ( - <> - - this._findTool.current.toggleShow()}/> - this._setActiveModal('HelpModal')}/> - this._setActiveModal('CachedQueriesModal')}/> - this._setActiveModal('ImportExportModal')}/> - this._setActiveModal('SettingsModal')} /> - { - this.state.tableViewerComponents.tableViewerCompActive ? this._closeTableViewer() : this._openTableViewer("tableViewerCompActive"); - }}/> - { - // Perfectly functional but does not provide enough functionality as of now to warrant its presence - /* this.setState ({ showTypeChart : true })} />*/ - // The tool works as intended but the annotator does not yet. - /* this._annotateGraph ()}/>*/ - } - - ); - } - /** - * Render the toolbar tools - * - * @private - */ - _getTools() { - return ( - <> - this._setNavMode(bool)}> - - - this._setSelectMode(bool)}> - - - this._setHighlightTypesMode(bool)}> - - - this._setConnectionExaminerActive(bool)}> - - - { - this.setState({ browseNodeActive : bool }); - if (!bool) { - this._browseNodeInterface.current.hide(); - } - }}> - - - - ); + _handleShowAnswerViewer () { + if (this.state.message && this.state.message.message) { + this._setActiveModal("AnswerViewerModal"); + } } + /* _handleShowAnswerViewer () { console.log (this._answerViewer); if (this.state.message) { @@ -2384,6 +1966,7 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ }); } } + */ /** * Handles error messages * @@ -2394,6 +1977,7 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ * @private */ _handleMessageDialog (title, message, details) { + if (!this._messageDialog.current) return; if (!Array.isArray(message)) { message = [{ message: message, @@ -2417,6 +2001,9 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ * @private */ _handleUpdateSettings (e) { + // We don't want settings to be modified when the app is embedded. + if (this.embedded) return; + var targetName = e.currentTarget.name; console.log ("--update settings: " + targetName); if (targetName === 'dynamicIdResolution') { @@ -2427,7 +2014,7 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ forceGraphOpts.enableNodeDrag = e.currentTarget.checked; this.setState({ forceGraphOpts }); localStorage.setItem('forceGraphOpts', JSON.stringify (forceGraphOpts)); - this.fg.refresh(); + if (this.fg.refresh) this.fg.refresh(); } else if (targetName === 'useToolCursor') { this.setState ({ useToolCursor : e.currentTarget.checked }); localStorage.setItem (targetName, JSON.stringify (e.currentTarget.checked)); @@ -2468,20 +2055,6 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ this._translateGraph(msg,false,schemaActive); } - _renderCheckboxes(stateKey) { - return this.state[stateKey].map((checkbox, index) => -
- -
- ); - } /** * * @private @@ -2624,9 +2197,9 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ ); } _closeObjectViewer() { - if (this.state.objectViewerEnabled) { + if (this.state.objectViewerEnabled && this._graphSplitPane.current) { let width = this._graphSplitPane.current.splitPane.offsetWidth; - this._graphSplitPane.current.setState({ draggedSize : width, pane1Size : width , position : width }); + this._graphSplitPane.current.setState({ draggedSize : width, pane1Size : width , position : width }); this._updateGraphSize(width); this.setState({ objectViewerSelection : null }); } @@ -2698,6 +2271,7 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ * @private */ _updateDimensions() { + if (!this._graphSplitPane.current) return; let prevWinWidth = this._graphSplitPane.current.state.prevWinWidth; let prevWinHeight = this._tableSplitPane.current.state.prevWinHeight; if (prevWinWidth === undefined) prevWinWidth = window.innerWidth; @@ -2834,175 +2408,6 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ ); } - /** - * Render the modal settings dialog. - * - * @private - */ - _renderSettingsModal () { - return ( - <> - this._setActiveModal(null)}> - - Settings - - - - -
-
- Visualization Mode and Graph Colorization -
-
- 3D   -
-
- 2D   -
-
- VR    -
-
- Color the graph. -
-
-
- -
- -
-
- Use Cache -
- Use cached responses. -
-
-
- -
-
- - { - /* Really *bad* feature... -
- -
- Cursor -
- Use active tool as cursor. -
-
- */ - } - -
- -
- Node Drag -
- Allow node dragging in the force graph (requires refresh). -
-
- -
- -
- Dynamic ID Resolution -
- Enables dynamic id lookup of curies. -
-
-
- -
- Link Weight Range Min: [{this.state.linkWeightRange[0] / 100}] Max: [{this.state.linkWeightRange[1] / 100}]
- Include only links with a weight in this range. - - - Node Connectivity Range Min: [{this.state.nodeDegreeRange[0]}] Max: [{this.state.nodeDegreeRange[1]}] (reset on load)
- Include only nodes with a number of connections in this range. - -
- Force Graph Charge
- Set the charge force on the active graph
-
- {if (e.keyCode === 13) e.preventDefault();}} - /> - -
- - Legend Display Limit ({this.state.schemaViewerActive && this.state.schemaViewerEnabled ? "schema" : "graph"})
-
- Set the number of nodes that the legend displays: - (this._onLegendDisplayLimitChange('nodes',e))} - onKeyDown={(e) => {if (e.keyCode === 13) e.preventDefault();}} - /> - Set the number of links that the legend displays: - (this._onLegendDisplayLimitChange('links',e))} - onKeyDown={(e) => {if (e.keyCode === 13) e.preventDefault();}} - /> - - - - {/*
*/} -
- - -
- Database Sources Filter graph edges by source database. Deselecting a database deletes all associations from that source. -
{this._renderCheckboxes('dataSources')}
-
- Reasoner Sources Filter graph elements by source reasoner. Deselecting a reasoner deletes all associations from that source. -
{this._renderCheckboxes('reasonerSources')}
-
- - - - - - - - ); - } /** * Sets the active modal * @@ -3019,13 +2424,71 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ // `ignoreQueryPrefix` automatically truncates the leading question mark within a query string in order to prevent it from being interpreted as part of it const params = qs.parse(window.location.search, { ignoreQueryPrefix : true }); + /** + * Parse params into vars: + * - q|query + * - embedded: full | (graph|simple|true|"") | (false|) + * - answer_viewer: (true|"") | (false|) <- DISABLED + * - use_last_view: (true|"") | (false|) + */ const tranqlQuery = params.q || params.query; + const { + embed: embedded, + use_last_view: useLastUsedView + // answer_viewer: showAnswerViewer + } = params; + if (tranqlQuery !== undefined) { - this._updateCode(tranqlQuery); - this.setState({}, () => { - this._executeQuery(); + this._queryExecOnLoad = tranqlQuery; + } + // i.e. "?embed=true" or "?embed" + if (embedded !== undefined && embedded !== null) { + // Note that `this.embedded` is a derived property of embedMode (without a setter) so there is no need to set it here. + switch (embedded.toLowerCase()) { + case "full": + this.embedMode = EmbedMode.FULL; + break; + case "graph": + case "simple": + case "true": + case "": + this.embedMode = EmbedMode.SIMPLE; + break; + case "false": + break; + default: + console.error("Unknown embed type:", embedded); + this.embedMode = EmbedMode.SIMPLE; + break; + } + // Disable the cache when embedded + this.setState({ + useCache : false + }); + // Maintain localStorage under window.embeddedLocalStorage such that + // the App can only use localStorage while embedded when explcitly intended. + window.embeddedLocalStorage = window.localStorage; + const _localStorage = window.localStorage; + // localStorage is a property of window with a getter but no setter, so it has to be fully deleted before setting it to a new value. + delete window.localStorage; + // Create a skeleton version of localStorage, in which persistent methods are overwritten while preserving the full API. + const skeletonStorage = Object.assign({ + setItem: () => {}, + removeItem: () => {}, + key: () => {}, + getItem: () => {}, + removeItem: () => {}, + length: 0 + }, _localStorage); + // Create the new localStorage property with the skeleton storage. + Object.defineProperty(window, "localStorage", { + get: () => skeletonStorage }); } + // Note: this option is only intended for use within an embedded page, since it doesn't make much sense in a normal context. + // Note2: *DISABLED* + // this.setState({ showAnswerViewerOnLoad: showAnswerViewer === "true" || showAnswerViewer === "" }); + this.setState({ useLastUsedView: useLastUsedView === "true" || useLastUsedView === "" }); } /** * Perform any necessary cleanup before being unmounted @@ -3048,12 +2511,18 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ this._updateDimensionsFunc = this._updateDimensions; window.addEventListener('resize', this._updateDimensionsFunc); - // Hydrate persistent state from local storage - this._hydrateState (); - // Handle query string parameters (mini-router implementation) // & hydrate state accordingly this._handleQueryString (); + // Hydrate persistent state from local storage + if (!this.embedded) { + this._hydrateState (); + // This is a class field, not a state variable, so it needs to be manually loaded. It also has to update codemirror state, + // which is another reason it needs to be manualy handled. + this._updateResolvedIdentifiers(JSON.parse(localStorage.getItem('autocompleteIdentifiers')) || {}, -1); + // Make sure that the code loaded from localStorage goes through `_updateCode` + this.setState({}, () => this._updateCode(this.state.code)); + } // Populate the cache viewer this._updateCacheViewer (); @@ -3067,6 +2536,14 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ this._updateGraphSize(document.body.offsetWidth); + if (this._queryExecOnLoad !== undefined) { + // After initial loading is complete, execute the query specified in the URL's query string if there is one. + this._updateCode(this._queryExecOnLoad); + this.setState({}, () => { + this._executeQuery(); + }); + } + this.setState({}, () => { if (this.fg) { if (this.state.visMode === "2D") { @@ -3087,17 +2564,38 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ } render() { - // Render it. + // Render just the graph if the app is being embedded + if (this.embedded) return {this.fg = el; this._updateFg ()}}/>; + // Render the app return (
- {this._renderSettingsModal () } + this._setActiveModal(null)} + appState={this.state} + + toggleCheckbox={this._toggleCheckbox} + + handleUpdateSettings={this._handleUpdateSettings} + clearCache={this._clearCache} + onLinkWeightRangeChange={this._onLinkWeightRangeChange} + onNodeDegreeRangeChange={this._onNodeDegreeRangeChange} + onChargeChange={this._onChargeChange} + onLegendNodeLimitChange={(e) => (this._onLegendDisplayLimitChange('nodes',e))} + onLegendLinkLimitChange={(e) => (this._onLegendDisplayLimitChange('links',e))}/> {this._renderTypeChart ()} - + buttons={this._toolbar.current._getButtons()} + tools={this._toolbar.current._getTools()} + />} chemical_substance->gene->biological_ title={"Cached queries"+(!this.state.useCache?' (cache disabled)':'')} tools={this.state.cachedQueriesModalTools} emptyText=
You currently have no cached queries.
/> - + {/* */} + {this._renderAnswerViewer()} -
-
-

TranQL

- - - { - !this.state.toolbarEnabled && - - } - { - !this.state.loading ? ( - - ) : ( - - ) - } -
- this._setActiveModal("SettingsModal")} /> - -
-
-
-
+ {this._renderBanner()} +
{ this.state.showCodeMirror ? ( @@ -3219,17 +2679,7 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ setActiveModal={this._setActiveModal} cache={this._cache} setCode={this._updateCode}/>*/} - {this._codemirror = editor;}} - className="query-code" - value={this.state.code} - onBeforeChange={(editor, data, code) => this._updateCode(code)} - onChange={(editor) => { - if (editor.state.completionActive) { - this._codeAutoComplete(); - } - }} - options={this.state.codeMirrorOptions} - autoFocus={true} /> + {this._renderCodemirror()} ) : ( @@ -3304,13 +2754,29 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_
{ this.state.toolbarEnabled && ( - + this._findTool.current.toggleShow()} + helpToolCallback={() => this._setActiveModal('HelpModal')} + cachedQueriesToolCallback={() => this._setActiveModal('CachedQueriesModal')} + importExportToolCallback={() => this._setActiveModal('ImportExportModal')} + settingsToolCallback={() => this._setActiveModal('SettingsModal')} + tableViewerToolCallback={() => { + this.state.tableViewerComponents.tableViewerCompActive ? this._closeTableViewer() : this._openTableViewer("tableViewerCompActive"); + }} + // Tool callbacks + navigateToolCallback={this._setNavMode} + selectToolCallback={this._setSelectMode} + highlightTypesToolCallback={this._setHighlightTypesMode} + examineConnectionToolCallback={this._setConnectionExaminerActive} + browseNodeToolCallback={(bool) => { + this.setState({ browseNodeActive : bool }); + if (!bool) { + this._browseNodeInterface.current.hide(); + } + }}/> ) }
@@ -3364,32 +2830,6 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ resultMouseLeave={(result) => { this._highlightType(result.id, false, false, undefined, "id") }}/> - {/*{ - const isNode = function(element) { - return !element.origin.hasOwnProperty('source_id') && !element.origin.hasOwnProperty('target_id'); - } - if (values.length > 1) { - // Grouped syntax which isn't really compatible - just use the link. - values = values.filter((element) => !isNode(element)); - } - values.forEach((element) => { - if (isNode(element)) { - this._handleNodeClick(element); - } - else { - this._handleLinkClick(element, true); - } - }); - }} - resultMouseEnter={(values)=>{ - values.forEach((element) => this._highlightType(element.id,0xff0000,false,undefined,'id'))} - } - resultMouseLeave={(values)=>{ - values.forEach((element) => this._highlightType(element.id,false,false,undefined,'id'))} - } - ref={this._findTool}/>*/} -
{/*
*/} @@ -3435,57 +2875,18 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ }
- { - const graph = this.state.schemaViewerActive && this.state.schemaViewerEnabled ? this.state.schema : this.state.graph; - // Table viewer generates a tab for every property in the object provided, but we only want nodes and links as our tabs. - return { - nodes : graph.nodes.map((node) => node.origin), - links : graph.links.map((link) => link.origin) - }; - })()} - defaultTableAttributes={{ - "nodes" : [ - "name", - "id", - "type" - ], - "links" : [ - "source_id", - "target_id", - "type", - "source_database" - ] - }} - tableProps={{ - getTdProps: (tableState, rowInfo, columnInfo, tableInstance) => { - const get_element = () => { - const is_node = this._tableViewer.current._tabs.current.props.activeKey === "0"; - const graph = this.state.schemaViewerActive && this.state.schemaViewerEnabled ? this.state.schema : this.state.graph; - const elements = is_node ? graph.nodes : graph.links; - const origin = rowInfo.original; - - const element = elements.filter((element) => element.origin.id === origin.id)[0]; - - const click_method = (is_node ? () => { - this._handleNodeClick(element); - } : () => { - this._handleLinkClick(element, true); - }); - - return { - click : click_method - }; - } - return { - onClick: () => { - get_element().click(); - } - }; - } - }}/> +
@@ -3495,4 +2896,14 @@ SELECT population_of_individual_organisms->chemical_substance->gene->biological_ } } +if(process.env.NODE_ENV === 'production') { + // behind proxy this would treat the path used to load index.html as root + // Slice the query string off of the URL. + const fullURL = window.location.origin + window.location.pathname; + // Slice the hanging "/" off of the end of the URL if it is present. + App.prototype.tranqlURL = fullURL.endsWith("/") ? fullURL.slice(0, -1) : fullURL; +} else { + App.prototype.tranqlURL = "http://localhost:8001"; +} + export default App; diff --git a/src/tranql/web/src/App.test.js b/src/tranql/web/src/App.test.js deleted file mode 100644 index a754b20..0000000 --- a/src/tranql/web/src/App.test.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import App from './App'; - -it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render(, div); - ReactDOM.unmountComponentAtNode(div); -}); diff --git a/src/tranql/web/src/GammaViewer.js b/src/tranql/web/src/GammaViewer.js new file mode 100644 index 0000000..7b62512 --- /dev/null +++ b/src/tranql/web/src/GammaViewer.js @@ -0,0 +1,36 @@ +import React from 'react'; +import { Modal } from 'react-bootstrap'; +import GammaViewerComponent from 'gamma-viewer-web'; + +console.log(React.version); + +export default function GammaViewer({ data, show, onHide, asModal=true }) { + const gammaData = JSON.parse(JSON.stringify(data)); + const validMessage = gammaData && gammaData.message; + // if (validMessage) { + // Object.values(gammaData.message.query_graph.nodes).forEach((n) => { + // if (!Array.isArray(n.category)) n.category = [n.category]; + // if (!n.hasOwnProperty("id")) n.id = ["foobar"]; + // if (!n.hasOwnProperty("is_set")) n.is_set = false; + // }); + // } + function renderGammaViewer() { + if (validMessage) return ( +
+ +
+ ); + return

Invalid message

; + } + if (!asModal) return renderGammaViewer(); + return ( + + + Answer Viewer + + + {renderGammaViewer()} + + + ); +} \ No newline at end of file diff --git a/src/tranql/web/src/SettingsModal.js b/src/tranql/web/src/SettingsModal.js new file mode 100644 index 0000000..291f78c --- /dev/null +++ b/src/tranql/web/src/SettingsModal.js @@ -0,0 +1,183 @@ +import React, { Component } from 'react'; +import { Modal, Tabs, Tab, Form } from 'react-bootstrap'; +import { Button } from 'reactstrap'; +import { Range } from 'rc-slider'; + +export default class SettingsModal extends Component { + _renderCheckboxes(stateKey) { + return this.props.appState[stateKey].map((checkbox, index) => ( +
+ +
+ )); + } + render() { + return ( + + + Settings + + + + +
+
+ Visualization Mode and Graph Colorization +
+
+ 3D   +
+
+ 2D   +
+
+ VR    +
+
+ Color the graph. +
+
+
+ +
+ +
+
+ Use Cache +
+ Use cached responses. +
+
+
+ +
+
+ + { + /* Really *bad* feature... +
+ +
+ Cursor +
+ Use active tool as cursor. +
+
+ */ + } + +
+ +
+ Node Drag +
+ Allow node dragging in the force graph (requires refresh). +
+
+ +
+ +
+ Dynamic ID Resolution +
+ Enables dynamic id lookup of curies. +
+
+
+ +
+ Link Weight Range Min: [{this.props.appState.linkWeightRange[0] / 100}] Max: [{this.props.appState.linkWeightRange[1] / 100}]
+ Include only links with a weight in this range. + + + Node Connectivity Range Min: [{this.props.appState.nodeDegreeRange[0]}] Max: [{this.props.appState.nodeDegreeRange[1]}] (reset on load)
+ Include only nodes with a number of connections in this range. + +
+ Force Graph Charge
+ Set the charge force on the active graph
+
+ {if (e.keyCode === 13) e.preventDefault();}} + /> + +
+ + Legend Display Limit ({this.props.appState.schemaViewerActive && this.props.appState.schemaViewerEnabled ? "schema" : "graph"})
+
+ Set the number of nodes that the legend displays: + {if (e.keyCode === 13) e.preventDefault();}} + /> + Set the number of links that the legend displays: + {if (e.keyCode === 13) e.preventDefault();}} + /> + + + + {/*
*/} +
+ + +
+ Database Sources Filter graph edges by source database. Deselecting a database deletes all associations from that source. +
{this._renderCheckboxes('dataSources')}
+
+ Reasoner Sources Filter graph elements by source reasoner. Deselecting a reasoner deletes all associations from that source. +
{this._renderCheckboxes('reasonerSources')}
+
+ + + + + + + ); + } +} \ No newline at end of file diff --git a/src/tranql/web/src/TableViewer.js b/src/tranql/web/src/TableViewer.js index d89c110..56da7be 100644 --- a/src/tranql/web/src/TableViewer.js +++ b/src/tranql/web/src/TableViewer.js @@ -38,10 +38,10 @@ export default class TableViewer extends Component { this.state = { tableFilterView : false, tableAttributes : this.props.defaultTableAttributes, - filterFilter : '' + filterFilter : '', + activeTab: undefined }; - this._tabs = React.createRef(); this._filterInputFilter = React.createRef(); this.resetAttributes = this.resetAttributes.bind(this); @@ -51,7 +51,11 @@ export default class TableViewer extends Component { this.setState({ tableAttributes : this.props.defaultTableAttributes }); } _getActiveTabName() { - return this._tabs.current.props.activeKey; + // return this._tabs.current.props.activeKey; + return this.state.activeTab; + } + componentDidMount() { + this.setState({ activeTab: Object.keys(this.props.data)[0] }); } render() { if (!this.props.tableView) return null; @@ -82,7 +86,7 @@ export default class TableViewer extends Component {
- + this.setState({activeTab: key})}> { (() => { const elementTypes = Object.keys(this.props.data); @@ -188,3 +192,61 @@ export default class TableViewer extends Component { ); } } + +export class AppTableViewer extends Component { + render() { + return ( + { + const graph = this.props.appState.schemaViewerActive && this.props.appState.schemaViewerEnabled ? this.props.appState.schema : this.props.appState.graph; + // Table viewer generates a tab for every property in the object provided, but we only want nodes and links as our tabs. + return { + nodes : graph.nodes.map((node) => node.origin), + links : graph.links.map((link) => link.origin) + }; + })()} + defaultTableAttributes={{ + "nodes" : [ + "name", + "id", + "type" + ], + "links" : [ + "source_id", + "target_id", + "type", + "source_database" + ] + }} + tableProps={{ + getTdProps: (tableState, rowInfo, columnInfo, tableInstance) => { + const getElement = () => { + const isNode = this.props.tableViewer.current._getActiveTabName() === "nodes"; + const graph = this.props.appState.schemaViewerActive && this.props.appState.schemaViewerEnabled ? this.props.appState.schema : this.props.appState.graph; + const elements = isNode ? graph.nodes : graph.links; + const origin = rowInfo.original; + + const element = elements.filter((element) => element.origin.id === origin.id)[0]; + + const clickMethod = (isNode ? () => { + this.props.handleNodeClick(element); + } : () => { + this.props.handleLinkClick(element, true); + }); + + return { + click : clickMethod + }; + } + return { + onClick: () => { + getElement().click(); + } + }; + } + }}/> + ) + } +} \ No newline at end of file diff --git a/src/tranql/web/src/Toolbar.js b/src/tranql/web/src/Toolbar.js index 4f7f497..bd6443b 100644 --- a/src/tranql/web/src/Toolbar.js +++ b/src/tranql/web/src/Toolbar.js @@ -1,5 +1,11 @@ import React, { Component } from 'react'; import ReactTooltip from 'react-tooltip'; +import { + FaArrowsAlt, FaMousePointer, FaHighlighter, FaEye, FaPlayCircle, + FaSearch, FaQuestionCircle, FaDatabase, FaFolderOpen, FaCog, + FaTable +} from 'react-icons/fa'; +import { IoMdBrowsers } from 'react-icons/io'; import { CSSStringtoRGB } from './Util.js'; import './Toolbar.css'; @@ -487,3 +493,70 @@ export class Toolbar extends Component { } } + +export class AppToolbar extends Component { + _getTools() { + return ( + <> + + + + + + + + + + + + + + + + + ); + } + _getButtons() { + return ( + <> + + + + + + + + { + // Perfectly functional but does not provide enough functionality as of now to warrant its presence + /* this.setState ({ showTypeChart : true })} />*/ + // The tool works as intended but the annotator does not yet. + /* this._annotateGraph ()}/>*/ + } + + ); + } + render() { + return ( + + ); + } +} \ No newline at end of file diff --git a/src/tranql/web/src/TranQLEmbedded.js b/src/tranql/web/src/TranQLEmbedded.js new file mode 100644 index 0000000..9a4407e --- /dev/null +++ b/src/tranql/web/src/TranQLEmbedded.js @@ -0,0 +1,98 @@ +import React, { useState } from 'react'; +import { Button } from 'reactstrap'; +import { SizeMe } from 'react-sizeme'; +import { GridLoader } from 'react-spinners'; +import { FaFrown, FaPlayCircle } from 'react-icons/fa'; +import { css } from '@emotion/core'; + +const spinnerStyleOverride = css` + display: block; + margin: 4 auto; + border-color: red; +`; + +/** + * Used to determine embedding behavior. Set based on query string param. + * + * @enum + * @see {App._handleQueryString} + */ + export const EmbedMode = Object.freeze({ + NONE: 0, + FULL: 1, + SIMPLE: 2 + }); + +export default function TranQLEmbedded({ embedMode, useLastUsedView, graphLoading, graph, renderForceGraph, renderCodemirror, renderAnswerViewer, graphRefCallback }) { + const defaultShowAnswerViewer = window.embeddedLocalStorage.getItem("defaultUseAnswerViewer") === "true"; + // If using the last used view, load that view from localStorage. Else, show the force graph by default. + const [answerViewer, useAnswerViewer] = useState(useLastUsedView ? defaultShowAnswerViewer : false); + + const toggleAnswerViewer = () => { + const newValue = !answerViewer; + useAnswerViewer(newValue); + // localStorage is disabled on embedded mode. Need to use embeddedLocalStorage to write/read. + window.embeddedLocalStorage.setItem('defaultUseAnswerViewer', JSON.stringify(newValue)); + } + const renderBody = () => { + if (answerViewer) return ( +
+ {renderAnswerViewer({asModal: false})} +
+ ); + else return ( + + { + ({size}) => + renderForceGraph ( + graph, { + ref: graphRefCallback, + height: size.height + } + ) + } + + ); + } + + return ( +
+ { + embedMode === EmbedMode.FULL && ( + <> + {/* {this._renderBanner()} */} + {renderCodemirror()} + + ) + } +
+ { + graphLoading ? ( + + ) : ( + graph.links.length === 0 ? ( +
+ + The query returned no results. +
+ ) : ( + <> + {renderBody()} + + + ) + ) + } +
+ {/* */} +
+ ); +} \ No newline at end of file diff --git a/src/tranql/web/src/__tests__/App.test.js b/src/tranql/web/src/__tests__/App.test.js new file mode 100644 index 0000000..1914f75 --- /dev/null +++ b/src/tranql/web/src/__tests__/App.test.js @@ -0,0 +1,337 @@ +import React from 'react'; +import puppeteer from 'puppeteer'; +import ReactTestUtils from 'react-dom/test-utils'; +import App from '../App.js'; +import { node } from 'prop-types'; +import { BrowserMode, SLEEP_INTERVAL, WEBSITE_URL } from '../setupTests.js'; +import { mockEndpoint, Mocker, pageFinishesRequest, Mocker as MockHelper } from '../testUtil.js'; +const util = require("util"); + +const mocker = new MockHelper(); + +const DEBUGGING = args.browserMode === BrowserMode.DEBUG; + +/** + * Global Puppeteer variables + */ +let browser; +let graphPage; + +/** + * Global test-set variables (i.e., data derived from the website that it used in multiple sequential tests) + */ + +// This is set in the "graph loads" test, which intercepts the request to /tranql/query and sets it to the response/mock response. +let loadedForceGraph; +// Loaded in "schema loads" test. Same as loadedForceGraph but for schema. +let loadedSchemaGraph; + + +beforeAll(async () => { + browser = await puppeteer.launch({ + headless: args.browserMode === BrowserMode.HEADLESS, + // Intercepting requests causing CORS issues so this needs to be disabled. + args: [ + "--disable-web-security", + args.sandbox === false ? "--no-sandbox" : "" + ] + }); +}); + +async function getThreeElements(page) { + // Wait for window.scene to be set + await page.waitForFunction(`window.scene !== undefined`); + + // Not sure why, but ReactForceGraph sets window.scene to the scene currently being rendered by it. + // Or maybe this is happening somewhere deep in the app? It seems weird that ReactForceGraph would do this + // because it would suggest that you could only render one graph per page. + // Regardless, just use the scene variable for now because there doesn't seem to be a way to access the internal + // Three.js scene from just a DOM node, so you would have to set a global variable in the App anyways. + const elements = await page.evaluate(_getThreeElementsFromWindow); + return elements; +} +function _getThreeElementsFromWindow() { + // FromKapsule is the object that ReactForceGraph stores objects in. + const kapsule = window.scene.children.find((i) => i.constructor.name === "FromKapsule"); + // kapsule.children contains Mesh objects (each representing either a node or link). + // Their data is stored in the __data attribute. + // Their type is stored in the __graphObjType attribute ("node"|"link") + + // This is mutable and will reflect changes in Three.js scene. + return kapsule.children.map((element) => element.__data.origin); +} + +describe('App', async () => { + /** + * Base-line (control) test to verify that the app loads at all. + * The header is static and should always render unless something + * goes seriously wrong. + */ + test( + "init browser", + async () => { + if (!browser) browser = await puppeteer.launch({ + headless: args.browserMode === BrowserMode.HEADLESS, + // Intercepting requests causing CORS issues so this needs to be disabled. + args: [ + "--disable-web-security" + ] + }); + }, + 30000 + ); + test( + "header renders", + async () => { + const page = await browser.newPage(); + await page.goto(WEBSITE_URL); + await page.waitForSelector("#headerContainer"); + + const header = await page.$eval("#headerContainer > p", e => e.innerText); + expect(header).toEqual("TranQL"); + + }, + 30000 + ); + /** + * Verify that the app will successfully load and parse the schema. + */ + test( + "schema loads", + async () => { + // Create a new page for testing if the schema loads. + // Need to intercept requests to /tranql/schema. Wouldn't want + // the requests to have already gone through by the time the test is run. + const schemaPage = await browser.newPage(); + // Set the mocker to mock the schema endpoint. + await mocker.mock(schemaPage, "schema"); + await schemaPage.goto(WEBSITE_URL); + const settingsButton = await schemaPage.waitForSelector("#settingsToolbar"); + // The click event is attached to an svg, which doesn't support `click`, and for some reason + // will not trigger its handler from `dispatchEvent(new Event("click"))` either. + const btn = await schemaPage.$("#settingsToolbar"); + schemaPage.evaluate((btn) => { + var e = document.createEvent("HTMLEvents"); + e.initEvent("click", true, false); + btn.dispatchEvent(e); + }, btn); + const cacheButton = await schemaPage.$("#clearCache"); + schemaPage.evaluate((btn) => { + var e = document.createEvent("HTMLEvents"); + e.initEvent("click", true, false); + btn.dispatchEvent(e); + }, cacheButton); + // Wait for Dexie cache to clear + await schemaPage.waitForFunction(` + document.querySelector("#clearCache").innerText === "Clear the cache (0B)" + `); + await schemaPage.reload(); + + const res = await mocker.resolveResponse(schemaPage, "schema"); + loadedSchemaGraph = await res.json(); + + // Assert that the schema returns a proper message + expect(loadedSchemaGraph.schema).not.toEqual(undefined); + expect(loadedSchemaGraph.schema.knowledge_graph).not.toEqual(undefined); + + await schemaPage.waitForSelector(".Legend"); + // todo: Provide dummy graph to React by mocking requests + const legendNodes = await schemaPage.$eval(".graph-element-type-container > div > div", (el) => el.children.length); + if (DEBUGGING) await schemaPage.waitFor(SLEEP_INTERVAL); + expect(legendNodes).toBeGreaterThan(0); + }, + 600000 + ); + /** + * This is a test to ensure that autocompletion suggests load, display properly, and modify the code properly. + * It isn't rigorous because the actual grammar should be tested in the backend, not here. + * + * Note: since the frontend handles suggesting schema-aware completions, this test should be extended to test all suggestion functionalities + * such as schema-aware predicate completion, arrow completion, schema completion (i.e. `from` statements), etc. + * - This would involve making the main part the test reusable, so that various different `/tranql/parse_incomplete` + * responses could be tested. + * - Also would involve adding a mock helper most likely. + */ + + test( + "autocompletion works", + async () => { + const page = await browser.newPage(); + await mocker.mock(page, ["reasoner_urls", "parse_incomplete_single_select", "parse_incomplete_forward_select_complete"]); + await page.goto(WEBSITE_URL); + + async function testAutocomplete(query, cursorPos, mockName, expectedResults, resultingQuery) { + await page.evaluate(async (query, cursorPos) => { + const cmInstance = document.querySelector(".query-code > .CodeMirror").CodeMirror; + + cmInstance.setValue(query); + cmInstance.setCursor(cursorPos); + // Call the autocomplete function attached to "ctrl + space" shortcut. + cmInstance.options.extraKeys["Ctrl-Space"](); + }, query, cursorPos); + await mocker.resolveResponse(page, mockName); + await page.waitForFunction(async () => { + const cmInstance = document.querySelector(".query-code > .CodeMirror").CodeMirror; + return cmInstance.state.completionActive !== undefined && cmInstance.state.completionActive.data.list[0].displayText !== "Loading"; + }); + const completions = await page.evaluate(() => { + const cmInstance = document.querySelector(".query-code > .CodeMirror").CodeMirror; + return cmInstance.state.completionActive.data.list; + }); + const completionTexts = completions.map((completion) => completion.displayText); + expectedResults.forEach((result) => { + if (!completionTexts.includes(result)) throw new Error(`${JSON.stringify(completionTexts)}, ${result}`) + expect(completionTexts.includes(result)).toEqual(true); + }); + if (DEBUGGING) await page.waitFor(SLEEP_INTERVAL); + const cmValue = await page.evaluate(async () => { + const cmInstance = document.querySelector(".query-code > .CodeMirror").CodeMirror; + cmInstance.state.completionActive.widget.changeActive(0); + cmInstance.state.completionActive.widget.pick(); + + return cmInstance.getValue(); + }); + expect(cmValue).toEqual(util.format(resultingQuery, completionTexts[0])); + if (DEBUGGING) await page.waitFor(SLEEP_INTERVAL); + } + const tests = [ + [ + "select ", + {line: 0, ch: 7}, + "parse_incomplete_single_select", + // Expect every node in the schema to be suggested + loadedSchemaGraph.schema.knowledge_graph.nodes.map((n) => n[0]), + "select %s" + ], + [ + `select disease-> + from "redis:test" +where disease="asthma"`, + {line: 0, ch: 16}, + "parse_incomplete_forward_select_complete", + // Expect the target node of every edge with source "disease", i.e. every node with an edge with source "disease". + // Leave out self-referencing nodes (disease->disease) which aren't currently supported in the force graph and are removed. + loadedSchemaGraph.schema.knowledge_graph.edges.filter((e) => e[0] === "disease" && e[1] !== "disease").map((e) => e[1]), + `select disease->%s + from "redis:test" +where disease="asthma"`, + ], + /*[ + `select disease-[]->phenotypic_feature`, + {line: 0, ch: 16}, + "parse_incomplete_predicate_completion", + // Expect the predicates of all edges with source disease and target phenotypic_feature. + loadedSchemaGraph.schema.knowledge.graph.edges.filter((e) => e[0] === "disease" && e[1] === "phenotypic_feature").map((e) => e[2]), + "select disease-[%s]->phenotypic_feature" + ]*/ + ]; + for (let i=0; i { + graphPage = await browser.newPage(); + await mocker.mock(graphPage, "query_disease_phenotypic_feature_diabetes"); + await graphPage.goto(WEBSITE_URL); + await graphPage.waitForSelector(".query-code > .CodeMirror"); + await graphPage.evaluate(async () => { + // Right now, there is no specific mocking structure to /tranql/query, it will just return the same + // static mock graph for any query made when mocking is enabled. + const testQuery = `select disease->phenotypic_feature + from "redis:test" +where disease="diabetes"`; + const el = document.querySelector(".query-code > .CodeMirror"); + const cmInstance = el.CodeMirror; + cmInstance.setValue(testQuery); + }); + const runButton = await graphPage.waitForSelector("#runButton"); + runButton.click(); + + const res = await mocker.resolveResponse(graphPage, "query_disease_phenotypic_feature_diabetes") + loadedForceGraph = await res.json(); + + // Assert that the query returns a proper message + expect(loadedForceGraph.message).not.toEqual(undefined); + expect(loadedForceGraph.message.knowledge_graph).not.toEqual(undefined); + + const elements = await getThreeElements(graphPage); + + const renderedNodes = []; + const renderedEdges = []; + elements.forEach((el) => { + if (el.hasOwnProperty("source_id")) renderedEdges.push(el); + else renderedNodes.push(el.id); + }); + Object.keys(loadedForceGraph.message.knowledge_graph.nodes).forEach((nId) => { + expect(renderedNodes.includes(nId)).toEqual(true); + }); + Object.values(loadedForceGraph.message.knowledge_graph.edges).forEach((edge) => { + const graphHasEdge = renderedEdges.some((rEdge) => { + return ( + rEdge.subject === edge.subject && + rEdge.object === edge.object && + rEdge.predicate === edge.predicate + ); + }); + expect(graphHasEdge).toEqual(true); + }); + if (DEBUGGING) await graphPage.waitFor(SLEEP_INTERVAL); + }, + 600000 + ); + /* ################## + * # Graph subtests # + * ################## + */ + + /** + * Verify that the legend properly hides and unhides graph types + */ + test( + "legend works", + async () => { + const [nodeLabel, linkLabel] = await graphPage.$$(".Legend .graph-element-content label:last-child"); + const nodeType = await nodeLabel.$eval("b", (e) => e.innerText); + const linkType = await linkLabel.$eval("b", (e) => e.innerText); + + const originalLength = (await getThreeElements(graphPage)).length; + + // Hide these labels + await nodeLabel.click(); + await linkLabel.click(); + + + (await getThreeElements(graphPage)).forEach((element) => { + if (element.hasOwnProperty("source_id")) { + // Assert that no link in the new graph has the hidden type + expect(element.predicate).not.toEqual(linkType); + } else { + // Assert that no node in the new graph has the hidden type + expect(element.type.includes(nodeType)).toEqual(false); + } + }); + + if (DEBUGGING) await graphPage.waitFor(SLEEP_INTERVAL); + + // Show the labels again + await nodeLabel.click(); + await linkLabel.click(); + + // Assert that the graph has the same amount of nodes/links as before + expect((await getThreeElements(graphPage)).length).toEqual(originalLength); + + if (DEBUGGING) await graphPage.waitFor(SLEEP_INTERVAL); + + }, + 10000 + ); +}); + +afterAll(() => { + browser.close(); +}); \ No newline at end of file diff --git a/src/tranql/web/src/__tests__/mock/mock_graph.js b/src/tranql/web/src/__tests__/mock/mock_graph.js new file mode 100644 index 0000000..c099d03 --- /dev/null +++ b/src/tranql/web/src/__tests__/mock/mock_graph.js @@ -0,0 +1,13528 @@ +/* + * This is a test query returned from the /tranql/query endpoint with parameters: + * dynamic_id_resolution=true + * asynchronous=true + * and body: + * select disease->phenotypic_feature + * from "redis:test" + * where disease="diabetes" + */ +module.exports = { + "message": { + "knowledge_graph": { + "nodes": { + "MONDO:0014523": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "juvenile-onset diabetes mellitus-central and peripheral neurodegeneration syndrome", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0014523", + "OMIM:616192", + "ORPHANET:445062", + "UMLS:C4015436" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0014523" + }, + "HP:0002066": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Gait ataxia", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0002066", + "NCIT:C3834", + "UMLS:C0231691", + "UMLS:C0427154", + "UMLS:C0751836", + "UMLS:C0751837", + "MEDDRA:10003597", + "SNOMEDCT:250029005", + "SNOMEDCT:25136009", + "SNOMEDCT:69021004", + "MESH:D020234" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0002066" + }, + "MONDO:0010802": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "pancreatic hypoplasia-diabetes-congenital heart disease syndrome", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0010802", + "DOID:0111733", + "OMIM:600001", + "ORPHANET:2255", + "UMLS:C1838780", + "UMLS:C2931296", + "UMLS:C3888085", + "UMLS:C4012454", + "MESH:C536714", + "MESH:C564011", + "SNOMEDCT:722206009" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0010802" + }, + "HP:0009112": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Aplasia of the left hemidiaphragm", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0009112", + "UMLS:C4024593" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0009112" + }, + "MONDO:0030087": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "diabetes mellitus, permanent neonatal 2", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0030087", + "OMIM:618856" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0030087" + }, + "HP:0007334": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Bilateral tonic-clonic seizure with focal onset", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0007334", + "UMLS:C0877017", + "MEDDRA:10048674", + "MEDDRA:10056209" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0007334" + }, + "MONDO:0030088": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "diabetes mellitus, permanent neonatal 3", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0030088", + "OMIM:618857" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0030088" + }, + "HP:0002919": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Ketonuria", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0002919", + "NCIT:C122512", + "UMLS:C0162275", + "MEDDRA:10000419", + "MEDDRA:10023388", + "SNOMEDCT:274783007", + "SNOMEDCT:36815008" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0002919" + }, + "MONDO:0012436": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "neonatal diabetes mellitus with congenital hypothyroidism", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0012436", + "DOID:0060638", + "OMIM:610199", + "ORPHANET:79118", + "UMLS:C1857775", + "MESH:C565705" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0012436" + }, + "HP:0005280": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Depressed nasal bridge", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0005280", + "UMLS:C1836542", + "UMLS:C3550546", + "UMLS:C4280495" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0005280" + }, + "HP:0011629": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Total absence of the pericardium", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0011629", + "UMLS:C0345140", + "SNOMEDCT:253732001" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0011629" + }, + "HP:0001660": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Truncus arteriosus", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001660", + "UMLS:C0041206", + "UMLS:C4020867", + "SNOMEDCT:58140002", + "MESH:D014338" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001660" + }, + "MONDO:0031481": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "microcephaly, epilepsy, and diabetes syndrome 1", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0031481", + "OMIM:614231", + "ORPHANET:306558", + "UMLS:C3280240" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0031481" + }, + "HP:0003241": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "External genital hypoplasia", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0003241", + "UMLS:C1855333" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0003241" + }, + "MONDO:0022993": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "dipsogenic diabetes insipidus", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0022993", + "UMLS:C0268813", + "MESH:C548013", + "NCIT:C129735", + "SNOMEDCT:82800008" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0022993" + }, + "HP:0001959": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Polydipsia", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001959", + "NCIT:C50700", + "UMLS:C0085602", + "MEDDRA:10015608", + "MEDDRA:10036067", + "MEDDRA:10036070", + "MEDDRA:10043459", + "MEDDRA:10072057", + "SNOMEDCT:17173007", + "SNOMEDCT:267026004", + "MESH:D059606" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001959" + }, + "MONDO:0010581": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "diabetes insipidus, nephrogenic, X-linked", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0010581", + "OMIM:304800", + "UMLS:C1563705" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0010581" + }, + "MONDO:0015790": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "central diabetes insipidus", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0015790", + "OMIM:304900", + "ORPHANET:178029", + "UMLS:C0687720", + "MESH:D020790", + "MEDDRA:10049518", + "MEDDRA:10068587", + "MEDDRA:10073192", + "MEDDRA:10078568", + "HP:0000863" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0015790" + }, + "MONDO:0007451": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "diabetes insipidus, nephrogenic, autosomal", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0007451", + "OMIM:125800", + "UMLS:C1563706" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0007451" + }, + "HP:0032794": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Myoclonic seizure", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0032794" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0032794" + }, + "HP:0001537": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Umbilical hernia", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001537", + "NCIT:C118375", + "UMLS:C0019322", + "UMLS:C1306503", + "MEDDRA:10010658", + "MEDDRA:10015677", + "MEDDRA:10045458", + "MEDDRA:10045459", + "MEDDRA:10045461", + "SNOMEDCT:396347007", + "SNOMEDCT:49324006", + "SNOMEDCT:5867007" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001537" + }, + "HP:0000286": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Epicanthus", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000286", + "UMLS:C0678230" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000286" + }, + "HP:0012434": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Delayed social development", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0012434", + "UMLS:C4022906" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0012434" + }, + "MONDO:0030089": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "diabetes mellitus, permanent neonatal 4", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0030089", + "OMIM:618858" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0030089" + }, + "HP:0040217": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Elevated hemoglobin A1c", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0040217", + "UMLS:C4073162" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0040217" + }, + "MONDO:0100165": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "permanent neonatal diabetes mellitus 1", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0100165", + "OMIM:606176", + "UMLS:C1833102", + "MESH:C563424" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0100165" + }, + "MONDO:0012522": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "diabetes mellitus, transient neonatal, 3", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0012522", + "OMIM:610582", + "UMLS:C1864623", + "MESH:C566432", + "SNOMEDCT:609581006" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0012522" + }, + "HP:0011968": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Feeding difficulties", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0011968", + "NCIT:C50560", + "UMLS:C0232466", + "SNOMEDCT:78164000" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0011968" + }, + "MONDO:0010785": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "maternally-inherited diabetes and deafness", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0010785", + "OMIM:520000", + "ORPHANET:225", + "UMLS:C0342289", + "UMLS:C4330695", + "MESH:C536246", + "NCIT:C130996", + "NCIT:C131859", + "SNOMEDCT:237619009" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0010785" + }, + "HP:0001260": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Dysarthria", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001260", + "NCIT:C79549", + "UMLS:C0013362", + "UMLS:C0240952", + "UMLS:C0454597", + "UMLS:C0454598", + "UMLS:C1563666", + "MEDDRA:10013887", + "SNOMEDCT:229685007", + "SNOMEDCT:229686008", + "SNOMEDCT:8011004", + "MESH:D004401" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001260" + }, + "MONDO:0007450": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "neurohypophyseal diabetes insipidus", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0007450", + "DOID:12388", + "OMIM:125700", + "ORPHANET:30925", + "NCIT:C84933", + "SNOMEDCT:267393007", + "SNOMEDCT:45369008" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0007450" + }, + "HP:0003196": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Short nose", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0003196", + "UMLS:C0426414", + "UMLS:C1854114", + "SNOMEDCT:249310005" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0003196" + }, + "HP:0000218": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "High palate", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000218", + "UMLS:C0240635", + "MEDDRA:10020046", + "SNOMEDCT:27272007" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000218" + }, + "MONDO:0008242": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "photomyoclonus, diabetes mellitus, deafness, nephropathy, and cerebral dysfunction", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0008242", + "OMIM:172500", + "UMLS:C1809475", + "MESH:C538113", + "SNOMEDCT:237612000" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0008242" + }, + "HP:0007164": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Slowed slurred speech", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0007164", + "UMLS:C4024929" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0007164" + }, + "HP:0001289": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Confusion", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001289", + "NCIT:C37928", + "UMLS:C0009676", + "UMLS:C0152124", + "UMLS:C0233407", + "UMLS:C0683369", + "UMLS:C0751053", + "UMLS:C0813178", + "UMLS:C1306037", + "MEDDRA:10004541", + "MEDDRA:10010298", + "MEDDRA:10010300", + "MEDDRA:10010301", + "MEDDRA:10010304", + "MEDDRA:10010305", + "MEDDRA:10010767", + "MEDDRA:10013394", + "MEDDRA:10013395", + "MEDDRA:10027350", + "MEDDRA:10031087", + "MEDDRA:10037997", + "MEDDRA:10050462", + "MEDDRA:10072536", + "SNOMEDCT:191678001", + "SNOMEDCT:280943007", + "SNOMEDCT:286933003", + "SNOMEDCT:40917007", + "SNOMEDCT:44031002", + "SNOMEDCT:62476001", + "MESH:D003221" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001289" + }, + "MONDO:0007669": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "renal cysts and diabetes syndrome", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0007669", + "DOID:0111101", + "OMIM:137920", + "ORPHANET:93111", + "UMLS:C0431693", + "UMLS:C2959918", + "UMLS:CN206512", + "MESH:C535520", + "NCIT:C123018", + "SNOMEDCT:253864004", + "SNOMEDCT:446641003", + "SNOMEDCT:609572000" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0007669" + }, + "HP:0002120": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Cerebral cortical atrophy", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0002120", + "UMLS:C4551583" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0002120" + }, + "MONDO:0012192": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "permanent neonatal diabetes mellitus-pancreatic and cerebellar agenesis syndrome", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0012192", + "OMIM:609069", + "ORPHANET:65288", + "UMLS:C1836780", + "MESH:C563796" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0012192" + }, + "HP:0000377": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Abnormality of the pinna", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000377", + "UMLS:C0431483", + "UMLS:C0857379", + "MEDDRA:10025534", + "SNOMEDCT:253255002" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000377" + }, + "HP:0012207": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Reduced sperm motility", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0012207", + "UMLS:C0403823", + "UMLS:C4023002", + "MEDDRA:10067162", + "MEDDRA:10067166", + "MESH:D053627" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0012207" + }, + "HP:0001488": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Bilateral ptosis", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001488", + "UMLS:C1865916" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001488" + }, + "HP:0000252": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Microcephaly", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000252", + "UMLS:C4551563" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000252" + }, + "HP:0000325": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Triangular face", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000325", + "UMLS:C1835884" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000325" + }, + "HP:0030795": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Reduced C-peptide level", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0030795", + "UMLS:C4280764" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0030795" + }, + "HP:0002756": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Pathologic fracture", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0002756", + "NCIT:C3047", + "UMLS:C0016663", + "MEDDRA:10005965", + "MEDDRA:10017272", + "MEDDRA:10034141", + "MEDDRA:10034156", + "MEDDRA:10041728", + "MEDDRA:10041731", + "SNOMEDCT:22640007", + "SNOMEDCT:268029009", + "MESH:D005598" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0002756" + }, + "MONDO:0012348": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "maturity-onset diabetes of the young type 8", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0012348", + "DOID:0111105", + "OMIM:609812", + "UMLS:C1853297", + "MESH:C565225", + "SNOMEDCT:609575003" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0012348" + }, + "HP:0002027": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Abdominal pain", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0002027", + "NCIT:C26682", + "NCIT:C78320", + "NCIT:C78630", + "UMLS:C0000737", + "UMLS:C0221512", + "UMLS:C0232487", + "UMLS:C0235309", + "UMLS:C0687713", + "UMLS:C3888418", + "MEDDRA:10000059", + "MEDDRA:10000081", + "MEDDRA:10000085", + "MEDDRA:10000429", + "MEDDRA:10004226", + "MEDDRA:10013084", + "MEDDRA:10017814", + "MEDDRA:10017999", + "MEDDRA:10018000", + "MEDDRA:10018241", + "MEDDRA:10018796", + "MEDDRA:10033374", + "MEDDRA:10033402", + "MEDDRA:10033492", + "MEDDRA:10042076", + "MEDDRA:10042101", + "MEDDRA:10042112", + "MEDDRA:10042124", + "MEDDRA:10042126", + "MEDDRA:10045148", + "MEDDRA:10046318", + "MEDDRA:10064906", + "SNOMEDCT:162059005", + "SNOMEDCT:21522001", + "SNOMEDCT:271681002", + "SNOMEDCT:43364001", + "SNOMEDCT:73063007", + "MESH:D015746" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0002027" + }, + "HP:0003259": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Elevated serum creatinine", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0003259", + "UMLS:C0700225", + "MEDDRA:10011375", + "MEDDRA:10021678", + "MEDDRA:10037825", + "MEDDRA:10040233", + "SNOMEDCT:166717003" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0003259" + }, + "HP:0000316": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Hypertelorism", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000316", + "NCIT:C34715", + "UMLS:C0020534", + "MEDDRA:10020771", + "MEDDRA:10057862", + "SNOMEDCT:194021007", + "SNOMEDCT:22006008", + "MESH:D006972" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000316" + }, + "HP:0000343": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Long philtrum", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000343", + "UMLS:C1865014" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000343" + }, + "HP:0003698": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Difficulty standing", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0003698", + "UMLS:C0241237", + "MEDDRA:10050218", + "MEDDRA:10050256", + "SNOMEDCT:249902000" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0003698" + }, + "HP:0000021": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Megacystis", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000021", + "UMLS:C1855311" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000021" + }, + "HP:0002104": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Apnea", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0002104", + "NCIT:C26698", + "UMLS:C0003578", + "MEDDRA:10002972", + "MEDDRA:10002974", + "SNOMEDCT:1023001", + "SNOMEDCT:248583008", + "MESH:D001049" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0002104" + }, + "HP:0030423": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Splenic cyst", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0030423", + "UMLS:C0272407", + "MEDDRA:10051169", + "SNOMEDCT:79040006" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0030423" + }, + "HP:0002910": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Elevated hepatic transaminase", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0002910", + "NCIT:C113751", + "NCIT:C122516", + "UMLS:C0151766", + "UMLS:C0235996", + "UMLS:C0438237", + "UMLS:C0438717", + "UMLS:C0877359", + "UMLS:C1842003", + "UMLS:C1848701", + "UMLS:C2242708", + "MEDDRA:10000156", + "MEDDRA:10000158", + "MEDDRA:10013505", + "MEDDRA:10014481", + "MEDDRA:10019662", + "MEDDRA:10024409", + "MEDDRA:10024677", + "MEDDRA:10024690", + "MEDDRA:10024692", + "MEDDRA:10024693", + "MEDDRA:10024696", + "MEDDRA:10029701", + "MEDDRA:10037822", + "MEDDRA:10037823", + "MEDDRA:10044346", + "MEDDRA:10044347", + "MEDDRA:10048556", + "MEDDRA:10048557", + "MEDDRA:10054889", + "MEDDRA:10054969", + "MEDDRA:10060795", + "MEDDRA:10062685", + "MEDDRA:10068237", + "MEDDRA:10068238", + "MEDDRA:10077692", + "SNOMEDCT:166603001", + "SNOMEDCT:166643006" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0002910" + }, + "MONDO:0011073": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "diabetes mellitus, transient neonatal, 1", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0011073", + "OMIM:601410", + "UMLS:C1832386", + "MESH:C563322", + "NCIT:C131846", + "SNOMEDCT:609579009" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0011073" + }, + "HP:0001525": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Severe failure to thrive", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001525", + "UMLS:C1855514" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001525" + }, + "HP:0001290": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Generalized hypotonia", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001290", + "UMLS:C1858120" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001290" + }, + "HP:0000737": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Irritability", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000737", + "UMLS:C2700617" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000737" + }, + "HP:0100611": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Multiple glomerular cysts", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0100611", + "NCIT:C123015", + "UMLS:C4020705", + "UMLS:C4022013" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0100611" + }, + "HP:0003228": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Hypernatremia", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0003228", + "UMLS:C0020488", + "MEDDRA:10020679", + "MEDDRA:10020680", + "MEDDRA:10028672", + "MEDDRA:10041277", + "SNOMEDCT:286926003", + "SNOMEDCT:39355002", + "SNOMEDCT:771115008", + "MESH:D006955" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0003228" + }, + "HP:0002317": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Unsteady gait", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0002317", + "NCIT:C122478", + "UMLS:C0231686", + "UMLS:C0231689", + "UMLS:C0231693", + "UMLS:C0231694", + "UMLS:C0231695", + "UMLS:C0231696", + "UMLS:C0234996", + "UMLS:C0235000", + "UMLS:C0337210", + "UMLS:C0427128", + "UMLS:C0427169", + "UMLS:C0427177", + "UMLS:C0751829", + "UMLS:C0751830", + "UMLS:C0751831", + "UMLS:C0751832", + "MEDDRA:10016474", + "MEDDRA:10017575", + "MEDDRA:10017579", + "MEDDRA:10017580", + "MEDDRA:10017582", + "MEDDRA:10017583", + "MEDDRA:10017587", + "MEDDRA:10017588", + "MEDDRA:10017589", + "MEDDRA:10019474", + "MEDDRA:10022455", + "MEDDRA:10033840", + "MEDDRA:10046261", + "MEDDRA:10082328", + "SNOMEDCT:22631008", + "SNOMEDCT:250002000", + "SNOMEDCT:250049001", + "SNOMEDCT:250054005", + "SNOMEDCT:250991000119100", + "SNOMEDCT:271707009", + "SNOMEDCT:365884000", + "SNOMEDCT:394616008", + "SNOMEDCT:397776000", + "SNOMEDCT:398218008", + "SNOMEDCT:52751000", + "SNOMEDCT:55791005", + "SNOMEDCT:62334008", + "MESH:D020233" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0002317" + }, + "HP:0001324": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Muscle weakness", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001324", + "NCIT:C50658", + "UMLS:C0151786", + "MEDDRA:10028350", + "MEDDRA:10028351", + "MEDDRA:10028372", + "MEDDRA:10047867", + "SNOMEDCT:26544005", + "MESH:D018908" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001324" + }, + "HP:0010804": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Tented upper lip vermilion", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0010804", + "UMLS:C1839767", + "UMLS:C1850072", + "UMLS:C1853383" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0010804" + }, + "HP:0011682": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Perimembranous ventricular septal defect", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0011682", + "NCIT:C124556", + "UMLS:C0344925", + "SNOMEDCT:109428005" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0011682" + }, + "HP:0002205": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Recurrent respiratory infections", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0002205", + "UMLS:C3806482" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0002205" + }, + "HP:0001737": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Pancreatic cysts", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001737", + "NCIT:C3304", + "UMLS:C0030283", + "UMLS:C1860394", + "MEDDRA:10033580", + "MEDDRA:10033615", + "SNOMEDCT:31258000", + "MESH:D010181" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001737" + }, + "HP:0001939": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Abnormality of metabolism/homeostasis", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001939", + "UMLS:C4021768" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001939" + }, + "HP:0001263": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Global developmental delay", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001263", + "UMLS:C0557874", + "UMLS:C1864897", + "UMLS:C4020875", + "SNOMEDCT:224958001" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001263" + }, + "HP:0002013": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Vomiting", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0002013", + "NCIT:C3442", + "UMLS:C0042963", + "MEDDRA:10014542", + "MEDDRA:10047699", + "MEDDRA:10047700", + "MEDDRA:10047706", + "SNOMEDCT:249497008", + "SNOMEDCT:300359004", + "SNOMEDCT:422400008", + "MESH:D014839" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0002013" + }, + "HP:0001955": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Unexplained fevers", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001955", + "UMLS:C1844662" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001955" + }, + "HP:0000666": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Horizontal nystagmus", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000666", + "UMLS:C0271385", + "SNOMEDCT:81756001" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000666" + }, + "MONDO:0008843": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "atherosclerosis-deafness-diabetes-epilepsy-nephropathy syndrome", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0008843", + "OMIM:209010", + "ORPHANET:1192", + "UMLS:C1859596", + "UMLS:C2931125", + "MESH:C536178", + "MESH:C565928", + "SNOMEDCT:720519003" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0008843" + }, + "HP:0001317": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Abnormal cerebellum morphology", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001317", + "UMLS:C0742038", + "UMLS:C1866129" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001317" + }, + "HP:0010557": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Overlapping fingers", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0010557", + "UMLS:C1446712" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0010557" + }, + "HP:0001744": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Splenomegaly", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001744", + "NCIT:C3384", + "UMLS:C0038002", + "MEDDRA:10041637", + "MEDDRA:10041660", + "SNOMEDCT:16294009", + "MESH:D013163" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001744" + }, + "HP:0001284": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Areflexia", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001284", + "NCIT:C115420", + "UMLS:C0234146", + "UMLS:C0241772", + "UMLS:C0278124", + "MEDDRA:10000338", + "MEDDRA:10000339", + "MEDDRA:10003084", + "MEDDRA:10038241", + "MEDDRA:10038256", + "MEDDRA:10067218", + "SNOMEDCT:349006", + "SNOMEDCT:37280007" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001284" + }, + "HP:0001387": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Joint stiffness", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001387", + "NCIT:C79603", + "UMLS:C0162298", + "MEDDRA:10023230", + "MEDDRA:10042041", + "MEDDRA:10042047", + "MEDDRA:10059879", + "SNOMEDCT:84445001" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001387" + }, + "HP:0007366": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Atrophy/Degeneration affecting the brainstem", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0007366", + "UMLS:C4024900" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0007366" + }, + "MONDO:0005147": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "type 1 diabetes mellitus", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0005147", + "DOID:9744", + "EFO:0001359", + "UMLS:C0011854", + "UMLS:C0205734", + "UMLS:C0342302", + "UMLS:C3837958", + "MESH:D003922", + "MEDDRA:10006372", + "MEDDRA:10012595", + "MEDDRA:10012608", + "MEDDRA:10012609", + "MEDDRA:10021211", + "MEDDRA:10022482", + "MEDDRA:10022497", + "MEDDRA:10023253", + "MEDDRA:10023392", + "MEDDRA:10045228", + "MEDDRA:10067584", + "NCIT:C2986", + "SNOMEDCT:11530004", + "SNOMEDCT:46635009", + "KEGG.DISEASE:04940", + "HP:0100651" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0005147" + }, + "HP:0006476": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Abnormality of the pancreatic islet cells", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0006476", + "UMLS:C4025043" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0006476" + }, + "HP:0004415": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Pulmonary artery stenosis", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0004415", + "NCIT:C99030", + "UMLS:C0238397", + "MEDDRA:10037338", + "SNOMEDCT:95441000", + "MESH:D000071079" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0004415" + }, + "HP:0002714": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Downturned corners of mouth", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0002714", + "UMLS:C1866195" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0002714" + }, + "HP:0012642": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Cerebellar agenesis", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0012642", + "UMLS:C4022808" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0012642" + }, + "HP:0002594": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Pancreatic hypoplasia", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0002594", + "UMLS:C0266267", + "SNOMEDCT:68591005" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0002594" + }, + "HP:0000341": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Narrow forehead", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000341", + "UMLS:C1839758" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000341" + }, + "HP:0000445": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Wide nose", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000445", + "UMLS:C0426421", + "SNOMEDCT:249321001" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000445" + }, + "HP:0000078": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Abnormality of the genital system", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000078", + "UMLS:C0281966", + "UMLS:C0744356" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000078" + }, + "MONDO:0005148": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "type 2 diabetes mellitus", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0005148", + "DOID:9352", + "OMIM:125853", + "OMIM:147545", + "OMIM:168820", + "EFO:0001360", + "UMLS:C0011860", + "UMLS:C1840169", + "UMLS:C1852091", + "UMLS:C2674662", + "UMLS:C2674663", + "UMLS:C2674665", + "UMLS:C3149706", + "UMLS:C4017238", + "UMLS:CN244395", + "MESH:D003924", + "MEDDRA:10012611", + "MEDDRA:10012613", + "MEDDRA:10026947", + "MEDDRA:10029402", + "MEDDRA:10029505", + "MEDDRA:10045242", + "MEDDRA:10067585", + "NCIT:C26747", + "SNOMEDCT:44054006", + "ICD10:E11", + "KEGG.DISEASE:04930", + "HP:0005978" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0005148" + }, + "HP:0000855": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Insulin resistance", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000855", + "NCIT:C113101", + "UMLS:C0021655", + "UMLS:C0920563", + "MEDDRA:10022489", + "SNOMEDCT:48606007", + "SNOMEDCT:763325000", + "MESH:D007333" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000855" + }, + "HP:0002079": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Hypoplasia of the corpus callosum", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0002079", + "UMLS:C0344482", + "SNOMEDCT:204043002" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0002079" + }, + "MONDO:0010813": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "pancreatic beta cell agenesis with neonatal diabetes mellitus", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0010813", + "OMIM:600089", + "ORPHANET:28455", + "UMLS:C1838655", + "MESH:C538111" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0010813" + }, + "HP:0001438": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Abnormal abdomen morphology", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001438", + "UMLS:C4020869" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001438" + }, + "HP:0000768": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Pectus carinatum", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000768", + "UMLS:C0158731", + "UMLS:C3850012", + "UMLS:C3852958", + "MEDDRA:10008504", + "MEDDRA:10023325", + "MEDDRA:10023326", + "MEDDRA:10034203", + "MEDDRA:10034205", + "MEDDRA:10035010", + "MEDDRA:10035011", + "SNOMEDCT:205101001", + "MESH:D066166" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000768" + }, + "HP:0004322": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Short stature", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0004322", + "UMLS:C0349588", + "MEDDRA:10040600", + "MEDDRA:10041194", + "MEDDRA:10041960" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0004322" + }, + "MONDO:0014458": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Fanconi renotubular syndrome 4 with maturity-onset diabetes of the young", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0014458", + "DOID:0080760", + "OMIM:616026", + "UMLS:C4014962" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0014458" + }, + "HP:0001270": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Motor delay", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001270", + "NCIT:C116943", + "UMLS:C1854301", + "UMLS:C4020874", + "MEDDRA:10070302" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001270" + }, + "HP:0002187": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Intellectual disability, profound", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0002187", + "UMLS:C0020796", + "UMLS:C3161330", + "MEDDRA:10021218", + "MEDDRA:10036791", + "SNOMEDCT:31216003" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0002187" + }, + "HP:0000023": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Inguinal hernia", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000023", + "NCIT:C34690", + "NCIT:C34691", + "NCIT:C34692", + "UMLS:C0019294", + "UMLS:C0019295", + "UMLS:C0019296", + "MEDDRA:10013046", + "MEDDRA:10019917", + "MEDDRA:10021715", + "MEDDRA:10022016", + "MEDDRA:10022017", + "MEDDRA:10022019", + "MEDDRA:10029887", + "MEDDRA:10082132", + "SNOMEDCT:155738001", + "SNOMEDCT:196800008", + "SNOMEDCT:396232000", + "SNOMEDCT:65626001", + "SNOMEDCT:73147001", + "MESH:D006552" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000023" + }, + "HP:0000751": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Personality changes", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000751", + "NCIT:C78548", + "UMLS:C0240735", + "MEDDRA:10034719", + "SNOMEDCT:102943000", + "SNOMEDCT:192073007" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000751" + }, + "HP:0012594": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Moderate albuminuria", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0012594", + "UMLS:C0730345", + "UMLS:C1654921", + "MEDDRA:10027525", + "MEDDRA:10076047", + "SNOMEDCT:312975006" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0012594" + }, + "HP:0001133": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Constriction of peripheral visual field", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001133", + "UMLS:C0235095", + "MEDDRA:10047554", + "SNOMEDCT:1151008", + "SNOMEDCT:267628004" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001133" + }, + "HP:0031819": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Increased waist to hip ratio", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0031819", + "UMLS:C4703554" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0031819" + }, + "HP:0012448": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Delayed myelination", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0012448", + "UMLS:C1277241", + "MEDDRA:10076456", + "SNOMEDCT:135810007" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0012448" + }, + "HP:0003076": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Glycosuria", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0003076", + "UMLS:C0017979", + "MEDDRA:10018473", + "MEDDRA:10018478", + "MEDDRA:10042453", + "MEDDRA:10042455", + "MEDDRA:10042456", + "MEDDRA:10046678", + "MEDDRA:10068405", + "SNOMEDCT:45154002", + "MESH:D006029" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0003076" + }, + "HP:0001993": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Ketoacidosis", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001993", + "UMLS:C0220982", + "MEDDRA:10023379", + "SNOMEDCT:56051008" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001993" + }, + "HP:0007201": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Cerebral artery atherosclerosis", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0007201", + "UMLS:C4024924" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0007201" + }, + "HP:0001520": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Large for gestational age", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001520", + "NCIT:C87092", + "UMLS:C1848395", + "MEDDRA:10023789", + "MEDDRA:10023790" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001520" + }, + "MONDO:0013242": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "maturity-onset diabetes of the young type 11", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0013242", + "DOID:0111109", + "OMIM:613375", + "UMLS:C3150618", + "SNOMEDCT:609578001" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0013242" + }, + "HP:0025502": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Overweight", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0025502", + "NCIT:C94250", + "UMLS:C0497406", + "MEDDRA:10033307", + "SNOMEDCT:238131007", + "MESH:D050177" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0025502" + }, + "HP:0100543": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Cognitive impairment", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0100543", + "NCIT:C116921", + "NCIT:C27101", + "NCIT:C46083", + "UMLS:C0338656", + "UMLS:C0683322", + "MEDDRA:10009845", + "MEDDRA:10009846", + "MEDDRA:10048599", + "SNOMEDCT:386806002" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0100543" + }, + "HP:0003758": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Reduced subcutaneous adipose tissue", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0003758", + "UMLS:C0424631", + "UMLS:C1857657", + "SNOMEDCT:248316006" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0003758" + }, + "HP:0001319": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Neonatal hypotonia", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001319", + "NCIT:C116708", + "UMLS:C0270971", + "UMLS:C2267233", + "MEDDRA:10021119", + "MEDDRA:10051004", + "MEDDRA:10053153", + "SNOMEDCT:205294008", + "SNOMEDCT:33010005" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001319" + }, + "HP:0009879": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Simplified gyral pattern", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0009879", + "UMLS:C2749675" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0009879" + }, + "HP:0000260": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Wide anterior fontanel", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000260", + "UMLS:C1866134" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000260" + }, + "HP:0002059": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Cerebral atrophy", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0002059", + "UMLS:C0235946", + "UMLS:C4020860", + "MEDDRA:10008096", + "SNOMEDCT:278849000" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0002059" + }, + "HP:0000369": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Low-set ears", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000369", + "UMLS:C0239234", + "MEDDRA:10024929", + "SNOMEDCT:95515009" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000369" + }, + "HP:0000952": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Jaundice", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000952", + "NCIT:C3143", + "NCIT:C35299", + "UMLS:C0022346", + "UMLS:C0242183", + "MEDDRA:10021207", + "MEDDRA:10023126", + "MEDDRA:10023132", + "MEDDRA:10023135", + "MEDDRA:10023139", + "SNOMEDCT:18165001", + "SNOMEDCT:60217008", + "MESH:D007565" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000952" + }, + "HP:0002944": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Thoracolumbar scoliosis", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0002944", + "UMLS:C0749379" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0002944" + }, + "HP:0000580": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Pigmentary retinopathy", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000580", + "UMLS:C1328514", + "UMLS:C1833999", + "UMLS:C4551715", + "MEDDRA:10054856", + "MEDDRA:10062964" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000580" + }, + "HP:0000077": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Abnormality of the kidney", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000077", + "UMLS:C0266292", + "MEDDRA:10010606", + "SNOMEDCT:44513007" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000077" + }, + "HP:0008872": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Feeding difficulties in infancy", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0008872", + "UMLS:C2674608" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0008872" + }, + "HP:0001751": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Vestibular dysfunction", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001751", + "UMLS:C1843865" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001751" + }, + "HP:0002305": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Athetosis", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0002305", + "NCIT:C116574", + "UMLS:C0004158", + "UMLS:C1845265", + "MEDDRA:10003620", + "SNOMEDCT:44913001", + "SNOMEDCT:58593005", + "MESH:D001264" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0002305" + }, + "HP:0003537": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Hypouricemia", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0003537", + "UMLS:C0221333", + "MEDDRA:10021131", + "MEDDRA:10054432", + "SNOMEDCT:4519003" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0003537" + }, + "HP:0011573": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Hypoplastic tricuspid valve", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0011573", + "UMLS:C0265837", + "UMLS:C4023294", + "SNOMEDCT:43176009" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0011573" + }, + "HP:0000073": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Ureteral duplication", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000073", + "NCIT:C98917", + "UMLS:C0221365", + "MEDDRA:10013613", + "MEDDRA:10013871", + "MEDDRA:10046380", + "SNOMEDCT:49496001" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000073" + }, + "HP:0004442": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Sagittal craniosynostosis", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0004442", + "UMLS:C0432123", + "SNOMEDCT:109418001" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0004442" + }, + "HP:0008936": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Muscular hypotonia of the trunk", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0008936", + "UMLS:C1853743" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0008936" + }, + "HP:0012092": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Abnormality of exocrine pancreas physiology", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0012092", + "UMLS:C4021103" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0012092" + }, + "HP:0011182": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Interictal epileptiform activity", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0011182", + "UMLS:C4023491" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0011182" + }, + "HP:0001948": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Alkalosis", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001948", + "NCIT:C37909", + "UMLS:C0002063", + "MEDDRA:10001680", + "MEDDRA:10001687", + "SNOMEDCT:21420006", + "MESH:D000471" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001948" + }, + "HP:0000857": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Neonatal insulin-dependent diabetes mellitus", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000857", + "UMLS:C3278636" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000857" + }, + "HP:0001395": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Hepatic fibrosis", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001395", + "UMLS:C0239946", + "MEDDRA:10016648", + "MEDDRA:10019668", + "SNOMEDCT:62484002" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001395" + }, + "HP:0002069": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Bilateral tonic-clonic seizure", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0002069", + "NCIT:C50574", + "UMLS:C0494475", + "MEDDRA:10018100", + "MEDDRA:10018101", + "MEDDRA:10018661", + "MEDDRA:10018662", + "MEDDRA:10018663", + "MEDDRA:10039909", + "MEDDRA:10044000", + "MEDDRA:10062448", + "SNOMEDCT:54200006" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0002069" + }, + "HP:0000219": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Thin upper lip vermilion", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000219", + "UMLS:C1865017" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000219" + }, + "HP:0002521": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Hypsarrhythmia", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0002521", + "UMLS:C0684276", + "MEDDRA:10049264" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0002521" + }, + "HP:0000408": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Progressive sensorineural hearing impairment", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000408", + "UMLS:C1843156" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000408" + }, + "HP:0002149": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Hyperuricemia", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0002149", + "NCIT:C3961", + "UMLS:C0740394", + "MEDDRA:10020903", + "MEDDRA:10020904", + "MEDDRA:10020907", + "MEDDRA:10046507", + "SNOMEDCT:271198001", + "SNOMEDCT:35885006", + "MESH:D033461" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0002149" + }, + "HP:0002240": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Hepatomegaly", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0002240", + "NCIT:C3100", + "UMLS:C0019209", + "MEDDRA:10019842", + "MEDDRA:10024676", + "SNOMEDCT:80515008", + "MESH:D006529" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0002240" + }, + "HP:0001508": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Failure to thrive", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001508", + "NCIT:C111659", + "NCIT:C113099", + "UMLS:C0231246", + "UMLS:C2315100", + "UMLS:C4531021", + "MEDDRA:10036164", + "MEDDRA:10047897", + "SNOMEDCT:36440009", + "SNOMEDCT:432788009" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001508" + }, + "HP:0001265": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Hyporeflexia", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001265", + "UMLS:C0700078", + "MEDDRA:10043245", + "SNOMEDCT:405946002" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001265" + }, + "HP:0001348": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Brisk reflexes", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001348", + "UMLS:C2673700" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001348" + }, + "HP:0001327": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Photosensitive myoclonic seizure", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001327", + "UMLS:C4025791" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001327" + }, + "HP:0001986": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Hypertonic dehydration", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001986", + "UMLS:C1112601", + "MEDDRA:10057218" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001986" + }, + "HP:0001195": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Single umbilical artery", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001195", + "NCIT:C117359", + "UMLS:C1384670", + "MEDDRA:10049807", + "SNOMEDCT:204470001", + "MESH:D058529" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001195" + }, + "HP:0031369": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Colon perforation", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0031369", + "NCIT:C78242", + "UMLS:C0347646", + "MEDDRA:10009977", + "MEDDRA:10010001", + "MEDDRA:10034386", + "MEDDRA:10034407", + "MEDDRA:10034418", + "SNOMEDCT:50257008" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0031369" + }, + "HP:0030997": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Atretic vas deferens", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0030997", + "UMLS:C0266445", + "SNOMEDCT:39513007" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0030997" + }, + "HP:0002719": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Recurrent infections", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0002719", + "UMLS:C0239998", + "UMLS:C0521978", + "MEDDRA:10067860", + "SNOMEDCT:102463001", + "SNOMEDCT:451991000124106" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0002719" + }, + "HP:0003109": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Hyperphosphaturia", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0003109", + "NCIT:C67236", + "UMLS:C0268079", + "UMLS:C0948023", + "MEDDRA:10051232", + "MEDDRA:10051321", + "SNOMEDCT:22450000" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0003109" + }, + "HP:0000046": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Scrotal hypoplasia", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000046" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000046" + }, + "HP:0002446": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Astrocytosis", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0002446", + "UMLS:C3887640" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0002446" + }, + "MONDO:0007452": { + "category": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "maturity-onset diabetes of the young type 1", + "attributes": [ + { + "type": "NA", + "value": [ + "MONDO:0007452", + "DOID:0111099", + "OMIM:125850", + "UMLS:C1852093", + "MESH:C565101", + "NCIT:C129744", + "SNOMEDCT:609562003" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "MONDO:0007452" + }, + "HP:0031284": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Flushing", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0031284" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0031284" + }, + "HP:0000463": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Anteverted nares", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000463", + "UMLS:C1840077", + "SNOMEDCT:708670007" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000463" + }, + "HP:0000103": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Polyuria", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000103", + "NCIT:C118303", + "UMLS:C0032617", + "MEDDRA:10013524", + "MEDDRA:10036142", + "MEDDRA:10046683", + "SNOMEDCT:28442001", + "SNOMEDCT:56574000", + "SNOMEDCT:718402002", + "MESH:D011141" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000103" + }, + "HP:0003487": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Babinski sign", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0003487", + "NCIT:C43247", + "UMLS:C0034935", + "UMLS:C0750948", + "MEDDRA:10003966", + "MEDDRA:10015727", + "MEDDRA:10035157", + "MEDDRA:10036210", + "MEDDRA:10036211", + "MEDDRA:10038238", + "MEDDRA:10038243", + "MEDDRA:10038244", + "MEDDRA:10040672", + "MEDDRA:10046271", + "SNOMEDCT:246586009", + "SNOMEDCT:366575004", + "MESH:D001405" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0003487" + }, + "HP:0005487": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Prominent metopic ridge", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0005487", + "UMLS:C1857949" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0005487" + }, + "HP:0005102": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Cochlear degeneration", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0005102", + "UMLS:C1849095" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0005102" + }, + "HP:0000444": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Convex nasal ridge", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000444", + "UMLS:C0240538" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000444" + }, + "HP:0004379": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Abnormality of alkaline phosphatase level", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0004379", + "UMLS:C0740888", + "UMLS:C4020829", + "UMLS:C4025328" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0004379" + }, + "HP:0000331": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Short chin", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000331", + "UMLS:C1839323", + "UMLS:C3697248", + "SNOMEDCT:699439001" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000331" + }, + "HP:0001944": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Dehydration", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001944", + "UMLS:C0011175", + "UMLS:C1136135", + "MEDDRA:10012174", + "MEDDRA:10054198", + "SNOMEDCT:34095006", + "MESH:D003681" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001944" + }, + "HP:0005563": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Decreased numbers of nephrons", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0005563", + "UMLS:C1841994", + "UMLS:C2673888" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0005563" + }, + "HP:0000293": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Full cheeks", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0000293", + "UMLS:C1866231", + "UMLS:C2748653", + "UMLS:C3806443", + "UMLS:C4280647", + "UMLS:C4280648" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0000293" + }, + "HP:0012210": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Abnormal renal morphology", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0012210", + "UMLS:C4551596", + "MEDDRA:10023430" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0012210" + }, + "HP:0001518": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Small for gestational age", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0001518", + "NCIT:C114934", + "UMLS:C0024032", + "UMLS:C0235991", + "UMLS:C0302511", + "UMLS:C0456060", + "UMLS:C1313876", + "MEDDRA:10004958", + "MEDDRA:10004959", + "MEDDRA:10004961", + "MEDDRA:10004962", + "MEDDRA:10041093", + "MEDDRA:10047892", + "SNOMEDCT:189445003", + "SNOMEDCT:267258002", + "SNOMEDCT:276610007", + "MESH:D007230" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0001518" + }, + "HP:0002171": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Gliosis", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0002171", + "NCIT:C26783", + "UMLS:C0017639", + "MEDDRA:10008110", + "MEDDRA:10018341", + "SNOMEDCT:359580009", + "SNOMEDCT:81415000", + "MESH:D005911" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0002171" + }, + "HP:0040270": { + "category": [ + "biolink:PhenotypicFeature", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:ThingWithTaxon", + "biolink:BiologicalEntity", + "biolink:NamedThing", + "biolink:Entity" + ], + "name": "Impaired glucose tolerance", + "attributes": [ + { + "type": "NA", + "value": [ + "HP:0040270", + "UMLS:C0151671", + "MEDDRA:10018428", + "MEDDRA:10043931", + "MP:0005291" + ], + "name": "equivalent_identifiers" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "HP:0040270" + } + }, + "edges": { + "72f574c98a9f6df79174a6c6544d3e1c": { + "subject": "MONDO:0014523", + "object": "HP:0002066", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "72f574c98a9f6df79174a6c6544d3e1c" + }, + "37bf2984fd709fb0b4cacedc812fbfc1": { + "subject": "MONDO:0010802", + "object": "HP:0009112", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "37bf2984fd709fb0b4cacedc812fbfc1" + }, + "351af9a711211da2e0630801682ed399": { + "subject": "MONDO:0030087", + "object": "HP:0007334", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "351af9a711211da2e0630801682ed399" + }, + "2923c2d9de0ffb39842bfe8f08c09460": { + "subject": "MONDO:0030088", + "object": "HP:0002919", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "2923c2d9de0ffb39842bfe8f08c09460" + }, + "e4a042934ba3cbdf1f0861f1125a6ea6": { + "subject": "MONDO:0012436", + "object": "HP:0005280", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "e4a042934ba3cbdf1f0861f1125a6ea6" + }, + "e0511c4e296a1b7767ece7f072a3e267": { + "subject": "MONDO:0010802", + "object": "HP:0011629", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "e0511c4e296a1b7767ece7f072a3e267" + }, + "51460d5bdd29a2dc0bf709e60fde25a6": { + "subject": "MONDO:0010802", + "object": "HP:0001660", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "51460d5bdd29a2dc0bf709e60fde25a6" + }, + "72b3e334dc998d48458deb6d3e490fe0": { + "subject": "MONDO:0031481", + "object": "HP:0003241", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "72b3e334dc998d48458deb6d3e490fe0" + }, + "bb35e3fe8512606067fe0ade76459763": { + "subject": "MONDO:0022993", + "object": "HP:0001959", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0004022" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "bb35e3fe8512606067fe0ade76459763" + }, + "f3f01da1dd8c3c6e66af38000ffd4d5f": { + "subject": "MONDO:0010581", + "object": "HP:0001959", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "f3f01da1dd8c3c6e66af38000ffd4d5f" + }, + "d1368af90f14336c281fcb87c478aa13": { + "subject": "MONDO:0015790", + "object": "HP:0001959", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "d1368af90f14336c281fcb87c478aa13" + }, + "722c97db3aabb7fc4642e19dfa761580": { + "subject": "MONDO:0007451", + "object": "HP:0001959", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "722c97db3aabb7fc4642e19dfa761580" + }, + "42574591073e3433d11a2af0c9a3958b": { + "subject": "MONDO:0030087", + "object": "HP:0032794", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "42574591073e3433d11a2af0c9a3958b" + }, + "afbbb67e6cdad4e59c53bfd7a8f8a319": { + "subject": "MONDO:0010802", + "object": "HP:0001537", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "afbbb67e6cdad4e59c53bfd7a8f8a319" + }, + "5ae4af69fbe2f9ef37775edffde2938d": { + "subject": "MONDO:0012436", + "object": "HP:0001537", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "5ae4af69fbe2f9ef37775edffde2938d" + }, + "5ecb3498d8d55415fd81f64743fc6696": { + "subject": "MONDO:0012436", + "object": "HP:0000286", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "5ecb3498d8d55415fd81f64743fc6696" + }, + "1a03657d92138472604c4b6d8d0590c5": { + "subject": "MONDO:0030088", + "object": "HP:0012434", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "1a03657d92138472604c4b6d8d0590c5" + }, + "7aa1cd2682e3f85e964a1813603e2381": { + "subject": "MONDO:0030089", + "object": "HP:0040217", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "7aa1cd2682e3f85e964a1813603e2381" + }, + "b906568a586bf9fb894ef8f764ee838c": { + "subject": "MONDO:0100165", + "object": "HP:0040217", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "b906568a586bf9fb894ef8f764ee838c" + }, + "d54418fb9c9ae70b59dde645a199a1f8": { + "subject": "MONDO:0012522", + "object": "HP:0040217", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "d54418fb9c9ae70b59dde645a199a1f8" + }, + "49970b8bc65ef9bb2d35ba3761ded4d4": { + "subject": "MONDO:0031481", + "object": "HP:0011968", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "49970b8bc65ef9bb2d35ba3761ded4d4" + }, + "38977f38313b535be7f462e175cc5e9f": { + "subject": "MONDO:0010785", + "object": "HP:0001260", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "38977f38313b535be7f462e175cc5e9f" + }, + "8d725884da55c75fd54e86fecbe770a5": { + "subject": "MONDO:0007450", + "object": "HP:0003196", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "8d725884da55c75fd54e86fecbe770a5" + }, + "0aa7194fa870bd44b28a992917ada714": { + "subject": "MONDO:0031481", + "object": "HP:0000218", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "0aa7194fa870bd44b28a992917ada714" + }, + "297e54b8d91ed69efcbfb4f48d6015cc": { + "subject": "MONDO:0008242", + "object": "HP:0007164", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "297e54b8d91ed69efcbfb4f48d6015cc" + }, + "484aadb651a31d3d3e2ecfb321bd7686": { + "subject": "MONDO:0008242", + "object": "HP:0001289", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "484aadb651a31d3d3e2ecfb321bd7686" + }, + "b7ba8b670efc34f4235f6ccd0c1bd90e": { + "subject": "MONDO:0007669", + "object": "HP:0002120", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "b7ba8b670efc34f4235f6ccd0c1bd90e" + }, + "a06d8e4715a782cdb42430c4d1f550ed": { + "subject": "MONDO:0012192", + "object": "HP:0000377", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "a06d8e4715a782cdb42430c4d1f550ed" + }, + "51b816cd8235dd2c0d7bbf8725711d60": { + "subject": "MONDO:0007669", + "object": "HP:0012207", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "51b816cd8235dd2c0d7bbf8725711d60" + }, + "05acfb9b187ff9d2aaa5bf71f66d4a2f": { + "subject": "MONDO:0030087", + "object": "HP:0001488", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "05acfb9b187ff9d2aaa5bf71f66d4a2f" + }, + "a2bf262be5b3b4f5e8e45dcf788284d3": { + "subject": "MONDO:0031481", + "object": "HP:0000252", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "a2bf262be5b3b4f5e8e45dcf788284d3" + }, + "37af9b09ba037eacfc7d62d9d41f0276": { + "subject": "MONDO:0012192", + "object": "HP:0000252", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "37af9b09ba037eacfc7d62d9d41f0276" + }, + "8e9af39cc99432919062604fe42d5e15": { + "subject": "MONDO:0010802", + "object": "HP:0000252", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "8e9af39cc99432919062604fe42d5e15" + }, + "243ce2e16ec9d6c423745ccba08ab306": { + "subject": "MONDO:0012192", + "object": "HP:0000325", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "243ce2e16ec9d6c423745ccba08ab306" + }, + "201d42ab930c539b34414b051b2933b5": { + "subject": "MONDO:0030089", + "object": "HP:0030795", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "201d42ab930c539b34414b051b2933b5" + }, + "6c58030d3c0df800ffb2b6226c610c30": { + "subject": "MONDO:0100165", + "object": "HP:0030795", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "6c58030d3c0df800ffb2b6226c610c30" + }, + "69a6b548a4322ae3ffce30c7ad8d9896": { + "subject": "MONDO:0030087", + "object": "HP:0030795", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "69a6b548a4322ae3ffce30c7ad8d9896" + }, + "82473f7835f4274f1791d4fca8a66a46": { + "subject": "MONDO:0031481", + "object": "HP:0002756", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "82473f7835f4274f1791d4fca8a66a46" + }, + "8fb1245e83c9409592589303c48a0f7e": { + "subject": "MONDO:0012348", + "object": "HP:0002027", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "8fb1245e83c9409592589303c48a0f7e" + }, + "601d00073b31469c8025f60074528283": { + "subject": "MONDO:0007669", + "object": "HP:0003259", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "601d00073b31469c8025f60074528283" + }, + "d9184293552e3edd65f4aad9444a6a11": { + "subject": "MONDO:0007450", + "object": "HP:0000316", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "d9184293552e3edd65f4aad9444a6a11" + }, + "b91726350d337b85cc90311496c4b4ba": { + "subject": "MONDO:0007450", + "object": "HP:0000343", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "b91726350d337b85cc90311496c4b4ba" + }, + "c9b82ea9e70b99fcc13b4a2a64197989": { + "subject": "MONDO:0012436", + "object": "HP:0000343", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "c9b82ea9e70b99fcc13b4a2a64197989" + }, + "2326f765c7ca386b306c307202593742": { + "subject": "MONDO:0030088", + "object": "HP:0003698", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "2326f765c7ca386b306c307202593742" + }, + "07fe2814e5b57f06069d991e9caac653": { + "subject": "MONDO:0010581", + "object": "HP:0000021", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "07fe2814e5b57f06069d991e9caac653" + }, + "49db1bf24410359cfe37fe9a4f70a807": { + "subject": "MONDO:0007451", + "object": "HP:0000021", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "49db1bf24410359cfe37fe9a4f70a807" + }, + "2baaca3555df1745d44056e8c01f9d0c": { + "subject": "MONDO:0012192", + "object": "HP:0002104", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "2baaca3555df1745d44056e8c01f9d0c" + }, + "926c80b6c91efb10ca31f2f612e7306e": { + "subject": "MONDO:0012436", + "object": "HP:0030423", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "926c80b6c91efb10ca31f2f612e7306e" + }, + "dae217f92a40edffdf6f2530edf304da": { + "subject": "MONDO:0031481", + "object": "HP:0002910", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "dae217f92a40edffdf6f2530edf304da" + }, + "cf9d42906200ace658ad0530fad03a21": { + "subject": "MONDO:0007669", + "object": "HP:0002910", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "cf9d42906200ace658ad0530fad03a21" + }, + "62ae1ea3c3063a00794e043716d32a3d": { + "subject": "MONDO:0011073", + "object": "HP:0001525", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "62ae1ea3c3063a00794e043716d32a3d" + }, + "b71440ac4c2a88cf203a830d06c024d6": { + "subject": "MONDO:0030088", + "object": "HP:0001290", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "b71440ac4c2a88cf203a830d06c024d6" + }, + "5e86025b5bd78576cb8b18ffa6d5867a": { + "subject": "MONDO:0010581", + "object": "HP:0000737", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "5e86025b5bd78576cb8b18ffa6d5867a" + }, + "999ec77bf768dc94964f303123766307": { + "subject": "MONDO:0007451", + "object": "HP:0000737", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "999ec77bf768dc94964f303123766307" + }, + "8988557302a7ee012dd836a96a431667": { + "subject": "MONDO:0007669", + "object": "HP:0100611", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "8988557302a7ee012dd836a96a431667" + }, + "a0dbf352923bfeeb102c3134f5ba99be": { + "subject": "MONDO:0010581", + "object": "HP:0003228", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "a0dbf352923bfeeb102c3134f5ba99be" + }, + "67e1312db439280b4d0362e1f988cce8": { + "subject": "MONDO:0007451", + "object": "HP:0003228", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "67e1312db439280b4d0362e1f988cce8" + }, + "2dae6510fcb0d3a4b174fcfcd01211eb": { + "subject": "MONDO:0010785", + "object": "HP:0002317", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "2dae6510fcb0d3a4b174fcfcd01211eb" + }, + "f8acd3df866c2d91cd76f507bac0d5c2": { + "subject": "MONDO:0030088", + "object": "HP:0001324", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "f8acd3df866c2d91cd76f507bac0d5c2" + }, + "6e2e69955e4f53b98ad9974154d32d47": { + "subject": "MONDO:0031481", + "object": "HP:0010804", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "6e2e69955e4f53b98ad9974154d32d47" + }, + "881622e672a74efd63c2e6a87ca3d7aa": { + "subject": "MONDO:0010802", + "object": "HP:0011682", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "881622e672a74efd63c2e6a87ca3d7aa" + }, + "9e177fdaa34d54821e00c748a297ca67": { + "subject": "MONDO:0031481", + "object": "HP:0002205", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "9e177fdaa34d54821e00c748a297ca67" + }, + "0767c97e995b7f74adb124c44518b6dc": { + "subject": "MONDO:0012436", + "object": "HP:0001737", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "0767c97e995b7f74adb124c44518b6dc" + }, + "ccc2fb079b82e04d1c8bc47aaa0b3bce": { + "subject": "MONDO:0007450", + "object": "HP:0001939", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "ccc2fb079b82e04d1c8bc47aaa0b3bce" + }, + "46bd37875dea004a4b5ee89fbf7eda63": { + "subject": "MONDO:0031481", + "object": "HP:0001263", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "46bd37875dea004a4b5ee89fbf7eda63" + }, + "f054829332046c1512d43d45224e698a": { + "subject": "MONDO:0010802", + "object": "HP:0001263", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "f054829332046c1512d43d45224e698a" + }, + "0e04b0a85646bd8427439bc731e1b889": { + "subject": "MONDO:0012436", + "object": "HP:0001263", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "0e04b0a85646bd8427439bc731e1b889" + }, + "9abdec98c57fed4957b3a4131f8cdc9e": { + "subject": "MONDO:0030087", + "object": "HP:0001263", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "9abdec98c57fed4957b3a4131f8cdc9e" + }, + "0631daecf88fb566b24cefa3c80ce4e1": { + "subject": "MONDO:0010581", + "object": "HP:0002013", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "0631daecf88fb566b24cefa3c80ce4e1" + }, + "08c8e428e707bff409415ef179c1dfd0": { + "subject": "MONDO:0007451", + "object": "HP:0002013", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "08c8e428e707bff409415ef179c1dfd0" + }, + "ccd5796368067d97ff198e0f1f369060": { + "subject": "MONDO:0010581", + "object": "HP:0001955", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "ccd5796368067d97ff198e0f1f369060" + }, + "7e79d0e5fa071d0ce1111b7de9489bcf": { + "subject": "MONDO:0007451", + "object": "HP:0001955", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "7e79d0e5fa071d0ce1111b7de9489bcf" + }, + "abf07362caab44c660ff2498787024bc": { + "subject": "MONDO:0008242", + "object": "HP:0000666", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "abf07362caab44c660ff2498787024bc" + }, + "a312fb4cf3e1b68855d782559eb34f64": { + "subject": "MONDO:0008843", + "object": "HP:0001317", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "a312fb4cf3e1b68855d782559eb34f64" + }, + "7adc20e575f4e20398f5c4be59addf5d": { + "subject": "MONDO:0012192", + "object": "HP:0010557", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "7adc20e575f4e20398f5c4be59addf5d" + }, + "5d892bfa6049851a075b470fb4f4956f": { + "subject": "MONDO:0012436", + "object": "HP:0001744", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "5d892bfa6049851a075b470fb4f4956f" + }, + "67f181c231b083e72355944d966ae750": { + "subject": "MONDO:0014523", + "object": "HP:0001284", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "67f181c231b083e72355944d966ae750" + }, + "bafa0d913c2dc7e78cd98d05278a6973": { + "subject": "MONDO:0012192", + "object": "HP:0001387", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "bafa0d913c2dc7e78cd98d05278a6973" + }, + "9b93e8f93a142bf4a1f8d92e156790b0": { + "subject": "MONDO:0014523", + "object": "HP:0007366", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "9b93e8f93a142bf4a1f8d92e156790b0" + }, + "57fb1643165252f168bf71e0e9ac22b8": { + "subject": "MONDO:0005147", + "object": "HP:0006476", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0004022" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "57fb1643165252f168bf71e0e9ac22b8" + }, + "87b0d725d20d76bfbbfffa9c2f8be4b7": { + "subject": "MONDO:0010802", + "object": "HP:0004415", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "87b0d725d20d76bfbbfffa9c2f8be4b7" + }, + "0bf145c14adcd3130d48b4f9293355f1": { + "subject": "MONDO:0030087", + "object": "HP:0002714", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "0bf145c14adcd3130d48b4f9293355f1" + }, + "8be3174a3ff57ae044779870894c3330": { + "subject": "MONDO:0012192", + "object": "HP:0012642", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "8be3174a3ff57ae044779870894c3330" + }, + "a6a5ebd58acc195443ed95d4f9386d75": { + "subject": "MONDO:0012192", + "object": "HP:0002594", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "a6a5ebd58acc195443ed95d4f9386d75" + }, + "d333d2d06625df3c0404ea5ac45a4ce8": { + "subject": "MONDO:0007669", + "object": "HP:0002594", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "d333d2d06625df3c0404ea5ac45a4ce8" + }, + "d6660e7d11d03c67a3e110f66133aa21": { + "subject": "MONDO:0010802", + "object": "HP:0002594", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "d6660e7d11d03c67a3e110f66133aa21" + }, + "f122ff46206a7f79354f2d3e0d32a91c": { + "subject": "MONDO:0012436", + "object": "HP:0002594", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "f122ff46206a7f79354f2d3e0d32a91c" + }, + "da6682ccb2e67223112d9dd8dd98eb65": { + "subject": "MONDO:0031481", + "object": "HP:0000341", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "da6682ccb2e67223112d9dd8dd98eb65" + }, + "fac9e02e721d9e31f118b1f35accce7d": { + "subject": "MONDO:0007450", + "object": "HP:0000445", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "fac9e02e721d9e31f118b1f35accce7d" + }, + "d74b1d1b6bd370c12549a2f930a6956f": { + "subject": "MONDO:0007669", + "object": "HP:0000078", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "d74b1d1b6bd370c12549a2f930a6956f" + }, + "14bf69af2cb4682c1c37d51834b74705": { + "subject": "MONDO:0005148", + "object": "HP:0000855", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "14bf69af2cb4682c1c37d51834b74705" + }, + "7626eb4804520ad1c5aaeb354bd864d8": { + "subject": "MONDO:0005148", + "object": "HP:0000855", + "predicate": "biolink:correlated_with", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0004029" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "7626eb4804520ad1c5aaeb354bd864d8" + }, + "653ec6f99255f973840d587aa6dd8563": { + "subject": "MONDO:0031481", + "object": "HP:0002079", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "653ec6f99255f973840d587aa6dd8563" + }, + "0036bfff38f786ebddb266159c2b1edc": { + "subject": "MONDO:0010813", + "object": "HP:0001438", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "0036bfff38f786ebddb266159c2b1edc" + }, + "ba4068283772d8fde2e760728714093f": { + "subject": "MONDO:0012192", + "object": "HP:0000768", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "ba4068283772d8fde2e760728714093f" + }, + "45c597e9d010374ec3ac93799ea2e1de": { + "subject": "MONDO:0010581", + "object": "HP:0004322", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "45c597e9d010374ec3ac93799ea2e1de" + }, + "f961b4c1c05e1416c1328ef65fc2e7e1": { + "subject": "MONDO:0014458", + "object": "HP:0004322", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "f961b4c1c05e1416c1328ef65fc2e7e1" + }, + "789e2535768fd466c10f956d723ef12f": { + "subject": "MONDO:0007451", + "object": "HP:0004322", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "789e2535768fd466c10f956d723ef12f" + }, + "3d5e20be86d6021f2988927f41c4be2b": { + "subject": "MONDO:0014523", + "object": "HP:0004322", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "3d5e20be86d6021f2988927f41c4be2b" + }, + "ef2867ef703d0ad73742927b4a564da6": { + "subject": "MONDO:0030088", + "object": "HP:0001270", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "ef2867ef703d0ad73742927b4a564da6" + }, + "f4a0a4dc7f90e9afeae9fd44aeea8813": { + "subject": "MONDO:0031481", + "object": "HP:0002187", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "f4a0a4dc7f90e9afeae9fd44aeea8813" + }, + "61cfbb6d86bf7d0122d9d2fcee23a411": { + "subject": "MONDO:0010802", + "object": "HP:0000023", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "61cfbb6d86bf7d0122d9d2fcee23a411" + }, + "e7ddd58d14c851598cc779945fa77e33": { + "subject": "MONDO:0008242", + "object": "HP:0000751", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "e7ddd58d14c851598cc779945fa77e33" + }, + "d464a8c34f227a63ec9a37cd31ca651c": { + "subject": "MONDO:0031481", + "object": "HP:0012594", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "d464a8c34f227a63ec9a37cd31ca651c" + }, + "9d3c17bdb3713aab9cd67e2f45d96176": { + "subject": "MONDO:0010785", + "object": "HP:0001133", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "9d3c17bdb3713aab9cd67e2f45d96176" + }, + "3f6c2588926d6c0e23d75b5021307743": { + "subject": "MONDO:0005148", + "object": "HP:0031819", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "3f6c2588926d6c0e23d75b5021307743" + }, + "3c462531445aa81fe021a2c590ce9ac4": { + "subject": "MONDO:0031481", + "object": "HP:0012448", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "3c462531445aa81fe021a2c590ce9ac4" + }, + "74f07d5037ce7603285936fd86f11b2e": { + "subject": "MONDO:0007669", + "object": "HP:0003076", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "74f07d5037ce7603285936fd86f11b2e" + }, + "ff772aa170d50ae55eeeb66555664e40": { + "subject": "MONDO:0010802", + "object": "HP:0003076", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "ff772aa170d50ae55eeeb66555664e40" + }, + "8dcb4c7c1c3b31a176ec878fee9eb94d": { + "subject": "MONDO:0014458", + "object": "HP:0003076", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "8dcb4c7c1c3b31a176ec878fee9eb94d" + }, + "1fb76ab0a55f83d57654045b03d53f06": { + "subject": "MONDO:0030088", + "object": "HP:0003076", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "1fb76ab0a55f83d57654045b03d53f06" + }, + "c6ce04b4b9b122d86d8ff5410b435afc": { + "subject": "MONDO:0030087", + "object": "HP:0001993", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "c6ce04b4b9b122d86d8ff5410b435afc" + }, + "52e9a61ea56e403103e735f9c16dcc36": { + "subject": "MONDO:0008843", + "object": "HP:0007201", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "52e9a61ea56e403103e735f9c16dcc36" + }, + "d8977920db81cfc6e2c29a9f8973d465": { + "subject": "MONDO:0014458", + "object": "HP:0001520", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "d8977920db81cfc6e2c29a9f8973d465" + }, + "51aa26e290a3c81c2479208c39314618": { + "subject": "MONDO:0013242", + "object": "HP:0025502", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "51aa26e290a3c81c2479208c39314618" + }, + "c6232ceb9be65554473af8260efd3699": { + "subject": "MONDO:0014523", + "object": "HP:0100543", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "c6232ceb9be65554473af8260efd3699" + }, + "8391820146a59b64f3533d7a6cb30b98": { + "subject": "MONDO:0012192", + "object": "HP:0003758", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "8391820146a59b64f3533d7a6cb30b98" + }, + "c0d12db45c4ca4267a2b9d88956fbb3c": { + "subject": "MONDO:0031481", + "object": "HP:0001319", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "c0d12db45c4ca4267a2b9d88956fbb3c" + }, + "202a1bae3c4aa2c86636cabcf1606347": { + "subject": "MONDO:0031481", + "object": "HP:0009879", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "202a1bae3c4aa2c86636cabcf1606347" + }, + "c0f06eccb9f8c247585f029474f343a6": { + "subject": "MONDO:0012436", + "object": "HP:0000260", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "c0f06eccb9f8c247585f029474f343a6" + }, + "0d72ffe450070fcf8bebff7ba05b4199": { + "subject": "MONDO:0014523", + "object": "HP:0002059", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "0d72ffe450070fcf8bebff7ba05b4199" + }, + "c7e1fe4608bc4c2ffa969e5409a7cc08": { + "subject": "MONDO:0012192", + "object": "HP:0000369", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "c7e1fe4608bc4c2ffa969e5409a7cc08" + }, + "496c4694ae07abe6a9ae59c91c6361d1": { + "subject": "MONDO:0012436", + "object": "HP:0000369", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "496c4694ae07abe6a9ae59c91c6361d1" + }, + "1a567cc1be2aeaf95aaf03ac75902cdf": { + "subject": "MONDO:0031481", + "object": "HP:0000952", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "1a567cc1be2aeaf95aaf03ac75902cdf" + }, + "9c8bece27098200104252d8a91215be5": { + "subject": "MONDO:0012436", + "object": "HP:0002944", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "9c8bece27098200104252d8a91215be5" + }, + "b290d04d5890e23c33ff7771026ea761": { + "subject": "MONDO:0010785", + "object": "HP:0000580", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "b290d04d5890e23c33ff7771026ea761" + }, + "3e38e4f2b9bf92804d162f8aa70a7f10": { + "subject": "MONDO:0007669", + "object": "HP:0000077", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "3e38e4f2b9bf92804d162f8aa70a7f10" + }, + "6a9c3c11cbd6b781ed8733cd2d53bada": { + "subject": "MONDO:0010581", + "object": "HP:0008872", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "6a9c3c11cbd6b781ed8733cd2d53bada" + }, + "69903964173648faa2374e21b687e3b5": { + "subject": "MONDO:0007451", + "object": "HP:0008872", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "69903964173648faa2374e21b687e3b5" + }, + "237cfdcec30985416bcc16063df6667e": { + "subject": "MONDO:0010785", + "object": "HP:0001751", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "237cfdcec30985416bcc16063df6667e" + }, + "ec484a1a1158bebb1c383e5d33788666": { + "subject": "MONDO:0030088", + "object": "HP:0002305", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "ec484a1a1158bebb1c383e5d33788666" + }, + "31cce92f4d3ab4a443292a3ec8ef19be": { + "subject": "MONDO:0014458", + "object": "HP:0003537", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "31cce92f4d3ab4a443292a3ec8ef19be" + }, + "e1eac0947d7eceeb92ee794e3bc097da": { + "subject": "MONDO:0010802", + "object": "HP:0011573", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "e1eac0947d7eceeb92ee794e3bc097da" + }, + "c708e35c5250dca846445e10a962b916": { + "subject": "MONDO:0010802", + "object": "HP:0000073", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "c708e35c5250dca846445e10a962b916" + }, + "2470307de035f6887e6d2f200e66c26a": { + "subject": "MONDO:0012436", + "object": "HP:0004442", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "2470307de035f6887e6d2f200e66c26a" + }, + "48f53cea7b3c1830d102b6e9dcb55c41": { + "subject": "MONDO:0031481", + "object": "HP:0008936", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "48f53cea7b3c1830d102b6e9dcb55c41" + }, + "792f792d97b6a8fb941ac721c7dca621": { + "subject": "MONDO:0012348", + "object": "HP:0012092", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "792f792d97b6a8fb941ac721c7dca621" + }, + "dcf0035f916bb4854956f87d9e99abbf": { + "subject": "MONDO:0030088", + "object": "HP:0011182", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "dcf0035f916bb4854956f87d9e99abbf" + }, + "1d99610b8f7245c14fb561f7138cd1d6": { + "subject": "MONDO:0015790", + "object": "HP:0001948", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "1d99610b8f7245c14fb561f7138cd1d6" + }, + "bc0676d58f47d10d35365d3a56888820": { + "subject": "MONDO:0010813", + "object": "HP:0000857", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "bc0676d58f47d10d35365d3a56888820" + }, + "9994fcc17beadb172f5a9f8c98b3f6af": { + "subject": "MONDO:0012436", + "object": "HP:0001395", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "9994fcc17beadb172f5a9f8c98b3f6af" + }, + "9cf290a01ef2c4d80fb8761f226ceb66": { + "subject": "MONDO:0030087", + "object": "HP:0002069", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "9cf290a01ef2c4d80fb8761f226ceb66" + }, + "29b7293adb191bfb28375b3909d619aa": { + "subject": "MONDO:0012436", + "object": "HP:0000219", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "29b7293adb191bfb28375b3909d619aa" + }, + "411049c99a5d225c6b804c5b6cde8cdc": { + "subject": "MONDO:0031481", + "object": "HP:0002521", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "411049c99a5d225c6b804c5b6cde8cdc" + }, + "365800f2145da658edb90b27bcfc7662": { + "subject": "MONDO:0030087", + "object": "HP:0002521", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "365800f2145da658edb90b27bcfc7662" + }, + "8244677a623518adc30e5c1deeea7bf6": { + "subject": "MONDO:0008242", + "object": "HP:0000408", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "8244677a623518adc30e5c1deeea7bf6" + }, + "466ac1a4184497fd6eca54a73fa8710e": { + "subject": "MONDO:0007669", + "object": "HP:0002149", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "466ac1a4184497fd6eca54a73fa8710e" + }, + "04684185427174ae615e45c429184eb5": { + "subject": "MONDO:0014458", + "object": "HP:0002240", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "04684185427174ae615e45c429184eb5" + }, + "c44a5a176a668d2ead40c083e677901c": { + "subject": "MONDO:0012436", + "object": "HP:0002240", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "c44a5a176a668d2ead40c083e677901c" + }, + "e50100eaf8e4171c799a03167a965e93": { + "subject": "MONDO:0012192", + "object": "HP:0001508", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "e50100eaf8e4171c799a03167a965e93" + }, + "eba8e4019b6c6dd290f29a1f299e243e": { + "subject": "MONDO:0010802", + "object": "HP:0001508", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "eba8e4019b6c6dd290f29a1f299e243e" + }, + "6febb513f14146b633662e6b9d4ce9f9": { + "subject": "MONDO:0010581", + "object": "HP:0001508", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "6febb513f14146b633662e6b9d4ce9f9" + }, + "33355b42f40fd75224022432cf11f866": { + "subject": "MONDO:0007451", + "object": "HP:0001508", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "33355b42f40fd75224022432cf11f866" + }, + "0a5341a2518307a926b3da7a57c752f4": { + "subject": "MONDO:0012192", + "object": "HP:0001265", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "0a5341a2518307a926b3da7a57c752f4" + }, + "4cb0f3799ee4c26768cbddc695b151cb": { + "subject": "MONDO:0031481", + "object": "HP:0001348", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "4cb0f3799ee4c26768cbddc695b151cb" + }, + "a4fac8947539a07bce6d882b6f21ca91": { + "subject": "MONDO:0008242", + "object": "HP:0001327", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "a4fac8947539a07bce6d882b6f21ca91" + }, + "08cb91b5ce5fd16715f04c88dfa02f7c": { + "subject": "MONDO:0008843", + "object": "HP:0001327", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "08cb91b5ce5fd16715f04c88dfa02f7c" + }, + "63f154fe033b6b4ca7d50b26cc057951": { + "subject": "MONDO:0010581", + "object": "HP:0001986", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "63f154fe033b6b4ca7d50b26cc057951" + }, + "7beeb6a1d9af33ede22efb9876d26549": { + "subject": "MONDO:0007451", + "object": "HP:0001986", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "7beeb6a1d9af33ede22efb9876d26549" + }, + "68b4ed8795c61c185124d2da5dfe0131": { + "subject": "MONDO:0010802", + "object": "HP:0001195", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "68b4ed8795c61c185124d2da5dfe0131" + }, + "614ea6dd7e6e8449019f60cd554f2bb8": { + "subject": "MONDO:0010802", + "object": "HP:0031369", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "614ea6dd7e6e8449019f60cd554f2bb8" + }, + "38650e4a07ca7c7ba6c3e6bbe6d4cd86": { + "subject": "MONDO:0007669", + "object": "HP:0030997", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "38650e4a07ca7c7ba6c3e6bbe6d4cd86" + }, + "fe8ff28949c0e790a3b5a7d4bda7438f": { + "subject": "MONDO:0012436", + "object": "HP:0002719", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "fe8ff28949c0e790a3b5a7d4bda7438f" + }, + "b7fb21983e694ef74d22bc08b33f2aec": { + "subject": "MONDO:0014458", + "object": "HP:0003109", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "b7fb21983e694ef74d22bc08b33f2aec" + }, + "11a567452a1fc1ef869090285d1089b7": { + "subject": "MONDO:0031481", + "object": "HP:0000046", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "11a567452a1fc1ef869090285d1089b7" + }, + "1974a9fbfd29a28de60f4483fc65c2b1": { + "subject": "MONDO:0008242", + "object": "HP:0002446", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "1974a9fbfd29a28de60f4483fc65c2b1" + }, + "987c54d297c5572d9bd174627bd071eb": { + "subject": "MONDO:0007452", + "object": "HP:0031284", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "987c54d297c5572d9bd174627bd071eb" + }, + "44345e96cbacacf247e7abfce845f454": { + "subject": "MONDO:0031481", + "object": "HP:0000463", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "44345e96cbacacf247e7abfce845f454" + }, + "1f3d6f020e9abcfd3c200a4095486f5b": { + "subject": "MONDO:0010581", + "object": "HP:0000103", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "1f3d6f020e9abcfd3c200a4095486f5b" + }, + "79dc27cb79f39a874dce6a434d16073e": { + "subject": "MONDO:0015790", + "object": "HP:0000103", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "79dc27cb79f39a874dce6a434d16073e" + }, + "7f8e3b0ecb6890a0906a7087d2c87366": { + "subject": "MONDO:0007451", + "object": "HP:0000103", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "7f8e3b0ecb6890a0906a7087d2c87366" + }, + "10004cc14f75612d8347234d2c2bfcc3": { + "subject": "MONDO:0014523", + "object": "HP:0003487", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "10004cc14f75612d8347234d2c2bfcc3" + }, + "1c625932ea9e61579629e46cec60188f": { + "subject": "MONDO:0030087", + "object": "HP:0005487", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "1c625932ea9e61579629e46cec60188f" + }, + "ce97d32c3fb169a55c535e75df615425": { + "subject": "MONDO:0008242", + "object": "HP:0005102", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "ce97d32c3fb169a55c535e75df615425" + }, + "38723240083a68736363a0dca06a0d47": { + "subject": "MONDO:0012192", + "object": "HP:0000444", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "38723240083a68736363a0dca06a0d47" + }, + "7e5d1946a4f89035a79c9ecce0992f04": { + "subject": "MONDO:0007669", + "object": "HP:0004379", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "7e5d1946a4f89035a79c9ecce0992f04" + }, + "d9e3c3071c84196249d15fcbc8e63ed5": { + "subject": "MONDO:0012192", + "object": "HP:0000331", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "d9e3c3071c84196249d15fcbc8e63ed5" + }, + "2d9d107968a4c383c599495dd5a83878": { + "subject": "MONDO:0011073", + "object": "HP:0001944", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "2d9d107968a4c383c599495dd5a83878" + }, + "e02e15acd4514e43ea1d2d7863dda89c": { + "subject": "MONDO:0007669", + "object": "HP:0005563", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "e02e15acd4514e43ea1d2d7863dda89c" + }, + "99fd613e2cc70f7aed5956ab69016463": { + "subject": "MONDO:0031481", + "object": "HP:0000293", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "99fd613e2cc70f7aed5956ab69016463" + }, + "6f3b338acd68d48e1070971722084eed": { + "subject": "MONDO:0007669", + "object": "HP:0012210", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "6f3b338acd68d48e1070971722084eed" + }, + "fb01d23a8c8c706d2c26497226076e3a": { + "subject": "MONDO:0030089", + "object": "HP:0001518", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "fb01d23a8c8c706d2c26497226076e3a" + }, + "5b9b0ba1b87b10530b6ad46ac91c346c": { + "subject": "MONDO:0100165", + "object": "HP:0001518", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "5b9b0ba1b87b10530b6ad46ac91c346c" + }, + "1bf5a981012915959dfd8fce76bed8b7": { + "subject": "MONDO:0030088", + "object": "HP:0001518", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "1bf5a981012915959dfd8fce76bed8b7" + }, + "c03488800b5c254fb66ee74a6cea945c": { + "subject": "MONDO:0007450", + "object": "HP:0002171", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "c03488800b5c254fb66ee74a6cea945c" + }, + "4b9266f35608dc10c1901e5432f4deaf": { + "subject": "MONDO:0007669", + "object": "HP:0040270", + "predicate": "biolink:has_phenotype", + "attributes": [ + { + "type": "NA", + "name": "relation", + "value": "RO:0002200" + }, + { + "name": "reasoner", + "value": [ + "redis:test" + ], + "type": "EDAM:data_0006" + } + ], + "id": "4b9266f35608dc10c1901e5432f4deaf" + } + } + }, + "results": [ + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0014523" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002066" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "72f574c98a9f6df79174a6c6544d3e1c" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010802" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0009112" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "37bf2984fd709fb0b4cacedc812fbfc1" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0030087" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0007334" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "351af9a711211da2e0630801682ed399" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0030088" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002919" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "2923c2d9de0ffb39842bfe8f08c09460" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012436" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0005280" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "e4a042934ba3cbdf1f0861f1125a6ea6" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010802" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0011629" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "e0511c4e296a1b7767ece7f072a3e267" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010802" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001660" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "51460d5bdd29a2dc0bf709e60fde25a6" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0031481" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0003241" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "72b3e334dc998d48458deb6d3e490fe0" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0022993" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001959" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "bb35e3fe8512606067fe0ade76459763" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010581" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001959" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "f3f01da1dd8c3c6e66af38000ffd4d5f" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0015790" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001959" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "d1368af90f14336c281fcb87c478aa13" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007451" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001959" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "722c97db3aabb7fc4642e19dfa761580" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0030087" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0032794" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "42574591073e3433d11a2af0c9a3958b" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010802" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001537" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "afbbb67e6cdad4e59c53bfd7a8f8a319" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012436" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001537" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "5ae4af69fbe2f9ef37775edffde2938d" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012436" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000286" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "5ecb3498d8d55415fd81f64743fc6696" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0030088" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0012434" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "1a03657d92138472604c4b6d8d0590c5" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0030089" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0040217" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "7aa1cd2682e3f85e964a1813603e2381" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0100165" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0040217" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "b906568a586bf9fb894ef8f764ee838c" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012522" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0040217" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "d54418fb9c9ae70b59dde645a199a1f8" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0031481" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0011968" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "49970b8bc65ef9bb2d35ba3761ded4d4" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010785" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001260" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "38977f38313b535be7f462e175cc5e9f" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007450" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0003196" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "8d725884da55c75fd54e86fecbe770a5" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0031481" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000218" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "0aa7194fa870bd44b28a992917ada714" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0008242" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0007164" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "297e54b8d91ed69efcbfb4f48d6015cc" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0008242" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001289" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "484aadb651a31d3d3e2ecfb321bd7686" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007669" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002120" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "b7ba8b670efc34f4235f6ccd0c1bd90e" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012192" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000377" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "a06d8e4715a782cdb42430c4d1f550ed" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007669" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0012207" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "51b816cd8235dd2c0d7bbf8725711d60" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0030087" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001488" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "05acfb9b187ff9d2aaa5bf71f66d4a2f" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0031481" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000252" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "a2bf262be5b3b4f5e8e45dcf788284d3" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012192" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000252" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "37af9b09ba037eacfc7d62d9d41f0276" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010802" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000252" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "8e9af39cc99432919062604fe42d5e15" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012192" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000325" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "243ce2e16ec9d6c423745ccba08ab306" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0030089" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0030795" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "201d42ab930c539b34414b051b2933b5" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0100165" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0030795" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "6c58030d3c0df800ffb2b6226c610c30" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0030087" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0030795" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "69a6b548a4322ae3ffce30c7ad8d9896" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0031481" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002756" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "82473f7835f4274f1791d4fca8a66a46" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012348" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002027" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "8fb1245e83c9409592589303c48a0f7e" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007669" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0003259" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "601d00073b31469c8025f60074528283" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007450" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000316" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "d9184293552e3edd65f4aad9444a6a11" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007450" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000343" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "b91726350d337b85cc90311496c4b4ba" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012436" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000343" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "c9b82ea9e70b99fcc13b4a2a64197989" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0030088" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0003698" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "2326f765c7ca386b306c307202593742" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010581" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000021" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "07fe2814e5b57f06069d991e9caac653" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007451" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000021" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "49db1bf24410359cfe37fe9a4f70a807" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012192" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002104" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "2baaca3555df1745d44056e8c01f9d0c" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012436" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0030423" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "926c80b6c91efb10ca31f2f612e7306e" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0031481" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002910" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "dae217f92a40edffdf6f2530edf304da" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007669" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002910" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "cf9d42906200ace658ad0530fad03a21" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0011073" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001525" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "62ae1ea3c3063a00794e043716d32a3d" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0030088" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001290" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "b71440ac4c2a88cf203a830d06c024d6" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010581" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000737" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "5e86025b5bd78576cb8b18ffa6d5867a" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007451" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000737" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "999ec77bf768dc94964f303123766307" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007669" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0100611" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "8988557302a7ee012dd836a96a431667" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010581" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0003228" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "a0dbf352923bfeeb102c3134f5ba99be" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007451" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0003228" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "67e1312db439280b4d0362e1f988cce8" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010785" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002317" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "2dae6510fcb0d3a4b174fcfcd01211eb" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0030088" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001324" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "f8acd3df866c2d91cd76f507bac0d5c2" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0031481" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0010804" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "6e2e69955e4f53b98ad9974154d32d47" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010802" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0011682" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "881622e672a74efd63c2e6a87ca3d7aa" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0031481" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002205" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "9e177fdaa34d54821e00c748a297ca67" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012436" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001737" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "0767c97e995b7f74adb124c44518b6dc" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007450" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001939" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "ccc2fb079b82e04d1c8bc47aaa0b3bce" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0031481" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001263" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "46bd37875dea004a4b5ee89fbf7eda63" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010802" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001263" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "f054829332046c1512d43d45224e698a" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012436" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001263" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "0e04b0a85646bd8427439bc731e1b889" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0030087" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001263" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "9abdec98c57fed4957b3a4131f8cdc9e" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010581" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002013" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "0631daecf88fb566b24cefa3c80ce4e1" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007451" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002013" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "08c8e428e707bff409415ef179c1dfd0" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010581" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001955" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "ccd5796368067d97ff198e0f1f369060" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007451" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001955" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "7e79d0e5fa071d0ce1111b7de9489bcf" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0008242" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000666" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "abf07362caab44c660ff2498787024bc" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0008843" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001317" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "a312fb4cf3e1b68855d782559eb34f64" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012192" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0010557" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "7adc20e575f4e20398f5c4be59addf5d" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012436" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001744" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "5d892bfa6049851a075b470fb4f4956f" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0014523" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001284" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "67f181c231b083e72355944d966ae750" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012192" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001387" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "bafa0d913c2dc7e78cd98d05278a6973" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0014523" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0007366" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "9b93e8f93a142bf4a1f8d92e156790b0" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0005147" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0006476" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "57fb1643165252f168bf71e0e9ac22b8" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010802" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0004415" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "87b0d725d20d76bfbbfffa9c2f8be4b7" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0030087" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002714" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "0bf145c14adcd3130d48b4f9293355f1" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012192" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0012642" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "8be3174a3ff57ae044779870894c3330" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012192" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002594" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "a6a5ebd58acc195443ed95d4f9386d75" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007669" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002594" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "d333d2d06625df3c0404ea5ac45a4ce8" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010802" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002594" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "d6660e7d11d03c67a3e110f66133aa21" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012436" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002594" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "f122ff46206a7f79354f2d3e0d32a91c" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0031481" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000341" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "da6682ccb2e67223112d9dd8dd98eb65" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007450" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000445" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "fac9e02e721d9e31f118b1f35accce7d" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007669" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000078" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "d74b1d1b6bd370c12549a2f930a6956f" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0005148" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000855" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "14bf69af2cb4682c1c37d51834b74705" + }, + { + "id": "7626eb4804520ad1c5aaeb354bd864d8" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0031481" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002079" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "653ec6f99255f973840d587aa6dd8563" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010813" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001438" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "0036bfff38f786ebddb266159c2b1edc" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012192" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000768" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "ba4068283772d8fde2e760728714093f" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010581" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0004322" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "45c597e9d010374ec3ac93799ea2e1de" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0014458" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0004322" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "f961b4c1c05e1416c1328ef65fc2e7e1" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007451" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0004322" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "789e2535768fd466c10f956d723ef12f" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0014523" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0004322" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "3d5e20be86d6021f2988927f41c4be2b" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0030088" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001270" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "ef2867ef703d0ad73742927b4a564da6" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0031481" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002187" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "f4a0a4dc7f90e9afeae9fd44aeea8813" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010802" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000023" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "61cfbb6d86bf7d0122d9d2fcee23a411" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0008242" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000751" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "e7ddd58d14c851598cc779945fa77e33" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0031481" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0012594" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "d464a8c34f227a63ec9a37cd31ca651c" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010785" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001133" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "9d3c17bdb3713aab9cd67e2f45d96176" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0005148" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0031819" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "3f6c2588926d6c0e23d75b5021307743" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0031481" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0012448" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "3c462531445aa81fe021a2c590ce9ac4" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007669" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0003076" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "74f07d5037ce7603285936fd86f11b2e" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010802" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0003076" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "ff772aa170d50ae55eeeb66555664e40" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0014458" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0003076" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "8dcb4c7c1c3b31a176ec878fee9eb94d" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0030088" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0003076" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "1fb76ab0a55f83d57654045b03d53f06" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0030087" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001993" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "c6ce04b4b9b122d86d8ff5410b435afc" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0008843" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0007201" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "52e9a61ea56e403103e735f9c16dcc36" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0014458" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001520" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "d8977920db81cfc6e2c29a9f8973d465" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0013242" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0025502" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "51aa26e290a3c81c2479208c39314618" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0014523" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0100543" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "c6232ceb9be65554473af8260efd3699" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012192" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0003758" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "8391820146a59b64f3533d7a6cb30b98" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0031481" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001319" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "c0d12db45c4ca4267a2b9d88956fbb3c" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0031481" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0009879" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "202a1bae3c4aa2c86636cabcf1606347" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012436" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000260" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "c0f06eccb9f8c247585f029474f343a6" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0014523" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002059" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "0d72ffe450070fcf8bebff7ba05b4199" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012192" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000369" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "c7e1fe4608bc4c2ffa969e5409a7cc08" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012436" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000369" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "496c4694ae07abe6a9ae59c91c6361d1" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0031481" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000952" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "1a567cc1be2aeaf95aaf03ac75902cdf" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012436" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002944" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "9c8bece27098200104252d8a91215be5" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010785" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000580" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "b290d04d5890e23c33ff7771026ea761" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007669" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000077" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "3e38e4f2b9bf92804d162f8aa70a7f10" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010581" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0008872" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "6a9c3c11cbd6b781ed8733cd2d53bada" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007451" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0008872" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "69903964173648faa2374e21b687e3b5" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010785" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001751" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "237cfdcec30985416bcc16063df6667e" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0030088" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002305" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "ec484a1a1158bebb1c383e5d33788666" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0014458" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0003537" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "31cce92f4d3ab4a443292a3ec8ef19be" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010802" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0011573" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "e1eac0947d7eceeb92ee794e3bc097da" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010802" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000073" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "c708e35c5250dca846445e10a962b916" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012436" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0004442" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "2470307de035f6887e6d2f200e66c26a" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0031481" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0008936" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "48f53cea7b3c1830d102b6e9dcb55c41" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012348" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0012092" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "792f792d97b6a8fb941ac721c7dca621" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0030088" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0011182" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "dcf0035f916bb4854956f87d9e99abbf" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0015790" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001948" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "1d99610b8f7245c14fb561f7138cd1d6" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010813" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000857" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "bc0676d58f47d10d35365d3a56888820" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012436" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001395" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "9994fcc17beadb172f5a9f8c98b3f6af" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0030087" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002069" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "9cf290a01ef2c4d80fb8761f226ceb66" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012436" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000219" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "29b7293adb191bfb28375b3909d619aa" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0031481" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002521" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "411049c99a5d225c6b804c5b6cde8cdc" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0030087" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002521" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "365800f2145da658edb90b27bcfc7662" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0008242" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000408" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "8244677a623518adc30e5c1deeea7bf6" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007669" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002149" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "466ac1a4184497fd6eca54a73fa8710e" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0014458" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002240" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "04684185427174ae615e45c429184eb5" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012436" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002240" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "c44a5a176a668d2ead40c083e677901c" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012192" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001508" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "e50100eaf8e4171c799a03167a965e93" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010802" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001508" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "eba8e4019b6c6dd290f29a1f299e243e" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010581" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001508" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "6febb513f14146b633662e6b9d4ce9f9" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007451" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001508" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "33355b42f40fd75224022432cf11f866" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012192" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001265" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "0a5341a2518307a926b3da7a57c752f4" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0031481" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001348" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "4cb0f3799ee4c26768cbddc695b151cb" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0008242" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001327" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "a4fac8947539a07bce6d882b6f21ca91" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0008843" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001327" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "08cb91b5ce5fd16715f04c88dfa02f7c" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010581" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001986" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "63f154fe033b6b4ca7d50b26cc057951" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007451" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001986" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "7beeb6a1d9af33ede22efb9876d26549" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010802" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001195" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "68b4ed8795c61c185124d2da5dfe0131" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010802" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0031369" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "614ea6dd7e6e8449019f60cd554f2bb8" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007669" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0030997" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "38650e4a07ca7c7ba6c3e6bbe6d4cd86" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012436" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002719" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "fe8ff28949c0e790a3b5a7d4bda7438f" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0014458" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0003109" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "b7fb21983e694ef74d22bc08b33f2aec" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0031481" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000046" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "11a567452a1fc1ef869090285d1089b7" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0008242" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002446" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "1974a9fbfd29a28de60f4483fc65c2b1" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007452" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0031284" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "987c54d297c5572d9bd174627bd071eb" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0031481" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000463" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "44345e96cbacacf247e7abfce845f454" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0010581" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000103" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "1f3d6f020e9abcfd3c200a4095486f5b" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0015790" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000103" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "79dc27cb79f39a874dce6a434d16073e" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007451" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000103" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "7f8e3b0ecb6890a0906a7087d2c87366" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0014523" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0003487" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "10004cc14f75612d8347234d2c2bfcc3" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0030087" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0005487" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "1c625932ea9e61579629e46cec60188f" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0008242" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0005102" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "ce97d32c3fb169a55c535e75df615425" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012192" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000444" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "38723240083a68736363a0dca06a0d47" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007669" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0004379" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "7e5d1946a4f89035a79c9ecce0992f04" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0012192" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000331" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "d9e3c3071c84196249d15fcbc8e63ed5" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0011073" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001944" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "2d9d107968a4c383c599495dd5a83878" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007669" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0005563" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "e02e15acd4514e43ea1d2d7863dda89c" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0031481" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0000293" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "99fd613e2cc70f7aed5956ab69016463" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007669" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0012210" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "6f3b338acd68d48e1070971722084eed" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0030089" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001518" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "fb01d23a8c8c706d2c26497226076e3a" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0100165" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001518" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "5b9b0ba1b87b10530b6ad46ac91c346c" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0030088" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0001518" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "1bf5a981012915959dfd8fce76bed8b7" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007450" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0002171" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "c03488800b5c254fb66ee74a6cea945c" + } + ] + } + }, + { + "node_bindings": { + "disease": [ + { + "id": "MONDO:0007669" + } + ], + "phenotypic_feature": [ + { + "id": "HP:0040270" + } + ] + }, + "edge_bindings": { + "e1_disease_phenotypic_feature": [ + { + "id": "4b9266f35608dc10c1901e5432f4deaf" + } + ] + } + } + ], + "query_graph": { + "edges": { + "e1_disease_phenotypic_feature": { + "subject": "disease", + "object": "phenotypic_feature" + } + }, + "nodes": { + "disease": { + "category": "biolink:Disease", + "id": [ + "NCIT:C160279", + "NCIT:C160277", + "EFO:0000400", + "MONDO:0005015", + "NCIT:C2985", + "NCIT:C159026", + "NCIT:C47836", + "http://purl.obolibrary.org/obo/MAXO_0001115", + "HP:0005978", + "HP:0100651", + "HP:0009800", + "EFO:0004593", + "HP:0004904", + "NCIT:C91449", + "HP:0000831", + "HP:0000877", + "MONDO:0018911", + "NCIT:C114769", + "NCIT:C129739", + "MONDO:0000065", + "HP:0000863", + "EFO:0001360", + "MONDO:0005148", + "NCIT:C26747", + "NCIT:C2986", + "NCIT:C91490", + "NCIT:C91529", + "MONDO:0007450", + "MONDO:0007669", + "MONDO:0006920", + "EFO:1001121", + "NCIT:C84933", + "MONDO:0010863", + "MONDO:0011027", + "MONDO:0011068", + "MONDO:0011073", + "MONDO:0011168", + "MONDO:0011363", + "MONDO:0011507", + "MONDO:0011955", + "EFO:0001359", + "MONDO:0005147", + "MONDO:0005406", + "MONDO:0007451", + "EFO:0004596", + "MONDO:0010581", + "MONDO:0010582", + "MONDO:0007452", + "MONDO:0007454", + "MONDO:0010255", + "MONDO:0008023", + "MONDO:0007453", + "MONDO:0010894", + "MONDO:0008843", + "MONDO:0009432", + "MONDO:0010861", + "MONDO:0010862", + "MONDO:0010864", + "MONDO:0010950", + "MONDO:0011016", + "MONDO:0011033", + "MONDO:0011123", + "MONDO:0011167", + "MONDO:0011302", + "MONDO:0011572", + "MONDO:0005827", + "EFO:0007346", + "MONDO:0010785", + "MONDO:0011667", + "MONDO:0011668", + "NCIT:C123018", + "NCIT:C129741", + "NCIT:C129742", + "NCIT:C129744", + "NCIT:C129745", + "NCIT:C129746", + "MONDO:0015790", + "MONDO:0011386", + "MONDO:0010813", + "MONDO:0011072", + "MONDO:0012192", + "MONDO:0008242", + "MONDO:0009099", + "MONDO:0019846", + "EFO:0004996", + "EFO:0004997", + "MONDO:0010802", + "MONDO:0012963", + "MONDO:0012480", + "MONDO:0014674", + "MONDO:0012921", + "MONDO:0014523", + "MONDO:0013242", + "MONDO:0016391", + "MONDO:0012348", + "MONDO:0012522", + "MONDO:0012919", + "MONDO:0014488", + "MONDO:0030087", + "MONDO:0030088", + "MONDO:0030089", + "MONDO:0022993", + "MONDO:0014458", + "MONDO:0012962", + "MONDO:0012971", + "MONDO:0012961", + "MONDO:0013078", + "MONDO:0012966", + "MONDO:0012422", + "MONDO:0012920", + "MONDO:0013240", + "MONDO:0014589", + "MONDO:0012513", + "MONDO:0012818", + "MONDO:0100164", + "MONDO:0012969", + "MONDO:0012970", + "MONDO:0012436", + "MONDO:0025690", + "MONDO:0031481", + "MONDO:0031010", + "MONDO:0015886", + "MONDO:0100165", + "MONDO:0015887", + "NCIT:C131848", + "MONDO:0015122", + "MONDO:0015967", + "MONDO:0020525", + "NCIT:C131846", + "NCIT:C113407", + "NCIT:C101857", + "NCIT:C106381", + "NCIT:C106382", + "NCIT:C113717", + "NCIT:C113463", + "NCIT:C99532", + "NCIT:C112844", + "NCIT:C112845", + "NCIT:C111913", + "NCIT:C129637", + "NCIT:C129736", + "NCIT:C122685", + "NCIT:C129735", + "NCIT:C131845", + "NCIT:C128671", + "NCIT:C128709", + "NCIT:C128753", + "NCIT:C128756", + "NCIT:C128754", + "NCIT:C128755", + "NCIT:C129743", + "NCIT:C131836", + "NCIT:C131847", + "NCIT:C129760", + "NCIT:C131859", + "NCIT:C114852", + "NCIT:C130996", + "NCIT:C114902", + "NCIT:C163027", + "NCIT:C163028", + "NCIT:C129747", + "NCIT:C160278", + "NCIT:C114899", + "NCIT:C129748", + "NCIT:C172606", + "NCIT:C161704", + "NCIT:C161709", + "NCIT:C161712", + "NCIT:C161713", + "NCIT:C161723", + "NCIT:C161724", + "NCIT:C161731", + "NCIT:C163816", + "NCIT:C163874", + "NCIT:C163878", + "NCIT:C163879", + "NCIT:C163886", + "NCIT:C163817", + "NCIT:C163872", + "NCIT:C163906", + "NCIT:C163911", + "NCIT:C163913", + "NCIT:C163920", + "NCIT:C161313", + "NCIT:C163390", + "NCIT:C163391", + "NCIT:C157813", + "NCIT:C158738", + "NCIT:C161655", + "NCIT:C163929", + "NCIT:C163930", + "NCIT:C163931", + "NCIT:C163935", + "NCIT:C163936", + "NCIT:C163937", + "NCIT:C163401", + "NCIT:C163814", + "NCIT:C163840", + "NCIT:C163841", + "NCIT:C163842", + "NCIT:C163843", + "NCIT:C163844", + "NCIT:C163845", + "NCIT:C163846", + "NCIT:C163847", + "NCIT:C163848", + "NCIT:C163849", + "NCIT:C163850", + "NCIT:C163851", + "NCIT:C163852", + "NCIT:C163853", + "NCIT:C163854", + "NCIT:C163855", + "NCIT:C163856", + "NCIT:C163857", + "NCIT:C163858", + "NCIT:C163859", + "NCIT:C177763", + "NCIT:C20835", + "NCIT:C177938", + "NCIT:C179441", + "NCIT:C179448", + "NCIT:C19756", + "NCIT:C34942", + "NCIT:C43263", + "NCIT:C34537", + "NCIT:C84433", + "NCIT:C84415", + "NCIT:C84919", + "NCIT:C99248", + "EFO:0009706", + "NCIT:C99994", + "EFO:1001503", + "EFO:1001511", + "EFO:0010164", + "EFO:0009756", + "EFO:0009757", + "OMIA:000277", + "MONDO:0016383", + "OMIA:000029", + "OMIA:000279", + "OMIA:000278", + "OMIA:000277-9598", + "OMIA:000277-9615", + "MONDO:0004782" + ] + }, + "phenotypic_feature": { + "category": "biolink:PhenotypicFeature" + } + } + } + } +}; \ No newline at end of file diff --git a/src/tranql/web/src/__tests__/mock/mock_parse_incomplete_forward_select_complete.json b/src/tranql/web/src/__tests__/mock/mock_parse_incomplete_forward_select_complete.json new file mode 100644 index 0000000..aed6485 --- /dev/null +++ b/src/tranql/web/src/__tests__/mock/mock_parse_incomplete_forward_select_complete.json @@ -0,0 +1 @@ +[[[["select", "disease", "->"]]], [[["select", "disease", "->", "from"]]]] \ No newline at end of file diff --git a/src/tranql/web/src/__tests__/mock/mock_parse_incomplete_single_select.js b/src/tranql/web/src/__tests__/mock/mock_parse_incomplete_single_select.js new file mode 100644 index 0000000..24a7f07 --- /dev/null +++ b/src/tranql/web/src/__tests__/mock/mock_parse_incomplete_single_select.js @@ -0,0 +1 @@ +module.exports = [[[["select"]]], [[["select"]]]]; \ No newline at end of file diff --git a/src/tranql/web/src/__tests__/mock/mock_reasoner_urls.json b/src/tranql/web/src/__tests__/mock/mock_reasoner_urls.json new file mode 100644 index 0000000..d0bbad7 --- /dev/null +++ b/src/tranql/web/src/__tests__/mock/mock_reasoner_urls.json @@ -0,0 +1 @@ +{"redis": "redis:test"} \ No newline at end of file diff --git a/src/tranql/web/src/__tests__/mock/mock_schema.json b/src/tranql/web/src/__tests__/mock/mock_schema.json new file mode 100644 index 0000000..f8fb35b --- /dev/null +++ b/src/tranql/web/src/__tests__/mock/mock_schema.json @@ -0,0 +1,388 @@ +{ + "schema":{ + "knowledge_graph":{ + "nodes":[ + [ + "anatomical_entity", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "disease", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "cell", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "polypeptide", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "small_molecule", + { + "reasoner":[ + "redis" + ] + } + ] + ], + "edges":[ + [ + "anatomical_entity", + "disease", + "treats", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "anatomical_entity", + "disease", + "biomarker_for", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "disease", + "disease", + "has_phenotype", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "disease", + "disease", + "correlated_with", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "disease", + "disease", + "contributes_to", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "disease", + "phenotypic_feature", + "has_phenotype", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "disease", + "phenotypic_feature", + "correlated_with", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "disease", + "cell", + "correlated_with", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "cell", + "cell", + "interacts_with", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "complex_molecular_mixture", + "phenotypic_feature", + "biomarker_for", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "complex_molecular_mixture", + "phenotypic_feature", + "treats", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "complex_molecular_mixture", + "disease", + "biomarker_for", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "complex_molecular_mixture", + "disease", + "treats", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "phenotypic_feature", + "disease", + "biomarker_for", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "phenotypic_feature", + "disease", + "treats", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "phenotypic_feature", + "phenotypic_feature", + "biomarker_for", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "phenotypic_feature", + "phenotypic_feature", + "treats", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "molecular_mixture", + "disease", + "biomarker_for", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "molecular_mixture", + "disease", + "treats", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "molecular_mixture", + "phenotypic_feature", + "biomarker_for", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "molecular_mixture", + "phenotypic_feature", + "treats", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "organism_taxon", + "disease", + "biomarker_for", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "organism_taxon", + "disease", + "treats", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "organism_taxon", + "phenotypic_feature", + "treats", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "organism_taxon", + "phenotypic_feature", + "biomarker_for", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "polypeptide", + "disease", + "biomarker_for", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "polypeptide", + "disease", + "treats", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "polypeptide", + "phenotypic_feature", + "biomarker_for", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "polypeptide", + "phenotypic_feature", + "treats", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "small_molecule", + "phenotypic_feature", + "treats", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "small_molecule", + "phenotypic_feature", + "biomarker_for", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "small_molecule", + "disease", + "biomarker_for", + { + "reasoner":[ + "redis" + ] + } + ], + [ + "small_molecule", + "disease", + "treats", + { + "reasoner":[ + "redis" + ] + } + ] + ] + }, + "knowledge_map":[ + { + + } + ], + "options":{ + + } + } +} \ No newline at end of file diff --git a/src/tranql/web/src/autocomplete.js b/src/tranql/web/src/autocomplete.js new file mode 100644 index 0000000..99551ff --- /dev/null +++ b/src/tranql/web/src/autocomplete.js @@ -0,0 +1,645 @@ +import * as CodeMirror from 'codemirror'; + +require('./codemirror-tooltip-extension/text-hover.js'); +require('./codemirror-tooltip-extension/text-hover.css'); + +const NO_ERROR_MESSAGE = true; + +/** +* Callback for handling autocompletion within the query editor. +* +* Note: Since this function is so long, it's been moved into its own file. +* App.js imports this function and binds it to the App context. It will not +* function outside of the App context. If this ever becomes problematic, +* just the parsing logic can be moved into its own function. +* +* @param {object} cm - The CodeMirror object. +* @private +*/ +export default function autoComplete () { + // https://github.com/JedWatson/react-codemirror/issues/52 + var codeMirror = this._codemirror; + + // See field declaration in App.js for explanation of Symbol usage. + const autoCompleteInstance = Symbol(); + this._autoCompleteInstance = autoCompleteInstance; + + // hint options for specific plugin & general show-hint + // 'tables' is sql-hint specific + // 'disableKeywords' is also sql-hint specific, and undocumented but referenced in sql-hint plugin + // Other general hint config, like 'completeSingle' and 'completeOnSingleClick' + // should be specified here and will be honored + + // Shallow copy it. + const pos = Object.assign({}, codeMirror.getCursor()); + const untrimmedPos = codeMirror.getCursor(); + const textToCursorPositionUntrimmed = codeMirror.getRange({ line : 0, ch : 0 }, { line : pos.line, ch : pos.ch }); + const textToCursorPosition = textToCursorPositionUntrimmed.trimRight(); + const entireText = codeMirror.getValue(); + + // const splitLines = textToCursorPosition.split(/\r\n|\r|\n/); + // // Adjust the position after trimming to be on the correct line. + // pos.line = splitLines.length - 1; + // // Adjust the position after trimming to be on the correct char. + // pos.ch = splitLines[splitLines.length-1].length; + + const isAutocompletionClosed = () => this._autoCompleteInstance !== autoCompleteInstance; + + const setHint = function(options, noResultsTip) { + // Prevent writing from stale calls. + if (isAutocompletionClosed()) return; + if (typeof noResultsTip === 'undefined') noResultsTip = true; + if (noResultsTip && options.length === 0) { + options.push({ + text: String(''), + displayText:'No valid results' + }); + } + const hintOptions = { + // tables: tables, + hint: function() { + return { + from: pos, + to: untrimmedPos, + list: options.map((option) => { + // Process custom options - `replaceText` + if (option.hasOwnProperty('replaceText')) { + let replaceText = option.replaceText; + let from = option.hasOwnProperty('from') ? option.from : pos; + let to = option.hasOwnProperty('to') ? option.to : untrimmedPos; + + option.from = { line : from.line, ch : from.ch - replaceText.length }; + option.to = { line : to.line, ch : to.ch}; + + // if (replaceText.length > 0) { + // const trimmedLines = textToCursorPositionUntrimmed.trimRight().split(/\r\n|\r|\n/); + // const lastLine = trimmedLines[trimmedLines.length-1]; + // option.from.line = trimmedLines.length - 1; + // option.from.ch = lastLine.length - replaceText.length; + // } + + + delete option.replaceText; + } + + return option; + }) + }; + }, + disableKeywords: true, + completeSingle: false, + completeOnSingleClick: false + }; + + codeMirror.showHint(hintOptions); + // codeMirror.state.completionActive.pick = () => { + // codeMirror.showHint({ + // hint: function() { + // return { + // from: pos, + // to: pos, + // list: [{ + // text: String(''), + // displayText: 'foobar', + // className: 'testing' + // }] + // }; + // }, + // disableKeywords: true, + // completeSingle: false, + // }); + // } + } + + const setError = (resultText, status, errors, resultOptions) => { + // Prevent writing from stale calls. + if (isAutocompletionClosed()) return; + if (NO_ERROR_MESSAGE) { + // Simply close the autocomplete prompt without any messages. + codeMirror.closeHint(); + return; + } + if (typeof resultOptions === "undefined") resultOptions = {}; + codeMirror.showHint({ + hint: function() { + return { + from: pos, + to: pos, + list: [{ + text: String(''), + displayText: resultText, + className: 'autocomplete-result-error', + ...resultOptions, + }] + }; + }, + disableKeywords: true, + completeSingle: false, + }); + if (typeof status !== "undefined" && typeof errors !== "undefined") { + codeMirror.state.completionActive.pick = () => { + this._handleMessageDialog (status, errors); + } + } + } + + const setLoading = function(loading) { + // Prevent writing from stale calls. + if (isAutocompletionClosed()) return; + if (loading) { + // text property has to be String('') because when it is '' (falsey) it refuses to display it. + codeMirror.showHint({ + hint: function() { + return { + from: pos, + to: pos, + list: [{ + text: String(''), + displayText: 'Loading', + className: 'loading-animation' + }] + }; + }, + disableKeywords: true, + completeSingle: false, + }); + } + else { + codeMirror.closeHint(); + } + } + + /** + * TODO: + * could try to see if its possible to have two select menus for predicates that also show concepts from the predicates + * would look something like this image, when, for example, you pressed the right arrow or left clicked or something on a predicate: + * https://i.imgur.com/LBsdrcq.png + * could somehow see if there's a way to have predicate suggestion work properly when there's a concept already following the predicate + * Ex: 'select foo-[what_can_I_put_here?]->baz' + * Would involve sending more of the query instead of cutting it off at cursor. + * Then would somehow have to backtrack and locate which token the cursor's position translates to. + */ + + this._autoCompleteController.abort(); + this._autoCompleteController = new window.AbortController(); + + setLoading(true); + + fetch(this.tranqlURL + '/tranql/parse_incomplete', { + signal: this._autoCompleteController.signal, + method: "POST", + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify([textToCursorPositionUntrimmed, entireText]) + }).then(res => res.json()) + .then(async (parsedTree) => { + setLoading(false) + + if (parsedTree.errors) { + // this._handleMessageDialog (parsedTree.status, parsedTree.errors); + setError("Failed to parse", parsedTree.status, parsedTree.errors); + } + else { + setLoading(true); + await this.schemaPromise; + setLoading(false); + const graph = this.state.schemaMessage.knowledge_graph; + + // Recursviely removes any tokens that are linebreaks from a parsed tree. + const stripLinebreaks = function(tree) { + if (Array.isArray(tree)) { + return tree.filter((token) => stripLinebreaks(token)); + } + else { + return tree.toString().match(/\r\n|\r|\n/) === null; + } + } + + const incompleteTree = parsedTree[0]; + const completeTree = parsedTree[1]; + + // Filter whitespace from the statements + const block = incompleteTree[incompleteTree.length-1].map((statement) => { + return stripLinebreaks(statement); + }); + const completeBlock = completeTree[completeTree.length-1].map((statement) => { + return stripLinebreaks(statement); + }); + + const lastStatement = block[block.length-1]; + const lastStatementComplete = completeBlock[block.length-1]; + + const statementType = lastStatement[0]; + + setLoading(true); + const fromOptions = await this.reasonerURLs; + setLoading(false); + + fromOptions["/schema"] = "/schema"; + + const whereOptions = [ + 'testing', + 'foobar' + ]; + + const concept_arrows = [ + '->', + '<-' + ]; + + const all_arrows = [ + '->', + '<-', + '-[', + '<-[' + ]; + + const arrow_to_pred_arrow = (arrow) => { + return { + '->' : [ + '-[', + '', + ']->' + ], + '<-' : [ + '<-[', + '', + ']-' + ] + }[arrow]; + } + + const arrowToEmptyPredicate = (arrow) => { + return arrow_to_pred_arrow(arrow); + } + + const isBackwardsPredicate = (predicate) => { + return predicate[0] === '<-['; + } + + const toForwardPredicate = (predicate) => { + predicate[0] = '-['; + predicate[2] = ']->'; + return predicate; + } + + const completePredicate = (predicate) => { + if (isBackwardsPredicate (predicate)) { + predicate[2] = arrow_to_pred_arrow("<-")[2]; + } + else { + predicate[2] = arrow_to_pred_arrow("->")[2]; + } + return predicate; + } + + const concept = (old_concept) => { + // Concept identifiers aren't actually parsed by the lexer, but rather the ast in Query::add. + // This just copies the methods that the ast uses to parse concept identifiers. + if (old_concept.indexOf(":") !== -1) { + const split = old_concept.split(":"); + if (split.length - 1 > 1) { + throw new Error(`Invalid concept identifier "${old_concept}"`); + } + const [name, type_name] = split; + return type_name; + } + else { + return old_concept; + } + } + + const lastToken = lastStatement[lastStatement.length-1]; + const secondLastToken = lastStatement[lastStatement.length-2]; + const thirdLastToken = lastStatement[lastStatement.length-3]; + + // It's difficult to tell when parsing "SELECT disease->| FROM ...", whether "FROM" is the second concept, or if it starts the FROM statement. + // In the ordinary parser, "FROM" will be parsed as a concept name here, but obviously this is almost 99% of the time not the intention when + // using autocomplete. + // Thus, in autocompletion specifcially, let's blacklist FROM as a concept name (the incomplete parser itself will still parse it as a concept name), + // for the sake of convenience. Of course, if by some chance "from" becomes a valid concept in the biolink model, this will a niche bug, but it's probably + // better for the sake of avoiding the headache that would be working around this fringe case. + let hasNoNextConcept = (lastStatementComplete.length - lastStatement.length) == 0; + let nextConcept = hasNoNextConcept ? undefined : concept(lastStatementComplete[lastStatement.length]) ; + // See https://github.com/frostyfan109/tranql/issues/117 for an older detailed breakdown on the issue. + if (typeof nextConcept === "string" && nextConcept.toLowerCase() === "from") nextConcept = undefined; + + console.log(statementType, lastStatement, lastToken); + + // Try/catch the entirety of the logic + try { + if (statementType === 'select') { + let validConcepts; + if (lastToken === "-") { + // Arrow suggestion + // "select foo-" + validConcepts = all_arrows.map((arrow) => { + return { + displayText: arrow, + text: arrow, + replaceText: "-" + }; + }); + } + else if (Array.isArray(lastToken) && lastToken.length < 3) { + // If the last token is an array and not length 3 then it is an incomplete predicate. + // "select foo-[" or "select foo-[bar" + let currentPredicate = completePredicate([ + lastToken[0], + lastToken[1] !== undefined ? lastToken[1] : "" + ]); + let previousConcept = concept(secondLastToken); + + const backwards = isBackwardsPredicate (currentPredicate); + + console.log ([previousConcept, currentPredicate, nextConcept]); + + // Should replace this method with reduce + const allEdges = graph.edges.filter((edge) => { + if (backwards) { + return edge.target_id === previousConcept && + (nextConcept === undefined || edge.source_id === nextConcept) && + edge.type.startsWith(currentPredicate[1]); + } + else { + return ( + edge.source_id === previousConcept && + (nextConcept === undefined || edge.target_id === nextConcept) && + edge.type.startsWith(currentPredicate[1]) + ); + } + }); + const uniqueEdgeMap = {}; + allEdges.forEach((edge) => { + if (!uniqueEdgeMap.hasOwnProperty(edge.type)) { + uniqueEdgeMap[edge.type] = edge; + } + }); + const uniqueEdges = Object.values(uniqueEdgeMap); + validConcepts = uniqueEdges.sort((e1, e2) => e2.score - e1.score).map((edge) => { + const replaceText = currentPredicate[1]; + // const actualText = type + currentPredicate[2]; + const conceptHint = " (" + (backwards ? edge.source_id : edge.target_id) + ")"; + const actualText = edge.type; + const displayText = edge.type; + return { + displayText: displayText, + text: actualText, + replaceText : replaceText + }; + }); + } + else { + // Otherwise, we are handling autocompletion of a concept. + let currentConcept = ""; + let predicate = null; + let previousConcept = null; + + if (lastToken === statementType) { + // "select" + } + else if (secondLastToken === statementType) { + // "select foo" + currentConcept = concept(lastToken); + } + else if (concept_arrows.includes(lastToken) || Array.isArray(lastToken)) { + // "select foo->" or "select foo-[bar]->" + predicate = lastToken; + previousConcept = concept(secondLastToken); + } + else { + previousConcept = concept(thirdLastToken); + predicate = secondLastToken; + currentConcept = concept(lastToken); + } + + + if (predicate === null) { + // Predicate will only be null if there are no arrows, and therefore the previousConcept is also null. + // Single concept - just "select" or "select foo" where the concept is either "" or "foo" + validConcepts = graph.nodes.filter((node) => node.type.startsWith(currentConcept)).map(node => node.type); + } + else { + // If there is a predicate, we have to factor in the previous concept, the predicate, and the current concept. + if (!Array.isArray(predicate)) { + // We want to assign an empty predicate + predicate = arrowToEmptyPredicate (predicate); + } + + const backwards = isBackwardsPredicate (predicate); + + console.log ([previousConcept, predicate, currentConcept]); + // Concepts could be named like select f1:foo->f2:bar + // we need to split them and grab the actual types + let previousConceptSplit = previousConcept.split(':'); + let currentConceptSplit = currentConcept.split(':'); + previousConcept = previousConceptSplit[previousConceptSplit.length - 1]; + currentConcept = currentConceptSplit[currentConceptSplit.length - 1]; + validConcepts = graph.edges.filter((edge) => { + if (backwards) { + return ( + edge.source_id.startsWith(currentConcept) && + edge.target_id === previousConcept && + (predicate[1] === "" || edge.type === predicate[1]) + ); + } + else { + return ( + edge.source_id === previousConcept && + edge.target_id.startsWith(currentConcept) && + (predicate[1] === "" || edge.type === predicate[1]) + ); + } + }).map((edge) => { + if (backwards) { + return edge.source_id; + } + else { + return edge.target_id + } + }) + } + const getEdgeCount = (sourceName, targetName) => ( + graph.edges.filter((edge) => edge.source_id === sourceName && edge.target_id === targetName).length + ); + const getConnectivityScore = (concept) => { + // Since select statement completion isn't really supported on backwards arrows yet, just sort as if it's a forwards arrow. + return getEdgeCount(previousConcept, concept); + } + validConcepts = validConcepts.unique() + .sort((conceptA, conceptB) => getConnectivityScore(conceptB) - getConnectivityScore(conceptA)) + .map((concept) => { + return { + displayText: concept, + text: concept, + replaceText: currentConcept + }; + }); + } + setHint(validConcepts); + + } + else if (statementType === 'from') { + let currentReasonerArray = lastStatement[1]; + let startingQuote = ""; + if (currentReasonerArray === undefined) { + // Adds an apostrophe to the start of the string if it doesn't have one ("from") + startingQuote = "'"; + currentReasonerArray = [[ + "'", + "" + ]]; + } + const endingQuote = currentReasonerArray[currentReasonerArray.length - 1][0]; + const currentReasoner = currentReasonerArray[currentReasonerArray.length - 1][1]; + const tokens = parseTokensFromSelect(block); + + let validReasoners = []; + + Object.keys(fromOptions).forEach((reasoner) => { + let valid = true; + if (tokens.length === 1) { + // Handles if there's only one concept ("select foo") + const currentConcept = concept(tokens[0]); + graph.nodes.filter((node) => node.type.startsWith(currentConcept)).forEach(node => node.reasoner.forEach((reasoner) => { + !validReasoners.includes(reasoner) && validReasoners.push(reasoner); + })); + } + else { + for (let i=0;i { + if (backwards) { + return ( + edge.source_id.startsWith(currentConcept) && + edge.target_id === previousConcept && + (predicate[1] === "" || edge.type === predicate[1]) && + (reasoner === "/schema" || edge.reasoner.includes(reasoner)) + ); + } + else { + return ( + edge.source_id === previousConcept && + edge.target_id.startsWith(currentConcept) && + (predicate[1] === "" || edge.type === predicate[1]) && + (reasoner === "/schema" || edge.reasoner.includes(reasoner)) + ); + } + }).length > 0; + if (!isTransitionValid) { + valid = false; + break; + } + } + if (valid) { + validReasoners.push(reasoner); + } + } + }); + + const validReasonerValues = validReasoners.map((reasoner) => { + return fromOptions[reasoner]; + }).filter((reasonerValue) => { + return reasonerValue.startsWith(currentReasoner); + }).map((reasonerValue) => { + return { + displayText: reasonerValue, + text: startingQuote + reasonerValue, + // text: startingQuote + reasonerValue + endingQuote, + replaceText: currentReasoner + }; + }); + + setHint(validReasonerValues); + } + else if (statementType === 'where') { + let whereBody = lastStatement[1]; + // Just "WHERE" -> suggest concepts in the select statement + if (whereBody === undefined) { + whereBody = [""]; + } + if (whereBody.length === 1) { + const [whereConcept] = whereBody; + const tokens = parseTokensFromSelect(block); + // Concepts (node selectors) are every 2n index, starting from n=0, i.e. 0, 2, 4, ... + // while edges are every 2n+1 index, i.e. 1, 3, 5, ... + // - For example, select disease->phenotypic feature would return tokens ['disease', '->', 'phenotypic_feature'] + // and return concepts ['disease', 'phenotypic_feature']. + // Then filter concepts to only those that start with the current text + const concepts = tokens.filter((token, i) => i % 2 === 0).filter((concept) => concept.startsWith(whereConcept)); + const hints = concepts.map((concept) => ({ + displayText: concept, + // text: concept + `="`, + text: concept, + replaceText: whereConcept + })); + setHint(hints); + } + else if (whereBody.length === 2 || whereBody.length === 3) { + let [ whereConcept, equals, whereValueFull ] = whereBody; + // If there is no value/open quotes, e.g. `disease=`, change it to `disease="` + if (whereValueFull === undefined) { + whereValueFull = [`"`, ""]; + } else if (typeof whereValueFull === "string") { + // A completed where statement, with opening and closing quotes, e.g. `where disease="MONDO:XXX"` + return; + } + const [ openQuote, whereValue ] = whereValueFull; + // Get possible values for this concept. + // Note that a side-effect of this function is that it automatically stores the results within App state for autocomplete purposes. + // Also note that this function will always return empty when whereValue is an empty string (due to API limitations) + try { + const maxResults = 10; + setLoading(true); + const possibleValues = await this._resolveIdentifiersFromConcept(whereValue, whereConcept, 250, maxResults); + setLoading(false); + // resultLimit limits the number of results that can be returned by name resolution (typeless) but these results are then + // culled by type-checking them, meaning there'll be much less than `resultLimit` results. Thus, we'll use a very high + // resultLimit to ensure an adequate number of type-checked results are returned and then only use the first few of them. + // const possibleValues = Object.fromEntries(Object.entries(possibleValuesFull).slice(0, maxResults)); + const hints = Object.entries(possibleValues).sort(([_, result1], [__, result2]) => result2.score - result1.score).map(([curie, info]) => ({ + displayText: info.preferredLabel, + text: info.preferredCurie, + replaceText: whereValue + })); + setHint(hints); + } catch (e) { + // Handle fetch requests to the APIs failing. + setError('Error', 'API request failed', [{message: e.message, details: e.stack}]); + } + + } + } + } + catch (e) { + setError('Failed to parse', 'Failed to parse', [{message: e.message, details: e.stack}]); + } + } + }) + .catch((error) => { + if (error.name !== "AbortError") { + setError('Error', 'Error', [{message: error.message, details: error.stack}]); + } + }); +} + +function parseTokensFromSelect(block) { + // The select statement must be the first statement in the block, but thorough just in case. + // We also want to filter out whitespace that would be detected as a token. + const selectStatement = block.filter((statement) => statement[0] === "select")[0].filter((token) => { + return typeof token !== "string" || token.match(/\s/) === null; + }); + // Don't want the first token ("select") + const concepts = selectStatement.slice(1); + return concepts; +} \ No newline at end of file diff --git a/src/tranql/web/src/codemirror-tooltip-extension/text-hover.css b/src/tranql/web/src/codemirror-tooltip-extension/text-hover.css new file mode 100644 index 0000000..548172a --- /dev/null +++ b/src/tranql/web/src/codemirror-tooltip-extension/text-hover.css @@ -0,0 +1,24 @@ +.CodeMirror-hover-tooltip { + background-color: infobackground; + border: 1px solid black; + border-radius: 4px 4px 4px 4px; + color: infotext; + font-family: monospace; + font-size: 10pt; + overflow: hidden; + padding: 2px 5px; + position: fixed; + z-index: 100; + max-width: 600px; + opacity: 0; + white-space: pre-wrap; + transition: opacity .4s; + -moz-transition: opacity .4s; + -webkit-transition: opacity .4s; + -o-transition: opacity .4s; + -ms-transition: opacity .4s; +} + +/*.CodeMirror-hover { + outline: 1px solid grey; +}*/ \ No newline at end of file diff --git a/src/tranql/web/src/codemirror-tooltip-extension/text-hover.js b/src/tranql/web/src/codemirror-tooltip-extension/text-hover.js new file mode 100644 index 0000000..a8bef14 --- /dev/null +++ b/src/tranql/web/src/codemirror-tooltip-extension/text-hover.js @@ -0,0 +1,198 @@ +import * as CodeMirror from 'codemirror'; + +export function getCurieFromCMToken(string) { + // Cut the starting and ending quotation marks/apostrophes out of the value. + let value = string.slice(1); + // A string may not necessarily be closed yet, so only remove the last character if it is, in fact, closed. + if (value.endsWith(`"`) || value.endsWith(`'`)) value = value.slice(0, -1); + return value; +} + +(function() { + "use strict"; + + var HOVER_CLASS = " CodeMirror-hover"; + + function showTooltip(e, content) { + var tt = document.createElement("div"); + tt.className = "CodeMirror-hover-tooltip"; + if (typeof content == "string") { + content = document.createTextNode(content); + } + if (content.innerText === "") tt.style.display = "none"; + else tt.style.display = "block"; + tt.appendChild(content); + document.body.appendChild(tt); + + function position(e) { + if (!tt.parentNode) + return CodeMirror.off(document, "mousemove", position); + tt.style.top = Math.max(0, e.clientY - tt.offsetHeight - 5) + "px"; + tt.style.left = (e.clientX + 5) + "px"; + } + CodeMirror.on(document, "mousemove", position); + position(e); + if (tt.style.opacity != null) + tt.style.opacity = 1; + return tt; + } + function rm(elt) { + if (elt.parentNode) + elt.parentNode.removeChild(elt); + } + function hideTooltip(tt) { + if (!tt.parentNode) + return; + if (tt.style.opacity == null) + rm(tt); + tt.style.opacity = 0; + setTimeout(function() { + rm(tt); + }, 600); + } + + function showTooltipFor(e, content, node, state, cm) { + var tooltip = showTooltip(e, content); + function hide() { + CodeMirror.off(node, "mouseout", hide); + CodeMirror.off(node, "click", hide); + node.className = node.className.replace(HOVER_CLASS, ""); + if (tooltip) { + hideTooltip(tooltip); + tooltip = null; + } + cm.removeKeyMap(state.keyMap); + } + var poll = setInterval(function() { + if (tooltip) + for ( var n = node;; n = n.parentNode) { + if (n == document.body) + return; + if (!n) { + hide(); + break; + } + } + if (!tooltip) + return clearInterval(poll); + }, 400); + CodeMirror.on(node, "mouseout", hide); + CodeMirror.on(node, "click", hide); + state.keyMap = {Esc: hide}; + cm.addKeyMap(state.keyMap); + } + + function TextHoverState(cm, options) { + this.options = options; + this.timeout = null; + if (options.delay) { + this.onMouseOver = function(e) { + onMouseOverWithDelay(cm, e); + }; + } else { + this.onMouseOver = function(e) { + onMouseOver(cm, e); + }; + } + this.keyMap = null; + } + + function parseOptions(cm, options) { + if (options instanceof Function) + return { + getTextHover : options + }; + if (!options || options === true) + options = {}; + if (!options.getTextHover) + options.getTextHover = function(cm, data, node) { + var html = null; + if (data) { + var {string, type, start, end } = data.token; + if (type === "string") { + const value = getCurieFromCMToken(string); + // Check if the value is a curie that is cached. If so, add a tooltip with the curie's English label. + if (cm.state.resolvedIdentifiers && cm.state.resolvedIdentifiers[value]) { + html = cm.state.resolvedIdentifiers[value].preferredLabel; + } + } + } + var result = document.createElement('div'); + if (html !== null) { + result.innerHTML = html; + } + return result; + } + if (!options.getTextHover) + throw new Error( + "Required option 'getTextHover' missing (text-hover addon)"); + return options; + } + + function onMouseOverWithDelay(cm, e) { + var state = cm.state.textHover, delay = state.options.delay; + clearTimeout(state.timeout); + if (e.srcElement) { + // hack for IE, because e.srcElement failed when it is used in the tiemout function + var newE = {srcElement: e.srcElement, clientX : e.clientX, clientY: e.clientY}; + e = newE; + } + state.timeout = setTimeout(function() {onMouseOver(cm, e);}, delay); + } + + function onMouseOver(cm, e) { + var node = e.target || e.srcElement; + if (node) { + if (/\bCodeMirror-lint-mark-/.test(node.className)) return; // hack if lint addon is used to give it a higher priority + var state = cm.state.textHover, data = getTokenAndPosAt(cm, e); + var content = state.options.getTextHover(cm, data, e); + if (content) { + node.className += HOVER_CLASS; + if (typeof content == 'function') + content(showTooltipFor, data, e, node, state, cm); + else + showTooltipFor(e, content, node, state, cm); + } + } + } + + function optionHandler(cm, val, old) { + if (old && old != CodeMirror.Init) { + CodeMirror.off(cm.getWrapperElement(), "mouseover", + cm.state.textHover.onMouseOver); + delete cm.state.textHover; + } + + if (val) { + var state = cm.state.textHover = new TextHoverState(cm, parseOptions(cm, + val)); + CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver); + } + } + + // When the mouseover fires, the cursor might not actually be over + // the character itself yet. These pairs of x,y offsets are used to + // probe a few nearby points when no suitable marked range is found. + var nearby = [ 0, 0, 0, 5, 0, -5, 5, 0, -5, 0 ]; + + function getTokenAndPosAt(cm, e) { + var node = e.target || e.srcElement, text = node.innerText + || node.textContent; + for ( var i = 0; i < nearby.length; i += 2) { + var pos = cm.coordsChar({ + left : e.clientX + nearby[i], + top : e.clientY + nearby[i + 1] + }); + var token = cm.getTokenAt(pos); + if (token && token.string === text) { + return { + token : token, + pos : pos + }; + } + } + } + + CodeMirror.defineOption("textHover", false, optionHandler); // deprecated + +})(); \ No newline at end of file diff --git a/src/tranql/web/src/setupTests.js b/src/tranql/web/src/setupTests.js new file mode 100644 index 0000000..a383a82 --- /dev/null +++ b/src/tranql/web/src/setupTests.js @@ -0,0 +1,38 @@ +jest.mock("../node_modules/elasticlunr/elasticlunr.js", () => {}); + +// Test the website served through Node.js +export const WEBSITE_URL = "http://localhost:3000"; + +// The amount of time (in ms) that tests will periodically sleep for when browser mode is set to DEBUG. +export const SLEEP_INTERVAL = 1000; +export const BrowserMode = Object.freeze({ + // Fastest, runs tests on a headless Chromium instance. + HEADLESS: 0, + // Runs on a headful (UI-enabled) Chromium instance. This can be enabled in order to see what's happening during the tests. + HEADFUL_FAST: 1, + // Same as HEADFUL_FAST, but has built in sleep intervals to better assess what's happening during the tests. + DEBUG: 2, + // Alias of DEBUG + HEADFUL_SLOW: 2 +}); + +/* Setup using environment variables */ +// If BROWSER_MODE is set, use that, else use default (DEBUG). +const BROWSER_MODE = process.env.BROWSER_MODE ? BrowserMode[process.env.BROWSER_MODE] : BrowserMode.DEBUG +// If MOCKING is set ("true" or "false"), convert it from string to bool, else use default (true). +const MOCKING = process.env.MOCKING ? (process.env.MOCKING === "true") : true; +// If SANDBOX is set ("true" or "false"), convert it from string to bool, else use default (true). +// Be aware that this option should be left true unless it's necessary for it to be disabled. +// When disabled, it will disable Chromium sandboxing which exposes security vulnerabilities. +const SANDBOX = process.env.SANDBOX ? (process.env.SANDBOX === "true") : true; + +// Initialize testing arguments for tesss +global.args = { + // Should tests intercept API calls with mock requests (i.e. schema/graph queries) + mocking: MOCKING, + // Configures whether the Chromium instance will run headless or not + browserMode: BROWSER_MODE, + sandbox: SANDBOX +}; + +jest.setTimeout(60000); \ No newline at end of file diff --git a/src/tranql/web/src/static/app_data/example_queries.js b/src/tranql/web/src/static/app_data/example_queries.js new file mode 100644 index 0000000..00699d2 --- /dev/null +++ b/src/tranql/web/src/static/app_data/example_queries.js @@ -0,0 +1,108 @@ +module.exports = [ + { + title: 'Protein-Metabolite Interaction', + query: +`-- What proteins are targetted by the metabolite KEGG:C00017? + +set metabolite = "KEGG:C00017" + +select metabolite->protein +from "/graph/rtx" +where metabolite=$metabolite + +` + }, + { + title: 'Chemical substances target genes that target asthma', + query: +`-- Which chemical substances target genes that target asthma? +select chemical_substance->gene->disease +from "/graph/gamma/quick" +where disease="asthma" +` + }, + { + title: 'Usage of predicates to narrow results', + query: +`-- Which chemical substances decrease activity of genes that contribute to asthma? +select chemical_substance-[decreases_activity_of]->gene-[contributes_to]->disease +from "/graph/gamma/quick" +where disease="asthma" +` + }, + { + title: 'Phenotypic Feature-Disease Association', + query: +`-- What diseases are associated with the phenotypic feature HP:0005978? + +select phenotypic_feature->disease +from "/graph/rtx" +where phenotypic_feature="HP:0005978" +` + }, + { + title: 'Drug-Disease Pair', + query: +`-- +-- Produce clinial outcome pathways for this drug disease pair. +-- + +set drug = 'PUBCHEM:2083' +set disease = 'MONDO:0004979' + +select chemical_substance->gene->anatomical_entity->phenotypic_feature<-disease +from '/graph/gamma/quick' +where chemical_substance = $drug +and disease = $disease` + }, + { + title: 'Drug Targets Gene', + query: +`-- +-- What drug targets some gene? +-- + +set target_gene = 'HGNC:6871' --mapk1 +select chemical_substance->gene +from '/graph/gamma/quick' +where gene = $target_gene` + }, + { + title: 'Tissue-Disease Association', + query: +`-- +-- What tissue types are associated with [disease]? +-- +set disease = 'asthma' +select disease->anatomical_feature->cell +from '/graph/gamma/quick' +where disease = $disease +` + }, + { + title: 'Workflow 5 v3', + query: +`-- +-- Workflow 5 +-- +-- Modules 1-4: Chemical Exposures by Clinical Clusters +-- For ICEES cohorts, eg, defined by differential population +-- density, which chemicals are associated with these +-- cohorts with a p_value lower than some threshold? +-- +-- Modules 5-*: Knowledge Graph Phenotypic Associations +-- For chemicals produced by steps 1-4, what phenotypes are +-- associated with exposure to these chemicals? +-- + +SELECT population_of_individual_organisms->chemical_substance->gene->biological_process_or_activity<-phenotypic_feature +FROM "/schema" +WHERE icees.table = 'patient' +AND icees.year = 2010 +AND icees.cohort_features.AgeStudyStart = '0-2' +AND icees.feature.EstResidentialDensity < 1 +AND icees.maximum_p_value = 1 +AND chemical_substance !=~ '^(SCTID.*|rxcui.*|CAS.*|SMILES.*|umlscui.*)$' +AND icees.regex = "(MONDO|HP):.*""` + } +]; \ No newline at end of file diff --git a/src/tranql/web/src/testUtil.js b/src/tranql/web/src/testUtil.js new file mode 100644 index 0000000..90541df --- /dev/null +++ b/src/tranql/web/src/testUtil.js @@ -0,0 +1,167 @@ +/** + * Utility functions to be reused specifically within Jest unit tests. + */ + +import { Puppeteer } from 'puppeteer'; +import App from './App.js'; + +/** + * Resolves once a response for an API request completes on a page. + * + * @param {Puppeteer.Page} page + * @param {String} apiPath - Relative API path, e.g. "/tranql/query" + * @returns {Promise} The response + */ +export function pageFinishesRequest(page, apiPath) { + return new Promise((resolve) => { + page.on("response", (resp) => { + const url = new URL(resp.url()); + if (url.origin + url.pathname === App.prototype.tranqlURL + apiPath) { + resolve(resp); + } + }); + }); +} + +/** + * Intercepts an endpoint and sends a mock response. + * This function will take args.mocking into account automatically, + * letting the request proceed normally when mocking is disabled. + * + * @param {Puppeteer.Page} page + * @param {String} endpoint - Relative API path, e.g. "/tranql/query" + * + */ + +export class Mocker { + /** + * + * @param {object} options + * @param {string} options.verbose - Verbose information about mocking. + */ + constructor(options) { + if (typeof options === "undefined") options = {}; + this.options = { + verbose: options.verbose || false + }; + this.mocks = [ + { + name: "query_disease_phenotypic_feature_diabetes", + endpoint: "/tranql/query", + request: { + body: `select disease->phenotypic_feature + from "redis:test" +where disease="diabetes"`, + }, + response: { + contentType: "application/json", + body: JSON.stringify(require("./__tests__/mock/mock_graph.js")) + } + }, + { + name: "schema", + endpoint: "/tranql/schema", + response: { + contentType: "application/json", + body: JSON.stringify(require("./__tests__/mock/mock_schema.json")) + } + }, + { + name: "parse_incomplete_single_select", + endpoint: "/tranql/parse_incomplete", + request: { + body: `["select ","select "]` + }, + response: { + contentType: "application/json", + body: JSON.stringify(require("./__tests__/mock/mock_parse_incomplete_single_select.js")) + } + }, + { + name: "parse_incomplete_forward_select_complete", + endpoint: "/tranql/parse_incomplete", + request: { + body: `["select disease->","select disease->\\n from \\"redis:test\\"\\nwhere disease=\\"asthma\\""]` + }, + response: { + contentType: "application/json", + body: JSON.stringify(require("./__tests__/mock/mock_parse_incomplete_forward_select_complete.json")) + } + }, + { + name: "reasoner_urls", + endpoint: "/tranql/reasonerURLs", + response: { + contentType: "application/json", + body: JSON.stringify(require("./__tests__/mock/mock_reasoner_urls.json")) + } + } + ]; + } + /** + * Setup mocking on a Puppeteer page. + * + * @param {Puppeteer.Page} page + * @param {String|String[]|undefined} specificMocks - Only mocks whose `name` key is included in this array will be mocked. By default, it will mock everything. + */ + async mock(page, specificMocks=undefined) { + if (typeof specificMocks === "undefined") specificMocks = this.mocks.map((m) => m.name).filter((name) => name !== undefined); + if (typeof specificMocks === "string") specificMocks = [specificMocks]; + specificMocks.forEach((mockName) => { + const mock = this.mocks.find((m) => m.name === mockName); + if (mock === undefined) throw new Error(`Mock "${mockName}" does not exist.`); + }); + await page.setRequestInterception(true); + page.on("request", (req) => { + const responded = false; + this.mocks.forEach((mock) => { + if (args.mocking && specificMocks.includes(mock.name) && this._matchMock(mock, req)) { + req.respond(mock.response); + if (this.options.verbose) console.log(`Mocked request to "${req.url()}" with "${mock.mockName}"`); + responded = true; + } + }); + // Continue the request if no mocks match it. + if (!responded) req.continue(); + }); + } + /** + * Since the Mocker is built to support both real and mocked responses simultaneously, this method should be used + * to intercept the response of a (possibly) mocked request. + * + * This method returns the first response for a mocked request. + * + * @param {Puppeteer.Page} page + * @param {String} mockName - Corresponds to the `name` key of a mock. + * @returns {Promise} + */ + resolveResponse(page, mockName) { + return new Promise((resolve, reject) => { + page.on("response", (resp) => { + const mock = this.mocks.find((mock) => mock.name === mockName); + if (mock === undefined) reject(`Mock with name "${mockName}" does not exist.`); + if (this._matchMock(mock, resp.request())) { + if (this.options.verbose) console.log(`Mock response completed to "${req.url()}" with "${mock.mockName}"`); + resolve(resp); + } + }); + }); + } + _matchMock(mock, request) { + const url = new URL(request.url()); + return ( + url.origin + url.pathname === App.prototype.tranqlURL + mock.endpoint && + (mock.request === undefined || this._matchRequestData(mock.request, request)) + ); + } + /** + * + * @param {Object} mockRequest + * @param {Puppeteer.HTTPRequest} request + */ + _matchRequestData(mockRequest, request) { + return ( + (mockRequest.body === undefined || mockRequest.body === request.postData()) + ); + } +} \ No newline at end of file diff --git a/tests/test_tranql.py b/tests/test_tranql.py index a2020e4..5ab3a7a 100644 --- a/tests/test_tranql.py +++ b/tests/test_tranql.py @@ -11,7 +11,7 @@ from tests.mocks import MockHelper from tests.mocks import MockMap from tests.util import assert_lists_equal, set_mock, ordered -from tranql.main import TranQL +from tranql.main import TranQL, TranQLIncompleteParser from tranql.tranql_ast import SetStatement, SelectStatement, custom_functions from tranql.tranql_schema import SchemaFactory from tranql.utils.merge_utils import connect_knowledge_maps, find_all_paths @@ -323,6 +323,177 @@ def test_parse_query_with_repeated_concept (GraphInterfaceMock, requests_mock): ] ]]) +###################################################################################### +# TranQLIncompleteParser tests. For /tranql/parse_incomplete autocompletion endpoint # +###################################################################################### + +""" +Helper function used by tests for TranQLIncompleteParser to cut down on the boilerplate required in each test scenario. +""" +def _test_parse_incomplete(program, expected): + parser = TranQLIncompleteParser (None) + parsed = parser.tokenize(program) + result = parsed.asList() + assert_lists_equal(result, expected) + +####################### +# Select clause tests # +####################### +def test_parse_incomplete_select_clause(): + _test_parse_incomplete( + """select gene-[affects_expression_of]->protein->disease<-[treats]-phenotypic_feature""", + [[ + [ + "select", + "gene", + ["-[", "affects_expression_of", "]->"], + "protein", + "->", + "disease", + ["<-[", "treats", "]-"], + "phenotypic_feature" + ] + ]] + ) + _test_parse_incomplete( + """select gene-""", + [[ + [ + "select", + "gene", + "-" + ] + ]] + ) + _test_parse_incomplete( + """select gene-[""", + [[ + [ + "select", + "gene", + ["-["] + ] + ]] + ) + _test_parse_incomplete( + """select gene-[affects""", + [[ + [ + "select", + "gene", + ["-[", "affects"] + ] + ]] + ) + _test_parse_incomplete( + """select """, + [[ + [ + "select", + ] + ]] + ) + +##################### +# From clause tests # +##################### +def test_parse_incomplete_from_clause(): + _test_parse_incomplete( + """ + select gene->disease + from "/schema" + """, + [[ + ["select", "gene", "->", "disease", "\n"], + ["from", [["/schema"]]] + ]] + ) + _test_parse_incomplete( + """ + select gene->disease + from "/schema + """, + [[ + ["select", "gene", "->", "disease", "\n"], + ["from", [['"', "/schema"]]] + ]] + ) + _test_parse_incomplete( + """ + select gene->disease + from + """, + [[ + ["select", "gene", "->", "disease", "\n"], + ["from"] + ]] + ) + +###################### +# Where clause tests # +###################### +def test_parse_incomplete_where_clause(): + _test_parse_incomplete( + """ + select gene->disease + from "/schema" + where disease="asth" + """, + [[ + ["select", "gene", "->", "disease", "\n"], + ["from", [["/schema"]]], + ["where", ["disease", "=", "asth"]] + ]] + ) + _test_parse_incomplete( + """ + select gene->disease + from "/schema" + where disease="asth + """, + [[ + ["select", "gene", "->", "disease", "\n"], + ["from", [["/schema"]]], + ["where", ["disease", "=", ['"', "asth"]]] + ]] + ) + _test_parse_incomplete( + """ + select gene->disease + from "/schema" + where disease= + """, + [[ + ["select", "gene", "->", "disease", "\n"], + ["from", [["/schema"]]], + ["where", ["disease", "="]] + ]] + ) + _test_parse_incomplete( + """ + select gene->disease + from "/schema" + where disease + """, + [[ + ["select", "gene", "->", "disease", "\n"], + ["from", [["/schema"]]], + ["where", ["disease"]] + ]] + ) + _test_parse_incomplete( + """ + select gene->disease + from "/schema" + where + """, + [[ + ["select", "gene", "->", "disease", "\n"], + ["from", [["/schema"]]], + ["where"] + ]] + ) + ##################################################### # # AST tests. Test abstract syntax tree components. @@ -1919,6 +2090,7 @@ def __init__(self, limit , skip, options_set): self.limit = limit self.skip = skip self.options_set = options_set + self.summary = {} async def answer_trapi_question(self, message, options={}, timeout=0): assert message @@ -1990,6 +2162,7 @@ class graph_Inteface_mock: def __init__(self): self.get_schema_called =False self.answer_trapi_question_called =False + self.summary = {} def get_schema(self, force_update=False): self.get_schema_called = True return {} @@ -2060,6 +2233,7 @@ def __init__(self, limit, skip, options_set): self.options_set = options_set self.answer_trapi_question self.is_called = False + self.summary = {} def get_schema(self, *args, **kwargs): return {