init public release
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
[bumpversion]
|
||||
current_version = 1.0.1
|
||||
commit = True
|
||||
tag = True
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
|
||||
serialize = {major}.{minor}.{patch}
|
||||
|
||||
[bumpversion:file:pyproject.toml]
|
||||
@@ -0,0 +1,27 @@
|
||||
# Never copy a host virtual environment into the image
|
||||
.venv
|
||||
venv
|
||||
env
|
||||
|
||||
# Python caches
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*.egg-info/
|
||||
.pytest_cache/
|
||||
.mypy_cache/
|
||||
.ruff_cache/
|
||||
|
||||
# VCS / editor
|
||||
.git
|
||||
.gitignore
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# Local env / secrets (manage these via compose env_file or secrets instead)
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Docker
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
@@ -0,0 +1,40 @@
|
||||
MODE= #possible values: none or testserver
|
||||
COMPLEX_PATIENTS= #TRUE if only complex patients should be used otherwise FALSE
|
||||
COMPLEX_FHIR_SEARCH= #URL for the patient selection on the server, e.g. all gout patients:
|
||||
IS_SYNTHEA= #TRUE if synthea data is used otherwise FALSE
|
||||
FHIR_SERVER_URL= #URL to the FHIR server, e.g. http://hapi.fhir.org/baseR4; use http://blaze:8080/fhir when COMPOSE_PROFILES includes blaze
|
||||
BOLT_ADVERTISED_ADDRESS=localhost:8081 #change in production
|
||||
NEO4J_AUTH= # set if the server needs authentification: none otherwise true
|
||||
ENABLE_BOLT_TLS= #true to require TLS on the bolt connector (needs real certs in ./neo4j-certs/bolt/, used in production); leave unset/false for local runs
|
||||
|
||||
DISABLE_IMPORT= #true if only neo4j should be started and load existing import data, false if the complete pipeline should run
|
||||
|
||||
### Settings for deployment - might be adapted depending on the network its deployed in and
|
||||
#FHIR_SERVER_USER=
|
||||
#FHIR_SERVER_PW=
|
||||
#HTTP_PROXY=
|
||||
#HTTPS_PROXY=
|
||||
#NO_PROXY=
|
||||
|
||||
### Settings for how to get the data
|
||||
NUMBER_OF_PATIENTS=2 # depends on the goal, very high to get all patients, lower numbers for testing; depends on disk space, plan with 30GB per 60k patients
|
||||
BATCH_SIZE=10 # depends on the computational power, plan with 12GB per 250 patients; higher batch sizes improve the needed time to run the pipeline
|
||||
|
||||
# MDM
|
||||
MDM_MODE= #biocypher or mdm (beta)
|
||||
|
||||
#OAUTH2_COOKIE_SECURE=true # or: NEO4J_COOKIE_SECRET= #for local setup
|
||||
|
||||
|
||||
#Keycloak settings if a local keycloak instance is used
|
||||
#KEYCLOAK_ISSUER_URL=https://---your productive keycloak url---/realms/yourRealm #if using the setup with a local keycloak use: http://localhost:4040/realms/testPipeline
|
||||
#NEO4J_OAUTH_CLIENT_ID=neo4j-client
|
||||
#NEO4J_OAUTH_SECRET=secret-from-production-keycloak or secret from local keycloak setup
|
||||
|
||||
#NEO4J_PROXY_REDIRECT_URL=https://your-domain.com/oauth2/callback
|
||||
|
||||
COMPOSE_PROFILES= #comma-separated: blaze, keycloak, proxy, server - activates parts of docker compose depending on the setup (see README)
|
||||
|
||||
#Test vars
|
||||
TEST_MODE=FALSE
|
||||
TEST_DEPTH=4
|
||||
@@ -0,0 +1,41 @@
|
||||
name: "Test and code quality"
|
||||
description: "Run tests and code quality checks"
|
||||
inputs:
|
||||
NEO4J_VERSION:
|
||||
description: "Neo4j version"
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
#----------------------------------------------
|
||||
# setup docker containers for testing
|
||||
#----------------------------------------------
|
||||
# currently only running on Linux due to technical limitations
|
||||
# - name: Install Docker
|
||||
# uses: douglascamata/setup-docker-macos-action@v1-alpha
|
||||
# if: ${{ runner.os == 'macOS' }}
|
||||
- name: Start Neo4j Docker
|
||||
run: docker run --restart always --publish=7474:7474 --publish=7687:7687 --env NEO4J_AUTH=neo4j/your_password_here --env NEO4J_PLUGINS='["apoc"]' --env=NEO4J_ACCEPT_LICENSE_AGREEMENT=yes -d neo4j:${{ inputs.NEO4J_VERSION }}
|
||||
shell: bash
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
- name: Start Postgres Docker
|
||||
run: docker run --restart always --publish=5432:5432 --env POSTGRES_PASSWORD=postgres -d postgres:11.21-bullseye
|
||||
shell: bash
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
#----------------------------------------------
|
||||
# run tests and code quality checks
|
||||
#----------------------------------------------
|
||||
- name: Run Tests (Windows)
|
||||
run: |
|
||||
uv run pytest --version
|
||||
uv run pytest --password=your_password_here
|
||||
shell: bash
|
||||
if: runner.os == 'Windows'
|
||||
- name: Run tests (Linux and MacOS)
|
||||
run: |
|
||||
uv run pytest --version
|
||||
uv run pytest --password=your_password_here
|
||||
shell: bash
|
||||
if: runner.os != 'Windows'
|
||||
- name: Check code quality
|
||||
uses: pre-commit/action@v3.0.0
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
*~
|
||||
*__pycache__
|
||||
build/
|
||||
docs/pypath_log/
|
||||
docs/_build/
|
||||
docs/biocypher-log/
|
||||
docs/modules/
|
||||
docs/notebooks/*.yaml
|
||||
docs/notebooks/*.py
|
||||
.DS_Store
|
||||
.vscode
|
||||
biocypher.egg-info/
|
||||
*.egg
|
||||
dist/
|
||||
*.prof
|
||||
*.coverage
|
||||
*.pickle
|
||||
out/*
|
||||
biocypher-log/*
|
||||
biocypher-out/*
|
||||
*.log
|
||||
dist/*
|
||||
*.pye
|
||||
*.pyc
|
||||
*.kate-swp
|
||||
.hypothesis/
|
||||
.venv/
|
||||
.empty
|
||||
.pytest_cache
|
||||
*.graphml
|
||||
.idea/*
|
||||
.cache
|
||||
*.iml
|
||||
|
||||
# Local env files with real secrets - keep *.example templates tracked
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
@@ -0,0 +1,6 @@
|
||||
[submodule "networkx-based"]
|
||||
path = networkx-based
|
||||
url = git@git.uni-greifswald.de:MeDaX/networkx-based.git
|
||||
[submodule "mdm2neo4j"]
|
||||
path = mdm2neo4j
|
||||
url = https://git.uni-greifswald.de/guetebierl/mdm2neo4j.git
|
||||
@@ -0,0 +1,50 @@
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
fail_fast: false
|
||||
default_language_version:
|
||||
python: python3
|
||||
default_stages:
|
||||
- commit
|
||||
- push
|
||||
minimum_pre_commit_version: 2.7.1
|
||||
repos:
|
||||
- repo: https://github.com/ambv/black
|
||||
rev: 23.7.0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/timothycrosley/isort
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
||||
additional_dependencies: [toml]
|
||||
- repo: https://github.com/snok/pep585-upgrade
|
||||
rev: v1.0
|
||||
hooks:
|
||||
- id: upgrade-type-hints
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: check-docstring-first
|
||||
- id: end-of-file-fixer
|
||||
- id: check-added-large-files
|
||||
- id: mixed-line-ending
|
||||
- id: trailing-whitespace
|
||||
exclude: ^.bumpversion.cfg$
|
||||
- id: check-merge-conflict
|
||||
- id: check-case-conflict
|
||||
- id: check-symlinks
|
||||
- id: check-yaml
|
||||
args: [--unsafe]
|
||||
- id: check-ast
|
||||
- id: fix-encoding-pragma
|
||||
args: [--remove] # for Python3 codebase, it's not necessary
|
||||
- id: requirements-txt-fixer
|
||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
||||
rev: v1.10.0
|
||||
hooks:
|
||||
- id: python-no-eval
|
||||
- id: python-use-type-annotations
|
||||
- id: python-check-blanket-noqa
|
||||
- id: rst-backticks
|
||||
- id: rst-directive-colons
|
||||
- id: rst-inline-touching-normal
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
FROM python:3.11-slim
|
||||
WORKDIR /app
|
||||
|
||||
# Bring in the uv binary from Astral's official image (pinned-ish, reproducible)
|
||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
||||
|
||||
# Compile bytecode for faster cold starts; install into a project-local
|
||||
# venv at /app/.venv and put it on PATH so `python` resolves to it directly
|
||||
# (this replaces `poetry config virtualenvs.create false`).
|
||||
ENV UV_COMPILE_BYTECODE=1 \
|
||||
UV_LINK_MODE=copy \
|
||||
VIRTUAL_ENV=/app/.venv \
|
||||
PATH="/app/.venv/bin:$PATH"
|
||||
|
||||
# --- Dependency layer: cached unless these files change ---
|
||||
COPY pyproject.toml uv.lock ./
|
||||
# --no-dev installs only the main deps (equivalent to poetry --only main).
|
||||
# --frozen fails the build instead of silently re-resolving if uv.lock is stale.
|
||||
RUN uv sync --no-dev --frozen
|
||||
|
||||
# --- Application layer ---
|
||||
COPY . .
|
||||
|
||||
# Normalize line endings and make the entrypoint executable
|
||||
RUN sed -i 's/\r$//' /app/entrypoint.sh \
|
||||
&& chmod +x /app/entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||
@@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Saez Lab
|
||||
Copyright (c) 2025 MeDaX research group
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,193 @@
|
||||
# MeDaX Pipeline
|
||||
|
||||
## 📋 Description
|
||||
The MeDaX pipeline transforms healthcare data from FHIR databases into Neo4j graph databases. This conversion enables efficient searching, querying, and analyses of interconnected health data that would otherwise be complex to retrieve using traditional SQL databases.
|
||||
|
||||
## ✨ Features
|
||||
- Seamless conversion from FHIR to Neo4j graph structure
|
||||
- Support for patient-centric data retrieval using FHIR's `$everything` operation
|
||||
- Configurable batch processing for handling large datasets
|
||||
- Docker-based deployment for easy setup and portability
|
||||
- Compatible with public FHIR servers (e.g., HAPI FHIR) and private authenticated instances
|
||||
|
||||
## ⚙️ Prerequisites
|
||||
- [Docker](https://docs.docker.com/engine/install/) with the [Docker Compose plugin](https://docs.docker.com/compose/install/linux/)
|
||||
- A FHIR database with API access and the `$everything` operation enabled for retrieving patient data
|
||||
- Alternatively: Use a public FHIR server such as [HAPI FHIR](https://hapi.fhir.org/) (default configuration)
|
||||
- Optional: [Semspect](https://www.semspect.de/) license
|
||||
- free scientific licenses can be requested
|
||||
- helps with graph exploration
|
||||
|
||||
## 🚀 Installation
|
||||
|
||||
### Setup
|
||||
1. Clone this repository
|
||||
2. Create an environment configuration file
|
||||
3. Configure the environment variables in `.env`:
|
||||
- For HAPI test server (default): No changes needed
|
||||
- For custom FHIR server:
|
||||
- Change `MODE` to anything else
|
||||
- Uncomment and set `URL`, `PASSWORD`, and `USERNAME` variables
|
||||
- Adjust `BATCH_SIZE` and `NUMBER_OF_PATIENTS` according to your needs
|
||||
- Configure any required proxy settings
|
||||
|
||||
4. If needed, modify proxy settings in the `Dockerfile`
|
||||
- Uncomment and set proxy variables
|
||||
|
||||
### Running the Pipeline
|
||||
|
||||
#### Option A: Connect to an existing FHIR server (e.g. HAPI)
|
||||
**Start the containers:**
|
||||
```bash
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
**Stop and clean up (between runs):**
|
||||
```bash
|
||||
docker compose down --volumes
|
||||
```
|
||||
|
||||
**Complete removal (containers and images):**
|
||||
```bash
|
||||
docker compose down --volumes --rmi all
|
||||
```
|
||||
|
||||
#### Option B: Build a local FHIR server with POLAR data
|
||||
This spins up a local [Blaze](https://github.com/samply/blaze) FHIR server pre-loaded with synthetic POLAR test bundles, using the `blaze` Compose profile.
|
||||
|
||||
**Setup:** in your `.env`, set:
|
||||
```
|
||||
COMPOSE_PROFILES=blaze
|
||||
FHIR_SERVER_URL=http://blaze:8080/fhir
|
||||
```
|
||||
|
||||
**Start the containers:**
|
||||
```bash
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
**Stop and clean up (between runs):**
|
||||
```bash
|
||||
docker compose down --volumes
|
||||
```
|
||||
|
||||
**Complete removal (containers and images):**
|
||||
```bash
|
||||
docker compose down --volumes --rmi all
|
||||
```
|
||||
|
||||
#### Option C: Build a local FHIR server with POLAR data and a local keycloak server
|
||||
This builds on Option B and adds a local Keycloak instance plus an OAuth2 proxy in front of Neo4j, using the `blaze`, `keycloak`, and `proxy` Compose profiles.
|
||||
|
||||
**Setup:** in your `.env`, set:
|
||||
```
|
||||
COMPOSE_PROFILES=blaze,keycloak,proxy
|
||||
FHIR_SERVER_URL=http://blaze:8080/fhir
|
||||
```
|
||||
|
||||
**Start the containers:**
|
||||
##### Init containers
|
||||
```bash
|
||||
docker compose up --build
|
||||
```
|
||||
##### Create realm, client and user
|
||||
1. Open keycloak in a browser: localhost:4040 and login with user: admin and password: admin
|
||||
2. Create the realm "testPipeline"
|
||||
3. Create a client:
|
||||
* Client ID: neo4j-client
|
||||
* Always display in UI "on"
|
||||
* Hit the "Next" button
|
||||
* Client authentification: on
|
||||
* Hit the "Next" button
|
||||
* Root URL: http://localhost:8082
|
||||
* Home URL: http://localhost:8082
|
||||
* Valid redirects URIs: http://localhost:8082/oauth2/callback
|
||||
* Valid post logout redirect URIs: http://localhost:8082/*
|
||||
* Web origin: http://localhost:8082
|
||||
* Hit the "Save" button
|
||||
* Go to the "Credentials" tab
|
||||
* Copy the "Client Secret" to "NEO4J_OAUTH_SECRET" in your .env
|
||||
4. Create a user:
|
||||
* "Email verified": on
|
||||
* Choose a user name
|
||||
* Use random mail, e.g.: test@example.com
|
||||
* Hit the "Create button"
|
||||
* Click on the new user, go to the "Credentials" tab
|
||||
* Set a password and disable "Temporary"
|
||||
|
||||
##### Restart containers without destroying the volumes
|
||||
```bash
|
||||
docker compose down
|
||||
docker compose up
|
||||
```
|
||||
|
||||
**Stop**
|
||||
```bash
|
||||
docker compose down
|
||||
```
|
||||
> **Note:** When adding --volumes to down, the keycloak setup will be lost
|
||||
|
||||
**Complete removal (containers and images):**
|
||||
```bash
|
||||
docker compose down --volumes --rmi all
|
||||
```
|
||||
|
||||
> **Note:** Depending on your Docker installation, you might need to use `docker-compose` instead of `docker compose`.
|
||||
> **Note:** With `COMPOSE_PROFILES` set in your `.env`, every `docker compose` command automatically includes the right services - no `-f` files needed.
|
||||
|
||||
## 🔍 Accessing the Neo4j Database
|
||||
|
||||
Once the pipeline has completed processing, you can access the Neo4j database:
|
||||
|
||||
1. Open your browser and navigate to `http://localhost:8080/` or `http://localhost:8082/` if you used keycloak (Option C, served via the `proxy` profile's `neo4j-proxy` service)
|
||||
2. Connect by clicking the button, user name and password are not needed (disabled by config)
|
||||
|
||||
## 📊 Example Queries
|
||||
|
||||
Here are some basic Cypher queries to get you started with exploring your health data:
|
||||
|
||||
```cypher
|
||||
// Count all nodes by type
|
||||
MATCH (n) RETURN labels(n) as NodeType, count(*) as Count;
|
||||
|
||||
// Find all records for a specific patient
|
||||
MATCH (p:Patient {id: 'patient-id'})-[r]-(connected)
|
||||
RETURN p, r, connected;
|
||||
|
||||
// Retrieve all medication prescriptions
|
||||
MATCH (m:Medication)-[r]-(p:Patient)
|
||||
RETURN m, r, p;
|
||||
```
|
||||
|
||||
## ❓ Troubleshooting
|
||||
|
||||
**Common Issues:**
|
||||
|
||||
- **Connection refused to FHIR server**: Check your network settings and ensure the FHIR server is accessible from within the Docker container.
|
||||
- **Authentication failures**: Verify your credentials in the `.env` file.
|
||||
- **Container startup failures**: Ensure all required Docker ports are available and not used by other applications.
|
||||
- **No data found in fhir bundle**: Ensure that the FHIR server is up and responding patient data. Try set the COMPLEX_PATIENTS variable to FALSE in your .env file. Some FHIR servers might not support the FHIR search logic.
|
||||
|
||||
## 📚 Architecture
|
||||
|
||||
The MeDaX pipeline consists of the following components:
|
||||
|
||||
1. **FHIR Client**: Connects to the FHIR server and retrieves patient data
|
||||
2. **Data Transformer**: Converts FHIR resources into graph entities and relationships
|
||||
3. **Reference Processor**: Converts references to relationships
|
||||
3. **BioCypher Adapter**: Prepares the transformed data for Neo4j admin import
|
||||
4. **Neo4j Database**: Stores and serves the graph representation of the health data
|
||||
|
||||
## ✍️ Citation
|
||||
|
||||
If you use the MeDaX pipeline in your research, please cite: 10.5281/zenodo.15229077 and Mazein, I and Gebhardt, T et al. [MeDaX, a knowledge graph on FHIR.](https://doi.org/10.3233/shti240423)
|
||||
|
||||
## 🙏 Acknowledgements
|
||||
|
||||
- We are leveraging [BioCypher](https://biocypher.org) [](https://doi.org/10.1038/s41587-023-01848-y) to create the Neo4j admin input.
|
||||
- Remark: We introduced slight adjustments to BioCypher's code to support batching.
|
||||
- We used BioCypher's git template as a starting point for our development:
|
||||
- Lobentanzer, S., BioCypher Consortium, & Saez-Rodriguez, J. Democratizing knowledge representation with BioCypher [Computer software]. https://github.com/biocypher/biocypher
|
||||
- We used synthetic data generated with [Synthea](https://doi.org/10.1093/jamia/ocx079) during the development process. This data is provided in the testData folder.
|
||||
- We are using the [HAPI](https://hapifhir.io/) R4 server while developing and showcasing the capabilities of our tool.
|
||||
- This project has been funded by the BMBF, FKZ: 01ZZ2019.
|
||||
@@ -0,0 +1,215 @@
|
||||
# BioCypher ODM Import Schema Configuration with BioLink Model Integration
|
||||
|
||||
# Node types extracted from ODM generators mapped to BioLink classes
|
||||
study:
|
||||
represented_as: node
|
||||
preferred_id: oid
|
||||
label_in_input: Study
|
||||
is_a: biolink:ClinicalTrial
|
||||
properties:
|
||||
OID: str
|
||||
Name: str
|
||||
Description: str
|
||||
Protocol: str
|
||||
ModelID: str
|
||||
|
||||
studyevent:
|
||||
represented_as: node
|
||||
preferred_id: oid
|
||||
label_in_input: StudyEvent
|
||||
is_a: biolink:ClinicalEntity
|
||||
properties:
|
||||
OID: str
|
||||
Name: str
|
||||
Type: str
|
||||
StudyOID: str
|
||||
ModelID: str
|
||||
|
||||
form:
|
||||
represented_as: node
|
||||
preferred_id: oid
|
||||
label_in_input: Form
|
||||
is_a: biolink:InformationContentEntity
|
||||
properties:
|
||||
OID: str
|
||||
Name: str
|
||||
StudyOID: str
|
||||
ModelID: str
|
||||
|
||||
itemgroup:
|
||||
represented_as: node
|
||||
preferred_id: oid
|
||||
label_in_input: ItemGroup
|
||||
is_a: biolink:InformationContentEntity
|
||||
properties:
|
||||
OID: str
|
||||
Name: str
|
||||
StudyOID: str
|
||||
ModelID: str
|
||||
|
||||
item:
|
||||
represented_as: node
|
||||
preferred_id: oid
|
||||
label_in_input: Item
|
||||
is_a: biolink:ClinicalFinding
|
||||
properties:
|
||||
OID: str
|
||||
Name: str
|
||||
DataType: str
|
||||
Question: str
|
||||
StudyOID: str
|
||||
ModelID: str
|
||||
|
||||
alias:
|
||||
represented_as: node
|
||||
preferred_id: [context, name]
|
||||
label_in_input: Alias
|
||||
is_a: biolink:InformationContentEntity
|
||||
properties:
|
||||
Context: str
|
||||
Name: str
|
||||
ItemOID: str
|
||||
StudyOID: str
|
||||
ModelID: str
|
||||
|
||||
measurementunit:
|
||||
represented_as: node
|
||||
preferred_id: oid
|
||||
label_in_input: MeasurementUnit
|
||||
is_a: biolink:Unit
|
||||
properties:
|
||||
OID: str
|
||||
Name: str
|
||||
Symbol: str
|
||||
|
||||
basicdefinitions:
|
||||
represented_as: node
|
||||
preferred_id: [studyoid, modelid]
|
||||
label_in_input: BasicDefinitions
|
||||
is_a: biolink:InformationContentEntity
|
||||
properties:
|
||||
StudyOID: str
|
||||
ModelID: str
|
||||
|
||||
rangecheck:
|
||||
represented_as: node
|
||||
preferred_id: [comparator, constraint, checkvalue]
|
||||
label_in_input: RangeCheck
|
||||
is_a: biolink:ClinicalFinding
|
||||
properties:
|
||||
Comparator: str
|
||||
Constraint: str
|
||||
CheckValue: str
|
||||
|
||||
codelist:
|
||||
represented_as: node
|
||||
preferred_id: oid
|
||||
label_in_input: CodeList
|
||||
is_a: biolink:InformationContentEntity
|
||||
properties:
|
||||
OID: str
|
||||
Name: str
|
||||
DataType: str
|
||||
StudyOID: str
|
||||
ModelID: str
|
||||
|
||||
codelistitem:
|
||||
represented_as: node
|
||||
preferred_id: codedvalue
|
||||
label_in_input: CodeListItem
|
||||
is_a: biolink:ConceptualEntity
|
||||
properties:
|
||||
CodedValue: str
|
||||
Decode: str
|
||||
|
||||
# Edge types extracted from ODM generators mapped to BioLink predicates
|
||||
study_has_studyevent:
|
||||
represented_as: edge
|
||||
label_in_input: STUDY_HAS_STUDYEVENT
|
||||
source: study
|
||||
target: studyevent
|
||||
is_a: biolink:related_to
|
||||
|
||||
itemgroup_has_item:
|
||||
represented_as: edge
|
||||
label_in_input: ITEMGROUP_HAS_ITEM
|
||||
source: itemgroup
|
||||
target: item
|
||||
is_a: biolink:has_part
|
||||
|
||||
form_has_itemgroup:
|
||||
represented_as: edge
|
||||
label_in_input: FORM_HAS_ITEMGROUP
|
||||
source: form
|
||||
target: itemgroup
|
||||
is_a: biolink:has_part
|
||||
|
||||
studyevent_has_form:
|
||||
represented_as: edge
|
||||
label_in_input: STUDYEVENT_HAS_FORM
|
||||
source: studyevent
|
||||
target: form
|
||||
is_a: biolink:has_part
|
||||
|
||||
item_has_alias:
|
||||
represented_as: edge
|
||||
label_in_input: ITEM_HAS_ALIAS
|
||||
source: item
|
||||
target: alias
|
||||
is_a: biolink:same_as
|
||||
|
||||
itemgroup_has_alias:
|
||||
represented_as: edge
|
||||
label_in_input: ITEMGROUP_HAS_ALIAS
|
||||
source: itemgroup
|
||||
target: alias
|
||||
is_a: biolink:same_as
|
||||
|
||||
item_has_measurementunit:
|
||||
represented_as: edge
|
||||
label_in_input: ITEM_HAS_MEASUREMENTUNIT
|
||||
source: item
|
||||
target: measurementunit
|
||||
is_a: biolink:has_attribute
|
||||
|
||||
has_basedef:
|
||||
represented_as: edge
|
||||
label_in_input: HAS_BASEDEF
|
||||
source: study
|
||||
target: basicdefinitions
|
||||
is_a: biolink:has_part
|
||||
|
||||
basedef_has_measurementunit:
|
||||
represented_as: edge
|
||||
label_in_input: BASEDEF_HAS_MEASUREMENTUNIT
|
||||
source: basicdefinitions
|
||||
target: measurementunit
|
||||
is_a: biolink:has_part
|
||||
|
||||
item_has_rangecheck:
|
||||
represented_as: edge
|
||||
label_in_input: ITEM_HAS_RANGECHECK
|
||||
source: item
|
||||
target: rangecheck
|
||||
is_a: biolink:has_attribute
|
||||
|
||||
item_has_codelist:
|
||||
represented_as: edge
|
||||
label_in_input: ITEM_HAS_CODELIST
|
||||
source: item
|
||||
target: codelist
|
||||
is_a: biolink:has_attribute
|
||||
|
||||
codelist_has_codelistitem:
|
||||
represented_as: edge
|
||||
label_in_input: CODELIST_HAS_CODELISTITEM
|
||||
source: codelist
|
||||
target: codelistitem
|
||||
is_a: biolink:has_part
|
||||
|
||||
composite:
|
||||
represented_as: edge
|
||||
label_in_input: COMPOSITE
|
||||
source: alias
|
||||
target: alias
|
||||
is_a: biolink:related_to
|
||||
@@ -0,0 +1,18 @@
|
||||
# add your settings here (overriding the defaults)
|
||||
|
||||
biocypher:
|
||||
dbms: neo4j
|
||||
offline: true
|
||||
#debug: true
|
||||
output_directory: /neo4j_import #comment if you want to debug, so that bc creates a new folder for each run in /biocypher-out
|
||||
schema_config_path: config/automated_schema.yaml #config/automated_schema.yaml
|
||||
|
||||
head_ontology:
|
||||
url: config/head_ontology/biolink-model.owl.ttl
|
||||
root_node: entity
|
||||
|
||||
neo4j:
|
||||
delimiter: '\t'
|
||||
array_delimiter: '|'
|
||||
skip_duplicate_nodes: true
|
||||
skip_bad_relationships: true
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,283 @@
|
||||
Title: BioCypher graph schema configuration file
|
||||
|
||||
# This configuration file establishes the hierarchy and connectivity in a newly
|
||||
# set-up BioCypher property graph database. Naming should adhere to Biolink
|
||||
# nomenclature (available at https://biolink.github.io/biolink-model/ or via
|
||||
# the python module 'biolink-model-toolkit').
|
||||
|
||||
# The BioCypher YAML file specifies only the leaves of the hierarchy tree of
|
||||
# the desired graph; the hierarchical structure of entities will be derived
|
||||
# from the Biolink model + BRO model. Thus, only the immediate constituents
|
||||
# of the graph need to be specified in the schema config.
|
||||
|
||||
|
||||
# ---
|
||||
# "Named Things"
|
||||
# ---
|
||||
# The implementation of named things is fairly straightforward, since they are
|
||||
# usually represented in node form, which is also the Biolink recommendation.
|
||||
# The same is not true for associations.
|
||||
#
|
||||
# A little more complex is the representation of aggregates of named things.
|
||||
|
||||
clinicalStatus:
|
||||
is_a: ClinicalEntity
|
||||
represented_as: node
|
||||
preferred_id: fhir_id
|
||||
label_in_input: clinicalStatus
|
||||
properties:
|
||||
coding_system: str
|
||||
label: str
|
||||
coding_code: str
|
||||
|
||||
Condition:
|
||||
is_a: ClinicalEntity
|
||||
represented_as: node
|
||||
preferred_id: fhir_id
|
||||
label_in_input: Condition
|
||||
properties:
|
||||
input_format: HL7 FHIR
|
||||
data_specification: Medical Informatics Initiative Germany Core Data Set, Basic Modules
|
||||
|
||||
diagnosis:
|
||||
is_a: ClinicalEntity
|
||||
represented_as: node
|
||||
preferred_id: fhir_id
|
||||
label_in_input: diagnosis
|
||||
properties:
|
||||
type.coding_code: str
|
||||
sequence: str
|
||||
label: str
|
||||
type.coding_system: str
|
||||
|
||||
DiagnosticReport:
|
||||
is_a: ClinicalEntity
|
||||
represented_as: node
|
||||
preferred_id: fhir_id
|
||||
label_in_input: DiagnosticReport
|
||||
properties:
|
||||
resourceType: str
|
||||
label: str
|
||||
status: str
|
||||
id: str
|
||||
|
||||
Encounter:
|
||||
is_a: ClinicalEntity
|
||||
represented_as: node
|
||||
preferred_id: fhir_id
|
||||
label_in_input: Encounter
|
||||
properties:
|
||||
resourceType: str
|
||||
label: str
|
||||
status: str
|
||||
id: str
|
||||
|
||||
identifier:
|
||||
is_a: Attribute
|
||||
represented_as: node
|
||||
preferred_id: fhir_id
|
||||
label_in_input: identifier
|
||||
properties:
|
||||
label: str
|
||||
value: str
|
||||
system: str
|
||||
|
||||
interpretation: #
|
||||
is_a: named thing
|
||||
represented_as: node
|
||||
preferred_id: fhir_id
|
||||
label_in_input: interpretation
|
||||
properties:
|
||||
extension.valueCoding_system: str
|
||||
extension_url: str
|
||||
extension.valueCoding_display: str
|
||||
coding_code: str
|
||||
coding_system: str
|
||||
label: str
|
||||
extension.valueCoding_code: str
|
||||
|
||||
maritalStatus:
|
||||
is_a: OrganismAttribute
|
||||
represented_as: node
|
||||
preferred_id: fhir_id
|
||||
label_in_input: maritalStatus
|
||||
properties:
|
||||
label: str
|
||||
coding_system: str
|
||||
coding_code: str
|
||||
|
||||
Observation:
|
||||
is_a: ClinicalEntity
|
||||
represented_as: node
|
||||
preferred_id: fhir_id
|
||||
label_in_input: Observation
|
||||
properties:
|
||||
resourceType: str
|
||||
label: str
|
||||
effectiveDateTime: str
|
||||
status: str
|
||||
id: str
|
||||
|
||||
Organization:
|
||||
is_a: AdministrativeEntity
|
||||
represented_as: node
|
||||
preferred_id: fhir_id
|
||||
label_in_input: Organization
|
||||
properties:
|
||||
label: str
|
||||
id: str
|
||||
name: str
|
||||
resourceType: str
|
||||
|
||||
Patient:
|
||||
is_a: Human
|
||||
represented_as: node
|
||||
preferred_id: fhir_id
|
||||
label_in_input: Patient
|
||||
properties:
|
||||
resourceType: str
|
||||
label: str
|
||||
gender: str
|
||||
id: str
|
||||
birthDate: str
|
||||
|
||||
procedure:
|
||||
is_a: named thing
|
||||
represented_as: node
|
||||
preferred_id: fhir_id
|
||||
label_in_input: Procedure
|
||||
properties:
|
||||
label: str
|
||||
performedDateTime: str
|
||||
resourceType: str
|
||||
status: str
|
||||
id: str
|
||||
|
||||
referenceRange: #
|
||||
is_a: named thing
|
||||
represented_as: node
|
||||
preferred_id: fhir_id
|
||||
label_in_input: referenceRange
|
||||
properties:
|
||||
high_system: str
|
||||
high_value: str
|
||||
high_code: str
|
||||
label: str
|
||||
high_unit: str
|
||||
|
||||
search: #
|
||||
is_a: named thing
|
||||
represented_as: node
|
||||
preferred_id: fhir_id
|
||||
label_in_input: search
|
||||
properties:
|
||||
label: str
|
||||
mode: str
|
||||
|
||||
type:
|
||||
is_a: Attribute
|
||||
represented_as: node
|
||||
preferred_id: fhir_id
|
||||
label_in_input: type
|
||||
properties:
|
||||
coding_system: str
|
||||
label: str
|
||||
coding_code: str
|
||||
coding_display: str
|
||||
|
||||
verificationStatus:
|
||||
is_a: Attribute
|
||||
represented_as: node
|
||||
preferred_id: fhir_id
|
||||
label_in_input: verificationStatus
|
||||
properties:
|
||||
coding_system: str
|
||||
label: str
|
||||
coding_code: str
|
||||
coding_display: str
|
||||
|
||||
|
||||
# ---
|
||||
# Associations
|
||||
# ---
|
||||
# Associations are not supposed to be represented in node form as per the
|
||||
# specifications of Biolink. However, in an analytic context, it often makes
|
||||
# sense to represent interactions as nodes in Neo4j, because it enables, for
|
||||
# instance, the annotation of a relationship with a publication as source of
|
||||
# evidence (also known as reification in the knowledge graph world).
|
||||
|
||||
# The Biolink specifications for these types of relationships do
|
||||
# not go into depth; for example, the hierarchy for molecular interactions
|
||||
# (ie, "associations") ends at "PairwiseMolecularInteraction", there are no
|
||||
# explicit terms for protein-protein-interaction, phosphorylation, miRNA-
|
||||
# targeting, etc. Biolink proposes to use interaction identifiers from
|
||||
# ontologies, such as https://www.ebi.ac.uk/ols/ontologies/mi/.
|
||||
|
||||
# association to connect anything to an identifier node
|
||||
# if functional, includes:
|
||||
# IDENTIFIED_BY_Condition_Identifier,
|
||||
# IDENTIFIED_BY_DiagnosticReport_Identifier,
|
||||
# IDENTIFIED_BY_Encounter_Identifier,
|
||||
# IDENTIFIED_BY_Observation_Identifier,
|
||||
# IDENTIFIED_BY_Organization_Identifier
|
||||
# IDENTIFIED_BY_Patient_Identifier,
|
||||
# IDENTIFIED_BY_Procedure_Identifier
|
||||
|
||||
condition to identifier association:
|
||||
is_a: association
|
||||
represented_as: edge
|
||||
label_in_input: IDENTIFIED_BY_Condition_Identifier
|
||||
|
||||
diagnostic report to identifier association:
|
||||
is_a: association
|
||||
represented_as: edge
|
||||
label_in_input: IDENTIFIED_BY_DiagnosticReport_Identifier
|
||||
|
||||
observation to identifier association:
|
||||
is_a: association
|
||||
represented_as: edge
|
||||
label_in_input: IDENTIFIED_BY_Observation_Identifier
|
||||
|
||||
observation derived from observation association:
|
||||
is_a: association
|
||||
represented_as: edge
|
||||
label_in_input: DERIVED_FROM_Observation_Observation
|
||||
|
||||
observation has member observation association:
|
||||
is_a: association
|
||||
represented_as: edge
|
||||
label_in_input: HAS_MEMBER_Observation_Observation
|
||||
|
||||
procedure to identifier association:
|
||||
is_a: association
|
||||
represented_as: edge
|
||||
label_in_input: IDENTIFIED_BY_Procedure_Identifier
|
||||
|
||||
procedure to diagnostic report association:
|
||||
is_a: association
|
||||
represented_as: edge
|
||||
label_in_input: IDENTIFIED_BY_Procedure_Identifier
|
||||
|
||||
procedure reasoned by observation association:
|
||||
is_a: association
|
||||
represented_as: edge
|
||||
label_in_input: HAS_REASON_REFERENCE_Procedure_Observation
|
||||
|
||||
procedure performer is practitioner association:
|
||||
is_a: association
|
||||
represented_as: edge
|
||||
label_in_input: HAS_ACTOR_ProcedurePerformer_Practitioner
|
||||
|
||||
#represented_as: edge
|
||||
#label_in_input: DERIVED_FROM_Observation_Observation:
|
||||
#represented_as: edge
|
||||
#label_in_input: DERIVED_FROM_Observation_Observation
|
||||
#protein interaction:
|
||||
# is_a: Pairwise molecular interaction
|
||||
# represented_as: edge
|
||||
# label_in_input: protein_protein_interaction
|
||||
|
||||
#protein to disease association:
|
||||
# is_a: Association
|
||||
# represented_as: edge
|
||||
# label_in_input: protein_disease_association
|
||||
@@ -0,0 +1,284 @@
|
||||
services:
|
||||
neo4j-certs-perm-fix:
|
||||
image: busybox
|
||||
command: ["chmod", "-R", "755", "/certs"]
|
||||
volumes:
|
||||
- ./neo4j-certs:/certs
|
||||
networks:
|
||||
- medax-network
|
||||
|
||||
neo4j:
|
||||
image: neo4j:5.7
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
neo4j-certs-perm-fix:
|
||||
condition: service_completed_successfully
|
||||
environment:
|
||||
- NEO4J_AUTH=${NEO4J_AUTH:-neo4j/password}
|
||||
- NEO4J_PLUGINS=["apoc"]
|
||||
- NEO4J_server_config_strict__validation_enabled=false
|
||||
- NEO4J_apoc_export_file_enabled=true
|
||||
- NEO4J_apoc_import_file_enabled=true
|
||||
- NEO4J_apoc_import_file_use__neo4j__config=true
|
||||
- SHARED_PATH=/neo4j_import
|
||||
- NO_PROXY=${NO_PROXY},10.0.0.0/8
|
||||
- DISABLE_IMPORT=${DISABLE_IMPORT:-false}
|
||||
- ENABLE_BOLT_TLS=${ENABLE_BOLT_TLS:-false}
|
||||
|
||||
command: >
|
||||
bash -c '
|
||||
if [ "$${DISABLE_IMPORT:-false}" != "true" ]; then
|
||||
echo "running cmd from docker compose" &&
|
||||
# Copy plugin files if they exist
|
||||
if [ -f /init_files/semspect_neo4j-plugin-8.2.0.jar ]; then
|
||||
echo "Copying Semspect plugin..." &&
|
||||
cp /init_files/semspect_neo4j-plugin-8.2.0.jar /var/lib/neo4j/plugins/
|
||||
fi
|
||||
if [ -f /init_files/semspect.lic ]; then
|
||||
echo "Copying Semspect license..." &&
|
||||
cp /init_files/semspect.lic /var/lib/neo4j/plugins/
|
||||
fi
|
||||
# Copy neo4j.conf if it exists
|
||||
ls
|
||||
ls /init_files/
|
||||
if [ -f /init_files/neo4j.conf ]; then
|
||||
echo "Copying Neo4j configuration..." &&
|
||||
cp /init_files/neo4j.conf /var/lib/neo4j/conf/
|
||||
fi
|
||||
if [ "$${ENABLE_BOLT_TLS:-false}" != "true" ]; then
|
||||
echo "ENABLE_BOLT_TLS is not true - disabling required bolt TLS (no certs mounted in ./neo4j-certs locally)." &&
|
||||
sed -i \
|
||||
-e "s/^server.bolt.tls_level=REQUIRED/#server.bolt.tls_level=REQUIRED/" \
|
||||
-e "s/^dbms.ssl.policy.bolt.enabled=true/#dbms.ssl.policy.bolt.enabled=true/" \
|
||||
/var/lib/neo4j/conf/neo4j.conf
|
||||
fi
|
||||
echo "<----------------"
|
||||
# Set proper permissions
|
||||
#chown -R neo4j:neo4j /var/lib/neo4j/plugins
|
||||
#chown -R neo4j:neo4j /var/lib/neo4j/conf
|
||||
echo "Import loop is enabled."
|
||||
|
||||
while true; do
|
||||
if [ -f /neo4j_import/ready-to-import ]; then
|
||||
echo "Starting import process..."
|
||||
neo4j stop &&
|
||||
bash /neo4j_import/neo4j-admin-import-call.sh &&
|
||||
rm /neo4j_import/ready-to-import &&
|
||||
touch /neo4j_import/import-complete &&
|
||||
chmod 777 /neo4j_import/import-complete
|
||||
neo4j start
|
||||
echo "The container is running. CTRL+C will end the bash command and thus, the neo4j container"
|
||||
fi
|
||||
sleep 10
|
||||
done
|
||||
else
|
||||
echo "Import loop is disabled. Keeping Neo4j container alive..."
|
||||
cp /init_files/neo4j.conf /var/lib/neo4j/conf/
|
||||
if [ "$${ENABLE_BOLT_TLS:-false}" != "true" ]; then
|
||||
echo "ENABLE_BOLT_TLS is not true - disabling required bolt TLS (no certs mounted in ./neo4j-certs locally)." &&
|
||||
sed -i \
|
||||
-e "s/^server.bolt.tls_level=REQUIRED/#server.bolt.tls_level=REQUIRED/" \
|
||||
-e "s/^dbms.ssl.policy.bolt.enabled=true/#dbms.ssl.policy.bolt.enabled=true/" \
|
||||
/var/lib/neo4j/conf/neo4j.conf
|
||||
fi
|
||||
neo4j start
|
||||
echo "The container is running. CTRL+C will end the bash command and thus, the neo4j container"
|
||||
tail -f /dev/null
|
||||
fi
|
||||
'
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:7474 || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
ports:
|
||||
- "8080:7474" # direct access — remove in production
|
||||
- "8081:7687" # remove in production
|
||||
#- "127.0.0.1:8080:7474" #production
|
||||
#- "127.0.0.1:8081:7687"
|
||||
|
||||
volumes:
|
||||
- neo4j_data:/data
|
||||
- neo4j_logs:/logs
|
||||
- neo4j_import:/neo4j_import
|
||||
- ${INPUT_DATA_PATH:-./data}:/input_data
|
||||
- ./init_scripts:/init_scripts
|
||||
- ./init_files:/init_files
|
||||
- ./importData:/importData
|
||||
- ./neo4j-certs:/var/lib/neo4j/certificates
|
||||
networks:
|
||||
- medax-network
|
||||
|
||||
python_app:
|
||||
depends_on:
|
||||
loader:
|
||||
condition: service_completed_successfully
|
||||
required: false
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
HTTP_PROXY: ${HTTP_PROXY}
|
||||
HTTPS_PROXY: ${HTTPS_PROXY}
|
||||
NO_PROXY: ${NO_PROXY}
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- NEO4J_URI=bolt://neo4j:7687
|
||||
- NEO4J_USER=${NEO4J_USER:-neo4j}
|
||||
- NEO4J_PASSWORD=${NEO4J_PASSWORD:-password}
|
||||
- DISABLE_IMPORT=${DISABLE_IMPORT:-false}
|
||||
- INPUT_DATA_PATH=/input_data
|
||||
- POETRY_VIRTUALENVS_CREATE=false
|
||||
- NEO4J_dbms_directories_import=/neo4j_import
|
||||
volumes:
|
||||
- neo4j_import:/neo4j_import
|
||||
- ${INPUT_DATA_PATH:-./data}:/input_data
|
||||
- ./importData:/importData # Share the import data directory
|
||||
# depends_on:
|
||||
# neo4j:
|
||||
# condition: service_healthy
|
||||
networks:
|
||||
- medax-network
|
||||
|
||||
oauth2-proxy:
|
||||
profiles:
|
||||
- server
|
||||
image: quay.io/oauth2-proxy/oauth2-proxy:v7.6.0
|
||||
container_name: oauth2-proxy
|
||||
restart: unless-stopped
|
||||
extra_hosts:
|
||||
- "keycloak-test.miracum.med.uni-greifswald.de:10.66.82.11"
|
||||
- "host.docker.internal:host-gateway"
|
||||
|
||||
command:
|
||||
- --config=/etc/oauth2-proxy.cfg
|
||||
ports:
|
||||
# - "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./oauth_conf/oauth2-proxy.cfg:/etc/oauth2-proxy.cfg:ro
|
||||
- ./oauth_templates:/templates:ro
|
||||
- ./oauth-certs:/etc/oauth-certs:ro
|
||||
- ./logs:/var/log/oauth2-proxy
|
||||
depends_on:
|
||||
- neo4j
|
||||
|
||||
networks:
|
||||
- medax-network
|
||||
|
||||
nginx:
|
||||
profiles:
|
||||
- server
|
||||
image: nginx:latest
|
||||
container_name: neo4j-nginx
|
||||
|
||||
## ports:
|
||||
## - "8088:80"
|
||||
# - "7687:7687"
|
||||
|
||||
volumes:
|
||||
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
- ./logs:/var/log/nginx
|
||||
depends_on:
|
||||
- neo4j
|
||||
|
||||
networks:
|
||||
- medax-network
|
||||
|
||||
neo4j-proxy:
|
||||
profiles:
|
||||
- proxy
|
||||
image: quay.io/oauth2-proxy/oauth2-proxy:latest
|
||||
restart: on-failure
|
||||
environment:
|
||||
OAUTH2_PROXY_PROVIDER: keycloak-oidc
|
||||
OAUTH2_PROXY_CLIENT_ID: ${NEO4J_OAUTH_CLIENT_ID:-neo4j-client}
|
||||
OAUTH2_PROXY_CLIENT_SECRET: ${NEO4J_OAUTH_SECRET}
|
||||
OAUTH2_PROXY_OIDC_ISSUER_URL: ${KEYCLOAK_ISSUER_URL}
|
||||
OAUTH2_PROXY_UPSTREAMS: http://neo4j:7474/
|
||||
OAUTH2_PROXY_HTTP_ADDRESS: 0.0.0.0:4180
|
||||
OAUTH2_PROXY_REDIRECT_URL: ${NEO4J_PROXY_REDIRECT_URL:-http://localhost:8082/oauth2/callback}
|
||||
OAUTH2_PROXY_COOKIE_SECRET: ${NEO4J_COOKIE_SECRET}
|
||||
OAUTH2_PROXY_EMAIL_DOMAINS: "*"
|
||||
OAUTH2_PROXY_SKIP_PROVIDER_BUTTON: "true"
|
||||
OAUTH2_PROXY_INSECURE_OIDC_SKIP_ISSUER_VERIFICATION: "true"
|
||||
OAUTH2_PROXY_COOKIE_SECURE: "false"
|
||||
extra_hosts:
|
||||
- "localhost:host-gateway"
|
||||
ports:
|
||||
- "8082:4180"
|
||||
depends_on:
|
||||
- neo4j
|
||||
#- keycloak # enable if a local keycloak is set up
|
||||
|
||||
keycloak:
|
||||
profiles:
|
||||
- keycloak
|
||||
image: quay.io/keycloak/keycloak:latest
|
||||
command: start-dev
|
||||
restart: on-failure
|
||||
environment:
|
||||
KC_BOOTSTRAP_ADMIN_USERNAME: admin
|
||||
KC_BOOTSTRAP_ADMIN_PASSWORD: admin
|
||||
KC_HOSTNAME: http://localhost:4040
|
||||
KC_HOSTNAME_BACKCHANNEL_DYNAMIC: "true"
|
||||
ports:
|
||||
- "4040:8080"
|
||||
volumes:
|
||||
- keycloak_data:/opt/keycloak/data
|
||||
|
||||
blaze:
|
||||
profiles:
|
||||
- blaze
|
||||
image: samply/blaze:latest
|
||||
ports:
|
||||
- "8090:8080"
|
||||
environment:
|
||||
JAVA_TOOL_OPTIONS: "-Xmx2g"
|
||||
ENFORCE_REFERENTIAL_INTEGRITY: "false"
|
||||
volumes:
|
||||
- blaze-data:/app/data
|
||||
healthcheck:
|
||||
test: curl -f http://localhost:8080/fhir/metadata || exit 1
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
networks:
|
||||
- medax-network
|
||||
|
||||
|
||||
loader:
|
||||
profiles:
|
||||
- blaze
|
||||
image: curlimages/curl:latest
|
||||
depends_on:
|
||||
blaze:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- ./testData/POLAR_mock:/bundles:ro #adapt first part to whereever the testfiles are stored
|
||||
entrypoint: /bin/sh
|
||||
command: >
|
||||
-c '
|
||||
for f in /bundles/*.json; do
|
||||
echo "Loading $$f ...";
|
||||
curl -s -X POST http://blaze:8080/fhir \
|
||||
-H "Content-Type: application/fhir+json" \
|
||||
-d @"$$f" > /dev/null;
|
||||
echo "";
|
||||
done;
|
||||
echo "Done loading all bundles."
|
||||
'
|
||||
networks:
|
||||
- medax-network
|
||||
# Define named volumes
|
||||
volumes:
|
||||
neo4j_data:
|
||||
neo4j_logs:
|
||||
neo4j_import:
|
||||
keycloak_data:
|
||||
blaze-data:
|
||||
|
||||
networks:
|
||||
medax-network:
|
||||
driver: bridge
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ "${DISABLE_IMPORT:-false}" = "true" ]; then
|
||||
echo "DISABLE_IMPORT is true - skipping ETL pipeline to only up the GDB"
|
||||
exit 0
|
||||
fi
|
||||
# Wait for a file to appear, with a timeout so the container can't hang forever.
|
||||
# Usage: wait_for_file <path> [timeout_seconds]
|
||||
wait_for_file() {
|
||||
local file="$1"
|
||||
local timeout="${2:-300}"
|
||||
local waited=0
|
||||
while [ ! -f "$file" ]; do
|
||||
if [ "$waited" -ge "$timeout" ]; then
|
||||
echo "Timed out after ${timeout}s waiting for $(basename "$file")" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Waiting for $(basename "$file")..."
|
||||
sleep 5
|
||||
waited=$((waited + 5))
|
||||
done
|
||||
}
|
||||
|
||||
# Make the import dir accessible to both the Python app and Neo4j
|
||||
chmod -R 777 /neo4j_import
|
||||
|
||||
#echo "Waiting for Neo4j to be ready..."
|
||||
#python wait-for-neo4j.py
|
||||
|
||||
echo "Running Python data processing script..."
|
||||
#python import_fhir_to_nx_diGraph.py
|
||||
python multiple_adapters.py
|
||||
|
||||
echo "Running Neo4j import..."
|
||||
# Give downstream services a moment before touching the database
|
||||
sleep 5
|
||||
|
||||
# Wait for the shell script to finish writing its data
|
||||
wait_for_file /neo4j_import/shell-scipt-complete
|
||||
|
||||
# Signal that data is prepared and ready to import
|
||||
touch /neo4j_import/ready-to-import
|
||||
chmod 777 /neo4j_import/ready-to-import
|
||||
|
||||
# Wait for the import to complete
|
||||
echo "Waiting for Neo4j import to complete..."
|
||||
wait_for_file /neo4j_import/import-complete
|
||||
|
||||
echo "Database setup complete!"
|
||||
@@ -0,0 +1,72 @@
|
||||
import requests
|
||||
from typing import List, Dict, Any
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
from requests.auth import HTTPBasicAuth
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
def get_bundle(url, search):
|
||||
headers = {
|
||||
'Accept': 'application/fhir+json',
|
||||
'Content-Type': 'application/fhir+json'
|
||||
}
|
||||
|
||||
# Get configuration from environment variables
|
||||
mode = os.getenv('MODE')
|
||||
fhir_server = os.getenv('FHIR_SERVER_URL')
|
||||
|
||||
if mode != 'testsever':
|
||||
username = os.getenv('FHIR_SERVER_USER')
|
||||
password = os.getenv('FHIR_SERVER_PW')
|
||||
|
||||
|
||||
if not fhir_server:
|
||||
raise ValueError("FHIR_SERVER_URL not found in environment variables")
|
||||
if (not username or not password) and mode != 'testserver':
|
||||
raise ValueError("FHIR_USERNAME and FHIR_SERVER_PW must be set in environment variables")
|
||||
|
||||
# Setup basic authentication
|
||||
auth = HTTPBasicAuth(username, password)
|
||||
|
||||
|
||||
if url is not None:
|
||||
if '?' in url:
|
||||
link = url + '&_format=json'
|
||||
else:
|
||||
link = url + '?_format=json'
|
||||
else:
|
||||
link = fhir_server + search + '&_format=json'
|
||||
|
||||
#print(link)
|
||||
|
||||
if mode != 'testserver':
|
||||
response = requests.get(
|
||||
link,
|
||||
headers=headers,
|
||||
auth=auth,
|
||||
timeout=300
|
||||
|
||||
)
|
||||
else:
|
||||
response = requests.get(
|
||||
link,
|
||||
headers=headers,
|
||||
timeout=300
|
||||
)
|
||||
return response
|
||||
|
||||
def get_patient_everything(id: str):
|
||||
search = '/Patient/' + id + '/$everything?'
|
||||
return get_bundle(None, search)
|
||||
|
||||
|
||||
# Example usage
|
||||
if __name__ == "__main__":
|
||||
bundles = get_bundles(None)
|
||||
data = bundles.json()
|
||||
|
||||
# Process the bundles
|
||||
for entry in data['entry']:
|
||||
print(f"{entry['fullUrl']}")
|
||||
@@ -0,0 +1,42 @@
|
||||
import json
|
||||
import networkx as nx
|
||||
|
||||
def add_nodes_from_dict(graph, parent_node, current_dict):
|
||||
for key, value in current_dict.items():
|
||||
if isinstance(value, dict):
|
||||
# Create a new node for the nested dictionary
|
||||
new_node = f"{parent_node}.{key}"
|
||||
graph.add_node(new_node, label=key)
|
||||
# Add an edge from the parent node to the new node
|
||||
graph.add_edge(parent_node, new_node, edge_type=key)
|
||||
# Recurse into the nested dictionary
|
||||
add_nodes_from_dict(graph, new_node, value)
|
||||
elif isinstance(value, list):
|
||||
# if list doesn't contain any nested dictionaries, make it a value in the node
|
||||
if any(isinstance(item, dict) for item in value)==False:
|
||||
graph.nodes[parent_node][key] = value
|
||||
else:
|
||||
process_dictionaries(value, parent_node, key, graph)
|
||||
|
||||
else:
|
||||
# For non-dict and non-list values, add them as attributes to the parent node
|
||||
graph.nodes[parent_node][key] = value
|
||||
|
||||
def process_dictionaries(value, parent_node, key, graph):
|
||||
# Process each dictionary in the list
|
||||
for index, item in enumerate(value):
|
||||
if isinstance(item, dict):
|
||||
if len(value)>1:
|
||||
item_node = f"{parent_node}.{key}[{index}]"
|
||||
else:
|
||||
item_node = f"{parent_node}.{key}"
|
||||
graph.add_node(item_node, label=key)
|
||||
graph.add_edge(parent_node, item_node, edge_type=key)
|
||||
add_nodes_from_dict(graph, item_node, item)
|
||||
|
||||
def add_json_to_networkx(json_data, bundle_name, graph):
|
||||
if not isinstance(graph, nx.DiGraph):
|
||||
raise ValueError("The provided graph must be a networkx.DiGraph")
|
||||
root_node = bundle_name+'_bundle'
|
||||
graph.add_node(root_node, label='root')
|
||||
add_nodes_from_dict(graph, root_node, json_data)
|
||||
@@ -0,0 +1,38 @@
|
||||
import networkx as nx
|
||||
|
||||
class Resource:
|
||||
def __init__(self, resource_type):
|
||||
self.resource_type = resource_type
|
||||
|
||||
def create_resource_class(resource_type):
|
||||
return type(resource_type, (Resource,), {})
|
||||
|
||||
def set_resource_type(graph):
|
||||
for node, data in graph.nodes(data=True):
|
||||
print(node, data)
|
||||
|
||||
print("-----------------------------")
|
||||
|
||||
nodes_to_replace = []
|
||||
for node, data in graph.nodes(data=True):
|
||||
print(isinstance(node, Resource), node, type(node))
|
||||
if isinstance(node, Resource):
|
||||
print("Found a resource: ", node)
|
||||
resource_type = node.resource_type
|
||||
if resource_type:
|
||||
# Dynamically create a new class based on the resource_type
|
||||
new_resource_class = create_resource_class(resource_type)
|
||||
new_node = new_resource_class(resource_type)
|
||||
nodes_to_replace.append((node, new_node, data))
|
||||
else:
|
||||
print(f"Warning: Node {node} is a resource but has no resource_type")
|
||||
|
||||
# Replace old nodes with new ones
|
||||
for old_node, new_node, data in nodes_to_replace:
|
||||
graph.add_node(new_node, **data)
|
||||
for pred in graph.predecessors(old_node):
|
||||
graph.add_edge(pred, new_node)
|
||||
for succ in graph.successors(old_node):
|
||||
graph.add_edge(new_node, succ)
|
||||
graph.remove_node(old_node)
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import networkx as nx
|
||||
import os
|
||||
|
||||
|
||||
def parse_synthea_reference(ref):
|
||||
if not ref.startswith('#'):
|
||||
#print("reference: ", ref)
|
||||
if '?' in ref and '|' in ref:
|
||||
parsed_ref = ref.split('|')[1]
|
||||
# elif '/' in ref:
|
||||
# parsed_ref = ref.split('/')[1]
|
||||
else:
|
||||
parsed_ref = ref.split(':')[2]
|
||||
else:
|
||||
parsed_ref = 'mock'
|
||||
return(parsed_ref)
|
||||
|
||||
def process_references(graph):
|
||||
|
||||
is_synthea = os.getenv('IS_SYNTHEA')
|
||||
print("--------------->", is_synthea)
|
||||
|
||||
nodes_with_reference = [[n, attr['reference']] for n, attr in graph.nodes(data=True) if 'reference' in attr]
|
||||
directly_referenced_nodes = []
|
||||
|
||||
indirectly_referenced_nodes = []
|
||||
|
||||
dummy_references = []
|
||||
|
||||
if is_synthea and is_synthea.upper() == 'TRUE':
|
||||
|
||||
nodes_with_mock_reference = []
|
||||
|
||||
for i in range(len(nodes_with_reference)):
|
||||
reference = nodes_with_reference[i][1]
|
||||
parsed_reference = parse_synthea_reference(reference)
|
||||
|
||||
if parsed_reference != 'mock':
|
||||
nodes_with_reference[i].append(parsed_reference)
|
||||
else:
|
||||
nodes_with_mock_reference.append(i)
|
||||
|
||||
for i in sorted(nodes_with_mock_reference, reverse=True):
|
||||
del nodes_with_reference[i]
|
||||
|
||||
id_to_node = {data["id"]: node for node, data in graph.nodes(data=True) if "id" in data}
|
||||
id_to_identifier_node = {data["value"]: node for node, data in graph.nodes(data=True) if ("value" in data and data['label'] == 'identifier')}
|
||||
|
||||
for i in nodes_with_reference:
|
||||
ref_id=i[2]
|
||||
if ref_id in id_to_node.keys():
|
||||
directly_referenced_nodes.append([i[0], id_to_node[ref_id]])
|
||||
elif ref_id in id_to_identifier_node.keys():
|
||||
indirectly_referenced_nodes.append([i[0], id_to_identifier_node[ref_id]])
|
||||
#else:
|
||||
# print("KEY ERROR: Key neither in to_node nor in to_identifier_node", i)
|
||||
|
||||
for i in indirectly_referenced_nodes:
|
||||
node_from=list(graph.predecessors(i[0]))[0]
|
||||
node_to=list(graph.predecessors(i[1]))[0]
|
||||
ref_type=graph.nodes[i[0]]['label']
|
||||
graph.add_edge(node_from, node_to, edge_type='reference', reference_type=ref_type)
|
||||
|
||||
else:
|
||||
id_to_node = {data["resourceType"]+'/'+data["id"]: node for node, data in graph.nodes(data=True) if ("id" in data and "resourceType" in data)}
|
||||
|
||||
for i in nodes_with_reference:
|
||||
|
||||
ref_id=i[1]
|
||||
if ref_id in id_to_node.keys():
|
||||
#print("everything is here?", ref_id)
|
||||
directly_referenced_nodes.append([i[0], id_to_node[ref_id]])
|
||||
else:
|
||||
#print("ALL WE KNOW ABOUT DUMMY NODES: ", ref_id)
|
||||
dummy_references.append([i[0], ref_id])
|
||||
|
||||
for i in directly_referenced_nodes:
|
||||
node_from=list(graph.predecessors(i[0]))[0]
|
||||
node_to=i[1]
|
||||
ref_type=graph.nodes[i[0]]['label']
|
||||
graph.add_edge(node_from, node_to, edge_type='reference', reference_type=ref_type)
|
||||
|
||||
for i in dummy_references:
|
||||
ref_type = i[1][:i[1].find('/')]
|
||||
node_to='dummy_' + i[1]
|
||||
graph.add_node(node_to, label='dummy', unique_id=i[1], edge_label=ref_type)
|
||||
node_from=list(graph.predecessors(i[0]))[0]
|
||||
graph.add_edge(node_from, node_to, edge_type='reference', reference_type=ref_type)
|
||||
|
||||
graph.remove_nodes_from([i[0] for i in directly_referenced_nodes])
|
||||
graph.remove_nodes_from([i[0] for i in indirectly_referenced_nodes])
|
||||
graph.remove_nodes_from([i[0] for i in dummy_references])
|
||||
|
||||
nodes_to_remove = [n for n, attr in graph.nodes(data=True) if attr.get('label') in ['root', 'entry', 'request']]
|
||||
|
||||
graph.remove_nodes_from(nodes_to_remove)
|
||||
@@ -0,0 +1,107 @@
|
||||
import networkx as nx
|
||||
|
||||
def find_paths(graph, start_node):
|
||||
def is_leaf(node):
|
||||
#Checks if a node is a leaf (no outgoing edges)
|
||||
return graph.out_degree(node) == 0
|
||||
|
||||
def custom_dfs(path, reference_count):
|
||||
#Performs a DFS to find paths for both patterns
|
||||
current_node = path[-1]
|
||||
|
||||
'''if the current node is labeled 'resource', the path length is greater than 3,
|
||||
and we have exactly one 'reference' edge in the path'''
|
||||
if len(path) > 3 and graph.nodes[current_node].get('label') == 'resource' and reference_count == 1:
|
||||
# add path to the list of property paths containing a reference
|
||||
reference_paths.append(list(path))
|
||||
|
||||
'''if the current node is a leaf node (no outgoing edges),
|
||||
the path length is greater than 2, and we have no references in the path'''
|
||||
if len(path) > 2 and is_leaf(current_node) and reference_count == 0:
|
||||
'''add path to the dictionary of property paths ending in leaves,
|
||||
by the corresponding property key'''
|
||||
leaf_paths.setdefault(path[1].split('.')[-1], []).extend(list(path))
|
||||
|
||||
# check neighbors
|
||||
for neighbor in graph.successors(current_node):
|
||||
edge_type = graph.edges[current_node, neighbor].get('edge_type', None)
|
||||
new_reference_count = reference_count + (1 if edge_type == 'reference' else 0)
|
||||
|
||||
# continue the search only if we have at most one 'reference' edge so far
|
||||
if new_reference_count <= 1:
|
||||
custom_dfs(path + [neighbor], new_reference_count)
|
||||
|
||||
reference_paths = []
|
||||
leaf_paths = {}
|
||||
|
||||
custom_dfs([start_node], 0)
|
||||
|
||||
return reference_paths, leaf_paths
|
||||
|
||||
def property_convolution(graph):
|
||||
|
||||
# Find all nodes with label 'resource'
|
||||
resource_nodes = [n for n, attr in graph.nodes(data=True) if attr.get('label') == 'resource']
|
||||
|
||||
#print("Got all nodes with label 'resource'", flush=True)
|
||||
|
||||
'''collect all paths starting with a resource node, that contain one reference edge,
|
||||
end with a resource node and are >3 nodes long'''
|
||||
'''collect all paths starting with a resource node, that do not contain reference edges,
|
||||
end with a leaf node and are >2 nodes long'''
|
||||
|
||||
property_paths_with_reference = []
|
||||
property_paths_with_leaves = {}
|
||||
|
||||
for resource_node in resource_nodes:
|
||||
temp_ref_paths, temp_leaf_paths = find_paths(graph, resource_node)
|
||||
# add paths to the list of property paths containing a reference, for all nodes
|
||||
property_paths_with_reference.extend(temp_ref_paths)
|
||||
# add paths to the dictionary of property paths ending in leaves, by the corresponding resouce key
|
||||
property_paths_with_leaves[resource_node] = temp_leaf_paths
|
||||
|
||||
# print("Collected all paths", flush=True)
|
||||
|
||||
# transfer reference edge to first property node for all reference paths
|
||||
for i in property_paths_with_reference:
|
||||
ref_edge_data = graph.get_edge_data(i[-2], i[-1])
|
||||
ref_type = ref_edge_data.get('reference_type')
|
||||
graph.remove_edge(i[-2], i[-1])
|
||||
graph.add_edge(i[1], i[-1], edge_type='reference', reference_type=ref_type)
|
||||
|
||||
'''after transferrence, add the modified reference path (that now ends in a leaf)
|
||||
to the dictionary of leaf paths, by corresponding resource and property keys'''
|
||||
property_paths_with_leaves[i[0]].setdefault(i[1].split('.')[-1], []).extend(i[:-1])
|
||||
|
||||
#print("Transfered all references edges", flush=True)
|
||||
|
||||
'''create a list of collections of property paths ending in leaves,
|
||||
removing duplicate nodes from each path collection'''
|
||||
list_property_paths_with_leaves = [list(dict.fromkeys(i)) for j in property_paths_with_leaves.values() for i in j.values()]
|
||||
|
||||
nodes_to_remove=[]
|
||||
|
||||
for i in list_property_paths_with_leaves:
|
||||
for j in range(len(i)-1, 1, -1):
|
||||
|
||||
source_attributes = graph.nodes[i[j]]
|
||||
|
||||
marker='|'.join(i[j].split('resource.')[1].split('.')[1:])
|
||||
|
||||
# transfer attributes to first property node
|
||||
for attr, value in source_attributes.items():
|
||||
if attr != 'label':
|
||||
graph.nodes[i[1]][marker+'_'+attr] = value
|
||||
|
||||
nodes_to_remove.append(i[j])
|
||||
|
||||
#print("Transferred attributes for all paths", flush=True)
|
||||
|
||||
graph.remove_nodes_from(nodes_to_remove)
|
||||
|
||||
for i in resource_nodes:
|
||||
unique_resource_id = graph.nodes[i]['resourceType']+'/'+graph.nodes[i]['id']
|
||||
graph.nodes[i]['unique_id'] = unique_resource_id
|
||||
for j in graph.successors(i):
|
||||
if graph[i][j].get('edge_type') != 'reference':
|
||||
graph.nodes[j]['unique_id'] = unique_resource_id+'/'+j.split('.')[-1]
|
||||
@@ -0,0 +1,303 @@
|
||||
from biocypher import BioCypher
|
||||
import networkx as nx
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import uuid
|
||||
import gc
|
||||
from dotenv import load_dotenv
|
||||
|
||||
|
||||
from graphCreation import create_graph
|
||||
|
||||
from graphCreation.process_references import process_references
|
||||
|
||||
from graphCreation.property_convolution import property_convolution
|
||||
|
||||
from schema_config_generation import write_automated_schema
|
||||
|
||||
from fhirImport import get_patient_everything, get_bundle
|
||||
|
||||
is_test = os.getenv('TEST_MODE')
|
||||
test_depth = os.getenv('TEST_DEPTH')
|
||||
|
||||
|
||||
|
||||
def load_multiple_fhir_patients(n):
|
||||
init_load = True
|
||||
ids = []
|
||||
#get n ids
|
||||
init_ids = True
|
||||
while len(ids) < n and init_ids:
|
||||
if init_load:
|
||||
is_complex = os.getenv('COMPLEX_PATIENTS')
|
||||
selected_patients_file = os.getenv('SELECTED_PATIENTS_FILE')
|
||||
#print("> /dev/null")
|
||||
if selected_patients_file and os.path.isfile(selected_patients_file):
|
||||
print(f"-- Loading patient IDs from file: {selected_patients_file} --")
|
||||
with open(selected_patients_file, 'r') as f:
|
||||
for line in f:
|
||||
patient_id = line.strip()
|
||||
if patient_id:
|
||||
ids.append(patient_id)
|
||||
print(f"Loaded {len(ids)} patient IDs from file.")
|
||||
n = len(ids)
|
||||
break # Exit the while loop since we got IDs from the file
|
||||
|
||||
elif is_complex and is_complex.upper() != 'TRUE':
|
||||
bundle = get_bundle(None, '/Patient?_count=' + str(n))
|
||||
else:
|
||||
#print("-- Looking for complex patients --")
|
||||
print("-- Looking for gout patients --")
|
||||
#bundle = get_bundle(None, '/Patient?_has:Observation:subject:status=final&_count=' + str(n))
|
||||
bundle = get_bundle(None, '/Patient?_has:Condition:patient:code=M10.00,M10.01,M10.02,M10.03,M10.04,M10.05,M10.06,M10.07,M10.08,M10.09,M10.10,M10.11,M10.12,M10.13,M10.14,M10.15,M10.16,M10.17,M10.18,M10.19,M10.20,M10.21,M10.22,M10.23,M10.24,M10.25,M10.26,M10.27,M10.28,M10.29,M10.30,M10.31,M10.32,M10.33,M10.34,M10.35,M10.36,M10.37,M10.38,M10.39,M10.40,M10.41,M10.42,M10.43,M10.44,M10.45,M10.46,M10.47,M10.48,M10.49,M10.90,M10.91,M10.92,M10.93,M10.94,M10.95,M10.96,M10.97,M10.98,M10.99&_count=' + str(n))
|
||||
print("Got patient ids")
|
||||
init_load = False
|
||||
else:
|
||||
print("NEXT LINK: ", next_link, flush=True)
|
||||
bundle = get_bundle(next_link, None)
|
||||
|
||||
#print(bundle.json())
|
||||
if 'entry' not in bundle.json():
|
||||
print("ERROR -- No data found in the fhir bundle. Check the request and if the server is up and responding")
|
||||
sys.exit(1)
|
||||
|
||||
for entry in bundle.json()['entry']:
|
||||
ids.append(entry['resource']['id'])
|
||||
|
||||
init_ids = False
|
||||
for l in bundle.json()['link']:
|
||||
if l['relation'] == "next":
|
||||
next_link = l['url']
|
||||
init_ids = True
|
||||
|
||||
if len(ids) < n:
|
||||
n = len(ids)
|
||||
|
||||
print("####GRABBED ", n , " IDs from the DB####", flush=True)
|
||||
bundles_to_graph(ids, n)
|
||||
|
||||
|
||||
def bundles_to_graph(ids, n):
|
||||
|
||||
init = True
|
||||
batch_size = int(os.getenv('BATCH_SIZE'))
|
||||
c = 0
|
||||
|
||||
print(len(ids))
|
||||
|
||||
#get bundle for each ID
|
||||
for id in ids:
|
||||
|
||||
c += 1
|
||||
bundle = get_patient_everything(id).json()
|
||||
bundle = replace_single_quotes(bundle) ### maybe not needed for german data
|
||||
if init:
|
||||
graph = nx.DiGraph()
|
||||
init = False
|
||||
|
||||
create_graph.add_json_to_networkx(bundle, id + '_bundle', graph)
|
||||
|
||||
if c % 50 == 0:
|
||||
print("---------- ", c, " patients loaded ----------", flush=True)
|
||||
|
||||
if c % batch_size == 0 or c == n:
|
||||
print(c, " patients imported, reducing graph", flush = True)
|
||||
process_references(graph)
|
||||
property_convolution(graph)
|
||||
|
||||
run_biocypher(graph)
|
||||
init = True
|
||||
#print("Full batch:", c)
|
||||
del graph
|
||||
|
||||
def replace_single_quotes(obj):
|
||||
if isinstance(obj, str): # If it's a string, replace single quotes
|
||||
return obj.replace("'", "''")
|
||||
elif isinstance(obj, dict): # If it's a dictionary, process each key-value pair
|
||||
return {key: replace_single_quotes(value) for key, value in obj.items()}
|
||||
elif isinstance(obj, list): # If it's a list, process each item
|
||||
return [replace_single_quotes(item) for item in obj]
|
||||
else:
|
||||
return obj # Leave other data types unchanged
|
||||
|
||||
def main():
|
||||
## create networkX and run improvement scripts
|
||||
print("Creating the graph...", flush=True)
|
||||
|
||||
n_patients = int(os.getenv('NUMBER_OF_PATIENTS'))
|
||||
load_multiple_fhir_patients(n_patients)
|
||||
|
||||
#write the import script -- we are creating our own script since BC would only consider the last batch as an input
|
||||
print("CREATING THE SCRIPT")
|
||||
generate_neo4j_import_script()
|
||||
with open('/neo4j_import/shell-scipt-complete', 'w') as f:
|
||||
f.write('Import completed successfully')
|
||||
|
||||
print("FHIR import completed successfully")
|
||||
|
||||
|
||||
def run_biocypher(nx_graph):
|
||||
|
||||
#get lists of node and edge types
|
||||
print("Generate auto schema...", flush=True)
|
||||
write_automated_schema(nx_graph, 'config/automated_schema.yaml', 'config/manual_schema_config.yaml')
|
||||
|
||||
|
||||
# create Biocypher driver
|
||||
bc = BioCypher(
|
||||
biocypher_config_path="config/biocypher_config.yaml",
|
||||
)
|
||||
|
||||
#bc.show_ontology_structure() #very extensive
|
||||
#BioCypher preperation
|
||||
|
||||
bc.write_nodes(node_generator(nx_graph))
|
||||
bc.write_edges(edge_generator(nx_graph))
|
||||
|
||||
|
||||
def node_generator(nx_graph):
|
||||
for node in nx_graph.nodes():
|
||||
|
||||
label = nx_graph.nodes[node].get('label')
|
||||
|
||||
if(label == 'dummy' or label == 'Dummy'):
|
||||
print("Skipped dummy: ", nx_graph.nodes[node])
|
||||
continue
|
||||
|
||||
if label == "resource":
|
||||
label = nx_graph.nodes[node].get('resourceType')
|
||||
|
||||
label = label.capitalize()
|
||||
nx_graph.nodes[node]['label'] = label
|
||||
|
||||
if(nx_graph.nodes[node].get('label') in ['search', 'meta', 'link', 'Search', 'Meta', 'Link']):
|
||||
continue
|
||||
|
||||
|
||||
yield(
|
||||
nx_graph.nodes[node].get('unique_id', node), #remark: this returns the node id if this attribute exists. otherwise it returns node which equals the identifier that is used by nx
|
||||
label,
|
||||
nx_graph.nodes[node] # get properties
|
||||
)
|
||||
|
||||
def edge_generator(nx_graph):
|
||||
for edge in nx_graph.edges(data = True):
|
||||
source, target, attributes = edge
|
||||
|
||||
|
||||
s_label = nx_graph.nodes[source].get('label')
|
||||
if s_label == 'resource':
|
||||
s_label = nx_graph.nodes[source].get('resourceType')
|
||||
elif s_label == 'dummy' or s_label == 'Dummy':
|
||||
s_label = nx_graph.nodes[source].get('edge_label')
|
||||
|
||||
t_label = nx_graph.nodes[target].get('label')
|
||||
if t_label == 'resource':
|
||||
t_label = nx_graph.nodes[target].get('resourceType')
|
||||
elif t_label == 'dummy' or t_label == 'Dummy':
|
||||
t_label = nx_graph.nodes[target].get('edge_label')
|
||||
|
||||
label = s_label.capitalize() + '_to_' + t_label.capitalize()
|
||||
|
||||
|
||||
yield(
|
||||
attributes.get('id', str(uuid.uuid4())), # Edge ID (if exists, otherwise use nx internal id)
|
||||
nx_graph.nodes[source].get('unique_id', source),
|
||||
nx_graph.nodes[target].get('unique_id', target),
|
||||
label,
|
||||
attributes # All edge attributes
|
||||
)
|
||||
|
||||
|
||||
def generate_neo4j_import_script(directory_path="/neo4j_import/", output_file="neo4j-admin-import-call.sh"):
|
||||
"""
|
||||
Reads files in a directory and generates a Neo4j import shell script.
|
||||
|
||||
Args:
|
||||
directory_path (str): Path to the directory containing CSV files
|
||||
output_file (str): Name of the output shell script file
|
||||
|
||||
Returns:
|
||||
str: Path to the generated shell script
|
||||
"""
|
||||
# Get all files in the directory
|
||||
all_files = os.listdir(directory_path)
|
||||
|
||||
# Dictionary to store entity types (nodes and relationships)
|
||||
entity_types = {}
|
||||
|
||||
|
||||
# Find all header files and use them to identify entity types
|
||||
for filename in all_files:
|
||||
if '-header.csv' in filename:
|
||||
entity_name = filename.split('-header.csv')[0]
|
||||
|
||||
# Check if it's a relationship (contains "To" and "Association")
|
||||
is_relationship = ("To" in entity_name and "Association" in entity_name) or "_has_" in entity_name or "Has_" in entity_name
|
||||
|
||||
# Store in entity_types dictionary
|
||||
if is_relationship:
|
||||
entity_type = "relationships"
|
||||
else:
|
||||
entity_type = "nodes"
|
||||
|
||||
# Initialize the entity if not already present
|
||||
if entity_name not in entity_types:
|
||||
entity_types[entity_name] = {
|
||||
"type": entity_type,
|
||||
"header": f"/neo4j_import/{filename}",
|
||||
"has_parts": False
|
||||
}
|
||||
|
||||
# Check for part files for each entity
|
||||
for entity_name in entity_types:
|
||||
# Create pattern to match part files for this entity
|
||||
part_pattern = f"{entity_name}-part"
|
||||
|
||||
# Check if any file matches the pattern
|
||||
for filename in all_files:
|
||||
if part_pattern in filename:
|
||||
entity_types[entity_name]["has_parts"] = True
|
||||
break
|
||||
|
||||
# Generate the import commands
|
||||
nodes_command = ""
|
||||
relationships_command = ""
|
||||
|
||||
for entity_name, info in entity_types.items():
|
||||
if info["has_parts"]:
|
||||
# Create the command string with wildcard for part files
|
||||
command = f" --{info['type']}=\"{info['header']},/neo4j_import/{entity_name}-part.*\""
|
||||
|
||||
# Add to appropriate command string
|
||||
if info['type'] == "nodes":
|
||||
nodes_command += command
|
||||
else: # relationships
|
||||
relationships_command += command
|
||||
|
||||
# Create the shell script content
|
||||
script_content = """#!/bin/bash
|
||||
version=$(bin/neo4j-admin --version | cut -d '.' -f 1)
|
||||
if [[ $version -ge 5 ]]; then
|
||||
\tbin/neo4j-admin database import full neo4j --delimiter="\\t" --array-delimiter="|" --quote="'" --overwrite-destination=true --skip-bad-relationships=true --skip-duplicate-nodes=true{nodes}{relationships}
|
||||
else
|
||||
\tbin/neo4j-admin import --database=neo4j --delimiter="\\t" --array-delimiter="|" --quote="'" --force=true --skip-bad-relationships=true --skip-duplicate-nodes=true{nodes}{relationships}
|
||||
fi
|
||||
""".format(nodes=nodes_command, relationships=relationships_command)
|
||||
|
||||
# Write the script to file
|
||||
script_path = os.path.join(directory_path, output_file)
|
||||
with open(script_path, 'w') as f:
|
||||
f.write(script_content)
|
||||
|
||||
# Make the script executable
|
||||
os.chmod(script_path, 0o755)
|
||||
|
||||
print("Shell import script created", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
from biocypher import BioCypher
|
||||
import networkx as nx
|
||||
import json
|
||||
import os
|
||||
import uuid
|
||||
from graphCreation import create_graph
|
||||
from graphCreation.process_references import process_references
|
||||
from graphCreation.property_convolution import property_convolution
|
||||
|
||||
from schema_config_generation import write_automated_schema
|
||||
from graphCreation.node_typing import set_resource_type
|
||||
|
||||
|
||||
|
||||
def load_multiple_fhir_bundles(directory_path):
|
||||
graph = nx.DiGraph()
|
||||
init = True
|
||||
|
||||
# Iterate over all files in the directory
|
||||
for filename in os.listdir(directory_path):
|
||||
if filename.endswith('.json'): # Assuming FHIR bundles are in JSON format
|
||||
file_path = os.path.join(directory_path, filename)
|
||||
with open(file_path, 'r') as f:
|
||||
bundle_json = json.load(f)
|
||||
|
||||
#fix all strings to to enable ' in neo4j
|
||||
fixed_quotes = replace_single_quotes(bundle_json)
|
||||
if init:
|
||||
#print(bundle_json, filename, graph)
|
||||
create_graph.json_to_networkx(fixed_quotes, filename, graph)
|
||||
init = False
|
||||
else:
|
||||
create_graph.add_json_to_networkx(fixed_quotes, filename, graph)
|
||||
print("Imported: ", filename)
|
||||
|
||||
return graph
|
||||
|
||||
def replace_single_quotes(obj):
|
||||
if isinstance(obj, str): # If it's a string, replace single quotes
|
||||
return obj.replace("'", "''")
|
||||
elif isinstance(obj, dict): # If it's a dictionary, process each key-value pair
|
||||
return {key: replace_single_quotes(value) for key, value in obj.items()}
|
||||
elif isinstance(obj, list): # If it's a list, process each item
|
||||
return [replace_single_quotes(item) for item in obj]
|
||||
else:
|
||||
return obj # Leave other data types unchanged
|
||||
|
||||
def main():
|
||||
#get a list of nodes that should be imported
|
||||
## create networkX and run improvement scripts
|
||||
print("Creating the graph...", flush=True)
|
||||
nx_graph = load_multiple_fhir_bundles('./testData/') # 'mockData' for unit test data, 'testData' for Synthea files
|
||||
print(nx_graph)
|
||||
|
||||
print("Reducing references...", flush=True)
|
||||
process_references(nx_graph)
|
||||
print(nx_graph)
|
||||
|
||||
print("Convolute references...", flush=True)
|
||||
property_convolution(nx_graph)
|
||||
print(nx_graph)
|
||||
|
||||
print("Generate auto schema...")
|
||||
write_automated_schema(nx_graph, 'config/automated_schema.yaml', None)
|
||||
|
||||
|
||||
# create Biocypher driver
|
||||
bc = BioCypher(
|
||||
biocypher_config_path="config/biocypher_config.yaml",
|
||||
)
|
||||
|
||||
bc.show_ontology_structure()
|
||||
|
||||
#BioCypher preperation
|
||||
## node generator: extract id, label and property dictionary
|
||||
def node_generator():
|
||||
for node in nx_graph.nodes():
|
||||
|
||||
""" #single qoutes break neo4j import, e.g. 'CHILDREN'S Hospital'
|
||||
checkDisplay = nx_graph.nodes[node].get('display')
|
||||
if checkDisplay:
|
||||
checkDisplay = checkDisplay.replace("'", "''")
|
||||
nx_graph.nodes[node]['display'] = checkDisplay
|
||||
#print("------->", nx_graph.nodes[node].get('display'))
|
||||
|
||||
checkName = nx_graph.nodes[node].get('name')
|
||||
if checkName:
|
||||
checkName = checkName.replace("'", "''")
|
||||
nx_graph.nodes[node]['name'] = checkName
|
||||
#print("------->", nx_graph.nodes[node].get('name')) """
|
||||
|
||||
label = nx_graph.nodes[node].get('label')
|
||||
|
||||
if label == "resource":
|
||||
label = nx_graph.nodes[node].get('resourceType')
|
||||
'''
|
||||
elif label == 'identifier':
|
||||
label = nx_graph.nodes[node].get('system')
|
||||
print('/' in label)
|
||||
if '/' in label:
|
||||
lastSlash = label.rfind('/') + 1
|
||||
label = label[lastSlash:] + '-ID'
|
||||
elif label == 'telecom':
|
||||
label = nx_graph.nodes[node].get('system')
|
||||
print('/' in label)
|
||||
if '/' in label:
|
||||
lastSlash = label.rfind('/') + 1
|
||||
label = 'telecom-' + label[lastSlash:]
|
||||
elif label == 'address':
|
||||
extension = nx_graph.nodes[node].get('extension_url')
|
||||
print("EX!: ", extension)
|
||||
if extension:
|
||||
lastSlash = extension.rfind('/') + 1
|
||||
label = label + '-' + extension[lastSlash:]
|
||||
'''
|
||||
|
||||
yield(
|
||||
nx_graph.nodes[node].get('id', node), #remark: this returns the node id if this attribute exists. otherwise it returns node which equals the identifier that is used by nx
|
||||
label,
|
||||
nx_graph.nodes[node] # get properties
|
||||
)
|
||||
|
||||
def edge_generator():
|
||||
for edge in nx_graph.edges(data = True):
|
||||
source, target, attributes = edge
|
||||
|
||||
s_label = nx_graph.nodes[source].get('label')
|
||||
if s_label == 'resource':
|
||||
s_label = nx_graph.nodes[source].get('resourceType')
|
||||
t_label = nx_graph.nodes[target].get('label')
|
||||
if t_label == 'resource':
|
||||
t_label = nx_graph.nodes[target].get('resourceType')
|
||||
label = s_label + '_to_' + t_label
|
||||
|
||||
yield(
|
||||
attributes.get('id', str(uuid.uuid4())), # Edge ID (if exists, otherwise use nx internal id)
|
||||
nx_graph.nodes[source].get('id', source),
|
||||
nx_graph.nodes[target].get('id', target),
|
||||
label,
|
||||
attributes # All edge attributes
|
||||
)
|
||||
|
||||
bc.write_nodes(node_generator())
|
||||
bc.write_edges(edge_generator())
|
||||
|
||||
#write the import script
|
||||
bc.write_import_call()
|
||||
|
||||
if __name__ == "__main__":
|
||||
#print("Called import script. Should run its main function now...")
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// Example initialization script - modify according to your schema
|
||||
CREATE CONSTRAINT IF NOT EXISTS FOR (n:YourLabel) REQUIRE n.id IS UNIQUE;
|
||||
CREATE INDEX IF NOT EXISTS FOR (n:YourLabel) ON (n.someProperty);
|
||||
|
||||
// Add any other initialization queries here
|
||||
// For example:
|
||||
// CREATE CONSTRAINT IF NOT EXISTS FOR (n:Person) REQUIRE n.email IS UNIQUE;
|
||||
// CREATE INDEX IF NOT EXISTS FOR (n:Product) ON (n.sku);
|
||||
@@ -0,0 +1,377 @@
|
||||
#*****************************************************************
|
||||
# Neo4j configuration
|
||||
#
|
||||
# For more details and a complete list of settings, please see
|
||||
# https://neo4j.com/docs/operations-manual/current/reference/configuration-settings/
|
||||
#*****************************************************************
|
||||
|
||||
# The name of the default database
|
||||
#initial.dbms.default_database=neo4j
|
||||
|
||||
# Paths of directories in the installation.
|
||||
#server.directories.data=data
|
||||
#server.directories.plugins=plugins
|
||||
#server.directories.logs=logs
|
||||
#server.directories.lib=lib
|
||||
#server.directories.run=run
|
||||
#server.directories.licenses=licenses
|
||||
#server.directories.transaction.logs.root=data/transactions
|
||||
|
||||
# This setting constrains all `LOAD CSV` import files to be under the `import` directory. Remove or comment it out to
|
||||
# allow files to be loaded from anywhere in the filesystem; this introduces possible security problems. See the
|
||||
# `LOAD CSV` section of the manual for details.
|
||||
server.directories.import=import
|
||||
|
||||
# Whether requests to Neo4j are authenticated.
|
||||
# To disable authentication, uncomment this line
|
||||
dbms.security.auth_enabled=false
|
||||
|
||||
#********************************************************************
|
||||
# Memory Settings
|
||||
#********************************************************************
|
||||
#
|
||||
# Memory settings are specified kilobytes with the 'k' suffix, megabytes with
|
||||
# 'm' and gigabytes with 'g'.
|
||||
# If Neo4j is running on a dedicated server, then it is generally recommended
|
||||
# to leave about 2-4 gigabytes for the operating system, give the JVM enough
|
||||
# heap to hold all your transaction state and query context, and then leave the
|
||||
# rest for the page cache.
|
||||
|
||||
# Java Heap Size: by default the Java heap size is dynamically calculated based
|
||||
# on available system resources. Uncomment these lines to set specific initial
|
||||
# and maximum heap size.
|
||||
#server.memory.heap.initial_size=512m
|
||||
#server.memory.heap.max_size=512m
|
||||
|
||||
# The amount of memory to use for mapping the store files.
|
||||
# The default page cache memory assumes the machine is dedicated to running
|
||||
# Neo4j, and is heuristically set to 50% of RAM minus the Java heap size.
|
||||
#server.memory.pagecache.size=10g
|
||||
|
||||
# Limit the amount of memory that all of the running transaction can consume.
|
||||
# The default value is 70% of the heap size limit.
|
||||
#dbms.memory.transaction.total.max=256m
|
||||
|
||||
# Limit the amount of memory that a single transaction can consume.
|
||||
# By default there is no limit.
|
||||
#db.memory.transaction.max=16m
|
||||
|
||||
# Transaction state location. It is recommended to use ON_HEAP.
|
||||
# db.tx_state.memory_allocation=ON_HEAP
|
||||
|
||||
#*****************************************************************
|
||||
# Network connector configuration
|
||||
#*****************************************************************
|
||||
|
||||
# With default configuration Neo4j only accepts local connections.
|
||||
# To accept non-local connections, uncomment this line:
|
||||
#server.default_listen_address=0.0.0.0
|
||||
|
||||
# You can also choose a specific network interface, and configure a non-default
|
||||
# port for each connector, by setting their individual listen_address.
|
||||
|
||||
# The address at which this server can be reached by its clients. This may be the server's IP address or DNS name, or
|
||||
# it may be the address of a reverse proxy which sits in front of the server. This setting may be overridden for
|
||||
# individual connectors below.
|
||||
#server.default_advertised_address=localhost
|
||||
|
||||
# You can also choose a specific advertised hostname or IP address, and
|
||||
# configure an advertised port for each connector, by setting their
|
||||
# individual advertised_address.
|
||||
|
||||
# By default, encryption is turned off.
|
||||
# To turn on encryption, an ssl policy for the connector needs to be configured
|
||||
# Read more in SSL policy section in this file for how to define a SSL policy.
|
||||
|
||||
# Bolt connector
|
||||
server.bolt.enabled=true
|
||||
#server.bolt.tls_level=DISABLED
|
||||
#server.bolt.listen_address=:7687
|
||||
server.bolt.advertised_address=localhost:8081
|
||||
#maybe has to be changed in production
|
||||
|
||||
# HTTP Connector. There can be zero or one HTTP connectors.
|
||||
server.http.enabled=true
|
||||
#server.http.listen_address=:7474
|
||||
#server.http.advertised_address=:7474
|
||||
|
||||
# HTTPS Connector. There can be zero or one HTTPS connectors.
|
||||
server.https.enabled=false
|
||||
#server.https.listen_address=:7473
|
||||
#server.https.advertised_address=:7473
|
||||
|
||||
# Number of Neo4j worker threads.
|
||||
#server.threads.worker_count=
|
||||
|
||||
#*****************************************************************
|
||||
# SSL policy configuration
|
||||
#*****************************************************************
|
||||
|
||||
# Each policy is configured under a separate namespace, e.g.
|
||||
# dbms.ssl.policy.<scope>.*
|
||||
# <scope> can be any of 'bolt', 'https', 'cluster' or 'backup'
|
||||
#
|
||||
# The scope is the name of the component where the policy will be used
|
||||
# Each component where the use of an ssl policy is desired needs to declare at least one setting of the policy.
|
||||
# Allowable values are 'bolt', 'https', 'cluster' or 'backup'.
|
||||
|
||||
# E.g if bolt and https connectors should use the same policy, the following could be declared
|
||||
# dbms.ssl.policy.bolt.base_directory=certificates/default
|
||||
# dbms.ssl.policy.https.base_directory=certificates/default
|
||||
# However, it's strongly encouraged to not use the same key pair for multiple scopes.
|
||||
#
|
||||
# N.B: Note that a connector must be configured to support/require
|
||||
# SSL/TLS for the policy to actually be utilized.
|
||||
#
|
||||
# see: dbms.connector.*.tls_level
|
||||
|
||||
# SSL settings (dbms.ssl.policy.<scope>.*)
|
||||
# .base_directory Base directory for SSL policies paths. All relative paths within the
|
||||
# SSL configuration will be resolved from the base dir.
|
||||
#
|
||||
# .private_key A path to the key file relative to the '.base_directory'.
|
||||
#
|
||||
# .private_key_password The password for the private key.
|
||||
#
|
||||
# .public_certificate A path to the public certificate file relative to the '.base_directory'.
|
||||
#
|
||||
# .trusted_dir A path to a directory containing trusted certificates.
|
||||
#
|
||||
# .revoked_dir Path to the directory with Certificate Revocation Lists (CRLs).
|
||||
#
|
||||
# .verify_hostname If true, the server will verify the hostname that the client uses to connect with. In order
|
||||
# for this to work, the server public certificate must have a valid CN and/or matching
|
||||
# Subject Alternative Names.
|
||||
#
|
||||
# .client_auth How the client should be authorized. Possible values are: 'none', 'optional', 'require'.
|
||||
#
|
||||
# .tls_versions A comma-separated list of allowed TLS versions. By default only TLSv1.2 is allowed.
|
||||
#
|
||||
# .trust_all Setting this to 'true' will ignore the trust truststore, trusting all clients and servers.
|
||||
# Use of this mode is discouraged. It would offer encryption but no security.
|
||||
#
|
||||
# .ciphers A comma-separated list of allowed ciphers. The default ciphers are the defaults of
|
||||
# the JVM platform.
|
||||
|
||||
# Bolt SSL configuration
|
||||
#dbms.ssl.policy.bolt.enabled=true
|
||||
#dbms.ssl.policy.bolt.base_directory=certificates/bolt
|
||||
#dbms.ssl.policy.bolt.private_key=private.key
|
||||
#dbms.ssl.policy.bolt.public_certificate=public.crt
|
||||
#dbms.ssl.policy.bolt.client_auth=NONE
|
||||
|
||||
# Https SSL configuration
|
||||
#dbms.ssl.policy.https.enabled=true
|
||||
#dbms.ssl.policy.https.base_directory=certificates/https
|
||||
#dbms.ssl.policy.https.private_key=private.key
|
||||
#dbms.ssl.policy.https.public_certificate=public.crt
|
||||
#dbms.ssl.policy.https.client_auth=NONE
|
||||
|
||||
# Cluster SSL configuration
|
||||
#dbms.ssl.policy.cluster.enabled=true
|
||||
#dbms.ssl.policy.cluster.base_directory=certificates/cluster
|
||||
#dbms.ssl.policy.cluster.private_key=private.key
|
||||
#dbms.ssl.policy.cluster.public_certificate=public.crt
|
||||
|
||||
# Backup SSL configuration
|
||||
#dbms.ssl.policy.backup.enabled=true
|
||||
#dbms.ssl.policy.backup.base_directory=certificates/backup
|
||||
#dbms.ssl.policy.backup.private_key=private.key
|
||||
#dbms.ssl.policy.backup.public_certificate=public.crt
|
||||
|
||||
#*****************************************************************
|
||||
# Logging configuration
|
||||
#*****************************************************************
|
||||
|
||||
# To enable HTTP logging, uncomment this line
|
||||
#dbms.logs.http.enabled=true
|
||||
|
||||
# To enable GC Logging, uncomment this line
|
||||
#server.logs.gc.enabled=true
|
||||
|
||||
# GC Logging Options
|
||||
# see https://docs.oracle.com/en/java/javase/11/tools/java.html#GUID-BE93ABDC-999C-4CB5-A88B-1994AAAC74D5
|
||||
#server.logs.gc.options=-Xlog:gc*,safepoint,age*=trace
|
||||
|
||||
# Number of GC logs to keep.
|
||||
#server.logs.gc.rotation.keep_number=5
|
||||
|
||||
# Size of each GC log that is kept.
|
||||
#server.logs.gc.rotation.size=20m
|
||||
|
||||
#*****************************************************************
|
||||
# Miscellaneous configuration
|
||||
#*****************************************************************
|
||||
|
||||
# Determines if Cypher will allow using file URLs when loading data using
|
||||
# `LOAD CSV`. Setting this value to `false` will cause Neo4j to fail `LOAD CSV`
|
||||
# clauses that load data from the file system.
|
||||
#dbms.security.allow_csv_import_from_file_urls=true
|
||||
|
||||
|
||||
# Value of the Access-Control-Allow-Origin header sent over any HTTP or HTTPS
|
||||
# connector. This defaults to '*', which allows broadest compatibility. Note
|
||||
# that any URI provided here limits HTTP/HTTPS access to that URI only.
|
||||
#dbms.security.http_access_control_allow_origin=*
|
||||
|
||||
# Value of the HTTP Strict-Transport-Security (HSTS) response header. This header
|
||||
# tells browsers that a webpage should only be accessed using HTTPS instead of HTTP.
|
||||
# It is attached to every HTTPS response. Setting is not set by default so
|
||||
# 'Strict-Transport-Security' header is not sent. Value is expected to contain
|
||||
# directives like 'max-age', 'includeSubDomains' and 'preload'.
|
||||
#dbms.security.http_strict_transport_security=
|
||||
|
||||
# Retention policy for transaction logs needed to perform recovery and backups.
|
||||
#db.tx_log.rotation.retention_policy=2 days
|
||||
|
||||
# Whether or not any database on this instance are read_only by default.
|
||||
# If false, individual databases may be marked as read_only using dbms.database.read_only.
|
||||
# If true, individual databases may be marked as writable using dbms.databases.writable.
|
||||
#dbms.databases.default_to_read_only=false
|
||||
|
||||
# Comma separated list of JAX-RS packages containing JAX-RS resources, one
|
||||
# package name for each mountpoint. The listed package names will be loaded
|
||||
# under the mountpoints specified. Uncomment this line to mount the
|
||||
# org.neo4j.examples.server.unmanaged.HelloWorldResource.java from
|
||||
# neo4j-server-examples under /examples/unmanaged, resulting in a final URL of
|
||||
# http://localhost:7474/examples/unmanaged/helloworld/{nodeId}
|
||||
#server.unmanaged_extension_classes=org.neo4j.examples.server.unmanaged=/examples/unmanaged
|
||||
|
||||
# A comma separated list of procedures and user defined functions that are allowed
|
||||
# full access to the database through unsupported/insecure internal APIs.
|
||||
#dbms.security.procedures.unrestricted=my.extensions.example,my.procedures.*
|
||||
|
||||
# A comma separated list of procedures to be loaded by default.
|
||||
# Leaving this unconfigured will load all procedures found.
|
||||
#dbms.security.procedures.allowlist=apoc.coll.*,apoc.load.*,gds.*
|
||||
|
||||
#********************************************************************
|
||||
# JVM Parameters
|
||||
#********************************************************************
|
||||
|
||||
# G1GC generally strikes a good balance between throughput and tail
|
||||
# latency, without too much tuning.
|
||||
server.jvm.additional=-XX:+UseG1GC
|
||||
|
||||
# Have common exceptions keep producing stack traces, so they can be
|
||||
# debugged regardless of how often logs are rotated.
|
||||
server.jvm.additional=-XX:-OmitStackTraceInFastThrow
|
||||
|
||||
# Make sure that `initmemory` is not only allocated, but committed to
|
||||
# the process, before starting the database. This reduces memory
|
||||
# fragmentation, increasing the effectiveness of transparent huge
|
||||
# pages. It also reduces the possibility of seeing performance drop
|
||||
# due to heap-growing GC events, where a decrease in available page
|
||||
# cache leads to an increase in mean IO response time.
|
||||
# Try reducing the heap memory, if this flag degrades performance.
|
||||
server.jvm.additional=-XX:+AlwaysPreTouch
|
||||
|
||||
# Trust that non-static final fields are really final.
|
||||
# This allows more optimizations and improves overall performance.
|
||||
# NOTE: Disable this if you use embedded mode, or have extensions or dependencies that may use reflection or
|
||||
# serialization to change the value of final fields!
|
||||
server.jvm.additional=-XX:+UnlockExperimentalVMOptions
|
||||
server.jvm.additional=-XX:+TrustFinalNonStaticFields
|
||||
|
||||
# Disable explicit garbage collection, which is occasionally invoked by the JDK itself.
|
||||
server.jvm.additional=-XX:+DisableExplicitGC
|
||||
|
||||
# Restrict size of cached JDK buffers to 1 KB
|
||||
server.jvm.additional=-Djdk.nio.maxCachedBufferSize=1024
|
||||
|
||||
# More efficient buffer allocation in Netty by allowing direct no cleaner buffers.
|
||||
server.jvm.additional=-Dio.netty.tryReflectionSetAccessible=true
|
||||
|
||||
# Exits JVM on the first occurrence of an out-of-memory error. Its preferable to restart VM in case of out of memory errors.
|
||||
# server.jvm.additional=-XX:+ExitOnOutOfMemoryError
|
||||
|
||||
# Expand Diffie Hellman (DH) key size from default 1024 to 2048 for DH-RSA cipher suites used in server TLS handshakes.
|
||||
# This is to protect the server from any potential passive eavesdropping.
|
||||
server.jvm.additional=-Djdk.tls.ephemeralDHKeySize=2048
|
||||
|
||||
# This mitigates a DDoS vector.
|
||||
server.jvm.additional=-Djdk.tls.rejectClientInitiatedRenegotiation=true
|
||||
|
||||
# Enable remote debugging
|
||||
#server.jvm.additional=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
|
||||
|
||||
# This filter prevents deserialization of arbitrary objects via java object serialization, addressing potential vulnerabilities.
|
||||
# By default this filter whitelists all neo4j classes, as well as classes from the hazelcast library and the java standard library.
|
||||
# These defaults should only be modified by expert users!
|
||||
# For more details (including filter syntax) see: https://openjdk.java.net/jeps/290
|
||||
#server.jvm.additional=-Djdk.serialFilter=java.**;org.neo4j.**;com.neo4j.**;com.hazelcast.**;net.sf.ehcache.Element;com.sun.proxy.*;org.openjdk.jmh.**;!*
|
||||
|
||||
# Increase the default flight recorder stack sampling depth from 64 to 256, to avoid truncating frames when profiling.
|
||||
server.jvm.additional=-XX:FlightRecorderOptions=stackdepth=256
|
||||
|
||||
# Allow profilers to sample between safepoints. Without this, sampling profilers may produce less accurate results.
|
||||
server.jvm.additional=-XX:+UnlockDiagnosticVMOptions
|
||||
server.jvm.additional=-XX:+DebugNonSafepoints
|
||||
|
||||
# Open modules for neo4j to allow internal access
|
||||
server.jvm.additional=--add-opens=java.base/java.nio=ALL-UNNAMED
|
||||
server.jvm.additional=--add-opens=java.base/java.io=ALL-UNNAMED
|
||||
server.jvm.additional=--add-opens=java.base/sun.nio.ch=ALL-UNNAMED
|
||||
|
||||
# Disable logging JMX endpoint.
|
||||
server.jvm.additional=-Dlog4j2.disable.jmx=true
|
||||
|
||||
# Limit JVM metaspace and code cache to allow garbage collection. Used by cypher for code generation and may grow indefinitely unless constrained.
|
||||
# Useful for memory constrained environments
|
||||
#server.jvm.additional=-XX:MaxMetaspaceSize=1024m
|
||||
#server.jvm.additional=-XX:ReservedCodeCacheSize=512m
|
||||
|
||||
# Allow big methods to be JIT compiled.
|
||||
# Useful for big queries and big expressions where cypher code generation can create large methods.
|
||||
#server.jvm.additional=-XX:-DontCompileHugeMethods
|
||||
|
||||
#********************************************************************
|
||||
# Wrapper Windows NT/2000/XP Service Properties
|
||||
#********************************************************************
|
||||
# WARNING - Do not modify any of these properties when an application
|
||||
# using this configuration file has been installed as a service.
|
||||
# Please uninstall the service before modifying this section. The
|
||||
# service can then be reinstalled.
|
||||
|
||||
# Name of the service
|
||||
server.windows_service_name=neo4j
|
||||
|
||||
#********************************************************************
|
||||
# Other Neo4j system properties
|
||||
#********************************************************************
|
||||
dbms.security.procedures.unrestricted=apoc.*
|
||||
|
||||
db.tx_log.rotation.retention_policy=100M size
|
||||
|
||||
server.memory.pagecache.size=512M
|
||||
|
||||
server.default_listen_address=0.0.0.0
|
||||
|
||||
|
||||
#### SEMSPECT ####
|
||||
server.unmanaged_extension_classes=de.derivo.semspect.server.neo4japp.server=/semspect
|
||||
#dbms.security.http_auth_allowlist=/,/semspect.*
|
||||
dbms.security.http_auth_allowlist=/,/browser.*,/semspect.*
|
||||
server.directories.logs=/logs
|
||||
server.config.strict_validation.enabled=false
|
||||
|
||||
|
||||
dbms.security.auth_enabled=false
|
||||
|
||||
## new advertise
|
||||
server.default_listen_address=0.0.0.0
|
||||
|
||||
server.bolt.enabled=true
|
||||
server.bolt.listen_address=:7687
|
||||
server.bolt.advertised_address=srvdizsrvmedaxdev.med.uni-greifswald.de:8081
|
||||
server.bolt.tls_level=REQUIRED
|
||||
|
||||
dbms.ssl.policy.bolt.enabled=true
|
||||
dbms.ssl.policy.bolt.base_directory=certificates/bolt
|
||||
dbms.ssl.policy.bolt.private_key=private.key
|
||||
dbms.ssl.policy.bolt.public_certificate=public.crt
|
||||
dbms.ssl.policy.bolt.client_auth=NONE
|
||||
|
||||
server.http.enabled=true
|
||||
server.http.listen_address=:7474
|
||||
server.http.advertised_address=srvdizsrvmedaxdev.med.uni-greifswald.de:443
|
||||
Submodule
+1
Submodule mdm2neo4j added at 3f91b1847d
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 366 KiB |
@@ -0,0 +1,210 @@
|
||||
{
|
||||
"resourceType": "Bundle",
|
||||
"type": "transaction",
|
||||
"entry": [ {
|
||||
"fullUrl": "urn:uuid:a7a285c0-4714-dd3c-4837-8719c9b67873",
|
||||
"resource": {
|
||||
"resourceType": "Patient",
|
||||
"id": "a7a285c0-4714-dd3c-4837-8719c9b67873",
|
||||
"meta": {
|
||||
"profile": [ "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient" ]
|
||||
},
|
||||
"text": {
|
||||
"status": "generated",
|
||||
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\">Generated by <a href=\"https://github.com/synthetichealth/synthea\">Synthea</a>.Version identifier: 3c23908\n . Person seed: -5557164924473669144 Population seed: 1693908535569</div>"
|
||||
},
|
||||
"extension": [ {
|
||||
"url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race",
|
||||
"extension": [ {
|
||||
"url": "ombCategory",
|
||||
"valueCoding": {
|
||||
"system": "urn:oid:2.16.840.1.113883.6.238",
|
||||
"code": "2106-3",
|
||||
"display": "White"
|
||||
}
|
||||
}, {
|
||||
"url": "text",
|
||||
"valueString": "White"
|
||||
} ]
|
||||
}, {
|
||||
"url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity",
|
||||
"extension": [ {
|
||||
"url": "ombCategory",
|
||||
"valueCoding": {
|
||||
"system": "urn:oid:2.16.840.1.113883.6.238",
|
||||
"code": "2186-5",
|
||||
"display": "Not Hispanic or Latino"
|
||||
}
|
||||
}, {
|
||||
"url": "text",
|
||||
"valueString": "Not Hispanic or Latino"
|
||||
} ]
|
||||
}, {
|
||||
"url": "http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName",
|
||||
"valueString": "Leana211 Sauer652"
|
||||
}, {
|
||||
"url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex",
|
||||
"valueCode": "M"
|
||||
}, {
|
||||
"url": "http://hl7.org/fhir/StructureDefinition/patient-birthPlace",
|
||||
"valueAddress": {
|
||||
"city": "Quincy",
|
||||
"state": "Massachusetts",
|
||||
"country": "US"
|
||||
}
|
||||
}, {
|
||||
"url": "http://synthetichealth.github.io/synthea/disability-adjusted-life-years",
|
||||
"valueDecimal": 0.0
|
||||
}, {
|
||||
"url": "http://synthetichealth.github.io/synthea/quality-adjusted-life-years",
|
||||
"valueDecimal": 1.0
|
||||
} ],
|
||||
"identifier": [
|
||||
{
|
||||
"system": "https://github.com/synthetichealth/synthea",
|
||||
"value": "a7a285c0-4714-dd3c-4837-8719c9b67873"
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"coding": [ {
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
|
||||
"code": "MR",
|
||||
"display": "Medical Record Number"
|
||||
} ],
|
||||
"text": "Medical Record Number"
|
||||
},
|
||||
"system": "http://hospital.smarthealthit.org",
|
||||
"value": "a7a285c0-4714-dd3c-4837-8719c9b67873"
|
||||
}, {
|
||||
"type": {
|
||||
"coding": [ {
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
|
||||
"code": "SS",
|
||||
"display": "Social Security Number"
|
||||
} ],
|
||||
"text": "Social Security Number"
|
||||
},
|
||||
"system": "http://hl7.org/fhir/sid/us-ssn",
|
||||
"value": "999-89-9528"
|
||||
} ],
|
||||
"name": [ {
|
||||
"use": "official",
|
||||
"family": "Schoen8",
|
||||
"given": [ "Johnny786", "Vince741" ]
|
||||
} ],
|
||||
"telecom": [ {
|
||||
"system": "phone",
|
||||
"value": "555-753-6560",
|
||||
"use": "home"
|
||||
} ],
|
||||
"gender": "male",
|
||||
"birthDate": "2021-05-22",
|
||||
"address": [ {
|
||||
"extension": [ {
|
||||
"url": "http://hl7.org/fhir/StructureDefinition/geolocation",
|
||||
"extension": [ {
|
||||
"url": "latitude",
|
||||
"valueDecimal": 42.05921178859317
|
||||
}, {
|
||||
"url": "longitude",
|
||||
"valueDecimal": -70.79219595855132
|
||||
} ]
|
||||
} ],
|
||||
"line": [ "463 Rempel Ranch Unit 81" ],
|
||||
"city": "Pembroke",
|
||||
"state": "MA",
|
||||
"postalCode": "00000",
|
||||
"country": "US"
|
||||
} ],
|
||||
"maritalStatus": {
|
||||
"coding": [ {
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v3-MaritalStatus",
|
||||
"code": "S",
|
||||
"display": "Never Married"
|
||||
} ],
|
||||
"text": "Never Married"
|
||||
},
|
||||
"multipleBirthBoolean": false,
|
||||
"communication": [ {
|
||||
"language": {
|
||||
"coding": [ {
|
||||
"system": "urn:ietf:bcp:47",
|
||||
"code": "en-US",
|
||||
"display": "English (United States)"
|
||||
} ],
|
||||
"text": "English (United States)"
|
||||
}
|
||||
} ]
|
||||
},
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "Patient"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "urn:uuid:0eb53bda-2881-5e8e-3597-87a9430af96a",
|
||||
"resource": {
|
||||
"resourceType": "Encounter",
|
||||
"id": "0eb53bda-2881-5e8e-3597-87a9430af96a",
|
||||
"meta": {
|
||||
"profile": [ "http://hl7.org/fhir/us/core/StructureDefinition/us-core-encounter" ]
|
||||
},
|
||||
"identifier": [ {
|
||||
"use": "official",
|
||||
"system": "https://github.com/synthetichealth/synthea",
|
||||
"value": "0eb53bda-2881-5e8e-3597-87a9430af96a"
|
||||
} ],
|
||||
"status": "finished",
|
||||
"class": {
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
|
||||
"code": "AMB"
|
||||
},
|
||||
"type": [ {
|
||||
"coding": [ {
|
||||
"system": "http://snomed.info/sct",
|
||||
"code": "410620009",
|
||||
"display": "Well child visit (procedure)"
|
||||
} ],
|
||||
"text": "Well child visit (procedure)"
|
||||
} ],
|
||||
"subject": {
|
||||
"reference": "urn:uuid:a7a285c0-4714-dd3c-4837-8719c9b67873",
|
||||
"display": "Johnny786 Vince741 Schoen8"
|
||||
},
|
||||
"participant": [ {
|
||||
"type": [ {
|
||||
"coding": [ {
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v3-ParticipationType",
|
||||
"code": "PPRF",
|
||||
"display": "primary performer"
|
||||
} ],
|
||||
"text": "primary performer"
|
||||
} ],
|
||||
"period": {
|
||||
"start": "2021-05-22T00:13:45+02:00",
|
||||
"end": "2021-05-22T00:28:45+02:00"
|
||||
},
|
||||
"individual": {
|
||||
"reference": "Practitioner?identifier=http://hl7.org/fhir/sid/us-npi|9999942599",
|
||||
"display": "Dr. Regenia619 Bosco882"
|
||||
}
|
||||
} ],
|
||||
"period": {
|
||||
"start": "2021-05-22T00:13:45+02:00",
|
||||
"end": "2021-05-22T00:28:45+02:00"
|
||||
},
|
||||
"location": [ {
|
||||
"location": {
|
||||
"reference": "Location?identifier=https://github.com/synthetichealth/synthea|6e3d04a3-9064-33e4-b8b5-63bb468d7629",
|
||||
"display": "UNITED MEDICAL CARE LLC"
|
||||
}
|
||||
} ],
|
||||
"serviceProvider": {
|
||||
"reference": "Organization?identifier=https://github.com/synthetichealth/synthea|4e56c7ec-99e5-3023-8e4f-95ad18a03f06",
|
||||
"display": "UNITED MEDICAL CARE LLC"
|
||||
}
|
||||
},
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "Encounter"
|
||||
}
|
||||
}]}
|
||||
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"resourceType": "Bundle",
|
||||
"type": "batch",
|
||||
"entry": [ {
|
||||
"fullUrl": "urn:uuid:4e56c7ec-99e5-3023-8e4f-95ad18a03f06",
|
||||
"resource": {
|
||||
"resourceType": "Organization",
|
||||
"id": "4e56c7ec-99e5-3023-8e4f-95ad18a03f06",
|
||||
"meta": {
|
||||
"profile": [ "http://hl7.org/fhir/us/core/StructureDefinition/us-core-organization" ]
|
||||
},
|
||||
"extension": [ {
|
||||
"url": "http://synthetichealth.github.io/synthea/utilization-encounters-extension",
|
||||
"valueInteger": 9
|
||||
}, {
|
||||
"url": "http://synthetichealth.github.io/synthea/utilization-procedures-extension",
|
||||
"valueInteger": 2
|
||||
}, {
|
||||
"url": "http://synthetichealth.github.io/synthea/utilization-labs-extension",
|
||||
"valueInteger": 1
|
||||
}, {
|
||||
"url": "http://synthetichealth.github.io/synthea/utilization-prescriptions-extension",
|
||||
"valueInteger": 3
|
||||
} ],
|
||||
"identifier": [ {
|
||||
"system": "https://github.com/synthetichealth/synthea",
|
||||
"value": "4e56c7ec-99e5-3023-8e4f-95ad18a03f06"
|
||||
} ],
|
||||
"active": true,
|
||||
"type": [ {
|
||||
"coding": [ {
|
||||
"system": "http://terminology.hl7.org/CodeSystem/organization-type",
|
||||
"code": "prov",
|
||||
"display": "Healthcare Provider"
|
||||
} ],
|
||||
"text": "Healthcare Provider"
|
||||
} ],
|
||||
"name": "UNITED MEDICAL CARE LLC",
|
||||
"telecom": [ {
|
||||
"system": "phone",
|
||||
"value": "5089715500"
|
||||
} ],
|
||||
"address": [ {
|
||||
"line": [ "28 RIVERSIDE DR STE 101" ],
|
||||
"city": "PEMBROKE",
|
||||
"state": "MA",
|
||||
"postalCode": "023594947",
|
||||
"country": "US"
|
||||
} ]
|
||||
},
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "Organization",
|
||||
"ifNoneExist": "identifier=https://github.com/synthetichealth/synthea|4e56c7ec-99e5-3023-8e4f-95ad18a03f06"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "urn:uuid:6e3d04a3-9064-33e4-b8b5-63bb468d7629",
|
||||
"resource": {
|
||||
"resourceType": "Location",
|
||||
"id": "6e3d04a3-9064-33e4-b8b5-63bb468d7629",
|
||||
"meta": {
|
||||
"profile": [ "http://hl7.org/fhir/us/core/StructureDefinition/us-core-location" ]
|
||||
},
|
||||
"identifier": [ {
|
||||
"system": "https://github.com/synthetichealth/synthea",
|
||||
"value": "6e3d04a3-9064-33e4-b8b5-63bb468d7629"
|
||||
} ],
|
||||
"status": "active",
|
||||
"name": "UNITED MEDICAL CARE LLC",
|
||||
"telecom": [ {
|
||||
"system": "phone",
|
||||
"value": "5089715500"
|
||||
} ],
|
||||
"address": {
|
||||
"line": [ "28 RIVERSIDE DR STE 101" ],
|
||||
"city": "PEMBROKE",
|
||||
"state": "MA",
|
||||
"postalCode": "023594947",
|
||||
"country": "US"
|
||||
},
|
||||
"position": {
|
||||
"longitude": -70.77534154695786,
|
||||
"latitude": 42.11004715
|
||||
},
|
||||
"managingOrganization": {
|
||||
"identifier": {
|
||||
"system": "https://github.com/synthetichealth/synthea",
|
||||
"value": "4e56c7ec-99e5-3023-8e4f-95ad18a03f06"
|
||||
},
|
||||
"display": "UNITED MEDICAL CARE LLC"
|
||||
}
|
||||
},
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "Location",
|
||||
"ifNoneExist": "identifier=https://github.com/synthetichealth/synthea|6e3d04a3-9064-33e4-b8b5-63bb468d7629"
|
||||
}
|
||||
}]}
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"resourceType": "Bundle",
|
||||
"type": "batch",
|
||||
"entry": [ {
|
||||
"fullUrl": "urn:uuid:0368f101-0e65-3251-a809-566ebd6b2c2a",
|
||||
"resource": {
|
||||
"resourceType": "Practitioner",
|
||||
"id": "0368f101-0e65-3251-a809-566ebd6b2c2a",
|
||||
"meta": {
|
||||
"profile": [ "http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner" ]
|
||||
},
|
||||
"extension": [ {
|
||||
"url": "http://synthetichealth.github.io/synthea/utilization-encounters-extension",
|
||||
"valueInteger": 9
|
||||
} ],
|
||||
"identifier": [ {
|
||||
"system": "http://hl7.org/fhir/sid/us-npi",
|
||||
"value": "9999942599"
|
||||
} ],
|
||||
"active": true,
|
||||
"name": [ {
|
||||
"family": "Bosco882",
|
||||
"given": [ "Regenia619" ],
|
||||
"prefix": [ "Dr." ]
|
||||
} ],
|
||||
"telecom": [ {
|
||||
"extension": [ {
|
||||
"url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-direct",
|
||||
"valueBoolean": true
|
||||
} ],
|
||||
"system": "email",
|
||||
"value": "Regenia619.Bosco882@example.com",
|
||||
"use": "work"
|
||||
} ],
|
||||
"address": [ {
|
||||
"line": [ "28 RIVERSIDE DR STE 101" ],
|
||||
"city": "PEMBROKE",
|
||||
"state": "MA",
|
||||
"postalCode": "023594947",
|
||||
"country": "US"
|
||||
} ],
|
||||
"gender": "female"
|
||||
},
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "Practitioner",
|
||||
"ifNoneExist": "identifier=http://hl7.org/fhir/sid/us-npi|9999942599"
|
||||
}
|
||||
}]}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
from biocypher import BioCypher
|
||||
from schema_config_generation import write_automated_schema
|
||||
|
||||
|
||||
# Add submodule to path BEFORE any other imports
|
||||
sys.path.insert(0, '/app/mdm2neo4j/src') # Absolute path in Docker
|
||||
# OR
|
||||
#sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'mdm2neo4j')) # Relative path
|
||||
|
||||
# Now your imports should work
|
||||
import mdm2neo4j.src.xml_processor.xml_processor as xp
|
||||
|
||||
from import_fhir_to_nx_diGraph import generate_neo4j_import_script
|
||||
|
||||
|
||||
from import_fhir_to_nx_diGraph import load_multiple_fhir_patients
|
||||
#import mdm2neo4j.src.xml_processor.xml_processor as xp
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
# create Biocypher driver
|
||||
bc = BioCypher(
|
||||
biocypher_config_path="config/biocypher_config.yaml",
|
||||
)
|
||||
|
||||
#bc.show_ontology_structure() #very extensive
|
||||
#BioCypher preperation
|
||||
|
||||
|
||||
## create networkX and run improvement scripts
|
||||
|
||||
adapter_mode = 0
|
||||
xml_file_path = "./odm_models"
|
||||
|
||||
write_automated_schema(None, 'config/automated_schema.yaml', ['config/manual_schema_config.yaml', 'config/ODM_schema.yaml'])
|
||||
|
||||
if(adapter_mode == 0 or adapter_mode == -1):
|
||||
n_patients = int(os.getenv('NUMBER_OF_PATIENTS'))
|
||||
print("--- load ", n_patients, " fhir patients ---")
|
||||
load_multiple_fhir_patients(n_patients)
|
||||
|
||||
if(adapter_mode == 1 or adapter_mode == -1):
|
||||
|
||||
#bc.show_ontology_structure() #very extensive
|
||||
|
||||
bc.write_nodes(xp.parse_xml_generate_nodes(xml_file_path))
|
||||
bc.write_edges(xp.parse_xml_generate_edges(xml_file_path))
|
||||
|
||||
|
||||
|
||||
print("CREATING THE SCRIPT", flush=True)
|
||||
generate_neo4j_import_script()
|
||||
with open('/neo4j_import/shell-scipt-complete', 'w') as f:
|
||||
f.write('Import completed successfully')
|
||||
|
||||
print("FHIR import completed successfully")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,94 @@
|
||||
@startuml Server Architecture
|
||||
left to right direction
|
||||
|
||||
!define RECTANGLE class
|
||||
|
||||
' Define styles
|
||||
skinparam rectangle {
|
||||
BackgroundColor<<zone>> LightBlue
|
||||
BorderColor<<zone>> Navy
|
||||
BackgroundColor<<server>> LightGreen
|
||||
BorderColor<<server>> DarkGreen
|
||||
BackgroundColor<<admin>> LightYellow
|
||||
BorderColor<<admin>> Orange
|
||||
BackgroundColor<<client>> LightPink
|
||||
BorderColor<<client>> Red
|
||||
BackgroundColor<<database>> LightCyan
|
||||
BorderColor<<database>> DarkCyan
|
||||
BackgroundColor<<component>> Wheat
|
||||
BorderColor<<component>> Brown
|
||||
}
|
||||
|
||||
' Zone 2 container
|
||||
rectangle "Zone 2" <<zone>> {
|
||||
' Admin DIZ (provider)
|
||||
actor "Admin DIZ\n(Provider)" <<admin>> as AdminD
|
||||
|
||||
' BLAZE Server
|
||||
rectangle "BLAZE Server" <<server>> as BlazeServer {
|
||||
note "FHIR data source for\nthe pipeline" as n1
|
||||
}
|
||||
|
||||
' Server M with internal components
|
||||
rectangle "Server MeDaX" <<server>> as ServerM {
|
||||
|
||||
' Web Interface
|
||||
rectangle "Web Interface" <<component>> as WebInterface
|
||||
' Pipeline components
|
||||
rectangle "Pipeline" <<component>> as Pipeline {
|
||||
rectangle "Python Scripts" <<component>> as PythonScripts
|
||||
collections "Input Files" <<files>> as InputFiles
|
||||
|
||||
note top of PythonScripts : 1. Python scripts fetch data\n2. Create input files\n3. Load into GDB
|
||||
|
||||
|
||||
' Graph Database
|
||||
database "Graph Database\n(GDB)" <<database>> as GraphDB
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
rectangle WTSDIZ <<client>> as DIZclient
|
||||
|
||||
|
||||
}
|
||||
|
||||
' Admin MeDaX
|
||||
rectangle "MeDaX Admins" <<admin>> as AdminGroup {
|
||||
actor "Admin 1" as Admin1
|
||||
actor "Admin 2" as Admin2
|
||||
}
|
||||
|
||||
|
||||
' Clients
|
||||
rectangle "Clients" <<client>> as Clients {
|
||||
rectangle "Clients 1 - MeDaX" as Client1
|
||||
rectangle "Clients 2 - Allgemeinmedizin" as Client2
|
||||
}
|
||||
|
||||
' Hidden alignment helpers
|
||||
BlazeServer -[hidden]right- ServerM
|
||||
BlazeServer -[hidden]down- AdminD
|
||||
|
||||
' Pipeline flow within Server M
|
||||
PythonScripts -down-> InputFiles : creates
|
||||
InputFiles -down-> GraphDB : loads into
|
||||
|
||||
' BLAZE Server connection
|
||||
PythonScripts -up-> BlazeServer : requests data
|
||||
|
||||
|
||||
' Web Interface connection
|
||||
WebInterface -up-> GraphDB : connected to
|
||||
|
||||
|
||||
' External relationships
|
||||
AdminD -up-> ServerM : manages, secures & installs
|
||||
AdminGroup -up-> DIZclient : access via VPN
|
||||
DIZclient -left-> ServerM : ssh connection
|
||||
Clients -up-> DIZclient : access via VPN
|
||||
DIZclient -left-> WebInterface : access
|
||||
|
||||
|
||||
@enduml
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
|
||||
[project]
|
||||
name = "medax-pipeline"
|
||||
version = "1.0.1"
|
||||
description = "An ETL pipeline to transfer FHIR data into a graph database"
|
||||
authors = [
|
||||
"Ilya Mazien",
|
||||
"Tom Gebhardt",
|
||||
"Lea Michaelis",
|
||||
"Ron Henkel",
|
||||
"Benjamin Winter",
|
||||
"Dagmar Waltemath",
|
||||
"Judith Wodke"
|
||||
]
|
||||
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Intended Audience :: Developers",
|
||||
"Intended Audience :: Science/Research",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python",
|
||||
"Natural Language :: English",
|
||||
"Topic :: Scientific/Engineering :: Medical-Informatics",
|
||||
]
|
||||
dependencies = [
|
||||
"pyyaml>=5.0",
|
||||
"biocypher>=0.16.0,<1.0.0",
|
||||
"more-itertools",
|
||||
"appdirs",
|
||||
"treelib==1.6.4",
|
||||
"rdflib>=6.2.0,<7.0.0",
|
||||
"networkx>=3.0,<4.0",
|
||||
"stringcase>=1.2.0,<2.0.0",
|
||||
"neo4j-utils==0.0.7",
|
||||
"pandas>=2.0.1,<3.0.0",
|
||||
"pooch>=1.7.0,<2.0.0",
|
||||
"tqdm>=4.65.0,<5.0.0",
|
||||
"requests>=2.31.0,<3.0.0",
|
||||
"python-dotenv>=1.0.0,<2.0.0",
|
||||
"beautifulsoup4>=4.12.3,<5.0.0",
|
||||
"lxml>=5.1.0,<6.0.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://www.medizin.uni-greifswald.de/medizininformatik/research/current-projects/medax/"
|
||||
Repository = "https://git.uni-greifswald.de/MILA_public/medax_pipeline"
|
||||
|
||||
# biocypher is pulled from a feature branch via uv's source override
|
||||
[tool.uv.sources]
|
||||
#biocypher = { git = "https://github.com/biocypher/biocypher.git", branch = "feat/dynamic-node-property-headers" } #enable this to test the feature branch again
|
||||
|
||||
|
||||
[tool.uv]
|
||||
package = false
|
||||
|
||||
# Dev dependencies (PEP 735 dependency groups, natively understood by uv)
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"sphinx>=5.0.0",
|
||||
"sphinx-design>=0.3.0,<0.4.0",
|
||||
"sphinx-rtd-theme>=1.0.0",
|
||||
"sphinx-last-updated-by-git>=0.3",
|
||||
"sphinx-autodoc-typehints>=1.18.0",
|
||||
"myst-parser>=0.18.0,<0.19.0",
|
||||
"yapf>=0.32.0,<0.33.0",
|
||||
"pytest>=6.0",
|
||||
"tox>=3.20.1",
|
||||
"pre-commit>=2.17.0",
|
||||
"bump2version",
|
||||
"coverage>=6.0",
|
||||
"pytest-cov>=3.0.0,<4.0.0",
|
||||
"hypothesis>=6.50.1,<7.0.0",
|
||||
"isort>=5.10.1,<6.0.0",
|
||||
"ipython>=8.7.0,<9.0.0",
|
||||
"ipykernel>=6.23.1,<7.0.0",
|
||||
"sphinxext-opengraph>=0.8.2,<0.9.0",
|
||||
"coverage-badge>=1.1.0,<2.0.0",
|
||||
"nbsphinx>=0.9.2,<0.10.0",
|
||||
"black>=23.9.1,<24.0.0",
|
||||
"flake8>=6.1.0,<7.0.0",
|
||||
]
|
||||
|
||||
# --- Tool configs below are build-tool-agnostic and carried over unchanged ---
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
addopts = "--ignore=mdm2neo4j"
|
||||
log_cli = true
|
||||
log_level = "INFO"
|
||||
markers = [
|
||||
"requires_neo4j: Requires connection to a Neo4j server",
|
||||
"requires_postgresql: Requires connection to a PostgreSQL server",
|
||||
"inject_driver_args(driver_args): Arguments for the Driver",
|
||||
]
|
||||
|
||||
[tool.black]
|
||||
line-length = 80
|
||||
target-version = ['py310']
|
||||
include = '\.pyi?$'
|
||||
exclude = '''
|
||||
(
|
||||
/(
|
||||
\.eggs
|
||||
| \.git
|
||||
| \.hg
|
||||
| \.mypy_cache
|
||||
| \.tox
|
||||
| \.venv
|
||||
| _build
|
||||
| buck-out
|
||||
| build
|
||||
| dist
|
||||
)/
|
||||
)
|
||||
'''
|
||||
|
||||
[tool.isort]
|
||||
from_first = true
|
||||
line_length = 80
|
||||
multi_line_output = 3
|
||||
include_trailing_comma = true
|
||||
use_parentheses = true
|
||||
known_num = "numpy,pandas"
|
||||
sections = "FUTURE,STDLIB,THIRDPARTY,NUM,FIRSTPARTY,LOCALFOLDER"
|
||||
no_lines_before = "LOCALFOLDER"
|
||||
balanced_wrapping = true
|
||||
force_grid_wrap = 0
|
||||
length_sort = "1"
|
||||
indent = " "
|
||||
profile = "black"
|
||||
|
||||
[tool.flake8]
|
||||
ignore = ["E203", "D200", "D202", "D401", "D105", "W504"]
|
||||
per-file-ignores = [
|
||||
"docs/source/conf.py:D100",
|
||||
"tests/*:D100,D101,D102",
|
||||
"*/__init__.py:F401",
|
||||
]
|
||||
max-line-length = 80
|
||||
count = true
|
||||
@@ -0,0 +1,105 @@
|
||||
"""
|
||||
Reproduction: vanilla BioCypher corrupts already-written part files when a later
|
||||
batch of the same node type introduces a new property.
|
||||
|
||||
Run:
|
||||
pip install biocypher
|
||||
python reproduce_header_misalignment.py
|
||||
|
||||
Tested against BioCypher 0.15.1. Self-contained: writes its own config files
|
||||
into a temp dir, needs no Neo4j (offline CSV writer only).
|
||||
|
||||
Background
|
||||
----------
|
||||
MeDaX ingests data in successive batches and extends the schema on the fly:
|
||||
when a node/edge type or attribute first appears, it is added to the schema so
|
||||
no information is dropped. BioCypher's Neo4j batch writer rewrites the *header*
|
||||
file on every write_nodes() call, but leaves the *part files already on disk*
|
||||
untouched. As soon as a new attribute widens/reorders the header, the rows in
|
||||
earlier part files no longer line up with it -> silent column misalignment on
|
||||
neo4j-admin import.
|
||||
|
||||
Exact location upstream:
|
||||
biocypher/output/write/graph/_neo4j.py
|
||||
_Neo4jBatchWriter._write_node_headers() (and ._write_edge_headers())
|
||||
-> the `if os.path.exists(header_path): logger.warning("... Overwriting.")`
|
||||
branch rewrites the header but never reconciles existing -part*.csv files.
|
||||
"""
|
||||
|
||||
import glob
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from biocypher import BioCypher
|
||||
|
||||
workdir = tempfile.mkdtemp(prefix="bc_repro_")
|
||||
os.chdir(workdir)
|
||||
|
||||
with open("schema_config.yaml", "w") as fh:
|
||||
fh.write(
|
||||
"patient:\n"
|
||||
" represented_as: node\n"
|
||||
" input_label: patient\n"
|
||||
" is_a: entity\n" # no fixed `properties:` -> inferred from data per batch
|
||||
)
|
||||
|
||||
with open("biocypher_config.yaml", "w") as fh:
|
||||
fh.write(
|
||||
"biocypher:\n"
|
||||
" dbms: neo4j\n"
|
||||
" offline: true\n"
|
||||
" strict_mode: false\n"
|
||||
" schema_config_path: schema_config.yaml\n"
|
||||
" head_ontology:\n"
|
||||
" url: https://raw.githubusercontent.com/biolink/biolink-model/v3.2.1/biolink-model.owl.ttl\n"
|
||||
" root_node: entity\n"
|
||||
)
|
||||
|
||||
bc = BioCypher(
|
||||
biocypher_config_path="biocypher_config.yaml",
|
||||
schema_config_path="schema_config.yaml",
|
||||
output_directory="out",
|
||||
)
|
||||
|
||||
# Batch 1: patients described by {name, age}
|
||||
bc.write_nodes(
|
||||
[
|
||||
("p1", "patient", {"name": "Alice", "age": 30}),
|
||||
("p2", "patient", {"name": "Bob", "age": 41}),
|
||||
],
|
||||
force=True,
|
||||
)
|
||||
|
||||
# Batch 2: same node type, now with an extra `diagnosis` attribute.
|
||||
bc.write_nodes(
|
||||
[("p3", "patient", {"name": "Carol", "age": 52, "diagnosis": "E11"})],
|
||||
force=True,
|
||||
)
|
||||
|
||||
# --- Inspect what ended up on disk -----------------------------------------
|
||||
header_path = "out/Patient-header.csv"
|
||||
header_cols = open(header_path).read().strip().split(";")
|
||||
n_header = len(header_cols)
|
||||
|
||||
print("HEADER :", header_cols, f"({n_header} columns)")
|
||||
ok = True
|
||||
for part in sorted(glob.glob("out/Patient-part*.csv")):
|
||||
for row in open(part).read().splitlines():
|
||||
n_row = len(row.split(";"))
|
||||
flag = "" if n_row == n_header else " <-- MISALIGNED"
|
||||
if n_row != n_header:
|
||||
ok = False
|
||||
print(f"{os.path.basename(part)}: {n_row} columns{flag} {row}")
|
||||
|
||||
print()
|
||||
if ok:
|
||||
print("PASS: every part file matches the header.")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print(
|
||||
"FAIL: a part file written before the schema grew no longer matches the\n"
|
||||
" header. On import, its values land under the wrong columns\n"
|
||||
" (e.g. the node id ends up under `diagnosis`, :LABEL ends up empty)."
|
||||
)
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,243 @@
|
||||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
from collections import defaultdict
|
||||
|
||||
#extract all node types and generate basic yaml config part for nodes
|
||||
|
||||
def write_automated_schema(graph, file_path, manual_schema_path):
|
||||
schema_data = {
|
||||
'nodes': {},
|
||||
'edges': {}
|
||||
}
|
||||
|
||||
if Path(file_path).exists():
|
||||
print("-- Using existing schema --")
|
||||
schema_data = load_manual_schema(file_path)
|
||||
elif isinstance(manual_schema_path, list):
|
||||
print("-- Using two schema files --")
|
||||
schema_data = schema_merger(manual_schema_path[0], manual_schema_path[1])
|
||||
elif manual_schema_path:
|
||||
print("-- Using the manual schema --")
|
||||
schema_data = load_manual_schema(manual_schema_path)
|
||||
|
||||
|
||||
if graph is not None:
|
||||
for node in graph.nodes():
|
||||
label = graph.nodes[node].get('label')
|
||||
if label == 'resource':
|
||||
label = graph.nodes[node].get('resourceType')
|
||||
|
||||
label = label.capitalize()
|
||||
|
||||
if label not in schema_data['nodes']:
|
||||
schema_data['nodes'][label] = {}
|
||||
|
||||
if 'properties' not in schema_data['nodes'][label] or schema_data['nodes'][label]['properties'] is None:
|
||||
schema_data['nodes'][label]['properties'] = {}
|
||||
|
||||
for k in graph.nodes[node].keys():
|
||||
schema_data['nodes'][label]['properties'][k] = 'str'
|
||||
|
||||
#schema_data['nodes'][label]['properties'].update(graph.nodes[node].keys())
|
||||
|
||||
|
||||
file=open(file_path, 'w')
|
||||
|
||||
for n in schema_data['nodes']:
|
||||
temp = n+':\n'
|
||||
if 'is_a' in schema_data['nodes'][n]:
|
||||
is_a_value = schema_data['nodes'][n]['is_a']
|
||||
temp += ' is_a: ' + (', '.join(is_a_value) if isinstance(is_a_value, list) else str(is_a_value)) + '\n'
|
||||
else:
|
||||
temp += ' is_a: named thing\n'
|
||||
if 'represented_as' in schema_data['nodes'][n]:
|
||||
represented_as_value = schema_data['nodes'][n]['represented_as']
|
||||
temp += ' represented_as: ' + (', '.join(represented_as_value) if isinstance(represented_as_value, list) else str(represented_as_value)) + '\n'
|
||||
else:
|
||||
temp += ' represented_as: node\n'
|
||||
if 'label_in_input' in schema_data['nodes'][n]:
|
||||
label_in_input_value = schema_data['nodes'][n]['label_in_input']
|
||||
temp += ' label_in_input: ' + (', '.join(label_in_input_value) if isinstance(label_in_input_value, list) else str(label_in_input_value)) + '\n'
|
||||
if 'preferred_id' in schema_data['nodes'][n]:
|
||||
preferred_id_value = schema_data['nodes'][n]['preferred_id']
|
||||
temp += ' preferred_id: ' + (', '.join(preferred_id_value) if isinstance(preferred_id_value, list) else str(preferred_id_value)) + '\n'
|
||||
else:
|
||||
temp += ' preferred_id: fhir_id\n'
|
||||
temp += ' label_in_input: ' + n + '\n'
|
||||
temp += ' properties:\n'
|
||||
# get property values from schema_data if exists
|
||||
#print("---------->", str(schema_data['nodes'][n]))
|
||||
if schema_data['nodes'][n]['properties'] is not None:
|
||||
for p_key in schema_data['nodes'][n]['properties']:
|
||||
prop_value = schema_data['nodes'][n]['properties'][p_key]
|
||||
temp += ' ' + p_key + ': ' + (', '.join(prop_value) if isinstance(prop_value, list) else str(prop_value)) + '\n'
|
||||
#elif schema_data['nodes']['properties']:
|
||||
#print("----> ", schema_data['nodes']['properties'])
|
||||
""" else:
|
||||
for attr in schema_data['nodes'][n]:
|
||||
temp += ' ' + attr + ': str\n' """
|
||||
|
||||
temp += '\n'
|
||||
|
||||
file.write(temp)
|
||||
|
||||
file.write('\n')
|
||||
|
||||
#extract all relationship types and generate basic yaml config part for relationships
|
||||
#if not edgeTypes: edgeTypes = set()
|
||||
|
||||
if graph is not None:
|
||||
for u, v, a in graph.edges(data=True):
|
||||
source_label = graph.nodes[u].get('label')
|
||||
target_label = graph.nodes[v].get('label')
|
||||
if source_label == 'resource':
|
||||
source_label = graph.nodes[u].get('resourceType', str(u))
|
||||
elif source_label == 'dummy' or source_label == 'Dummy':
|
||||
source_label = graph.nodes[u].get('edge_label', str(u))
|
||||
|
||||
if target_label == 'resource':
|
||||
target_label = graph.nodes[v].get('resourceType', str(v))
|
||||
elif target_label == 'dummy' or target_label == 'Dummy':
|
||||
target_label = graph.nodes[v].get('edge_label', str(v))
|
||||
|
||||
source_label = source_label.capitalize()
|
||||
target_label = target_label.capitalize()
|
||||
|
||||
CONST_ASSOCIATION = ' association'
|
||||
if source_label + ' to ' + target_label + CONST_ASSOCIATION in schema_data['edges']:
|
||||
# add missing attributes
|
||||
continue
|
||||
elif source_label + ' derived from ' + target_label + CONST_ASSOCIATION in schema_data['edges']:
|
||||
continue
|
||||
elif source_label + ' has member ' + target_label + CONST_ASSOCIATION in schema_data['edges']:
|
||||
continue
|
||||
elif source_label + ' reasoned by ' + target_label + CONST_ASSOCIATION in schema_data['edges']:
|
||||
continue
|
||||
elif source_label + ' is ' + target_label + CONST_ASSOCIATION in schema_data['edges']:
|
||||
continue
|
||||
else:
|
||||
schema_data['edges'][source_label + ' to ' + target_label + ' association'] = {
|
||||
'is_a': 'association',
|
||||
'represented_as': 'edge',
|
||||
'label_in_input': source_label + '_to_' + target_label,
|
||||
'properties': a
|
||||
}
|
||||
|
||||
for label in schema_data['edges']:
|
||||
temp = '' + label + ':\n'
|
||||
for key in schema_data['edges'][label]:
|
||||
if key == 'properties':
|
||||
if schema_data['edges'][label][key] is not None:
|
||||
temp += ' properties:\n'
|
||||
for prop in schema_data['edges'][label][key]:
|
||||
prop_value = schema_data['edges'][label][key][prop]
|
||||
temp += ' ' + prop + ': ' + (', '.join(prop_value) if isinstance(prop_value, list) else str(prop_value)) + '\n'
|
||||
else:
|
||||
field_value = schema_data['edges'][label][key]
|
||||
temp += ' ' + key + ': ' + (', '.join(field_value) if isinstance(field_value, list) else str(field_value)) + '\n'
|
||||
|
||||
temp += '\n'
|
||||
file.write(temp)
|
||||
|
||||
file.close()
|
||||
|
||||
def load_manual_schema(path):
|
||||
schema_data = {
|
||||
'nodes': {},
|
||||
'edges': {}
|
||||
}
|
||||
edgeTypes = set()
|
||||
|
||||
with open(path, 'r') as file:
|
||||
# Load YAML with comments stripped
|
||||
data = yaml.safe_load(file)
|
||||
|
||||
for label, attrs in data.items():
|
||||
cLabel = label#.capitalize() # less n, e and p with capitalize
|
||||
if label != 'Title':
|
||||
if attrs["represented_as"] == 'node':
|
||||
if not hasattr(schema_data['nodes'], cLabel):
|
||||
schema_data['nodes'][cLabel] = set()
|
||||
|
||||
#assuming uniqueness in schema file here. If the same node type exits twice, it will be overwritten.
|
||||
schema_data['nodes'][cLabel] = attrs
|
||||
#for a in attrs:
|
||||
|
||||
#print(v)
|
||||
""" for k, v in attrs:
|
||||
if not k == ''
|
||||
schema_data['nodes'][label][k] = v """
|
||||
else:
|
||||
if not hasattr(schema_data['edges'], cLabel):
|
||||
schema_data['edges'][cLabel] = set()
|
||||
|
||||
#assuming uniqueness in schema file here. If the same node type exits twice, it will be overwritten.
|
||||
schema_data['edges'][cLabel] = attrs
|
||||
|
||||
return schema_data
|
||||
|
||||
|
||||
def schema_merger(schema_path1, schema_path2):
|
||||
schema1 = load_manual_schema(schema_path1)
|
||||
schema2 = load_manual_schema(schema_path2)
|
||||
|
||||
merged = {
|
||||
'nodes': {**schema1['nodes'], **schema2['nodes']},
|
||||
'edges': {**schema1['edges'], **schema2['edges']}
|
||||
}
|
||||
|
||||
return merged
|
||||
|
||||
|
||||
def schema_diff_report(manual_path, auto_path):
|
||||
#manual_path = "config/manual_schema_config.yaml"
|
||||
#auto_path = "config/automated_schema.yaml"
|
||||
|
||||
man_schema = load_manual_schema(manual_path)
|
||||
automated_schema = load_manual_schema(auto_path)
|
||||
|
||||
print("\nNODES:\n")
|
||||
for label, attributes in automated_schema['nodes'].items():
|
||||
if 'nodes' in man_schema and label in man_schema['nodes']: # node already exists
|
||||
print("Found in man schema: ", label)
|
||||
for prop, val in attributes.items():
|
||||
if prop in man_schema['nodes'][label]: # property exists
|
||||
if isinstance(val, dict):
|
||||
for attr, attr_val in val.items():
|
||||
if attr not in man_schema['nodes'][label][prop]:
|
||||
print("Added the property " + attr + " to " + prop + " of " + label)
|
||||
elif attr_val != man_schema['nodes'][label][prop][attr]:
|
||||
print("Updated property value of " + label + "." + prop + "." + attr + "to: " + attr_val)
|
||||
elif val != man_schema['nodes'][label][prop]: # property value is not the same
|
||||
print("Updated property value of " + label + "." + prop + " to: " + val)
|
||||
else:
|
||||
print(label + "." + prop + " has not changed")
|
||||
else:
|
||||
print(label + ": added property " + prop + ". Value: " + val)
|
||||
else: # node not found in manual schema
|
||||
print('Resource type added: ' + label)
|
||||
|
||||
print("\nEDGES:\n")
|
||||
for label, attributes in automated_schema['edges'].items():
|
||||
if 'edges' in man_schema and label in man_schema['edges']: # node already exists
|
||||
print("Found in man schema: ", label)
|
||||
for prop, val in attributes.items():
|
||||
if prop in man_schema['edges'][label]: # property exists
|
||||
if isinstance(val, dict):
|
||||
for attr, attr_val in val.items():
|
||||
if attr not in man_schema['edges'][label][prop]:
|
||||
print("Added the property " + attr + " to " + prop)
|
||||
elif attr_val != man_schema['edges'][label][prop][attr]:
|
||||
print("Updated property value of " + label + "." + prop + "." + attr + "to: " + attr_val)
|
||||
elif val != man_schema['edges'][label][prop]: # property value is not the same
|
||||
print("Updated property value of " + label + "." + prop + " to: " + val)
|
||||
else:
|
||||
print(label + "." + prop + " has not changed")
|
||||
else:
|
||||
print(label + ": added property " + prop + ". Value: " + val)
|
||||
else: # node not found in manual schema
|
||||
print('Resource type added: ' + label)
|
||||
@@ -0,0 +1,250 @@
|
||||
52466995
|
||||
52467955
|
||||
52466558
|
||||
5
|
||||
13
|
||||
14
|
||||
22
|
||||
30
|
||||
37
|
||||
57
|
||||
110
|
||||
118
|
||||
134
|
||||
170
|
||||
177
|
||||
id1568758349858
|
||||
233
|
||||
567
|
||||
768
|
||||
1006
|
||||
1009
|
||||
1021
|
||||
1029
|
||||
1059
|
||||
1091
|
||||
1190
|
||||
1194
|
||||
1200
|
||||
1201
|
||||
1203
|
||||
1208
|
||||
1222
|
||||
1316
|
||||
1526
|
||||
1654
|
||||
2000
|
||||
patient
|
||||
24739
|
||||
25420
|
||||
30163
|
||||
30358
|
||||
31678
|
||||
38100
|
||||
39254
|
||||
40052
|
||||
40625
|
||||
42024
|
||||
45276
|
||||
45280
|
||||
49006
|
||||
50427
|
||||
123a
|
||||
example
|
||||
cf-1572285491892
|
||||
51779
|
||||
52865
|
||||
53254
|
||||
53373
|
||||
55190
|
||||
55193
|
||||
55810
|
||||
55812
|
||||
59530
|
||||
69050
|
||||
76107
|
||||
141547
|
||||
141549
|
||||
206342
|
||||
206686
|
||||
221706
|
||||
221709
|
||||
257273
|
||||
Patient1
|
||||
pat1
|
||||
pat2
|
||||
579296
|
||||
579367
|
||||
581516
|
||||
591093
|
||||
591164
|
||||
591221
|
||||
591229
|
||||
591245
|
||||
591252
|
||||
591253
|
||||
591264
|
||||
591286
|
||||
591309
|
||||
591352
|
||||
591360
|
||||
591372
|
||||
591378
|
||||
591427
|
||||
591626
|
||||
591645
|
||||
591653
|
||||
591661
|
||||
591695
|
||||
591702
|
||||
591723
|
||||
591727
|
||||
591744
|
||||
591788
|
||||
591829
|
||||
591841
|
||||
591874
|
||||
591956
|
||||
591975
|
||||
592014
|
||||
592030
|
||||
592215
|
||||
592228
|
||||
592274
|
||||
592275
|
||||
592294
|
||||
592295
|
||||
592318
|
||||
592350
|
||||
592353
|
||||
592473
|
||||
592474
|
||||
592485
|
||||
592502
|
||||
592529
|
||||
592530
|
||||
592580
|
||||
592621
|
||||
592733
|
||||
592736
|
||||
592738
|
||||
592740
|
||||
592741
|
||||
592760
|
||||
592761
|
||||
592766
|
||||
592773
|
||||
592808
|
||||
592809
|
||||
592817
|
||||
592824
|
||||
592841
|
||||
592845
|
||||
592911
|
||||
592912
|
||||
592913
|
||||
592922
|
||||
592940
|
||||
592943
|
||||
592944
|
||||
592955
|
||||
592956
|
||||
592995
|
||||
593004
|
||||
593043
|
||||
593160
|
||||
593165
|
||||
593166
|
||||
593170
|
||||
593171
|
||||
593178
|
||||
593186
|
||||
593190
|
||||
593210
|
||||
593218
|
||||
593271
|
||||
593321
|
||||
593380
|
||||
594843
|
||||
595184
|
||||
595251
|
||||
595262
|
||||
595271
|
||||
595280
|
||||
595284
|
||||
595606
|
||||
596328
|
||||
596336
|
||||
596341
|
||||
596357
|
||||
596380
|
||||
596388
|
||||
596407
|
||||
596438
|
||||
596439
|
||||
596467
|
||||
596490
|
||||
596492
|
||||
596495
|
||||
596496
|
||||
596498
|
||||
596500
|
||||
596505
|
||||
596506
|
||||
596510
|
||||
596513
|
||||
596517
|
||||
596518
|
||||
596520
|
||||
596522
|
||||
596544
|
||||
596545
|
||||
596546
|
||||
596547
|
||||
596555
|
||||
596557
|
||||
596561
|
||||
596562
|
||||
596563
|
||||
596568
|
||||
596570
|
||||
596571
|
||||
596574
|
||||
596575
|
||||
596599
|
||||
596615
|
||||
596634
|
||||
596638
|
||||
596644
|
||||
596805
|
||||
596813
|
||||
596857
|
||||
597041
|
||||
597063
|
||||
597064
|
||||
597065
|
||||
597067
|
||||
597068
|
||||
597069
|
||||
597083
|
||||
597110
|
||||
597118
|
||||
597172
|
||||
597176
|
||||
597178
|
||||
597226
|
||||
597291
|
||||
597296
|
||||
597305
|
||||
597306
|
||||
597309
|
||||
597395
|
||||
597521
|
||||
597879
|
||||
597991
|
||||
599548
|
||||
602313
|
||||
611763
|
||||
618626
|
||||
618670
|
||||
618750
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,959 @@
|
||||
{
|
||||
"resourceType": "Bundle",
|
||||
"type": "transaction",
|
||||
"entry": [ {
|
||||
"fullUrl": "Patient/UKE-0013",
|
||||
"resource": {
|
||||
"resourceType": "Patient",
|
||||
"id": "UKE-0013",
|
||||
"meta": {
|
||||
"profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-person/StructureDefinition/Patient" ]
|
||||
},
|
||||
"identifier": [ {
|
||||
"use": "usual",
|
||||
"type": {
|
||||
"coding": [ {
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
|
||||
"code": "MR"
|
||||
} ]
|
||||
},
|
||||
"system": "https://UKE.de/pid",
|
||||
"value": "UKE-0013"
|
||||
} ],
|
||||
"name": [ {
|
||||
"use": "official",
|
||||
"family": "l",
|
||||
"given": [ "i" ]
|
||||
} ],
|
||||
"gender": "male",
|
||||
"birthDate": "1943-01-01",
|
||||
"address": [ {
|
||||
"extension": [ {
|
||||
"url": "http://terminology.hl7.org/CodeSystem/data-absent-reason",
|
||||
"valueCode": "unknown"
|
||||
} ]
|
||||
} ],
|
||||
"generalPractitioner": [ {
|
||||
"display": "Barmer"
|
||||
} ]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "Patient/UKE-0013"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "Encounter/UKE-0013-E-1",
|
||||
"resource": {
|
||||
"resourceType": "Encounter",
|
||||
"id": "UKE-0013-E-1",
|
||||
"meta": {
|
||||
"profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-fall/StructureDefinition/KontaktGesundheitseinrichtung" ]
|
||||
},
|
||||
"identifier": [ {
|
||||
"type": {
|
||||
"coding": [ {
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
|
||||
"code": "VN"
|
||||
} ]
|
||||
},
|
||||
"_system": {
|
||||
"extension": [ {
|
||||
"url": "http://terminology.hl7.org/CodeSystem/data-absent-reason",
|
||||
"valueCode": "unknown"
|
||||
} ]
|
||||
},
|
||||
"value": "UKE-0013-E-1",
|
||||
"assigner": {
|
||||
"identifier": {
|
||||
"system": "https://www.medizininformatik-initiative.de/fhir/core/NamingSystem/org-identifier",
|
||||
"value": "UKE"
|
||||
}
|
||||
}
|
||||
} ],
|
||||
"status": "finished",
|
||||
"class": {
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
|
||||
"code": "IMP",
|
||||
"display": "inpatient encounter"
|
||||
},
|
||||
"type": [ {
|
||||
"coding": [ {
|
||||
"code": "einrichtungskontakt",
|
||||
"display": "Einrichtungskontakt"
|
||||
} ]
|
||||
} ],
|
||||
"subject": {
|
||||
"reference": "Patient/UKE-0013"
|
||||
},
|
||||
"period": {
|
||||
"start": "2020-06-13T00:00:00+02:00",
|
||||
"end": "2020-06-18T00:00:00+02:00"
|
||||
},
|
||||
"diagnosis": [ {
|
||||
"condition": {
|
||||
"reference": "Condition/UKE-0013-E-1-D-1"
|
||||
},
|
||||
"use": {
|
||||
"coding": [ {
|
||||
"system": "http://terminology.hl7.org/CodeSystem/diagnosis-role",
|
||||
"code": "AD",
|
||||
"display": "Admission diagnosis"
|
||||
} ]
|
||||
}
|
||||
} ]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "Encounter/UKE-0013-E-1"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "Encounter/UKE-0013-E-1-A-1",
|
||||
"resource": {
|
||||
"resourceType": "Encounter",
|
||||
"id": "UKE-0013-E-1-A-1",
|
||||
"meta": {
|
||||
"profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-fall/StructureDefinition/KontaktGesundheitseinrichtung" ]
|
||||
},
|
||||
"status": "finished",
|
||||
"class": {
|
||||
"extension": [ {
|
||||
"url": "http://terminology.hl7.org/CodeSystem/data-absent-reason",
|
||||
"valueCode": "unknown"
|
||||
} ]
|
||||
},
|
||||
"type": [ {
|
||||
"coding": [ {
|
||||
"code": "abteilungskontakt",
|
||||
"display": "Abteilungskontakt"
|
||||
} ]
|
||||
} ],
|
||||
"serviceType": {
|
||||
"coding": [ {
|
||||
"system": "http://fhir.de/CodeSystem/dkgev/Fachabteilungsschluessel",
|
||||
"code": "0300",
|
||||
"display": "Kardiologie"
|
||||
} ]
|
||||
},
|
||||
"subject": {
|
||||
"reference": "Patient/UKE-0013"
|
||||
},
|
||||
"period": {
|
||||
"start": "2020-06-13T00:00:00+02:00",
|
||||
"end": "2020-06-18T00:00:00+02:00"
|
||||
},
|
||||
"diagnosis": [ {
|
||||
"condition": {
|
||||
"_reference": {
|
||||
"extension": [ {
|
||||
"url": "http://terminology.hl7.org/CodeSystem/data-absent-reason",
|
||||
"valueCode": "unknown"
|
||||
} ]
|
||||
}
|
||||
}
|
||||
} ],
|
||||
"partOf": {
|
||||
"reference": "Encounter/UKE-0013-E-1"
|
||||
}
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "Encounter/UKE-0013-E-1-A-1"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "Condition/UKE-0013-E-1-D-1",
|
||||
"resource": {
|
||||
"resourceType": "Condition",
|
||||
"id": "UKE-0013-E-1-D-1",
|
||||
"meta": {
|
||||
"profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-diagnose/StructureDefinition/Diagnose" ]
|
||||
},
|
||||
"identifier": [ {
|
||||
"value": "UKE-0013-E-1-D-1"
|
||||
} ],
|
||||
"code": {
|
||||
"coding": [ {
|
||||
"system": "http://fhir.de/CodeSystem/bfarm/icd-10-gm",
|
||||
"version": "2020",
|
||||
"code": "I25.22"
|
||||
} ],
|
||||
"text": "Myokardinfarkt in Vergangenheit"
|
||||
},
|
||||
"subject": {
|
||||
"reference": "Patient/UKE-0013"
|
||||
},
|
||||
"recordedDate": "2020-06-13T00:00:00+02:00"
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "Condition/UKE-0013-E-1-D-1"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "Medication/Medication-154102257",
|
||||
"resource": {
|
||||
"resourceType": "Medication",
|
||||
"id": "Medication-154102257",
|
||||
"meta": {
|
||||
"profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-medikation/StructureDefinition/Medication" ]
|
||||
},
|
||||
"identifier": [ {
|
||||
"value": "Medication-154102257"
|
||||
} ],
|
||||
"code": {
|
||||
"coding": [ {
|
||||
"system": "http://fhir.de/CodeSystem/ifa/pzn",
|
||||
"code": "05484267"
|
||||
}, {
|
||||
"system": "http://fhir.de/CodeSystem/bfarm/atc",
|
||||
"code": "L03AA02"
|
||||
} ],
|
||||
"text": "Filgastrim"
|
||||
},
|
||||
"ingredient": [ {
|
||||
"itemCodeableConcept": {
|
||||
"coding": [ {
|
||||
"system": "http://fhir.de/CodeSystem/ask",
|
||||
"_code": {
|
||||
"extension": [ {
|
||||
"url": "http://terminology.hl7.org/CodeSystem/data-absent-reason",
|
||||
"valueCode": "unknown"
|
||||
} ]
|
||||
}
|
||||
} ]
|
||||
},
|
||||
"strength": {
|
||||
"numerator": {
|
||||
"value": 48,
|
||||
"unit": "Mio I.E./0,5ml",
|
||||
"system": "http://unitsofmeasure.org",
|
||||
"code": "Mio I.E./0,5ml"
|
||||
},
|
||||
"denominator": {
|
||||
"value": 1,
|
||||
"system": "http://XXX",
|
||||
"code": "Fertigspritzen"
|
||||
}
|
||||
}
|
||||
} ]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "Medication/Medication-154102257"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "MedicationStatement/UKE-0013-E-1-MS-1",
|
||||
"resource": {
|
||||
"resourceType": "MedicationStatement",
|
||||
"id": "UKE-0013-E-1-MS-1",
|
||||
"meta": {
|
||||
"profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-medikation/StructureDefinition/MedicationStatement" ]
|
||||
},
|
||||
"status": "active",
|
||||
"medicationReference": {
|
||||
"reference": "Medication/Medication-154102257"
|
||||
},
|
||||
"subject": {
|
||||
"reference": "Patient/UKE-0013"
|
||||
},
|
||||
"context": {
|
||||
"reference": "Encounter/UKE-0013-E-1"
|
||||
},
|
||||
"effectiveDateTime": "2020-06-13T00:00:00+02:00",
|
||||
"dosage": [ {
|
||||
"doseAndRate": [ {
|
||||
"doseQuantity": {
|
||||
"value": 1,
|
||||
"unit": "Fertigspritzen",
|
||||
"system": "http://unitsofmeasure.org",
|
||||
"code": "1"
|
||||
}
|
||||
} ]
|
||||
} ]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "MedicationStatement/UKE-0013-E-1-MS-1"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "Medication/Medication--132842199",
|
||||
"resource": {
|
||||
"resourceType": "Medication",
|
||||
"id": "Medication--132842199",
|
||||
"meta": {
|
||||
"profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-medikation/StructureDefinition/Medication" ]
|
||||
},
|
||||
"identifier": [ {
|
||||
"value": "Medication--132842199"
|
||||
} ],
|
||||
"code": {
|
||||
"coding": [ {
|
||||
"system": "http://fhir.de/CodeSystem/ifa/pzn",
|
||||
"code": "12636016"
|
||||
}, {
|
||||
"system": "http://fhir.de/CodeSystem/bfarm/atc",
|
||||
"code": "B01AF01"
|
||||
} ],
|
||||
"text": "Rivaroxaban"
|
||||
},
|
||||
"ingredient": [ {
|
||||
"itemCodeableConcept": {
|
||||
"coding": [ {
|
||||
"system": "http://fhir.de/CodeSystem/ask",
|
||||
"_code": {
|
||||
"extension": [ {
|
||||
"url": "http://terminology.hl7.org/CodeSystem/data-absent-reason",
|
||||
"valueCode": "unknown"
|
||||
} ]
|
||||
}
|
||||
} ]
|
||||
},
|
||||
"strength": {
|
||||
"numerator": {
|
||||
"value": 10,
|
||||
"unit": "milligram",
|
||||
"system": "http://unitsofmeasure.org",
|
||||
"code": "mg"
|
||||
},
|
||||
"denominator": {
|
||||
"value": 1,
|
||||
"system": "http://XXX",
|
||||
"code": "Tabletten"
|
||||
}
|
||||
}
|
||||
} ]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "Medication/Medication--132842199"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "MedicationStatement/UKE-0013-E-1-MS-2",
|
||||
"resource": {
|
||||
"resourceType": "MedicationStatement",
|
||||
"id": "UKE-0013-E-1-MS-2",
|
||||
"meta": {
|
||||
"profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-medikation/StructureDefinition/MedicationStatement" ]
|
||||
},
|
||||
"status": "active",
|
||||
"medicationReference": {
|
||||
"reference": "Medication/Medication--132842199"
|
||||
},
|
||||
"subject": {
|
||||
"reference": "Patient/UKE-0013"
|
||||
},
|
||||
"context": {
|
||||
"reference": "Encounter/UKE-0013-E-1"
|
||||
},
|
||||
"effectiveDateTime": "2020-06-13T00:00:00+02:00",
|
||||
"dosage": [ {
|
||||
"doseAndRate": [ {
|
||||
"doseQuantity": {
|
||||
"value": 1,
|
||||
"unit": "Tabletten",
|
||||
"system": "http://unitsofmeasure.org",
|
||||
"code": "1"
|
||||
}
|
||||
} ]
|
||||
} ]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "MedicationStatement/UKE-0013-E-1-MS-2"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "Medication/Medication-1928216850",
|
||||
"resource": {
|
||||
"resourceType": "Medication",
|
||||
"id": "Medication-1928216850",
|
||||
"meta": {
|
||||
"profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-medikation/StructureDefinition/Medication" ]
|
||||
},
|
||||
"identifier": [ {
|
||||
"value": "Medication-1928216850"
|
||||
} ],
|
||||
"code": {
|
||||
"coding": [ {
|
||||
"system": "http://fhir.de/CodeSystem/ifa/pzn",
|
||||
"code": "06882768"
|
||||
}, {
|
||||
"system": "http://fhir.de/CodeSystem/bfarm/atc",
|
||||
"code": "N02BB02"
|
||||
} ],
|
||||
"text": "Metamizol natrium-1-Wasser"
|
||||
},
|
||||
"ingredient": [ {
|
||||
"itemCodeableConcept": {
|
||||
"coding": [ {
|
||||
"system": "http://fhir.de/CodeSystem/ask",
|
||||
"_code": {
|
||||
"extension": [ {
|
||||
"url": "http://terminology.hl7.org/CodeSystem/data-absent-reason",
|
||||
"valueCode": "unknown"
|
||||
} ]
|
||||
}
|
||||
} ]
|
||||
},
|
||||
"strength": {
|
||||
"numerator": {
|
||||
"value": 1000,
|
||||
"unit": "milligram",
|
||||
"system": "http://unitsofmeasure.org",
|
||||
"code": "mg"
|
||||
},
|
||||
"denominator": {
|
||||
"value": 1,
|
||||
"system": "http://XXX",
|
||||
"code": "Injektionslösung"
|
||||
}
|
||||
}
|
||||
} ]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "Medication/Medication-1928216850"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "MedicationStatement/UKE-0013-E-1-MS-3",
|
||||
"resource": {
|
||||
"resourceType": "MedicationStatement",
|
||||
"id": "UKE-0013-E-1-MS-3",
|
||||
"meta": {
|
||||
"profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-medikation/StructureDefinition/MedicationStatement" ]
|
||||
},
|
||||
"status": "active",
|
||||
"medicationReference": {
|
||||
"reference": "Medication/Medication-1928216850"
|
||||
},
|
||||
"subject": {
|
||||
"reference": "Patient/UKE-0013"
|
||||
},
|
||||
"context": {
|
||||
"reference": "Encounter/UKE-0013-E-1"
|
||||
},
|
||||
"effectiveDateTime": "2020-06-13T00:00:00+02:00",
|
||||
"dosage": [ {
|
||||
"doseAndRate": [ {
|
||||
"doseQuantity": {
|
||||
"value": 4,
|
||||
"unit": "Injektionslösung",
|
||||
"system": "http://unitsofmeasure.org",
|
||||
"code": "1"
|
||||
}
|
||||
} ]
|
||||
} ]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "MedicationStatement/UKE-0013-E-1-MS-3"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "MedicationStatement/UKE-0013-E-1-MS-4",
|
||||
"resource": {
|
||||
"resourceType": "MedicationStatement",
|
||||
"id": "UKE-0013-E-1-MS-4",
|
||||
"meta": {
|
||||
"profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-medikation/StructureDefinition/MedicationStatement" ]
|
||||
},
|
||||
"status": "active",
|
||||
"medicationReference": {
|
||||
"reference": "Medication/Medication-154102257"
|
||||
},
|
||||
"subject": {
|
||||
"reference": "Patient/UKE-0013"
|
||||
},
|
||||
"context": {
|
||||
"reference": "Encounter/UKE-0013-E-1"
|
||||
},
|
||||
"effectiveDateTime": "2020-06-14T00:00:00+02:00",
|
||||
"dosage": [ {
|
||||
"doseAndRate": [ {
|
||||
"doseQuantity": {
|
||||
"value": 1,
|
||||
"unit": "Fertigspritzen",
|
||||
"system": "http://unitsofmeasure.org",
|
||||
"code": "1"
|
||||
}
|
||||
} ]
|
||||
} ]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "MedicationStatement/UKE-0013-E-1-MS-4"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "MedicationStatement/UKE-0013-E-1-MS-5",
|
||||
"resource": {
|
||||
"resourceType": "MedicationStatement",
|
||||
"id": "UKE-0013-E-1-MS-5",
|
||||
"meta": {
|
||||
"profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-medikation/StructureDefinition/MedicationStatement" ]
|
||||
},
|
||||
"status": "active",
|
||||
"medicationReference": {
|
||||
"reference": "Medication/Medication--132842199"
|
||||
},
|
||||
"subject": {
|
||||
"reference": "Patient/UKE-0013"
|
||||
},
|
||||
"context": {
|
||||
"reference": "Encounter/UKE-0013-E-1"
|
||||
},
|
||||
"effectiveDateTime": "2020-06-14T00:00:00+02:00",
|
||||
"dosage": [ {
|
||||
"doseAndRate": [ {
|
||||
"doseQuantity": {
|
||||
"value": 1,
|
||||
"unit": "Tabletten",
|
||||
"system": "http://unitsofmeasure.org",
|
||||
"code": "1"
|
||||
}
|
||||
} ]
|
||||
} ]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "MedicationStatement/UKE-0013-E-1-MS-5"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "MedicationStatement/UKE-0013-E-1-MS-6",
|
||||
"resource": {
|
||||
"resourceType": "MedicationStatement",
|
||||
"id": "UKE-0013-E-1-MS-6",
|
||||
"meta": {
|
||||
"profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-medikation/StructureDefinition/MedicationStatement" ]
|
||||
},
|
||||
"status": "active",
|
||||
"medicationReference": {
|
||||
"reference": "Medication/Medication-1928216850"
|
||||
},
|
||||
"subject": {
|
||||
"reference": "Patient/UKE-0013"
|
||||
},
|
||||
"context": {
|
||||
"reference": "Encounter/UKE-0013-E-1"
|
||||
},
|
||||
"effectiveDateTime": "2020-06-14T00:00:00+02:00",
|
||||
"dosage": [ {
|
||||
"doseAndRate": [ {
|
||||
"doseQuantity": {
|
||||
"value": 4,
|
||||
"unit": "Injektionslösung",
|
||||
"system": "http://unitsofmeasure.org",
|
||||
"code": "1"
|
||||
}
|
||||
} ]
|
||||
} ]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "MedicationStatement/UKE-0013-E-1-MS-6"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "MedicationStatement/UKE-0013-E-1-MS-7",
|
||||
"resource": {
|
||||
"resourceType": "MedicationStatement",
|
||||
"id": "UKE-0013-E-1-MS-7",
|
||||
"meta": {
|
||||
"profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-medikation/StructureDefinition/MedicationStatement" ]
|
||||
},
|
||||
"status": "active",
|
||||
"medicationReference": {
|
||||
"reference": "Medication/Medication-154102257"
|
||||
},
|
||||
"subject": {
|
||||
"reference": "Patient/UKE-0013"
|
||||
},
|
||||
"context": {
|
||||
"reference": "Encounter/UKE-0013-E-1"
|
||||
},
|
||||
"effectiveDateTime": "2020-06-15T00:00:00+02:00",
|
||||
"dosage": [ {
|
||||
"doseAndRate": [ {
|
||||
"doseQuantity": {
|
||||
"value": 1,
|
||||
"unit": "Fertigspritzen",
|
||||
"system": "http://unitsofmeasure.org",
|
||||
"code": "1"
|
||||
}
|
||||
} ]
|
||||
} ]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "MedicationStatement/UKE-0013-E-1-MS-7"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "MedicationStatement/UKE-0013-E-1-MS-8",
|
||||
"resource": {
|
||||
"resourceType": "MedicationStatement",
|
||||
"id": "UKE-0013-E-1-MS-8",
|
||||
"meta": {
|
||||
"profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-medikation/StructureDefinition/MedicationStatement" ]
|
||||
},
|
||||
"status": "active",
|
||||
"medicationReference": {
|
||||
"reference": "Medication/Medication--132842199"
|
||||
},
|
||||
"subject": {
|
||||
"reference": "Patient/UKE-0013"
|
||||
},
|
||||
"context": {
|
||||
"reference": "Encounter/UKE-0013-E-1"
|
||||
},
|
||||
"effectiveDateTime": "2020-06-15T00:00:00+02:00",
|
||||
"dosage": [ {
|
||||
"doseAndRate": [ {
|
||||
"doseQuantity": {
|
||||
"value": 1,
|
||||
"unit": "Tabletten",
|
||||
"system": "http://unitsofmeasure.org",
|
||||
"code": "1"
|
||||
}
|
||||
} ]
|
||||
} ]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "MedicationStatement/UKE-0013-E-1-MS-8"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "MedicationStatement/UKE-0013-E-1-MS-9",
|
||||
"resource": {
|
||||
"resourceType": "MedicationStatement",
|
||||
"id": "UKE-0013-E-1-MS-9",
|
||||
"meta": {
|
||||
"profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-medikation/StructureDefinition/MedicationStatement" ]
|
||||
},
|
||||
"status": "active",
|
||||
"medicationReference": {
|
||||
"reference": "Medication/Medication-1928216850"
|
||||
},
|
||||
"subject": {
|
||||
"reference": "Patient/UKE-0013"
|
||||
},
|
||||
"context": {
|
||||
"reference": "Encounter/UKE-0013-E-1"
|
||||
},
|
||||
"effectiveDateTime": "2020-06-15T00:00:00+02:00",
|
||||
"dosage": [ {
|
||||
"doseAndRate": [ {
|
||||
"doseQuantity": {
|
||||
"value": 4,
|
||||
"unit": "Injektionslösung",
|
||||
"system": "http://unitsofmeasure.org",
|
||||
"code": "1"
|
||||
}
|
||||
} ]
|
||||
} ]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "MedicationStatement/UKE-0013-E-1-MS-9"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "MedicationStatement/UKE-0013-E-1-MS-10",
|
||||
"resource": {
|
||||
"resourceType": "MedicationStatement",
|
||||
"id": "UKE-0013-E-1-MS-10",
|
||||
"meta": {
|
||||
"profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-medikation/StructureDefinition/MedicationStatement" ]
|
||||
},
|
||||
"status": "active",
|
||||
"medicationReference": {
|
||||
"reference": "Medication/Medication-154102257"
|
||||
},
|
||||
"subject": {
|
||||
"reference": "Patient/UKE-0013"
|
||||
},
|
||||
"context": {
|
||||
"reference": "Encounter/UKE-0013-E-1"
|
||||
},
|
||||
"effectiveDateTime": "2020-06-16T00:00:00+02:00",
|
||||
"dosage": [ {
|
||||
"doseAndRate": [ {
|
||||
"doseQuantity": {
|
||||
"value": 1,
|
||||
"unit": "Fertigspritzen",
|
||||
"system": "http://unitsofmeasure.org",
|
||||
"code": "1"
|
||||
}
|
||||
} ]
|
||||
} ]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "MedicationStatement/UKE-0013-E-1-MS-10"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "MedicationStatement/UKE-0013-E-1-MS-11",
|
||||
"resource": {
|
||||
"resourceType": "MedicationStatement",
|
||||
"id": "UKE-0013-E-1-MS-11",
|
||||
"meta": {
|
||||
"profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-medikation/StructureDefinition/MedicationStatement" ]
|
||||
},
|
||||
"status": "active",
|
||||
"medicationReference": {
|
||||
"reference": "Medication/Medication--132842199"
|
||||
},
|
||||
"subject": {
|
||||
"reference": "Patient/UKE-0013"
|
||||
},
|
||||
"context": {
|
||||
"reference": "Encounter/UKE-0013-E-1"
|
||||
},
|
||||
"effectiveDateTime": "2020-06-16T00:00:00+02:00",
|
||||
"dosage": [ {
|
||||
"doseAndRate": [ {
|
||||
"doseQuantity": {
|
||||
"value": 1,
|
||||
"unit": "Tabletten",
|
||||
"system": "http://unitsofmeasure.org",
|
||||
"code": "1"
|
||||
}
|
||||
} ]
|
||||
} ]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "MedicationStatement/UKE-0013-E-1-MS-11"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "MedicationStatement/UKE-0013-E-1-MS-12",
|
||||
"resource": {
|
||||
"resourceType": "MedicationStatement",
|
||||
"id": "UKE-0013-E-1-MS-12",
|
||||
"meta": {
|
||||
"profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-medikation/StructureDefinition/MedicationStatement" ]
|
||||
},
|
||||
"status": "active",
|
||||
"medicationReference": {
|
||||
"reference": "Medication/Medication-1928216850"
|
||||
},
|
||||
"subject": {
|
||||
"reference": "Patient/UKE-0013"
|
||||
},
|
||||
"context": {
|
||||
"reference": "Encounter/UKE-0013-E-1"
|
||||
},
|
||||
"effectiveDateTime": "2020-06-16T00:00:00+02:00",
|
||||
"dosage": [ {
|
||||
"doseAndRate": [ {
|
||||
"doseQuantity": {
|
||||
"value": 4,
|
||||
"unit": "Injektionslösung",
|
||||
"system": "http://unitsofmeasure.org",
|
||||
"code": "1"
|
||||
}
|
||||
} ]
|
||||
} ]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "MedicationStatement/UKE-0013-E-1-MS-12"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "MedicationStatement/UKE-0013-E-1-MS-13",
|
||||
"resource": {
|
||||
"resourceType": "MedicationStatement",
|
||||
"id": "UKE-0013-E-1-MS-13",
|
||||
"meta": {
|
||||
"profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-medikation/StructureDefinition/MedicationStatement" ]
|
||||
},
|
||||
"status": "active",
|
||||
"medicationReference": {
|
||||
"reference": "Medication/Medication-154102257"
|
||||
},
|
||||
"subject": {
|
||||
"reference": "Patient/UKE-0013"
|
||||
},
|
||||
"context": {
|
||||
"reference": "Encounter/UKE-0013-E-1"
|
||||
},
|
||||
"effectiveDateTime": "2020-06-17T00:00:00+02:00",
|
||||
"dosage": [ {
|
||||
"doseAndRate": [ {
|
||||
"doseQuantity": {
|
||||
"value": 1,
|
||||
"unit": "Fertigspritzen",
|
||||
"system": "http://unitsofmeasure.org",
|
||||
"code": "1"
|
||||
}
|
||||
} ]
|
||||
} ]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "MedicationStatement/UKE-0013-E-1-MS-13"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "MedicationStatement/UKE-0013-E-1-MS-14",
|
||||
"resource": {
|
||||
"resourceType": "MedicationStatement",
|
||||
"id": "UKE-0013-E-1-MS-14",
|
||||
"meta": {
|
||||
"profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-medikation/StructureDefinition/MedicationStatement" ]
|
||||
},
|
||||
"status": "active",
|
||||
"medicationReference": {
|
||||
"reference": "Medication/Medication--132842199"
|
||||
},
|
||||
"subject": {
|
||||
"reference": "Patient/UKE-0013"
|
||||
},
|
||||
"context": {
|
||||
"reference": "Encounter/UKE-0013-E-1"
|
||||
},
|
||||
"effectiveDateTime": "2020-06-17T00:00:00+02:00",
|
||||
"dosage": [ {
|
||||
"doseAndRate": [ {
|
||||
"doseQuantity": {
|
||||
"value": 1,
|
||||
"unit": "Tabletten",
|
||||
"system": "http://unitsofmeasure.org",
|
||||
"code": "1"
|
||||
}
|
||||
} ]
|
||||
} ]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "MedicationStatement/UKE-0013-E-1-MS-14"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "MedicationStatement/UKE-0013-E-1-MS-15",
|
||||
"resource": {
|
||||
"resourceType": "MedicationStatement",
|
||||
"id": "UKE-0013-E-1-MS-15",
|
||||
"meta": {
|
||||
"profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-medikation/StructureDefinition/MedicationStatement" ]
|
||||
},
|
||||
"status": "active",
|
||||
"medicationReference": {
|
||||
"reference": "Medication/Medication-1928216850"
|
||||
},
|
||||
"subject": {
|
||||
"reference": "Patient/UKE-0013"
|
||||
},
|
||||
"context": {
|
||||
"reference": "Encounter/UKE-0013-E-1"
|
||||
},
|
||||
"effectiveDateTime": "2020-06-17T00:00:00+02:00",
|
||||
"dosage": [ {
|
||||
"doseAndRate": [ {
|
||||
"doseQuantity": {
|
||||
"value": 4,
|
||||
"unit": "Injektionslösung",
|
||||
"system": "http://unitsofmeasure.org",
|
||||
"code": "1"
|
||||
}
|
||||
} ]
|
||||
} ]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "MedicationStatement/UKE-0013-E-1-MS-15"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "MedicationStatement/UKE-0013-E-1-MS-16",
|
||||
"resource": {
|
||||
"resourceType": "MedicationStatement",
|
||||
"id": "UKE-0013-E-1-MS-16",
|
||||
"meta": {
|
||||
"profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-medikation/StructureDefinition/MedicationStatement" ]
|
||||
},
|
||||
"status": "active",
|
||||
"medicationReference": {
|
||||
"reference": "Medication/Medication-154102257"
|
||||
},
|
||||
"subject": {
|
||||
"reference": "Patient/UKE-0013"
|
||||
},
|
||||
"context": {
|
||||
"reference": "Encounter/UKE-0013-E-1"
|
||||
},
|
||||
"effectiveDateTime": "2020-06-18T00:00:00+02:00",
|
||||
"dosage": [ {
|
||||
"doseAndRate": [ {
|
||||
"doseQuantity": {
|
||||
"value": 1,
|
||||
"unit": "Fertigspritzen",
|
||||
"system": "http://unitsofmeasure.org",
|
||||
"code": "1"
|
||||
}
|
||||
} ]
|
||||
} ]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "MedicationStatement/UKE-0013-E-1-MS-16"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "MedicationStatement/UKE-0013-E-1-MS-17",
|
||||
"resource": {
|
||||
"resourceType": "MedicationStatement",
|
||||
"id": "UKE-0013-E-1-MS-17",
|
||||
"meta": {
|
||||
"profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-medikation/StructureDefinition/MedicationStatement" ]
|
||||
},
|
||||
"status": "active",
|
||||
"medicationReference": {
|
||||
"reference": "Medication/Medication--132842199"
|
||||
},
|
||||
"subject": {
|
||||
"reference": "Patient/UKE-0013"
|
||||
},
|
||||
"context": {
|
||||
"reference": "Encounter/UKE-0013-E-1"
|
||||
},
|
||||
"effectiveDateTime": "2020-06-18T00:00:00+02:00",
|
||||
"dosage": [ {
|
||||
"doseAndRate": [ {
|
||||
"doseQuantity": {
|
||||
"value": 1,
|
||||
"unit": "Tabletten",
|
||||
"system": "http://unitsofmeasure.org",
|
||||
"code": "1"
|
||||
}
|
||||
} ]
|
||||
} ]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "MedicationStatement/UKE-0013-E-1-MS-17"
|
||||
}
|
||||
}, {
|
||||
"fullUrl": "MedicationStatement/UKE-0013-E-1-MS-18",
|
||||
"resource": {
|
||||
"resourceType": "MedicationStatement",
|
||||
"id": "UKE-0013-E-1-MS-18",
|
||||
"meta": {
|
||||
"profile": [ "https://www.medizininformatik-initiative.de/fhir/core/modul-medikation/StructureDefinition/MedicationStatement" ]
|
||||
},
|
||||
"status": "active",
|
||||
"medicationReference": {
|
||||
"reference": "Medication/Medication-1928216850"
|
||||
},
|
||||
"subject": {
|
||||
"reference": "Patient/UKE-0013"
|
||||
},
|
||||
"context": {
|
||||
"reference": "Encounter/UKE-0013-E-1"
|
||||
},
|
||||
"effectiveDateTime": "2020-06-18T00:00:00+02:00",
|
||||
"dosage": [ {
|
||||
"doseAndRate": [ {
|
||||
"doseQuantity": {
|
||||
"value": 4,
|
||||
"unit": "Injektionslösung",
|
||||
"system": "http://unitsofmeasure.org",
|
||||
"code": "1"
|
||||
}
|
||||
} ]
|
||||
} ]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "MedicationStatement/UKE-0013-E-1-MS-18"
|
||||
}
|
||||
} ]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user