From eea23f787466914a3f921325e88551d1f4a990e5 Mon Sep 17 00:00:00 2001 From: jgsuess Date: Mon, 4 Aug 2025 12:29:26 +0000 Subject: [PATCH] deploy: b10d9a8cecb20165b91803f6b363103301de0984 --- .github/ISSUE_TEMPLATE/bug_report.md | 38 - .github/ISSUE_TEMPLATE/feature_request.md | 20 - .github/ct/chart-schema.yaml | 23 - .github/ct/config.yaml | 15 - .github/workflows/build-images.yaml | 84 - .github/workflows/chart-release.yaml | 41 - .github/workflows/chart-test.yaml | 73 - .github/workflows/docker-publish.yml | 58 - .gitignore | 6 - .nojekyll | 0 .project | 23 - .settings/.jsdtscope | 7 - ...rg.eclipse.wst.jsdt.ui.superType.container | 1 - .../org.eclipse.wst.jsdt.ui.superType.name | 1 - Build and Run for first time.bat | 211 - DockerCommands.MD | 26 - Dockerfile | 57 - LICENSE.md | 3 - README.html | 855 +++ README.md | 661 --- ...NTEGRATION FHIRVINE as Moduel in FLARE.md | 151 - Starting | 1 - app.py | 3096 ---------- assets/css/style.css | 137 + assets/images/checker.png | Bin 0 -> 108 bytes assets/js/scale.fix.js | 20 + charts/.gitignore | 1 - charts/fhirflare-ig-toolkit/.gitignore | 1 - charts/fhirflare-ig-toolkit/Chart.yaml | 16 - .../templates/_helpers.tpl | 152 - .../templates/deployment.yaml | 91 - .../templates/ingress.yaml | 36 - .../templates/service.yaml | 18 - .../templates/tests/test-endpoints.yaml | 41 - charts/fhirflare-ig-toolkit/values.yaml | 89 - charts/install.sh | 23 - docker-compose.yml | 22 - docker-compose/all-in-one/docker-compose.yml | 22 - docker-compose/all-in-one/down.sh | 5 - docker-compose/all-in-one/up.sh | 5 - docker-compose/lite/local/application.yaml | 18 - docker-compose/lite/local/docker-compose.yml | 50 - docker-compose/lite/local/down.sh | 5 - docker-compose/lite/local/readme.md | 19 - docker-compose/lite/local/up.sh | 5 - docker-compose/lite/remote/docker-compose.yml | 25 - docker-compose/lite/remote/down.sh | 5 - docker-compose/lite/remote/readme.md | 19 - docker-compose/lite/remote/up.sh | 5 - docker/Dockerfile | 66 - docker/build-docker.sh | 7 - forms.py | 309 - hapi-fhir-Setup/README.md | 111 - .../target/classes/application.yaml | 342 -- index.html | 92 + index.yaml | 24 - migrations/README | 1 - migrations/__pycache__/env.cpython-312.pyc | Bin 4493 -> 0 bytes migrations/alembic.ini | 50 - migrations/env.py | 113 - migrations/script.py.mako | 24 - package.py | 123 - requirements.txt | 14 - services.py | 4985 ----------------- setup_linux.sh | 233 - supervisord.conf | 36 - templates/_flash_messages.html | 24 - templates/_form_helpers.html | 42 - templates/_fsh_output.html | 60 - templates/_search_results_table.html | 113 - templates/about.html | 66 - templates/base.html | 905 --- templates/config_hapi.html | 234 - templates/cp_downloaded_igs.html | 168 - templates/cp_push_igs.html | 501 -- templates/cp_view_processed_ig.html | 1338 ----- templates/fhir_ui.html | 473 -- templates/fhir_ui_operations.html | 1957 ------- templates/fsh_converter.html | 225 - templates/import_ig.html | 145 - templates/index.html | 168 - templates/manual_import_ig.html | 194 - templates/package.canonicals.html | 7 - templates/package.dependents.html | 31 - templates/package.logs.html | 22 - templates/package.problems.html | 16 - templates/package_details.html | 124 - templates/retrieve_split_data.html | 690 --- templates/search_and_import_ig.html | 461 -- templates/upload_test_data.html | 302 - templates/validate_sample.html | 376 -- tests/test_app.py | 648 --- .../upload_samples/Bundle-transaction-ex.json | 431 -- tests/upload_samples/PHCDI.r4-0.1.0.tgz | Bin 593179 -> 0 bytes .../example.fhir.ph.core.r4-0.1.0.tgz | Bin 158567 -> 0 bytes tests/upload_samples/validation.log | 25 - 96 files changed, 1104 insertions(+), 21453 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 .github/ct/chart-schema.yaml delete mode 100644 .github/ct/config.yaml delete mode 100644 .github/workflows/build-images.yaml delete mode 100644 .github/workflows/chart-release.yaml delete mode 100644 .github/workflows/chart-test.yaml delete mode 100644 .github/workflows/docker-publish.yml delete mode 100644 .gitignore create mode 100644 .nojekyll delete mode 100644 .project delete mode 100644 .settings/.jsdtscope delete mode 100644 .settings/org.eclipse.wst.jsdt.ui.superType.container delete mode 100644 .settings/org.eclipse.wst.jsdt.ui.superType.name delete mode 100644 Build and Run for first time.bat delete mode 100644 DockerCommands.MD delete mode 100644 Dockerfile delete mode 100644 LICENSE.md create mode 100644 README.html delete mode 100644 README.md delete mode 100644 README_INTEGRATION FHIRVINE as Moduel in FLARE.md delete mode 100644 Starting delete mode 100644 app.py create mode 100644 assets/css/style.css create mode 100644 assets/images/checker.png create mode 100644 assets/js/scale.fix.js delete mode 100644 charts/.gitignore delete mode 100644 charts/fhirflare-ig-toolkit/.gitignore delete mode 100644 charts/fhirflare-ig-toolkit/Chart.yaml delete mode 100644 charts/fhirflare-ig-toolkit/templates/_helpers.tpl delete mode 100644 charts/fhirflare-ig-toolkit/templates/deployment.yaml delete mode 100644 charts/fhirflare-ig-toolkit/templates/ingress.yaml delete mode 100644 charts/fhirflare-ig-toolkit/templates/service.yaml delete mode 100644 charts/fhirflare-ig-toolkit/templates/tests/test-endpoints.yaml delete mode 100644 charts/fhirflare-ig-toolkit/values.yaml delete mode 100755 charts/install.sh delete mode 100644 docker-compose.yml delete mode 100644 docker-compose/all-in-one/docker-compose.yml delete mode 100755 docker-compose/all-in-one/down.sh delete mode 100755 docker-compose/all-in-one/up.sh delete mode 100644 docker-compose/lite/local/application.yaml delete mode 100644 docker-compose/lite/local/docker-compose.yml delete mode 100755 docker-compose/lite/local/down.sh delete mode 100644 docker-compose/lite/local/readme.md delete mode 100755 docker-compose/lite/local/up.sh delete mode 100644 docker-compose/lite/remote/docker-compose.yml delete mode 100755 docker-compose/lite/remote/down.sh delete mode 100644 docker-compose/lite/remote/readme.md delete mode 100755 docker-compose/lite/remote/up.sh delete mode 100644 docker/Dockerfile delete mode 100755 docker/build-docker.sh delete mode 100644 forms.py delete mode 100644 hapi-fhir-Setup/README.md delete mode 100644 hapi-fhir-Setup/target/classes/application.yaml create mode 100644 index.html delete mode 100644 index.yaml delete mode 100644 migrations/README delete mode 100644 migrations/__pycache__/env.cpython-312.pyc delete mode 100644 migrations/alembic.ini delete mode 100644 migrations/env.py delete mode 100644 migrations/script.py.mako delete mode 100644 package.py delete mode 100644 requirements.txt delete mode 100644 services.py delete mode 100644 setup_linux.sh delete mode 100644 supervisord.conf delete mode 100644 templates/_flash_messages.html delete mode 100644 templates/_form_helpers.html delete mode 100644 templates/_fsh_output.html delete mode 100644 templates/_search_results_table.html delete mode 100644 templates/about.html delete mode 100644 templates/base.html delete mode 100644 templates/config_hapi.html delete mode 100644 templates/cp_downloaded_igs.html delete mode 100644 templates/cp_push_igs.html delete mode 100644 templates/cp_view_processed_ig.html delete mode 100644 templates/fhir_ui.html delete mode 100644 templates/fhir_ui_operations.html delete mode 100644 templates/fsh_converter.html delete mode 100644 templates/import_ig.html delete mode 100644 templates/index.html delete mode 100644 templates/manual_import_ig.html delete mode 100644 templates/package.canonicals.html delete mode 100644 templates/package.dependents.html delete mode 100644 templates/package.logs.html delete mode 100644 templates/package.problems.html delete mode 100644 templates/package_details.html delete mode 100644 templates/retrieve_split_data.html delete mode 100644 templates/search_and_import_ig.html delete mode 100644 templates/upload_test_data.html delete mode 100644 templates/validate_sample.html delete mode 100644 tests/test_app.py delete mode 100644 tests/upload_samples/Bundle-transaction-ex.json delete mode 100644 tests/upload_samples/PHCDI.r4-0.1.0.tgz delete mode 100644 tests/upload_samples/example.fhir.ph.core.r4-0.1.0.tgz delete mode 100644 tests/upload_samples/validation.log diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index dd84ea7..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] - -**Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index bbcbbe7..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/ct/chart-schema.yaml b/.github/ct/chart-schema.yaml deleted file mode 100644 index 7b3fb0a..0000000 --- a/.github/ct/chart-schema.yaml +++ /dev/null @@ -1,23 +0,0 @@ -name: str() -home: str() -version: str() -apiVersion: str() -appVersion: any(str(), num(), required=False) -type: str() -dependencies: any(required=False) -description: str() -keywords: list(str(), required=False) -sources: list(str(), required=False) -maintainers: list(include('maintainer'), required=False) -icon: str(required=False) -engine: str(required=False) -condition: str(required=False) -tags: str(required=False) -deprecated: bool(required=False) -kubeVersion: str(required=False) -annotations: map(str(), str(), required=False) ---- -maintainer: - name: str() - email: str(required=False) - url: str(required=False) diff --git a/.github/ct/config.yaml b/.github/ct/config.yaml deleted file mode 100644 index 3721957..0000000 --- a/.github/ct/config.yaml +++ /dev/null @@ -1,15 +0,0 @@ -debug: true -remote: origin -chart-yaml-schema: .github/ct/chart-schema.yaml -validate-maintainers: false -validate-chart-schema: true -validate-yaml: true -check-version-increment: true -chart-dirs: - - charts -helm-extra-args: --timeout 300s -upgrade: true -skip-missing-values: true -release-label: release -release-name-template: "helm-v{{ .Version }}" -target-branch: master diff --git a/.github/workflows/build-images.yaml b/.github/workflows/build-images.yaml deleted file mode 100644 index 542bd64..0000000 --- a/.github/workflows/build-images.yaml +++ /dev/null @@ -1,84 +0,0 @@ -name: Build Container Images - -on: - push: - tags: - - "image/v*" - paths-ignore: - - "charts/**" - pull_request: - branches: [master] - paths-ignore: - - "charts/**" -env: - IMAGES: docker.io/hapiproject/hapi - PLATFORMS: linux/amd64,linux/arm64/v8 - -jobs: - build: - name: Build - runs-on: ubuntu-22.04 - steps: - - name: Container meta for default (distroless) image - id: docker_meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.IMAGES }} - tags: | - type=match,pattern=image/(.*),group=1,enable=${{github.event_name != 'pull_request'}} - - - - name: Container meta for tomcat image - id: docker_tomcat_meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.IMAGES }} - tags: | - type=match,pattern=image/(.*),group=1,enable=${{github.event_name != 'pull_request'}} - flavor: | - suffix=-tomcat,onlatest=true - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to DockerHub - uses: docker/login-action@v3 - if: github.event_name != 'pull_request' - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Cache Docker layers - uses: actions/cache@v3 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx- - - - name: Build and push default (distroless) image - id: docker_build - uses: docker/build-push-action@v5 - with: - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.docker_meta.outputs.tags }} - labels: ${{ steps.docker_meta.outputs.labels }} - platforms: ${{ env.PLATFORMS }} - target: default - - - name: Build and push tomcat image - id: docker_build_tomcat - uses: docker/build-push-action@v5 - with: - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.docker_tomcat_meta.outputs.tags }} - labels: ${{ steps.docker_tomcat_meta.outputs.labels }} - platforms: ${{ env.PLATFORMS }} - target: tomcat diff --git a/.github/workflows/chart-release.yaml b/.github/workflows/chart-release.yaml deleted file mode 100644 index 39c8f0a..0000000 --- a/.github/workflows/chart-release.yaml +++ /dev/null @@ -1,41 +0,0 @@ -name: Release Charts - -on: - push: - branches: - - main - paths: - - "charts/**" - -jobs: - release: - runs-on: ubuntu-22.04 - steps: - - name: Add workspace as safe directory - run: | - git config --global --add safe.directory /__w/FHIRFLARE-IG-Toolkit/FHIRFLARE-IG-Toolkit - - - name: Checkout - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - with: - fetch-depth: 0 - - - name: Configure Git - run: | - git config user.name "$GITHUB_ACTOR" - git config user.email "$GITHUB_ACTOR@users.noreply.github.com" - - - name: Update dependencies - run: find charts/ ! -path charts/ -maxdepth 1 -type d -exec helm dependency update {} \; - - - name: Add Helm Repositories - run: | - helm repo add hapifhir https://hapifhir.github.io/hapi-fhir-jpaserver-starter/ - helm repo update - - - name: Run chart-releaser - uses: helm/chart-releaser-action@be16258da8010256c6e82849661221415f031968 # v1.5.0 - with: - config: .github/ct/config.yaml - env: - CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/chart-test.yaml b/.github/workflows/chart-test.yaml deleted file mode 100644 index ef83c32..0000000 --- a/.github/workflows/chart-test.yaml +++ /dev/null @@ -1,73 +0,0 @@ -name: Lint and Test Charts - -on: - pull_request: - branches: - - master - paths: - - "charts/**" - -jobs: - lint: - runs-on: ubuntu-22.04 - container: quay.io/helmpack/chart-testing:v3.11.0@sha256:f2fd21d30b64411105c7eafb1862783236a219d29f2292219a09fe94ca78ad2a - steps: - - name: Install helm-docs - working-directory: /tmp - env: - HELM_DOCS_URL: https://github.com/norwoodj/helm-docs/releases/download/v1.14.2/helm-docs_1.14.2_Linux_x86_64.tar.gz - run: | - curl -LSs $HELM_DOCS_URL | tar xz && \ - mv ./helm-docs /usr/local/bin/helm-docs && \ - chmod +x /usr/local/bin/helm-docs && \ - helm-docs --version - - - name: Add workspace as safe directory - run: | - git config --global --add safe.directory /__w/hapi-fhir-jpaserver-starter/hapi-fhir-jpaserver-starter - - - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - with: - fetch-depth: 0 - - - name: Check if documentation is up-to-date - run: helm-docs && git diff --exit-code HEAD - - - name: Run chart-testing (lint) - run: ct lint --config .github/ct/config.yaml - - test: - runs-on: ubuntu-22.04 - strategy: - matrix: - k8s-version: [1.30.8, 1.31.4, 1.32.0] - needs: - - lint - steps: - - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - with: - fetch-depth: 0 - - - name: Set up chart-testing - uses: helm/chart-testing-action@e6669bcd63d7cb57cb4380c33043eebe5d111992 # v2.6.1 - - - name: Run chart-testing (list-changed) - id: list-changed - run: | - changed=$(ct list-changed --config .github/ct/config.yaml) - if [[ -n "$changed" ]]; then - echo "::set-output name=changed::true" - fi - - - name: Create k8s Kind Cluster - uses: helm/kind-action@dda0770415bac9fc20092cacbc54aa298604d140 # v1.8.0 - if: ${{ steps.list-changed.outputs.changed == 'true' }} - with: - cluster_name: kind-cluster-k8s-${{ matrix.k8s-version }} - node_image: kindest/node:v${{ matrix.k8s-version }} - - - name: Run chart-testing (install) - run: ct install --config .github/ct/config.yaml - if: ${{ steps.list-changed.outputs.changed == 'true' }} diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml deleted file mode 100644 index e55d2d1..0000000 --- a/.github/workflows/docker-publish.yml +++ /dev/null @@ -1,58 +0,0 @@ -# This workflow builds and pushes a multi-architecture Docker image to GitHub Container Registry (ghcr.io). -# -# The Docker meta step is required because GitHub repository names can contain uppercase letters, but Docker image tags must be lowercase. -# The docker/metadata-action@v5 normalizes the repository name to lowercase, ensuring the build and push steps use a valid image tag. -# -# This workflow builds for both AMD64 and ARM64 architectures using Docker Buildx and QEMU emulation. - -name: Build and Push Docker image - -on: - push: - branches: - - main - - '*' # This will run the workflow on any branch - workflow_dispatch: # This enables manual triggering - -jobs: - build-and-push: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: ghcr.io/${{ github.repository }} - - - name: Set normalized image name - run: | - if [[ "${{ github.ref_name }}" == "main" ]]; then - echo "IMAGE_NAME=$(echo ${{ steps.meta.outputs.tags }} | sed 's/:main/:latest/')" >> $GITHUB_ENV - else - echo "IMAGE_NAME=${{ steps.meta.outputs.tags }}" >> $GITHUB_ENV - fi - - - name: Build and push multi-architecture Docker image - uses: docker/build-push-action@v5 - with: - context: . - file: ./docker/Dockerfile - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ env.IMAGE_NAME }} \ No newline at end of file diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 5b62952..0000000 --- a/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -/instance/ -/logs/ -/.pydevproject -/__pycache__/ -/myenv/ -/tmp/ diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/.project b/.project deleted file mode 100644 index f6266a1..0000000 --- a/.project +++ /dev/null @@ -1,23 +0,0 @@ - - - FHIRFLARE-IG-Toolkit - - - - - - org.python.pydev.PyDevBuilder - - - - - org.eclipse.wst.validation.validationbuilder - - - - - - org.eclipse.wst.jsdt.core.jsNature - org.python.pydev.pythonNature - - diff --git a/.settings/.jsdtscope b/.settings/.jsdtscope deleted file mode 100644 index cca691f..0000000 --- a/.settings/.jsdtscope +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/.settings/org.eclipse.wst.jsdt.ui.superType.container b/.settings/org.eclipse.wst.jsdt.ui.superType.container deleted file mode 100644 index 49c8cd4..0000000 --- a/.settings/org.eclipse.wst.jsdt.ui.superType.container +++ /dev/null @@ -1 +0,0 @@ -org.eclipse.wst.jsdt.launching.JRE_CONTAINER \ No newline at end of file diff --git a/.settings/org.eclipse.wst.jsdt.ui.superType.name b/.settings/org.eclipse.wst.jsdt.ui.superType.name deleted file mode 100644 index 11006e2..0000000 --- a/.settings/org.eclipse.wst.jsdt.ui.superType.name +++ /dev/null @@ -1 +0,0 @@ -Global \ No newline at end of file diff --git a/Build and Run for first time.bat b/Build and Run for first time.bat deleted file mode 100644 index 9179731..0000000 --- a/Build and Run for first time.bat +++ /dev/null @@ -1,211 +0,0 @@ -@echo off -setlocal enabledelayedexpansion - -REM --- Configuration --- -set REPO_URL=https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git -set CLONE_DIR=hapi-fhir-jpaserver -set SOURCE_CONFIG_DIR=hapi-fhir-setup -set CONFIG_FILE=application.yaml - -REM --- Define Paths --- -set SOURCE_CONFIG_PATH=..\%SOURCE_CONFIG_DIR%\target\classes\%CONFIG_FILE% -set DEST_CONFIG_PATH=%CLONE_DIR%\target\classes\%CONFIG_FILE% - -REM === CORRECTED: Prompt for Version === -:GetModeChoice -SET "APP_MODE=" REM Clear the variable first -echo Select Installation Mode: -echo 1. Standalone (Includes local HAPI FHIR Server - Requires Git & Maven) -echo 2. Lite (Excludes local HAPI FHIR Server - No Git/Maven needed) -CHOICE /C 12 /N /M "Enter your choice (1 or 2):" - -IF ERRORLEVEL 2 ( - SET APP_MODE=lite - goto :ModeSet -) -IF ERRORLEVEL 1 ( - SET APP_MODE=standalone - goto :ModeSet -) -REM If somehow neither was chosen (e.g., Ctrl+C), loop back -echo Invalid input. Please try again. -goto :GetModeChoice - -:ModeSet -IF "%APP_MODE%"=="" ( - echo Invalid choice detected after checks. Exiting. - goto :eof -) -echo Selected Mode: %APP_MODE% -echo. -REM === END CORRECTION === - - -REM === Conditionally Execute HAPI Setup === -IF "%APP_MODE%"=="standalone" ( - echo Running Standalone setup including HAPI FHIR... - echo. - - REM --- Step 0: Clean up previous clone (optional) --- - echo Checking for existing directory: %CLONE_DIR% - if exist "%CLONE_DIR%" ( - echo Found existing directory, removing it... - rmdir /s /q "%CLONE_DIR%" - if errorlevel 1 ( - echo ERROR: Failed to remove existing directory: %CLONE_DIR% - goto :error - ) - echo Existing directory removed. - ) else ( - echo Directory does not exist, proceeding with clone. - ) - echo. - - REM --- Step 1: Clone the HAPI FHIR server repository --- - echo Cloning repository: %REPO_URL% into %CLONE_DIR%... - git clone "%REPO_URL%" "%CLONE_DIR%" - if errorlevel 1 ( - echo ERROR: Failed to clone repository. Check Git installation and network connection. - goto :error - ) - echo Repository cloned successfully. - echo. - - REM --- Step 2: Navigate into the cloned directory --- - echo Changing directory to %CLONE_DIR%... - cd "%CLONE_DIR%" - if errorlevel 1 ( - echo ERROR: Failed to change directory to %CLONE_DIR%. - goto :error - ) - echo Current directory: %CD% - echo. - - REM --- Step 3: Build the HAPI server using Maven --- - echo ===> "Starting Maven build (Step 3)..."" - cmd /c "mvn clean package -DskipTests=true -Pboot" - echo ===> Maven command finished. Checking error level... - if errorlevel 1 ( - echo ERROR: Maven build failed or cmd /c failed - cd .. - goto :error - ) - echo Maven build completed successfully. ErrorLevel: %errorlevel% - echo. - - REM --- Step 4: Copy the configuration file --- - echo ===> "Starting file copy (Step 4)..." - echo Copying configuration file... - echo Source: %SOURCE_CONFIG_PATH% - echo Destination: target\classes\%CONFIG_FILE% - xcopy "%SOURCE_CONFIG_PATH%" "target\classes\" /Y /I - echo ===> xcopy command finished. Checking error level... - if errorlevel 1 ( - echo WARNING: Failed to copy configuration file. Check if the source file exists. - echo The script will continue, but the server might use default configuration. - ) else ( - echo Configuration file copied successfully. ErrorLevel: %errorlevel% - ) - echo. - - REM --- Step 5: Navigate back to the parent directory --- - echo ===> "Changing directory back (Step 5)..." - cd .. - if errorlevel 1 ( - echo ERROR: Failed to change back to the parent directory. ErrorLevel: %errorlevel% - goto :error - ) - echo Current directory: %CD% - echo. - -) ELSE ( - echo Running Lite setup, skipping HAPI FHIR build... - REM Ensure the hapi-fhir-jpaserver directory doesn't exist or is empty if Lite mode is chosen after a standalone attempt - if exist "%CLONE_DIR%" ( - echo Found existing HAPI directory in Lite mode. Removing it to avoid build issues... - rmdir /s /q "%CLONE_DIR%" - ) - REM Create empty target directories expected by Dockerfile COPY, even if not used - mkdir "%CLONE_DIR%\target\classes" 2> nul - mkdir "%CLONE_DIR%\custom" 2> nul - REM Create a placeholder empty WAR file to satisfy Dockerfile COPY - echo. > "%CLONE_DIR%\target\ROOT.war" - echo. > "%CLONE_DIR%\target\classes\application.yaml" - echo Placeholder files created for Lite mode build. - echo. -) - -REM === Modify docker-compose.yml to set APP_MODE === -echo Updating docker-compose.yml with APP_MODE=%APP_MODE%... -( - echo version: '3.8' - echo services: - echo fhirflare: - echo build: - echo context: . - echo dockerfile: Dockerfile - echo ports: - echo - "5000:5000" - echo - "8080:8080" # Keep port exposed, even if Tomcat isn't running useful stuff in Lite - echo volumes: - echo - ./instance:/app/instance - echo - ./static/uploads:/app/static/uploads - echo - ./instance/hapi-h2-data/:/app/h2-data # Keep volume mounts consistent - echo - ./logs:/app/logs - echo environment: - echo - FLASK_APP=app.py - echo - FLASK_ENV=development - echo - NODE_PATH=/usr/lib/node_modules - echo - APP_MODE=%APP_MODE% - echo - APP_BASE_URL=http://localhost:5000 - echo - HAPI_FHIR_URL=http://localhost:8080/fhir - echo command: supervisord -c /etc/supervisord.conf -) > docker-compose.yml.tmp - -REM Check if docker-compose.yml.tmp was created successfully -if not exist docker-compose.yml.tmp ( - echo ERROR: Failed to create temporary docker-compose file. - goto :error -) - -REM Replace the original docker-compose.yml -del docker-compose.yml /Q > nul 2>&1 -ren docker-compose.yml.tmp docker-compose.yml -echo docker-compose.yml updated successfully. -echo. - -REM --- Step 6: Build Docker images --- -echo ===> Starting Docker build (Step 6)... -docker-compose build --no-cache -if errorlevel 1 ( - echo ERROR: Docker Compose build failed. Check Docker installation and docker-compose.yml file. ErrorLevel: %errorlevel% - goto :error -) -echo Docker images built successfully. ErrorLevel: %errorlevel% -echo. - -REM --- Step 7: Start Docker containers --- -echo ===> Starting Docker containers (Step 7)... -docker-compose up -d -if errorlevel 1 ( - echo ERROR: Docker Compose up failed. Check Docker installation and container configurations. ErrorLevel: %errorlevel% - goto :error -) -echo Docker containers started successfully. ErrorLevel: %errorlevel% -echo. - -echo ==================================== -echo Script finished successfully! (Mode: %APP_MODE%) -echo ==================================== -goto :eof - -:error -echo ------------------------------------ -echo An error occurred. Script aborted. -echo ------------------------------------ -pause -exit /b 1 - -:eof -echo Script execution finished. -pause \ No newline at end of file diff --git a/DockerCommands.MD b/DockerCommands.MD deleted file mode 100644 index 1dba080..0000000 --- a/DockerCommands.MD +++ /dev/null @@ -1,26 +0,0 @@ -Docker Commands.MD - - - -to pull and clone: -git clone https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git hapi-fhir-jpaserver - -to build: -mvn clean package -DskipTests=true -Pboot - -to run: -java -jar target/ROOT.war - - - - -docker-compose build --no-cache -docker-compose up -d - - - - - -cp :/app/PATH/Filename.ext . - . copies to the root folder you ran it from - -docker exec -it bash - to get a bash - session in the container - \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index f75083b..0000000 --- a/Dockerfile +++ /dev/null @@ -1,57 +0,0 @@ -# Base image with Python and Java -FROM tomcat:10.1-jdk17 - -# Install build dependencies, Node.js 18, and coreutils (for stdbuf) -RUN apt-get update && apt-get install -y --no-install-recommends \ - python3 python3-pip python3-venv curl coreutils \ - && curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \ - && apt-get install -y --no-install-recommends nodejs \ - && rm -rf /var/lib/apt/lists/* - -# Install specific versions of GoFSH and SUSHI -# REMOVED pip install fhirpath from this line -RUN npm install -g gofsh fsh-sushi - -# Set up Python environment -WORKDIR /app -RUN python3 -m venv /app/venv -ENV PATH="/app/venv/bin:$PATH" - -# ADDED: Uninstall old fhirpath just in case it's in requirements.txt -RUN pip uninstall -y fhirpath || true -# ADDED: Install the new fhirpathpy library -RUN pip install --no-cache-dir fhirpathpy - -# Copy Flask files -COPY requirements.txt . -# Install requirements (including Pydantic - check version compatibility if needed) -RUN pip install --no-cache-dir -r requirements.txt -COPY app.py . -COPY services.py . -COPY forms.py . -COPY package.py . -COPY templates/ templates/ -COPY static/ static/ -COPY tests/ tests/ - -# Ensure /tmp, /app/h2-data, /app/static/uploads, and /app/logs are writable -RUN mkdir -p /tmp /app/h2-data /app/static/uploads /app/logs && chmod 777 /tmp /app/h2-data /app/static/uploads /app/logs - -# Copy pre-built HAPI WAR and configuration -COPY hapi-fhir-jpaserver/target/ROOT.war /usr/local/tomcat/webapps/ -COPY hapi-fhir-jpaserver/target/classes/application.yaml /usr/local/tomcat/conf/ -COPY hapi-fhir-jpaserver/target/classes/application.yaml /app/config/application.yaml -COPY hapi-fhir-jpaserver/target/classes/application.yaml /usr/local/tomcat/webapps/app/config/application.yaml -COPY hapi-fhir-jpaserver/custom/ /usr/local/tomcat/webapps/custom/ - -# Install supervisord -RUN pip install supervisor - -# Configure supervisord -COPY supervisord.conf /etc/supervisord.conf - -# Expose ports -EXPOSE 5000 8080 - -# Start supervisord -CMD ["supervisord", "-c", "/etc/supervisord.conf"] \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index 67e06e2..0000000 --- a/LICENSE.md +++ /dev/null @@ -1,3 +0,0 @@ -# License - -This project, FHIRFLARE-IG-Toolkit, is licensed under the Apache License, Version 2.0. diff --git a/README.html b/README.html new file mode 100644 index 0000000..e5d1784 --- /dev/null +++ b/README.html @@ -0,0 +1,855 @@ + + + + + + + +FHIRFLARE IG Toolkit | Helm chart for deploying the fhirflare-ig-toolkit application + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

FHIRFLARE IG Toolkit

+ +

Helm chart for deploying the fhirflare-ig-toolkit application

+ +

View the Project on GitHub

+ +
+
+ +

FHIRFLARE IG Toolkit

+ +

FHIRFLARE Logo

+ +

Overview

+ +

The FHIRFLARE IG Toolkit is a Flask-based web application designed to streamline the management, processing, validation, and deployment of FHIR Implementation Guides (IGs) and test data. It offers a user-friendly interface for importing IG packages, extracting metadata, validating FHIR resources or bundles, pushing IGs to FHIR servers, converting FHIR resources to FHIR Shorthand (FSH), uploading complex test data sets with dependency management, and retrieving/splitting FHIR bundles. The toolkit includes live consoles for real-time feedback, making it an essential tool for FHIR developers and implementers.

+ +

The application can run in two modes:

+ +
    +
  • Standalone: Includes a Dockerized Flask frontend, SQLite database, and an embedded HAPI FHIR server for local validation and interaction.
  • +
  • Lite: Includes only the Dockerized Flask frontend and SQLite database, excluding the local HAPI FHIR server. Requires connection to external FHIR servers for certain features.
  • +
+ +

Installation Modes (Lite vs. Standalone)

+ +

This toolkit offers two primary installation modes to suit different needs:

+ +
    +
  • Standalone Version: +
      +
    • Includes the full FHIRFLARE Toolkit application and an embedded HAPI FHIR server running locally within the Docker environment.
    • +
    • Allows for local FHIR resource validation using HAPI FHIR’s capabilities.
    • +
    • Enables the “Use Local HAPI” option in the FHIR API Explorer and FHIR UI Operations pages, proxying requests to the internal HAPI server (http://localhost:8080/fhir).
    • +
    • Requires Git and Maven during the initial build process (via the .bat script or manual steps) to prepare the HAPI FHIR server.
    • +
    • Ideal for users who want a self-contained environment for development and testing or who don’t have readily available external FHIR servers.
    • +
    +
  • +
  • Lite Version: +
      +
    • Includes the FHIRFLARE Toolkit application without the embedded HAPI FHIR server.
    • +
    • Requires users to provide URLs for external FHIR servers when using features like the FHIR API Explorer and FHIR UI Operations pages. The “Use Local HAPI” option will be disabled in the UI.
    • +
    • Resource validation relies solely on local checks against downloaded StructureDefinitions, which may be less comprehensive than HAPI FHIR’s validation (e.g., for terminology bindings or complex invariants).
    • +
    • Does not require Git or Maven for setup if using the .bat script or running the pre-built Docker image.
    • +
    • Ideal for users who primarily want to use the IG management, processing, and FSH conversion features, or who will always connect to existing external FHIR servers.
    • +
    +
  • +
+ +

Features

+ +
    +
  • Import IGs: Download FHIR IG packages and dependencies from a package registry, supporting flexible version formats (e.g., 1.2.3, 1.1.0-preview, current) and dependency pulling modes (Recursive, Patch Canonical, Tree Shaking).
  • +
  • Enhanced Package Search and Import: +
      +
    • Interactive page (/search-and-import) to search for FHIR IG packages from configured registries.
    • +
    • Displays package details, version history, dependencies, and dependents.
    • +
    • Utilizes a local database cache (CachedPackage) for faster subsequent searches.
    • +
    • Background task to refresh the package cache from registries (/api/refresh-cache-task).
    • +
    • Direct import from search results.
    • +
    +
  • +
  • Manage IGs: View, process, unload, or delete downloaded IGs, with duplicate detection and resolution.
  • +
  • Process IGs: Extract resource types, profiles, must-support elements, examples, and profile relationships (structuredefinition-compliesWithProfile and structuredefinition-imposeProfile).
  • +
  • Validate FHIR Resources/Bundles: Validate single FHIR resources or bundles against selected IGs, with detailed error and warning reports (alpha feature). Note: Lite version uses local SD checks only.
  • +
  • Push IGs: Upload IG resources (and optionally dependencies) to a target FHIR server. Features include: +
      +
    • Real-time console output.
    • +
    • Authentication support (Bearer Token).
    • +
    • Filtering by resource type or specific files to skip.
    • +
    • Semantic comparison to skip uploading identical resources (override with Force Upload option).
    • +
    • Correct handling of canonical resources (searching by URL/version before deciding POST/PUT).
    • +
    • Dry run mode for simulation.
    • +
    • Verbose logging option.
    • +
    +
  • +
  • Upload Test Data: Upload complex sets of test data (individual JSON/XML files or ZIP archives) to a target FHIR server. Features include: +
      +
    • Robust parsing of JSON and XML (using fhir.resources library when available).
    • +
    • Automatic dependency analysis based on resource references within the uploaded set.
    • +
    • Topological sorting to ensure resources are uploaded in the correct order.
    • +
    • Cycle detection in dependencies.
    • +
    • Choice of individual resource uploads or a single transaction bundle.
    • +
    • Optional Pre-Upload Validation: Validate resources against a selected profile package before uploading.
    • +
    • Optional Conditional Uploads (Individual Mode): Check resource existence (GET) and use conditional If-Match headers for updates (PUT) or create resources (PUT/POST). Falls back to simple PUT if unchecked.
    • +
    • Configurable error handling (stop on first error or continue).
    • +
    • Authentication support (Bearer Token).
    • +
    • Streaming progress log via the UI.
    • +
    • Handles large numbers of files using a custom form parser.
    • +
    +
  • +
  • Profile Relationships: Display and validate compliesWithProfile and imposeProfile extensions in the UI (configurable).
  • +
  • FSH Converter: Convert FHIR JSON/XML resources to FHIR Shorthand (FSH) using GoFSH, with advanced options (Package context, Output styles, Log levels, FHIR versions, Fishing Trip, Dependencies, Indentation, Meta Profile handling, Alias File, No Alias). Includes a waiting spinner.
  • +
  • Retrieve and Split Bundles: +
      +
    • Retrieve specified resource types as bundles from a FHIR server.
    • +
    • Optionally fetch referenced resources, either individually or as full bundles for each referenced type.
    • +
    • Split uploaded ZIP files containing bundles into individual resource JSON files.
    • +
    • Download retrieved/split resources as a ZIP archive.
    • +
    • Streaming progress log via the UI for retrieval operations.
    • +
    +
  • +
  • FHIR Interaction UIs: Explore FHIR server capabilities and interact with resources using the “FHIR API Explorer” (simple GET/POST/PUT/DELETE) and “FHIR UI Operations” (Swagger-like interface based on CapabilityStatement). Note: Lite version requires custom server URLs.
  • +
  • HAPI FHIR Configuration (Standalone Mode): +
      +
    • A dedicated page (/config-hapi) to view and edit the application.yaml configuration for the embedded HAPI FHIR server.
    • +
    • Allows modification of HAPI FHIR properties directly from the UI.
    • +
    • Option to restart the HAPI FHIR server (Tomcat) to apply changes.
    • +
    +
  • +
  • API Support: RESTful API endpoints for importing, pushing, retrieving metadata, validating, uploading test data, and retrieving/splitting bundles.
  • +
  • Live Console: Real-time logs for push, validation, upload test data, FSH conversion, and bundle retrieval operations.
  • +
  • Configurable Behavior: Control validation modes, display options via app.config.
  • +
  • Theming: Supports light and dark modes.
  • +
+ +

Technology Stack

+ +
    +
  • Python 3.12+, Flask 2.3.3, Flask-SQLAlchemy 3.0.5, Flask-WTF 1.2.1
  • +
  • Jinja2, Bootstrap 5.3.3, JavaScript (ES6), Lottie-Web 5.12.2
  • +
  • SQLite
  • +
  • Docker, Docker Compose, Supervisor
  • +
  • Node.js 18+ (for GoFSH/SUSHI), GoFSH, SUSHI
  • +
  • HAPI FHIR (Standalone version only)
  • +
  • Requests 2.31.0, Tarfile, Logging, Werkzeug
  • +
  • fhir.resources (optional, for robust XML parsing)
  • +
+ +

Prerequisites

+ +
    +
  • Docker: Required for containerized deployment (both versions).
  • +
  • Git & Maven: Required only for building the Standalone version from source using the .bat script or manual steps. Not required for the Lite version build or for running pre-built Docker Hub images.
  • +
  • Windows: Required if using the .bat scripts.
  • +
+ +

Setup Instructions

+ +

Running Pre-built Images (General Users)

+ +

This is the easiest way to get started without needing Git or Maven. Choose the version you need:

+ +

Lite Version (No local HAPI FHIR):

+ +
# Pull the latest Lite image
+docker pull ghcr.io/sudo-jhare/fhirflare-ig-toolkit-lite:latest
+
+# Run the Lite version (maps port 5000 for the UI)
+# You'll need to create local directories for persistent data first:
+# mkdir instance logs static static/uploads instance/hapi-h2-data
+docker run -d \
+  -p 5000:5000 \
+  -v ./instance:/app/instance \
+  -v ./static/uploads:/app/static/uploads \
+  -v ./instance/hapi-h2-data:/app/h2-data \
+  -v ./logs:/app/logs \
+  --name fhirflare-lite \
+  ghcr.io/sudo-jhare/fhirflare-ig-toolkit-lite:latest
+Standalone Version (Includes local HAPI FHIR):
+
+

Bash

+ +
# Pull the latest Standalone image
+docker pull ghcr.io/sudo-jhare/fhirflare-ig-toolkit-standalone:latest
+
+# Run the Standalone version (maps ports 5000 and 8080)
+# You'll need to create local directories for persistent data first:
+# mkdir instance logs static static/uploads instance/hapi-h2-data
+docker run -d \
+  -p 5000:5000 \
+  -p 8080:8080 \
+  -v ./instance:/app/instance \
+  -v ./static/uploads:/app/static/uploads \
+  -v ./instance/hapi-h2-data:/app/h2-data \
+  -v ./logs:/app/logs \
+  --name fhirflare-standalone \
+  ghcr.io/sudo-jhare/fhirflare-ig-toolkit-standalone:latest
+
+ +

Building from Source (Developers) +Using Windows .bat Scripts (Standalone Version Only):

+ +

First Time Setup:

+ +

Run Build and Run for first time.bat:

+ +

Code snippet

+ +
cd "<project folder>"
+git clone [https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git](https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git) hapi-fhir-jpaserver
+copy .\\hapi-fhir-Setup\\target\\classes\\application.yaml .\\hapi-fhir-jpaserver\\target\\classes\\application.yaml
+mvn clean package -DskipTests=true -Pboot
+docker-compose build --no-cache
+docker-compose up -d
+
+ +

This clones the HAPI FHIR server, copies configuration, builds the project, and starts the containers.

+ +

Subsequent Runs:

+ +

Run Run.bat:

+ +

Code snippet

+
cd "<project folder>"
+docker-compose up -d
+
+

This starts the Flask app (port 5000) and HAPI FHIR server (port 8080).

+ +

Access the Application:

+ +
    +
  • Flask UI: http://localhost:5000
  • +
  • HAPI FHIR server: http://localhost:8080
  • +
  • Manual Setup (Linux/MacOS/Windows):
  • +
+ +

Preparation (Standalone Version Only):

+ +
cd <project folder>
+git clone [https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git](https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git) hapi-fhir-jpaserver
+cp ./hapi-fhir-Setup/target/classes/application.yaml ./hapi-fhir-jpaserver/target/classes/application.yaml
+
+ +

Build:

+ +
# Build HAPI FHIR (Standalone Version Only)
+mvn clean package -DskipTests=true -Pboot
+
+# Build Docker Image (Specify APP_MODE=lite in docker-compose.yml for Lite version)
+docker-compose build --no-cache
+
+ +

Run:

+ +
docker-compose up -d
+Access the Application:
+
+ +
    +
  • Flask UI: http://localhost:5000
  • +
  • HAPI FHIR server (Standalone only): http://localhost:8080
  • +
  • Local Development (Without Docker):
  • +
+ +

Clone the Repository:

+ +
git clone [https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit.git](https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit.git)
+cd FHIRFLARE-IG-Toolkit
+
+ +

Install Dependencies:

+ +
python -m venv venv
+source venv/bin/activate  # On Windows: venv\Scripts\activate
+pip install -r requirements.txt
+
+ +

Install Node.js, GoFSH, and SUSHI (for FSH Converter):

+ +
# Example for Debian/Ubuntu
+curl -fsSL [https://deb.nodesource.com/setup_18.x](https://deb.nodesource.com/setup_18.x) | sudo bash -
+sudo apt-get install -y nodejs
+# Install globally
+npm install -g gofsh fsh-sushi
+Set Environment Variables:
+
+ +
export FLASK_SECRET_KEY='your-secure-secret-key'
+export API_KEY='your-api-key'
+# Optional: Set APP_MODE to 'lite' if desired
+# export APP_MODE='lite'
+
+ +

Initialize Directories:

+ +
mkdir -p instance static/uploads logs
+# Ensure write permissions if needed
+# chmod -R 777 instance static/uploads logs
+
+ +

Run the Application:

+ +
export FLASK_APP=app.py
+flask run
+
+

Access at http://localhost:5000.

+ +

Usage +Import an IG

+

Search, View Details, and Import Packages

+

Navigate to Search and Import Packages (/search-and-import).

+
    +
  1. The page will load a list of available FHIR Implementation Guide packages from a local cache or by fetching from configured registries. +
      +
    • A loading animation and progress messages are shown if fetching from registries.
    • +
    • The timestamp of the last cache update is displayed.
    • +
    +
  2. +
  3. Use the search bar to filter packages by name or author.
  4. +
  5. Packages are paginated for easier Browse.
  6. +
  7. For each package, you can: +
      +
    • View its latest official and absolute versions.
    • +
    • Click on the package name to navigate to a detailed view (/package-details/<name>) showing: +
        +
      • Comprehensive metadata (author, FHIR version, canonical URL, description).
      • +
      • A full list of available versions with publication dates.
      • +
      • Declared dependencies.
      • +
      • Other packages that depend on it (dependents).
      • +
      • Version history (logs).
      • +
      +
    • +
    • Directly import a specific version using the “Import” button on the search page or the details page.
    • +
    +
  8. +
  9. Cache Management: +
      +
    • A “Clear & Refresh Cache” button is available to trigger a background task (/api/refresh-cache-task) that clears the local database and in-memory cache and fetches the latest package information from all configured registries. Progress is shown via a live log.
    • +
    +
  10. +
+ +
    +
  • Enter a package name (e.g., hl7.fhir.au.core) and version (e.g., 1.1.0-preview).
  • +
  • Choose a dependency mode:
  • +
  • Current Recursive: Import all dependencies listed in package.json recursively.
  • +
  • Patch Canonical Versions: Import only canonical FHIR packages (e.g., hl7.fhir.r4.core).
  • +
  • Tree Shaking: Import only dependencies containing resources actually used by the main package.
  • +
  • Click Import to download the package and dependencies.
  • +
+ +

Manage IGs +Go to Manage FHIR Packages (/view-igs) to view downloaded and processed IGs.

+ +

Actions:

+
    +
  • Process: Extract metadata (resource types, profiles, must-support elements, examples).
  • +
  • Unload: Remove processed IG data from the database.
  • +
  • Delete: Remove package files from the filesystem.
  • +
+ +

Duplicates are highlighted for resolution.

+ +

View Processed IGs

+ +

After processing, view IG details (/view-ig/), including:

+ +
    +
  • Resource types and profiles.
  • +
  • Must-support elements and examples.
  • +
  • Profile relationships (compliesWithProfile, imposeProfile) if enabled (DISPLAY_PROFILE_RELATIONSHIPS).
  • +
+ +

Interactive StructureDefinition viewer (Differential, Snapshot, Must Support, Key Elements, Constraints, Terminology, Search Params).

+
    +
  • Validate FHIR Resources/Bundles
  • +
  • Navigate to Validate FHIR Sample (/validate-sample).
  • +
+ +

Select a package (e.g., hl7.fhir.au.core#1.1.0-preview).

+ +
    +
  • Choose Single Resource or Bundle mode.
  • +
  • Paste or upload FHIR JSON/XML (e.g., a Patient resource).
  • +
  • Submit to view validation errors/warnings. Note: Alpha feature; report issues to GitHub (remove PHI).
  • +
  • Push IGs to a FHIR Server
  • +
  • Go to Push IGs (/push-igs).
  • +
+ +

Select a downloaded package.

+ +
    +
  • Enter the Target FHIR Server URL.
  • +
  • Configure Authentication (None, Bearer Token).
  • +
  • Choose options: Include Dependencies, Force Upload (skips comparison check), Dry Run, Verbose Log.
  • +
  • Optionally filter by Resource Types (comma-separated) or Skip Specific Files (paths within package, comma/newline separated).
  • +
  • Click Push to FHIR Server to upload resources. Canonical resources are checked before upload. Identical resources are skipped unless Force Upload is checked.
  • +
  • Monitor progress in the live console.
  • +
  • Upload Test Data
  • +
  • Navigate to Upload Test Data (/upload-test-data).
  • +
  • Enter the Target FHIR Server URL.
  • +
  • Configure Authentication (None, Bearer Token).
  • +
  • Select one or more .json, .xml files, or a single .zip file containing test resources.
  • +
  • Optionally check Validate Resources Before Upload? and select a Validation Profile Package.
  • +
  • Choose Upload Mode:
  • +
  • Individual Resources: Uploads each resource one by one in dependency order.
  • +
  • Transaction Bundle: Uploads all resources in a single transaction.
  • +
  • Optionally check Use Conditional Upload (Individual Mode Only)? to use If-Match headers for updates.
  • +
  • Choose Error Handling:
  • +
  • Stop on First Error: Halts the process if any validation or upload fails.
  • +
  • Continue on Error: Reports errors but attempts to process/upload remaining resources.
  • +
  • Click Upload and Process. The tool parses files, optionally validates, analyzes dependencies, topologically sorts resources, and uploads them according to selected options.
  • +
  • Monitor progress in the streaming log output.
  • +
+ +

Convert FHIR to FSH

+
    +
  • Navigate to FSH Converter (/fsh-converter).
  • +
+ +

Optionally select a package for context (e.g., hl7.fhir.au.core#1.1.0-preview). +Choose input mode: +Upload File: Upload a FHIR JSON/XML file. +Paste Text: Paste FHIR JSON/XML content. +Configure options: +Output Style: file-per-definition, group-by-fsh-type, group-by-profile, single-file. +Log Level: error, warn, info, debug. +FHIR Version: R4, R4B, R5, or auto-detect. +Fishing Trip: Enable round-trip validation with SUSHI, generating a comparison report. +Dependencies: Specify additional packages (e.g., hl7.fhir.us.core@6.1.0, one per line). +Indent Rules: Enable context path indentation for readable FSH. +Meta Profile: Choose only-one, first, or none for meta.profile handling. +Alias File: Upload an FSH file with aliases (e.g., $MyAlias = http://example.org). +No Alias: Disable automatic alias generation. +Click Convert to FSH to generate and display FSH output, with a waiting spinner (light/dark theme) during processing. +If Fishing Trip is enabled, view the comparison report via the “Click here for SUSHI Validation” badge button. +Download the result as a .fsh file. +Retrieve and Split Bundles +Navigate to Retrieve/Split Data (/retrieve-split-data).

+ +

Retrieve Bundles from Server:

+ +
    +
  • Enter the FHIR Server URL (defaults to the proxy if empty).
  • +
  • Select one or more Resource Types to retrieve (e.g., Patient, Observation).
  • +
  • Optionally check Fetch Referenced Resources.
  • +
  • If checked, further optionally check Fetch Full Reference Bundles to retrieve entire bundles for each referenced type (e.g., all Patients if a Patient is referenced) instead of individual resources by ID.
  • +
  • Click Retrieve Bundles.
  • +
  • Monitor progress in the streaming log. A ZIP file containing the retrieved bundles/resources will be prepared for download.
  • +
+ +

Split Uploaded Bundles:

+ +
    +
  • Upload a ZIP file containing FHIR bundles (JSON format).
  • +
  • Click Split Bundles.
  • +
  • A ZIP file containing individual resources extracted from the bundles will be prepared for download.
  • +
  • Explore FHIR Operations
  • +
  • Navigate to FHIR UI Operations (/fhir-ui-operations).
  • +
+ +

Toggle between local HAPI (/fhir) or a custom FHIR server.

+ +
    +
  • Click Fetch Metadata to load the server’s CapabilityStatement.
  • +
  • Select a resource type (e.g., Patient, Observation) or System to view operations:
  • +
  • System operations: GET /metadata, POST /, GET /_history, GET/POST /$diff, POST /$reindex, POST /$expunge, etc.
  • +
  • Resource operations: GET Patient/:id, POST Observation/_search, etc.
  • +
  • Use Try it out to input parameters or request bodies, then Execute to view results in JSON, XML, or narrative formats.
  • +
+ +

Configure Embedded HAPI FHIR Server (Standalone Mode)

+

For users running the Standalone version, which includes an embedded HAPI FHIR server.

+
    +
  1. Navigate to Configure HAPI FHIR (/config-hapi).
  2. +
  3. The page displays the content of the HAPI FHIR server’s application.yaml file.
  4. +
  5. You can edit the configuration directly in the text area. +
      +
    • Caution: Incorrect modifications can break the HAPI FHIR server.
    • +
    +
  6. +
  7. Click Save Configuration to apply your changes to the application.yaml file.
  8. +
  9. Click Restart Tomcat to restart the HAPI FHIR server and load the new configuration. The restart process may take a few moments.
  10. +
+ +

API Usage +Import IG +Bash

+ +

curl -X POST http://localhost:5000/api/import-ig
+-H “Content-Type: application/json”
+-H “X-API-Key: your-api-key”
+-d ‘{“package_name”: “hl7.fhir.au.core”, “version”: “1.1.0-preview”, “dependency_mode”: “recursive”}’ +Returns complies_with_profiles, imposed_profiles, and duplicate_packages_present info.

+ +

Refresh Package Cache (Background Task)

+
curl -X POST http://localhost:5000/api/refresh-cache-task \
+-H "X-API-Key: your-api-key"
+
+ +

Push IG +Bash

+ +
curl -X POST http://localhost:5000/api/push-ig \
+-H "Content-Type: application/json" \
+-H "Accept: application/x-ndjson" \
+-H "X-API-Key: your-api-key" \
+-d '{
+      "package_name": "hl7.fhir.au.core",
+      "version": "1.1.0-preview",
+      "fhir_server_url": "http://localhost:8080/fhir",
+      "include_dependencies": true,
+      "force_upload": false,
+      "dry_run": false,
+      "verbose": false,
+      "auth_type": "none"
+    }'
+
+ +

Returns a streaming NDJSON response with progress and final summary.

+ +

Upload Test Data +Bash

+ +
curl -X POST http://localhost:5000/api/upload-test-data \
+-H "X-API-Key: your-api-key" \
+-H "Accept: application/x-ndjson" \
+-F "fhir_server_url=http://your-fhir-server/fhir" \
+-F "auth_type=bearerToken" \
+-F "auth_token=YOUR_TOKEN" \
+-F "upload_mode=individual" \
+-F "error_handling=continue" \
+-F "validate_before_upload=true" \
+-F "validation_package_id=hl7.fhir.r4.core#4.0.1" \
+-F "use_conditional_uploads=true" \
+-F "test_data_files=@/path/to/your/patient.json" \
+-F "test_data_files=@/path/to/your/observations.zip"
+
+ +

Returns a streaming NDJSON response with progress and final summary. Uses multipart/form-data for file uploads.

+ +

Retrieve Bundles +Bash

+ +
curl -X POST http://localhost:5000/api/retrieve-bundles \
+-H "X-API-Key: your-api-key" \
+-H "Accept: application/x-ndjson" \
+-F "fhir_server_url=http://your-fhir-server/fhir" \
+-F "resources=Patient" \
+-F "resources=Observation" \
+-F "validate_references=true" \
+-F "fetch_reference_bundles=false"
+
+ +

Returns a streaming NDJSON response with progress. The X-Zip-Path header in the final response part will contain the path to download the ZIP archive (e.g., /tmp/retrieved_bundles_datetime.zip).

+ +

Split Bundles +Bash

+ +
curl -X POST http://localhost:5000/api/split-bundles \
+-H "X-API-Key: your-api-key" \
+-H "Accept: application/x-ndjson" \
+-F "split_bundle_zip_path=@/path/to/your/bundles.zip"
+
+ +

Returns a streaming NDJSON response. The X-Zip-Path header in the final response part will contain the path to download the ZIP archive of split resources.

+ +

Validate Resource/Bundle +Not yet exposed via API; use the UI at /validate-sample.

+ +

Configuration Options +Located in app.py:

+ +
    +
  • VALIDATE_IMPOSED_PROFILES: (Default: True) Validates resources against imposed profiles during push.
  • +
  • DISPLAY_PROFILE_RELATIONSHIPS: (Default: True) Shows compliesWithProfile and imposeProfile in the UI.
  • +
  • FHIR_PACKAGES_DIR: (Default: /app/instance/fhir_packages) Stores .tgz packages and metadata.
  • +
  • UPLOAD_FOLDER: (Default: /app/static/uploads) Stores GoFSH output files and FSH comparison reports.
  • +
  • SECRET_KEY: Required for CSRF protection and sessions. Set via environment variable or directly.
  • +
  • API_KEY: Required for API authentication. Set via environment variable or directly.
  • +
  • MAX_CONTENT_LENGTH: (Default: Flask default) Max size for HTTP request body (e.g., 16 * 1024 * 1024 for 16MB). Important for large uploads.
  • +
  • MAX_FORM_PARTS: (Default: Werkzeug default, often 1000) Default max number of form parts. Overridden for /api/upload-test-data by CustomFormDataParser.
  • +
+ +

Get HAPI FHIR Configuration (Standalone Mode)

+
curl -X GET http://localhost:5000/api/config \
+-H "X-API-Key: your-api-key"
+
+

Save HAPI FHIR Configuration:

+
curl -X POST http://localhost:5000/api/config \
+-H "Content-Type: application/json" \
+-H "X-API-Key: your-api-key" \
+-d '{"your_yaml_key": "your_value", ...}' # Send the full YAML content as JSON
+
+

Restart HAPI FHIR Server:

+ +
curl -X POST http://localhost:5000/api/restart-tomcat \
+-H "X-API-Key: your-api-key"
+
+ +

Testing +The project includes a test suite covering UI, API, database, file operations, and security.

+ +

Test Prerequisites:

+ +

pytest: For running tests. +pytest-mock: For mocking dependencies. Install: pip install pytest pytest-mock +Running Tests:

+ +

Bash

+ +
cd <project folder>
+pytest tests/test_app.py -v
+
+

Test Coverage:

+ +
    +
  • UI Pages: Homepage, Import IG, Manage IGs, Push IGs, Validate Sample, View Processed IG, FSH Converter, Upload Test Data, Retrieve/Split Data.
  • +
  • API Endpoints: POST /api/import-ig, POST /api/push-ig, GET /get-structure, GET /get-example, POST /api/upload-test-data, POST /api/retrieve-bundles, POST /api/split-bundles.
  • +
  • Database: IG processing, unloading, viewing.
  • +
  • File Operations: Package processing, deletion, FSH output, ZIP handling.
  • +
  • Security: CSRF protection, flash messages, secret key.
  • +
  • FSH Converter: Form submission, file/text input, GoFSH execution, Fishing Trip comparison.
  • +
  • Upload Test Data: Parsing, dependency graph, sorting, upload modes, validation, conditional uploads.
  • +
+ +

Development Notes

+ +

Background

+ +

The toolkit addresses the need for a comprehensive FHIR IG management tool, with recent enhancements for resource validation, FSH conversion with advanced GoFSH features, flexible versioning, improved IG pushing, dependency-aware test data uploading, and bundle retrieval/splitting, making it a versatile platform for FHIR developers.

+ +

Technical Decisions

+ +
    +
  • Flask: Lightweight and flexible for web development.
  • +
  • SQLite: Simple for development; consider PostgreSQL for production.
  • +
  • Bootstrap 5.3.3: Responsive UI with custom styling.
  • +
  • Lottie-Web: Renders themed animations for FSH conversion waiting spinner.
  • +
  • GoFSH/SUSHI: Integrated via Node.js for advanced FSH conversion and round-trip validation.
  • +
  • Docker: Ensures consistent deployment with Flask and HAPI FHIR.
  • +
  • Flexible Versioning: Supports non-standard IG versions (e.g., -preview, -ballot).
  • +
  • Live Console/Streaming: Real-time feedback for complex operations (Push, Upload Test Data, FSH, Retrieve Bundles).
  • +
  • Validation: Alpha feature with ongoing FHIRPath improvements.
  • +
  • Dependency Management: Uses topological sort for Upload Test Data feature.
  • +
  • Form Parsing: Uses custom Werkzeug parser for Upload Test Data to handle large numbers of files.
  • +
+ +

Recent Updates

+ +
    +
  • Enhanced package search page with caching, detailed views (dependencies, dependents, version history), and background cache refresh.
  • +
  • Upload Test Data Enhancements (April 2025):
  • +
  • Added optional Pre-Upload Validation against selected IG profiles.
  • +
  • Added optional Conditional Uploads (GET + POST/PUT w/ If-Match) for individual mode.
  • +
  • Implemented robust XML parsing using fhir.resources library (when available).
  • +
  • Fixed 413 Request Entity Too Large errors for large file counts using a custom Werkzeug FormDataParser.
  • +
  • Path: templates/upload_test_data.html, app.py, services.py, forms.py.
  • +
  • Push IG Enhancements (April 2025):
  • +
  • Added semantic comparison to skip uploading identical resources.
  • +
  • Added “Force Upload” option to bypass comparison.
  • +
  • Improved handling of canonical resources (search before PUT/POST).
  • +
  • Added filtering by specific files to skip during push.
  • +
  • More detailed summary report in stream response.
  • +
  • Path: templates/cp_push_igs.html, app.py, services.py.
  • +
  • Waiting Spinner for FSH Converter (April 2025):
  • +
  • Added a themed (light/dark) Lottie animation spinner during FSH execution.
  • +
  • Path: templates/fsh_converter.html, static/animations/, static/js/lottie-web.min.js.
  • +
  • Advanced FSH Converter (April 2025):
  • +
  • Added support for GoFSH advanced options: –fshing-trip, –dependency, –indent, –meta-profile, –alias-file, –no-alias.
  • +
  • Displays Fishing Trip comparison reports.
  • +
  • Path: templates/fsh_converter.html, app.py, services.py, forms.py.
  • +
  • (New) Retrieve and Split Data (May 2025):
  • +
  • Added UI and API for retrieving bundles from a FHIR server by resource type.
  • +
  • Added options to fetch referenced resources (individually or as full type bundles).
  • +
  • Added functionality to split uploaded ZIP files of bundles into individual resources.
  • +
  • Streaming log for retrieval and ZIP download for results.
  • +
  • Paths: templates/retrieve_split_data.html, app.py, services.py, forms.py.
  • +
  • Known Issues and Workarounds
  • +
  • Favicon 404: Clear browser cache or verify /app/static/favicon.ico.
  • +
  • CSRF Errors: Set FLASK_SECRET_KEY and ensure in forms.
  • +
  • Import Fails: Check package name/version and connectivity.
  • +
  • Validation Accuracy: Alpha feature; report issues to GitHub (remove PHI).
  • +
  • Package Parsing: Non-standard .tgz filenames may parse incorrectly. Fallback uses name-only parsing.
  • +
  • Permissions: Ensure instance/ and static/uploads/ are writable.
  • +
  • GoFSH/SUSHI Errors: Check ./logs/flask_err.log for ERROR:services:GoFSH failed. Ensure valid FHIR inputs and SUSHI installation.
  • +
  • Upload Test Data XML Parsing: Relies on fhir.resources library for full validation; basic parsing used as fallback. Complex XML structures might not be fully analyzed for dependencies with basic parsing. Prefer JSON for reliable dependency analysis.
  • +
  • 413 Request Entity Too Large: Primarily handled by CustomFormDataParser for /api/upload-test-data. Check the parser’s max_form_parts limit if still occurring. MAX_CONTENT_LENGTH in app.py controls overall size. Reverse proxy limits (client_max_body_size in Nginx) might also apply.
  • +
+ +

Future Improvements

+ +
    +
  • Upload Test Data: Improve XML parsing further (direct XML->fhir.resource object if possible), add visual progress bar, add upload order preview, implement transaction bundle size splitting, add ‘Clear Target Server’ option (with confirmation).
  • +
  • Validation: Enhance FHIRPath for complex constraints; add API endpoint.
  • +
  • Sorting: Sort IG versions in /view-igs (e.g., ascending).
  • +
  • Duplicate Resolution: Options to keep latest version or merge resources.
  • +
  • Production Database: Support PostgreSQL.
  • +
  • Error Reporting: Detailed validation error paths in the UI.
  • +
  • FSH Enhancements: Add API endpoint for FSH conversion; support inline instance construction.
  • +
  • FHIR Operations: Add complex parameter support (e.g., /$diff with left/right).
  • +
  • Retrieve/Split Data: Add option to filter resources during retrieval (e.g., by date, specific IDs).
  • +
+ +

Completed Items

+ +
    +
  • Testing suite with basic coverage.
  • +
  • API endpoints for POST /api/import-ig and POST /api/push-ig.
  • +
  • Flexible versioning (-preview, -ballot).
  • +
  • CSRF fixes for forms.
  • +
  • Resource validation UI (alpha).
  • +
  • FSH Converter with advanced GoFSH features and waiting spinner.
  • +
  • Push IG enhancements (force upload, semantic comparison, canonical handling, skip files).
  • +
  • Upload Test Data feature with dependency sorting, multiple upload modes, pre-upload validation, conditional uploads, robust XML parsing, and fix for large file counts.
  • +
  • Retrieve and Split Data functionality with reference fetching and ZIP download.
  • +
  • Far-Distant Improvements
  • +
  • Cache Service: Use Redis for IG metadata caching.
  • +
  • Database Optimization: Composite index on ProcessedIg.package_name and ProcessedIg.version.
  • +
+ +

Directory Structure

+
FHIRFLARE-IG-Toolkit/
+├── app.py                              # Main Flask application
+├── Build and Run for first time.bat    # Windows script for first-time Docker setup
+├── docker-compose.yml                  # Docker Compose configuration
+├── Dockerfile                          # Docker configuration
+├── forms.py                            # Form definitions
+├── LICENSE.md                          # Apache 2.0 License
+├── README.md                           # Project documentation
+├── requirements.txt                    # Python dependencies
+├── Run.bat                             # Windows script for running Docker
+├── services.py                         # Logic for IG import, processing, validation, pushing, FSH conversion, test data upload, retrieve/split
+├── supervisord.conf                    # Supervisor configuration
+├── hapi-fhir-Setup/
+│   ├── README.md                       # HAPI FHIR setup instructions
+│   └── target/
+│       └── classes/
+│           └── application.yaml        # HAPI FHIR configuration
+├── instance/
+│   ├── fhir_ig.db                      # SQLite database
+│   ├── fhir_ig.db.old                  # Database backup
+│   └── fhir_packages/                  # Stored IG packages and metadata
+│       ├── ... (example packages) ...
+├── logs/
+│   ├── flask.log                       # Flask application logs
+│   ├── flask_err.log                   # Flask error logs
+│   ├── supervisord.log                 # Supervisor logs
+│   ├── supervisord.pid                 # Supervisor PID file
+│   ├── tomcat.log                      # Tomcat logs for HAPI FHIR
+│   └── tomcat_err.log                  # Tomcat error logs
+├── static/
+│   ├── animations/
+│   │   ├── loading-dark.json           # Dark theme spinner animation
+│   │   └── loading-light.json          # Light theme spinner animation
+│   ├── favicon.ico                     # Application favicon
+│   ├── FHIRFLARE.png                   # Application logo
+│   ├── js/
+│   │   └── lottie-web.min.js           # Lottie library for spinner
+│   └── uploads/
+│       ├── output.fsh                  # Generated FSH output (temp location)
+│       └── fsh_output/                 # GoFSH output directory
+│           ├── ... (example GoFSH output) ...
+├── templates/
+│   ├── base.html                       # Base template
+│   ├── cp_downloaded_igs.html          # UI for managing IGs
+│   ├── cp_push_igs.html                # UI for pushing IGs
+│   ├── cp_view_processed_ig.html       # UI for viewing processed IGs
+│   ├── fhir_ui.html                    # UI for FHIR API explorer
+│   ├── fhir_ui_operations.html         # UI for FHIR server operations
+│   ├── fsh_converter.html              # UI for FSH conversion
+│   ├── import_ig.html                  # UI for importing IGs
+│   ├── index.html                      # Homepage
+│   ├── retrieve_split_data.html        # UI for Retrieve and Split Data
+│   ├── upload_test_data.html           # UI for Uploading Test Data
+│   ├── validate_sample.html            # UI for validating resources/bundles
+│   ├── config_hapi.html                # UI for HAPI FHIR Configuration
+│   └── _form_helpers.html              # Form helper macros
+├── tests/
+│   └── test_app.py                     # Test suite
+└── hapi-fhir-jpaserver/                # HAPI FHIR server resources (if Standalone)
+
+ +

Contributing

+ +
    +
  1. Fork the repository.
  2. +
  3. Create a feature branch (git checkout -b feature/your-feature).
  4. +
  5. Commit changes (git commit -m “Add your feature”).
  6. +
  7. Push to your branch (git push origin feature/your-feature).
  8. +
  9. Open a Pull Request.
  10. +
  11. Ensure code follows PEP 8 and includes tests in tests/test_app.py.
  12. +
+ +

Troubleshooting

+ +
    +
  • Favicon 404: Clear browser cache or verify /app/static/favicon.ico: docker exec -it curl http://localhost:5000/static/favicon.ico
  • +
  • CSRF Errors: Set FLASK_SECRET_KEY and ensure in forms.
  • +
  • Import Fails: Check package name/version and connectivity.
  • +
  • Validation Accuracy: Alpha feature; report issues to GitHub (remove PHI).
  • +
  • Package Parsing: Non-standard .tgz filenames may parse incorrectly. Fallback uses name-only parsing.
  • +
  • Permissions: Ensure instance/ and static/uploads/ are writable: chmod -R 777 instance static/uploads logs
  • +
  • GoFSH/SUSHI Errors: Check ./logs/flask_err.log for ERROR:services:GoFSH failed. Ensure valid FHIR inputs and SUSHI installation: docker exec -it sushi --version
  • +
  • 413 Request Entity Too Large: Increase MAX_CONTENT_LENGTH and MAX_FORM_PARTS in app.py. If using a reverse proxy (e.g., Nginx), increase its client_max_body_size setting as well. Ensure the application/container is fully restarted/rebuilt.
  • +
+ +

License

+ +

Licensed under the Apache 2.0 License. See LICENSE.md for details.

+ + +
+
+ + + + diff --git a/README.md b/README.md deleted file mode 100644 index 030e215..0000000 --- a/README.md +++ /dev/null @@ -1,661 +0,0 @@ -# FHIRFLARE IG Toolkit -![FHIRFLARE Logo](static/FHIRFLARE.png) - -## Overview - -The FHIRFLARE IG Toolkit is a Flask-based web application designed to streamline the management, processing, validation, and deployment of FHIR Implementation Guides (IGs) and test data. It offers a user-friendly interface for importing IG packages, extracting metadata, validating FHIR resources or bundles, pushing IGs to FHIR servers, converting FHIR resources to FHIR Shorthand (FSH), uploading complex test data sets with dependency management, and retrieving/splitting FHIR bundles. The toolkit includes live consoles for real-time feedback, making it an essential tool for FHIR developers and implementers. - -The application can run in two modes: - -* **Standalone:** Includes a Dockerized Flask frontend, SQLite database, and an embedded HAPI FHIR server for local validation and interaction. -* **Lite:** Includes only the Dockerized Flask frontend and SQLite database, excluding the local HAPI FHIR server. Requires connection to external FHIR servers for certain features. - -## Installation Modes (Lite vs. Standalone) - -This toolkit offers two primary installation modes to suit different needs: - -* **Standalone Version:** - * Includes the full FHIRFLARE Toolkit application **and** an embedded HAPI FHIR server running locally within the Docker environment. - * Allows for local FHIR resource validation using HAPI FHIR's capabilities. - * Enables the "Use Local HAPI" option in the FHIR API Explorer and FHIR UI Operations pages, proxying requests to the internal HAPI server (`http://localhost:8080/fhir`). - * Requires Git and Maven during the initial build process (via the `.bat` script or manual steps) to prepare the HAPI FHIR server. - * Ideal for users who want a self-contained environment for development and testing or who don't have readily available external FHIR servers. - -* **Lite Version:** - * Includes the FHIRFLARE Toolkit application **without** the embedded HAPI FHIR server. - * Requires users to provide URLs for external FHIR servers when using features like the FHIR API Explorer and FHIR UI Operations pages. The "Use Local HAPI" option will be disabled in the UI. - * Resource validation relies solely on local checks against downloaded StructureDefinitions, which may be less comprehensive than HAPI FHIR's validation (e.g., for terminology bindings or complex invariants). - * **Does not require Git or Maven** for setup if using the `.bat` script or running the pre-built Docker image. - * Ideal for users who primarily want to use the IG management, processing, and FSH conversion features, or who will always connect to existing external FHIR servers. - -## Features - -* **Import IGs:** Download FHIR IG packages and dependencies from a package registry, supporting flexible version formats (e.g., `1.2.3`, `1.1.0-preview`, `current`) and dependency pulling modes (Recursive, Patch Canonical, Tree Shaking). -* **Enhanced Package Search and Import:** - * Interactive page (`/search-and-import`) to search for FHIR IG packages from configured registries. - * Displays package details, version history, dependencies, and dependents. - * Utilizes a local database cache (`CachedPackage`) for faster subsequent searches. - * Background task to refresh the package cache from registries (`/api/refresh-cache-task`). - * Direct import from search results. -* **Manage IGs:** View, process, unload, or delete downloaded IGs, with duplicate detection and resolution. -* **Process IGs:** Extract resource types, profiles, must-support elements, examples, and profile relationships (`structuredefinition-compliesWithProfile` and `structuredefinition-imposeProfile`). -* **Validate FHIR Resources/Bundles:** Validate single FHIR resources or bundles against selected IGs, with detailed error and warning reports (alpha feature). *Note: Lite version uses local SD checks only.* -* **Push IGs:** Upload IG resources (and optionally dependencies) to a target FHIR server. Features include: - * Real-time console output. - * Authentication support (Bearer Token). - * Filtering by resource type or specific files to skip. - * Semantic comparison to skip uploading identical resources (override with **Force Upload** option). - * Correct handling of canonical resources (searching by URL/version before deciding POST/PUT). - * Dry run mode for simulation. - * Verbose logging option. -* **Upload Test Data:** Upload complex sets of test data (individual JSON/XML files or ZIP archives) to a target FHIR server. Features include: - * Robust parsing of JSON and XML (using `fhir.resources` library when available). - * Automatic dependency analysis based on resource references within the uploaded set. - * Topological sorting to ensure resources are uploaded in the correct order. - * Cycle detection in dependencies. - * Choice of individual resource uploads or a single transaction bundle. - * **Optional Pre-Upload Validation:** Validate resources against a selected profile package before uploading. - * **Optional Conditional Uploads (Individual Mode):** Check resource existence (GET) and use conditional `If-Match` headers for updates (PUT) or create resources (PUT/POST). Falls back to simple PUT if unchecked. - * Configurable error handling (stop on first error or continue). - * Authentication support (Bearer Token). - * Streaming progress log via the UI. - * Handles large numbers of files using a custom form parser. -* **Profile Relationships:** Display and validate `compliesWithProfile` and `imposeProfile` extensions in the UI (configurable). -* **FSH Converter:** Convert FHIR JSON/XML resources to FHIR Shorthand (FSH) using GoFSH, with advanced options (Package context, Output styles, Log levels, FHIR versions, Fishing Trip, Dependencies, Indentation, Meta Profile handling, Alias File, No Alias). Includes a waiting spinner. -* **Retrieve and Split Bundles:** - * Retrieve specified resource types as bundles from a FHIR server. - * Optionally fetch referenced resources, either individually or as full bundles for each referenced type. - * Split uploaded ZIP files containing bundles into individual resource JSON files. - * Download retrieved/split resources as a ZIP archive. - * Streaming progress log via the UI for retrieval operations. -* **FHIR Interaction UIs:** Explore FHIR server capabilities and interact with resources using the "FHIR API Explorer" (simple GET/POST/PUT/DELETE) and "FHIR UI Operations" (Swagger-like interface based on CapabilityStatement). *Note: Lite version requires custom server URLs.* -* **HAPI FHIR Configuration (Standalone Mode):** - * A dedicated page (`/config-hapi`) to view and edit the `application.yaml` configuration for the embedded HAPI FHIR server. - * Allows modification of HAPI FHIR properties directly from the UI. - * Option to restart the HAPI FHIR server (Tomcat) to apply changes. -* **API Support:** RESTful API endpoints for importing, pushing, retrieving metadata, validating, uploading test data, and retrieving/splitting bundles. -* **Live Console:** Real-time logs for push, validation, upload test data, FSH conversion, and bundle retrieval operations. -* **Configurable Behavior:** Control validation modes, display options via `app.config`. -* **Theming:** Supports light and dark modes. - -## Technology Stack - -* Python 3.12+, Flask 2.3.3, Flask-SQLAlchemy 3.0.5, Flask-WTF 1.2.1 -* Jinja2, Bootstrap 5.3.3, JavaScript (ES6), Lottie-Web 5.12.2 -* SQLite -* Docker, Docker Compose, Supervisor -* Node.js 18+ (for GoFSH/SUSHI), GoFSH, SUSHI -* HAPI FHIR (Standalone version only) -* Requests 2.31.0, Tarfile, Logging, Werkzeug -* fhir.resources (optional, for robust XML parsing) - -## Prerequisites - -* **Docker:** Required for containerized deployment (both versions). -* **Git & Maven:** Required **only** for building the **Standalone** version from source using the `.bat` script or manual steps. Not required for the Lite version build or for running pre-built Docker Hub images. -* **Windows:** Required if using the `.bat` scripts. - -## Setup Instructions - -### Running Pre-built Images (General Users) - -This is the easiest way to get started without needing Git or Maven. Choose the version you need: - -**Lite Version (No local HAPI FHIR):** - -```bash -# Pull the latest Lite image -docker pull ghcr.io/sudo-jhare/fhirflare-ig-toolkit-lite:latest - -# Run the Lite version (maps port 5000 for the UI) -# You'll need to create local directories for persistent data first: -# mkdir instance logs static static/uploads instance/hapi-h2-data -docker run -d \ - -p 5000:5000 \ - -v ./instance:/app/instance \ - -v ./static/uploads:/app/static/uploads \ - -v ./instance/hapi-h2-data:/app/h2-data \ - -v ./logs:/app/logs \ - --name fhirflare-lite \ - ghcr.io/sudo-jhare/fhirflare-ig-toolkit-lite:latest -Standalone Version (Includes local HAPI FHIR): - -Bash - -# Pull the latest Standalone image -docker pull ghcr.io/sudo-jhare/fhirflare-ig-toolkit-standalone:latest - -# Run the Standalone version (maps ports 5000 and 8080) -# You'll need to create local directories for persistent data first: -# mkdir instance logs static static/uploads instance/hapi-h2-data -docker run -d \ - -p 5000:5000 \ - -p 8080:8080 \ - -v ./instance:/app/instance \ - -v ./static/uploads:/app/static/uploads \ - -v ./instance/hapi-h2-data:/app/h2-data \ - -v ./logs:/app/logs \ - --name fhirflare-standalone \ - ghcr.io/sudo-jhare/fhirflare-ig-toolkit-standalone:latest -Building from Source (Developers) -Using Windows .bat Scripts (Standalone Version Only): - -First Time Setup: - -Run Build and Run for first time.bat: - -Code snippet - -cd "" -git clone [https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git](https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git) hapi-fhir-jpaserver -copy .\\hapi-fhir-Setup\\target\\classes\\application.yaml .\\hapi-fhir-jpaserver\\target\\classes\\application.yaml -mvn clean package -DskipTests=true -Pboot -docker-compose build --no-cache -docker-compose up -d -This clones the HAPI FHIR server, copies configuration, builds the project, and starts the containers. - -Subsequent Runs: - -Run Run.bat: - -Code snippet - -cd "" -docker-compose up -d -This starts the Flask app (port 5000) and HAPI FHIR server (port 8080). - -Access the Application: - -Flask UI: http://localhost:5000 -HAPI FHIR server: http://localhost:8080 -Manual Setup (Linux/MacOS/Windows): - -Preparation (Standalone Version Only): - -Bash - -cd -git clone [https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git](https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git) hapi-fhir-jpaserver -cp ./hapi-fhir-Setup/target/classes/application.yaml ./hapi-fhir-jpaserver/target/classes/application.yaml -Build: - -Bash - -# Build HAPI FHIR (Standalone Version Only) -mvn clean package -DskipTests=true -Pboot - -# Build Docker Image (Specify APP_MODE=lite in docker-compose.yml for Lite version) -docker-compose build --no-cache -Run: - -Bash - -docker-compose up -d -Access the Application: - -Flask UI: http://localhost:5000 -HAPI FHIR server (Standalone only): http://localhost:8080 -Local Development (Without Docker): - -Clone the Repository: - -Bash - -git clone [https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit.git](https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit.git) -cd FHIRFLARE-IG-Toolkit -Install Dependencies: - -Bash - -python -m venv venv -source venv/bin/activate # On Windows: venv\Scripts\activate -pip install -r requirements.txt -Install Node.js, GoFSH, and SUSHI (for FSH Converter): - -Bash - -# Example for Debian/Ubuntu -curl -fsSL [https://deb.nodesource.com/setup_18.x](https://deb.nodesource.com/setup_18.x) | sudo bash - -sudo apt-get install -y nodejs -# Install globally -npm install -g gofsh fsh-sushi -Set Environment Variables: - -Bash - -export FLASK_SECRET_KEY='your-secure-secret-key' -export API_KEY='your-api-key' -# Optional: Set APP_MODE to 'lite' if desired -# export APP_MODE='lite' -Initialize Directories: - -Bash - -mkdir -p instance static/uploads logs -# Ensure write permissions if needed -# chmod -R 777 instance static/uploads logs -Run the Application: - -Bash - -export FLASK_APP=app.py -flask run -Access at http://localhost:5000. - -Usage -Import an IG -### Search, View Details, and Import Packages -Navigate to **Search and Import Packages** (`/search-and-import`). -1. The page will load a list of available FHIR Implementation Guide packages from a local cache or by fetching from configured registries. - * A loading animation and progress messages are shown if fetching from registries. - * The timestamp of the last cache update is displayed. -2. Use the search bar to filter packages by name or author. -3. Packages are paginated for easier Browse. -4. For each package, you can: - * View its latest official and absolute versions. - * Click on the package name to navigate to a **detailed view** (`/package-details/`) showing: - * Comprehensive metadata (author, FHIR version, canonical URL, description). - * A full list of available versions with publication dates. - * Declared dependencies. - * Other packages that depend on it (dependents). - * Version history (logs). - * Directly import a specific version using the "Import" button on the search page or the details page. -5. **Cache Management:** - * A "Clear & Refresh Cache" button is available to trigger a background task (`/api/refresh-cache-task`) that clears the local database and in-memory cache and fetches the latest package information from all configured registries. Progress is shown via a live log. - -Enter a package name (e.g., hl7.fhir.au.core) and version (e.g., 1.1.0-preview). -Choose a dependency mode: -Current Recursive: Import all dependencies listed in package.json recursively. -Patch Canonical Versions: Import only canonical FHIR packages (e.g., hl7.fhir.r4.core). -Tree Shaking: Import only dependencies containing resources actually used by the main package. -Click Import to download the package and dependencies. -Manage IGs -Go to Manage FHIR Packages (/view-igs) to view downloaded and processed IGs. - -Actions: -Process: Extract metadata (resource types, profiles, must-support elements, examples). -Unload: Remove processed IG data from the database. -Delete: Remove package files from the filesystem. -Duplicates are highlighted for resolution. -View Processed IGs -After processing, view IG details (/view-ig/), including: - -Resource types and profiles. -Must-support elements and examples. -Profile relationships (compliesWithProfile, imposeProfile) if enabled (DISPLAY_PROFILE_RELATIONSHIPS). -Interactive StructureDefinition viewer (Differential, Snapshot, Must Support, Key Elements, Constraints, Terminology, Search Params). -Validate FHIR Resources/Bundles -Navigate to Validate FHIR Sample (/validate-sample). - -Select a package (e.g., hl7.fhir.au.core#1.1.0-preview). -Choose Single Resource or Bundle mode. -Paste or upload FHIR JSON/XML (e.g., a Patient resource). -Submit to view validation errors/warnings. Note: Alpha feature; report issues to GitHub (remove PHI). -Push IGs to a FHIR Server -Go to Push IGs (/push-igs). - -Select a downloaded package. -Enter the Target FHIR Server URL. -Configure Authentication (None, Bearer Token). -Choose options: Include Dependencies, Force Upload (skips comparison check), Dry Run, Verbose Log. -Optionally filter by Resource Types (comma-separated) or Skip Specific Files (paths within package, comma/newline separated). -Click Push to FHIR Server to upload resources. Canonical resources are checked before upload. Identical resources are skipped unless Force Upload is checked. -Monitor progress in the live console. -Upload Test Data -Navigate to Upload Test Data (/upload-test-data). - -Enter the Target FHIR Server URL. -Configure Authentication (None, Bearer Token). -Select one or more .json, .xml files, or a single .zip file containing test resources. -Optionally check Validate Resources Before Upload? and select a Validation Profile Package. -Choose Upload Mode: -Individual Resources: Uploads each resource one by one in dependency order. -Transaction Bundle: Uploads all resources in a single transaction. -Optionally check Use Conditional Upload (Individual Mode Only)? to use If-Match headers for updates. -Choose Error Handling: -Stop on First Error: Halts the process if any validation or upload fails. -Continue on Error: Reports errors but attempts to process/upload remaining resources. -Click Upload and Process. The tool parses files, optionally validates, analyzes dependencies, topologically sorts resources, and uploads them according to selected options. -Monitor progress in the streaming log output. -Convert FHIR to FSH -Navigate to FSH Converter (/fsh-converter). - -Optionally select a package for context (e.g., hl7.fhir.au.core#1.1.0-preview). -Choose input mode: -Upload File: Upload a FHIR JSON/XML file. -Paste Text: Paste FHIR JSON/XML content. -Configure options: -Output Style: file-per-definition, group-by-fsh-type, group-by-profile, single-file. -Log Level: error, warn, info, debug. -FHIR Version: R4, R4B, R5, or auto-detect. -Fishing Trip: Enable round-trip validation with SUSHI, generating a comparison report. -Dependencies: Specify additional packages (e.g., hl7.fhir.us.core@6.1.0, one per line). -Indent Rules: Enable context path indentation for readable FSH. -Meta Profile: Choose only-one, first, or none for meta.profile handling. -Alias File: Upload an FSH file with aliases (e.g., $MyAlias = http://example.org). -No Alias: Disable automatic alias generation. -Click Convert to FSH to generate and display FSH output, with a waiting spinner (light/dark theme) during processing. -If Fishing Trip is enabled, view the comparison report via the "Click here for SUSHI Validation" badge button. -Download the result as a .fsh file. -Retrieve and Split Bundles -Navigate to Retrieve/Split Data (/retrieve-split-data). - -Retrieve Bundles from Server: - -Enter the FHIR Server URL (defaults to the proxy if empty). -Select one or more Resource Types to retrieve (e.g., Patient, Observation). -Optionally check Fetch Referenced Resources. -If checked, further optionally check Fetch Full Reference Bundles to retrieve entire bundles for each referenced type (e.g., all Patients if a Patient is referenced) instead of individual resources by ID. -Click Retrieve Bundles. -Monitor progress in the streaming log. A ZIP file containing the retrieved bundles/resources will be prepared for download. -Split Uploaded Bundles: - -Upload a ZIP file containing FHIR bundles (JSON format). -Click Split Bundles. -A ZIP file containing individual resources extracted from the bundles will be prepared for download. -Explore FHIR Operations -Navigate to FHIR UI Operations (/fhir-ui-operations). - -Toggle between local HAPI (/fhir) or a custom FHIR server. -Click Fetch Metadata to load the server’s CapabilityStatement. -Select a resource type (e.g., Patient, Observation) or System to view operations: -System operations: GET /metadata, POST /, GET /_history, GET/POST /$diff, POST /$reindex, POST /$expunge, etc. -Resource operations: GET Patient/:id, POST Observation/_search, etc. -Use Try it out to input parameters or request bodies, then Execute to view results in JSON, XML, or narrative formats. - -### Configure Embedded HAPI FHIR Server (Standalone Mode) -For users running the **Standalone version**, which includes an embedded HAPI FHIR server. -1. Navigate to **Configure HAPI FHIR** (`/config-hapi`). -2. The page displays the content of the HAPI FHIR server's `application.yaml` file. -3. You can edit the configuration directly in the text area. - * *Caution: Incorrect modifications can break the HAPI FHIR server.* -4. Click **Save Configuration** to apply your changes to the `application.yaml` file. -5. Click **Restart Tomcat** to restart the HAPI FHIR server and load the new configuration. The restart process may take a few moments. - -API Usage -Import IG -Bash - -curl -X POST http://localhost:5000/api/import-ig \ --H "Content-Type: application/json" \ --H "X-API-Key: your-api-key" \ --d '{"package_name": "hl7.fhir.au.core", "version": "1.1.0-preview", "dependency_mode": "recursive"}' -Returns complies_with_profiles, imposed_profiles, and duplicate_packages_present info. - -### Refresh Package Cache (Background Task) -```bash -curl -X POST http://localhost:5000/api/refresh-cache-task \ --H "X-API-Key: your-api-key" - -Push IG -Bash - -curl -X POST http://localhost:5000/api/push-ig \ --H "Content-Type: application/json" \ --H "Accept: application/x-ndjson" \ --H "X-API-Key: your-api-key" \ --d '{ - "package_name": "hl7.fhir.au.core", - "version": "1.1.0-preview", - "fhir_server_url": "http://localhost:8080/fhir", - "include_dependencies": true, - "force_upload": false, - "dry_run": false, - "verbose": false, - "auth_type": "none" - }' -Returns a streaming NDJSON response with progress and final summary. - -Upload Test Data -Bash - -curl -X POST http://localhost:5000/api/upload-test-data \ --H "X-API-Key: your-api-key" \ --H "Accept: application/x-ndjson" \ --F "fhir_server_url=http://your-fhir-server/fhir" \ --F "auth_type=bearerToken" \ --F "auth_token=YOUR_TOKEN" \ --F "upload_mode=individual" \ --F "error_handling=continue" \ --F "validate_before_upload=true" \ --F "validation_package_id=hl7.fhir.r4.core#4.0.1" \ --F "use_conditional_uploads=true" \ --F "test_data_files=@/path/to/your/patient.json" \ --F "test_data_files=@/path/to/your/observations.zip" -Returns a streaming NDJSON response with progress and final summary. Uses multipart/form-data for file uploads. - -Retrieve Bundles -Bash - -curl -X POST http://localhost:5000/api/retrieve-bundles \ --H "X-API-Key: your-api-key" \ --H "Accept: application/x-ndjson" \ --F "fhir_server_url=http://your-fhir-server/fhir" \ --F "resources=Patient" \ --F "resources=Observation" \ --F "validate_references=true" \ --F "fetch_reference_bundles=false" -Returns a streaming NDJSON response with progress. The X-Zip-Path header in the final response part will contain the path to download the ZIP archive (e.g., /tmp/retrieved_bundles_datetime.zip). - -Split Bundles -Bash - -curl -X POST http://localhost:5000/api/split-bundles \ --H "X-API-Key: your-api-key" \ --H "Accept: application/x-ndjson" \ --F "split_bundle_zip_path=@/path/to/your/bundles.zip" -Returns a streaming NDJSON response. The X-Zip-Path header in the final response part will contain the path to download the ZIP archive of split resources. - -Validate Resource/Bundle -Not yet exposed via API; use the UI at /validate-sample. - -Configuration Options -Located in app.py: - -VALIDATE_IMPOSED_PROFILES: (Default: True) Validates resources against imposed profiles during push. -DISPLAY_PROFILE_RELATIONSHIPS: (Default: True) Shows compliesWithProfile and imposeProfile in the UI. -FHIR_PACKAGES_DIR: (Default: /app/instance/fhir_packages) Stores .tgz packages and metadata. -UPLOAD_FOLDER: (Default: /app/static/uploads) Stores GoFSH output files and FSH comparison reports. -SECRET_KEY: Required for CSRF protection and sessions. Set via environment variable or directly. -API_KEY: Required for API authentication. Set via environment variable or directly. -MAX_CONTENT_LENGTH: (Default: Flask default) Max size for HTTP request body (e.g., 16 * 1024 * 1024 for 16MB). Important for large uploads. -MAX_FORM_PARTS: (Default: Werkzeug default, often 1000) Default max number of form parts. Overridden for /api/upload-test-data by CustomFormDataParser. - -### Get HAPI FHIR Configuration (Standalone Mode) -```bash -curl -X GET http://localhost:5000/api/config \ --H "X-API-Key: your-api-key" - -Save HAPI FHIR Configuration: -curl -X POST http://localhost:5000/api/config \ --H "Content-Type: application/json" \ --H "X-API-Key: your-api-key" \ --d '{"your_yaml_key": "your_value", ...}' # Send the full YAML content as JSON - -Restart HAPI FHIR Server: -curl -X POST http://localhost:5000/api/restart-tomcat \ --H "X-API-Key: your-api-key" - -Testing -The project includes a test suite covering UI, API, database, file operations, and security. - -Test Prerequisites: - -pytest: For running tests. -pytest-mock: For mocking dependencies. Install: pip install pytest pytest-mock -Running Tests: - -Bash - -cd -pytest tests/test_app.py -v -Test Coverage: - -UI Pages: Homepage, Import IG, Manage IGs, Push IGs, Validate Sample, View Processed IG, FSH Converter, Upload Test Data, Retrieve/Split Data. -API Endpoints: POST /api/import-ig, POST /api/push-ig, GET /get-structure, GET /get-example, POST /api/upload-test-data, POST /api/retrieve-bundles, POST /api/split-bundles. -Database: IG processing, unloading, viewing. -File Operations: Package processing, deletion, FSH output, ZIP handling. -Security: CSRF protection, flash messages, secret key. -FSH Converter: Form submission, file/text input, GoFSH execution, Fishing Trip comparison. -Upload Test Data: Parsing, dependency graph, sorting, upload modes, validation, conditional uploads. -Development Notes -Background -The toolkit addresses the need for a comprehensive FHIR IG management tool, with recent enhancements for resource validation, FSH conversion with advanced GoFSH features, flexible versioning, improved IG pushing, dependency-aware test data uploading, and bundle retrieval/splitting, making it a versatile platform for FHIR developers. - -Technical Decisions -Flask: Lightweight and flexible for web development. -SQLite: Simple for development; consider PostgreSQL for production. -Bootstrap 5.3.3: Responsive UI with custom styling. -Lottie-Web: Renders themed animations for FSH conversion waiting spinner. -GoFSH/SUSHI: Integrated via Node.js for advanced FSH conversion and round-trip validation. -Docker: Ensures consistent deployment with Flask and HAPI FHIR. -Flexible Versioning: Supports non-standard IG versions (e.g., -preview, -ballot). -Live Console/Streaming: Real-time feedback for complex operations (Push, Upload Test Data, FSH, Retrieve Bundles). -Validation: Alpha feature with ongoing FHIRPath improvements. -Dependency Management: Uses topological sort for Upload Test Data feature. -Form Parsing: Uses custom Werkzeug parser for Upload Test Data to handle large numbers of files. -Recent Updates -* Enhanced package search page with caching, detailed views (dependencies, dependents, version history), and background cache refresh. -Upload Test Data Enhancements (April 2025): -Added optional Pre-Upload Validation against selected IG profiles. -Added optional Conditional Uploads (GET + POST/PUT w/ If-Match) for individual mode. -Implemented robust XML parsing using fhir.resources library (when available). -Fixed 413 Request Entity Too Large errors for large file counts using a custom Werkzeug FormDataParser. -Path: templates/upload_test_data.html, app.py, services.py, forms.py. -Push IG Enhancements (April 2025): -Added semantic comparison to skip uploading identical resources. -Added "Force Upload" option to bypass comparison. -Improved handling of canonical resources (search before PUT/POST). -Added filtering by specific files to skip during push. -More detailed summary report in stream response. -Path: templates/cp_push_igs.html, app.py, services.py. -Waiting Spinner for FSH Converter (April 2025): -Added a themed (light/dark) Lottie animation spinner during FSH execution. -Path: templates/fsh_converter.html, static/animations/, static/js/lottie-web.min.js. -Advanced FSH Converter (April 2025): -Added support for GoFSH advanced options: --fshing-trip, --dependency, --indent, --meta-profile, --alias-file, --no-alias. -Displays Fishing Trip comparison reports. -Path: templates/fsh_converter.html, app.py, services.py, forms.py. -(New) Retrieve and Split Data (May 2025): -Added UI and API for retrieving bundles from a FHIR server by resource type. -Added options to fetch referenced resources (individually or as full type bundles). -Added functionality to split uploaded ZIP files of bundles into individual resources. -Streaming log for retrieval and ZIP download for results. -Paths: templates/retrieve_split_data.html, app.py, services.py, forms.py. -Known Issues and Workarounds -Favicon 404: Clear browser cache or verify /app/static/favicon.ico. -CSRF Errors: Set FLASK_SECRET_KEY and ensure {{ form.hidden_tag() }} in forms. -Import Fails: Check package name/version and connectivity. -Validation Accuracy: Alpha feature; report issues to GitHub (remove PHI). -Package Parsing: Non-standard .tgz filenames may parse incorrectly. Fallback uses name-only parsing. -Permissions: Ensure instance/ and static/uploads/ are writable. -GoFSH/SUSHI Errors: Check ./logs/flask_err.log for ERROR:services:GoFSH failed. Ensure valid FHIR inputs and SUSHI installation. -Upload Test Data XML Parsing: Relies on fhir.resources library for full validation; basic parsing used as fallback. Complex XML structures might not be fully analyzed for dependencies with basic parsing. Prefer JSON for reliable dependency analysis. -413 Request Entity Too Large: Primarily handled by CustomFormDataParser for /api/upload-test-data. Check the parser's max_form_parts limit if still occurring. MAX_CONTENT_LENGTH in app.py controls overall size. Reverse proxy limits (client_max_body_size in Nginx) might also apply. - - -Future Improvements -Upload Test Data: Improve XML parsing further (direct XML->fhir.resource object if possible), add visual progress bar, add upload order preview, implement transaction bundle size splitting, add 'Clear Target Server' option (with confirmation). -Validation: Enhance FHIRPath for complex constraints; add API endpoint. -Sorting: Sort IG versions in /view-igs (e.g., ascending). -Duplicate Resolution: Options to keep latest version or merge resources. -Production Database: Support PostgreSQL. -Error Reporting: Detailed validation error paths in the UI. -FSH Enhancements: Add API endpoint for FSH conversion; support inline instance construction. -FHIR Operations: Add complex parameter support (e.g., /$diff with left/right). -Retrieve/Split Data: Add option to filter resources during retrieval (e.g., by date, specific IDs). -Completed Items -Testing suite with basic coverage. -API endpoints for POST /api/import-ig and POST /api/push-ig. -Flexible versioning (-preview, -ballot). -CSRF fixes for forms. -Resource validation UI (alpha). -FSH Converter with advanced GoFSH features and waiting spinner. -Push IG enhancements (force upload, semantic comparison, canonical handling, skip files). -Upload Test Data feature with dependency sorting, multiple upload modes, pre-upload validation, conditional uploads, robust XML parsing, and fix for large file counts. -Retrieve and Split Data functionality with reference fetching and ZIP download. -Far-Distant Improvements -Cache Service: Use Redis for IG metadata caching. -Database Optimization: Composite index on ProcessedIg.package_name and ProcessedIg.version. - - -Directory Structure -FHIRFLARE-IG-Toolkit/ -├── app.py # Main Flask application -├── Build and Run for first time.bat # Windows script for first-time Docker setup -├── docker-compose.yml # Docker Compose configuration -├── Dockerfile # Docker configuration -├── forms.py # Form definitions -├── LICENSE.md # Apache 2.0 License -├── README.md # Project documentation -├── requirements.txt # Python dependencies -├── Run.bat # Windows script for running Docker -├── services.py # Logic for IG import, processing, validation, pushing, FSH conversion, test data upload, retrieve/split -├── supervisord.conf # Supervisor configuration -├── hapi-fhir-Setup/ -│ ├── README.md # HAPI FHIR setup instructions -│ └── target/ -│ └── classes/ -│ └── application.yaml # HAPI FHIR configuration -├── instance/ -│ ├── fhir_ig.db # SQLite database -│ ├── fhir_ig.db.old # Database backup -│ └── fhir_packages/ # Stored IG packages and metadata -│ ├── ... (example packages) ... -├── logs/ -│ ├── flask.log # Flask application logs -│ ├── flask_err.log # Flask error logs -│ ├── supervisord.log # Supervisor logs -│ ├── supervisord.pid # Supervisor PID file -│ ├── tomcat.log # Tomcat logs for HAPI FHIR -│ └── tomcat_err.log # Tomcat error logs -├── static/ -│ ├── animations/ -│ │ ├── loading-dark.json # Dark theme spinner animation -│ │ └── loading-light.json # Light theme spinner animation -│ ├── favicon.ico # Application favicon -│ ├── FHIRFLARE.png # Application logo -│ ├── js/ -│ │ └── lottie-web.min.js # Lottie library for spinner -│ └── uploads/ -│ ├── output.fsh # Generated FSH output (temp location) -│ └── fsh_output/ # GoFSH output directory -│ ├── ... (example GoFSH output) ... -├── templates/ -│ ├── base.html # Base template -│ ├── cp_downloaded_igs.html # UI for managing IGs -│ ├── cp_push_igs.html # UI for pushing IGs -│ ├── cp_view_processed_ig.html # UI for viewing processed IGs -│ ├── fhir_ui.html # UI for FHIR API explorer -│ ├── fhir_ui_operations.html # UI for FHIR server operations -│ ├── fsh_converter.html # UI for FSH conversion -│ ├── import_ig.html # UI for importing IGs -│ ├── index.html # Homepage -│ ├── retrieve_split_data.html # UI for Retrieve and Split Data -│ ├── upload_test_data.html # UI for Uploading Test Data -│ ├── validate_sample.html # UI for validating resources/bundles -│ ├── config_hapi.html # UI for HAPI FHIR Configuration -│ └── _form_helpers.html # Form helper macros -├── tests/ -│ └── test_app.py # Test suite -└── hapi-fhir-jpaserver/ # HAPI FHIR server resources (if Standalone) - -Contributing -Fork the repository. -Create a feature branch (git checkout -b feature/your-feature). -Commit changes (git commit -m "Add your feature"). -Push to your branch (git push origin feature/your-feature). -Open a Pull Request. -Ensure code follows PEP 8 and includes tests in tests/test_app.py. - -Troubleshooting -Favicon 404: Clear browser cache or verify /app/static/favicon.ico: docker exec -it curl http://localhost:5000/static/favicon.ico -CSRF Errors: Set FLASK_SECRET_KEY and ensure {{ form.hidden_tag() }} in forms. -Import Fails: Check package name/version and connectivity. -Validation Accuracy: Alpha feature; report issues to GitHub (remove PHI). -Package Parsing: Non-standard .tgz filenames may parse incorrectly. Fallback uses name-only parsing. -Permissions: Ensure instance/ and static/uploads/ are writable: chmod -R 777 instance static/uploads logs -GoFSH/SUSHI Errors: Check ./logs/flask_err.log for ERROR:services:GoFSH failed. Ensure valid FHIR inputs and SUSHI installation: docker exec -it sushi --version -413 Request Entity Too Large: Increase MAX_CONTENT_LENGTH and MAX_FORM_PARTS in app.py. If using a reverse proxy (e.g., Nginx), increase its client_max_body_size setting as well. Ensure the application/container is fully restarted/rebuilt. -License -Licensed under the Apache 2.0 License. See LICENSE.md for details. \ No newline at end of file diff --git a/README_INTEGRATION FHIRVINE as Moduel in FLARE.md b/README_INTEGRATION FHIRVINE as Moduel in FLARE.md deleted file mode 100644 index 650805a..0000000 --- a/README_INTEGRATION FHIRVINE as Moduel in FLARE.md +++ /dev/null @@ -1,151 +0,0 @@ -# Integrating FHIRVINE as a Module in FHIRFLARE - -## Overview - -FHIRFLARE is a Flask-based FHIR Implementation Guide (IG) toolkit for managing and validating FHIR packages. This guide explains how to integrate FHIRVINE—a SMART on FHIR proxy—as a module within FHIRFLARE, enabling OAuth2 authentication and FHIR request proxying directly in the application. This modular approach embeds FHIRVINE’s functionality into FHIRFLARE, avoiding the need for a separate proxy service. - -## Prerequisites - -- FHIRFLARE repository cloned: `https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit`. -- FHIRVINE repository cloned: ``. -- Python 3.11 and dependencies installed (`requirements.txt` from both projects). -- A FHIR server (e.g., `http://hapi.fhir.org/baseR4`). - -## Integration Steps - -### 1. Prepare FHIRFLARE Structure - -Ensure FHIRFLARE’s file structure supports modular integration. It should look like: - -``` -FHIRFLARE-IG-Toolkit/ -├── app.py -├── services.py -├── templates/ -├── static/ -└── requirements.txt -``` - -### 2. Copy FHIRVINE Files into FHIRFLARE - -FHIRVINE’s core functionality (OAuth2 proxy, app registration) will be integrated as a Flask Blueprint. - -- **Copy Files**: - - - Copy `smart_proxy.py`, `forms.py`, `models.py`, and `app.py` (relevant parts) from FHIRVINE into a new `fhirvine/` directory in FHIRFLARE: - - ``` - FHIRFLARE-IG-Toolkit/ - ├── fhirvine/ - │ ├── smart_proxy.py - │ ├── forms.py - │ ├── models.py - │ └── __init__.py - ``` - - - Copy FHIRVINE’s templates (e.g., `app_gallery/`, `configure/`, `test_client.html`) into `FHIRFLARE-IG-Toolkit/templates/` while maintaining their folder structure. - -- **Add Dependencies**: - - - Add FHIRVINE’s dependencies to `requirements.txt` (e.g., `authlib`, `flasgger`, `flask-sqlalchemy`). - -### 3. Modify FHIRVINE Code as a Module - -- **Create Blueprint in** `fhirvine/__init__.py`: - - ```python - from flask import Blueprint - - fhirvine_bp = Blueprint('fhirvine', __name__, template_folder='templates') - - from .smart_proxy import * - ``` - - This registers FHIRVINE as a Flask Blueprint. - -- **Update** `smart_proxy.py`: - - - Replace direct `app.route` decorators with `fhirvine_bp.route`. For example: - - ```python - @fhirvine_bp.route('/authorize', methods=['GET', 'POST']) - def authorize(): - # Existing authorization logic - ``` - -### 4. Integrate FHIRVINE Blueprint into FHIRFLARE - -- **Update** `app.py` **in FHIRFLARE**: - - - Import and register the FHIRVINE Blueprint: - - ```python - from fhirvine import fhirvine_bp - from fhirvine.models import database, RegisteredApp, OAuthToken, AuthorizationCode, Configuration - from fhirvine.smart_proxy import configure_oauth - - app = Flask(__name__) - app.config.from_mapping( - SECRET_KEY='your-secure-random-key', - SQLALCHEMY_DATABASE_URI='sqlite:////app/instance/fhirflare.db', - SQLALCHEMY_TRACK_MODIFICATIONS=False, - FHIR_SERVER_URL='http://hapi.fhir.org/baseR4', - PROXY_TIMEOUT=10, - TOKEN_DURATION=3600, - REFRESH_TOKEN_DURATION=86400, - ALLOWED_SCOPES='openid profile launch launch/patient patient/*.read offline_access' - ) - - database.init_app(app) - configure_oauth(app, db=database, registered_app_model=RegisteredApp, oauth_token_model=OAuthToken, auth_code_model=AuthorizationCode) - - app.register_blueprint(fhirvine_bp, url_prefix='/fhirvine') - ``` - -### 5. Update FHIRFLARE Templates - -- **Add FHIRVINE Links to Navbar**: - - - In `templates/base.html`, add links to FHIRVINE features: - - ```html - - - ``` - -### 6. Run and Test - -- **Install Dependencies**: - - ```bash - pip install -r requirements.txt - ``` - -- **Run FHIRFLARE**: - - ```bash - flask db upgrade - flask run --host=0.0.0.0 --port=8080 - ``` - -- **Access FHIRVINE Features**: - - - App Gallery: `http://localhost:8080/fhirvine/app-gallery` - - Test Client: `http://localhost:8080/fhirvine/test-client` - - Proxy Requests: Use `/fhirvine/oauth2/proxy/` within FHIRFLARE. - -## Using FHIRVINE in FHIRFLARE - -- **Register Apps**: Use `/fhirvine/app-gallery` to register SMART apps within FHIRFLARE. -- **Authenticate**: Use `/fhirvine/oauth2/authorize` for OAuth2 flows. -- **Proxy FHIR Requests**: FHIRFLARE can now make FHIR requests via `/fhirvine/oauth2/proxy`, leveraging FHIRVINE’s authentication. - -## Troubleshooting - -- **Route Conflicts**: Ensure no overlapping routes between FHIRFLARE and FHIRVINE. -- **Database Issues**: Verify `SQLALCHEMY_DATABASE_URI` points to the same database. -- **Logs**: Check `flask run` logs for errors. \ No newline at end of file diff --git a/Starting b/Starting deleted file mode 100644 index 2eefb56..0000000 --- a/Starting +++ /dev/null @@ -1 +0,0 @@ -=== Docker containers (Step 7)... diff --git a/app.py b/app.py deleted file mode 100644 index 8790493..0000000 --- a/app.py +++ /dev/null @@ -1,3096 +0,0 @@ -import sys -import os -# Make paths relative to the current directory instead of absolute '/app' paths -CURRENT_DIR = os.path.abspath(os.path.dirname(__file__)) -# Introduce app_dir variable that can be overridden by environment -app_dir = os.environ.get('APP_DIR', CURRENT_DIR) -sys.path.append(CURRENT_DIR) -import datetime -import shutil -import queue -from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, Response, current_app, session, send_file, make_response, g -from flask_sqlalchemy import SQLAlchemy -from flask_migrate import Migrate -from flask_wtf import FlaskForm -from flask_wtf.csrf import CSRFProtect -from werkzeug.utils import secure_filename -from werkzeug.formparser import FormDataParser -from werkzeug.exceptions import RequestEntityTooLarge -from urllib.parse import urlparse -from cachetools import TTLCache -from types import SimpleNamespace -import tarfile -import base64 -import json -import logging -import requests -import re -import yaml -import threading -import time # Add time import -import services -from services import ( - services_bp, - construct_tgz_filename, - parse_package_filename, - import_package_and_dependencies, - retrieve_bundles, - split_bundles, - fetch_packages_from_registries, - normalize_package_data, - cache_packages, - HAS_PACKAGING_LIB, - pkg_version, - get_package_description, - safe_parse_version, - import_manual_package_and_dependencies -) -from forms import IgImportForm, ManualIgImportForm, ValidationForm, FSHConverterForm, TestDataUploadForm, RetrieveSplitDataForm -from wtforms import SubmitField -from package import package_bp -from flasgger import Swagger, swag_from # Import Flasgger -from copy import deepcopy -import tempfile -from logging.handlers import RotatingFileHandler - -#app setup -app = Flask(__name__) -app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'your-fallback-secret-key-here') - -# Update paths to be relative to current directory -instance_path = os.path.join(CURRENT_DIR, 'instance') -app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL', f'sqlite:///{os.path.join(instance_path, "fhir_ig.db")}') -app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False -app.config['FHIR_PACKAGES_DIR'] = os.path.join(instance_path, 'fhir_packages') -app.config['API_KEY'] = os.environ.get('API_KEY', 'your-fallback-api-key-here') -app.config['VALIDATE_IMPOSED_PROFILES'] = True -app.config['DISPLAY_PROFILE_RELATIONSHIPS'] = True -app.config['UPLOAD_FOLDER'] = os.path.join(CURRENT_DIR, 'static', 'uploads') # For GoFSH output -app.config['APP_BASE_URL'] = os.environ.get('APP_BASE_URL', 'http://localhost:5000') -app.config['HAPI_FHIR_URL'] = os.environ.get('HAPI_FHIR_URL', 'http://localhost:8080/fhir') -CONFIG_PATH = os.environ.get('CONFIG_PATH', '/usr/local/tomcat/conf/application.yaml') - -# Basic Swagger configuration -app.config['SWAGGER'] = { - 'title': 'FHIRFLARE IG Toolkit API', - 'uiversion': 3, # Use Swagger UI 3 - 'version': '1.0.0', - 'description': 'API documentation for the FHIRFLARE IG Toolkit. This provides access to various FHIR IG management and validation functionalities.', - 'termsOfService': 'https://example.com/terms', # Replace with your terms - 'contact': { - 'name': 'FHIRFLARE Support', - 'url': 'https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit/issues', # Replace with your support URL - 'email': 'xsannz@gmail.com', # Replace with your support email - }, - 'license': { - 'name': 'MIT License', # Or your project's license - 'url': 'https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit/blob/main/LICENSE.md', # Link to your license - }, - 'securityDefinitions': { # Defines how API key security is handled - 'ApiKeyAuth': { - 'type': 'apiKey', - 'name': 'X-API-Key', # The header name for the API key - 'in': 'header', - 'description': 'API Key for accessing protected endpoints.' - } - }, - # 'security': [{'ApiKeyAuth': []}], # Optional: Apply ApiKeyAuth globally to all Flasgger-documented API endpoints by default - # If you set this, individual public endpoints would need 'security': [] in their swag_from spec. - # It's often better to define security per-endpoint in @swag_from. - 'specs_route': '/apidocs/' # URL for the Swagger UI. This makes url_for('flasgger.apidocs') work. -} -swagger = Swagger(app) # Initialize Flasgger with the app. This registers its routes. - - -# Register blueprints immediately after app setup -app.register_blueprint(services_bp, url_prefix='/api') -app.register_blueprint(package_bp) -logging.getLogger(__name__).info("Registered package_bp blueprint") - - - -# Set max upload size (e.g., 12 MB, adjust as needed) -app.config['MAX_CONTENT_LENGTH'] = 6 * 1024 * 1024 - -# In-memory cache with 5-minute TTL -package_cache = TTLCache(maxsize=100, ttl=300) - -# Increase max number of form parts (default is often 1000) -#app.config['MAX_FORM_PARTS'] = 1000 # Allow up to 1000 parts this is a hard coded stop limit in MAX_FORM_PARTS of werkzeug - - -#----------------------------------------------------------------------------------------------------------------------- -# --- Basic Logging Setup (adjust level and format as needed) --- -# Configure root logger first - This sets the foundation -# Set level to DEBUG initially to capture everything, handlers can filter later -logging.basicConfig(level=logging.DEBUG, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', - # Force=True might be needed if basicConfig was called elsewhere implicitly - # force=True - ) - -# Get the application logger (for app-specific logs) -logger = logging.getLogger(__name__) -# Explicitly set the app logger's level (can be different from root) -logger.setLevel(logging.DEBUG) - -# --- Optional: Add File Handler for Debugging --- -# Ensure the instance path exists before setting up the file handler -# Note: This assumes app.instance_path is correctly configured later -# If running this setup *before* app = Flask(), define instance path manually. -instance_folder_path_for_log = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'instance') -os.makedirs(instance_folder_path_for_log, exist_ok=True) -log_file_path = os.path.join(instance_folder_path_for_log, 'fhirflare_debug.log') - -file_handler = None # Initialize file_handler to None -try: - # Rotate logs: 5 files, 5MB each - file_handler = RotatingFileHandler(log_file_path, maxBytes=5*1024*1024, backupCount=5, encoding='utf-8') - file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')) - # Set the file handler level - DEBUG will capture everything - file_handler.setLevel(logging.DEBUG) - # Add handler to the *root* logger to capture logs from all modules (like services) - logging.getLogger().addHandler(file_handler) - logger.info(f"--- File logging initialized to {log_file_path} (Level: DEBUG) ---") -except Exception as e: - # Log error if file handler setup fails, but continue execution - logger.error(f"Failed to set up file logging to {log_file_path}: {e}", exc_info=True) -# --- End File Handler Setup --- - -#----------------------------------------------------------------------------------------------------------------------- - -try: - import packaging.version as pkg_version - HAS_PACKAGING_LIB = True -except ImportError: - HAS_PACKAGING_LIB = False - # Define a simple fallback parser if needed - class BasicVersion: - def __init__(self, v_str): self.v_str = str(v_str) # Ensure string - def __gt__(self, other): return self.v_str > str(other) - def __lt__(self, other): return self.v_str < str(other) - def __eq__(self, other): return self.v_str == str(other) - def __str__(self): return self.v_str - pkg_version = SimpleNamespace(parse=BasicVersion, InvalidVersion=ValueError) -# --- End Imports --- - - -# --- NEW: Define Custom Form Parser --- -class CustomFormDataParser(FormDataParser): - """Subclass to increase the maximum number of form parts.""" - def __init__(self, *args, **kwargs): - # Set a higher limit for max_form_parts. Adjust value as needed. - # This overrides the default limit checked by Werkzeug's parser. - # Set to a sufficiently high number for your expected maximum file count. - super().__init__(*args, max_form_parts=2000, **kwargs) # Example: Allow 2000 parts -# --- END NEW --- - -# Custom logging handler to capture INFO logs from services module -log_queue = queue.Queue() -class StreamLogHandler(logging.Handler): - def __init__(self): - super().__init__(level=logging.INFO) - self.formatter = logging.Formatter('%(levelname)s:%(name)s:%(message)s') - - def emit(self, record): - if record.name == 'services' and record.levelno == logging.INFO: - msg = self.format(record) - log_queue.put(msg) - -# Add custom handler to services logger -services_logger = logging.getLogger('services') -stream_handler = StreamLogHandler() -services_logger.addHandler(stream_handler) - -# <<< ADD THIS CONTEXT PROCESSOR >>> -@app.context_processor -def inject_app_mode(): - """Injects the app_mode into template contexts.""" - return dict(app_mode=app.config.get('APP_MODE', 'standalone')) -# <<< END ADD >>> - -# Read application mode from environment variable, default to 'standalone' -app.config['APP_MODE'] = os.environ.get('APP_MODE', 'standalone').lower() -logger.info(f"Application running in mode: {app.config['APP_MODE']}") -# --- END mode check --- - -# Ensure directories exist and are writable -instance_path = '/app/instance' -packages_path = app.config['FHIR_PACKAGES_DIR'] -logger.debug(f"Instance path configuration: {instance_path}") -logger.debug(f"Database URI: {app.config['SQLALCHEMY_DATABASE_URI']}") -logger.debug(f"Packages path: {packages_path}") - -try: - instance_folder_path = app.instance_path - logger.debug(f"Flask instance folder path: {instance_folder_path}") - os.makedirs(instance_folder_path, exist_ok=True) - os.makedirs(packages_path, exist_ok=True) - os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) - logger.debug(f"Directories created/verified: Instance: {instance_folder_path}, Packages: {packages_path}") -except Exception as e: - logger.error(f"Failed to create/verify directories: {e}", exc_info=True) - -db = SQLAlchemy(app) -csrf = CSRFProtect(app) -migrate = Migrate(app, db) - -# Add a global application state dictionary for sharing state between threads -app_state = { - 'fetch_failed': False -} - -# @app.route('/clear-cache') -# def clear_cache(): -# """Clears the in-memory package cache, the DB timestamp, and the CachedPackage table.""" -# # Clear in-memory cache -# app.config['MANUAL_PACKAGE_CACHE'] = None -# app.config['MANUAL_CACHE_TIMESTAMP'] = None -# logger.info("In-memory package cache cleared.") - -# # Clear DB timestamp and CachedPackage table -# try: -# # Clear the timestamp -# timestamp_info = RegistryCacheInfo.query.first() -# if timestamp_info: -# timestamp_info.last_fetch_timestamp = None -# db.session.commit() -# logger.info("Database timestamp cleared.") -# else: -# logger.info("No database timestamp found to clear.") - -# # Clear the CachedPackage table -# num_deleted = db.session.query(CachedPackage).delete() -# db.session.commit() -# logger.info(f"Cleared {num_deleted} entries from CachedPackage table.") -# except Exception as db_err: -# db.session.rollback() -# logger.error(f"Failed to clear DB timestamp or CachedPackage table: {db_err}", exc_info=True) -# flash("Failed to clear database cache.", "warning") - -# flash("Package cache cleared. Fetching fresh list from registries...", "info") -# # Redirect back to the search page to force a reload and fetch -# return redirect(url_for('search_and_import')) - -# Remove logic from /clear-cache route - it's now handled by the API + background task -@app.route('/clear-cache') -def clear_cache(): - """ - This route is now effectively deprecated if the button uses the API. - If accessed directly, it could just redirect or show a message. - For safety, let it clear only the in-memory part and redirect. - """ - app.config['MANUAL_PACKAGE_CACHE'] = None - app.config['MANUAL_CACHE_TIMESTAMP'] = None - session['fetch_failed'] = False # Reset flag - logger.info("Direct /clear-cache access: Cleared in-memory cache only.") - flash("Cache refresh must be initiated via the 'Clear & Refresh Cache' button.", "info") - return redirect(url_for('search_and_import')) - -# No changes needed in search_and_import logic itself for this fix. - -class ProcessedIg(db.Model): - id = db.Column(db.Integer, primary_key=True) - package_name = db.Column(db.String(128), nullable=False) - version = db.Column(db.String(64), nullable=False) - processed_date = db.Column(db.DateTime, nullable=False) - resource_types_info = db.Column(db.JSON, nullable=False) - must_support_elements = db.Column(db.JSON, nullable=True) - examples = db.Column(db.JSON, nullable=True) - complies_with_profiles = db.Column(db.JSON, nullable=True) - imposed_profiles = db.Column(db.JSON, nullable=True) - optional_usage_elements = db.Column(db.JSON, nullable=True) - # --- ADD THIS LINE --- - search_param_conformance = db.Column(db.JSON, nullable=True) # Stores the extracted conformance map - # --- END ADD --- - __table_args__ = (db.UniqueConstraint('package_name', 'version', name='uq_package_version'),) - -class CachedPackage(db.Model): - id = db.Column(db.Integer, primary_key=True) - package_name = db.Column(db.String(128), nullable=False) - version = db.Column(db.String(64), nullable=False) - author = db.Column(db.String(128)) - fhir_version = db.Column(db.String(64)) - version_count = db.Column(db.Integer) - url = db.Column(db.String(256)) - all_versions = db.Column(db.JSON, nullable=True) - dependencies = db.Column(db.JSON, nullable=True) - latest_absolute_version = db.Column(db.String(64)) - latest_official_version = db.Column(db.String(64)) - canonical = db.Column(db.String(256)) - registry = db.Column(db.String(256)) - __table_args__ = (db.UniqueConstraint('package_name', 'version', name='uq_cached_package_version'),) - -class RegistryCacheInfo(db.Model): - id = db.Column(db.Integer, primary_key=True) # Simple primary key - last_fetch_timestamp = db.Column(db.DateTime(timezone=True), nullable=True) # Store UTC timestamp - - def __repr__(self): - return f'' - -# --- Make sure to handle database migration if you use Flask-Migrate --- -# (e.g., flask db migrate -m "Add search_param_conformance to ProcessedIg", flask db upgrade) -# If not using migrations, you might need to drop and recreate the table (losing existing processed data) -# or manually alter the table using SQLite tools. - -def check_api_key(): - api_key = request.headers.get('X-API-Key') - if not api_key and request.is_json: - api_key = request.json.get('api_key') - if not api_key: - logger.error("API key missing in request") - return jsonify({"status": "error", "message": "API key missing"}), 401 - if api_key != app.config['API_KEY']: - logger.error("Invalid API key provided.") - return jsonify({"status": "error", "message": "Invalid API key"}), 401 - logger.debug("API key validated successfully") - return None - -def list_downloaded_packages(packages_dir): - packages = [] - errors = [] - duplicate_groups = {} - logger.debug(f"Scanning packages directory: {packages_dir}") - if not os.path.exists(packages_dir): - logger.warning(f"Packages directory not found: {packages_dir}") - return packages, errors, duplicate_groups - for filename in os.listdir(packages_dir): - if filename.endswith('.tgz'): - full_path = os.path.join(packages_dir, filename) - name = filename[:-4] - version = '' - parsed_name, parsed_version = services.parse_package_filename(filename) - if parsed_name: - name = parsed_name - version = parsed_version - else: - logger.warning(f"Could not parse version from {filename}, using default name.") - errors.append(f"Could not parse {filename}") - try: - with tarfile.open(full_path, "r:gz") as tar: - # Ensure correct path within tarfile - pkg_json_member_path = "package/package.json" - try: - pkg_json_member = tar.getmember(pkg_json_member_path) - fileobj = tar.extractfile(pkg_json_member) - if fileobj: - pkg_data = json.loads(fileobj.read().decode('utf-8-sig')) - name = pkg_data.get('name', name) - version = pkg_data.get('version', version) - fileobj.close() - except KeyError: - logger.warning(f"{pkg_json_member_path} not found in {filename}") - # Keep parsed name/version if package.json is missing - except (tarfile.TarError, json.JSONDecodeError, UnicodeDecodeError) as e: - logger.warning(f"Could not read package.json from {filename}: {e}") - errors.append(f"Error reading {filename}: {str(e)}") - except Exception as e: - logger.error(f"Unexpected error reading package.json from {filename}: {e}", exc_info=True) - errors.append(f"Unexpected error for {filename}: {str(e)}") - - if name and version: # Only add if both name and version are valid - packages.append({'name': name, 'version': version, 'filename': filename}) - else: - logger.warning(f"Skipping package {filename} due to invalid name ('{name}') or version ('{version}')") - errors.append(f"Invalid package {filename}: name='{name}', version='{version}'") - - # Group duplicates - name_counts = {} - for pkg in packages: - name_val = pkg['name'] - name_counts[name_val] = name_counts.get(name_val, 0) + 1 - for name_val, count in name_counts.items(): - if count > 1: - duplicate_groups[name_val] = sorted([p['version'] for p in packages if p['name'] == name_val]) - - logger.debug(f"Found packages: {len(packages)}") - logger.debug(f"Errors during package listing: {errors}") - logger.debug(f"Duplicate groups: {duplicate_groups}") - return packages, errors, duplicate_groups - -@app.route('/') -def index(): - return render_template('index.html', site_name='FHIRFLARE IG Toolkit', now=datetime.datetime.now()) - -@app.route('/debug-routes') -@swag_from({ - 'tags': ['Debugging'], - 'summary': 'List all application routes.', - 'description': 'Provides a JSON list of all registered URL rules and their endpoints. Useful for development and debugging.', - 'responses': { - '200': { - 'description': 'A list of route strings.', - 'schema': { - 'type': 'array', - 'items': { - 'type': 'string', - 'example': 'Endpoint: my_endpoint, Methods: GET,POST, URL: /my/url' - } - } - } - } - # No API key needed for this one, so you can add: - # 'security': [] -}) -def debug_routes(): - """ - Debug endpoint to list all registered routes and their endpoints. - """ - routes = [] - for rule in app.url_map.iter_rules(): - routes.append(f"Endpoint: {rule.endpoint}, URL: {rule}") - return jsonify(routes) - -@app.route('/api/config', methods=['GET']) -@csrf.exempt -@swag_from({ - 'tags': ['HAPI Configuration'], - 'summary': 'Get HAPI FHIR server configuration.', - 'description': 'Retrieves the current HAPI FHIR server configuration from the application.yaml file.', - 'security': [{'ApiKeyAuth': []}], # Requires API Key - 'responses': { - '200': { - 'description': 'HAPI FHIR configuration.', - 'schema': { 'type': 'object' } # You can be more specific if you know the YAML structure - }, - '500': {'description': 'Error reading configuration file.'} - } -}) -def get_config(): - try: - with open(CONFIG_PATH, 'r') as file: - config = yaml.safe_load(file) - return jsonify(config) - except Exception as e: - logger.error(f"Error reading config file: {e}") - return jsonify({'error': str(e)}), 500 - -@app.route('/api/config', methods=['POST']) -@csrf.exempt -@swag_from({ - 'tags': ['HAPI Configuration'], - 'summary': 'Save HAPI FHIR server configuration.', - 'description': 'Saves the provided HAPI FHIR server configuration to the application.yaml file.', - 'security': [{'ApiKeyAuth': []}], # Requires API Key - 'parameters': [ - { - 'name': 'config_payload', # Changed name to avoid conflict with function arg - 'in': 'body', - 'required': True, - 'description': 'The HAPI FHIR configuration object.', - 'schema': { - 'type': 'object', - # Add example properties if you know them - 'example': {'fhir_server': {'base_url': 'http://localhost:8080/fhir'}} - } - } - ], - 'responses': { - '200': {'description': 'Configuration saved successfully.'}, - '400': {'description': 'Invalid request body.'}, - '500': {'description': 'Error saving configuration file.'} - } -}) -def save_config(): - try: - config = request.get_json() - with open(CONFIG_PATH, 'w') as file: - yaml.safe_dump(config, file, default_flow_style=False) - logger.info("Configuration saved successfully") - return jsonify({'message': 'Configuration saved'}) - except Exception as e: - logger.error(f"Error saving config file: {e}") - return jsonify({'error': str(e)}), 500 - -@app.route('/api/restart-tomcat', methods=['POST']) -@csrf.exempt -@swag_from({ - 'tags': ['HAPI Configuration'], - 'summary': 'Restart the Tomcat server.', - 'description': 'Attempts to restart the Tomcat server using supervisorctl. Requires appropriate server permissions.', - 'security': [{'ApiKeyAuth': []}], # Requires API Key - 'responses': { - '200': {'description': 'Tomcat restart initiated successfully.'}, - '500': {'description': 'Error restarting Tomcat (e.g., supervisorctl not found or command failed).'} - } -}) -def restart_tomcat(): - try: - result = subprocess.run(['supervisorctl', 'restart', 'tomcat'], capture_output=True, text=True) - if result.returncode == 0: - logger.info("Tomcat restarted successfully") - return jsonify({'message': 'Tomcat restarted'}) - else: - logger.error(f"Failed to restart Tomcat: {result.stderr}") - return jsonify({'error': result.stderr}), 500 - except Exception as e: - logger.error(f"Error restarting Tomcat: {e}") - return jsonify({'error': str(e)}), 500 - -@app.route('/config-hapi') -def config_hapi(): - return render_template('config_hapi.html', site_name='FHIRFLARE IG Toolkit', now=datetime.datetime.now()) - -@app.route('/manual-import-ig', methods=['GET', 'POST']) -def manual_import_ig(): - """ - Handle manual import of FHIR Implementation Guides using file or URL uploads. - Uses ManualIgImportForm to support file and URL inputs without registry option. - """ - form = ManualIgImportForm() - is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest' or request.headers.get('HX-Request') == 'true' - - if form.validate_on_submit(): - import_mode = form.import_mode.data - dependency_mode = form.dependency_mode.data - resolve_dependencies = form.resolve_dependencies.data - while not log_queue.empty(): - log_queue.get() - - try: - if import_mode == 'file': - tgz_file = form.tgz_file.data - temp_dir = tempfile.mkdtemp() - temp_path = os.path.join(temp_dir, secure_filename(tgz_file.filename)) - tgz_file.save(temp_path) - result = import_manual_package_and_dependencies(temp_path, dependency_mode=dependency_mode, is_file=True, resolve_dependencies=resolve_dependencies) - identifier = result.get('requested', tgz_file.filename) - shutil.rmtree(temp_dir, ignore_errors=True) - elif import_mode == 'url': - tgz_url = form.tgz_url.data - result = import_manual_package_and_dependencies(tgz_url, dependency_mode=dependency_mode, is_url=True, resolve_dependencies=resolve_dependencies) - identifier = result.get('requested', tgz_url) - - if result['errors'] and not result['downloaded']: - error_msg = result['errors'][0] - simplified_msg = error_msg - if "HTTP error" in error_msg and "404" in error_msg: - simplified_msg = "Package not found (404). Check input." - elif "HTTP error" in error_msg: - simplified_msg = f"Error: {error_msg.split(': ', 1)[-1]}" - elif "Connection error" in error_msg: - simplified_msg = "Could not connect to source." - flash(f"Failed to import {identifier}: {simplified_msg}", "error") - logger.error(f"Manual import failed for {identifier}: {error_msg}") - if is_ajax: - return jsonify({"status": "error", "message": simplified_msg}), 400 - return render_template('manual_import_ig.html', form=form, site_name='FHIRFLARE IG Toolkit', now=datetime.datetime.now()) - else: - if result['errors']: - flash(f"Partially imported {identifier} with errors. Check logs.", "warning") - for err in result['errors']: - logger.warning(f"Manual import warning for {identifier}: {err}") - else: - flash(f"Successfully imported {identifier}! Mode: {dependency_mode}", "success") - if is_ajax: - return jsonify({"status": "success", "message": f"Imported {identifier}", "redirect": url_for('view_igs')}), 200 - return redirect(url_for('view_igs')) - except Exception as e: - logger.error(f"Unexpected error during manual IG import: {str(e)}", exc_info=True) - flash(f"An unexpected error occurred: {str(e)}", "error") - if is_ajax: - return jsonify({"status": "error", "message": str(e)}), 500 - return render_template('manual_import_ig.html', form=form, site_name='FHIRFLARE IG Toolkit', now=datetime.datetime.now()) - else: - for field, errors in form.errors.items(): - for error in errors: - flash(f"Error in {getattr(form, field).label.text}: {error}", "danger") - if is_ajax: - return jsonify({"status": "error", "message": "Form validation failed", "errors": form.errors}), 400 - return render_template('manual_import_ig.html', form=form, site_name='FHIRFLARE IG Toolkit', now=datetime.datetime.now()) - -@app.route('/import-ig', methods=['GET', 'POST']) -def import_ig(): - form = IgImportForm() - # Check for HTMX request using both X-Requested-With and HX-Request headers - is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest' or request.headers.get('HX-Request') == 'true' - - if form.validate_on_submit(): - name = form.package_name.data - version = form.package_version.data - dependency_mode = form.dependency_mode.data - - # Clear log queue for this request - while not log_queue.empty(): - log_queue.get() - - try: - result = import_package_and_dependencies(name, version, dependency_mode=dependency_mode) - if result['errors'] and not result['downloaded']: - error_msg = result['errors'][0] - simplified_msg = error_msg - if "HTTP error" in error_msg and "404" in error_msg: - simplified_msg = "Package not found on registry (404). Check name and version." - elif "HTTP error" in error_msg: - simplified_msg = f"Registry error: {error_msg.split(': ', 1)[-1]}" - elif "Connection error" in error_msg: - simplified_msg = "Could not connect to the FHIR package registry." - flash(f"Failed to import {name}#{version}: {simplified_msg}", "error") - logger.error(f"Import failed critically for {name}#{version}: {error_msg}") - if is_ajax: - return jsonify({"status": "error", "message": simplified_msg}), 400 - return render_template('import_ig.html', form=form, site_name='FHIRFLARE IG Toolkit', now=datetime.datetime.now()) - else: - if result['errors']: - flash(f"Partially imported {name}#{version} with errors during dependency processing. Check logs.", "warning") - for err in result['errors']: - logger.warning(f"Import warning for {name}#{version}: {err}") - else: - flash(f"Successfully downloaded {name}#{version} and dependencies! Mode: {dependency_mode}", "success") - if is_ajax: - return jsonify({"status": "success", "message": f"Imported {name}#{version}", "redirect": url_for('view_igs')}), 200 - return redirect(url_for('view_igs')) - except Exception as e: - logger.error(f"Unexpected error during IG import: {str(e)}", exc_info=True) - flash(f"An unexpected error occurred downloading the IG: {str(e)}", "error") - if is_ajax: - return jsonify({"status": "error", "message": str(e)}), 500 - return render_template('import_ig.html', form=form, site_name='FHIRFLARE IG Toolkit', now=datetime.datetime.now()) - else: - for field, errors in form.errors.items(): - for error in errors: - flash(f"Error in {getattr(form, field).label.text}: {error}", "danger") - if is_ajax: - return jsonify({"status": "error", "message": "Form validation failed", "errors": form.errors}), 400 - return render_template('import_ig.html', form=form, site_name='FHIRFLARE IG Toolkit', now=datetime.datetime.now()) - -# Function to perform the actual refresh logic in the background -def perform_cache_refresh_and_log(): - """Clears caches, fetches, normalizes, and caches packages, logging progress.""" - # Ensure this runs within an app context to access db, config etc. - with app.app_context(): - logger.info("--- Starting Background Cache Refresh ---") - try: - # 1. Clear In-Memory Cache - app.config['MANUAL_PACKAGE_CACHE'] = None - app.config['MANUAL_CACHE_TIMESTAMP'] = None - logger.info("In-memory cache cleared.") - - # 2. Clear DB Timestamp and CachedPackage Table - try: - timestamp_info = RegistryCacheInfo.query.first() - if timestamp_info: - timestamp_info.last_fetch_timestamp = None - # Don't commit yet, commit at the end - num_deleted = db.session.query(CachedPackage).delete() - db.session.flush() # Apply delete within transaction - logger.info(f"Cleared {num_deleted} entries from CachedPackage table (DB).") - except Exception as db_clear_err: - db.session.rollback() - logger.error(f"Failed to clear DB cache tables: {db_clear_err}", exc_info=True) - log_queue.put(f"ERROR: Failed to clear DB - {db_clear_err}") - log_queue.put("[DONE]") # Signal completion even on error - return # Stop processing - - # 3. Fetch from Registries - logger.info("Fetching fresh package list from registries...") - fetch_failed = False - try: - raw_packages = fetch_packages_from_registries(search_term='') # Uses services logger internally - if not raw_packages: - logger.warning("No packages returned from registries during refresh.") - fetch_failed = True - normalized_packages = [] - else: - # 4. Normalize Data - logger.info("Normalizing fetched package data...") - normalized_packages = normalize_package_data(raw_packages) # Uses services logger - - except Exception as fetch_norm_err: - logger.error(f"Error during fetch/normalization: {fetch_norm_err}", exc_info=True) - fetch_failed = True - normalized_packages = [] - log_queue.put(f"ERROR: Failed during fetch/normalization - {fetch_norm_err}") - - - # 5. Update In-Memory Cache (always update, even if empty on failure) - now_ts = datetime.datetime.now(datetime.timezone.utc) - app.config['MANUAL_PACKAGE_CACHE'] = normalized_packages - app.config['MANUAL_CACHE_TIMESTAMP'] = now_ts - app_state['fetch_failed'] = fetch_failed # Update app_state instead of session - logger.info(f"Updated in-memory cache with {len(normalized_packages)} packages. Fetch failed: {fetch_failed}") - - # 6. Cache in Database (if successful fetch) - if not fetch_failed and normalized_packages: - try: - logger.info("Caching packages in database...") - cache_packages(normalized_packages, db, CachedPackage) # Uses services logger - except Exception as cache_err: - db.session.rollback() # Rollback DB changes on caching error - logger.error(f"Failed to cache packages in database: {cache_err}", exc_info=True) - log_queue.put(f"ERROR: Failed to cache packages in DB - {cache_err}") - log_queue.put("[DONE]") # Signal completion - return # Stop processing - elif fetch_failed: - logger.warning("Skipping database caching due to fetch failure.") - else: # No packages but fetch didn't fail (edge case?) - logger.info("No packages to cache in database.") - - - # 7. Update DB Timestamp (only if fetch didn't fail) - if not fetch_failed: - if timestamp_info: - timestamp_info.last_fetch_timestamp = now_ts - else: - timestamp_info = RegistryCacheInfo(last_fetch_timestamp=now_ts) - db.session.add(timestamp_info) - logger.info(f"Set DB timestamp to {now_ts}.") - else: - # Ensure timestamp_info is not added if fetch failed and it was new - if timestamp_info and timestamp_info in db.new: - db.session.expunge(timestamp_info) - logger.warning("Skipping DB timestamp update due to fetch failure.") - - - # 8. Commit all DB changes (only commit if successful) - if not fetch_failed: - db.session.commit() - logger.info("Database changes committed.") - else: - # Rollback any potential flushed changes if fetch failed - db.session.rollback() - logger.info("Rolled back DB changes due to fetch failure.") - - except Exception as e: - db.session.rollback() # Rollback on any other unexpected error - logger.error(f"Critical error during background cache refresh: {e}", exc_info=True) - log_queue.put(f"CRITICAL ERROR: {e}") - finally: - logger.info("--- Background Cache Refresh Finished ---") - log_queue.put("[DONE]") # Signal completion - - -@app.route('/api/refresh-cache-task', methods=['POST']) -@csrf.exempt # Ensure CSRF is handled if needed, or keep exempt -@swag_from({ - 'tags': ['Package Management'], - 'summary': 'Refresh FHIR package cache.', - 'description': 'Triggers an asynchronous background task to clear and refresh the FHIR package cache from configured registries.', - 'security': [{'ApiKeyAuth': []}], # Requires API Key - 'responses': { - '202': {'description': 'Cache refresh process started in the background.'}, - # Consider if other error codes are possible before task starts - } -}) -def refresh_cache_task(): - """API endpoint to trigger the background cache refresh.""" - # Note: Clearing queue here might interfere if multiple users click concurrently. - # A more robust solution uses per-request queues or task IDs. - # For simplicity, we clear it assuming low concurrency for this action. - while not log_queue.empty(): - try: log_queue.get_nowait() - except queue.Empty: break - - logger.info("Received API request to refresh cache.") - thread = threading.Thread(target=perform_cache_refresh_and_log, daemon=True) - thread.start() - logger.info("Background cache refresh thread started.") - # Return 202 Accepted: Request accepted, processing in background. - return jsonify({"status": "accepted", "message": "Cache refresh process started in the background."}), 202 - - -# Modify stream_import_logs - Simpler version: relies on thread putting [DONE] -@app.route('/stream-import-logs') -@swag_from({ - 'tags': ['Package Management'], - 'summary': 'Stream package import logs.', - 'description': 'Provides a Server-Sent Events (SSE) stream of logs generated during package import or cache refresh operations. The client should listen for "data:" events. The stream ends with "data: [DONE]".', - 'produces': ['text/event-stream'], - # No API key usually for SSE streams if they are tied to an existing user session/action - # 'security': [], - 'responses': { - '200': { - 'description': 'An event stream of log messages.', - 'schema': { - 'type': 'string', - 'format': 'text/event-stream', - 'example': "data: INFO: Starting import...\ndata: INFO: Package downloaded.\ndata: [DONE]\n\n" - } - } - } -}) -def stream_import_logs(): - logger.debug("SSE connection established to stream-import-logs") - def generate(): - # Directly consume from the shared queue - while True: - try: - # Block-wait on the shared queue with a timeout - msg = log_queue.get(timeout=300) # 5 min timeout on get - clean_msg = str(msg).replace('INFO:services:', '').replace('INFO:app:', '').strip() - yield f"data: {clean_msg}\n\n" - - if msg == '[DONE]': - logger.debug("SSE stream received [DONE] from queue, closing stream.") - break # Exit the generate loop - except queue.Empty: - # Timeout occurred waiting for message or [DONE] - logger.warning("SSE stream timed out waiting for logs. Closing.") - yield "data: ERROR: Timeout waiting for logs.\n\n" - yield "data: [DONE]\n\n" # Still send DONE to signal client closure - break - except GeneratorExit: - logger.debug("SSE client disconnected.") - break # Exit loop if client disconnects - except Exception as e: - logger.error(f"Error in SSE generate loop: {e}", exc_info=True) - yield f"data: ERROR: Server error in log stream - {e}\n\n" - yield "data: [DONE]\n\n" # Send DONE to signal client closure on error - break - - response = Response(generate(), mimetype='text/event-stream') - response.headers['Cache-Control'] = 'no-cache' - response.headers['X-Accel-Buffering'] = 'no' # Useful for Nginx proxying - return response - -@app.route('/view-igs') -def view_igs(): - form = FlaskForm() - processed_igs = ProcessedIg.query.order_by(ProcessedIg.package_name, ProcessedIg.version).all() - processed_ids = {(ig.package_name, ig.version) for ig in processed_igs} - packages_dir = app.config['FHIR_PACKAGES_DIR'] - packages, errors, duplicate_groups = list_downloaded_packages(packages_dir) - if errors: - flash(f"Warning: Errors encountered while listing packages: {', '.join(errors)}", "warning") - colors = ['bg-warning', 'bg-info', 'bg-success', 'bg-danger', 'bg-secondary'] - group_colors = {} - for i, name in enumerate(duplicate_groups.keys()): - group_colors[name] = colors[i % len(colors)] - return render_template('cp_downloaded_igs.html', form=form, packages=packages, - processed_list=processed_igs, processed_ids=processed_ids, - duplicate_groups=duplicate_groups, group_colors=group_colors, - site_name='FHIRFLARE IG Toolkit', now=datetime.datetime.now(), - config=app.config) - -@app.route('/about') -def about(): - """Renders the about page.""" - # The app_mode is automatically injected by the context processor - return render_template('about.html', - title="About", # Optional title for the page - site_name='FHIRFLARE IG Toolkit') # Or get from config - - -@app.route('/push-igs', methods=['GET']) -def push_igs(): - # form = FlaskForm() # OLD - Replace this line - form = IgImportForm() # Use a real form class that has CSRF handling built-in - processed_igs = ProcessedIg.query.order_by(ProcessedIg.package_name, ProcessedIg.version).all() - processed_ids = {(ig.package_name, ig.version) for ig in processed_igs} - packages_dir = app.config['FHIR_PACKAGES_DIR'] - packages, errors, duplicate_groups = list_downloaded_packages(packages_dir) - if errors: - flash(f"Warning: Errors encountered while listing packages: {', '.join(errors)}", "warning") - colors = ['bg-warning', 'bg-info', 'bg-success', 'bg-danger', 'bg-secondary'] - group_colors = {} - for i, name in enumerate(duplicate_groups.keys()): - group_colors[name] = colors[i % len(colors)] - return render_template('cp_push_igs.html', form=form, packages=packages, # Pass the form instance - processed_list=processed_igs, processed_ids=processed_ids, - duplicate_groups=duplicate_groups, group_colors=group_colors, - site_name='FHIRFLARE IG Toolkit', now=datetime.datetime.now(), - api_key=app.config['API_KEY'], config=app.config) - -@app.route('/process-igs', methods=['POST']) -def process_ig(): - form = FlaskForm() # Assuming a basic FlaskForm for CSRF protection - if form.validate_on_submit(): - filename = request.form.get('filename') - # --- Keep existing filename and path validation --- - if not filename or not filename.endswith('.tgz'): - flash("Invalid package file selected.", "error") - return redirect(url_for('view_igs')) - tgz_path = os.path.join(app.config['FHIR_PACKAGES_DIR'], filename) - if not os.path.exists(tgz_path): - flash(f"Package file not found: {filename}", "error") - return redirect(url_for('view_igs')) - - name, version = services.parse_package_filename(filename) - if not name: # Add fallback naming if parse fails - name = filename[:-4].replace('_', '.') # Basic guess - version = 'unknown' - logger.warning(f"Using fallback naming for {filename} -> {name}#{version}") - - try: - logger.info(f"Starting processing for {name}#{version} from file {filename}") - # This now returns the conformance map too - package_info = services.process_package_file(tgz_path) - - if package_info.get('errors'): - flash(f"Processing completed with errors for {name}#{version}: {', '.join(package_info['errors'])}", "warning") - - # (Keep existing optional_usage_dict logic) - optional_usage_dict = { - info['name']: True - for info in package_info.get('resource_types_info', []) - if info.get('optional_usage') - } - logger.debug(f"Optional usage elements identified: {optional_usage_dict}") - - # Find existing or create new DB record - existing_ig = ProcessedIg.query.filter_by(package_name=name, version=version).first() - - if existing_ig: - logger.info(f"Updating existing processed record for {name}#{version}") - processed_ig = existing_ig - else: - logger.info(f"Creating new processed record for {name}#{version}") - processed_ig = ProcessedIg(package_name=name, version=version) - db.session.add(processed_ig) - - # Update all fields - processed_ig.processed_date = datetime.datetime.now(tz=datetime.timezone.utc) - processed_ig.resource_types_info = package_info.get('resource_types_info', []) - processed_ig.must_support_elements = package_info.get('must_support_elements') - processed_ig.examples = package_info.get('examples') - processed_ig.complies_with_profiles = package_info.get('complies_with_profiles', []) - processed_ig.imposed_profiles = package_info.get('imposed_profiles', []) - processed_ig.optional_usage_elements = optional_usage_dict - # --- ADD THIS LINE: Save the extracted conformance map --- - processed_ig.search_param_conformance = package_info.get('search_param_conformance') # Get map from results - # --- END ADD --- - - db.session.commit() # Commit all changes - flash(f"Successfully processed {name}#{version}!", "success") - - except Exception as e: - db.session.rollback() # Rollback on error - logger.error(f"Error processing IG {filename}: {str(e)}", exc_info=True) - flash(f"Error processing IG '{filename}': {str(e)}", "error") - else: - # Handle CSRF or other form validation errors - logger.warning(f"Form validation failed for process-igs: {form.errors}") - flash("CSRF token missing or invalid, or other form error.", "error") - - return redirect(url_for('view_igs')) - -# --- End of /process-igs Function --- - -@app.route('/delete-ig', methods=['POST']) -def delete_ig(): - form = FlaskForm() - if form.validate_on_submit(): - filename = request.form.get('filename') - if not filename or not filename.endswith('.tgz'): - flash("Invalid package file specified.", "error") - return redirect(url_for('view_igs')) - tgz_path = os.path.join(app.config['FHIR_PACKAGES_DIR'], filename) - metadata_path = tgz_path.replace('.tgz', '.metadata.json') - deleted_files = [] - errors = [] - if os.path.exists(tgz_path): - try: - os.remove(tgz_path) - deleted_files.append(filename) - logger.info(f"Deleted package file: {tgz_path}") - except OSError as e: - errors.append(f"Could not delete {filename}: {e}") - logger.error(f"Error deleting {tgz_path}: {e}") - else: - flash(f"Package file not found: {filename}", "warning") - if os.path.exists(metadata_path): - try: - os.remove(metadata_path) - deleted_files.append(os.path.basename(metadata_path)) - logger.info(f"Deleted metadata file: {metadata_path}") - except OSError as e: - errors.append(f"Could not delete metadata for {filename}: {e}") - logger.error(f"Error deleting {metadata_path}: {e}") - if errors: - for error in errors: - flash(error, "error") - elif deleted_files: - flash(f"Deleted: {', '.join(deleted_files)}", "success") - else: - flash("No files found to delete.", "info") - else: - logger.warning(f"Form validation failed for delete-ig: {form.errors}") - flash("CSRF token missing or invalid.", "error") - return redirect(url_for('view_igs')) - -@app.route('/unload-ig', methods=['POST']) -def unload_ig(): - form = FlaskForm() - if form.validate_on_submit(): - ig_id = request.form.get('ig_id') - try: - ig_id_int = int(ig_id) - processed_ig = db.session.get(ProcessedIg, ig_id_int) - if processed_ig: - try: - pkg_name = processed_ig.package_name - pkg_version = processed_ig.version - db.session.delete(processed_ig) - db.session.commit() - flash(f"Unloaded processed data for {pkg_name}#{pkg_version}", "success") - logger.info(f"Unloaded DB record for {pkg_name}#{pkg_version} (ID: {ig_id_int})") - except Exception as e: - db.session.rollback() - flash(f"Error unloading package data: {str(e)}", "error") - logger.error(f"Error deleting ProcessedIg record ID {ig_id_int}: {e}", exc_info=True) - else: - flash(f"Processed package data not found with ID: {ig_id}", "error") - logger.warning(f"Attempted to unload non-existent ProcessedIg record ID: {ig_id}") - except ValueError: - flash("Invalid package ID provided.", "error") - logger.warning(f"Invalid ID format received for unload-ig: {ig_id}") - except Exception as e: - flash(f"An unexpected error occurred during unload: {str(e)}", "error") - logger.error(f"Unexpected error in unload_ig for ID {ig_id}: {e}", exc_info=True) - else: - logger.warning(f"Form validation failed for unload-ig: {form.errors}") - flash("CSRF token missing or invalid.", "error") - return redirect(url_for('view_igs')) - -@app.route('/view-ig/') -def view_ig(processed_ig_id): - processed_ig = db.session.get(ProcessedIg, processed_ig_id) - if not processed_ig: - flash(f"Processed IG with ID {processed_ig_id} not found.", "error") - return redirect(url_for('view_igs')) - profile_list = [t for t in processed_ig.resource_types_info if t.get('is_profile')] - base_list = [t for t in processed_ig.resource_types_info if not t.get('is_profile')] - examples_by_type = processed_ig.examples or {} - optional_usage_elements = processed_ig.optional_usage_elements or {} - complies_with_profiles = processed_ig.complies_with_profiles or [] - imposed_profiles = processed_ig.imposed_profiles or [] - logger.debug(f"Viewing IG {processed_ig.package_name}#{processed_ig.version}: " - f"{len(profile_list)} profiles, {len(base_list)} base resources, " - f"{len(optional_usage_elements)} optional elements") - return render_template('cp_view_processed_ig.html', - title=f"View {processed_ig.package_name}#{processed_ig.version}", - processed_ig=processed_ig, - profile_list=profile_list, - base_list=base_list, - examples_by_type=examples_by_type, - site_name='FHIRFLARE IG Toolkit', - now=datetime.datetime.now(), - complies_with_profiles=complies_with_profiles, - imposed_profiles=imposed_profiles, - optional_usage_elements=optional_usage_elements, - config=current_app.config) - -@app.route('/get-example') -@swag_from({ - 'tags': ['Package Management'], - 'summary': 'Get a specific example resource from a package.', - 'description': 'Retrieves the content of an example JSON file from a specified FHIR package and version.', - 'parameters': [ - {'name': 'package_name', 'in': 'query', 'type': 'string', 'required': True, 'description': 'Name of the FHIR package.'}, - {'name': 'version', 'in': 'query', 'type': 'string', 'required': True, 'description': 'Version of the FHIR package.'}, - {'name': 'filename', 'in': 'query', 'type': 'string', 'required': True, 'description': 'Path to the example file within the package (e.g., "package/Patient-example.json").'}, - {'name': 'include_narrative', 'in': 'query', 'type': 'boolean', 'required': False, 'default': False, 'description': 'Whether to include the HTML narrative in the response.'} - ], - 'responses': { - '200': {'description': 'The example FHIR resource in JSON format.', 'schema': {'type': 'object'}}, - '400': {'description': 'Missing required query parameters or invalid file path.'}, - '404': {'description': 'Package or example file not found.'}, - '500': {'description': 'Server error during file retrieval or processing.'} - } -}) -def get_example(): - package_name = request.args.get('package_name') - version = request.args.get('version') - filename = request.args.get('filename') - include_narrative = request.args.get('include_narrative', 'false').lower() == 'true' - if not all([package_name, version, filename]): - logger.warning("get_example: Missing query parameters: package_name=%s, version=%s, filename=%s", package_name, version, filename) - return jsonify({"error": "Missing required query parameters: package_name, version, filename"}), 400 - if not filename.startswith('package/') or '..' in filename: - logger.warning(f"Invalid example file path requested: {filename}") - return jsonify({"error": "Invalid example file path."}), 400 - packages_dir = current_app.config.get('FHIR_PACKAGES_DIR') - if not packages_dir: - logger.error("FHIR_PACKAGES_DIR not configured.") - return jsonify({"error": "Server configuration error: Package directory not set."}), 500 - tgz_filename = services.construct_tgz_filename(package_name, version) - tgz_path = os.path.join(packages_dir, tgz_filename) - if not os.path.exists(tgz_path): - logger.error(f"Package file not found: {tgz_path}") - return jsonify({"error": f"Package {package_name}#{version} not found"}), 404 - try: - with tarfile.open(tgz_path, "r:gz") as tar: - try: - example_member = tar.getmember(filename) - with tar.extractfile(example_member) as example_fileobj: - content_bytes = example_fileobj.read() - content_string = content_bytes.decode('utf-8-sig') - content = json.loads(content_string) - if not include_narrative: - content = services.remove_narrative(content, include_narrative=False) - filtered_content_string = json.dumps(content, separators=(',', ':'), sort_keys=False) - return Response(filtered_content_string, mimetype='application/json') - except KeyError: - logger.error(f"Example file '{filename}' not found within {tgz_filename}") - return jsonify({"error": f"Example file '{os.path.basename(filename)}' not found in package."}), 404 - except json.JSONDecodeError as e: - logger.error(f"JSON parsing error for example '{filename}' in {tgz_filename}: {e}") - return jsonify({"error": f"Invalid JSON in example file: {str(e)}"}), 500 - except UnicodeDecodeError as e: - logger.error(f"Encoding error reading example '{filename}' from {tgz_filename}: {e}") - return jsonify({"error": f"Error decoding example file (invalid UTF-8?): {str(e)}"}), 500 - except tarfile.TarError as e: - logger.error(f"TarError reading example '{filename}' from {tgz_filename}: {e}") - return jsonify({"error": f"Error reading package archive: {str(e)}"}), 500 - except tarfile.TarError as e: - logger.error(f"Error opening package file {tgz_path}: {e}") - return jsonify({"error": f"Error reading package archive: {str(e)}"}), 500 - except FileNotFoundError: - logger.error(f"Package file disappeared: {tgz_path}") - return jsonify({"error": f"Package file not found: {package_name}#{version}"}), 404 - except Exception as e: - logger.error(f"Unexpected error getting example '{filename}' from {tgz_filename}: {e}", exc_info=True) - return jsonify({"error": f"Unexpected error: {str(e)}"}), 500 - -#----------------------------------------------------------------------new -def collect_all_structure_definitions(tgz_path): - """Collect all StructureDefinitions from a .tgz package.""" - structure_definitions = {} - try: - with tarfile.open(tgz_path, "r:gz") as tar: - for member in tar: - if not (member.isfile() and member.name.startswith('package/') and member.name.lower().endswith('.json')): - continue - if os.path.basename(member.name).lower() in ['package.json', '.index.json', 'validation-summary.json', 'validation-oo.json']: - continue - fileobj = None - try: - fileobj = tar.extractfile(member) - if fileobj: - content_bytes = fileobj.read() - content_string = content_bytes.decode('utf-8-sig') - data = json.loads(content_string) - if isinstance(data, dict) and data.get('resourceType') == 'StructureDefinition': - sd_url = data.get('url') - if sd_url: - structure_definitions[sd_url] = data - except Exception as e: - logger.warning(f"Could not read/parse potential SD {member.name}, skipping: {e}") - finally: - if fileobj: - fileobj.close() - except Exception as e: - logger.error(f"Unexpected error collecting StructureDefinitions from {tgz_path}: {e}", exc_info=True) - return structure_definitions - -def generate_snapshot(structure_def, core_package_path, local_package_path): - """Generate a snapshot by merging the differential with the base StructureDefinition.""" - if 'snapshot' in structure_def: - return structure_def - - # Fetch all StructureDefinitions from the local package for reference resolution - local_sds = collect_all_structure_definitions(local_package_path) - - # Get the base StructureDefinition from the core package - base_url = structure_def.get('baseDefinition') - if not base_url: - logger.error("No baseDefinition found in StructureDefinition.") - return structure_def - - resource_type = structure_def.get('type') - base_sd_data, _ = services.find_and_extract_sd(core_package_path, resource_type, profile_url=base_url) - if not base_sd_data or 'snapshot' not in base_sd_data: - logger.error(f"Could not fetch or find snapshot in base StructureDefinition: {base_url}") - return structure_def - - # Copy the base snapshot elements - snapshot_elements = deepcopy(base_sd_data['snapshot']['element']) - differential_elements = structure_def.get('differential', {}).get('element', []) - - # Map snapshot elements by path and id for easier lookup - snapshot_by_path = {el['path']: el for el in snapshot_elements} - snapshot_by_id = {el['id']: el for el in snapshot_elements if 'id' in el} - - # Process differential elements - for diff_el in differential_elements: - diff_path = diff_el.get('path') - diff_id = diff_el.get('id') - - # Resolve extensions or referenced types - if 'type' in diff_el: - for type_info in diff_el['type']: - if 'profile' in type_info: - for profile_url in type_info['profile']: - if profile_url in local_sds: - # Add elements from the referenced StructureDefinition - ref_sd = local_sds[profile_url] - ref_elements = ref_sd.get('snapshot', {}).get('element', []) or ref_sd.get('differential', {}).get('element', []) - for ref_el in ref_elements: - # Adjust paths to fit within the current structure - ref_path = ref_el.get('path') - if ref_path.startswith(ref_sd.get('type')): - new_path = diff_path + ref_path[len(ref_sd.get('type')):] - new_el = deepcopy(ref_el) - new_el['path'] = new_path - new_el['id'] = diff_id + ref_path[len(ref_sd.get('type')):] - snapshot_elements.append(new_el) - - # Find matching element in snapshot - target_el = snapshot_by_id.get(diff_id) or snapshot_by_path.get(diff_path) - if target_el: - # Update existing element with differential constraints - target_el.update(diff_el) - else: - # Add new element (e.g., extensions or new slices) - snapshot_elements.append(diff_el) - - structure_def['snapshot'] = {'element': snapshot_elements} - return structure_def - -@app.route('/get-structure') -@swag_from({ - 'tags': ['Package Management'], - 'summary': 'Get a StructureDefinition from a package.', - 'description': 'Retrieves a StructureDefinition, optionally generating or filtering for snapshot/differential views.', - 'parameters': [ - {'name': 'package_name', 'in': 'query', 'type': 'string', 'required': True}, - {'name': 'version', 'in': 'query', 'type': 'string', 'required': True}, - {'name': 'resource_type', 'in': 'query', 'type': 'string', 'required': True, 'description': 'The resource type or profile ID.'}, - {'name': 'view', 'in': 'query', 'type': 'string', 'required': False, 'default': 'snapshot', 'enum': ['snapshot', 'differential']}, - {'name': 'include_narrative', 'in': 'query', 'type': 'boolean', 'required': False, 'default': False}, - {'name': 'raw', 'in': 'query', 'type': 'boolean', 'required': False, 'default': False, 'description': 'If true, returns the raw SD JSON.'}, - {'name': 'profile_url', 'in': 'query', 'type': 'string', 'required': False, 'description': 'Canonical URL of the profile to retrieve.'} - ], - 'responses': { - '200': { - 'description': 'The StructureDefinition data.', - 'schema': { - 'type': 'object', - 'properties': { - 'elements': {'type': 'array', 'items': {'type': 'object'}}, - 'must_support_paths': {'type': 'array', 'items': {'type': 'string'}}, - 'search_parameters': {'type': 'array', 'items': {'type': 'object'}}, - 'fallback_used': {'type': 'boolean'}, - 'source_package': {'type': 'string'} - } - } - }, - '400': {'description': 'Missing required parameters.'}, - '404': {'description': 'StructureDefinition not found.'}, - '500': {'description': 'Server error.'} - } -}) -def get_structure(): - package_name = request.args.get('package_name') - version = request.args.get('version') - resource_type = request.args.get('resource_type') - view = request.args.get('view', 'snapshot') - include_narrative = request.args.get('include_narrative', 'false').lower() == 'true' - raw = request.args.get('raw', 'false').lower() == 'true' - profile_url = request.args.get('profile_url') - if not all([package_name, version, resource_type]): - logger.warning("get_structure: Missing query parameters: package_name=%s, version=%s, resource_type=%s", package_name, version, resource_type) - return jsonify({"error": "Missing required query parameters: package_name, version, resource_type"}), 400 - packages_dir = current_app.config.get('FHIR_PACKAGES_DIR') - if not packages_dir: - logger.error("FHIR_PACKAGES_DIR not configured.") - return jsonify({"error": "Server configuration error: Package directory not set."}), 500 - tgz_filename = services.construct_tgz_filename(package_name, version) - tgz_path = os.path.join(packages_dir, tgz_filename) - core_package_name, core_package_version = services.CANONICAL_PACKAGE - core_tgz_filename = services.construct_tgz_filename(core_package_name, core_package_version) - core_tgz_path = os.path.join(packages_dir, core_tgz_filename) - sd_data = None - search_params_data = [] - fallback_used = False - source_package_id = f"{package_name}#{version}" - base_resource_type_for_sp = None - logger.debug(f"Attempting to find SD for '{resource_type}' in {tgz_filename}") - primary_package_exists = os.path.exists(tgz_path) - core_package_exists = os.path.exists(core_tgz_path) - if primary_package_exists: - try: - sd_data, _ = services.find_and_extract_sd(tgz_path, resource_type, profile_url=profile_url, include_narrative=include_narrative, raw=raw) - if sd_data: - base_resource_type_for_sp = sd_data.get('type') - logger.debug(f"Determined base resource type '{base_resource_type_for_sp}' from primary SD '{resource_type}'") - except Exception as e: - logger.error(f"Unexpected error extracting SD '{resource_type}' from primary package {tgz_path}: {e}", exc_info=True) - sd_data = None - if sd_data is None: - logger.info(f"SD for '{resource_type}' not found or failed to load from {source_package_id}. Attempting fallback to {services.CANONICAL_PACKAGE_ID}.") - if not core_package_exists: - error_message = f"SD for '{resource_type}' not found in primary package, and core package is missing." if primary_package_exists else f"Primary package {package_name}#{version} and core package are missing." - return jsonify({"error": error_message}), 500 if primary_package_exists else 404 - try: - sd_data, _ = services.find_and_extract_sd(core_tgz_path, resource_type, profile_url=profile_url, include_narrative=include_narrative, raw=raw) - if sd_data is not None: - fallback_used = True - source_package_id = services.CANONICAL_PACKAGE_ID - base_resource_type_for_sp = sd_data.get('type') - logger.info(f"Found SD for '{resource_type}' in fallback package {source_package_id}. Base type: '{base_resource_type_for_sp}'") - except Exception as e: - logger.error(f"Unexpected error extracting SD '{resource_type}' from fallback {core_tgz_path}: {e}", exc_info=True) - return jsonify({"error": f"Unexpected error reading fallback StructureDefinition: {str(e)}"}), 500 - if not sd_data: - logger.error(f"SD for '{resource_type}' could not be found in primary or fallback packages.") - return jsonify({"error": f"StructureDefinition for '{resource_type}' not found."}), 404 - - # Generate snapshot if missing - if 'snapshot' not in sd_data: - logger.info(f"Snapshot missing for {resource_type}. Generating snapshot...") - sd_data = generate_snapshot(sd_data, core_tgz_path, tgz_path) - - if raw: - return Response(json.dumps(sd_data, indent=None, separators=(',', ':')), mimetype='application/json') - - # Prepare elements based on the view - snapshot_elements = sd_data.get('snapshot', {}).get('element', []) - differential_elements = sd_data.get('differential', {}).get('element', []) - differential_ids = {el.get('id') for el in differential_elements if el.get('id')} - logger.debug(f"Found {len(differential_ids)} unique IDs in differential.") - - # Select elements based on the view - enriched_elements = [] - if view == 'snapshot': - if snapshot_elements: - logger.debug(f"Processing {len(snapshot_elements)} snapshot elements for Snapshot view.") - for element in snapshot_elements: - element_id = element.get('id') - element['isInDifferential'] = bool(element_id and element_id in differential_ids) - enriched_elements.append(element) - else: - logger.warning(f"No snapshot elements found for {resource_type} in {source_package_id} for Snapshot view.") - else: # Differential, Must Support, Key Elements views use differential elements as a base - if differential_elements: - logger.debug(f"Processing {len(differential_elements)} differential elements for {view} view.") - for element in differential_elements: - element['isInDifferential'] = True - enriched_elements.append(element) - else: - logger.warning(f"No differential elements found for {resource_type} in {source_package_id} for {view} view.") - - enriched_elements = [services.remove_narrative(el, include_narrative=include_narrative) for el in enriched_elements] - - must_support_paths = [] - processed_ig_record = ProcessedIg.query.filter_by(package_name=package_name, version=version).first() - if processed_ig_record and processed_ig_record.must_support_elements: - ms_elements_dict = processed_ig_record.must_support_elements - must_support_paths = ms_elements_dict.get(resource_type, []) - if not must_support_paths and base_resource_type_for_sp: - must_support_paths = ms_elements_dict.get(base_resource_type_for_sp, []) - if must_support_paths: - logger.debug(f"Retrieved {len(must_support_paths)} MS paths using base type key '{base_resource_type_for_sp}' from DB.") - elif must_support_paths: - logger.debug(f"Retrieved {len(must_support_paths)} MS paths using profile key '{resource_type}' from DB.") - else: - logger.debug(f"No specific MS paths found for keys '{resource_type}' or '{base_resource_type_for_sp}' in DB.") - else: - logger.debug(f"No processed IG record or no must_support_elements found in DB for {package_name}#{version}") - - if base_resource_type_for_sp and primary_package_exists: - try: - logger.info(f"Fetching SearchParameters for base type '{base_resource_type_for_sp}' from primary package {tgz_path}") - search_params_data = services.find_and_extract_search_params(tgz_path, base_resource_type_for_sp) - except Exception as e: - logger.error(f"Error extracting SearchParameters for '{base_resource_type_for_sp}' from primary package {tgz_path}: {e}", exc_info=True) - search_params_data = [] - elif not primary_package_exists: - logger.warning(f"Original package {tgz_path} not found, cannot search it for specific SearchParameters.") - elif not base_resource_type_for_sp: - logger.warning(f"Base resource type could not be determined for '{resource_type}', cannot search for SearchParameters.") - if not search_params_data and base_resource_type_for_sp and core_package_exists: - logger.info(f"No relevant SearchParameters found in primary package for '{base_resource_type_for_sp}'. Searching core package {core_tgz_path}.") - try: - search_params_data = services.find_and_extract_search_params(core_tgz_path, base_resource_type_for_sp) - if search_params_data: - logger.info(f"Found {len(search_params_data)} SearchParameters for '{base_resource_type_for_sp}' in core package.") - except Exception as e: - logger.error(f"Error extracting SearchParameters for '{base_resource_type_for_sp}' from core package {core_tgz_path}: {e}", exc_info=True) - search_params_data = [] - elif not search_params_data and not core_package_exists: - logger.warning(f"Core package {core_tgz_path} not found, cannot perform fallback search for SearchParameters.") - search_param_conformance_rules = {} - if base_resource_type_for_sp: - if processed_ig_record: - if hasattr(processed_ig_record, 'search_param_conformance') and processed_ig_record.search_param_conformance: - all_conformance_data = processed_ig_record.search_param_conformance - search_param_conformance_rules = all_conformance_data.get(base_resource_type_for_sp, {}) - logger.debug(f"Retrieved conformance rules for {base_resource_type_for_sp} from DB: {search_param_conformance_rules}") - else: - logger.warning(f"ProcessedIg record found, but 'search_param_conformance' attribute/data is missing or empty for {package_name}#{version}.") - else: - logger.warning(f"No ProcessedIg record found for {package_name}#{version} to get conformance rules.") - if search_params_data: - logger.debug(f"Merging conformance data into {len(search_params_data)} search parameters.") - for param in search_params_data: - param_code = param.get('code') - if param_code: - conformance_level = search_param_conformance_rules.get(param_code, 'Optional') - param['conformance'] = conformance_level - else: - param['conformance'] = 'Unknown' - logger.debug("Finished merging conformance data.") - else: - logger.debug(f"No search parameters found for {base_resource_type_for_sp} to merge conformance data into.") - else: - logger.warning(f"Cannot fetch conformance data because base resource type (e.g., Patient) for '{resource_type}' could not be determined.") - for param in search_params_data: - if 'conformance' not in param or param['conformance'] == 'N/A': - param['conformance'] = 'Optional' - response_data = { - 'elements': enriched_elements, - 'must_support_paths': must_support_paths, - 'search_parameters': search_params_data, - 'fallback_used': fallback_used, - 'source_package': source_package_id - } - return Response(json.dumps(response_data, indent=None, separators=(',', ':')), mimetype='application/json') -#------------------------------------------------------------------------ - - -@app.route('/get-package-metadata') -@swag_from({ - 'tags': ['Package Management'], - 'summary': 'Get metadata for a downloaded package.', - 'parameters': [ - {'name': 'package_name', 'in': 'query', 'type': 'string', 'required': True}, - {'name': 'version', 'in': 'query', 'type': 'string', 'required': True} - ], - 'responses': { - '200': { - 'description': 'Package metadata.', - 'schema': { - 'type': 'object', - 'properties': { - 'package_name': {'type': 'string'}, - 'version': {'type': 'string'}, - 'dependency_mode': {'type': 'string'}, - 'imported_dependencies': {'type': 'array', 'items': {'type': 'object'}}, - 'complies_with_profiles': {'type': 'array', 'items': {'type': 'string'}}, - 'imposed_profiles': {'type': 'array', 'items': {'type': 'string'}} - } - } - }, - '400': {'description': 'Missing parameters.'}, - '404': {'description': 'Metadata not found.'}, - '500': {'description': 'Server error.'} - } -}) -def get_package_metadata(): - package_name = request.args.get('package_name') - version = request.args.get('version') - if not package_name or not version: - return jsonify({'error': 'Missing package_name or version parameter'}), 400 - try: - metadata = services.get_package_metadata(package_name, version) - if metadata: - return jsonify({ - 'package_name': metadata.get('package_name'), - 'version': metadata.get('version'), - 'dependency_mode': metadata.get('dependency_mode'), - 'imported_dependencies': metadata.get('imported_dependencies', []), - 'complies_with_profiles': metadata.get('complies_with_profiles', []), - 'imposed_profiles': metadata.get('imposed_profiles', []) - }) - else: - return jsonify({'error': 'Metadata file not found for this package version.'}), 404 - except Exception as e: - logger.error(f"Error retrieving metadata for {package_name}#{version}: {e}", exc_info=True) - return jsonify({'error': f'Error retrieving metadata: {str(e)}'}), 500 - -@app.route('/api/import-ig', methods=['POST']) -@swag_from({ - 'tags': ['Package Management'], - 'summary': 'Import a FHIR Implementation Guide via API.', - 'description': 'Downloads and processes a FHIR IG and its dependencies.', - 'security': [{'ApiKeyAuth': []}], - 'consumes': ['application/json'], - 'parameters': [ - { - 'name': 'body', - 'in': 'body', - 'required': True, - 'schema': { - 'type': 'object', - 'required': ['package_name', 'version'], - 'properties': { - 'package_name': {'type': 'string', 'example': 'hl7.fhir.us.core'}, - 'version': {'type': 'string', 'example': '6.1.0'}, - 'dependency_mode': { - 'type': 'string', 'enum': ['recursive', 'patch-canonical', 'tree-shaking', 'direct'], - 'default': 'recursive' - } - } - } - } - ], - 'responses': { - '200': {'description': 'Package imported successfully or with warnings.'}, - '400': {'description': 'Invalid request (e.g., missing fields, invalid mode).'}, - '404': {'description': 'Package not found on registry.'}, - '500': {'description': 'Server error during import.'} - } -}) -def api_import_ig(): - auth_error = check_api_key() - if auth_error: - return auth_error - if not request.is_json: - return jsonify({"status": "error", "message": "Request must be JSON"}), 400 - data = request.get_json() - package_name = data.get('package_name') - version = data.get('version') - dependency_mode = data.get('dependency_mode', 'recursive') - if not package_name or not version: - return jsonify({"status": "error", "message": "Missing package_name or version"}), 400 - if not (isinstance(package_name, str) and isinstance(version, str) and - re.match(r'^[a-zA-Z0-9\-\.]+$', package_name) and - re.match(r'^[a-zA-Z0-9\.\-\+]+$', version)): - return jsonify({"status": "error", "message": "Invalid characters in package name or version"}), 400 - valid_modes = ['recursive', 'patch-canonical', 'tree-shaking', 'direct'] - if dependency_mode not in valid_modes: - return jsonify({"status": "error", "message": f"Invalid dependency mode: {dependency_mode}. Must be one of {valid_modes}"}), 400 - try: - result = services.import_package_and_dependencies(package_name, version, dependency_mode=dependency_mode) - if result['errors'] and not result['downloaded']: - error_msg = f"Failed to import {package_name}#{version}: {result['errors'][0]}" - logger.error(f"[API] Import failed: {error_msg}") - status_code = 404 if "404" in result['errors'][0] else 500 - return jsonify({"status": "error", "message": error_msg}), status_code - package_filename = services.construct_tgz_filename(package_name, version) - packages_dir = current_app.config.get('FHIR_PACKAGES_DIR', '/app/instance/fhir_packages') - package_path = os.path.join(packages_dir, package_filename) - complies_with_profiles = [] - imposed_profiles = [] - processing_errors = [] - if os.path.exists(package_path): - logger.info(f"[API] Processing downloaded package {package_path} for metadata.") - process_result = services.process_package_file(package_path) - complies_with_profiles = process_result.get('complies_with_profiles', []) - imposed_profiles = process_result.get('imposed_profiles', []) - if process_result.get('errors'): - processing_errors.extend(process_result['errors']) - logger.warning(f"[API] Errors during post-import processing of {package_name}#{version}: {processing_errors}") - else: - logger.warning(f"[API] Package file {package_path} not found after reported successful download.") - processing_errors.append("Package file disappeared after download.") - all_packages, errors, duplicate_groups_after = list_downloaded_packages(packages_dir) - duplicates_found = [] - for name, versions in duplicate_groups_after.items(): - duplicates_found.append(f"{name} (Versions present: {', '.join(versions)})") - response_status = "success" - response_message = "Package imported successfully." - if result['errors'] or processing_errors: - response_status = "warning" - response_message = "Package imported, but some errors occurred during processing or dependency handling." - all_issues = result.get('errors', []) + processing_errors - logger.warning(f"[API] Import for {package_name}#{version} completed with warnings/errors: {all_issues}") - response = { - "status": response_status, - "message": response_message, - "package_name": package_name, - "version": version, - "dependency_mode": dependency_mode, - "dependencies_processed": result.get('dependencies', []), - "complies_with_profiles": complies_with_profiles, - "imposed_profiles": imposed_profiles, - "processing_issues": result.get('errors', []) + processing_errors, - "duplicate_packages_present": duplicates_found - } - return jsonify(response), 200 - except Exception as e: - logger.error(f"[API] Unexpected error in api_import_ig for {package_name}#{version}: {str(e)}", exc_info=True) - return jsonify({"status": "error", "message": f"Unexpected server error during import: {str(e)}"}), 500 - -@app.route('/api/push-ig', methods=['POST']) -@csrf.exempt # Retain CSRF exemption as specified -@swag_from({ - 'tags': ['Package Management'], - 'summary': 'Push a FHIR Implementation Guide to a server via API.', - 'description': 'Uploads resources from a specified FHIR IG (and optionally its dependencies) to a target FHIR server. Returns an NDJSON stream of progress.', - 'security': [{'ApiKeyAuth': []}], - 'consumes': ['application/json'], - 'produces': ['application/x-ndjson'], - 'parameters': [ - { - 'name': 'body', - 'in': 'body', - 'required': True, - 'schema': { - 'type': 'object', - 'required': ['package_name', 'version', 'fhir_server_url'], - 'properties': { - 'package_name': {'type': 'string', 'example': 'hl7.fhir.us.core'}, - 'version': {'type': 'string', 'example': '6.1.0'}, - 'fhir_server_url': {'type': 'string', 'format': 'url', 'example': 'http://localhost:8080/fhir'}, - 'include_dependencies': {'type': 'boolean', 'default': True}, - 'auth_type': {'type': 'string', 'enum': ['apiKey', 'bearerToken', 'basic', 'none'], 'default': 'none'}, - 'auth_token': {'type': 'string', 'description': 'Required if auth_type is bearerToken or basic (for basic, use "Basic ")'}, - 'username': {'type': 'string', 'description': 'Required if auth_type is basic'}, - 'password': {'type': 'string', 'format': 'password', 'description': 'Required if auth_type is basic'}, - 'resource_types_filter': {'type': 'array', 'items': {'type': 'string'}, 'description': 'List of resource types to include.'}, - 'skip_files': {'type': 'array', 'items': {'type': 'string'}, 'description': 'List of specific file paths within packages to skip.'}, - 'dry_run': {'type': 'boolean', 'default': False}, - 'verbose': {'type': 'boolean', 'default': False}, - 'force_upload': {'type': 'boolean', 'default': False, 'description': 'If true, uploads resources even if they appear identical to server versions.'} - } - } - } - ], - 'responses': { - '200': {'description': 'NDJSON stream of push progress and results.'}, - '400': {'description': 'Invalid request parameters.'}, - '401': {'description': 'Authentication error.'}, - '404': {'description': 'Package not found locally.'}, - '500': {'description': 'Server error during push operation setup.'} - } -}) -def api_push_ig(): - auth_error = check_api_key() - if auth_error: return auth_error - if not request.is_json: return jsonify({"status": "error", "message": "Request must be JSON"}), 400 - - data = request.get_json() - package_name = data.get('package_name') - version = data.get('version') - fhir_server_url = data.get('fhir_server_url') - include_dependencies = data.get('include_dependencies', True) - auth_type = data.get('auth_type', 'none') - auth_token = data.get('auth_token') - username = data.get('username') # ADD: Extract username - password = data.get('password') # ADD: Extract password - resource_types_filter_raw = data.get('resource_types_filter') - skip_files_raw = data.get('skip_files') - dry_run = data.get('dry_run', False) - verbose = data.get('verbose', False) - force_upload = data.get('force_upload', False) - - # --- Input Validation --- - if not all([package_name, version, fhir_server_url]): return jsonify({"status": "error", "message": "Missing required fields"}), 400 - valid_auth_types = ['apiKey', 'bearerToken', 'basic', 'none'] # ADD: 'basic' to valid auth types - if auth_type not in valid_auth_types: return jsonify({"status": "error", "message": f"Invalid auth_type."}), 400 - if auth_type == 'bearerToken' and not auth_token: return jsonify({"status": "error", "message": "auth_token required for bearerToken."}), 400 - if auth_type == 'basic' and (not username or not password): # ADD: Validate Basic Auth inputs - return jsonify({"status": "error", "message": "Username and password required for Basic Authentication."}), 400 - - # Parse filters (unchanged) - resource_types_filter = None - if resource_types_filter_raw: - if isinstance(resource_types_filter_raw, list): resource_types_filter = [s for s in resource_types_filter_raw if isinstance(s, str)] - elif isinstance(resource_types_filter_raw, str): resource_types_filter = [s.strip() for s in resource_types_filter_raw.split(',') if s.strip()] - else: return jsonify({"status": "error", "message": "Invalid resource_types_filter format."}), 400 - skip_files = None - if skip_files_raw: - if isinstance(skip_files_raw, list): skip_files = [s.strip().replace('\\', '/') for s in skip_files_raw if isinstance(s, str) and s.strip()] - elif isinstance(skip_files_raw, str): skip_files = [s.strip().replace('\\', '/') for s in re.split(r'[,\n]', skip_files_raw) if s.strip()] - else: return jsonify({"status": "error", "message": "Invalid skip_files format."}), 400 - - # --- File Path Setup (unchanged) --- - packages_dir = current_app.config.get('FHIR_PACKAGES_DIR') - if not packages_dir: return jsonify({"status": "error", "message": "Server config error: Package dir missing."}), 500 - tgz_filename = services.construct_tgz_filename(package_name, version) - tgz_path = os.path.join(packages_dir, tgz_filename) - if not os.path.exists(tgz_path): return jsonify({"status": "error", "message": f"Package not found locally: {package_name}#{version}"}), 404 - - # ADD: Handle Basic Authentication - if auth_type == 'basic': - credentials = f"{username}:{password}" - auth_token = f"Basic {base64.b64encode(credentials.encode('utf-8')).decode('utf-8')}" - - # --- Streaming Response --- - def generate_stream_wrapper(): - yield from services.generate_push_stream( - package_name=package_name, version=version, fhir_server_url=fhir_server_url, - include_dependencies=include_dependencies, auth_type=auth_type, - auth_token=auth_token, resource_types_filter=resource_types_filter, - skip_files=skip_files, dry_run=dry_run, verbose=verbose, - force_upload=force_upload, packages_dir=packages_dir - ) - return Response(generate_stream_wrapper(), mimetype='application/x-ndjson') - -# Ensure csrf.exempt(api_push_ig) remains - -@app.route('/validate-sample', methods=['GET']) -def validate_sample(): - form = ValidationForm() - packages = [] - packages_dir = app.config['FHIR_PACKAGES_DIR'] - if os.path.exists(packages_dir): - for filename in os.listdir(packages_dir): - if filename.endswith('.tgz'): - try: - with tarfile.open(os.path.join(packages_dir, filename), 'r:gz') as tar: - package_json = tar.extractfile('package/package.json') - if package_json: - pkg_info = json.load(package_json) - name = pkg_info.get('name') - version = pkg_info.get('version') - if name and version: - packages.append({'name': name, 'version': version}) - except Exception as e: - logger.warning(f"Error reading package {filename}: {e}") - continue - return render_template( - 'validate_sample.html', - form=form, - packages=packages, - validation_report=None, - site_name='FHIRFLARE IG Toolkit', - now=datetime.datetime.now(), app_mode=app.config['APP_MODE'] - ) - -# Exempt specific API views defined directly on 'app' -csrf.exempt(api_import_ig) # Add this line -csrf.exempt(api_push_ig) # Add this line - -# Exempt the entire API blueprint (for routes defined IN services.py, like /api/validate-sample) -csrf.exempt(services_bp) # Keep this line for routes defined in the blueprint - -def create_db(): - logger.debug(f"Attempting to create database tables for URI: {app.config['SQLALCHEMY_DATABASE_URI']}") - try: - db.create_all() # This will create RegistryCacheInfo if it doesn't exist - # Optionally initialize the timestamp row if it's missing - with app.app_context(): - if RegistryCacheInfo.query.first() is None: - initial_info = RegistryCacheInfo(last_fetch_timestamp=None) - db.session.add(initial_info) - db.session.commit() - logger.info("Initialized RegistryCacheInfo table.") - logger.info("Database tables created/verified successfully.") - except Exception as e: - logger.error(f"Failed to initialize database tables: {e}", exc_info=True) - #db.session.rollback() # Rollback in case of error during init - raise - -with app.app_context(): - create_db() - - -class FhirRequestForm(FlaskForm): - submit = SubmitField('Send Request') - -@app.route('/fhir-ui') -def fhir_ui(): - form = FhirRequestForm() - return render_template('fhir_ui.html', form=form, site_name='FHIRFLARE IG Toolkit', now=datetime.datetime.now(), app_mode=app.config['APP_MODE']) - -@app.route('/fhir-ui-operations') -def fhir_ui_operations(): - form = FhirRequestForm() - return render_template('fhir_ui_operations.html', form=form, site_name='FHIRFLARE IG Toolkit', now=datetime.datetime.now(), app_mode=app.config['APP_MODE']) - -# --- CORRECTED PROXY FUNCTION DEFINITION (Simplified Decorator) --- - -# Use a single route to capture everything after /fhir/ -# The 'path' converter handles slashes. 'subpath' can be empty. -@app.route('/fhir', defaults={'subpath': ''}, methods=['GET', 'POST', 'PUT', 'DELETE']) -@app.route('/fhir/', defaults={'subpath': ''}, methods=['GET', 'POST', 'PUT', 'DELETE']) -@app.route('/fhir/', methods=['GET', 'POST', 'PUT', 'DELETE']) -def proxy_hapi(subpath): - """ - Proxies FHIR requests to either the local HAPI server or a custom - target server specified by the 'X-Target-FHIR-Server' header. - Handles requests to /fhir/ (base, subpath='') and /fhir/. - The route '/fhir' (no trailing slash) is handled separately for the UI. - """ - # Clean subpath just in case prefixes were somehow included - clean_subpath = subpath.replace('r4/', '', 1).replace('fhir/', '', 1).strip('/') - logger.debug(f"Proxy received request for path: '/fhir/{subpath}', cleaned subpath: '{clean_subpath}'") - - # Determine the target FHIR server base URL - target_server_header = request.headers.get('X-Target-FHIR-Server') - final_base_url = None - is_custom_target = False - - if target_server_header: - try: - parsed_url = urlparse(target_server_header) - if not parsed_url.scheme or not parsed_url.netloc: - raise ValueError("Invalid URL format in X-Target-FHIR-Server header") - final_base_url = target_server_header.rstrip('/') - is_custom_target = True - logger.info(f"Proxy target identified from header: {final_base_url}") - except ValueError as e: - logger.warning(f"Invalid URL in X-Target-FHIR-Server header: '{target_server_header}'. Falling back. Error: {e}") - final_base_url = current_app.config['HAPI_FHIR_URL'].rstrip('/') - logger.debug(f"Falling back to default local HAPI due to invalid header: {final_base_url}") - else: - final_base_url = current_app.config['HAPI_FHIR_URL'].rstrip('/') - logger.debug(f"No target header found, proxying to default local HAPI: {final_base_url}") - - # Construct the final URL for the target server request - # Append the cleaned subpath only if it's not empty - final_url = f"{final_base_url}/{clean_subpath}" if clean_subpath else final_base_url - - # Prepare headers to forward - headers_to_forward = { - k: v for k, v in request.headers.items() - if k.lower() not in [ - 'host', 'x-target-fhir-server', 'content-length', 'connection', - 'keep-alive', 'proxy-authenticate', 'proxy-authorization', 'te', - 'trailers', 'transfer-encoding', 'upgrade' - ] - } - if 'Content-Type' in request.headers: - headers_to_forward['Content-Type'] = request.headers['Content-Type'] - if 'Accept' in request.headers: - headers_to_forward['Accept'] = request.headers['Accept'] - elif 'Accept' not in headers_to_forward: - headers_to_forward['Accept'] = 'application/fhir+json, application/fhir+xml;q=0.9, */*;q=0.8' - - logger.info(f"Proxying request: {request.method} {final_url}") - request_data = request.get_data() - - try: - # Make the request - response = requests.request( - method=request.method, - url=final_url, - headers=headers_to_forward, - data=request_data, - cookies=request.cookies, - allow_redirects=False, - timeout=60 - ) - logger.info(f"Target server '{final_base_url}' responded with status: {response.status_code}") - response.raise_for_status() - - # Filter hop-by-hop headers - response_headers = { k: v for k, v in response.headers.items() if k.lower() not in ('transfer-encoding', 'connection', 'content-encoding', 'content-length', 'keep-alive', 'proxy-authenticate', 'proxy-authorization', 'te', 'trailers', 'upgrade', 'server', 'date', 'x-powered-by', 'via', 'x-forwarded-for', 'x-forwarded-proto', 'x-request-id') } - response_content = response.content - response_headers['Content-Length'] = str(len(response_content)) - - # Create Flask response - resp = make_response(response_content) - resp.status_code = response.status_code - for key, value in response_headers.items(): resp.headers[key] = value - if 'Content-Type' in response.headers: resp.headers['Content-Type'] = response.headers['Content-Type'] - return resp - - # --- Exception Handling (same as previous version) --- - except requests.exceptions.Timeout: - error_msg = f"Request to the target FHIR server timed out: {final_url}" - logger.error(f"Proxy timeout error: {error_msg}") - return jsonify({'resourceType': 'OperationOutcome', 'issue': [{'severity': 'error', 'code': 'timeout', 'diagnostics': error_msg}]}), 504 - except requests.exceptions.ConnectionError as e: - target_name = 'custom server' if is_custom_target else 'local HAPI' - error_message = f"Could not connect to the target FHIR server ({target_name} at {final_base_url}). Please check the URL and server status." - logger.error(f"Proxy connection error: {error_message} - {str(e)}") - return jsonify({'resourceType': 'OperationOutcome', 'issue': [{'severity': 'error', 'code': 'exception', 'diagnostics': error_message, 'details': {'text': str(e)}}]}), 503 - except requests.exceptions.HTTPError as e: - logger.warning(f"Proxy received HTTP error from target {final_url}: {e.response.status_code}") - try: - error_response_headers = { k: v for k, v in e.response.headers.items() if k.lower() not in ('transfer-encoding', 'connection', 'content-encoding','content-length', 'keep-alive', 'proxy-authenticate','proxy-authorization', 'te', 'trailers', 'upgrade','server', 'date', 'x-powered-by', 'via', 'x-forwarded-for','x-forwarded-proto', 'x-request-id') } - error_content = e.response.content - error_response_headers['Content-Length'] = str(len(error_content)) - error_resp = make_response(error_content) - error_resp.status_code = e.response.status_code - for key, value in error_response_headers.items(): error_resp.headers[key] = value - if 'Content-Type' in e.response.headers: error_resp.headers['Content-Type'] = e.response.headers['Content-Type'] - return error_resp - except Exception as inner_e: - logger.error(f"Failed to process target server's error response: {inner_e}") - diag_text = f'Target server returned status {e.response.status_code}, but failed to forward its error details.' - return jsonify({'resourceType': 'OperationOutcome', 'issue': [{'severity': 'error', 'code': 'exception', 'diagnostics': diag_text, 'details': {'text': str(e)}}]}), e.response.status_code or 502 - except requests.exceptions.RequestException as e: - logger.error(f"Proxy request error for {final_url}: {str(e)}") - return jsonify({'resourceType': 'OperationOutcome', 'issue': [{'severity': 'error', 'code': 'exception', 'diagnostics': 'Error communicating with the target FHIR server.', 'details': {'text': str(e)}}]}), 502 - except Exception as e: - logger.error(f"Unexpected proxy error for {final_url}: {str(e)}", exc_info=True) - return jsonify({'resourceType': 'OperationOutcome', 'issue': [{'severity': 'error', 'code': 'exception', 'diagnostics': 'An unexpected error occurred within the FHIR proxy.', 'details': {'text': str(e)}}]}), 500 - -# --- End of corrected proxy_hapi function --- - - -@app.route('/api/load-ig-to-hapi', methods=['POST']) -@swag_from({ - 'tags': ['HAPI Integration'], - 'summary': 'Load an IG into the local HAPI FHIR server.', - 'description': 'Extracts all resources from a specified IG package and PUTs them to the configured HAPI FHIR server.', - 'security': [{'ApiKeyAuth': []}], - 'consumes': ['application/json'], - 'parameters': [ - { - 'name': 'body', - 'in': 'body', - 'required': True, - 'schema': { - 'type': 'object', - 'required': ['package_name', 'version'], - 'properties': { - 'package_name': {'type': 'string', 'example': 'hl7.fhir.us.core'}, - 'version': {'type': 'string', 'example': '6.1.0'} - } - } - } - ], - 'responses': { - '200': {'description': 'Package loaded to HAPI successfully.'}, - '400': {'description': 'Invalid request (e.g., missing package_name/version).'}, - '404': {'description': 'Package not found locally.'}, - '500': {'description': 'Error loading IG to HAPI (e.g., HAPI server connection issue, resource upload failure).'} - } -}) -def load_ig_to_hapi(): - data = request.get_json() - package_name = data.get('package_name') - version = data.get('version') - tgz_path = os.path.join(current_app.config['FHIR_PACKAGES_DIR'], construct_tgz_filename(package_name, version)) - if not os.path.exists(tgz_path): - return jsonify({"error": "Package not found"}), 404 - try: - with tarfile.open(tgz_path, "r:gz") as tar: - for member in tar.getmembers(): - if member.name.endswith('.json') and member.name not in ['package/package.json', 'package/.index.json']: - resource = json.load(tar.extractfile(member)) - resource_type = resource.get('resourceType') - resource_id = resource.get('id') - if resource_type and resource_id: - response = requests.put( - f"{current_app.config['HAPI_FHIR_URL'].rstrip('/')}/{resource_type}/{resource_id}", - json=resource, - headers={'Content-Type': 'application/fhir+json'} - ) - response.raise_for_status() - return jsonify({"status": "success", "message": f"Loaded {package_name}#{version} to HAPI"}) - except Exception as e: - logger.error(f"Failed to load IG to HAPI: {e}") - return jsonify({"error": str(e)}), 500 - - -# Assuming 'app' and 'logger' are defined, and other necessary imports are present above - -@app.route('/fsh-converter', methods=['GET', 'POST']) -def fsh_converter(): - form = FSHConverterForm() - fsh_output = None - error = None - comparison_report = None - - # --- Populate package choices --- - packages = [] - packages_dir = app.config.get('FHIR_PACKAGES_DIR', '/app/instance/fhir_packages') # Use .get with default - logger.debug(f"Scanning packages directory: {packages_dir}") - if os.path.exists(packages_dir): - tgz_files = [f for f in os.listdir(packages_dir) if f.endswith('.tgz')] - logger.debug(f"Found {len(tgz_files)} .tgz files: {tgz_files}") - for filename in tgz_files: - package_file_path = os.path.join(packages_dir, filename) - try: - # Check if it's a valid tar.gz file before opening - if not tarfile.is_tarfile(package_file_path): - logger.warning(f"Skipping non-tarfile or corrupted file: {filename}") - continue - - with tarfile.open(package_file_path, 'r:gz') as tar: - # Find package.json case-insensitively and handle potential path variations - package_json_path = next((m for m in tar.getmembers() if m.name.lower().endswith('package.json') and m.isfile() and ('/' not in m.name.replace('package/','', 1).lower())), None) # Handle package/ prefix better - - if package_json_path: - package_json_stream = tar.extractfile(package_json_path) - if package_json_stream: - try: - pkg_info = json.load(package_json_stream) - name = pkg_info.get('name') - version = pkg_info.get('version') - if name and version: - package_id = f"{name}#{version}" - packages.append((package_id, package_id)) - logger.debug(f"Added package: {package_id}") - else: - logger.warning(f"Missing name or version in {filename}/package.json: name={name}, version={version}") - except json.JSONDecodeError as json_e: - logger.warning(f"Error decoding package.json from {filename}: {json_e}") - except Exception as read_e: - logger.warning(f"Error reading stream from package.json in {filename}: {read_e}") - finally: - package_json_stream.close() # Ensure stream is closed - else: - logger.warning(f"Could not extract package.json stream from {filename} (path: {package_json_path.name})") - else: - logger.warning(f"No suitable package.json found in {filename}") - except tarfile.ReadError as tar_e: - logger.warning(f"Tarfile read error for {filename}: {tar_e}") - except Exception as e: - logger.warning(f"Error processing package {filename}: {str(e)}") - continue # Continue to next file - else: - logger.warning(f"Packages directory does not exist: {packages_dir}") - - unique_packages = sorted(list(set(packages)), key=lambda x: x[0]) - form.package.choices = [('', 'None')] + unique_packages - logger.debug(f"Set package choices: {form.package.choices}") - # --- End package choices --- - - if form.validate_on_submit(): # This block handles POST requests - input_mode = form.input_mode.data - # Use request.files.get to safely access file data - fhir_file_storage = request.files.get(form.fhir_file.name) - fhir_file = fhir_file_storage if fhir_file_storage and fhir_file_storage.filename != '' else None - - fhir_text = form.fhir_text.data - - alias_file_storage = request.files.get(form.alias_file.name) - alias_file = alias_file_storage if alias_file_storage and alias_file_storage.filename != '' else None - - output_style = form.output_style.data - log_level = form.log_level.data - fhir_version = form.fhir_version.data if form.fhir_version.data != 'auto' else None - fishing_trip = form.fishing_trip.data - dependencies = [dep.strip() for dep in form.dependencies.data.splitlines() if dep.strip()] if form.dependencies.data else None # Use splitlines() - indent_rules = form.indent_rules.data - meta_profile = form.meta_profile.data - no_alias = form.no_alias.data - - logger.debug(f"Processing input: mode={input_mode}, has_file={bool(fhir_file)}, has_text={bool(fhir_text)}, has_alias={bool(alias_file)}") - # Pass the FileStorage object directly if needed by process_fhir_input - input_file, temp_dir, alias_path, input_error = services.process_fhir_input(input_mode, fhir_file, fhir_text, alias_file) - - if input_error: - error = input_error - flash(error, 'error') - logger.error(f"Input processing error: {error}") - if temp_dir and os.path.exists(temp_dir): - try: shutil.rmtree(temp_dir, ignore_errors=True) - except Exception as cleanup_e: logger.warning(f"Error removing temp dir after input error {temp_dir}: {cleanup_e}") - else: - # Proceed only if input processing was successful - output_dir = os.path.join(app.config.get('UPLOAD_FOLDER', '/app/static/uploads'), 'fsh_output') # Use .get - os.makedirs(output_dir, exist_ok=True) - logger.debug(f"Running GoFSH with input: {input_file}, output_dir: {output_dir}") - # Pass form data directly to run_gofsh - fsh_output, comparison_report, gofsh_error = services.run_gofsh( - input_file, output_dir, output_style, log_level, fhir_version, - fishing_trip, dependencies, indent_rules, meta_profile, alias_path, no_alias - ) - # Clean up temp dir after GoFSH run - if temp_dir and os.path.exists(temp_dir): - try: - shutil.rmtree(temp_dir, ignore_errors=True) - logger.debug(f"Successfully removed temp directory: {temp_dir}") - except Exception as cleanup_e: - logger.warning(f"Error removing temp directory {temp_dir}: {cleanup_e}") - - if gofsh_error: - error = gofsh_error - flash(error, 'error') - logger.error(f"GoFSH error: {error}") - else: - # Store potentially large output carefully - session might have limits - session['fsh_output'] = fsh_output - flash('Conversion successful!', 'success') - logger.info("FSH conversion successful") - - # Return response for POST (AJAX or full page) - if request.headers.get('X-Requested-With') == 'XMLHttpRequest': - logger.debug("Returning partial HTML for AJAX POST request.") - return render_template('_fsh_output.html', form=form, error=error, fsh_output=fsh_output, comparison_report=comparison_report) - else: - # For standard POST, re-render the full page with results/errors - logger.debug("Handling standard POST request, rendering full page.") - return render_template('fsh_converter.html', form=form, error=error, fsh_output=fsh_output, comparison_report=comparison_report, site_name='FHIRFLARE IG Toolkit', now=datetime.datetime.now()) - - # --- Handle GET request (Initial Page Load or Failed POST Validation) --- - else: - if request.method == 'POST': # POST but validation failed - logger.warning("POST request failed form validation.") - # Render the full page, WTForms errors will be displayed by render_field - return render_template('fsh_converter.html', form=form, error="Form validation failed. Please check fields.", fsh_output=None, comparison_report=None, site_name='FHIRFLARE IG Toolkit', now=datetime.datetime.now()) - else: - # This is the initial GET request - logger.debug("Handling GET request for FSH converter page.") - # **** FIX APPLIED HERE **** - # Make the response object to add headers - response = make_response(render_template( - 'fsh_converter.html', - form=form, # Pass the empty form - error=None, - fsh_output=None, - comparison_report=None, - site_name='FHIRFLARE IG Toolkit', - now=datetime.datetime.now() - )) - # Add headers to prevent caching - response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0' - response.headers['Pragma'] = 'no-cache' - response.headers['Expires'] = '0' - return response - # **** END OF FIX **** - -@app.route('/download-fsh') -def download_fsh(): - fsh_output = session.get('fsh_output') - if not fsh_output: - flash('No FSH output available for download.', 'error') - return redirect(url_for('fsh_converter')) - - temp_file = os.path.join(app.config['UPLOAD_FOLDER'], 'output.fsh') - with open(temp_file, 'w', encoding='utf-8') as f: - f.write(fsh_output) - - return send_file(temp_file, as_attachment=True, download_name='output.fsh') - -@app.route('/upload-test-data', methods=['GET']) -def upload_test_data(): - """Renders the page for uploading test data.""" - form = TestDataUploadForm() - try: - processed_igs = ProcessedIg.query.order_by(ProcessedIg.package_name, ProcessedIg.version).all() - form.validation_package_id.choices = [('', '-- Select Package for Validation --')] + [ - (f"{ig.package_name}#{ig.version}", f"{ig.package_name}#{ig.version}") for ig in processed_igs ] - except Exception as e: - logger.error(f"Error fetching processed IGs: {e}") - flash("Could not load processed packages for validation.", "warning") - form.validation_package_id.choices = [('', '-- Error Loading Packages --')] - api_key = current_app.config.get('API_KEY', '') - return render_template('upload_test_data.html', title="Upload Test Data", form=form, api_key=api_key) - - -# --- Updated /api/upload-test-data Endpoint --- -@app.route('/api/upload-test-data', methods=['POST']) -@csrf.exempt -@swag_from({ - 'tags': ['Test Data Management'], - 'summary': 'Upload and process FHIR test data.', - 'description': 'Handles multipart/form-data uploads of FHIR resources (JSON, XML, or ZIP containing these) for processing and uploading to a target FHIR server. Returns an NDJSON stream of progress.', - 'security': [{'ApiKeyAuth': []}], - 'consumes': ['multipart/form-data'], - 'produces': ['application/x-ndjson'], - 'parameters': [ - {'name': 'fhir_server_url', 'in': 'formData', 'type': 'string', 'required': True, 'format': 'url', 'description': 'Target FHIR server URL.'}, - {'name': 'auth_type', 'in': 'formData', 'type': 'string', 'enum': ['none', 'bearerToken', 'basic'], 'default': 'none'}, - {'name': 'auth_token', 'in': 'formData', 'type': 'string', 'description': 'Bearer token if auth_type is bearerToken.'}, - {'name': 'username', 'in': 'formData', 'type': 'string', 'description': 'Username if auth_type is basic.'}, - {'name': 'password', 'in': 'formData', 'type': 'string', 'format': 'password', 'description': 'Password if auth_type is basic.'}, - {'name': 'test_data_files', 'in': 'formData', 'type': 'file', 'required': True, 'description': 'One or more FHIR resource files (JSON, XML) or ZIP archives containing them.'}, - {'name': 'validate_before_upload', 'in': 'formData', 'type': 'boolean', 'default': False}, - {'name': 'validation_package_id', 'in': 'formData', 'type': 'string', 'description': 'Package ID (name#version) for validation, if validate_before_upload is true.'}, - {'name': 'upload_mode', 'in': 'formData', 'type': 'string', 'enum': ['individual', 'transaction'], 'default': 'individual'}, - {'name': 'use_conditional_uploads', 'in': 'formData', 'type': 'boolean', 'default': True, 'description': 'For individual mode, use conditional logic (GET then PUT/POST).'}, - {'name': 'error_handling', 'in': 'formData', 'type': 'string', 'enum': ['stop', 'continue'], 'default': 'stop'} - ], - 'responses': { - '200': {'description': 'NDJSON stream of upload progress and results.'}, - '400': {'description': 'Invalid request parameters or file types.'}, - '401': {'description': 'Authentication error.'}, - '413': {'description': 'Request entity too large.'}, - '500': {'description': 'Server error during upload processing.'} - } -}) -def api_upload_test_data(): - """API endpoint to handle test data upload and processing, using custom parser.""" - auth_error = check_api_key() - if auth_error: return auth_error - - temp_dir = None - try: - parser = CustomFormDataParser() - stream = request.stream - mimetype = request.mimetype - content_length = request.content_length - options = request.mimetype_params - _, form_data, files_data = parser.parse(stream, mimetype, content_length, options) - logger.debug(f"Form parsed using CustomFormDataParser. Form fields: {len(form_data)}, Files: {len(files_data)}") - - # --- Extract Form Data --- - fhir_server_url = form_data.get('fhir_server_url') - auth_type = form_data.get('auth_type', 'none') - auth_token = form_data.get('auth_token') - username = form_data.get('username') - password = form_data.get('password') - upload_mode = form_data.get('upload_mode', 'individual') - error_handling = form_data.get('error_handling', 'stop') - validate_before_upload_str = form_data.get('validate_before_upload', 'false') - validate_before_upload = validate_before_upload_str.lower() == 'true' - validation_package_id = form_data.get('validation_package_id') if validate_before_upload else None - use_conditional_uploads_str = form_data.get('use_conditional_uploads', 'false') - use_conditional_uploads = use_conditional_uploads_str.lower() == 'true' - - logger.debug(f"API Upload Request Params: validate={validate_before_upload}, pkg_id={validation_package_id}, conditional={use_conditional_uploads}") - - # --- Basic Validation --- - if not fhir_server_url or not fhir_server_url.startswith(('http://', 'https://')): - return jsonify({"status": "error", "message": "Invalid Target FHIR Server URL."}), 400 - if auth_type not in ['none', 'bearerToken', 'basic']: - return jsonify({"status": "error", "message": "Invalid Authentication Type."}), 400 - if auth_type == 'bearerToken' and not auth_token: - return jsonify({"status": "error", "message": "auth_token required for bearerToken."}), 400 - if auth_type == 'basic' and (not username or not password): - return jsonify({"status": "error", "message": "Username and Password required for Basic Authentication."}), 400 - if upload_mode not in ['individual', 'transaction']: - return jsonify({"status": "error", "message": "Invalid Upload Mode."}), 400 - if error_handling not in ['stop', 'continue']: - return jsonify({"status": "error", "message": "Invalid Error Handling mode."}), 400 - if validate_before_upload and not validation_package_id: - return jsonify({"status": "error", "message": "Validation Package ID required."}), 400 - - # --- Handle File Uploads --- - uploaded_files = files_data.getlist('test_data_files') - if not uploaded_files or all(f.filename == '' for f in uploaded_files): - return jsonify({"status": "error", "message": "No files selected."}), 400 - - temp_dir = tempfile.mkdtemp(prefix='fhirflare_upload_') - saved_file_paths = [] - allowed_extensions = {'.json', '.xml', '.zip'} - try: - for file_storage in uploaded_files: - if file_storage and file_storage.filename: - filename = secure_filename(file_storage.filename) - file_ext = os.path.splitext(filename)[1].lower() - if file_ext not in allowed_extensions: - raise ValueError(f"Invalid file type: '{filename}'. Only JSON, XML, ZIP allowed.") - save_path = os.path.join(temp_dir, filename) - file_storage.save(save_path) - saved_file_paths.append(save_path) - if not saved_file_paths: - raise ValueError("No valid files saved.") - logger.debug(f"Saved {len(saved_file_paths)} files to {temp_dir}") - except ValueError as ve: - if temp_dir and os.path.exists(temp_dir): - shutil.rmtree(temp_dir) - logger.warning(f"Upload rejected: {ve}") - return jsonify({"status": "error", "message": str(ve)}), 400 - except Exception as file_err: - if temp_dir and os.path.exists(temp_dir): - shutil.rmtree(temp_dir) - logger.error(f"Error saving uploaded files: {file_err}", exc_info=True) - return jsonify({"status": "error", "message": "Error saving uploaded files."}), 500 - - # --- Prepare Server Info and Options --- - server_info = {'url': fhir_server_url, 'auth_type': auth_type} - if auth_type == 'bearer': - server_info['auth_token'] = auth_token - elif auth_type == 'basic': - credentials = f"{username}:{password}" - encoded_credentials = base64.b64encode(credentials.encode('utf-8')).decode('utf-8') - server_info['auth_token'] = f"Basic {encoded_credentials}" - options = { - 'upload_mode': upload_mode, - 'error_handling': error_handling, - 'validate_before_upload': validate_before_upload, - 'validation_package_id': validation_package_id, - 'use_conditional_uploads': use_conditional_uploads - } - - # --- Call Service Function (Streaming Response) --- - def generate_stream_wrapper(): - try: - with app.app_context(): - yield from services.process_and_upload_test_data(server_info, options, temp_dir) - finally: - try: - logger.debug(f"Cleaning up temp dir: {temp_dir}") - shutil.rmtree(temp_dir) - except Exception as cleanup_e: - logger.error(f"Error cleaning up temp dir {temp_dir}: {cleanup_e}") - - return Response(generate_stream_wrapper(), mimetype='application/x-ndjson') - - except RequestEntityTooLarge as e: - logger.error(f"RequestEntityTooLarge error in /api/upload-test-data despite custom parser: {e}", exc_info=True) - if temp_dir and os.path.exists(temp_dir): - try: - shutil.rmtree(temp_dir) - except Exception as cleanup_e: - logger.error(f"Error cleaning up temp dir during exception: {cleanup_e}") - return jsonify({"status": "error", "message": f"Upload failed: Request entity too large. Try increasing parser limit or reducing files/size. ({str(e)})"}), 413 - - except Exception as e: - logger.error(f"Error in /api/upload-test-data: {e}", exc_info=True) - if temp_dir and os.path.exists(temp_dir): - try: - shutil.rmtree(temp_dir) - except Exception as cleanup_e: - logger.error(f"Error cleaning up temp dir during exception: {cleanup_e}") - return jsonify({"status": "error", "message": f"Unexpected server error: {str(e)}"}), 500 - -@app.route('/retrieve-split-data', methods=['GET', 'POST']) -def retrieve_split_data(): - form = RetrieveSplitDataForm() - if form.validate_on_submit(): - if form.submit_retrieve.data: - session['retrieve_params'] = { - 'fhir_server_url': form.fhir_server_url.data, - 'validate_references': form.validate_references.data, - 'resources': request.form.getlist('resources') - } - if form.bundle_zip.data: - # Save uploaded ZIP to temporary file - temp_dir = tempfile.gettempdir() - zip_path = os.path.join(temp_dir, 'uploaded_bundles.zip') - form.bundle_zip.data.save(zip_path) - session['retrieve_params']['bundle_zip_path'] = zip_path - flash('Bundle retrieval initiated. Download will start after processing.', 'info') - elif form.submit_split.data: - # Save uploaded ZIP to temporary file - temp_dir = tempfile.gettempdir() - zip_path = os.path.join(temp_dir, 'split_bundles.zip') - form.split_bundle_zip.data.save(zip_path) - session['split_params'] = {'split_bundle_zip_path': zip_path} - flash('Bundle splitting initiated. Download will start after processing.', 'info') - return render_template('retrieve_split_data.html', form=form, site_name='FHIRFLARE IG Toolkit', - now=datetime.datetime.now(), app_mode=app.config['APP_MODE'], - api_key=app.config['API_KEY']) - -@app.route('/api/retrieve-bundles', methods=['POST']) -@csrf.exempt -@swag_from({ - 'tags': ['Test Data Management'], - 'summary': 'Retrieve FHIR resource bundles from a server.', - 'description': 'Fetches bundles for specified resource types from a FHIR server. Optionally fetches referenced resources. Returns an NDJSON stream and prepares a ZIP file for download.', - 'security': [{'ApiKeyAuth': []}], - 'consumes': ['application/x-www-form-urlencoded'], # Or multipart/form-data if files are involved - 'produces': ['application/x-ndjson'], - 'parameters': [ - {'name': 'fhir_server_url', 'in': 'formData', 'type': 'string', 'required': False, 'format': 'url', 'description': 'Target FHIR server URL. Defaults to local proxy (/fhir).'}, - {'name': 'resources', 'in': 'formData', 'type': 'array', 'items': {'type': 'string'}, 'collectionFormat': 'multi', 'required': True, 'description': 'List of resource types to retrieve (e.g., Patient, Observation).'}, - {'name': 'validate_references', 'in': 'formData', 'type': 'boolean', 'default': False, 'description': 'Fetch resources referenced by the initial bundles.'}, - {'name': 'fetch_reference_bundles', 'in': 'formData', 'type': 'boolean', 'default': False, 'description': 'If fetching references, get full bundles for referenced types instead of individual resources.'}, - {'name': 'auth_type', 'in': 'formData', 'type': 'string', 'enum': ['none', 'bearer', 'basic'], 'default': 'none'}, - {'name': 'bearer_token', 'in': 'formData', 'type': 'string', 'description': 'Bearer token if auth_type is bearer.'}, - {'name': 'username', 'in': 'formData', 'type': 'string', 'description': 'Username if auth_type is basic.'}, - {'name': 'password', 'in': 'formData', 'type': 'string', 'format': 'password', 'description': 'Password if auth_type is basic.'} - ], - 'responses': { - '200': { - 'description': 'NDJSON stream of retrieval progress. X-Zip-Path header indicates path to the created ZIP file.', - 'headers': { - 'X-Zip-Path': {'type': 'string', 'description': 'Server path to the generated ZIP file.'} - } - }, - '400': {'description': 'Invalid request parameters.'}, - '401': {'description': 'Authentication error.'}, - '500': {'description': 'Server error during retrieval.'} - } -}) -def api_retrieve_bundles(): - auth_error = check_api_key() - if auth_error: - return auth_error - - # Use request.form for standard form data - params = request.form.to_dict() - resources = request.form.getlist('resources') - validate_references = params.get('validate_references', 'false').lower() == 'true' - fetch_reference_bundles = params.get('fetch_reference_bundles', 'false').lower() == 'true' - auth_type = params.get('auth_type', 'none') - bearer_token = params.get('bearer_token') - username = params.get('username') - password = params.get('password') - - # Get FHIR server URL, default to '/fhir' (local proxy) - fhir_server_url = params.get('fhir_server_url', '/fhir').strip() - if not fhir_server_url: - fhir_server_url = '/fhir' - - # Validation - if not resources: - return jsonify({"status": "error", "message": "No resources selected."}), 400 - valid_auth_types = ['none', 'bearer', 'basic'] - if auth_type not in valid_auth_types: - return jsonify({"status": "error", "message": f"Invalid auth_type. Must be one of {valid_auth_types}."}), 400 - if auth_type == 'bearer' and not bearer_token: - return jsonify({"status": "error", "message": "Bearer token required for bearer authentication."}), 400 - if auth_type == 'basic' and (not username or not password): - return jsonify({"status": "error", "message": "Username and password required for basic authentication."}), 400 - - # Handle authentication - auth_token = None - if auth_type == 'bearer': - auth_token = f"Bearer {bearer_token}" - elif auth_type == 'basic': - credentials = f"{username}:{password}" - auth_token = f"Basic {base64.b64encode(credentials.encode('utf-8')).decode('utf-8')}" - - logger.info(f"Retrieve API: Server='{fhir_server_url}', Resources={resources}, ValidateRefs={validate_references}, FetchRefBundles={fetch_reference_bundles}, AuthType={auth_type}") - - # Ensure the temp directory exists - temp_dir = tempfile.gettempdir() - zip_filename = f"retrieved_bundles_{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}.zip" - output_zip = os.path.join(temp_dir, zip_filename) - - def generate(): - try: - yield from services.retrieve_bundles( - fhir_server_url=fhir_server_url, - resources=resources, - output_zip=output_zip, - validate_references=validate_references, - fetch_reference_bundles=fetch_reference_bundles, - auth_type=auth_type, - auth_token=auth_token - ) - except Exception as e: - logger.error(f"Error in retrieve_bundles: {e}", exc_info=True) - yield json.dumps({"type": "error", "message": f"Unexpected error: {str(e)}"}) + "\n" - - response = Response(generate(), mimetype='application/x-ndjson') - response.headers['X-Zip-Path'] = os.path.join('/tmp', zip_filename) - return response - -@app.route('/api/split-bundles', methods=['POST']) -@swag_from({ - 'tags': ['Test Data Management'], - 'summary': 'Split FHIR bundles from a ZIP into individual resources.', - 'description': 'Takes a ZIP file containing FHIR bundles, extracts individual resources, and creates a new ZIP file with these resources. Returns an NDJSON stream of progress.', - 'security': [{'ApiKeyAuth': []}], - 'consumes': ['multipart/form-data'], # Assuming split_bundle_zip_path comes from a form that might include a file upload in other contexts, or it's a path string. If it's always a path string from a JSON body, change consumes. - 'produces': ['application/x-ndjson'], - 'parameters': [ - # If split_bundle_zip_path is a path sent in form data: - {'name': 'split_bundle_zip_path', 'in': 'formData', 'type': 'string', 'required': True, 'description': 'Path to the input ZIP file containing bundles (server-side path).'}, - # If it's an uploaded file: - # {'name': 'split_bundle_zip_file', 'in': 'formData', 'type': 'file', 'required': True, 'description': 'ZIP file containing bundles to split.'} - ], - 'responses': { - '200': { - 'description': 'NDJSON stream of splitting progress. X-Zip-Path header indicates path to the output ZIP file.', - 'headers': { - 'X-Zip-Path': {'type': 'string', 'description': 'Server path to the generated ZIP file with split resources.'} - } - }, - '400': {'description': 'Invalid request (e.g., missing input ZIP path/file).'}, - '401': {'description': 'Authentication error.'}, - '500': {'description': 'Server error during splitting.'} - } -}) -def api_split_bundles(): - auth_error = check_api_key() - if auth_error: - return auth_error - params = request.form.to_dict() - input_zip_path = params.get('split_bundle_zip_path') - if not input_zip_path: - return jsonify({"status": "error", "message": "Missing input ZIP file."}), 400 - temp_dir = tempfile.gettempdir() - output_zip = os.path.join(temp_dir, 'split_resources.zip') - def generate(): - for message in split_bundles(input_zip_path, output_zip): - yield message - response = Response(generate(), mimetype='application/x-ndjson') - response.headers['X-Zip-Path'] = output_zip - return response - -@app.route('/tmp/', methods=['GET']) -def serve_zip(filename): - file_path = os.path.join('/tmp', filename) - if not os.path.exists(file_path): - logger.error(f"ZIP file not found: {file_path}") - return jsonify({'error': 'File not found'}), 404 - try: - return send_file(file_path, as_attachment=True, download_name=filename) - except Exception as e: - logger.error(f"Error serving ZIP file {file_path}: {str(e)}") - return jsonify({'error': 'Error serving file', 'details': str(e)}), 500 - -@app.route('/clear-session', methods=['POST']) -def clear_session(): - session.pop('retrieve_params', None) - session.pop('split_params', None) - return jsonify({"status": "success", "message": "Session cleared"}) - - -@app.route('/api/package/', methods=['GET']) -@swag_from({ - 'tags': ['Package Management'], - 'summary': 'Get details for a specific FHIR package.', - 'description': 'Retrieves details for a FHIR IG package by its name. Data is sourced from ProcessedIg, CachedPackage, or fetched live from registries.', - 'parameters': [ - {'name': 'name', 'in': 'path', 'type': 'string', 'required': True, 'description': 'The canonical name of the package (e.g., hl7.fhir.us.core).'} - ], - 'responses': { - '200': { - 'description': 'Package details.', - 'schema': { - 'type': 'object', - 'properties': { - 'name': {'type': 'string'}, - 'latest': {'type': 'string', 'description': 'Latest known version.'}, - 'author': {'type': 'string'}, - 'fhir_version': {'type': 'string'}, - 'version_count': {'type': 'integer'}, - 'url': {'type': 'string', 'format': 'url'} - } - } - }, - '404': {'description': 'Package not found.'} - } -}) -def package_details(name): - """ - Retrieve details for a specific FHIR Implementation Guide package by name. - Fetches from ProcessedIg or CachedPackage if not found in the database. - - Args: - name (str): The name of the package (e.g., 'hl7.fhir.us.core'). - - Returns: - JSON with package details (name, latest version, author, FHIR version, version count, URL) - or a 404 error if the package is not found. - """ - from services import fetch_packages_from_registries, normalize_package_data - - # Check ProcessedIg first (processed IGs) - package = ProcessedIg.query.filter_by(package_name=name).first() - if package: - return jsonify({ - 'name': package.package_name, - 'latest': package.version, - 'author': package.author, - 'fhir_version': package.fhir_version, - 'version_count': package.version_count, - 'url': package.url - }) - - # Check CachedPackage (cached packages) - package = CachedPackage.query.filter_by(package_name=name).first() - if package: - return jsonify({ - 'name': package.package_name, - 'latest': package.version, - 'author': package.author, - 'fhir_version': package.fhir_version, - 'version_count': package.version_count, - 'url': package.url - }) - - # Fetch from registries if not in database - logger.info(f"Package {name} not found in database. Fetching from registries.") - raw_packages = fetch_packages_from_registries(search_term=name) - normalized_packages = normalize_package_data(raw_packages) - package = next((pkg for pkg in normalized_packages if pkg['name'].lower() == name.lower()), None) - - if not package: - return jsonify({'error': 'Package not found'}), 404 - - return jsonify({ - 'name': package['name'], - 'latest': package['version'], - 'author': package['author'], - 'fhir_version': package['fhir_version'], - 'version_count': package['version_count'], - 'url': package['url'] - }) - -@app.route('/search-and-import') -def search_and_import(): - """ - Render the Search and Import page. Uses the database (CachedPackage) to load the package cache if available. - If not available, fetches from registries and caches the result. Displays latest official version if available, - otherwise falls back to latest absolute version. Shows fire animation and logs during cache loading. - """ - logger.debug("--- Entering search_and_import route (DB Cache Logic) ---") - page = request.args.get('page', 1, type=int) - per_page = 50 - - in_memory_packages = app.config.get('MANUAL_PACKAGE_CACHE') - in_memory_timestamp = app.config.get('MANUAL_CACHE_TIMESTAMP') - db_timestamp_info = RegistryCacheInfo.query.first() - db_timestamp = db_timestamp_info.last_fetch_timestamp if db_timestamp_info else None - logger.debug(f"DB Timestamp: {db_timestamp}, In-Memory Timestamp: {in_memory_timestamp}") - - normalized_packages = None - fetch_failed_flag = False - display_timestamp = None - is_fetching = False - - # Check if a fetch is in progress (stored in session) - fetch_in_progress = session.get('fetch_in_progress', False) - - if fetch_in_progress and in_memory_packages is not None: - # Fetch has completed, clear the session flag and proceed - session['fetch_in_progress'] = False - logger.info("Fetch completed, clearing fetch_in_progress flag.") - normalized_packages = in_memory_packages - display_timestamp = in_memory_timestamp - fetch_failed_flag = session.get('fetch_failed', False) - elif in_memory_packages is not None: - logger.info(f"Using in-memory cached package list from {in_memory_timestamp}.") - normalized_packages = in_memory_packages - display_timestamp = in_memory_timestamp - fetch_failed_flag = session.get('fetch_failed', False) - else: - # Check if there are cached packages in the database - try: - cached_packages = CachedPackage.query.all() - if cached_packages: - logger.info(f"Loading {len(cached_packages)} packages from CachedPackage table.") - # Reconstruct the normalized package format from the database entries - normalized_packages = [] - packages_by_name = {} - for pkg in cached_packages: - # Use getattr to provide defaults for potentially missing fields - pkg_data = { - 'name': pkg.package_name, - 'version': pkg.version, - 'latest_absolute_version': getattr(pkg, 'latest_absolute_version', pkg.version), - 'latest_official_version': getattr(pkg, 'latest_official_version', None), - 'author': getattr(pkg, 'author', ''), - 'fhir_version': getattr(pkg, 'fhir_version', ''), - 'url': getattr(pkg, 'url', ''), - 'canonical': getattr(pkg, 'canonical', ''), - 'dependencies': getattr(pkg, 'dependencies', []) or [], - 'version_count': getattr(pkg, 'version_count', 1), - 'all_versions': getattr(pkg, 'all_versions', [{'version': pkg.version, 'pubDate': ''}]) or [], - 'versions_data': [], - 'registry': getattr(pkg, 'registry', '') - } - # Group by package name to handle version aggregation - if pkg_data['name'] not in packages_by_name: - packages_by_name[pkg_data['name']] = pkg_data - normalized_packages.append(pkg_data) - else: - # Update all_versions for the existing package - existing_pkg = packages_by_name[pkg_data['name']] - if pkg_data['all_versions']: - existing_pkg['all_versions'].extend(pkg_data['all_versions']) - # Update version_count - existing_pkg['version_count'] = len(existing_pkg['all_versions']) - - # Sort all_versions within each package - for pkg in normalized_packages: - pkg['all_versions'].sort(key=lambda x: safe_parse_version(x.get('version', '0.0.0a0')), reverse=True) - - app.config['MANUAL_PACKAGE_CACHE'] = normalized_packages - app.config['MANUAL_CACHE_TIMESTAMP'] = db_timestamp or datetime.datetime.now(datetime.timezone.utc) - display_timestamp = app.config['MANUAL_CACHE_TIMESTAMP'] - fetch_failed_flag = session.get('fetch_failed', False) - logger.info(f"Loaded {len(normalized_packages)} packages into in-memory cache from database.") - else: - logger.info("No packages found in CachedPackage table. Fetching from registries...") - is_fetching = True - except Exception as db_err: - logger.error(f"Error loading packages from CachedPackage table: {db_err}", exc_info=True) - flash("Error loading package cache from database. Fetching from registries...", "warning") - is_fetching = True - - # If no packages were loaded from the database, fetch from registries - if normalized_packages is None: - logger.info("Fetching package list from registries...") - try: - # Clear the log queue to capture fetch logs - while not log_queue.empty(): - log_queue.get() - - # Set session flag to indicate fetch is in progress - session['fetch_in_progress'] = True - - raw_packages = fetch_packages_from_registries(search_term='') - logger.debug(f"fetch_packages_from_registries returned {len(raw_packages)} raw packages.") - if not raw_packages: - logger.warning("No packages returned from registries during refresh.") - normalized_packages = [] - fetch_failed_flag = True - session['fetch_failed'] = True - app.config['MANUAL_PACKAGE_CACHE'] = [] - app.config['MANUAL_CACHE_TIMESTAMP'] = None - display_timestamp = db_timestamp - else: - logger.debug("Normalizing fetched packages...") - normalized_packages = normalize_package_data(raw_packages) - logger.debug(f"Normalization resulted in {len(normalized_packages)} unique packages.") - now_ts = datetime.datetime.now(datetime.timezone.utc) - app.config['MANUAL_PACKAGE_CACHE'] = normalized_packages - app.config['MANUAL_CACHE_TIMESTAMP'] = now_ts - app_state['fetch_failed'] = False - logger.info(f"Stored {len(normalized_packages)} packages in manual cache (memory).") - - # Save to CachedPackage table - try: - cache_packages(normalized_packages, db, CachedPackage) - except Exception as cache_err: - logger.error(f"Failed to cache packages in database: {cache_err}", exc_info=True) - flash("Error saving package cache to database.", "warning") - - if db_timestamp_info: - db_timestamp_info.last_fetch_timestamp = now_ts - else: - db_timestamp_info = RegistryCacheInfo(last_fetch_timestamp=now_ts) - db.session.add(db_timestamp_info) - try: - db.session.commit() - logger.info(f"Updated DB timestamp to {now_ts}") - except Exception as db_err: - db.session.rollback() - logger.error(f"Failed to update DB timestamp: {db_err}", exc_info=True) - flash("Failed to save cache timestamp to database.", "warning") - session['fetch_failed'] = False - fetch_failed_flag = False - display_timestamp = now_ts - - # Do not redirect here; let the template render with is_fetching=True - except Exception as fetch_err: - logger.error(f"Error during package fetch/normalization: {fetch_err}", exc_info=True) - normalized_packages = [] - fetch_failed_flag = True - session['fetch_failed'] = True - app.config['MANUAL_PACKAGE_CACHE'] = [] - app.config['MANUAL_CACHE_TIMESTAMP'] = None - display_timestamp = db_timestamp - flash("Error fetching package list from registries.", "error") - - if not isinstance(normalized_packages, list): - logger.error(f"normalized_packages is not a list (type: {type(normalized_packages)}). Using empty list.") - normalized_packages = [] - fetch_failed_flag = True - session['fetch_failed'] = True - display_timestamp = None - - total_packages = len(normalized_packages) if normalized_packages else 0 - start = (page - 1) * per_page - end = start + per_page - packages_processed_for_page = [] - if normalized_packages: - for pkg_data in normalized_packages: - # Fall back to latest_absolute_version if latest_official_version is None - display_version = pkg_data.get('latest_official_version') or pkg_data.get('latest_absolute_version') or 'N/A' - pkg_data['display_version'] = display_version - packages_processed_for_page.append(pkg_data) - - packages_on_page = packages_processed_for_page[start:end] - total_pages_calc = max(1, (total_packages + per_page - 1) // per_page) - - def iter_pages(left_edge=1, left_current=1, right_current=2, right_edge=1): - pages = [] - last_page = 0 - for i in range(1, min(left_edge + 1, total_pages_calc + 1)): - pages.append(i) - last_page = i - if last_page < page - left_current - 1: - pages.append(None) - for i in range(max(last_page + 1, page - left_current), min(page + right_current + 1, total_pages_calc + 1)): - pages.append(i) - last_page = i - if last_page < total_pages_calc - right_edge: - pages.append(None) - for i in range(max(last_page + 1, total_pages_calc - right_edge + 1), total_pages_calc + 1): - pages.append(i) - return pages - - pagination = SimpleNamespace( - items=packages_on_page, - page=page, - pages=total_pages_calc, - total=total_packages, - per_page=per_page, - has_prev=(page > 1), - has_next=(page < total_pages_calc), - prev_num=(page - 1 if page > 1 else None), - next_num=(page + 1 if page < total_pages_calc else None), - iter_pages=iter_pages() - ) - - form = IgImportForm() - logger.debug(f"--- Rendering search_and_import template (Page: {page}, Total: {total_packages}, Failed Fetch: {fetch_failed_flag}, Display TS: {display_timestamp}) ---") - - return render_template('search_and_import_ig.html', - packages=packages_on_page, - pagination=pagination, - form=form, - fetch_failed=fetch_failed_flag, - last_cached_timestamp=display_timestamp, - is_fetching=is_fetching) - -@app.route('/api/search-packages', methods=['GET'], endpoint='api_search_packages') -@swag_from({ - 'tags': ['Package Management'], - 'summary': 'Search FHIR packages (HTMX).', - 'description': 'Searches the in-memory package cache. Returns an HTML fragment for HTMX to display matching packages. Primarily for UI interaction.', - 'parameters': [ - {'name': 'search', 'in': 'query', 'type': 'string', 'required': False, 'description': 'Search term for package name or author.'}, - {'name': 'page', 'in': 'query', 'type': 'integer', 'required': False, 'default': 1} - ], - 'produces': ['text/html'], - 'responses': { - '200': {'description': 'HTML fragment containing the search results table.'} - } -}) -def api_search_packages(): - """ - Handles HTMX search requests. Filters packages from the in-memory cache. - Returns an HTML fragment (_search_results_table.html) displaying the - latest official version if available, otherwise falls back to latest absolute version. - """ - search_term = request.args.get('search', '').lower() - page = request.args.get('page', 1, type=int) - per_page = 50 - logger.debug(f"API search request: term='{search_term}', page={page}") - - all_cached_packages = app.config.get('MANUAL_PACKAGE_CACHE') - if all_cached_packages is None: - logger.warning("API search called but in-memory cache is empty. Returning no results.") - return render_template('_search_results_table.html', packages=[], pagination=None) - - if search_term: - filtered_packages_raw = [ - pkg for pkg in all_cached_packages - if isinstance(pkg, dict) and ( - search_term in pkg.get('name', '').lower() or - search_term in pkg.get('author', '').lower() - ) - ] - logger.debug(f"Filtered {len(all_cached_packages)} cached packages down to {len(filtered_packages_raw)} for term '{search_term}'") - else: - filtered_packages_raw = all_cached_packages - logger.debug(f"No search term provided, using all {len(filtered_packages_raw)} cached packages.") - - filtered_packages_processed = [] - for pkg_data in filtered_packages_raw: - # Fall back to latest_absolute_version if latest_official_version is None - display_version = pkg_data.get('latest_official_version') or pkg_data.get('latest_absolute_version') or 'N/A' - pkg_data['display_version'] = display_version - filtered_packages_processed.append(pkg_data) - - total_filtered = len(filtered_packages_processed) - start = (page - 1) * per_page - end = start + per_page - packages_on_page = filtered_packages_processed[start:end] - total_pages_calc = max(1, (total_filtered + per_page - 1) // per_page) - - def iter_pages(left_edge=1, left_current=1, right_current=2, right_edge=1): - pages = [] - last_page = 0 - for i in range(1, min(left_edge + 1, total_pages_calc + 1)): - pages.append(i) - last_page = i - if last_page < page - left_current - 1: - pages.append(None) - for i in range(max(last_page + 1, page - left_current), min(page + right_current + 1, total_pages_calc + 1)): - pages.append(i) - last_page = i - if last_page < total_pages_calc - right_edge: - pages.append(None) - for i in range(max(last_page + 1, total_pages_calc - right_edge + 1), total_pages_calc + 1): - pages.append(i) - return pages - - pagination = SimpleNamespace( - items=packages_on_page, - page=page, - pages=total_pages_calc, - total=total_filtered, - per_page=per_page, - has_prev=(page > 1), - has_next=(page < total_pages_calc), - prev_num=(page - 1 if page > 1 else None), - next_num=(page + 1 if page < total_pages_calc else None), - iter_pages=iter_pages() - ) - - logger.debug(f"Rendering _search_results_table.html for API response (found {len(packages_on_page)} packages for page {page})") - html_response = render_template('_search_results_table.html', - packages=packages_on_page, - pagination=pagination) - return html_response - -def safe_parse_version_local(v_str): # Use different name - """ - Local copy of safe version parser for package_details_view. - """ - if not v_str or not isinstance(v_str, str): - return pkg_version_local.parse("0.0.0a0") - try: - return pkg_version_local.parse(v_str) - except pkg_version_local.InvalidVersion: - original_v_str = v_str - v_str_norm = v_str.lower() - base_part = v_str_norm.split('-', 1)[0] if '-' in v_str_norm else v_str_norm - suffix = v_str_norm.split('-', 1)[1] if '-' in v_str_norm else None - if re.match(r'^\d+(\.\d+)*$', base_part): - try: - if suffix in ['dev', 'snapshot', 'ci-build']: return pkg_version_local.parse(f"{base_part}a0") - elif suffix in ['draft', 'ballot', 'preview']: return pkg_version_local.parse(f"{base_part}b0") - elif suffix and suffix.startswith('rc'): return pkg_version_local.parse(f"{base_part}rc{ ''.join(filter(str.isdigit, suffix)) or '0'}") - return pkg_version_local.parse(base_part) - except pkg_version_local.InvalidVersion: - logger_details.warning(f"[DetailsView] Invalid base version '{base_part}' after splitting '{original_v_str}'. Treating as alpha.") - return pkg_version_local.parse("0.0.0a0") - except Exception as e: - logger_details.error(f"[DetailsView] Unexpected error parsing FHIR-suffixed version '{original_v_str}': {e}") - return pkg_version_local.parse("0.0.0a0") - else: - logger_details.warning(f"[DetailsView] Unparseable version '{original_v_str}' (base '{base_part}' not standard). Treating as alpha.") - return pkg_version_local.parse("0.0.0a0") - except Exception as e: - logger_details.error(f"[DetailsView] Unexpected error in safe_parse_version_local for '{v_str}': {e}") - return pkg_version_local.parse("0.0.0a0") -# --- End Local Helper Definition --- - -@app.route('/package-details/') -def package_details_view(name): - """Renders package details, using cache/db/fetch.""" - from services import get_package_description - packages = None - source = "Not Found" - - def safe_parse_version_local(v_str): - """ - Local version parser to handle FHIR package versions. - Uses pkg_version from services or falls back to basic comparison. - """ - if not v_str or not isinstance(v_str, str): - logger.warning(f"Invalid version string: {v_str}. Treating as 0.0.0a0.") - return pkg_version.parse("0.0.0a0") - try: - return pkg_version.parse(v_str) - except pkg_version.InvalidVersion: - original_v_str = v_str - v_str_norm = v_str.lower() - base_part = v_str_norm.split('-', 1)[0] if '-' in v_str_norm else v_str_norm - suffix = v_str_norm.split('-', 1)[1] if '-' in v_str_norm else None - if re.match(r'^\d+(\.\d+)+$', base_part): - try: - if suffix in ['dev', 'snapshot', 'ci-build']: - return pkg_version.parse(f"{base_part}a0") - elif suffix in ['draft', 'ballot', 'preview']: - return pkg_version.parse(f"{base_part}b0") - elif suffix and suffix.startswith('rc'): - rc_num = ''.join(filter(str.isdigit, suffix)) or '0' - return pkg_version.parse(f"{base_part}rc{rc_num}") - return pkg_version.parse(base_part) - except pkg_version.InvalidVersion: - logger.warning(f"Invalid base version '{base_part}' after splitting '{original_v_str}'. Treating as alpha.") - return pkg_version.parse("0.0.0a0") - except Exception as e: - logger.error(f"Unexpected error parsing FHIR-suffixed version '{original_v_str}': {e}") - return pkg_version.parse("0.0.0a0") - else: - logger.warning(f"Unparseable version '{original_v_str}' (base '{base_part}' not standard). Treating as alpha.") - return pkg_version.parse("0.0.0a0") - except Exception as e: - logger.error(f"Unexpected error in safe_parse_version_local for '{v_str}': {e}") - return pkg_version.parse("0.0.0a0") - - in_memory_cache = app.config.get('MANUAL_PACKAGE_CACHE') - if in_memory_cache: - cached_data = [pkg for pkg in in_memory_cache if isinstance(pkg, dict) and pkg.get('name', '').lower() == name.lower()] - if cached_data: - packages = cached_data - source = "In-Memory Cache" - logger.debug(f"Package '{name}' found in in-memory cache.") - - if packages is None: - logger.debug(f"Package '{name}' not in memory cache. Checking database.") - try: - db_packages = CachedPackage.query.filter(CachedPackage.package_name.ilike(name)).all() - if db_packages: - packages = db_packages - source = "Database (CachedPackage)" - logger.debug(f"Package '{name}' found in CachedPackage DB.") - except Exception as db_err: - logger.error(f"Database error querying package '{name}': {db_err}", exc_info=True) - - if packages is None: - logger.info(f"Package '{name}' not found in cache or DB. Fetching from registries.") - source = "Fetched from Registries" - try: - raw_packages = fetch_packages_from_registries(search_term=name) - normalized_packages = normalize_package_data(raw_packages) - packages = [pkg for pkg in normalized_packages if pkg.get('name', '').lower() == name.lower()] - if not packages: - logger.warning(f"Fetch/Normalization for '{name}' resulted in zero packages.") - else: - logger.debug(f"Fetch/Normalization successful for '{name}'. Found {len(packages)} versions.") - except Exception as fetch_err: - logger.error(f"Error fetching/normalizing from registries for '{name}': {fetch_err}", exc_info=True) - flash(f"Error fetching package details for {name} from registries.", "error") - return redirect(url_for('search_and_import')) - - if not packages: - logger.warning(f"Package '{name}' could not be found from any source ({source}).") - flash(f"Package {name} not found.", "error") - return redirect(url_for('search_and_import')) - - is_dict_list = bool(isinstance(packages[0], dict)) - latest_absolute_version_str = None - latest_official_version_str = None - latest_absolute_data = None - all_versions = [] - dependencies = [] - - try: - if is_dict_list: - package = packages[0] - latest_absolute_version_str = package.get('latest_absolute_version') - latest_official_version_str = package.get('latest_official_version') - latest_absolute_data = package - all_versions = package.get('all_versions', []) - dependencies = package.get('dependencies', []) - else: - package = packages[0] - latest_absolute_version_str = getattr(package, 'version', None) - latest_official_version_str = getattr(package, 'latest_official_version', None) - latest_absolute_data = package - all_versions = getattr(package, 'all_versions', []) - dependencies = getattr(package, 'dependencies', []) - - if not all_versions: - logger.error(f"No versions found for package '{name}'. Package data: {package}") - flash(f"No versions found for package {name}.", "error") - return redirect(url_for('search_and_import')) - - except Exception as e: - logger.error(f"Error processing versions for {name}: {e}", exc_info=True) - flash(f"Error determining latest versions for {name}.", "error") - return redirect(url_for('search_and_import')) - - if not latest_absolute_data or not latest_absolute_version_str: - logger.error(f"Failed to determine latest version for '{name}'. Latest data: {latest_absolute_data}, Version: {latest_absolute_version_str}") - flash(f"Could not determine latest version details for {name}.", "error") - return redirect(url_for('search_and_import')) - - actual_package_name = None - package_json = {} - if isinstance(latest_absolute_data, dict): - actual_package_name = latest_absolute_data.get('name', name) - package_json = { - 'name': actual_package_name, - 'version': latest_absolute_version_str, - 'author': latest_absolute_data.get('author'), - 'fhir_version': latest_absolute_data.get('fhir_version'), - 'canonical': latest_absolute_data.get('canonical', ''), - 'dependencies': latest_absolute_data.get('dependencies', []), - 'url': latest_absolute_data.get('url'), - 'registry': latest_absolute_data.get('registry', 'https://packages.simplifier.net'), - 'description': get_package_description(actual_package_name, latest_absolute_version_str, app.config['FHIR_PACKAGES_DIR']) - } - else: - actual_package_name = getattr(latest_absolute_data, 'package_name', getattr(latest_absolute_data, 'name', name)) - package_json = { - 'name': actual_package_name, - 'version': latest_absolute_version_str, - 'author': getattr(latest_absolute_data, 'author', None), - 'fhir_version': getattr(latest_absolute_data, 'fhir_version', None), - 'canonical': getattr(latest_absolute_data, 'canonical', ''), - 'dependencies': getattr(latest_absolute_data, 'dependencies', []), - 'url': getattr(latest_absolute_data, 'url', None), - 'registry': getattr(latest_absolute_data, 'registry', 'https://packages.simplifier.net'), - 'description': get_package_description(actual_package_name, latest_absolute_version_str, app.config['FHIR_PACKAGES_DIR']) - } - - # Since all_versions now contains dictionaries with version and pubDate, extract just the version for display - versions_sorted = [] - try: - versions_sorted = sorted(all_versions, key=lambda x: safe_parse_version_local(x['version']), reverse=True) - except Exception as sort_err: - logger.warning(f"Version sorting failed for {name}: {sort_err}. Using basic reverse sort.") - versions_sorted = sorted(all_versions, key=lambda x: x['pubDate'], reverse=True) - - logger.info(f"Rendering details for package '{package_json.get('name')}' (Source: {source}). Latest: {latest_absolute_version_str}, Official: {latest_official_version_str}") - return render_template('package_details.html', - package_json=package_json, - dependencies=dependencies, - versions=[v['version'] for v in versions_sorted], - package_name=actual_package_name, - latest_official_version=latest_official_version_str) - - - -@app.route('/favicon.ico') -def favicon(): - return send_file(os.path.join(app.static_folder, 'favicon.ico'), mimetype='image/x-icon') - - -if __name__ == '__main__': - with app.app_context(): - logger.debug(f"Instance path configuration: {app.instance_path}") - logger.debug(f"Database URI: {app.config['SQLALCHEMY_DATABASE_URI']}") - logger.debug(f"Packages path: {app.config['FHIR_PACKAGES_DIR']}") - logger.debug(f"Flask instance folder path: {app.instance_path}") - logger.debug(f"Directories created/verified: Instance: {app.instance_path}, Packages: {app.config['FHIR_PACKAGES_DIR']}") - logger.debug(f"Attempting to create database tables for URI: {app.config['SQLALCHEMY_DATABASE_URI']}") - db.create_all() - logger.info("Database tables created successfully (if they didn't exist).") - app.run(host='0.0.0.0', port=5000, debug=False) diff --git a/assets/css/style.css b/assets/css/style.css new file mode 100644 index 0000000..ab5049f --- /dev/null +++ b/assets/css/style.css @@ -0,0 +1,137 @@ +/* generated by rouge http://rouge.jneen.net/ original base16 by Chris Kempson (https://github.com/chriskempson/base16) +*/ +@import url("https://fonts.googleapis.com/css?family=Lato:300italic,700italic,300,700"); +.highlight table td { padding: 5px; } + +.highlight table pre { margin: 0; } + +.highlight, .highlight .w { color: #d0d0d0; } + +.highlight .err { color: #151515; background-color: #ac4142; } + +.highlight .c, .highlight .cd, .highlight .cm, .highlight .c1, .highlight .cs { color: #888; } + +.highlight .cp { color: #f4bf75; } + +.highlight .nt { color: #f4bf75; } + +.highlight .o, .highlight .ow { color: #d0d0d0; } + +.highlight .p, .highlight .pi { color: #d0d0d0; } + +.highlight .gi { color: #90a959; } + +.highlight .gd { color: #ac4142; } + +.highlight .gh { color: #6a9fb5; font-weight: bold; } + +.highlight .k, .highlight .kn, .highlight .kp, .highlight .kr, .highlight .kv { color: #aa759f; } + +.highlight .kc { color: #d28445; } + +.highlight .kt { color: #d28445; } + +.highlight .kd { color: #d28445; } + +.highlight .s, .highlight .sb, .highlight .sc, .highlight .sd, .highlight .s2, .highlight .sh, .highlight .sx, .highlight .s1 { color: #90a959; } + +.highlight .sr { color: #75b5aa; } + +.highlight .si { color: #8f5536; } + +.highlight .se { color: #8f5536; } + +.highlight .nn { color: #f4bf75; } + +.highlight .nc { color: #f4bf75; } + +.highlight .no { color: #f4bf75; } + +.highlight .na { color: #6a9fb5; } + +.highlight .m, .highlight .mf, .highlight .mh, .highlight .mi, .highlight .il, .highlight .mo, .highlight .mb, .highlight .mx { color: #90a959; } + +.highlight .ss { color: #90a959; } + +html { background: #6C7989; background: #6C7989 linear-gradient(#6C7989, #434B55) fixed; height: 100%; } + +body { padding: 50px 0; margin: 0; font: 14px/1.5 Lato, "Helvetica Neue", Helvetica, Arial, sans-serif; color: #555; font-weight: 300; background: url("../images/checker.png") fixed; min-height: calc(100% - 100px); } + +.wrapper { width: 740px; margin: 0 auto; background: #DEDEDE; border-radius: 8px; box-shadow: rgba(0, 0, 0, 0.2) 0 0 0 1px, rgba(0, 0, 0, 0.45) 0 3px 10px; } + +header, section, footer { display: block; } + +a { color: #069; text-decoration: none; } + +p { margin: 0 0 20px; padding: 0; } + +strong { color: #222; font-weight: 700; } + +header { border-radius: 8px 8px 0 0; background: #C6EAFA; background: linear-gradient(#DDFBFC, #C6EAFA); position: relative; padding: 15px 20px; border-bottom: 1px solid #B2D2E1; } +header h1 { margin: 0; padding: 0; font-size: 24px; line-height: 1.2; color: #069; text-shadow: rgba(255, 255, 255, 0.9) 0 1px 0; } +header.without-description h1 { margin: 10px 0; } +header p { margin: 0; color: #61778B; width: 300px; font-size: 13px; } +header p.view { display: none; font-weight: 700; text-shadow: rgba(255, 255, 255, 0.9) 0 1px 0; -webkit-font-smoothing: antialiased; } +header p.view a { color: #06c; } +header p.view small { font-weight: 400; } +header ul { margin: 0; padding: 0; list-style: none; position: absolute; z-index: 1; right: 20px; top: 20px; height: 38px; padding: 1px 0; background: #5198DF; background: linear-gradient(#77B9FB, #3782CD); border-radius: 5px; box-shadow: inset rgba(255, 255, 255, 0.45) 0 1px 0, inset rgba(0, 0, 0, 0.2) 0 -1px 0; width: auto; } +header ul:before { content: ''; position: absolute; z-index: -1; left: -5px; top: -4px; right: -5px; bottom: -6px; background: rgba(0, 0, 0, 0.1); border-radius: 8px; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0, inset rgba(255, 255, 255, 0.7) 0 -1px 0; } +header ul li { width: 79px; float: left; border-right: 1px solid #3A7CBE; height: 38px; } +header ul li.single { border: none; } +header ul li + li { width: 78px; border-left: 1px solid #8BBEF3; } +header ul li + li + li { border-right: none; width: 79px; } +header ul a { line-height: 1; font-size: 11px; color: #fff; color: rgba(255, 255, 255, 0.8); display: block; text-align: center; font-weight: 400; padding-top: 6px; height: 40px; text-shadow: rgba(0, 0, 0, 0.4) 0 -1px 0; } +header ul a strong { font-size: 14px; display: block; color: #fff; -webkit-font-smoothing: antialiased; } + +section { padding: 15px 20px; font-size: 15px; border-top: 1px solid #fff; background: linear-gradient(#fafafa, #DEDEDE 700px); border-radius: 0 0 8px 8px; position: relative; } + +h1, h2, h3, h4, h5, h6 { color: #222; padding: 0; margin: 0 0 20px; line-height: 1.2; } + +p, ul, ol, table, pre, dl { margin: 0 0 20px; } + +h1, h2, h3 { line-height: 1.1; } + +h1 { font-size: 28px; } + +h2 { color: #393939; } + +h3, h4, h5, h6 { color: #494949; } + +blockquote { margin: 0 -20px 20px; padding: 15px 20px 1px 40px; font-style: italic; background: #ccc; background: rgba(0, 0, 0, 0.06); color: #222; } + +img { max-width: 100%; } + +code, pre { font-family: Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal; color: #333; font-size: 12px; overflow-x: auto; } + +pre { padding: 20px; background: #3A3C42; color: #f8f8f2; margin: 0 -20px 20px; } +pre code { color: #f8f8f2; } +li pre { margin-left: -60px; padding-left: 60px; } + +table { width: 100%; border-collapse: collapse; } + +th, td { text-align: left; padding: 5px 10px; border-bottom: 1px solid #aaa; } + +dt { color: #222; font-weight: 700; } + +th { color: #222; } + +small { font-size: 11px; } + +hr { border: 0; background: #aaa; height: 1px; margin: 0 0 20px; } + +kbd { background-color: #fafbfc; border: 1px solid #c6cbd1; border-bottom-color: #959da5; border-radius: 3px; box-shadow: inset 0 -1px 0 #959da5; color: #444d56; display: inline-block; font-size: 11px; line-height: 10px; padding: 3px 5px; vertical-align: middle; } + +footer { width: 640px; margin: 0 auto; padding: 20px 0 0; color: #ccc; overflow: hidden; } +footer a { color: #fff; font-weight: bold; } +footer p { float: left; } +footer p + p { float: right; } + +@media print, screen and (max-width: 740px) { body { padding: 0; } + .wrapper { border-radius: 0; box-shadow: none; width: 100%; } + footer { border-radius: 0; padding: 20px; width: auto; } + footer p { float: none; margin: 0; } + footer p + p { float: none; } } +@media print, screen and (max-width: 580px) { header ul { display: none; } + header p.view { display: block; } + header p { width: 100%; } } +@media print { header p.view a small:before { content: 'at https://github.com/'; } } diff --git a/assets/images/checker.png b/assets/images/checker.png new file mode 100644 index 0000000000000000000000000000000000000000..7a65b2333db936d13c14599ba346697316e5c17a GIT binary patch literal 108 zcmeAS@N?(olHy`uVBq!ia0vp^3P3E!$P6UQkMv~$DYgKg5Z4C}9_U53{QLec1t`K) z666;wC1ri*+)N-(*we)^q=GS7Cp{q{;Z1{Ja|<`abO!#^Yu7^`0i_u{UHx3vIVCg! E0JkR|&j0`b literal 0 HcmV?d00001 diff --git a/assets/js/scale.fix.js b/assets/js/scale.fix.js new file mode 100644 index 0000000..08716c0 --- /dev/null +++ b/assets/js/scale.fix.js @@ -0,0 +1,20 @@ +fixScale = function(doc) { + + var addEvent = 'addEventListener', + type = 'gesturestart', + qsa = 'querySelectorAll', + scales = [1, 1], + meta = qsa in doc ? doc[qsa]('meta[name=viewport]') : []; + + function fix() { + meta.content = 'width=device-width,minimum-scale=' + scales[0] + ',maximum-scale=' + scales[1]; + doc.removeEventListener(type, fix, true); + } + + if ((meta = meta[meta.length - 1]) && addEvent in doc) { + fix(); + scales = [.25, 1.6]; + doc[addEvent](type, fix, true); + } + +}; \ No newline at end of file diff --git a/charts/.gitignore b/charts/.gitignore deleted file mode 100644 index 7368961..0000000 --- a/charts/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/hapi-fhir-jpaserver-0.20.0.tgz diff --git a/charts/fhirflare-ig-toolkit/.gitignore b/charts/fhirflare-ig-toolkit/.gitignore deleted file mode 100644 index 10d10c5..0000000 --- a/charts/fhirflare-ig-toolkit/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/rendered/ diff --git a/charts/fhirflare-ig-toolkit/Chart.yaml b/charts/fhirflare-ig-toolkit/Chart.yaml deleted file mode 100644 index 57c9cc2..0000000 --- a/charts/fhirflare-ig-toolkit/Chart.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: v2 -name: fhirflare-ig-toolkit -version: 0.4.0 -description: Helm chart for deploying the fhirflare-ig-toolkit application -type: application -appVersion: "latest" -icon: https://github.com/jgsuess/FHIRFLARE-IG-Toolkit/raw/main/static/FHIRFLARE.png -keywords: - - fhir - - healthcare - - ig-toolkit - - implementation-guide -home: https://github.com/jgsuess/FHIRFLARE-IG-Toolkit -maintainers: - - name: Jörn Guy Süß - email: jgsuess@gmail.com \ No newline at end of file diff --git a/charts/fhirflare-ig-toolkit/templates/_helpers.tpl b/charts/fhirflare-ig-toolkit/templates/_helpers.tpl deleted file mode 100644 index 6383d80..0000000 --- a/charts/fhirflare-ig-toolkit/templates/_helpers.tpl +++ /dev/null @@ -1,152 +0,0 @@ -{{/* -Expand the name of the chart. -*/}} -{{- define "fhirflare-ig-toolkit.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "fhirflare-ig-toolkit.fullname" -}} -{{- if .Values.fullnameOverride }} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- $name := default .Chart.Name .Values.nameOverride }} -{{- if contains $name .Release.Name }} -{{- .Release.Name | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} -{{- end }} -{{- end }} -{{- end }} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "fhirflare-ig-toolkit.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Common labels -*/}} -{{- define "fhirflare-ig-toolkit.labels" -}} -helm.sh/chart: {{ include "fhirflare-ig-toolkit.chart" . }} -{{ include "fhirflare-ig-toolkit.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end }} - -{{/* -Selector labels -*/}} -{{- define "fhirflare-ig-toolkit.selectorLabels" -}} -app.kubernetes.io/name: {{ include "fhirflare-ig-toolkit.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} - -{{/* -Create the name of the service account to use -*/}} -{{- define "hapi-fhir-jpaserver.serviceAccountName" -}} -{{- if .Values.serviceAccount.create }} -{{- default (include "hapi-fhir-jpaserver.fullname" .) .Values.serviceAccount.name }} -{{- else }} -{{- default "default" .Values.serviceAccount.name }} -{{- end }} -{{- end }} - -{{/* -Create a default fully qualified postgresql name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "hapi-fhir-jpaserver.postgresql.fullname" -}} -{{- $name := default "postgresql" .Values.postgresql.nameOverride -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Get the Postgresql credentials secret name. -*/}} -{{- define "hapi-fhir-jpaserver.postgresql.secretName" -}} -{{- if .Values.postgresql.enabled -}} - {{- if .Values.postgresql.auth.existingSecret -}} - {{- printf "%s" .Values.postgresql.auth.existingSecret -}} - {{- else -}} - {{- printf "%s" (include "hapi-fhir-jpaserver.postgresql.fullname" .) -}} - {{- end -}} -{{- else }} - {{- if .Values.externalDatabase.existingSecret -}} - {{- printf "%s" .Values.externalDatabase.existingSecret -}} - {{- else -}} - {{ printf "%s-%s" (include "hapi-fhir-jpaserver.fullname" .) "external-db" }} - {{- end -}} -{{- end -}} -{{- end -}} - -{{/* -Get the Postgresql credentials secret key. -*/}} -{{- define "hapi-fhir-jpaserver.postgresql.secretKey" -}} -{{- if .Values.postgresql.enabled -}} - {{- if .Values.postgresql.auth.username -}} - {{- printf "%s" .Values.postgresql.auth.secretKeys.userPasswordKey -}} - {{- else -}} - {{- printf "%s" .Values.postgresql.auth.secretKeys.adminPasswordKey -}} - {{- end -}} -{{- else }} - {{- if .Values.externalDatabase.existingSecret -}} - {{- printf "%s" .Values.externalDatabase.existingSecretKey -}} - {{- else -}} - {{- printf "postgres-password" -}} - {{- end -}} -{{- end -}} -{{- end -}} - -{{/* -Add environment variables to configure database values -*/}} -{{- define "hapi-fhir-jpaserver.database.host" -}} -{{- ternary (include "hapi-fhir-jpaserver.postgresql.fullname" .) .Values.externalDatabase.host .Values.postgresql.enabled -}} -{{- end -}} - -{{/* -Add environment variables to configure database values -*/}} -{{- define "hapi-fhir-jpaserver.database.user" -}} -{{- if .Values.postgresql.enabled -}} - {{- printf "%s" .Values.postgresql.auth.username | default "postgres" -}} -{{- else -}} - {{- printf "%s" .Values.externalDatabase.user -}} -{{- end -}} -{{- end -}} - -{{/* -Add environment variables to configure database values -*/}} -{{- define "hapi-fhir-jpaserver.database.name" -}} -{{- ternary .Values.postgresql.auth.database .Values.externalDatabase.database .Values.postgresql.enabled -}} -{{- end -}} - -{{/* -Add environment variables to configure database values -*/}} -{{- define "hapi-fhir-jpaserver.database.port" -}} -{{- ternary "5432" .Values.externalDatabase.port .Values.postgresql.enabled -}} -{{- end -}} - -{{/* -Create the JDBC URL from the host, port and database name. -*/}} -{{- define "hapi-fhir-jpaserver.database.jdbcUrl" -}} -{{- $host := (include "hapi-fhir-jpaserver.database.host" .) -}} -{{- $port := (include "hapi-fhir-jpaserver.database.port" .) -}} -{{- $name := (include "hapi-fhir-jpaserver.database.name" .) -}} -{{- $appName := .Release.Name -}} -{{ printf "jdbc:postgresql://%s:%d/%s?ApplicationName=%s" $host (int $port) $name $appName }} -{{- end -}} \ No newline at end of file diff --git a/charts/fhirflare-ig-toolkit/templates/deployment.yaml b/charts/fhirflare-ig-toolkit/templates/deployment.yaml deleted file mode 100644 index a4ceae0..0000000 --- a/charts/fhirflare-ig-toolkit/templates/deployment.yaml +++ /dev/null @@ -1,91 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "fhirflare-ig-toolkit.fullname" . }} - labels: - {{- include "fhirflare-ig-toolkit.labels" . | nindent 4 }} -spec: - replicas: {{ .Values.replicaCount | default 1 }} - selector: - matchLabels: - {{- include "fhirflare-ig-toolkit.selectorLabels" . | nindent 6 }} - strategy: - type: Recreate - template: - metadata: - labels: - {{- include "fhirflare-ig-toolkit.selectorLabels" . | nindent 8 }} - {{- with .Values.podAnnotations }} - annotations: - {{- toYaml . | nindent 8 }} - {{- end }} - spec: - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - securityContext: - {{- toYaml .Values.podSecurityContext | nindent 8 }} - containers: - - name: {{ .Chart.Name }} - securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - args: ["supervisord", "-c", "/etc/supervisord.conf"] - env: - - name: APP_BASE_URL - value: {{ .Values.config.appBaseUrl | default "http://localhost:5000" | quote }} - - name: APP_MODE - value: {{ .Values.config.appMode | default "lite" | quote }} - - name: FLASK_APP - value: {{ .Values.config.flaskApp | default "app.py" | quote }} - - name: FLASK_ENV - value: {{ .Values.config.flaskEnv | default "development" | quote }} - - name: HAPI_FHIR_URL - value: {{ .Values.config.externalHapiServerUrl | default "http://external-hapi-fhir:8080/fhir" | quote }} - - name: NODE_PATH - value: {{ .Values.config.nodePath | default "/usr/lib/node_modules" | quote }} - - name: TMPDIR - value: "/tmp-dir" - ports: - - name: http - containerPort: {{ .Values.service.port | default 5000 }} - protocol: TCP - volumeMounts: - - name: logs - mountPath: /app/logs - - name: tmp-dir - mountPath: /tmp-dir - {{- with .Values.resources }} - resources: - {{- toYaml . | nindent 12 }} - {{- end }} - {{- with .Values.livenessProbe }} - livenessProbe: - {{- toYaml . | nindent 12 }} - {{- end }} - {{- with .Values.readinessProbe }} - readinessProbe: - {{- toYaml . | nindent 12 }} - {{- end }} - volumes: - - name: logs - emptyDir: {} - - name: tmp-dir - emptyDir: {} - # Always require Intel 64-bit architecture nodes - nodeSelector: - kubernetes.io/arch: amd64 - {{- with .Values.nodeSelector }} - # Merge with user-defined nodeSelectors if any - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} \ No newline at end of file diff --git a/charts/fhirflare-ig-toolkit/templates/ingress.yaml b/charts/fhirflare-ig-toolkit/templates/ingress.yaml deleted file mode 100644 index a2286ba..0000000 --- a/charts/fhirflare-ig-toolkit/templates/ingress.yaml +++ /dev/null @@ -1,36 +0,0 @@ -{{- if .Values.ingress.enabled -}} -{{- $fullName := include "fhirflare-ig-toolkit.fullname" . -}} -{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }} -apiVersion: networking.k8s.io/v1 -{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion }} -apiVersion: networking.k8s.io/v1beta1 -{{- else }} -apiVersion: extensions/v1beta1 -{{- end }} -kind: Ingress -metadata: - name: {{ $fullName }} - labels: - {{- include "fhirflare-ig-toolkit.labels" . | nindent 4 }} - {{- with .Values.ingress.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - rules: - - http: - paths: - - path: / - {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }} - pathType: Prefix - backend: - service: - name: {{ $fullName }} - port: - number: {{ .Values.service.port | default 5000 }} - {{- else }} - backend: - serviceName: {{ $fullName }} - servicePort: {{ .Values.service.port | default 5000 }} - {{- end }} -{{- end }} \ No newline at end of file diff --git a/charts/fhirflare-ig-toolkit/templates/service.yaml b/charts/fhirflare-ig-toolkit/templates/service.yaml deleted file mode 100644 index 467d861..0000000 --- a/charts/fhirflare-ig-toolkit/templates/service.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ include "fhirflare-ig-toolkit.fullname" . }} - labels: - {{- include "fhirflare-ig-toolkit.labels" . | nindent 4 }} -spec: - type: {{ .Values.service.type | default "ClusterIP" }} - ports: - - name: http - port: {{ .Values.service.port | default 5000 }} - targetPort: {{ .Values.service.port | default 5000 }} - protocol: TCP - {{- if and (eq .Values.service.type "NodePort") .Values.service.nodePort }} - nodePort: {{ .Values.service.nodePort }} - {{- end }} - selector: - {{- include "fhirflare-ig-toolkit.selectorLabels" . | nindent 4 }} \ No newline at end of file diff --git a/charts/fhirflare-ig-toolkit/templates/tests/test-endpoints.yaml b/charts/fhirflare-ig-toolkit/templates/tests/test-endpoints.yaml deleted file mode 100644 index 024b446..0000000 --- a/charts/fhirflare-ig-toolkit/templates/tests/test-endpoints.yaml +++ /dev/null @@ -1,41 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: "{{ .Release.Name }}-fhirflare-test-endpoint" - labels: - helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - app.kubernetes.io/name: {{ .Chart.Name }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} - app.kubernetes.io/managed-by: {{ .Release.Service }} - app.kubernetes.io/component: tests - annotations: - "helm.sh/hook": test -spec: - restartPolicy: Never - containers: - - name: test-fhirflare-endpoint - image: curlimages/curl:8.12.1 - command: ["curl", "--fail-with-body", "--retry", "5", "--retry-delay", "10"] - args: ["http://fhirflare:5000"] - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - privileged: false - readOnlyRootFilesystem: true - runAsGroup: 65534 - runAsNonRoot: true - runAsUser: 65534 - seccompProfile: - type: RuntimeDefault - resources: - limits: - cpu: 150m - ephemeral-storage: 2Gi - memory: 192Mi - requests: - cpu: 100m - ephemeral-storage: 50Mi - memory: 128Mi \ No newline at end of file diff --git a/charts/fhirflare-ig-toolkit/values.yaml b/charts/fhirflare-ig-toolkit/values.yaml deleted file mode 100644 index 5283cf3..0000000 --- a/charts/fhirflare-ig-toolkit/values.yaml +++ /dev/null @@ -1,89 +0,0 @@ -# Default values for fhirflare-ig-toolkit -replicaCount: 1 - -image: - repository: ghcr.io/jgsuess/fhirflare-ig-toolkit - pullPolicy: Always - tag: "latest" - -imagePullSecrets: [] -nameOverride: "" -fullnameOverride: "" - -# FHIRflare specific configuration -config: - # Application mode: "lite" means using external HAPI server, "standalone" means running with embedded HAPI server - appMode: "lite" - # URL for the external HAPI FHIR server when in lite mode - externalHapiServerUrl: "http://external-hapi-fhir:8080/fhir" - appBaseUrl: "http://localhost:5000" - flaskApp: "app.py" - flaskEnv: "development" - nodePath: "/usr/lib/node_modules" - -service: - type: ClusterIP - port: 5000 - nodePort: null - -podAnnotations: {} - -# podSecurityContext: -# fsGroup: 65532 -# fsGroupChangePolicy: OnRootMismatch -# runAsNonRoot: true -# runAsGroup: 65532 -# runAsUser: 65532 -# seccompProfile: -# type: RuntimeDefault - -# securityContext: -# allowPrivilegeEscalation: false -# capabilities: -# drop: -# - ALL -# privileged: false -# readOnlyRootFilesystem: true -# runAsGroup: 65532 -# runAsNonRoot: true -# runAsUser: 65532 -# seccompProfile: -# type: RuntimeDefault - -resources: - limits: - cpu: 500m - memory: 512Mi - ephemeral-storage: 1Gi - requests: - cpu: 100m - memory: 128Mi - ephemeral-storage: 100Mi - -livenessProbe: - httpGet: - path: / - port: http - initialDelaySeconds: 30 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 6 - successThreshold: 1 - -readinessProbe: - httpGet: - path: / - port: http - initialDelaySeconds: 5 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 6 - successThreshold: 1 - -nodeSelector: {} -tolerations: [] -affinity: {} - -ingress: - # -- whether to create a primitive Ingress to expose the FHIR server HTTP endpoint - enabled: false \ No newline at end of file diff --git a/charts/install.sh b/charts/install.sh deleted file mode 100755 index de29f80..0000000 --- a/charts/install.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -# -# FHIRFLARE-IG-Toolkit Installation Script -# -# Description: -# This script installs the FHIRFLARE-IG-Toolkit Helm chart into a Kubernetes cluster. -# It adds the FHIRFLARE-IG-Toolkit Helm repository and then installs the chart -# in the 'flare' namespace, creating the namespace if it doesn't exist. -# -# Usage: -# ./install.sh -# -# Requirements: -# - Helm (v3+) -# - kubectl configured with access to your Kubernetes cluster -# - -# Add the FHIRFLARE-IG-Toolkit Helm repository -helm repo add flare https://jgsuess.github.io/FHIRFLARE-IG-Toolkit/ - -# Install the FHIRFLARE-IG-Toolkit chart in the 'flare' namespace - -helm install flare/fhirflare-ig-toolkit --namespace flare --create-namespace --generate-name --set hapi-fhir-jpaserver.postgresql.primary.persistence.storageClass=gp2 --atomic \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 0c1823f..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,22 +0,0 @@ -version: '3.8' -services: - fhirflare: - build: - context: . - dockerfile: Dockerfile - ports: - - "5000:5000" - - "8080:8080" # Keep port exposed, even if Tomcat isn't running useful stuff in Lite - volumes: - - ./instance:/app/instance - - ./static/uploads:/app/static/uploads - - ./instance/hapi-h2-data/:/app/h2-data # Keep volume mounts consistent - - ./logs:/app/logs - environment: - - FLASK_APP=app.py - - FLASK_ENV=development - - NODE_PATH=/usr/lib/node_modules - - APP_MODE=standalone - - APP_BASE_URL=http://localhost:5000 - - HAPI_FHIR_URL=http://localhost:8080/fhir - command: supervisord -c /etc/supervisord.conf diff --git a/docker-compose/all-in-one/docker-compose.yml b/docker-compose/all-in-one/docker-compose.yml deleted file mode 100644 index 73bcc63..0000000 --- a/docker-compose/all-in-one/docker-compose.yml +++ /dev/null @@ -1,22 +0,0 @@ -# This docker-compose file uses ephemeral Docker named volumes for all data storage. -# These volumes persist only as long as the Docker volumes exist and are deleted if you run `docker-compose down -v`. -# No data is stored on the host filesystem. If you want persistent storage, replace these with host mounts. -services: - fhirflare-standalone: - image: ${FHIRFLARE_IMAGE:-ghcr.io/sudo-jhare/fhirflare-ig-toolkit-standalone:latest} - container_name: fhirflare-standalone - ports: - - "5000:5000" - - "8080:8080" - volumes: - - fhirflare-instance:/app/instance - - fhirflare-uploads:/app/static/uploads - - fhirflare-h2-data:/app/h2-data - - fhirflare-logs:/app/logs - restart: unless-stopped - -volumes: - fhirflare-instance: - fhirflare-uploads: - fhirflare-h2-data: - fhirflare-logs: \ No newline at end of file diff --git a/docker-compose/all-in-one/down.sh b/docker-compose/all-in-one/down.sh deleted file mode 100755 index 672f4de..0000000 --- a/docker-compose/all-in-one/down.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -# Stop and remove all containers defined in the Docker Compose file, -# along with any anonymous volumes attached to them. -docker compose down --volumes \ No newline at end of file diff --git a/docker-compose/all-in-one/up.sh b/docker-compose/all-in-one/up.sh deleted file mode 100755 index 2e87977..0000000 --- a/docker-compose/all-in-one/up.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -# Run Docker Compose - -docker compose up --detach --force-recreate --renew-anon-volumes --always-recreate-deps diff --git a/docker-compose/lite/local/application.yaml b/docker-compose/lite/local/application.yaml deleted file mode 100644 index 7e3fc4c..0000000 --- a/docker-compose/lite/local/application.yaml +++ /dev/null @@ -1,18 +0,0 @@ -hapi.fhir: - ig_runtime_upload_enabled: false - narrative_enabled: true - logical_urls: - - http://terminology.hl7.org/* - - https://terminology.hl7.org/* - - http://snomed.info/* - - https://snomed.info/* - - http://unitsofmeasure.org/* - - https://unitsofmeasure.org/* - - http://loinc.org/* - - https://loinc.org/* - cors: - allow_Credentials: true - allowed_origin: - - '*' - tester.home.name: FHIRFLARE Tester - inline_resource_storage_below_size: 4000 diff --git a/docker-compose/lite/local/docker-compose.yml b/docker-compose/lite/local/docker-compose.yml deleted file mode 100644 index 625279a..0000000 --- a/docker-compose/lite/local/docker-compose.yml +++ /dev/null @@ -1,50 +0,0 @@ -services: - fhirflare: - image: ${FHIRFLARE_IMAGE:-ghcr.io/sudo-jhare/fhirflare-ig-toolkit-lite:latest} - ports: - - "5000:5000" - # Ephemeral Docker named volumes for all data storage. No data is stored on the host filesystem. - volumes: - - fhirflare-instance:/app/instance - - fhirflare-uploads:/app/static/uploads - - fhirflare-h2-data:/app/h2-data - - fhirflare-logs:/app/logs - environment: - - FLASK_APP=app.py - - FLASK_ENV=development - - NODE_PATH=/usr/lib/node_modules - - APP_MODE=lite - - APP_BASE_URL=http://localhost:5000 - - HAPI_FHIR_URL=http://fhir:8080/fhir - command: supervisord -c /etc/supervisord.conf - - fhir: - container_name: hapi - image: "hapiproject/hapi:v8.2.0-1" - ports: - - "8080:8080" - configs: - - source: hapi - target: /app/config/application.yaml - depends_on: - - db - - db: - image: "postgres:17.2-bookworm" - restart: always - environment: - POSTGRES_PASSWORD: admin - POSTGRES_USER: admin - POSTGRES_DB: hapi - volumes: - - ./hapi.postgress.data:/var/lib/postgresql/data - -configs: - hapi: - file: ./application.yaml - -volumes: - fhirflare-instance: - fhirflare-uploads: - fhirflare-h2-data: - fhirflare-logs: \ No newline at end of file diff --git a/docker-compose/lite/local/down.sh b/docker-compose/lite/local/down.sh deleted file mode 100755 index 672f4de..0000000 --- a/docker-compose/lite/local/down.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -# Stop and remove all containers defined in the Docker Compose file, -# along with any anonymous volumes attached to them. -docker compose down --volumes \ No newline at end of file diff --git a/docker-compose/lite/local/readme.md b/docker-compose/lite/local/readme.md deleted file mode 100644 index 6051cd5..0000000 --- a/docker-compose/lite/local/readme.md +++ /dev/null @@ -1,19 +0,0 @@ -# FHIRFLARE IG Toolkit - -This directory provides scripts and configuration to start and stop a FHIRFLARE instance with an attached HAPI FHIR server using Docker Compose. - -## Usage - -- To start the FHIRFLARE toolkit and HAPI server: - ```sh - ./docker-compose/up.sh - ``` - -- To stop and remove the containers and volumes: - ```sh - ./docker-compose/down.sh - ``` - -The web interface will be available at [http://localhost:5000](http://localhost:5000) and the HAPI FHIR server at [http://localhost:8080/fhir](http://localhost:8080/fhir). - -For more details, see the configuration files in this directory. \ No newline at end of file diff --git a/docker-compose/lite/local/up.sh b/docker-compose/lite/local/up.sh deleted file mode 100755 index 2e87977..0000000 --- a/docker-compose/lite/local/up.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -# Run Docker Compose - -docker compose up --detach --force-recreate --renew-anon-volumes --always-recreate-deps diff --git a/docker-compose/lite/remote/docker-compose.yml b/docker-compose/lite/remote/docker-compose.yml deleted file mode 100644 index 4e7bf3b..0000000 --- a/docker-compose/lite/remote/docker-compose.yml +++ /dev/null @@ -1,25 +0,0 @@ -services: - fhirflare: - image: ${FHIRFLARE_IMAGE:-ghcr.io/sudo-jhare/fhirflare-ig-toolkit-lite:latest} - ports: - - "5000:5000" - # Ephemeral Docker named volumes for all data storage. No data is stored on the host filesystem. - volumes: - - fhirflare-instance:/app/instance - - fhirflare-uploads:/app/static/uploads - - fhirflare-h2-data:/app/h2-data - - fhirflare-logs:/app/logs - environment: - - FLASK_APP=app.py - - FLASK_ENV=development - - NODE_PATH=/usr/lib/node_modules - - APP_MODE=lite - - APP_BASE_URL=http://localhost:5000 - - HAPI_FHIR_URL=https://cdr.fhirlab.net/fhir - command: supervisord -c /etc/supervisord.conf - -volumes: - fhirflare-instance: - fhirflare-uploads: - fhirflare-h2-data: - fhirflare-logs: \ No newline at end of file diff --git a/docker-compose/lite/remote/down.sh b/docker-compose/lite/remote/down.sh deleted file mode 100755 index 672f4de..0000000 --- a/docker-compose/lite/remote/down.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -# Stop and remove all containers defined in the Docker Compose file, -# along with any anonymous volumes attached to them. -docker compose down --volumes \ No newline at end of file diff --git a/docker-compose/lite/remote/readme.md b/docker-compose/lite/remote/readme.md deleted file mode 100644 index 6051cd5..0000000 --- a/docker-compose/lite/remote/readme.md +++ /dev/null @@ -1,19 +0,0 @@ -# FHIRFLARE IG Toolkit - -This directory provides scripts and configuration to start and stop a FHIRFLARE instance with an attached HAPI FHIR server using Docker Compose. - -## Usage - -- To start the FHIRFLARE toolkit and HAPI server: - ```sh - ./docker-compose/up.sh - ``` - -- To stop and remove the containers and volumes: - ```sh - ./docker-compose/down.sh - ``` - -The web interface will be available at [http://localhost:5000](http://localhost:5000) and the HAPI FHIR server at [http://localhost:8080/fhir](http://localhost:8080/fhir). - -For more details, see the configuration files in this directory. \ No newline at end of file diff --git a/docker-compose/lite/remote/up.sh b/docker-compose/lite/remote/up.sh deleted file mode 100755 index 2e87977..0000000 --- a/docker-compose/lite/remote/up.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -# Run Docker Compose - -docker compose up --detach --force-recreate --renew-anon-volumes --always-recreate-deps diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index 0978d89..0000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,66 +0,0 @@ -# ------------------------------------------------------------------------------ -# Dockerfile for FHIRFLARE-IG-Toolkit (Optimized for Python/Flask) -# -# This Dockerfile builds a container for the FHIRFLARE-IG-Toolkit application. -# -# Key Features: -# - Uses python:3.11-slim as the base image for a minimal, secure Python runtime. -# - Installs Node.js and global NPM packages (gofsh, fsh-sushi) for FHIR IG tooling. -# - Sets up a Python virtual environment and installs all Python dependencies. -# - Installs and configures Supervisor to manage the Flask app and related processes. -# - Copies all necessary application code, templates, static files, and configuration. -# - Exposes ports 5000 (Flask) and 8080 (optional, for compatibility). -# - Entrypoint runs Supervisor for process management. -# -# Notes: -# - The Dockerfile is optimized for Python. Tomcat/Java is not included. -# - Node.js is only installed if needed for FHIR IG tooling. -# - The image is suitable for development and production with minimal changes. -# ------------------------------------------------------------------------------ - -# Optimized Dockerfile for Python (Flask) -FROM python:3.11-slim AS base - -# Install system dependencies -RUN apt-get update && apt-get install -y --no-install-recommends \ - curl \ - coreutils \ - && rm -rf /var/lib/apt/lists/* - -# Optional: Install Node.js if needed for GoFSH/SUSHI -RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \ - && apt-get install -y --no-install-recommends nodejs \ - && npm install -g gofsh fsh-sushi \ - && rm -rf /var/lib/apt/lists/* - -# Set workdir -WORKDIR /app - -# Copy requirements and install Python dependencies -COPY requirements.txt . -RUN python -m venv /app/venv \ - && . /app/venv/bin/activate \ - && pip install --upgrade pip \ - && pip install --no-cache-dir -r requirements.txt \ - && pip uninstall -y fhirpath || true \ - && pip install --no-cache-dir fhirpathpy \ - && pip install supervisor - -# Copy application files -COPY app.py . -COPY services.py . -COPY forms.py . -COPY package.py . -COPY templates/ templates/ -COPY static/ static/ -COPY tests/ tests/ -COPY supervisord.conf /etc/supervisord.conf - -# Expose ports -EXPOSE 5000 8080 - -# Set environment -ENV PATH="/app/venv/bin:$PATH" - -# Start supervisord -CMD ["supervisord", "-c", "/etc/supervisord.conf"] \ No newline at end of file diff --git a/docker/build-docker.sh b/docker/build-docker.sh deleted file mode 100755 index f0eea0b..0000000 --- a/docker/build-docker.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -# Build FHIRFLARE-IG-Toolkit Docker image - -# Build the image using the Dockerfile in the docker directory -docker build -f Dockerfile -t fhirflare-ig-toolkit:latest .. - -echo "Docker image built successfully" \ No newline at end of file diff --git a/forms.py b/forms.py deleted file mode 100644 index 71b26e8..0000000 --- a/forms.py +++ /dev/null @@ -1,309 +0,0 @@ -# forms.py -from flask_wtf import FlaskForm -from wtforms import StringField, SelectField, TextAreaField, BooleanField, SubmitField, FileField, PasswordField -from wtforms.validators import DataRequired, Regexp, ValidationError, URL, Optional, InputRequired -from flask import request -import json -import xml.etree.ElementTree as ET -import re -import logging -import os - -logger = logging.getLogger(__name__) - -class RetrieveSplitDataForm(FlaskForm): - """Form for retrieving FHIR bundles and splitting them into individual resources.""" - fhir_server_url = StringField('FHIR Server URL', validators=[URL(), Optional()], - render_kw={'placeholder': 'e.g., https://hapi.fhir.org/baseR4'}) - auth_type = SelectField('Authentication Type (for Custom URL)', choices=[ - ('none', 'None'), - ('bearerToken', 'Bearer Token'), - ('basicAuth', 'Basic Authentication') - ], default='none', validators=[Optional()]) - auth_token = StringField('Bearer Token', validators=[Optional()], - render_kw={'placeholder': 'Enter Bearer Token', 'type': 'password'}) - basic_auth_username = StringField('Username', validators=[Optional()], - render_kw={'placeholder': 'Enter Basic Auth Username'}) - basic_auth_password = PasswordField('Password', validators=[Optional()], - render_kw={'placeholder': 'Enter Basic Auth Password'}) - validate_references = BooleanField('Fetch Referenced Resources', default=False, - description="If checked, fetches resources referenced by the initial bundles.") - fetch_reference_bundles = BooleanField('Fetch Full Reference Bundles (instead of individual resources)', default=False, - description="Requires 'Fetch Referenced Resources'. Fetches e.g. /Patient instead of Patient/id for each reference.", - render_kw={'data-dependency': 'validate_references'}) - split_bundle_zip = FileField('Upload Bundles to Split (ZIP)', validators=[Optional()], - render_kw={'accept': '.zip'}) - submit_retrieve = SubmitField('Retrieve Bundles') - submit_split = SubmitField('Split Bundles') - - def validate(self, extra_validators=None): - if not super().validate(extra_validators): - return False - if self.fetch_reference_bundles.data and not self.validate_references.data: - self.fetch_reference_bundles.errors.append('Cannot fetch full reference bundles unless "Fetch Referenced Resources" is also checked.') - return False - if self.auth_type.data == 'bearerToken' and self.submit_retrieve.data and not self.auth_token.data: - self.auth_token.errors.append('Bearer Token is required when Bearer Token authentication is selected.') - return False - if self.auth_type.data == 'basicAuth' and self.submit_retrieve.data: - if not self.basic_auth_username.data: - self.basic_auth_username.errors.append('Username is required for Basic Authentication.') - return False - if not self.basic_auth_password.data: - self.basic_auth_password.errors.append('Password is required for Basic Authentication.') - return False - if self.split_bundle_zip.data: - if not self.split_bundle_zip.data.filename.lower().endswith('.zip'): - self.split_bundle_zip.errors.append('File must be a ZIP file.') - return False - return True - -class IgImportForm(FlaskForm): - """Form for importing Implementation Guides.""" - package_name = StringField('Package Name', validators=[ - DataRequired(), - Regexp(r'^[a-zA-Z0-9][a-zA-Z0-9\-\.]*[a-zA-Z0-9]$', message="Invalid package name format.") - ], render_kw={'placeholder': 'e.g., hl7.fhir.au.core'}) - package_version = StringField('Package Version', validators=[ - DataRequired(), - Regexp(r'^[a-zA-Z0-9\.\-]+$', message="Invalid version format. Use alphanumeric characters, dots, or hyphens (e.g., 1.2.3, 1.1.0-preview, current).") - ], render_kw={'placeholder': 'e.g., 1.1.0-preview'}) - dependency_mode = SelectField('Dependency Mode', choices=[ - ('recursive', 'Current Recursive'), - ('patch-canonical', 'Patch Canonical Versions'), - ('tree-shaking', 'Tree Shaking (Only Used Dependencies)') - ], default='recursive') - submit = SubmitField('Import') - -class ManualIgImportForm(FlaskForm): - """Form for manual importing Implementation Guides via file or URL.""" - import_mode = SelectField('Import Mode', choices=[ - ('file', 'Upload File'), - ('url', 'From URL') - ], default='file', validators=[DataRequired()]) - tgz_file = FileField('IG Package File (.tgz)', validators=[Optional()], - render_kw={'accept': '.tgz'}) - tgz_url = StringField('IG Package URL', validators=[Optional(), URL()], - render_kw={'placeholder': 'e.g., https://example.com/hl7.fhir.au.core-1.1.0-preview.tgz'}) - dependency_mode = SelectField('Dependency Mode', choices=[ - ('recursive', 'Current Recursive'), - ('patch-canonical', 'Patch Canonical Versions'), - ('tree-shaking', 'Tree Shaking (Only Used Dependencies)') - ], default='recursive') - resolve_dependencies = BooleanField('Resolve Dependencies', default=True, - render_kw={'class': 'form-check-input'}) - submit = SubmitField('Import') - - def validate(self, extra_validators=None): - if not super().validate(extra_validators): - return False - mode = self.import_mode.data - has_file = request and request.files and self.tgz_file.name in request.files and request.files[self.tgz_file.name].filename != '' - has_url = bool(self.tgz_url.data) # Convert to boolean: True if non-empty string - - # Ensure exactly one input method is used - inputs_provided = sum([has_file, has_url]) - if inputs_provided != 1: - if inputs_provided == 0: - self.import_mode.errors.append('Please provide input for one import method (File or URL).') - else: - self.import_mode.errors.append('Please use only one import method at a time.') - return False - - # Validate based on import mode - if mode == 'file': - if not has_file: - self.tgz_file.errors.append('A .tgz file is required for File import.') - return False - if not self.tgz_file.data.filename.lower().endswith('.tgz'): - self.tgz_file.errors.append('File must be a .tgz file.') - return False - elif mode == 'url': - if not has_url: - self.tgz_url.errors.append('A valid URL is required for URL import.') - return False - if not self.tgz_url.data.lower().endswith('.tgz'): - self.tgz_url.errors.append('URL must point to a .tgz file.') - return False - else: - self.import_mode.errors.append('Invalid import mode selected.') - return False - - return True - -class ValidationForm(FlaskForm): - """Form for validating FHIR samples.""" - package_name = StringField('Package Name', validators=[DataRequired()]) - version = StringField('Package Version', validators=[DataRequired()]) - include_dependencies = BooleanField('Include Dependencies', default=True) - mode = SelectField('Validation Mode', choices=[ - ('single', 'Single Resource'), - ('bundle', 'Bundle') - ], default='single') - sample_input = TextAreaField('Sample Input', validators=[ - DataRequired(), - ]) - submit = SubmitField('Validate') - -class FSHConverterForm(FlaskForm): - """Form for converting FHIR resources to FSH.""" - package = SelectField('FHIR Package (Optional)', choices=[('', 'None')], validators=[Optional()]) - input_mode = SelectField('Input Mode', choices=[ - ('file', 'Upload File'), - ('text', 'Paste Text') - ], validators=[DataRequired()]) - fhir_file = FileField('FHIR Resource File (JSON/XML)', validators=[Optional()]) - fhir_text = TextAreaField('FHIR Resource Text (JSON/XML)', validators=[Optional()]) - output_style = SelectField('Output Style', choices=[ - ('file-per-definition', 'File per Definition'), - ('group-by-fsh-type', 'Group by FSH Type'), - ('group-by-profile', 'Group by Profile'), - ('single-file', 'Single File') - ], validators=[DataRequired()]) - log_level = SelectField('Log Level', choices=[ - ('error', 'Error'), - ('warn', 'Warn'), - ('info', 'Info'), - ('debug', 'Debug') - ], validators=[DataRequired()]) - fhir_version = SelectField('FHIR Version', choices=[ - ('', 'Auto-detect'), - ('4.0.1', 'R4'), - ('4.3.0', 'R4B'), - ('5.0.0', 'R5') - ], validators=[Optional()]) - fishing_trip = BooleanField('Run Fishing Trip (Round-Trip Validation with SUSHI)', default=False) - dependencies = TextAreaField('Dependencies (e.g., hl7.fhir.us.core@6.1.0)', validators=[Optional()]) - indent_rules = BooleanField('Indent Rules with Context Paths', default=False) - meta_profile = SelectField('Meta Profile Handling', choices=[ - ('only-one', 'Only One Profile (Default)'), - ('first', 'First Profile'), - ('none', 'Ignore Profiles') - ], validators=[DataRequired()]) - alias_file = FileField('Alias FSH File', validators=[Optional()]) - no_alias = BooleanField('Disable Alias Generation', default=False) - submit = SubmitField('Convert to FSH') - - def validate(self, extra_validators=None): - if not super().validate(extra_validators): - return False - has_file_in_request = request and request.files and self.fhir_file.name in request.files and request.files[self.fhir_file.name].filename != '' - if self.input_mode.data == 'file' and not has_file_in_request: - if not self.fhir_file.data: - self.fhir_file.errors.append('File is required when input mode is Upload File.') - return False - if self.input_mode.data == 'text' and not self.fhir_text.data: - self.fhir_text.errors.append('Text input is required when input mode is Paste Text.') - return False - if self.input_mode.data == 'text' and self.fhir_text.data: - try: - content = self.fhir_text.data.strip() - if not content: pass - elif content.startswith('{'): json.loads(content) - elif content.startswith('<'): ET.fromstring(content) - else: - self.fhir_text.errors.append('Text input must be valid JSON or XML.') - return False - except (json.JSONDecodeError, ET.ParseError): - self.fhir_text.errors.append('Invalid JSON or XML format.') - return False - if self.dependencies.data: - for dep in self.dependencies.data.splitlines(): - dep = dep.strip() - if dep and not re.match(r'^[a-zA-Z0-9\-\.]+@[a-zA-Z0-9\.\-]+$', dep): - self.dependencies.errors.append(f'Invalid dependency format: "{dep}". Use package@version (e.g., hl7.fhir.us.core@6.1.0).') - return False - has_alias_file_in_request = request and request.files and self.alias_file.name in request.files and request.files[self.alias_file.name].filename != '' - alias_file_data = self.alias_file.data or (request.files.get(self.alias_file.name) if request else None) - if alias_file_data and alias_file_data.filename: - if not alias_file_data.filename.lower().endswith('.fsh'): - self.alias_file.errors.append('Alias file should have a .fsh extension.') - return True - -class TestDataUploadForm(FlaskForm): - """Form for uploading FHIR test data.""" - fhir_server_url = StringField('Target FHIR Server URL', validators=[DataRequired(), URL()], - render_kw={'placeholder': 'e.g., http://localhost:8080/fhir'}) - auth_type = SelectField('Authentication Type', choices=[ - ('none', 'None'), - ('bearerToken', 'Bearer Token'), - ('basic', 'Basic Authentication') - ], default='none') - auth_token = StringField('Bearer Token', validators=[Optional()], - render_kw={'placeholder': 'Enter Bearer Token', 'type': 'password'}) - username = StringField('Username', validators=[Optional()], - render_kw={'placeholder': 'Enter Basic Auth Username'}) - password = PasswordField('Password', validators=[Optional()], - render_kw={'placeholder': 'Enter Basic Auth Password'}) - test_data_file = FileField('Select Test Data File(s)', validators=[InputRequired("Please select at least one file.")], - render_kw={'multiple': True, 'accept': '.json,.xml,.zip'}) - validate_before_upload = BooleanField('Validate Resources Before Upload?', default=False, - description="Validate resources against selected package profile before uploading.") - validation_package_id = SelectField('Validation Profile Package (Optional)', - choices=[('', '-- Select Package for Validation --')], - validators=[Optional()], - description="Select the processed IG package to use for validation.") - upload_mode = SelectField('Upload Mode', choices=[ - ('individual', 'Individual Resources'), - ('transaction', 'Transaction Bundle') - ], default='individual') - use_conditional_uploads = BooleanField('Use Conditional Upload (Individual Mode Only)?', default=True, - description="If checked, checks resource existence (GET) and uses If-Match (PUT) or creates (PUT). If unchecked, uses simple PUT for all.") - error_handling = SelectField('Error Handling', choices=[ - ('stop', 'Stop on First Error'), - ('continue', 'Continue on Error') - ], default='stop') - submit = SubmitField('Upload and Process') - - def validate(self, extra_validators=None): - if not super().validate(extra_validators): - return False - if self.validate_before_upload.data and not self.validation_package_id.data: - self.validation_package_id.errors.append('Please select a package to validate against when pre-upload validation is enabled.') - return False - if self.use_conditional_uploads.data and self.upload_mode.data == 'transaction': - self.use_conditional_uploads.errors.append('Conditional Uploads only apply to the "Individual Resources" mode.') - return False - if self.auth_type.data == 'bearerToken' and not self.auth_token.data: - self.auth_token.errors.append('Bearer Token is required when Bearer Token authentication is selected.') - return False - if self.auth_type.data == 'basic': - if not self.username.data: - self.username.errors.append('Username is required for Basic Authentication.') - return False - if not self.password.data: - self.password.errors.append('Password is required for Basic Authentication.') - return False - return True - -class FhirRequestForm(FlaskForm): - fhir_server_url = StringField('FHIR Server URL', validators=[URL(), Optional()], - render_kw={'placeholder': 'e.g., https://hapi.fhir.org/baseR4'}) - auth_type = SelectField('Authentication Type (for Custom URL)', choices=[ - ('none', 'None'), - ('bearerToken', 'Bearer Token'), - ('basicAuth', 'Basic Authentication') - ], default='none', validators=[Optional()]) - auth_token = StringField('Bearer Token', validators=[Optional()], - render_kw={'placeholder': 'Enter Bearer Token', 'type': 'password'}) - basic_auth_username = StringField('Username', validators=[Optional()], - render_kw={'placeholder': 'Enter Basic Auth Username'}) - basic_auth_password = PasswordField('Password', validators=[Optional()], - render_kw={'placeholder': 'Enter Basic Auth Password'}) - submit = SubmitField('Send Request') - - def validate(self, extra_validators=None): - if not super().validate(extra_validators): - return False - if self.fhir_server_url.data: - if self.auth_type.data == 'bearerToken' and not self.auth_token.data: - self.auth_token.errors.append('Bearer Token is required when Bearer Token authentication is selected for a custom URL.') - return False - if self.auth_type.data == 'basicAuth': - if not self.basic_auth_username.data: - self.basic_auth_username.errors.append('Username is required for Basic Authentication with a custom URL.') - return False - if not self.basic_auth_password.data: - self.basic_auth_password.errors.append('Password is required for Basic Authentication with a custom URL.') - return False - return True \ No newline at end of file diff --git a/hapi-fhir-Setup/README.md b/hapi-fhir-Setup/README.md deleted file mode 100644 index ea613b1..0000000 --- a/hapi-fhir-Setup/README.md +++ /dev/null @@ -1,111 +0,0 @@ -# Application Build and Run Guide - MANUAL STEPS - -This guide outlines the steps to set up, build, and run the application, including the HAPI FHIR server component and the rest of the application managed via Docker Compose. - -## Prerequisites - -Before you begin, ensure you have the following installed on your system: - -* [Git](https://git-scm.com/) -* [Maven](https://maven.apache.org/) -* [Java Development Kit (JDK)](https://www.oracle.com/java/technologies/downloads/) (Ensure compatibility with the HAPI FHIR version) -* [Docker](https://www.docker.com/products/docker-desktop/) -* [Docker Compose](https://docs.docker.com/compose/install/) (Often included with Docker Desktop) - -## Setup and Build - -Follow these steps to clone the necessary repository and build the components. - -### 1. Clone and Build the HAPI FHIR Server - -First, clone the HAPI FHIR JPA Server Starter project and build the server application. - - -# Step 1: Clone the repository -git clone https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git hapi-fhir-jpaserver hapi-fhir-jpaserver - -# Navigate into the cloned directory -cd hapi-fhir-jpaserver - -copy the folder from hapi-fhir-setup/target/classes/application.yaml to the hapi-fhir-jpaserver/target/classes/application.yaml folder created above - -# Step 2: Build the HAPI server package (skipping tests, using 'boot' profile) -# This creates the runnable WAR file in the 'target/' directory -mvn clean package -DskipTests=true -Pboot - -# Return to the parent directory (or your project root) -cd .. -2. Build the Rest of the Application (Docker) -Next, build the Docker images for the remaining parts of the application as defined in your docker-compose.yml file. Run this command from the root directory where your docker-compose.yml file is located. - - - -# Step 3: Build Docker images without using cache -docker-compose build --no-cache -Running the Application -Option A: Running the Full Application (Recommended) -Use Docker Compose to start all services, including (presumably) the HAPI FHIR server if it's configured in your docker-compose.yml. Run this from the root directory containing your docker-compose.yml. - - - -# Step 4: Start all services defined in docker-compose.yml in detached mode -docker-compose up -d -Option B: Running the HAPI FHIR Server Standalone (Debugging Only) -This method runs only the HAPI FHIR server directly using the built WAR file. Use this primarily for debugging the server in isolation. - - - -# Navigate into the HAPI server directory where you built it -cd hapi-fhir-jpaserver - -# Run the WAR file directly using Java -java -jar target/ROOT.war - -# Note: You might need to configure ports or database connections -# separately when running this way, depending on the application's needs. - -# Remember to navigate back when done -# cd .. -Useful Docker Commands -Here are some helpful commands for interacting with your running Docker containers: - -Copying files from a container: -To copy a file from a running container to your local machine's current directory: - - - -# Syntax: docker cp : -docker cp :/app/PATH/Filename.ext . -(Replace , /app/PATH/Filename.ext with actual values. . refers to the current directory on your host machine.) - -Accessing a container's shell: -To get an interactive bash shell inside a running container: - - - -# Syntax: docker exec -it bash -docker exec -it bash -(Replace with the actual container ID or name. You can find this using docker ps.) - -Viewing running containers: - - - -docker ps -Viewing application logs: - - - -# Follow logs for all services -docker-compose logs -f - -# Follow logs for a specific service -docker-compose logs -f -(Replace with the name defined in your docker-compose.yml) - -Stopping the application: -To stop the services started with docker-compose up -d: - - - -docker-compose down diff --git a/hapi-fhir-Setup/target/classes/application.yaml b/hapi-fhir-Setup/target/classes/application.yaml deleted file mode 100644 index a79d07e..0000000 --- a/hapi-fhir-Setup/target/classes/application.yaml +++ /dev/null @@ -1,342 +0,0 @@ -#Uncomment the "servlet" and "context-path" lines below to make the fhir endpoint available at /example/path/fhir instead of the default value of /fhir -server: - # servlet: - # context-path: /example/path - port: 8080 -#Adds the option to go to eg. http://localhost:8080/actuator/health for seeing the running configuration -#see https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints -management: - #The following configuration will enable the actuator endpoints at /actuator/health, /actuator/info, /actuator/prometheus, /actuator/metrics. For security purposes, only /actuator/health is enabled by default. - endpoints: - enabled-by-default: false - web: - exposure: - include: 'health' # or e.g. 'info,health,prometheus,metrics' or '*' for all' - endpoint: - info: - enabled: true - metrics: - enabled: true - health: - enabled: true - probes: - enabled: true - group: - liveness: - include: - - livenessState - - readinessState - prometheus: - enabled: true - prometheus: - metrics: - export: - enabled: true -spring: - main: - allow-circular-references: true - flyway: - enabled: false - baselineOnMigrate: true - fail-on-missing-locations: false - datasource: - #url: 'jdbc:h2:file:./target/database/h2' - url: jdbc:h2:file:/app/h2-data/fhir;DB_CLOSE_DELAY=-1;AUTO_SERVER=TRUE - #url: jdbc:h2:mem:test_mem - username: sa - password: null - driverClassName: org.h2.Driver - max-active: 15 - - # database connection pool size - hikari: - maximum-pool-size: 10 - jpa: - properties: - hibernate.format_sql: false - hibernate.show_sql: false - - #Hibernate dialect is automatically detected except Postgres and H2. - #If using H2, then supply the value of ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect - #If using postgres, then supply the value of ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgresDialect - hibernate.dialect: ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect - # hibernate.hbm2ddl.auto: update - # hibernate.jdbc.batch_size: 20 - # hibernate.cache.use_query_cache: false - # hibernate.cache.use_second_level_cache: false - # hibernate.cache.use_structured_entries: false - # hibernate.cache.use_minimal_puts: false - - ### These settings will enable fulltext search with lucene or elastic - hibernate.search.enabled: false - ### lucene parameters -# hibernate.search.backend.type: lucene -# hibernate.search.backend.analysis.configurer: ca.uhn.fhir.jpa.search.HapiHSearchAnalysisConfigurers$HapiLuceneAnalysisConfigurer -# hibernate.search.backend.directory.type: local-filesystem -# hibernate.search.backend.directory.root: target/lucenefiles -# hibernate.search.backend.lucene_version: lucene_current - ### elastic parameters ===> see also elasticsearch section below <=== -# hibernate.search.backend.type: elasticsearch -# hibernate.search.backend.analysis.configurer: ca.uhn.fhir.jpa.search.HapiHSearchAnalysisConfigurers$HapiElasticAnalysisConfigurer -hapi: - fhir: - ### This flag when enabled to true, will avail evaluate measure operations from CR Module. - ### Flag is false by default, can be passed as command line argument to override. - cr: - enabled: false - caregaps: - reporter: "default" - section_author: "default" - cql: - use_embedded_libraries: true - compiler: - ### These are low-level compiler options. - ### They are not typically needed by most users. - # validate_units: true - # verify_only: false - # compatibility_level: "1.5" - error_level: Info - signature_level: All - # analyze_data_requirements: false - # collapse_data_requirements: false - # translator_format: JSON - # enable_date_range_optimization: true - enable_annotations: true - enable_locators: true - enable_results_type: true - enable_detailed_errors: true - # disable_list_traversal: false - # disable_list_demotion: false - # enable_interval_demotion: false - # enable_interval_promotion: false - # disable_method_invocation: false - # require_from_keyword: false - # disable_default_model_info_load: false - runtime: - debug_logging_enabled: false - # enable_validation: false - # enable_expression_caching: true - terminology: - valueset_preexpansion_mode: REQUIRE # USE_IF_PRESENT, REQUIRE, IGNORE - valueset_expansion_mode: PERFORM_NAIVE_EXPANSION # AUTO, USE_EXPANSION_OPERATION, PERFORM_NAIVE_EXPANSION - valueset_membership_mode: USE_EXPANSION # AUTO, USE_VALIDATE_CODE_OPERATION, USE_EXPANSION - code_lookup_mode: USE_VALIDATE_CODE_OPERATION # AUTO, USE_VALIDATE_CODE_OPERATION, USE_CODESYSTEM_URL - data: - search_parameter_mode: USE_SEARCH_PARAMETERS # AUTO, USE_SEARCH_PARAMETERS, FILTER_IN_MEMORY - terminology_parameter_mode: FILTER_IN_MEMORY # AUTO, USE_VALUE_SET_URL, USE_INLINE_CODES, FILTER_IN_MEMORY - profile_mode: DECLARED # ENFORCED, DECLARED, OPTIONAL, TRUST, OFF - - cdshooks: - enabled: false - clientIdHeaderName: client_id - - ### This enables the swagger-ui at /fhir/swagger-ui/index.html as well as the /fhir/api-docs (see https://hapifhir.io/hapi-fhir/docs/server_plain/openapi.html) - openapi_enabled: true - ### This is the FHIR version. Choose between, DSTU2, DSTU3, R4 or R5 - fhir_version: R4 - ### Flag is false by default. This flag enables runtime installation of IG's. - ig_runtime_upload_enabled: false - ### This flag when enabled to true, will avail evaluate measure operations from CR Module. - - ### enable to use the ApacheProxyAddressStrategy which uses X-Forwarded-* headers - ### to determine the FHIR server address - # use_apache_address_strategy: false - ### forces the use of the https:// protocol for the returned server address. - ### alternatively, it may be set using the X-Forwarded-Proto header. - # use_apache_address_strategy_https: false - ### enables the server to overwrite defaults on HTML, css, etc. under the url pattern of eg. /content/custom ** - ### Folder with custom content MUST be named custom. If omitted then default content applies - custom_content_path: ./custom - ### enables the server host custom content. If e.g. the value ./configs/app is supplied then the content - ### will be served under /web/app - #app_content_path: ./configs/app - ### enable to set the Server URL - # server_address: http://hapi.fhir.org/baseR4 - # defer_indexing_for_codesystems_of_size: 101 - ### Flag is true by default. This flag filters resources during package installation, allowing only those resources with a valid status (e.g. active) to be installed. - # validate_resource_status_for_package_upload: false - # install_transitive_ig_dependencies: true - #implementationguides: - ### example from registry (packages.fhir.org) - # swiss: - # name: swiss.mednet.fhir - # version: 0.8.0 - # reloadExisting: false - # installMode: STORE_AND_INSTALL - # example not from registry - # ips_1_0_0: - # packageUrl: https://build.fhir.org/ig/HL7/fhir-ips/package.tgz - # name: hl7.fhir.uv.ips - # version: 1.0.0 - # supported_resource_types: - # - Patient - # - Observation - ################################################## - # Allowed Bundle Types for persistence (defaults are: COLLECTION,DOCUMENT,MESSAGE) - ################################################## - # allowed_bundle_types: COLLECTION,DOCUMENT,MESSAGE,TRANSACTION,TRANSACTIONRESPONSE,BATCH,BATCHRESPONSE,HISTORY,SEARCHSET - # allow_cascading_deletes: true - # allow_contains_searches: true - # allow_external_references: true - # allow_multiple_delete: true - # allow_override_default_search_params: true - # auto_create_placeholder_reference_targets: false - # mass_ingestion_mode_enabled: false - ### tells the server to automatically append the current version of the target resource to references at these paths - # auto_version_reference_at_paths: Device.patient, Device.location, Device.parent, DeviceMetric.parent, DeviceMetric.source, Observation.device, Observation.subject - # ips_enabled: false - # default_encoding: JSON - # default_pretty_print: true - # default_page_size: 20 - # delete_expunge_enabled: true - # enable_repository_validating_interceptor: true - # enable_index_missing_fields: false - # enable_index_of_type: true - # enable_index_contained_resource: false - # upliftedRefchains_enabled: true - # resource_dbhistory_enabled: false - ### !!Extended Lucene/Elasticsearch Indexing is still a experimental feature, expect some features (e.g. _total=accurate) to not work as expected!! - ### more information here: https://hapifhir.io/hapi-fhir/docs/server_jpa/elastic.html - advanced_lucene_indexing: false - bulk_export_enabled: false - bulk_import_enabled: false - # language_search_parameter_enabled: true - # enforce_referential_integrity_on_delete: false - # This is an experimental feature, and does not fully support _total and other FHIR features. - # enforce_referential_integrity_on_delete: false - # enforce_referential_integrity_on_write: false - # etag_support_enabled: true - # expunge_enabled: true - # client_id_strategy: ALPHANUMERIC - # server_id_strategy: SEQUENTIAL_NUMERIC - # fhirpath_interceptor_enabled: false - # filter_search_enabled: true - # graphql_enabled: true - narrative_enabled: true - mdm_enabled: false - mdm_rules_json_location: "mdm-rules.json" - ## see: https://hapifhir.io/hapi-fhir/docs/interceptors/built_in_server_interceptors.html#jpa-server-retry-on-version-conflicts - # userRequestRetryVersionConflictsInterceptorEnabled : false - # local_base_urls: - # - https://hapi.fhir.org/baseR4 - # pre_expand_value_sets: true - # enable_task_pre_expand_value_sets: true - # pre_expand_value_sets_default_count: 1000 - # pre_expand_value_sets_max_count: 1000 - # maximum_expansion_size: 1000 - - logical_urls: - - http://terminology.hl7.org/* - - https://terminology.hl7.org/* - - http://snomed.info/* - - https://snomed.info/* - - http://unitsofmeasure.org/* - - https://unitsofmeasure.org/* - - http://loinc.org/* - - https://loinc.org/* - # partitioning: - # allow_references_across_partitions: false - # partitioning_include_in_search_hashes: false - # conditional_create_duplicate_identifiers_enabled: false - cors: - allow_Credentials: true - # These are allowed_origin patterns, see: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/cors/CorsConfiguration.html#setAllowedOriginPatterns-java.util.List- - allowed_origin: - - '*' - - # Search coordinator thread pool sizes - search-coord-core-pool-size: 20 - search-coord-max-pool-size: 100 - search-coord-queue-capacity: 200 - - # Search Prefetch Thresholds. - - # This setting sets the number of search results to prefetch. For example, if this list - # is set to [100, 1000, -1] then the server will initially load 100 results and not - # attempt to load more. If the user requests subsequent page(s) of results and goes - # past 100 results, the system will load the next 900 (up to the following threshold of 1000). - # The system will progressively work through these thresholds. - # A threshold of -1 means to load all results. Note that if the final threshold is a - # number other than -1, the system will never prefetch more than the given number. - search_prefetch_thresholds: 13,503,2003,-1 - - # comma-separated package names, will be @ComponentScan'ed by Spring to allow for creating custom Spring beans - #custom-bean-packages: - - # comma-separated list of fully qualified interceptor classes. - # classes listed here will be fetched from the Spring context when combined with 'custom-bean-packages', - # or will be instantiated via reflection using an no-arg contructor; then registered with the server - #custom-interceptor-classes: - - # comma-separated list of fully qualified provider classes. - # classes listed here will be fetched from the Spring context when combined with 'custom-bean-packages', - # or will be instantiated via reflection using an no-arg contructor; then registered with the server - #custom-provider-classes: - - # Threadpool size for BATCH'ed GETs in a bundle. - # bundle_batch_pool_size: 10 - # bundle_batch_pool_max_size: 50 - - # logger: - # error_format: 'ERROR - ${requestVerb} ${requestUrl}' - # format: >- - # Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] - # Operation[${operationType} ${operationName} ${idOrResourceName}] - # UA[${requestHeader.user-agent}] Params[${requestParameters}] - # ResponseEncoding[${responseEncodingNoDefault}] - # log_exceptions: true - # name: fhirtest.access - # max_binary_size: 104857600 - # max_page_size: 200 - # retain_cached_searches_mins: 60 - # reuse_cached_search_results_millis: 60000 - tester: - home: - name: FHIRFLARE Tester - server_address: http://localhost:8080/fhir - refuse_to_fetch_third_party_urls: false - fhir_version: R4 - global: - name: Global Tester - server_address: "http://hapi.fhir.org/baseR4" - refuse_to_fetch_third_party_urls: false - fhir_version: R4 - # validation: - # requests_enabled: true - # responses_enabled: true - # binary_storage_enabled: true - inline_resource_storage_below_size: 4000 -# bulk_export_enabled: true -# subscription: -# resthook_enabled: true -# websocket_enabled: false -# polling_interval_ms: 5000 -# immediately_queued: false -# email: -# from: some@test.com -# host: google.com -# port: -# username: -# password: -# auth: -# startTlsEnable: -# startTlsRequired: -# quitWait: -# lastn_enabled: true -# store_resource_in_lucene_index_enabled: true -### This is configuration for normalized quantity search level default is 0 -### 0: NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED - default -### 1: NORMALIZED_QUANTITY_STORAGE_SUPPORTED -### 2: NORMALIZED_QUANTITY_SEARCH_SUPPORTED -# normalized_quantity_search_level: 2 -#elasticsearch: -# debug: -# pretty_print_json_log: false -# refresh_after_write: false -# enabled: false -# password: SomePassword -# required_index_status: YELLOW -# rest_url: 'localhost:9200' -# protocol: 'http' -# schema_management_strategy: CREATE -# username: SomeUsername diff --git a/index.html b/index.html new file mode 100644 index 0000000..71210ec --- /dev/null +++ b/index.html @@ -0,0 +1,92 @@ + + + + + + + +FHIRFLARE IG Toolkit | Helm chart for deploying the fhirflare-ig-toolkit application + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

FHIRFLARE IG Toolkit

+ +

Helm chart for deploying the fhirflare-ig-toolkit application

+ +

View the Project on GitHub

+ +
+
+ +

FHIRFLARE IG Toolkit

+ +

Helm chart for deploying the fhirflare-ig-toolkit application

+ +

Overview

+ +

FHIRFLARE-IG-Toolkit is a comprehensive solution for working with FHIR Implementation Guides.

+ +

Features

+ +
    +
  • Helm chart deployment
  • +
  • FHIR resources management
  • +
  • Implementation Guide toolkit
  • +
+ +

Getting Started

+ +

Check out the documentation to get started with FHIRFLARE IG Toolkit.

+ + +
+
+ + + + diff --git a/index.yaml b/index.yaml deleted file mode 100644 index 1ddaa4c..0000000 --- a/index.yaml +++ /dev/null @@ -1,24 +0,0 @@ -apiVersion: v1 -entries: - fhirflare-ig-toolkit: - - apiVersion: v2 - appVersion: latest - created: "2025-08-04T05:53:00.152693988Z" - description: Helm chart for deploying the fhirflare-ig-toolkit application - digest: faef7991101501ae64e368fd2fb8021ec623d73e8cc808ea8c3df9920dcefb6a - home: https://github.com/jgsuess/FHIRFLARE-IG-Toolkit - icon: https://github.com/jgsuess/FHIRFLARE-IG-Toolkit/raw/main/static/FHIRFLARE.png - keywords: - - fhir - - healthcare - - ig-toolkit - - implementation-guide - maintainers: - - email: jgsuess@gmail.com - name: Jörn Guy Süß - name: fhirflare-ig-toolkit - type: application - urls: - - https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit/releases/download/helm-v0.5.0/fhirflare-ig-toolkit-0.5.0.tgz - version: 0.5.0 -generated: "2025-08-04T05:53:00.152703546Z" diff --git a/migrations/README b/migrations/README deleted file mode 100644 index 0e04844..0000000 --- a/migrations/README +++ /dev/null @@ -1 +0,0 @@ -Single-database configuration for Flask. diff --git a/migrations/__pycache__/env.cpython-312.pyc b/migrations/__pycache__/env.cpython-312.pyc deleted file mode 100644 index 5c294b5a6c5c0eaf9e2f8fbb4e30fc76a0641e87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4493 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 L`%|vd4Uhi-cOkzQ diff --git a/migrations/alembic.ini b/migrations/alembic.ini deleted file mode 100644 index ec9d45c..0000000 --- a/migrations/alembic.ini +++ /dev/null @@ -1,50 +0,0 @@ -# A generic, single database configuration. - -[alembic] -# template used to generate migration files -# file_template = %%(rev)s_%%(slug)s - -# set to 'true' to run the environment during -# the 'revision' command, regardless of autogenerate -# revision_environment = false - - -# Logging configuration -[loggers] -keys = root,sqlalchemy,alembic,flask_migrate - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = WARN -handlers = console -qualname = - -[logger_sqlalchemy] -level = WARN -handlers = -qualname = sqlalchemy.engine - -[logger_alembic] -level = INFO -handlers = -qualname = alembic - -[logger_flask_migrate] -level = INFO -handlers = -qualname = flask_migrate - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(levelname)-5.5s [%(name)s] %(message)s -datefmt = %H:%M:%S diff --git a/migrations/env.py b/migrations/env.py deleted file mode 100644 index 4c97092..0000000 --- a/migrations/env.py +++ /dev/null @@ -1,113 +0,0 @@ -import logging -from logging.config import fileConfig - -from flask import current_app - -from alembic import context - -# this is the Alembic Config object, which provides -# access to the values within the .ini file in use. -config = context.config - -# Interpret the config file for Python logging. -# This line sets up loggers basically. -fileConfig(config.config_file_name) -logger = logging.getLogger('alembic.env') - - -def get_engine(): - try: - # this works with Flask-SQLAlchemy<3 and Alchemical - return current_app.extensions['migrate'].db.get_engine() - except (TypeError, AttributeError): - # this works with Flask-SQLAlchemy>=3 - return current_app.extensions['migrate'].db.engine - - -def get_engine_url(): - try: - return get_engine().url.render_as_string(hide_password=False).replace( - '%', '%%') - except AttributeError: - return str(get_engine().url).replace('%', '%%') - - -# add your model's MetaData object here -# for 'autogenerate' support -# from myapp import mymodel -# target_metadata = mymodel.Base.metadata -config.set_main_option('sqlalchemy.url', get_engine_url()) -target_db = current_app.extensions['migrate'].db - -# other values from the config, defined by the needs of env.py, -# can be acquired: -# my_important_option = config.get_main_option("my_important_option") -# ... etc. - - -def get_metadata(): - if hasattr(target_db, 'metadatas'): - return target_db.metadatas[None] - return target_db.metadata - - -def run_migrations_offline(): - """Run migrations in 'offline' mode. - - This configures the context with just a URL - and not an Engine, though an Engine is acceptable - here as well. By skipping the Engine creation - we don't even need a DBAPI to be available. - - Calls to context.execute() here emit the given string to the - script output. - - """ - url = config.get_main_option("sqlalchemy.url") - context.configure( - url=url, target_metadata=get_metadata(), literal_binds=True - ) - - with context.begin_transaction(): - context.run_migrations() - - -def run_migrations_online(): - """Run migrations in 'online' mode. - - In this scenario we need to create an Engine - and associate a connection with the context. - - """ - - # this callback is used to prevent an auto-migration from being generated - # when there are no changes to the schema - # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html - def process_revision_directives(context, revision, directives): - if getattr(config.cmd_opts, 'autogenerate', False): - script = directives[0] - if script.upgrade_ops.is_empty(): - directives[:] = [] - logger.info('No changes in schema detected.') - - conf_args = current_app.extensions['migrate'].configure_args - if conf_args.get("process_revision_directives") is None: - conf_args["process_revision_directives"] = process_revision_directives - - connectable = get_engine() - - with connectable.connect() as connection: - context.configure( - connection=connection, - target_metadata=get_metadata(), - **conf_args - ) - - with context.begin_transaction(): - context.run_migrations() - - -if context.is_offline_mode(): - run_migrations_offline() -else: - run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako deleted file mode 100644 index 2c01563..0000000 --- a/migrations/script.py.mako +++ /dev/null @@ -1,24 +0,0 @@ -"""${message} - -Revision ID: ${up_revision} -Revises: ${down_revision | comma,n} -Create Date: ${create_date} - -""" -from alembic import op -import sqlalchemy as sa -${imports if imports else ""} - -# revision identifiers, used by Alembic. -revision = ${repr(up_revision)} -down_revision = ${repr(down_revision)} -branch_labels = ${repr(branch_labels)} -depends_on = ${repr(depends_on)} - - -def upgrade(): - ${upgrades if upgrades else "pass"} - - -def downgrade(): - ${downgrades if downgrades else "pass"} diff --git a/package.py b/package.py deleted file mode 100644 index acd96d9..0000000 --- a/package.py +++ /dev/null @@ -1,123 +0,0 @@ -from flask import Blueprint, jsonify, current_app, render_template -import os -import tarfile -import json -from datetime import datetime -import time -from services import pkg_version, safe_parse_version - -package_bp = Blueprint('package', __name__) - -@package_bp.route('/logs/') -def logs(name): - """ - Fetch logs for a package, listing each version with its publication date. - - Args: - name (str): The name of the package. - - Returns: - Rendered template with logs or an error message. - """ - try: - in_memory_cache = current_app.config.get('MANUAL_PACKAGE_CACHE', []) - if not in_memory_cache: - current_app.logger.error(f"No in-memory cache found for package logs: {name}") - return "

Package cache not found.

" - - package_data = next((pkg for pkg in in_memory_cache if isinstance(pkg, dict) and pkg.get('name', '').lower() == name.lower()), None) - if not package_data: - current_app.logger.error(f"Package not found in cache: {name}") - return "

Package not found.

" - - # Get the versions list with pubDate - versions = package_data.get('all_versions', []) - if not versions: - current_app.logger.warning(f"No versions found for package: {name}. Package data: {package_data}") - return "

No version history found for this package.

" - - current_app.logger.debug(f"Found {len(versions)} versions for package {name}: {versions[:5]}...") - - logs = [] - now = time.time() - for version_info in versions: - if not isinstance(version_info, dict): - current_app.logger.warning(f"Invalid version info for {name}: {version_info}") - continue - version = version_info.get('version', '') - pub_date_str = version_info.get('pubDate', '') - if not version or not pub_date_str: - current_app.logger.warning(f"Skipping version info with missing version or pubDate: {version_info}") - continue - - # Parse pubDate and calculate "when" - when = "Unknown" - try: - pub_date = datetime.strptime(pub_date_str, "%a, %d %b %Y %H:%M:%S %Z") - pub_time = pub_date.timestamp() - time_diff = now - pub_time - days_ago = int(time_diff / 86400) - if days_ago < 1: - hours_ago = int(time_diff / 3600) - if hours_ago < 1: - minutes_ago = int(time_diff / 60) - when = f"{minutes_ago} minute{'s' if minutes_ago != 1 else ''} ago" - else: - when = f"{hours_ago} hour{'s' if hours_ago != 1 else ''} ago" - else: - when = f"{days_ago} day{'s' if days_ago != 1 else ''} ago" - except ValueError as e: - current_app.logger.warning(f"Failed to parse pubDate '{pub_date_str}' for version {version}: {e}") - - logs.append({ - "version": version, - "pubDate": pub_date_str, - "when": when - }) - - if not logs: - current_app.logger.warning(f"No valid version entries with pubDate for package: {name}") - return "

No version history found for this package.

" - - # Sort logs by version number (newest first) - logs.sort(key=lambda x: safe_parse_version(x.get('version', '0.0.0a0')), reverse=True) - - current_app.logger.debug(f"Rendering logs for {name} with {len(logs)} entries") - return render_template('package.logs.html', logs=logs) - - except Exception as e: - current_app.logger.error(f"Error in logs endpoint for {name}: {str(e)}", exc_info=True) - return "

Error loading version history.

", 500 - -@package_bp.route('/dependents/') -def dependents(name): - """ - HTMX endpoint to fetch packages that depend on the current package. - Returns an HTML fragment with a table of dependent packages. - """ - in_memory_cache = current_app.config.get('MANUAL_PACKAGE_CACHE', []) - package_data = next((pkg for pkg in in_memory_cache if isinstance(pkg, dict) and pkg.get('name', '').lower() == name.lower()), None) - - if not package_data: - return "

Package not found.

" - - # Find dependents: packages whose dependencies include the current package - dependents = [] - for pkg in in_memory_cache: - if not isinstance(pkg, dict): - continue - dependencies = pkg.get('dependencies', []) - for dep in dependencies: - dep_name = dep.get('name', '') - if dep_name.lower() == name.lower(): - dependents.append({ - "name": pkg.get('name', 'Unknown'), - "version": pkg.get('latest_absolute_version', 'N/A'), - "author": pkg.get('author', 'N/A'), - "fhir_version": pkg.get('fhir_version', 'N/A'), - "version_count": pkg.get('version_count', 0), - "canonical": pkg.get('canonical', 'N/A') - }) - break - - return render_template('package.dependents.html', dependents=dependents) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index cec64a5..0000000 --- a/requirements.txt +++ /dev/null @@ -1,14 +0,0 @@ -Flask==2.3.3 -Flask-SQLAlchemy==3.0.5 -Werkzeug==2.3.7 -requests==2.31.0 -Flask-WTF==1.2.1 -WTForms==3.1.2 -Pytest -pyyaml==6.0.1 -fhir.resources==8.0.0 -Flask-Migrate==4.1.0 -cachetools -beautifulsoup4 -feedparser==6.0.11 -flasgger \ No newline at end of file diff --git a/services.py b/services.py deleted file mode 100644 index 8685550..0000000 --- a/services.py +++ /dev/null @@ -1,4985 +0,0 @@ -import requests -import os -import tarfile -import json -import re -import logging -import shutil -import sqlite3 -import feedparser -from flask import current_app, Blueprint, request, jsonify -from fhirpathpy import evaluate -from collections import defaultdict, deque -from pathlib import Path -from urllib.parse import quote, urlparse -from types import SimpleNamespace -import datetime -import subprocess -import tempfile -import zipfile -import xml.etree.ElementTree as ET -from flasgger import swag_from # Import swag_from here - -# Define Blueprint -services_bp = Blueprint('services', __name__) - -# Configure logging -if __name__ == '__main__': - logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') -else: - pass -logger = logging.getLogger(__name__) - -# --- ADD fhir.resources imports --- -try: - from fhir.resources import get_fhir_model_class - from fhir.resources.fhirtypesvalidators import FHIRValidationError # Updated import path - FHIR_RESOURCES_AVAILABLE = True - logger.info("fhir.resources library found. XML parsing will use this.") -except ImportError as e: - FHIR_RESOURCES_AVAILABLE = False - logger.warning(f"fhir.resources library failed to import. XML parsing will be basic and dependency analysis for XML may be incomplete. Error: {str(e)}") - # Define dummy classes if library not found to avoid NameErrors later - class FHIRValidationError(Exception): pass - def get_fhir_model_class(resource_type): raise NotImplementedError("fhir.resources not installed") -except Exception as e: - FHIR_RESOURCES_AVAILABLE = False - logger.error(f"Unexpected error importing fhir.resources library: {str(e)}") - class FHIRValidationError(Exception): pass - def get_fhir_model_class(resource_type): raise NotImplementedError("fhir.resources not installed") -# --- END fhir.resources imports --- - -# --- Check for optional 'packaging' library --- -try: - import packaging.version as pkg_version - HAS_PACKAGING_LIB = True - logger.info("Optional 'packaging' library found. Using for robust version comparison.") -except ImportError: - HAS_PACKAGING_LIB = False - logger.warning("Optional 'packaging' library not found. Using basic string comparison for versions.") - # Define a simple fallback class if packaging is missing - class BasicVersion: - def __init__(self, v_str): self.v_str = str(v_str) - # Define comparison methods for sorting compatibility - def __lt__(self, other): return self.v_str < str(other) - def __gt__(self, other): return self.v_str > str(other) - def __eq__(self, other): return self.v_str == str(other) - def __le__(self, other): return self.v_str <= str(other) - def __ge__(self, other): return self.v_str >= str(other) - def __ne__(self, other): return self.v_str != str(other) - def __str__(self): return self.v_str - pkg_version = SimpleNamespace(parse=BasicVersion, InvalidVersion=ValueError) # Mock parse and InvalidVersion - -# --- Constants --- -FHIR_REGISTRY_BASE_URL = "https://packages.fhir.org" -DOWNLOAD_DIR_NAME = "fhir_packages" -CANONICAL_PACKAGE = ("hl7.fhir.r4.core", "4.0.1") -CANONICAL_PACKAGE_ID = f"{CANONICAL_PACKAGE[0]}#{CANONICAL_PACKAGE[1]}" - -# --- Define Canonical Types --- -CANONICAL_RESOURCE_TYPES = { - "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", - "CapabilityStatement", "ImplementationGuide", "ConceptMap", "NamingSystem", - "OperationDefinition", "MessageDefinition", "CompartmentDefinition", - "GraphDefinition", "StructureMap", "Questionnaire" -} -# ----------------------------- - -# Define standard FHIR R4 base types -FHIR_R4_BASE_TYPES = { - "Account", "ActivityDefinition", "AdministrableProductDefinition", "AdverseEvent", "AllergyIntolerance", - "Appointment", "AppointmentResponse", "AuditEvent", "Basic", "Binary", "BiologicallyDerivedProduct", - "BodyStructure", "Bundle", "CapabilityStatement", "CarePlan", "CareTeam", "CatalogEntry", "ChargeItem", - "ChargeItemDefinition", "Claim", "ClaimResponse", "ClinicalImpression", "CodeSystem", "Communication", - "CommunicationRequest", "CompartmentDefinition", "Composition", "ConceptMap", "Condition", "Consent", - "Contract", "Coverage", "CoverageEligibilityRequest", "CoverageEligibilityResponse", "DetectedIssue", - "Device", "DeviceDefinition", "DeviceMetric", "DeviceRequest", "DeviceUseStatement", "DiagnosticReport", - "DocumentManifest", "DocumentReference", "DomainResource", "EffectEvidenceSynthesis", "Encounter", - "Endpoint", "EnrollmentRequest", "EnrollmentResponse", "EpisodeOfCare", "EventDefinition", "Evidence", - "EvidenceVariable", "ExampleScenario", "ExplanationOfBenefit", "FamilyMemberHistory", "Flag", "Goal", - "GraphDefinition", "Group", "GuidanceResponse", "HealthcareService", "ImagingStudy", "Immunization", - "ImmunizationEvaluation", "ImmunizationRecommendation", "ImplementationGuide", "InsurancePlan", - "Invoice", "Library", "Linkage", "List", "Location", "Measure", "MeasureReport", "Media", "Medication", - "MedicationAdministration", "MedicationDispense", "MedicationKnowledge", "MedicationRequest", - "MedicationStatement", "MedicinalProduct", "MedicinalProductAuthorization", "MedicinalProductContraindication", - "MedicinalProductIndication", "MedicinalProductIngredient", "MedicinalProductInteraction", - "MedicinalProductManufactured", "MedicinalProductPackaged", "MedicinalProductPharmaceutical", - "MedicinalProductUndesirableEffect", "MessageDefinition", "MessageHeader", "MolecularSequence", - "NamingSystem", "NutritionOrder", "Observation", "ObservationDefinition", "OperationDefinition", - "OperationOutcome", "Organization", "OrganizationAffiliation", "Patient", "PaymentNotice", - "PaymentReconciliation", "Person", "PlanDefinition", "Practitioner", "PractitionerRole", "Procedure", - "Provenance", "Questionnaire", "QuestionnaireResponse", "RelatedPerson", "RequestGroup", "ResearchDefinition", - "ResearchElementDefinition", "ResearchStudy", "ResearchSubject", "Resource", "RiskAssessment", - "RiskEvidenceSynthesis", "Schedule", "SearchParameter", "ServiceRequest", "Slot", "Specimen", - "SpecimenDefinition", "StructureDefinition", "StructureMap", "Subscription", "Substance", - "SubstanceNucleicAcid", "SubstancePolymer", "SubstanceProtein", "SubstanceReferenceInformation", - "SubstanceSourceMaterial", "SubstanceSpecification", "SupplyDelivery", "SupplyRequest", "Task", - "TerminologyCapabilities", "TestReport", "TestScript", "ValueSet", "VerificationResult", "VisionPrescription" -} - - -# ------------------------------------------------------------------- -#Helper function to support normalize: - -def safe_parse_version(v_str): - """ - Attempts to parse a version string using packaging.version. - Handles common FHIR suffixes like -dev, -ballot, -draft, -preview - by treating them as standard pre-releases (-a0, -b0, -rc0) for comparison. - Returns a comparable Version object or a fallback for unparseable strings. - """ - if not v_str or not isinstance(v_str, str): - # Handle None or non-string input, treat as lowest possible version - return pkg_version.parse("0.0.0a0") # Use alpha pre-release - - # Try standard parsing first - try: - return pkg_version.parse(v_str) - except pkg_version.InvalidVersion: - # Handle common FHIR suffixes if standard parsing fails - original_v_str = v_str # Keep original for logging - v_str_norm = v_str.lower() - # Split into base version and suffix - base_part = v_str_norm - suffix = None - if '-' in v_str_norm: - parts = v_str_norm.split('-', 1) - base_part = parts[0] - suffix = parts[1] - - # Check if base looks like a version number - if re.match(r'^\d+(\.\d+)*$', base_part): - try: - # Map FHIR suffixes to PEP 440 pre-release types for sorting - if suffix in ['dev', 'snapshot', 'ci-build']: - # Treat as alpha (earliest pre-release) - return pkg_version.parse(f"{base_part}a0") - elif suffix in ['draft', 'ballot', 'preview']: - # Treat as beta (after alpha) - return pkg_version.parse(f"{base_part}b0") - # Add more mappings if needed (e.g., -rc -> rc0) - elif suffix and suffix.startswith('rc'): - rc_num = ''.join(filter(str.isdigit, suffix)) or '0' - return pkg_version.parse(f"{base_part}rc{rc_num}") - - # If suffix isn't recognized, still try parsing base as final/post - # This might happen for odd suffixes like -final (though unlikely) - # If base itself parses, use that (treats unknown suffix as > pre-release) - return pkg_version.parse(base_part) - - except pkg_version.InvalidVersion: - # If base_part itself is invalid after splitting - logger.warning(f"Invalid base version '{base_part}' after splitting '{original_v_str}'. Treating as alpha.") - return pkg_version.parse("0.0.0a0") - except Exception as e: - logger.error(f"Unexpected error parsing FHIR-suffixed version '{original_v_str}': {e}") - return pkg_version.parse("0.0.0a0") - else: - # Base part doesn't look like numbers/dots (e.g., "current", "dev") - logger.warning(f"Unparseable version '{original_v_str}' (base '{base_part}' not standard). Treating as alpha.") - return pkg_version.parse("0.0.0a0") # Treat fully non-standard versions as very early - - except Exception as e: - # Catch any other unexpected parsing errors - logger.error(f"Unexpected error in safe_parse_version for '{v_str}': {e}") - return pkg_version.parse("0.0.0a0") # Fallback - -# --- MODIFIED FUNCTION with Enhanced Logging --- -def get_additional_registries(): - """Fetches the list of additional FHIR IG registries from the master feed.""" - logger.debug("Entering get_additional_registries function") - feed_registry_url = 'https://raw.githubusercontent.com/FHIR/ig-registry/master/package-feeds.json' - feeds = [] # Default to empty list - try: - logger.info(f"Attempting to fetch feed registry from {feed_registry_url}") - # Use a reasonable timeout - response = requests.get(feed_registry_url, timeout=15) - logger.debug(f"Feed registry request to {feed_registry_url} returned status code: {response.status_code}") - # Raise HTTPError for bad responses (4xx or 5xx) - response.raise_for_status() - - # Log successful fetch - logger.debug(f"Successfully fetched feed registry. Response text (first 500 chars): {response.text[:500]}...") - - try: - # Attempt to parse JSON - data = json.loads(response.text) - feeds_raw = data.get('feeds', []) - # Ensure structure is as expected before adding - feeds = [{'name': feed['name'], 'url': feed['url']} - for feed in feeds_raw - if isinstance(feed, dict) and 'name' in feed and 'url' in feed] - logger.info(f"Successfully parsed {len(feeds)} valid feeds from {feed_registry_url}") - - except json.JSONDecodeError as e: - # Log JSON parsing errors specifically - logger.error(f"JSON decoding error for feed registry from {feed_registry_url}: {e}") - # Log the problematic text snippet to help diagnose - logger.error(f"Problematic JSON text snippet: {response.text[:500]}...") - # feeds remains [] - - # --- Specific Exception Handling --- - except requests.exceptions.HTTPError as e: - logger.error(f"HTTP error fetching feed registry from {feed_registry_url}: {e}", exc_info=True) - # feeds remains [] - except requests.exceptions.ConnectionError as e: - logger.error(f"Connection error fetching feed registry from {feed_registry_url}: {e}", exc_info=True) - # feeds remains [] - except requests.exceptions.Timeout as e: - logger.error(f"Timeout fetching feed registry from {feed_registry_url}: {e}", exc_info=True) - # feeds remains [] - except requests.exceptions.RequestException as e: - # Catch other potential request-related errors - logger.error(f"General request error fetching feed registry from {feed_registry_url}: {e}", exc_info=True) - # feeds remains [] - except Exception as e: - # Catch any other unexpected errors during the process - logger.error(f"Unexpected error fetching feed registry from {feed_registry_url}: {e}", exc_info=True) - # feeds remains [] - - logger.debug(f"Exiting get_additional_registries function, returning {len(feeds)} feeds.") - return feeds -# --- END MODIFIED FUNCTION --- - -def import_manual_package_and_dependencies(input_source, version=None, dependency_mode='recursive', is_file=False, is_url=False, resolve_dependencies=True): - """ - Import a FHIR Implementation Guide package manually, cloning import_package_and_dependencies. - Supports registry, file, or URL inputs with dependency handling. - - Args: - input_source (str): Package name (for registry), file path (for file), or URL (for URL). - version (str, optional): Package version for registry imports. - dependency_mode (str): Dependency import mode ('recursive', 'patch-canonical', 'tree-shaking'). - is_file (bool): True if input_source is a file path. - is_url (bool): True if input_source is a URL. - resolve_dependencies (bool): Whether to resolve and import dependencies. - - Returns: - dict: Import results with 'requested', 'downloaded', 'dependencies', and 'errors'. - """ - logger.info(f"Starting manual import for {input_source} (mode={dependency_mode}, resolve_deps={resolve_dependencies})") - download_dir = _get_download_dir() - if not download_dir: - return { - "requested": input_source, - "downloaded": {}, - "dependencies": [], - "errors": ["Failed to get download directory."] - } - - results = { - "requested": input_source, - "downloaded": {}, - "dependencies": [], - "errors": [] - } - - try: - if is_file: - tgz_path = input_source - if not os.path.exists(tgz_path): - results['errors'].append(f"File not found: {tgz_path}") - return results - name, version = parse_package_filename(os.path.basename(tgz_path)) - if not name: - name = os.path.splitext(os.path.basename(tgz_path))[0] - version = "unknown" - target_filename = construct_tgz_filename(name, version) - target_path = os.path.join(download_dir, target_filename) - shutil.copy(tgz_path, target_path) - results['downloaded'][name, version] = target_path - elif is_url: - tgz_path = download_manual_package_from_url(input_source, download_dir) - if not tgz_path: - results['errors'].append(f"Failed to download package from URL: {input_source}") - return results - name, version = parse_package_filename(os.path.basename(tgz_path)) - if not name: - name = os.path.splitext(os.path.basename(tgz_path))[0] - version = "unknown" - results['downloaded'][name, version] = tgz_path - else: - tgz_path = download_manual_package(input_source, version, download_dir) - if not tgz_path: - results['errors'].append(f"Failed to download {input_source}#{version}") - return results - results['downloaded'][input_source, version] = tgz_path - name = input_source - - if resolve_dependencies: - pkg_info = process_manual_package_file(tgz_path) - if pkg_info.get('errors'): - results['errors'].extend(pkg_info['errors']) - dependencies = pkg_info.get('dependencies', []) - results['dependencies'] = dependencies - - if dependencies and dependency_mode != 'tree-shaking': - for dep in dependencies: - dep_name = dep.get('name') - dep_version = dep.get('version', 'latest') - if not dep_name: - continue - logger.info(f"Processing dependency {dep_name}#{dep_version}") - dep_result = import_manual_package_and_dependencies( - dep_name, - dep_version, - dependency_mode=dependency_mode, - resolve_dependencies=True - ) - results['downloaded'].update(dep_result['downloaded']) - results['dependencies'].extend(dep_result['dependencies']) - results['errors'].extend(dep_result['errors']) - - save_package_metadata(name, version, dependency_mode, results['dependencies']) - return results - except Exception as e: - logger.error(f"Error during manual import of {input_source}: {str(e)}", exc_info=True) - results['errors'].append(f"Unexpected error: {str(e)}") - return results - -def download_manual_package(package_name, version, download_dir): - """ - Download a FHIR package from the registry, cloning download_package. - - Args: - package_name (str): Package name. - version (str): Package version. - download_dir (str): Directory to save the package. - - Returns: - str: Path to the downloaded file, or None if failed. - """ - logger.info(f"Attempting manual download of {package_name}#{version}") - tgz_filename = construct_tgz_filename(package_name, version) - if not tgz_filename: - logger.error(f"Invalid filename constructed for {package_name}#{version}") - return None - target_path = os.path.join(download_dir, tgz_filename) - if os.path.exists(target_path): - logger.info(f"Manual package {package_name}#{version} already exists at {target_path}") - return target_path - - url = f"{FHIR_REGISTRY_BASE_URL}/{package_name}/{version}" - try: - response = requests.get(url, stream=True, timeout=30) - response.raise_for_status() - with open(target_path, 'wb') as f: - for chunk in response.iter_content(chunk_size=8192): - f.write(chunk) - logger.info(f"Manually downloaded {package_name}#{version} to {target_path}") - return target_path - except requests.exceptions.HTTPError as e: - logger.error(f"HTTP error downloading {package_name}#{version}: {e}") - return None - except requests.exceptions.RequestException as e: - logger.error(f"Request error downloading {package_name}#{version}: {e}") - return None - except Exception as e: - logger.error(f"Unexpected error downloading {package_name}#{version}: {e}", exc_info=True) - return None - -def download_manual_package_from_url(url, download_dir): - """ - Download a FHIR package from a URL, cloning download_package logic. - - Args: - url (str): URL to the .tgz file. - download_dir (str): Directory to save the package. - - Returns: - str: Path to the downloaded file, or None if failed. - """ - logger.info(f"Attempting manual download from URL: {url}") - parsed_url = urlparse(url) - filename = os.path.basename(parsed_url.path) - if not filename.endswith('.tgz'): - logger.error(f"URL does not point to a .tgz file: {filename}") - return None - target_path = os.path.join(download_dir, filename) - if os.path.exists(target_path): - logger.info(f"Package from {url} already exists at {target_path}") - return target_path - - try: - response = requests.get(url, stream=True, timeout=30) - response.raise_for_status() - with open(target_path, 'wb') as f: - for chunk in response.iter_content(chunk_size=8192): - f.write(chunk) - logger.info(f"Manually downloaded package from {url} to {target_path}") - return target_path - except requests.exceptions.HTTPError as e: - logger.error(f"HTTP error downloading from {url}: {e}") - return None - except requests.exceptions.RequestException as e: - logger.error(f"Request error downloading from {url}: {e}") - return None - except Exception as e: - logger.error(f"Unexpected error downloading from {url}: {e}", exc_info=True) - return None - -def process_manual_package_file(tgz_path): - """ - Process a .tgz package file to extract metadata, cloning process_package_file. - - Args: - tgz_path (str): Path to the .tgz file. - - Returns: - dict: Package metadata including dependencies and errors. - """ - if not tgz_path or not os.path.exists(tgz_path): - logger.error(f"Package file not found for manual processing: {tgz_path}") - return {'errors': [f"Package file not found: {tgz_path}"], 'dependencies': []} - - pkg_basename = os.path.basename(tgz_path) - name, version = parse_package_filename(tgz_path) - logger.info(f"Manually processing package: {pkg_basename} ({name}#{version})") - - results = { - 'dependencies': [], - 'errors': [] - } - - try: - with tarfile.open(tgz_path, "r:gz") as tar: - pkg_json_member = next((m for m in tar if m.name == 'package/package.json'), None) - if pkg_json_member: - with tar.extractfile(pkg_json_member) as f: - pkg_data = json.load(f) - dependencies = pkg_data.get('dependencies', {}) - results['dependencies'] = [ - {'name': dep_name, 'version': dep_version} - for dep_name, dep_version in dependencies.items() - ] - else: - results['errors'].append("package.json not found in archive") - except Exception as e: - logger.error(f"Error manually processing {tgz_path}: {e}", exc_info=True) - results['errors'].append(f"Error processing package: {str(e)}") - - return results - -def fetch_packages_from_registries(search_term=''): - logger.debug("Entering fetch_packages_from_registries function with search_term: %s", search_term) - packages_dict = defaultdict(list) - - try: - logger.debug("Calling get_additional_registries") - feed_registries = get_additional_registries() - logger.debug("Returned from get_additional_registries with %d registries: %s", len(feed_registries), feed_registries) - - if not feed_registries: - logger.warning("No feed registries available. Cannot fetch packages.") - return [] - - logger.info(f"Processing {len(feed_registries)} feed registries") - for feed in feed_registries: - try: - logger.info(f"Fetching feed: {feed['name']} from {feed['url']}") - response = requests.get(feed['url'], timeout=30) - response.raise_for_status() - - # Log the raw response content for debugging - response_text = response.text[:500] # Limit to first 500 chars for logging - logger.debug(f"Raw response from {feed['url']}: {response_text}") - - try: - data = json.loads(response.text) - num_feed_packages = len(data.get('packages', [])) - logger.info(f"Fetched from feed {feed['name']}: {num_feed_packages} packages (JSON)") - for pkg in data.get('packages', []): - if not isinstance(pkg, dict): - continue - pkg_name = pkg.get('name', '') - if not pkg_name: - continue - packages_dict[pkg_name].append(pkg) - except json.JSONDecodeError: - feed_data = feedparser.parse(response.text) - if not feed_data.entries: - logger.warning(f"No entries found in feed {feed['name']}") - continue - num_rss_packages = len(feed_data.entries) - logger.info(f"Fetched from feed {feed['name']}: {num_rss_packages} packages (Atom/RSS)") - logger.info(f"Sample feed entries from {feed['name']}: {feed_data.entries[:2]}") - for entry in feed_data.entries: - try: - # Extract package name and version from title (e.g., "hl7.fhir.au.ereq#0.3.0-preview") - title = entry.get('title', '') - if '#' in title: - pkg_name, version = title.split('#', 1) - else: - pkg_name = title - version = entry.get('version', '') - if not pkg_name: - pkg_name = entry.get('id', '') or entry.get('summary', '') - if not pkg_name: - continue - - package = { - 'name': pkg_name, - 'version': version, - 'author': entry.get('author', ''), - 'fhirVersion': entry.get('fhir_version', [''])[0] or '', - 'url': entry.get('link', ''), - 'canonical': entry.get('canonical', ''), - 'dependencies': entry.get('dependencies', []), - 'pubDate': entry.get('published', entry.get('pubdate', '')), - 'registry': feed['url'] - } - if search_term and package['name'] and search_term.lower() not in package['name'].lower(): - continue - packages_dict[pkg_name].append(package) - except Exception as entry_error: - logger.error(f"Error processing entry in feed {feed['name']}: {entry_error}") - logger.info(f"Problematic entry: {entry}") - except requests.exceptions.HTTPError as e: - if e.response.status_code == 404: - logger.warning(f"Feed endpoint not found for {feed['name']}: {feed['url']} - 404 Not Found") - else: - logger.error(f"HTTP error fetching from feed {feed['name']}: {e}") - except requests.exceptions.RequestException as e: - logger.error(f"Request error fetching from feed {feed['name']}: {e}") - except Exception as error: - logger.error(f"Unexpected error fetching from feed {feed['name']}: {error}") - except Exception as e: - logger.error(f"Unexpected error in fetch_packages_from_registries: {e}") - - # Convert packages_dict to a list of packages with aggregated versions - packages = [] - for pkg_name, entries in packages_dict.items(): - # Aggregate versions with their publication dates - versions = [ - { - "version": entry.get('version', ''), - "pubDate": entry.get('pubDate', '') - } - for entry in entries - if entry.get('version', '') - ] - # Sort versions by pubDate (newest first) - versions.sort(key=lambda x: x.get('pubDate', ''), reverse=True) - if not versions: - continue - - # Take the latest entry for the main package fields - latest_entry = entries[0] - package = { - 'name': pkg_name, - 'version': latest_entry.get('version', ''), - 'latestVersion': latest_entry.get('version', ''), - 'author': latest_entry.get('author', ''), - 'fhirVersion': latest_entry.get('fhirVersion', ''), - 'url': latest_entry.get('url', ''), - 'canonical': latest_entry.get('canonical', ''), - 'dependencies': latest_entry.get('dependencies', []), - 'versions': versions, # List of versions with pubDate - 'registry': latest_entry.get('registry', '') - } - packages.append(package) - - logger.info(f"Total packages fetched: {len(packages)}") - return packages - -def normalize_package_data(raw_packages): - """ - Normalizes package data, identifying latest absolute and latest official versions. - Uses safe_parse_version for robust comparison. - """ - packages_grouped = defaultdict(list) - skipped_raw_count = 0 - for entry in raw_packages: - if not isinstance(entry, dict): - skipped_raw_count += 1 - logger.warning(f"Skipping raw package entry, not a dict: {entry}") - continue - raw_name = entry.get('name') or entry.get('title') or '' - if not isinstance(raw_name, str): - raw_name = str(raw_name) - name_part = raw_name.split('#', 1)[0].strip().lower() - if name_part: - packages_grouped[name_part].append(entry) - else: - if not entry.get('id'): - skipped_raw_count += 1 - logger.warning(f"Skipping raw package entry, no name or id: {entry}") - logger.info(f"Initial grouping: {len(packages_grouped)} unique package names found. Skipped {skipped_raw_count} raw entries.") - - normalized_list = [] - skipped_norm_count = 0 - total_entries_considered = 0 - - for name_key, entries in packages_grouped.items(): - total_entries_considered += len(entries) - latest_absolute_data = None - latest_official_data = None - latest_absolute_ver_for_comp = safe_parse_version("0.0.0a0") - latest_official_ver_for_comp = safe_parse_version("0.0.0a0") - all_versions = [] - package_name_display = name_key - - # Aggregate all versions from entries - processed_versions = set() - for package_entry in entries: - versions_list = package_entry.get('versions', []) - for version_info in versions_list: - if isinstance(version_info, dict) and 'version' in version_info: - version_str = version_info.get('version', '') - if version_str and version_str not in processed_versions: - all_versions.append(version_info) - processed_versions.add(version_str) - - processed_entries = [] - for package_entry in entries: - version_str = None - raw_name_entry = package_entry.get('name') or package_entry.get('title') or '' - if not isinstance(raw_name_entry, str): - raw_name_entry = str(raw_name_entry) - version_keys = ['version', 'latestVersion'] - for key in version_keys: - val = package_entry.get(key) - if isinstance(val, str) and val: - version_str = val.strip() - break - elif isinstance(val, list) and val and isinstance(val[0], str) and val[0]: - version_str = val[0].strip() - break - if not version_str and '#' in raw_name_entry: - parts = raw_name_entry.split('#', 1) - if len(parts) == 2 and parts[1]: - version_str = parts[1].strip() - - if not version_str: - logger.warning(f"Skipping entry for {raw_name_entry}: no valid version found. Entry: {package_entry}") - skipped_norm_count += 1 - continue - - version_str = version_str.strip() - current_display_name = str(raw_name_entry).split('#')[0].strip() - if current_display_name and current_display_name != name_key: - package_name_display = current_display_name - - entry_with_version = package_entry.copy() - entry_with_version['version'] = version_str - processed_entries.append(entry_with_version) - - try: - current_ver_obj_for_comp = safe_parse_version(version_str) - if latest_absolute_data is None or current_ver_obj_for_comp > latest_absolute_ver_for_comp: - latest_absolute_ver_for_comp = current_ver_obj_for_comp - latest_absolute_data = entry_with_version - - if re.match(r'^\d+\.\d+\.\d+(?:-[a-zA-Z0-9\.]+)?$', version_str): - if latest_official_data is None or current_ver_obj_for_comp > latest_official_ver_for_comp: - latest_official_ver_for_comp = current_ver_obj_for_comp - latest_official_data = entry_with_version - except Exception as comp_err: - logger.error(f"Error comparing version '{version_str}' for package '{package_name_display}': {comp_err}", exc_info=True) - - if latest_absolute_data: - final_absolute_version = latest_absolute_data.get('version', 'unknown') - final_official_version = latest_official_data.get('version') if latest_official_data else None - - author_raw = latest_absolute_data.get('author') or latest_absolute_data.get('publisher') or '' - if isinstance(author_raw, dict): - author = author_raw.get('name', str(author_raw)) - elif not isinstance(author_raw, str): - author = str(author_raw) - else: - author = author_raw - - fhir_version_str = None - fhir_keys = ['fhirVersion', 'fhirVersions', 'fhir_version'] - for key in fhir_keys: - val = latest_absolute_data.get(key) - if isinstance(val, list) and val and isinstance(val[0], str): - fhir_version_str = val[0] - break - elif isinstance(val, str) and val: - fhir_version_str = val - break - fhir_version_str = fhir_version_str or 'unknown' - - url_raw = latest_absolute_data.get('url') or latest_absolute_data.get('link') or '' - url = str(url_raw) if not isinstance(url_raw, str) else url_raw - canonical_raw = latest_absolute_data.get('canonical') or url - canonical = str(canonical_raw) if not isinstance(canonical_raw, str) else canonical_raw - - dependencies_raw = latest_absolute_data.get('dependencies', []) - dependencies = [] - if isinstance(dependencies_raw, dict): - dependencies = [{"name": str(dn), "version": str(dv)} for dn, dv in dependencies_raw.items()] - elif isinstance(dependencies_raw, list): - for dep in dependencies_raw: - if isinstance(dep, str): - if '@' in dep: - dep_name, dep_version = dep.split('@', 1) - dependencies.append({"name": dep_name, "version": dep_version}) - else: - dependencies.append({"name": dep, "version": "N/A"}) - elif isinstance(dep, dict) and 'name' in dep and 'version' in dep: - dependencies.append(dep) - - # Sort all_versions by pubDate (newest first) - all_versions.sort(key=lambda x: x.get('pubDate', ''), reverse=True) - - normalized_entry = { - 'name': package_name_display, - 'version': final_absolute_version, - 'latest_absolute_version': final_absolute_version, - 'latest_official_version': final_official_version, - 'author': author.strip(), - 'fhir_version': fhir_version_str.strip(), - 'url': url.strip(), - 'canonical': canonical.strip(), - 'dependencies': dependencies, - 'version_count': len(all_versions), - 'all_versions': all_versions, # Preserve the full versions list with pubDate - 'versions_data': processed_entries, - 'registry': latest_absolute_data.get('registry', '') - } - normalized_list.append(normalized_entry) - if not final_official_version: - logger.warning(f"No official version found for package '{package_name_display}'. Versions: {[v['version'] for v in all_versions]}") - else: - logger.warning(f"No valid entries found to determine details for package name key '{name_key}'. Entries: {entries}") - skipped_norm_count += len(entries) - - logger.info(f"Normalization complete. Entries considered: {total_entries_considered}, Skipped during norm: {skipped_norm_count}, Unique Packages Found: {len(normalized_list)}") - normalized_list.sort(key=lambda x: x.get('name', '').lower()) - return normalized_list - -def cache_packages(normalized_packages, db, CachedPackage): - """ - Cache normalized FHIR Implementation Guide packages in the CachedPackage database. - Updates existing records or adds new ones to improve performance for other routes. - - Args: - normalized_packages (list): List of normalized package dictionaries. - db: The SQLAlchemy database instance. - CachedPackage: The CachedPackage model class. - """ - try: - for package in normalized_packages: - existing = CachedPackage.query.filter_by(package_name=package['name'], version=package['version']).first() - if existing: - existing.author = package['author'] - existing.fhir_version = package['fhir_version'] - existing.version_count = package['version_count'] - existing.url = package['url'] - existing.all_versions = package['all_versions'] - existing.dependencies = package['dependencies'] - existing.latest_absolute_version = package['latest_absolute_version'] - existing.latest_official_version = package['latest_official_version'] - existing.canonical = package['canonical'] - existing.registry = package.get('registry', '') - else: - new_package = CachedPackage( - package_name=package['name'], - version=package['version'], - author=package['author'], - fhir_version=package['fhir_version'], - version_count=package['version_count'], - url=package['url'], - all_versions=package['all_versions'], - dependencies=package['dependencies'], - latest_absolute_version=package['latest_absolute_version'], - latest_official_version=package['latest_official_version'], - canonical=package['canonical'], - registry=package.get('registry', '') - ) - db.session.add(new_package) - db.session.commit() - logger.info(f"Cached {len(normalized_packages)} packages in CachedPackage.") - except Exception as error: - db.session.rollback() - logger.error(f"Error caching packages: {error}") - raise - -#----------------------------------------------------------------------- - -# --- Helper Functions --- - -def _get_download_dir(): - """Gets the absolute path to the configured FHIR package download directory.""" - packages_dir = None - try: - packages_dir = current_app.config.get('FHIR_PACKAGES_DIR') - if packages_dir: - logger.debug(f"Using FHIR_PACKAGES_DIR from current_app config: {packages_dir}") - else: - logger.warning("FHIR_PACKAGES_DIR not found in current_app config.") - instance_path = current_app.instance_path - packages_dir = os.path.join(instance_path, DOWNLOAD_DIR_NAME) - logger.warning(f"Falling back to default packages path: {packages_dir}") - except RuntimeError: - logger.warning("No app context found. Constructing default relative path for packages.") - script_dir = os.path.dirname(__file__) - instance_path_fallback = os.path.abspath(os.path.join(script_dir, '..', 'instance')) - packages_dir = os.path.join(instance_path_fallback, DOWNLOAD_DIR_NAME) - logger.debug(f"Constructed fallback packages path: {packages_dir}") - if not packages_dir: - logger.error("Fatal Error: Could not determine FHIR packages directory.") - return None - try: - os.makedirs(packages_dir, exist_ok=True) - return packages_dir - except OSError as e: - logger.error(f"Fatal Error creating packages directory {packages_dir}: {e}", exc_info=True) - return None - except Exception as e: - logger.error(f"Unexpected error getting/creating packages directory {packages_dir}: {e}", exc_info=True) - return None - -# --- Helper to get description (Add this to services.py) --- -def get_package_description(package_name, package_version, packages_dir): - """Reads package.json from a tgz and returns the description.""" - tgz_filename = construct_tgz_filename(package_name, package_version) - if not tgz_filename: return "Error: Could not construct filename." - tgz_path = os.path.join(packages_dir, tgz_filename) - if not os.path.exists(tgz_path): - return f"Error: Package file not found ({tgz_filename})." - - try: - with tarfile.open(tgz_path, "r:gz") as tar: - pkg_json_member = next((m for m in tar if m.name == 'package/package.json'), None) - if pkg_json_member: - with tar.extractfile(pkg_json_member) as f: - pkg_data = json.load(f) - return pkg_data.get('description', 'No description found in package.json.') - else: - return "Error: package.json not found in archive." - except (tarfile.TarError, json.JSONDecodeError, KeyError, IOError, Exception) as e: - logger.error(f"Error reading description from {tgz_filename}: {e}") - return f"Error reading package details: {e}" - -def sanitize_filename_part(text): - """Basic sanitization for name/version parts of filename.""" - if not isinstance(text, str): - text = str(text) - safe_text = "".join(c if c.isalnum() or c in ['.', '-'] else '_' for c in text) - safe_text = re.sub(r'_+', '_', safe_text) - safe_text = safe_text.strip('_-.') - return safe_text if safe_text else "invalid_name" - -def construct_tgz_filename(name, version): - """Constructs the standard FHIR package filename using sanitized parts.""" - if not name or not version: - logger.error(f"Cannot construct filename with missing name ('{name}') or version ('{version}')") - return None - return f"{sanitize_filename_part(name)}-{sanitize_filename_part(version)}.tgz" - -def construct_metadata_filename(name, version): - """Constructs the standard metadata filename.""" - if not name or not version: - logger.error(f"Cannot construct metadata filename with missing name ('{name}') or version ('{version}')") - return None - return f"{sanitize_filename_part(name)}-{sanitize_filename_part(version)}.metadata.json" - -# --- Helper Function to Find References (Keep as before) --- -def find_references(element, refs_list): - """ - Recursively finds all 'reference' strings within a FHIR resource element (dict or list). - Appends found reference strings to refs_list. - """ - if isinstance(element, dict): - for key, value in element.items(): - if key == 'reference' and isinstance(value, str): - refs_list.append(value) - elif isinstance(value, (dict, list)): - find_references(value, refs_list) # Recurse - elif isinstance(element, list): - for item in element: - if isinstance(item, (dict, list)): - find_references(item, refs_list) # Recurse - -# --- NEW: Helper Function for Basic FHIR XML to Dict --- -def basic_fhir_xml_to_dict(xml_string): - """ - Very basic conversion of FHIR XML to a dictionary. - Focuses on resourceType, id, and finding reference elements/attributes. - NOTE: This is NOT a complete or robust FHIR XML parser. Use with caution. - Returns a dictionary representation or None if parsing fails. - """ - try: - # Replace namespace prefixes for easier parsing with ElementTree find methods - # This is a common simplification but might fail for complex XML namespaces - xml_string_no_ns = re.sub(r' xmlns="[^"]+"', '', xml_string, count=1) - xml_string_no_ns = re.sub(r' xmlns:[^=]+="[^"]+"', '', xml_string_no_ns) - root = ET.fromstring(xml_string_no_ns) - - resource_dict = {"resourceType": root.tag} - - # Find 'id' element usually directly under the root - id_element = root.find("./id[@value]") - if id_element is not None: - resource_dict["id"] = id_element.get("value") - else: # Check if id is an attribute of the root (less common) - res_id = root.get("id") - if res_id: resource_dict["id"] = res_id - - # Recursively find 'reference' elements and extract their 'value' attribute - references = [] - for ref_element in root.findall(".//reference[@value]"): - ref_value = ref_element.get("value") - if ref_value: - references.append({"reference": ref_value}) # Store in a way find_references can find - - # Find other potential references (e.g., url attributes on certain elements) - # This needs to be expanded based on common FHIR patterns if needed - for url_element in root.findall(".//*[@url]"): # Find any element with a 'url' attribute - url_value = url_element.get("url") - # Basic check if it looks like a resource reference (simplistic) - if url_value and ('/' in url_value or url_value.startswith('urn:')): - # Decide how to store this - maybe add to a specific key? - # For now, let's add it to a generic '_references_from_url' list - if '_references_from_url' not in resource_dict: - resource_dict['_references_from_url'] = [] - resource_dict['_references_from_url'].append({"reference": url_value}) - - - # Add references found into the main dict structure so find_references can process them - if references or '_references_from_url' in resource_dict: - # Combine them - choose a suitable key, e.g., '_extracted_references' - resource_dict['_extracted_references'] = references + resource_dict.get('_references_from_url', []) - - # Include raw XML for debugging or potential later use - # resource_dict["_xml_content"] = xml_string - return resource_dict - - except ET.ParseError as e: - logger.error(f"XML Parse Error during basic conversion: {e}") - return None - except Exception as e: - logger.error(f"Unexpected error during basic_fhir_xml_to_dict: {e}", exc_info=True) - return None - -# def parse_package_filename(filename): -# """Parses a standard FHIR package filename into name and version.""" -# if not filename or not filename.endswith('.tgz'): -# logger.debug(f"Filename '{filename}' does not end with .tgz") -# return None, None -# base_name = filename[:-4] -# last_hyphen_index = base_name.rfind('-') -# while last_hyphen_index != -1: -# potential_name = base_name[:last_hyphen_index] -# potential_version = base_name[last_hyphen_index + 1:] -# if potential_version and (potential_version[0].isdigit() or any(potential_version.startswith(kw) for kw in ['v', 'dev', 'draft', 'preview', 'release', 'alpha', 'beta'])): -# name = potential_name.replace('_', '.') -# version = potential_version -# logger.debug(f"Parsed '{filename}' -> name='{name}', version='{version}'") -# return name, version -# else: -# last_hyphen_index = base_name.rfind('-', 0, last_hyphen_index) -# logger.warning(f"Could not parse version from '{filename}'. Treating '{base_name}' as name.") -# name = base_name.replace('_', '.') -# version = "" -# return name, version - -def parse_package_filename(filename): - """ - Parses a standard FHIR package filename into name and version. - Handles various version formats including semantic versions, pre-releases, snapshots, and complex suffixes. - """ - if not filename or not filename.endswith('.tgz'): - logger.debug(f"Filename '{filename}' does not end with .tgz") - return None, None - - base_name = filename[:-4] # Remove '.tgz' - - # Define a comprehensive pattern for FHIR package versions as a single string - # Matches versions like: - # - 1.0.0, 4.0.2 - # - 1.1.0-preview, 0.1.0-draft, 1.0.0-ballot-3 - # - 1.0.0-alpha.1, 1.0.0-RC2, 0.9.0-alpha1.0.8 - # - 1.1.0-snapshot-3, 0.0.1-snapshot - # - 2.3.5-buildnumbersuffix2 - version_pattern = r'(\d+\.\d+\.\d+)(?:-(?:preview|ballot|draft|snapshot|alpha|beta|RC\d*|buildnumbersuffix\d*|alpha\d+\.\d+\.\d+|snapshot-\d+|ballot-\d+|alpha\.\d+))?$' - - # Find the last occurrence of the version pattern in the base_name - match = None - for i in range(len(base_name), 0, -1): - substring = base_name[:i] - if re.search(version_pattern, substring): - match = re.search(version_pattern, base_name[:i]) - if match: - break - - if not match: - logger.warning(f"Could not parse version from '{filename}'. Treating '{base_name}' as name.") - name = base_name.replace('_', '.') - version = "" - return name, version - - # Extract the matched version - version_start_idx = match.start(1) # Start of the version (e.g., start of "1.1.0" in "1.1.0-preview") - name = base_name[:version_start_idx].rstrip('-').replace('_', '.') # Everything before the version - version = base_name[version_start_idx:] # The full version string - - # Validate the name and version - if not name or not version: - logger.warning(f"Invalid parse for '{filename}': name='{name}', version='{version}'. Using fallback.") - name = base_name.replace('_', '.') - version = "" - return name, version - - logger.debug(f"Parsed '{filename}' -> name='{name}', version='{version}'") - return name, version - -def remove_narrative(resource, include_narrative=False): - """Remove narrative text element from a FHIR resource if not including narrative.""" - if isinstance(resource, dict) and not include_narrative: - if 'text' in resource: - logger.debug(f"Removing narrative text from resource: {resource.get('resourceType', 'unknown')}") - del resource['text'] - if resource.get('resourceType') == 'Bundle' and 'entry' in resource: - resource['entry'] = [ - dict(entry, resource=remove_narrative(entry.get('resource'), include_narrative)) - if entry.get('resource') else entry - for entry in resource['entry'] - ] - return resource - -def get_cached_structure(package_name, package_version, resource_type, view): - """Retrieve cached StructureDefinition from SQLite.""" - try: - conn = sqlite3.connect(os.path.join(current_app.instance_path, 'fhir_ig.db')) - cursor = conn.cursor() - cursor.execute(""" - SELECT structure_data FROM structure_cache - WHERE package_name = ? AND package_version = ? AND resource_type = ? AND view = ? - """, (package_name, package_version, resource_type, view)) - result = cursor.fetchone() - conn.close() - if result: - logger.debug(f"Cache hit for {package_name}#{package_version}:{resource_type}:{view}") - return json.loads(result[0]) - logger.debug(f"No cache entry for {package_name}#{package_version}:{resource_type}:{view}") - return None - except Exception as e: - logger.error(f"Error accessing structure cache: {e}", exc_info=True) - return None - -def cache_structure(package_name, package_version, resource_type, view, structure_data): - """Cache StructureDefinition in SQLite.""" - try: - conn = sqlite3.connect(os.path.join(current_app.instance_path, 'fhir_ig.db')) - cursor = conn.cursor() - cursor.execute(""" - CREATE TABLE IF NOT EXISTS structure_cache ( - package_name TEXT, - package_version TEXT, - resource_type TEXT, - view TEXT, - structure_data TEXT, - PRIMARY KEY (package_name, package_version, resource_type, view) - ) - """) - cursor.execute(""" - INSERT OR REPLACE INTO structure_cache - (package_name, package_version, resource_type, view, structure_data) - VALUES (?, ?, ?, ?, ?) - """, (package_name, package_version, resource_type, view, json.dumps(structure_data))) - conn.commit() - conn.close() - logger.debug(f"Cached structure for {package_name}#{package_version}:{resource_type}:{view}") - except Exception as e: - logger.error(f"Error caching structure: {e}", exc_info=True) - - -#----OLD CODE HERE -# def find_and_extract_sd(tgz_path, resource_identifier, profile_url=None, include_narrative=False, raw=False): -# """Helper to find and extract StructureDefinition json from a tgz path, prioritizing profile match.""" -# sd_data = None -# found_path = None -# if not tgz_path or not os.path.exists(tgz_path): -# logger.error(f"File not found in find_and_extract_sd: {tgz_path}") -# return None, None -# try: -# with tarfile.open(tgz_path, "r:gz") as tar: -# logger.debug(f"Searching for SD matching '{resource_identifier}' with profile '{profile_url}' in {os.path.basename(tgz_path)}") -# potential_matches = [] -# for member in tar: -# if not (member.isfile() and member.name.startswith('package/') and member.name.lower().endswith('.json')): -# continue -# if os.path.basename(member.name).lower() in ['package.json', '.index.json', 'validation-summary.json', 'validation-oo.json']: -# continue -# fileobj = None -# try: -# fileobj = tar.extractfile(member) -# if fileobj: -# content_bytes = fileobj.read() -# content_string = content_bytes.decode('utf-8-sig') -# data = json.loads(content_string) -# if isinstance(data, dict) and data.get('resourceType') == 'StructureDefinition': -# sd_id = data.get('id') -# sd_name = data.get('name') -# sd_type = data.get('type') -# sd_url = data.get('url') -# sd_filename_base = os.path.splitext(os.path.basename(member.name))[0] -# sd_filename_lower = sd_filename_base.lower() -# resource_identifier_lower = resource_identifier.lower() if resource_identifier else None -# match_score = 0 -# if profile_url and sd_url == profile_url: -# match_score = 5 -# sd_data = remove_narrative(data, include_narrative) -# found_path = member.name -# logger.info(f"Found definitive SD matching profile '{profile_url}' at path: {found_path}") -# break -# elif resource_identifier_lower: -# if sd_id and resource_identifier_lower == sd_id.lower(): -# match_score = 4 -# elif sd_name and resource_identifier_lower == sd_name.lower(): -# match_score = 4 -# elif sd_filename_lower == f"structuredefinition-{resource_identifier_lower}": -# match_score = 3 -# elif sd_type and resource_identifier_lower == sd_type.lower() and not re.search(r'[-.]', resource_identifier): -# match_score = 2 -# elif resource_identifier_lower in sd_filename_lower: -# match_score = 1 -# elif sd_url and resource_identifier_lower in sd_url.lower(): -# match_score = 1 -# if match_score > 0: -# potential_matches.append((match_score, remove_narrative(data, include_narrative), member.name)) -# if match_score >= 3: -# sd_data = remove_narrative(data, include_narrative) -# found_path = member.name -# break -# except json.JSONDecodeError as e: -# logger.debug(f"Could not parse JSON in {member.name}, skipping: {e}") -# except UnicodeDecodeError as e: -# logger.warning(f"Could not decode UTF-8 in {member.name}, skipping: {e}") -# except tarfile.TarError as e: -# logger.warning(f"Tar error reading member {member.name}, skipping: {e}") -# except Exception as e: -# logger.warning(f"Could not read/parse potential SD {member.name}, skipping: {e}") -# finally: -# if fileobj: -# fileobj.close() -# if not sd_data and potential_matches: -# potential_matches.sort(key=lambda x: x[0], reverse=True) -# best_match = potential_matches[0] -# sd_data = best_match[1] -# found_path = best_match[2] -# logger.info(f"Selected best match for '{resource_identifier}' from potential matches (Score: {best_match[0]}): {found_path}") -# if sd_data is None: -# logger.info(f"SD matching identifier '{resource_identifier}' or profile '{profile_url}' not found within archive {os.path.basename(tgz_path)}") -# elif raw: -# # Return the full, unprocessed StructureDefinition JSON -# with tarfile.open(tgz_path, "r:gz") as tar: -# fileobj = tar.extractfile(found_path) -# content_bytes = fileobj.read() -# content_string = content_bytes.decode('utf-8-sig') -# raw_data = json.loads(content_string) -# return remove_narrative(raw_data, include_narrative), found_path -# except tarfile.ReadError as e: -# logger.error(f"Tar ReadError reading {tgz_path}: {e}") -# return None, None -# except tarfile.TarError as e: -# logger.error(f"TarError reading {tgz_path} in find_and_extract_sd: {e}") -# raise -# except FileNotFoundError: -# logger.error(f"FileNotFoundError reading {tgz_path} in find_and_extract_sd.") -# raise -# except Exception as e: -# logger.error(f"Unexpected error in find_and_extract_sd for {tgz_path}: {e}", exc_info=True) -# raise -# return sd_data, found_path -#--- OLD - -# --- UPDATED: find_and_extract_sd function --- -def find_and_extract_sd(tgz_path, resource_identifier, profile_url=None, include_narrative=False, raw=False): - """ - Helper to find and extract StructureDefinition json from a tgz path, prioritizing profile match. - - This version includes logic to handle canonical URLs with version numbers (e.g., `|5.2.0`) - and to prioritize a direct profile URL match. - """ - sd_data = None - found_path = None - if not tgz_path or not os.path.exists(tgz_path): - logger.error(f"File not found in find_and_extract_sd: {tgz_path}") - return None, None - try: - with tarfile.open(tgz_path, "r:gz") as tar: - logger.debug(f"Searching for SD matching '{resource_identifier}' with profile '{profile_url}' in {os.path.basename(tgz_path)}") - potential_matches = [] - - # --- Work Item 3: Sanitize profile URL to strip version --- - clean_profile_url = profile_url.split('|')[0] if profile_url else None - logger.debug(f"Cleaned profile URL for search: '{clean_profile_url}'") - - for member in tar: - if not (member.isfile() and member.name.startswith('package/') and member.name.lower().endswith('.json')): - continue - if os.path.basename(member.name).lower() in ['package.json', '.index.json', 'validation-summary.json', 'validation-oo.json']: - continue - fileobj = None - try: - fileobj = tar.extractfile(member) - if fileobj: - content_bytes = fileobj.read() - content_string = content_bytes.decode('utf-8-sig') - data = json.loads(content_string) - if isinstance(data, dict) and data.get('resourceType') == 'StructureDefinition': - sd_id = data.get('id') - sd_name = data.get('name') - sd_type = data.get('type') - sd_url = data.get('url') - sd_filename_base = os.path.splitext(os.path.basename(member.name))[0] - sd_filename_lower = sd_filename_base.lower() - resource_identifier_lower = resource_identifier.lower() if resource_identifier else None - match_score = 0 - - # --- Prioritize exact match on the canonical URL (without version) --- - if clean_profile_url and sd_url == clean_profile_url: - match_score = 5 - sd_data = remove_narrative(data, include_narrative) - found_path = member.name - logger.info(f"Found definitive SD matching profile '{clean_profile_url}' at path: {found_path}") - break - - elif resource_identifier_lower: - if sd_id and resource_identifier_lower == sd_id.lower(): - match_score = 4 - elif sd_name and resource_identifier_lower == sd_name.lower(): - match_score = 4 - elif sd_filename_lower == f"structuredefinition-{resource_identifier_lower}": - match_score = 3 - # --- Work Item 2: Score match on resourceType for fallback logic --- - elif sd_type and resource_identifier_lower == sd_type.lower() and not re.search(r'[-.]', resource_identifier): - match_score = 2 - elif resource_identifier_lower in sd_filename_lower: - match_score = 1 - elif sd_url and resource_identifier_lower in sd_url.lower(): - match_score = 1 - if match_score > 0: - potential_matches.append((match_score, remove_narrative(data, include_narrative), member.name)) - if match_score >= 3: - sd_data = remove_narrative(data, include_narrative) - found_path = member.name - break - except json.JSONDecodeError as e: - logger.debug(f"Could not parse JSON in {member.name}, skipping: {e}") - except UnicodeDecodeError as e: - logger.warning(f"Could not decode UTF-8 in {member.name}, skipping: {e}") - except tarfile.TarError as e: - logger.warning(f"Tar error reading member {member.name}, skipping: {e}") - except Exception as e: - logger.warning(f"Could not read/parse potential SD {member.name}, skipping: {e}") - finally: - if fileobj: - fileobj.close() - if not sd_data and potential_matches: - potential_matches.sort(key=lambda x: x[0], reverse=True) - best_match = potential_matches[0] - sd_data = best_match[1] - found_path = best_match[2] - logger.info(f"Selected best match for '{resource_identifier}' from potential matches (Score: {best_match[0]}): {found_path}") - if sd_data is None: - logger.info(f"SD matching identifier '{resource_identifier}' or profile '{profile_url}' not found within archive {os.path.basename(tgz_path)}") - elif raw: - with tarfile.open(tgz_path, "r:gz") as tar: - fileobj = tar.extractfile(found_path) - content_bytes = fileobj.read() - content_string = content_bytes.decode('utf-8-sig') - raw_data = json.loads(content_string) - return remove_narrative(raw_data, include_narrative), found_path - except tarfile.ReadError as e: - logger.error(f"Tar ReadError reading {tgz_path}: {e}") - return None, None - except tarfile.TarError as e: - logger.error(f"TarError reading {tgz_path} in find_and_extract_sd: {e}") - raise - except FileNotFoundError: - logger.error(f"FileNotFoundError reading {tgz_path} in find_and_extract_sd.") - raise - except Exception as e: - logger.error(f"Unexpected error in find_and_extract_sd for {tgz_path}: {e}", exc_info=True) - raise - return sd_data, found_path - - - -# --- Metadata Saving/Loading --- -def save_package_metadata(name, version, dependency_mode, dependencies, complies_with_profiles=None, imposed_profiles=None): - """Saves dependency mode, imported dependencies, and profile relationships as metadata.""" - download_dir = _get_download_dir() - if not download_dir: - logger.error("Could not get download directory for metadata saving.") - return False - metadata_filename = construct_metadata_filename(name, version) - if not metadata_filename: return False - metadata_path = os.path.join(download_dir, metadata_filename) - metadata = { - 'package_name': name, - 'version': version, - 'dependency_mode': dependency_mode, - 'imported_dependencies': dependencies or [], - 'complies_with_profiles': complies_with_profiles or [], - 'imposed_profiles': imposed_profiles or [], - 'timestamp': datetime.datetime.now(datetime.timezone.utc).isoformat() - } - try: - with open(metadata_path, 'w', encoding='utf-8') as f: - json.dump(metadata, f, indent=2) - logger.info(f"Saved metadata for {name}#{version} at {metadata_path}") - return True - except IOError as e: - logger.error(f"Failed to write metadata file {metadata_path}: {e}") - return False - except Exception as e: - logger.error(f"Unexpected error saving metadata for {name}#{version}: {e}", exc_info=True) - return False - -def get_package_metadata(name, version): - """Retrieves the metadata for a given package.""" - download_dir = _get_download_dir() - if not download_dir: - logger.error("Could not get download directory for metadata retrieval.") - return None - metadata_filename = construct_metadata_filename(name, version) - if not metadata_filename: return None - metadata_path = os.path.join(download_dir, metadata_filename) - if os.path.exists(metadata_path): - try: - with open(metadata_path, 'r', encoding='utf-8') as f: - return json.load(f) - except (IOError, json.JSONDecodeError) as e: - logger.error(f"Failed to read or parse metadata file {metadata_path}: {e}") - return None - except Exception as e: - logger.error(f"Unexpected error reading metadata for {name}#{version}: {e}", exc_info=True) - return None - else: - logger.debug(f"Metadata file not found: {metadata_path}") - return None - -def process_package_file(tgz_path): - """ - Extracts types, profile status, MS elements, examples, profile relationships, - and search parameter conformance from a downloaded .tgz package. - """ - if not tgz_path or not os.path.exists(tgz_path): - logger.error(f"Package file not found for processing: {tgz_path}") - return {'errors': [f"Package file not found: {tgz_path}"], 'resource_types_info': []} - - pkg_basename = os.path.basename(tgz_path) - name, version = parse_package_filename(tgz_path) # Assumes parse_package_filename exists - logger.info(f"Processing package file details: {pkg_basename} ({name}#{version})") - - # Initialize results dictionary - results = { - 'resource_types_info': [], - 'must_support_elements': {}, - 'examples': {}, - 'complies_with_profiles': [], - 'imposed_profiles': [], - 'search_param_conformance': {}, # Dictionary to store conformance - 'errors': [] - } - - # Intermediate storage for processing - resource_info = defaultdict(lambda: { - 'name': None, - 'type': None, - 'is_profile': False, - 'ms_flag': False, - 'ms_paths': set(), - 'examples': set(), - 'sd_processed': False, - 'optional_usage': False - }) - referenced_types = set() - capability_statement_data = None # Store the main CapabilityStatement - - try: - with tarfile.open(tgz_path, "r:gz") as tar: - members = tar.getmembers() - logger.debug(f"Found {len(members)} members in {pkg_basename}") - - # Filter for relevant JSON files once - json_members = [] - for m in members: - if m.isfile() and m.name.startswith('package/') and m.name.lower().endswith('.json'): - # Exclude common metadata files by basename - basename_lower = os.path.basename(m.name).lower() - if basename_lower not in ['package.json', '.index.json', 'validation-summary.json', 'validation-oo.json']: - json_members.append(m) - logger.debug(f"Found {len(json_members)} potential JSON resource members.") - - # --- Pass 1: Process StructureDefinitions and Find CapabilityStatement --- - logger.debug("Pass 1: Processing StructureDefinitions and finding CapabilityStatement...") - for member in json_members: - fileobj = None - try: - fileobj = tar.extractfile(member) - if not fileobj: continue - - content_bytes = fileobj.read() - # Handle potential BOM (Byte Order Mark) - content_string = content_bytes.decode('utf-8-sig') - data = json.loads(content_string) - - if not isinstance(data, dict): continue - resourceType = data.get('resourceType') - - # --- Process StructureDefinition --- - if resourceType == 'StructureDefinition': - data = remove_narrative(data) # Assumes remove_narrative exists - profile_id = data.get('id') or data.get('name') - sd_type = data.get('type') - sd_base = data.get('baseDefinition') - is_profile_sd = bool(sd_base) - - if not profile_id or not sd_type: - logger.warning(f"Skipping SD {member.name}: missing ID ('{profile_id}') or Type ('{sd_type}').") - continue - - entry_key = profile_id - entry = resource_info[entry_key] - if entry.get('sd_processed'): continue # Avoid reprocessing - - logger.debug(f"Processing SD: {entry_key} (type={sd_type}, profile={is_profile_sd})") - entry['name'] = entry_key - entry['type'] = sd_type - entry['is_profile'] = is_profile_sd - entry['sd_processed'] = True - referenced_types.add(sd_type) - - # Extract compliesWith/imposed profile URLs - complies_with = [] - imposed = [] - for ext in data.get('extension', []): - ext_url = ext.get('url') - value = ext.get('valueCanonical') - if value: - if ext_url == 'http://hl7.org/fhir/StructureDefinition/structuredefinition-compliesWithProfile': - complies_with.append(value) - elif ext_url == 'http://hl7.org/fhir/StructureDefinition/structuredefinition-imposeProfile': - imposed.append(value) - # Add unique URLs to results - results['complies_with_profiles'].extend(c for c in complies_with if c not in results['complies_with_profiles']) - results['imposed_profiles'].extend(i for i in imposed if i not in results['imposed_profiles']) - - # Must Support and Optional Usage Logic - has_ms_in_this_sd = False - ms_paths_in_this_sd = set() - elements = data.get('snapshot', {}).get('element', []) or data.get('differential', {}).get('element', []) - for element in elements: - if not isinstance(element, dict): continue - must_support = element.get('mustSupport') - element_id = element.get('id') - element_path = element.get('path') - slice_name = element.get('sliceName') - if must_support is True: - if element_id and element_path: - # Use element ID as the key for MS paths unless it's a slice - ms_path_key = f"{element_path}[sliceName='{slice_name}']" if slice_name else element_id - ms_paths_in_this_sd.add(ms_path_key) - has_ms_in_this_sd = True - else: - logger.warning(f"MS=true without path/id in {entry_key} ({member.name})") - has_ms_in_this_sd = True - - if has_ms_in_this_sd: - entry['ms_paths'].update(ms_paths_in_this_sd) - entry['ms_flag'] = True - - if sd_type == 'Extension' and has_ms_in_this_sd: - # Check if any MustSupport path is internal to the Extension definition - internal_ms_exists = any(p.startswith('Extension.') or ':' in p for p in entry['ms_paths']) - if internal_ms_exists: - entry['optional_usage'] = True - logger.info(f"Marked Extension {entry_key} as optional_usage") - - # --- Find CapabilityStatement --- - elif resourceType == 'CapabilityStatement': - # Store the first one found. Add logic here if specific selection needed. - if capability_statement_data is None: - capability_statement_data = data - logger.info(f"Found primary CapabilityStatement in: {member.name} (ID: {data.get('id', 'N/A')})") - else: - logger.warning(f"Found multiple CapabilityStatements. Using first found ({capability_statement_data.get('id', 'unknown')}). Ignoring {member.name}.") - - # Error handling for individual file processing - except json.JSONDecodeError as e: logger.warning(f"JSON parse error in {member.name}: {e}"); results['errors'].append(f"JSON error in {member.name}") - except UnicodeDecodeError as e: logger.warning(f"Encoding error in {member.name}: {e}"); results['errors'].append(f"Encoding error in {member.name}") - except Exception as e: logger.warning(f"Error processing member {member.name}: {e}", exc_info=False); results['errors'].append(f"Processing error in {member.name}: {e}") - finally: - if fileobj: fileobj.close() - # --- End Pass 1 --- - - # --- Pass 1.5: Process CapabilityStatement for Search Param Conformance --- - if capability_statement_data: - logger.debug("Processing CapabilityStatement for Search Parameter Conformance...") - conformance_map = defaultdict(dict) - # Standard FHIR extension URL for defining expectations - expectation_extension_url = "http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation" - - for rest_component in capability_statement_data.get('rest', []): - for resource_component in rest_component.get('resource', []): - resource_type = resource_component.get('type') - if not resource_type: continue - - for search_param in resource_component.get('searchParam', []): - param_name = search_param.get('name') - param_doc = search_param.get('documentation', '') - # Default conformance level if not explicitly stated - conformance_level = 'Optional' - - # Check for the standard expectation extension first - extensions = search_param.get('extension', []) - expectation_ext = next((ext for ext in extensions if ext.get('url') == expectation_extension_url), None) - - if expectation_ext and expectation_ext.get('valueCode'): - # Use the value from the standard extension - conformance_code = expectation_ext['valueCode'].upper() - # Map to SHALL, SHOULD, MAY - adjust if other codes are used by the IG - if conformance_code in ['SHALL', 'SHOULD', 'MAY', 'SHOULD-NOT']: # Add more if needed - conformance_level = conformance_code - else: - logger.warning(f"Unknown expectation code '{expectation_ext['valueCode']}' for {resource_type}.{param_name}. Defaulting to Optional.") - logger.debug(f" Conformance for {resource_type}.{param_name} from extension: {conformance_level}") - elif param_doc: - # Fallback: Check documentation string for keywords (less reliable) - doc_lower = param_doc.lower() - if 'shall' in doc_lower: conformance_level = 'SHALL' - elif 'should' in doc_lower: conformance_level = 'SHOULD' - elif 'may' in doc_lower: conformance_level = 'MAY' - if conformance_level != 'Optional': - logger.debug(f" Conformance for {resource_type}.{param_name} from documentation keywords: {conformance_level}") - - if param_name: - conformance_map[resource_type][param_name] = conformance_level - - results['search_param_conformance'] = dict(conformance_map) # Convert back to regular dict - logger.info(f"Extracted Search Parameter conformance rules for {len(conformance_map)} resource types.") - # logger.debug(f"Full Conformance Map: {json.dumps(results['search_param_conformance'], indent=2)}") # Optional detailed logging - else: - logger.warning(f"No CapabilityStatement found in package {pkg_basename}. Search parameter conformance data will be unavailable.") - # --- End Pass 1.5 --- - - # --- Pass 2: Process Examples --- - logger.debug("Pass 2: Processing Examples...") - example_members = [m for m in members if m.isfile() and m.name.startswith('package/') and 'example' in m.name.lower()] - - for member in example_members: - # Skip metadata files again just in case - basename_lower = os.path.basename(member.name).lower() - if basename_lower in ['package.json', '.index.json', 'validation-summary.json', 'validation-oo.json']: continue - - logger.debug(f"Processing potential example file: {member.name}") - is_json = member.name.lower().endswith('.json') - fileobj = None - associated_key = None - - try: - fileobj = tar.extractfile(member) - if not fileobj: continue - - if is_json: - content_bytes = fileobj.read() - content_string = content_bytes.decode('utf-8-sig') - data = json.loads(content_string) - - if not isinstance(data, dict): continue - resource_type_ex = data.get('resourceType') - if not resource_type_ex: continue - - # Find association key (profile or type) - profile_meta = data.get('meta', {}).get('profile', []) - found_profile_match = False - if profile_meta and isinstance(profile_meta, list): - for profile_url in profile_meta: - if profile_url and isinstance(profile_url, str): - # Try matching by ID derived from profile URL first - profile_id_from_meta = profile_url.split('/')[-1] - if profile_id_from_meta in resource_info: - associated_key = profile_id_from_meta - found_profile_match = True - break - # Fallback to matching by full profile URL if needed - elif profile_url in resource_info: - associated_key = profile_url - found_profile_match = True - break - # If no profile match, associate with base resource type - if not found_profile_match: - key_to_use = resource_type_ex - # Ensure the base type exists in resource_info - if key_to_use not in resource_info: - resource_info[key_to_use].update({'name': key_to_use, 'type': resource_type_ex, 'is_profile': False}) - associated_key = key_to_use - - referenced_types.add(resource_type_ex) # Track type even if example has profile - - else: # Guessing for non-JSON examples - guessed_type = basename_lower.split('-')[0].capitalize() - guessed_profile_id = basename_lower.split('-')[0] # Often filename starts with profile ID - key_to_use = None - if guessed_profile_id in resource_info: key_to_use = guessed_profile_id - elif guessed_type in resource_info: key_to_use = guessed_type - else: # Add base type if not seen - key_to_use = guessed_type - resource_info[key_to_use].update({'name': key_to_use, 'type': key_to_use, 'is_profile': False}) - associated_key = key_to_use - referenced_types.add(guessed_type) - - # Add example filename to the associated resource/profile - if associated_key: - resource_info[associated_key]['examples'].add(member.name) - # logger.debug(f"Associated example {member.name} with {associated_key}") - else: - logger.warning(f"Could not associate example {member.name} with any known resource or profile.") - - # --- CORRECTED INDENTATION FOR FINALLY BLOCK --- - except json.JSONDecodeError as e: logger.warning(f"Could not parse JSON example {member.name}: {e}") - except UnicodeDecodeError as e: logger.warning(f"Could not decode example {member.name}: {e}") - except tarfile.TarError as e: logger.warning(f"TarError reading example {member.name}: {e}") - except Exception as e: logger.warning(f"Could not process example member {member.name}: {e}", exc_info=False) - finally: - if fileobj: fileobj.close() - # --- End Pass 2 --- - - # --- Pass 3: Ensure Relevant Base Types --- - logger.debug("Pass 3: Ensuring relevant base types...") - essential_types = {'CapabilityStatement'} # Add any other types vital for display/logic - for type_name in referenced_types | essential_types: - # Check against a predefined list of valid FHIR types (FHIR_R4_BASE_TYPES) - if type_name in FHIR_R4_BASE_TYPES and type_name not in resource_info: - resource_info[type_name]['name'] = type_name - resource_info[type_name]['type'] = type_name - resource_info[type_name]['is_profile'] = False - logger.debug(f"Added base type entry for {type_name}") - # --- End Pass 3 --- - - # --- Final Consolidation --- - logger.debug(f"Finalizing results from {len(resource_info)} resource_info entries...") - final_list = [] - final_ms_elements = {} - final_examples = {} - for key, info in resource_info.items(): - display_name = info.get('name') or key - base_type = info.get('type') - # Skip entries missing essential info (should be rare now) - if not display_name or not base_type: - logger.warning(f"Skipping final format for incomplete key: {key} - Info: {info}") - continue - # Add to final list for UI display - final_list.append({ - 'name': display_name, - 'type': base_type, - 'is_profile': info.get('is_profile', False), - 'must_support': info.get('ms_flag', False), - 'optional_usage': info.get('optional_usage', False) - }) - # Add Must Support paths if present - if info['ms_paths']: - final_ms_elements[display_name] = sorted(list(info['ms_paths'])) - # Add Examples if present - if info['examples']: - final_examples[display_name] = sorted(list(info['examples'])) - - # Store final lists/dicts in results - results['resource_types_info'] = sorted(final_list, key=lambda x: (not x.get('is_profile', False), x.get('name', ''))) - results['must_support_elements'] = final_ms_elements - results['examples'] = final_examples - logger.debug(f"Final must_support_elements count: {len(final_ms_elements)}") - logger.debug(f"Final examples count: {len(final_examples)}") - # --- End Final Consolidation --- - - # Exception handling for opening/reading the tarfile itself - except tarfile.ReadError as e: err_msg = f"Tar ReadError processing package file {pkg_basename}: {e}"; logger.error(err_msg); results['errors'].append(err_msg) - except tarfile.TarError as e: err_msg = f"TarError processing package file {pkg_basename}: {e}"; logger.error(err_msg); results['errors'].append(err_msg) - except FileNotFoundError: err_msg = f"Package file not found during processing: {tgz_path}"; logger.error(err_msg); results['errors'].append(err_msg) - except Exception as e: err_msg = f"Unexpected error processing package file {pkg_basename}: {e}"; logger.error(err_msg, exc_info=True); results['errors'].append(err_msg) - - # --- Final Summary Logging --- - final_types_count = len(results['resource_types_info']) - ms_count = sum(1 for r in results['resource_types_info'] if r.get('must_support')) - optional_ms_count = sum(1 for r in results['resource_types_info'] if r.get('optional_usage')) - total_ms_paths = sum(len(v) for v in results['must_support_elements'].values()) - total_examples = sum(len(v) for v in results['examples'].values()) - total_conf_types = len(results['search_param_conformance']) - total_conf_params = sum(len(v) for v in results['search_param_conformance'].values()) - - logger.info(f"Package processing finished for {pkg_basename}: " - f"{final_types_count} Res/Profs; {ms_count} MS ({optional_ms_count} OptExt); {total_ms_paths} MS paths; " - f"{total_examples} Exs; Comp={len(results['complies_with_profiles'])}; Imp={len(results['imposed_profiles'])}; " - f"ConfParams={total_conf_params} for {total_conf_types} types; Errors={len(results['errors'])}") - - return results # Return the full results dictionary - - -# --- Validation Functions --- - -def _legacy_navigate_fhir_path(resource, path, extension_url=None): - """Navigates a FHIR resource using a FHIRPath-like expression, handling nested structures.""" - logger.debug(f"Navigating FHIR path: {path}") - if not resource or not path: - return None - parts = path.split('.') - current = resource - resource_type = resource.get('resourceType') - for i, part in enumerate(parts): - # Skip resource type prefix (e.g., Patient) - if i == 0 and part == resource_type: - continue - # Handle array indexing (e.g., name[0]) - match = re.match(r'^(\w+)\[(\d+)\]$', part) - if match: - key, index = match.groups() - index = int(index) - if isinstance(current, dict) and key in current: - if isinstance(current[key], list) and index < len(current[key]): - current = current[key][index] - else: - logger.debug(f"Path {part} invalid: key={key}, index={index}, current={current.get(key)}") - return None - elif isinstance(current, list) and index < len(current): - current = current[index] - else: - logger.debug(f"Path {part} not found in current={current}") - return None - else: - # Handle choice types (e.g., onset[x]) - if '[x]' in part: - part = part.replace('[x]', '') - # Try common choice type suffixes - for suffix in ['', 'DateTime', 'Age', 'Period', 'Range', 'String', 'CodeableConcept']: - test_key = part + suffix - if isinstance(current, dict) and test_key in current: - current = current[test_key] - break - else: - logger.debug(f"Choice type {part}[x] not found in current={current}") - return None - elif isinstance(current, dict): - if part in current: - current = current[part] - else: - # Handle FHIR complex types - if part == 'code' and 'coding' in current and isinstance(current['coding'], list) and current['coding']: - current = current['coding'] - elif part == 'patient' and 'reference' in current and current['reference']: - current = current['reference'] - elif part == 'manifestation' and isinstance(current, list) and current and 'coding' in current[0] and current[0]['coding']: - current = current[0]['coding'] - elif part == 'clinicalStatus' and 'coding' in current and isinstance(current['coding'], list) and current['coding']: - current = current['coding'] - else: - logger.debug(f"Path {part} not found in current={current}") - return None - elif isinstance(current, list) and len(current) > 0: - # Try to find the part in list items - found = False - for item in current: - if isinstance(item, dict) and part in item: - current = item[part] - found = True - break - if not found: - # For nested paths like communication.language, return None only if the parent is absent - logger.debug(f"Path {part} not found in list items: {current}") - return None - if extension_url and isinstance(current, list): - current = [item for item in current if item.get('url') == extension_url] - # Return non-None/non-empty values as present - result = current if (current is not None and (not isinstance(current, list) or current)) else None - logger.debug(f"Path {path} resolved to: {result}") - return result - -def navigate_fhir_path(resource, path, extension_url=None): - """Navigates a FHIR resource using FHIRPath expressions.""" - logger.debug(f"Navigating FHIR path: {path}, extension_url={extension_url}") - if not resource or not path: - return None - try: - # Adjust path for extension filtering - if extension_url and 'extension' in path: - path = f"{path}[url='{extension_url}']" - result = evaluate(resource, path) - # Return first result if list, None if empty - return result[0] if result else None - except Exception as e: - logger.error(f"FHIRPath evaluation failed for {path}: {e}") - # Fallback to legacy navigation for compatibility - return _legacy_navigate_fhir_path(resource, path, extension_url) - -def _legacy_validate_resource_against_profile(package_name, version, resource, include_dependencies=True): - """Validates a FHIR resource against a StructureDefinition in the specified package.""" - logger.debug(f"Validating resource {resource.get('resourceType')} against {package_name}#{version}, include_dependencies={include_dependencies}") - result = { - 'valid': True, - 'errors': [], - 'warnings': [], - 'details': [], # Enhanced info for future use - 'resource_type': resource.get('resourceType'), - 'resource_id': resource.get('id', 'unknown'), - 'profile': resource.get('meta', {}).get('profile', [None])[0] - } - download_dir = _get_download_dir() - if not download_dir: - result['valid'] = False - result['errors'].append("Could not access download directory") - result['details'].append({ - 'issue': "Could not access download directory", - 'severity': 'error', - 'description': "The server could not locate the directory where FHIR packages are stored." - }) - logger.error("Validation failed: Could not access download directory") - return result - - tgz_path = os.path.join(download_dir, construct_tgz_filename(package_name, version)) - logger.debug(f"Checking for package file: {tgz_path}") - if not os.path.exists(tgz_path): - result['valid'] = False - result['errors'].append(f"Package file not found: {package_name}#{version}") - result['details'].append({ - 'issue': f"Package file not found: {package_name}#{version}", - 'severity': 'error', - 'description': f"The package {package_name}#{version} is not available in the download directory." - }) - logger.error(f"Validation failed: Package file not found at {tgz_path}") - return result - - # Use profile from meta.profile if available - profile_url = None - meta = resource.get('meta', {}) - profiles = meta.get('profile', []) - if profiles: - profile_url = profiles[0] - logger.debug(f"Using profile from meta.profile: {profile_url}") - - # Find StructureDefinition - sd_data, sd_path = find_and_extract_sd(tgz_path, resource.get('resourceType'), profile_url) - if not sd_data and include_dependencies: - logger.debug(f"SD not found in {package_name}#{version}. Checking dependencies.") - try: - with tarfile.open(tgz_path, "r:gz") as tar: - package_json_member = None - for member in tar: - if member.name == 'package/package.json': - package_json_member = member - break - if package_json_member: - fileobj = tar.extractfile(package_json_member) - pkg_data = json.load(fileobj) - fileobj.close() - dependencies = pkg_data.get('dependencies', {}) - logger.debug(f"Found dependencies: {dependencies}") - for dep_name, dep_version in dependencies.items(): - dep_tgz = os.path.join(download_dir, construct_tgz_filename(dep_name, dep_version)) - if os.path.exists(dep_tgz): - logger.debug(f"Searching SD in dependency {dep_name}#{dep_version}") - sd_data, sd_path = find_and_extract_sd(dep_tgz, resource.get('resourceType'), profile_url) - if sd_data: - logger.info(f"Found SD in dependency {dep_name}#{dep_version} at {sd_path}") - break - else: - logger.warning(f"Dependency package {dep_name}#{dep_version} not found at {dep_tgz}") - else: - logger.warning(f"No package.json found in {tgz_path}") - except json.JSONDecodeError as e: - logger.error(f"Failed to parse package.json in {tgz_path}: {e}") - except tarfile.TarError as e: - logger.error(f"Failed to read {tgz_path} while checking dependencies: {e}") - except Exception as e: - logger.error(f"Unexpected error while checking dependencies in {tgz_path}: {e}") - - if not sd_data: - result['valid'] = False - result['errors'].append(f"No StructureDefinition found for {resource.get('resourceType')} with profile {profile_url or 'any'}") - result['details'].append({ - 'issue': f"No StructureDefinition found for {resource.get('resourceType')} with profile {profile_url or 'any'}", - 'severity': 'error', - 'description': f"The package {package_name}#{version} (and dependencies, if checked) does not contain a matching StructureDefinition." - }) - logger.error(f"Validation failed: No SD for {resource.get('resourceType')} in {tgz_path}") - return result - logger.debug(f"Found SD at {sd_path}") - - # Validate required elements (min=1) - errors = [] - warnings = set() # Deduplicate warnings - elements = sd_data.get('snapshot', {}).get('element', []) - for element in elements: - path = element.get('path') - min_val = element.get('min', 0) - must_support = element.get('mustSupport', False) - definition = element.get('definition', 'No definition provided in StructureDefinition.') - - # Check required elements - if min_val > 0 and not '.' in path[1 + path.find('.'):] if path.find('.') != -1 else True: - value = navigate_fhir_path(resource, path) - if value is None or (isinstance(value, list) and not any(value)): - error_msg = f"{resource.get('resourceType')}/{resource.get('id', 'unknown')}: Required element {path} missing" - errors.append(error_msg) - result['details'].append({ - 'issue': error_msg, - 'severity': 'error', - 'description': f"{definition} This element is mandatory (min={min_val}) per the profile {profile_url or 'unknown'}." - }) - logger.info(f"Validation error: Required element {path} missing") - - # Check must-support elements - if must_support and not '.' in path[1 + path.find('.'):] if path.find('.') != -1 else True: - if '[x]' in path: - base_path = path.replace('[x]', '') - found = False - for suffix in ['Quantity', 'CodeableConcept', 'String', 'DateTime', 'Period', 'Range']: - test_path = f"{base_path}{suffix}" - value = navigate_fhir_path(resource, test_path) - if value is not None and (not isinstance(value, list) or any(value)): - found = True - break - if not found: - warning_msg = f"{resource.get('resourceType')}/{resource.get('id', 'unknown')}: Must Support element {path} missing or empty" - warnings.add(warning_msg) - result['details'].append({ - 'issue': warning_msg, - 'severity': 'warning', - 'description': f"{definition} This element is marked as Must Support in AU Core, meaning it should be populated if the data is available (e.g., phone or email for Patient.telecom)." - }) - logger.info(f"Validation warning: Must Support element {path} missing or empty") - else: - value = navigate_fhir_path(resource, path) - if value is None or (isinstance(value, list) and not any(value)): - if element.get('min', 0) == 0: - warning_msg = f"{resource.get('resourceType')}/{resource.get('id', 'unknown')}: Must Support element {path} missing or empty" - warnings.add(warning_msg) - result['details'].append({ - 'issue': warning_msg, - 'severity': 'warning', - 'description': f"{definition} This element is marked as Must Support in AU Core, meaning it should be populated if the data is available (e.g., phone or email for Patient.telecom)." - }) - logger.info(f"Validation warning: Must Support element {path} missing or empty") - - # Handle dataAbsentReason for must-support elements - if path.endswith('dataAbsentReason') and must_support: - value_x_path = path.replace('dataAbsentReason', 'value[x]') - value_found = False - for suffix in ['Quantity', 'CodeableConcept', 'String', 'DateTime', 'Period', 'Range']: - test_path = path.replace('dataAbsentReason', f'value{suffix}') - value = navigate_fhir_path(resource, test_path) - if value is not None and (not isinstance(value, list) or any(value)): - value_found = True - break - if not value_found: - value = navigate_fhir_path(resource, path) - if value is None or (isinstance(value, list) and not any(value)): - warning_msg = f"{resource.get('resourceType')}/{resource.get('id', 'unknown')}: Must Support element {path} missing or empty" - warnings.add(warning_msg) - result['details'].append({ - 'issue': warning_msg, - 'severity': 'warning', - 'description': f"{definition} This element is marked as Must Support and should be used to indicate why the associated value is absent." - }) - logger.info(f"Validation warning: Must Support element {path} missing or empty") - - result['errors'] = errors - result['warnings'] = list(warnings) - result['valid'] = len(errors) == 0 - result['summary'] = { - 'error_count': len(errors), - 'warning_count': len(warnings) - } - logger.debug(f"Validation result: valid={result['valid']}, errors={len(result['errors'])}, warnings={len(result['warnings'])}") - return result -# -- OLD -# def validate_resource_against_profile(package_name, version, resource, include_dependencies=True): -# result = { -# 'valid': True, -# 'errors': [], -# 'warnings': [], -# 'details': [], -# 'resource_type': resource.get('resourceType'), -# 'resource_id': resource.get('id', 'unknown'), -# 'profile': resource.get('meta', {}).get('profile', [None])[0] -# } - -# # Attempt HAPI validation if a profile is specified -# if result['profile']: -# try: -# hapi_url = f"{current_app.config['HAPI_FHIR_URL'].rstrip('/')}/{resource['resourceType']}/$validate?profile={result['profile']}" -# response = requests.post( -# hapi_url, -# json=resource, -# headers={'Content-Type': 'application/fhir+json', 'Accept': 'application/fhir+json'}, -# timeout=10 -# ) -# response.raise_for_status() -# outcome = response.json() -# if outcome.get('resourceType') == 'OperationOutcome': -# for issue in outcome.get('issue', []): -# severity = issue.get('severity') -# diagnostics = issue.get('diagnostics', issue.get('details', {}).get('text', 'No details provided')) -# detail = { -# 'issue': diagnostics, -# 'severity': severity, -# 'description': issue.get('details', {}).get('text', diagnostics) -# } -# if severity in ['error', 'fatal']: -# result['valid'] = False -# result['errors'].append(diagnostics) -# elif severity == 'warning': -# result['warnings'].append(diagnostics) -# result['details'].append(detail) -# result['summary'] = { -# 'error_count': len(result['errors']), -# 'warning_count': len(result['warnings']) -# } -# logger.debug(f"HAPI validation for {result['resource_type']}/{result['resource_id']}: valid={result['valid']}, errors={len(result['errors'])}, warnings={len(result['warnings'])}") -# return result -# else: -# logger.warning(f"HAPI returned non-OperationOutcome: {outcome.get('resourceType')}") -# except requests.RequestException as e: -# logger.error(f"HAPI validation failed for {result['resource_type']}/{result['resource_id']}: {e}") -# result['details'].append({ -# 'issue': f"HAPI validation failed: {str(e)}", -# 'severity': 'warning', -# 'description': 'Falling back to local validation due to HAPI server error.' -# }) - -# # Fallback to local validation -# download_dir = _get_download_dir() -# if not download_dir: -# result['valid'] = False -# result['errors'].append("Could not access download directory") -# result['details'].append({ -# 'issue': "Could not access download directory", -# 'severity': 'error', -# 'description': "The server could not locate the directory where FHIR packages are stored." -# }) -# return result - -# tgz_path = os.path.join(download_dir, construct_tgz_filename(package_name, version)) -# sd_data, sd_path = find_and_extract_sd(tgz_path, resource.get('resourceType'), result['profile']) -# if not sd_data: -# result['valid'] = False -# result['errors'].append(f"No StructureDefinition found for {resource.get('resourceType')}") -# result['details'].append({ -# 'issue': f"No StructureDefinition found for {resource.get('resourceType')}", -# 'severity': 'error', -# 'description': f"The package {package_name}#{version} does not contain a matching StructureDefinition." -# }) -# return result - -# elements = sd_data.get('snapshot', {}).get('element', []) -# for element in elements: -# path = element.get('path') -# min_val = element.get('min', 0) -# must_support = element.get('mustSupport', False) -# slicing = element.get('slicing') -# slice_name = element.get('sliceName') - -# # Check required elements -# if min_val > 0: -# value = navigate_fhir_path(resource, path) -# if value is None or (isinstance(value, list) and not any(value)): -# result['valid'] = False -# result['errors'].append(f"Required element {path} missing") -# result['details'].append({ -# 'issue': f"Required element {path} missing", -# 'severity': 'error', -# 'description': f"Element {path} has min={min_val} in profile {result['profile'] or 'unknown'}" -# }) - -# # Check must-support elements -# if must_support: -# value = navigate_fhir_path(resource, slice_name if slice_name else path) -# if value is None or (isinstance(value, list) and not any(value)): -# result['warnings'].append(f"Must Support element {path} missing or empty") -# result['details'].append({ -# 'issue': f"Must Support element {path} missing or empty", -# 'severity': 'warning', -# 'description': f"Element {path} is marked as Must Support in profile {result['profile'] or 'unknown'}" -# }) - -# # Validate slicing -# if slicing and not slice_name: # Parent slicing element -# discriminator = slicing.get('discriminator', []) -# for d in discriminator: -# d_type = d.get('type') -# d_path = d.get('path') -# if d_type == 'value': -# sliced_elements = navigate_fhir_path(resource, path) -# if isinstance(sliced_elements, list): -# seen_values = set() -# for elem in sliced_elements: -# d_value = navigate_fhir_path(elem, d_path) -# if d_value in seen_values: -# result['valid'] = False -# result['errors'].append(f"Duplicate discriminator value {d_value} for {path}.{d_path}") -# seen_values.add(d_value) -# elif d_type == 'type': -# sliced_elements = navigate_fhir_path(resource, path) -# if isinstance(sliced_elements, list): -# for elem in sliced_elements: -# if not navigate_fhir_path(elem, d_path): -# result['valid'] = False -# result['errors'].append(f"Missing discriminator type {d_path} for {path}") - -# result['summary'] = { -# 'error_count': len(result['errors']), -# 'warning_count': len(result['warnings']) -# } -# return result - -# def validate_bundle_against_profile(package_name, version, bundle, include_dependencies=True): -# """Validates a FHIR Bundle against profiles in the specified package.""" -# logger.debug(f"Validating bundle against {package_name}#{version}, include_dependencies={include_dependencies}") -# result = { -# 'valid': True, -# 'errors': [], -# 'warnings': [], -# 'details': [], -# 'results': {}, -# 'summary': { -# 'resource_count': 0, -# 'failed_resources': 0, -# 'profiles_validated': set() -# } -# } -# if not bundle.get('resourceType') == 'Bundle': -# result['valid'] = False -# result['errors'].append("Resource is not a Bundle") -# result['details'].append({ -# 'issue': "Resource is not a Bundle", -# 'severity': 'error', -# 'description': "The provided resource must have resourceType 'Bundle' to be validated as a bundle." -# }) -# logger.error("Validation failed: Resource is not a Bundle") -# return result - -# # Track references to validate resolvability -# references = set() -# resolved_references = set() - -# for entry in bundle.get('entry', []): -# resource = entry.get('resource') -# if not resource: -# continue -# resource_type = resource.get('resourceType') -# resource_id = resource.get('id', 'unknown') -# result['summary']['resource_count'] += 1 - -# # Collect references -# for key, value in resource.items(): -# if isinstance(value, dict) and 'reference' in value: -# references.add(value['reference']) -# elif isinstance(value, list): -# for item in value: -# if isinstance(item, dict) and 'reference' in item: -# references.add(item['reference']) - -# # Validate resource -# validation_result = validate_resource_against_profile(package_name, version, resource, include_dependencies) -# result['results'][f"{resource_type}/{resource_id}"] = validation_result -# result['summary']['profiles_validated'].add(validation_result['profile'] or 'unknown') - -# # Aggregate errors and warnings -# if not validation_result['valid']: -# result['valid'] = False -# result['summary']['failed_resources'] += 1 -# result['errors'].extend(validation_result['errors']) -# result['warnings'].extend(validation_result['warnings']) -# result['details'].extend(validation_result['details']) - -# # Mark resource as resolved if it has an ID -# if resource_id != 'unknown': -# resolved_references.add(f"{resource_type}/{resource_id}") - -# # Check for unresolved references -# unresolved = references - resolved_references -# for ref in unresolved: -# warning_msg = f"Unresolved reference: {ref}" -# result['warnings'].append(warning_msg) -# result['details'].append({ -# 'issue': warning_msg, -# 'severity': 'warning', -# 'description': f"The reference {ref} points to a resource not included in the bundle. Ensure the referenced resource is present or resolvable." -# }) -# logger.info(f"Validation warning: Unresolved reference {ref}") - -# # Finalize summary -# result['summary']['profiles_validated'] = list(result['summary']['profiles_validated']) -# result['summary']['error_count'] = len(result['errors']) -# result['summary']['warning_count'] = len(result['warnings']) -# logger.debug(f"Bundle validation result: valid={result['valid']}, errors={result['summary']['error_count']}, warnings={result['summary']['warning_count']}, resources={result['summary']['resource_count']}") -# return result -# -- OLD - - -# --- UPDATED: validate_resource_against_profile function --- -def validate_resource_against_profile(package_name, version, resource, include_dependencies=True): - """ - Validates a FHIR resource against a StructureDefinition in the specified package. - - This version correctly handles the absence of a `meta.profile` by falling back - to the base resource definition. It also sanitizes profile URLs to avoid - version mismatch errors. - """ - result = { - 'valid': True, - 'errors': [], - 'warnings': [], - 'details': [], - 'resource_type': resource.get('resourceType'), - 'resource_id': resource.get('id', 'unknown'), - 'profile': resource.get('meta', {}).get('profile', [None])[0] - } - - download_dir = _get_download_dir() - if not download_dir: - result['valid'] = False - result['errors'].append("Could not access download directory") - result['details'].append({ - 'issue': "Could not access download directory", - 'severity': 'error', - 'description': "The server could not locate the directory where FHIR packages are stored." - }) - logger.error("Validation failed: Could not access download directory") - return result - - # --- Work Item 3 & 2: Get profile URL or fallback to resourceType --- - profile_url = result['profile'] - resource_identifier = resource.get('resourceType') - - if profile_url: - # Sanitize profile URL to remove version - clean_profile_url = profile_url.split('|')[0] - logger.debug(f"Using provided profile: {profile_url}. Cleaned to: {clean_profile_url}") - resource_identifier = profile_url - else: - # No profile provided, fallback to resource type - logger.debug(f"No profile in resource, using base type as identifier: {resource_identifier}") - clean_profile_url = None - - tgz_path = os.path.join(download_dir, construct_tgz_filename(package_name, version)) - logger.debug(f"Checking for package file: {tgz_path}") - - # Find StructureDefinition - sd_data, sd_path = find_and_extract_sd(tgz_path, resource_identifier, clean_profile_url) - - if not sd_data and include_dependencies: - logger.debug(f"SD not found in {package_name}#{version}. Checking dependencies.") - try: - with tarfile.open(tgz_path, "r:gz") as tar: - package_json_member = None - for member in tar: - if member.name == 'package/package.json': - package_json_member = member - break - if package_json_member: - fileobj = tar.extractfile(package_json_member) - pkg_data = json.load(fileobj) - fileobj.close() - dependencies = pkg_data.get('dependencies', {}) - logger.debug(f"Found dependencies: {dependencies}") - for dep_name, dep_version in dependencies.items(): - dep_tgz = os.path.join(download_dir, construct_tgz_filename(dep_name, dep_version)) - if os.path.exists(dep_tgz): - logger.debug(f"Searching SD in dependency {dep_name}#{dep_version}") - sd_data, sd_path = find_and_extract_sd(dep_tgz, resource_identifier, clean_profile_url) - if sd_data: - logger.info(f"Found SD in dependency {dep_name}#{dep_version} at {sd_path}") - break - else: - logger.warning(f"Dependency package {dep_name}#{dep_version} not found at {dep_tgz}") - else: - logger.warning(f"No package.json found in {tgz_path}") - except json.JSONDecodeError as e: - logger.error(f"Failed to parse package.json in {tgz_path}: {e}") - except tarfile.TarError as e: - logger.error(f"Failed to read {tgz_path} while checking dependencies: {e}") - except Exception as e: - logger.error(f"Unexpected error while checking dependencies in {tgz_path}: {e}") - - if not sd_data: - result['valid'] = False - result['errors'].append(f"No StructureDefinition found for {resource_identifier} with profile {clean_profile_url or 'any'}") - result['details'].append({ - 'issue': f"No StructureDefinition found for {resource_identifier} with profile {clean_profile_url or 'any'}", - 'severity': 'error', - 'description': f"The package {package_name}#{version} (and dependencies, if checked) does not contain a matching StructureDefinition." - }) - logger.error(f"Validation failed: No SD for {resource_identifier} in {tgz_path}") - return result - logger.debug(f"Found SD at {sd_path}") - - # Validate required elements (min=1) - errors = [] - warnings = set() - elements = sd_data.get('snapshot', {}).get('element', []) - for element in elements: - path = element.get('path') - min_val = element.get('min', 0) - must_support = element.get('mustSupport', False) - definition = element.get('definition', 'No definition provided in StructureDefinition.') - - # Check required elements - if min_val > 0 and not '.' in path[1 + path.find('.'):] if path.find('.') != -1 else True: - value = navigate_fhir_path(resource, path) - if value is None or (isinstance(value, list) and not any(value)): - error_msg = f"{resource.get('resourceType')}/{resource.get('id', 'unknown')}: Required element {path} missing" - errors.append(error_msg) - result['details'].append({ - 'issue': error_msg, - 'severity': 'error', - 'description': f"{definition} This element is mandatory (min={min_val}) per the profile {profile_url or 'unknown'}." - }) - logger.info(f"Validation error: Required element {path} missing") - - # Check must-support elements - if must_support and not '.' in path[1 + path.find('.'):] if path.find('.') != -1 else True: - if '[x]' in path: - base_path = path.replace('[x]', '') - found = False - for suffix in ['Quantity', 'CodeableConcept', 'String', 'DateTime', 'Period', 'Range']: - test_path = f"{base_path}{suffix}" - value = navigate_fhir_path(resource, test_path) - if value is not None and (not isinstance(value, list) or any(value)): - found = True - break - if not found: - warning_msg = f"{resource.get('resourceType')}/{resource.get('id', 'unknown')}: Must Support element {path} missing or empty" - warnings.add(warning_msg) - result['details'].append({ - 'issue': warning_msg, - 'severity': 'warning', - 'description': f"{definition} This element is marked as Must Support in AU Core, meaning it should be populated if the data is available (e.g., phone or email for Patient.telecom)." - }) - logger.info(f"Validation warning: Must Support element {path} missing or empty") - else: - value = navigate_fhir_path(resource, path) - if value is None or (isinstance(value, list) and not any(value)): - if element.get('min', 0) == 0: - warning_msg = f"{resource.get('resourceType')}/{resource.get('id', 'unknown')}: Must Support element {path} missing or empty" - warnings.add(warning_msg) - result['details'].append({ - 'issue': warning_msg, - 'severity': 'warning', - 'description': f"{definition} This element is marked as Must Support in AU Core, meaning it should be populated if the data is available (e.g., phone or email for Patient.telecom)." - }) - logger.info(f"Validation warning: Must Support element {path} missing or empty") - - # Handle dataAbsentReason for must-support elements - if path.endswith('dataAbsentReason') and must_support: - value_x_path = path.replace('dataAbsentReason', 'value[x]') - value_found = False - for suffix in ['Quantity', 'CodeableConcept', 'String', 'DateTime', 'Period', 'Range']: - test_path = path.replace('dataAbsentReason', f'value{suffix}') - value = navigate_fhir_path(resource, test_path) - if value is not None and (not isinstance(value, list) or any(value)): - value_found = True - break - if not value_found: - value = navigate_fhir_path(resource, path) - if value is None or (isinstance(value, list) and not any(value)): - warning_msg = f"{resource.get('resourceType')}/{resource.get('id', 'unknown')}: Must Support element {path} missing or empty" - warnings.add(warning_msg) - result['details'].append({ - 'issue': warning_msg, - 'severity': 'warning', - 'description': f"{definition} This element is marked as Must Support and should be used to indicate why the associated value is absent." - }) - logger.info(f"Validation warning: Must Support element {path} missing or empty") - - result['errors'] = errors - result['warnings'] = list(warnings) - result['valid'] = len(errors) == 0 - result['summary'] = { - 'error_count': len(errors), - 'warning_count': len(warnings) - } - logger.debug(f"Validation result: valid={result['valid']}, errors={len(result['errors'])}, warnings={len(result['warnings'])}") - return result - -# --- UPDATED: validate_bundle_against_profile function --- -def validate_bundle_against_profile(package_name, version, bundle, include_dependencies=True): - """ - Validates a FHIR Bundle against profiles in the specified package. - - This version adds a new two-pass process to correctly resolve `urn:uuid` - references within the bundle before flagging them as unresolved. - """ - logger.debug(f"Validating bundle against {package_name}#{version}, include_dependencies={include_dependencies}") - result = { - 'valid': True, - 'errors': [], - 'warnings': [], - 'details': [], - 'results': {}, - 'summary': { - 'resource_count': 0, - 'failed_resources': 0, - 'profiles_validated': set() - } - } - if not bundle.get('resourceType') == 'Bundle': - result['valid'] = False - result['errors'].append("Resource is not a Bundle") - result['details'].append({ - 'issue': "Resource is not a Bundle", - 'severity': 'error', - 'description': "The provided resource must have resourceType 'Bundle' to be validated as a bundle." - }) - logger.error("Validation failed: Resource is not a Bundle") - return result - - # --- Work Item 1: First pass to collect all local references --- - local_references = set() - for entry in bundle.get('entry', []): - fullUrl = entry.get('fullUrl') - resource = entry.get('resource') - if fullUrl: - local_references.add(fullUrl) - if resource and resource.get('resourceType') and resource.get('id'): - local_references.add(f"{resource['resourceType']}/{resource['id']}") - logger.debug(f"Found {len(local_references)} local references in the bundle.") - - # Track references and resolved references for external check - all_references_found = set() - - # Second pass for validation and reference checking - for entry in bundle.get('entry', []): - resource = entry.get('resource') - if not resource: - continue - resource_type = resource.get('resourceType') - resource_id = resource.get('id', 'unknown') - result['summary']['resource_count'] += 1 - - # Collect references - current_refs = [] - find_references(resource, current_refs) - for ref_str in current_refs: - if isinstance(ref_str, str): - all_references_found.add(ref_str) - - # Validate resource - validation_result = validate_resource_against_profile(package_name, version, resource, include_dependencies) - result['results'][f"{resource_type}/{resource_id}"] = validation_result - result['summary']['profiles_validated'].add(validation_result['profile'] or 'unknown') - - # Aggregate errors and warnings - if not validation_result['valid']: - result['valid'] = False - result['summary']['failed_resources'] += 1 - result['errors'].extend(validation_result['errors']) - result['warnings'].extend(validation_result['warnings']) - result['details'].extend(validation_result['details']) - - # --- Work Item 1: Check for unresolved references *after* processing all local resources --- - for ref in all_references_found: - if ref not in local_references: - warning_msg = f"Unresolved reference: {ref}" - result['warnings'].append(warning_msg) - result['details'].append({ - 'issue': warning_msg, - 'severity': 'warning', - 'description': f"The reference {ref} points to a resource not included in the bundle. Ensure the referenced resource is present or resolvable." - }) - logger.info(f"Validation warning: Unresolved reference {ref}") - - # Finalize summary - result['summary']['profiles_validated'] = list(result['summary']['profiles_validated']) - result['summary']['error_count'] = len(result['errors']) - result['summary']['warning_count'] = len(result['warnings']) - logger.debug(f"Bundle validation result: valid={result['valid']}, errors={result['summary']['error_count']}, warnings={result['summary']['warning_count']}, resources={result['summary']['resource_count']}") - return result - - -# --- Structure Definition Retrieval --- -def get_structure_definition(package_name, version, resource_type): - """Fetches StructureDefinition with slicing support.""" - download_dir = _get_download_dir() - if not download_dir: - logger.error("Could not get download directory.") - return {'error': 'Download directory not accessible'} - - tgz_filename = construct_tgz_filename(package_name, version) - tgz_path = os.path.join(download_dir, tgz_filename) - sd_data, sd_path = find_and_extract_sd(tgz_path, resource_type) - - if not sd_data: - # Fallback to canonical package - canonical_tgz = construct_tgz_filename(*CANONICAL_PACKAGE) - canonical_path = os.path.join(download_dir, canonical_tgz) - sd_data, sd_path = find_and_extract_sd(canonical_path, resource_type) - if sd_data: - logger.info(f"Using canonical SD for {resource_type} from {canonical_path}") - elements = sd_data.get('snapshot', {}).get('element', []) - return { - 'elements': elements, - 'must_support_paths': [el['path'] for el in elements if el.get('mustSupport', False)], - 'slices': [], - 'fallback_used': True, - 'source_package': f"{CANONICAL_PACKAGE[0]}#{CANONICAL_PACKAGE[1]}" - } - logger.error(f"No StructureDefinition found for {resource_type} in {package_name}#{version} or canonical package") - return {'error': f"No StructureDefinition for {resource_type}"} - - elements = sd_data.get('snapshot', {}).get('element', []) - must_support_paths = [] - slices = [] - - # Process elements for must-support and slicing - for element in elements: - path = element.get('path', '') - element_id = element.get('id', '') - slice_name = element.get('sliceName') - if element.get('mustSupport', False): - ms_path = f"{path}[sliceName='{slice_name}']" if slice_name else element_id - must_support_paths.append(ms_path) - if 'slicing' in element: - slice_info = { - 'path': path, - 'sliceName': slice_name, - 'discriminator': element.get('slicing', {}).get('discriminator', []), - 'nested_slices': [] - } - # Find nested slices - for sub_element in elements: - if sub_element['path'].startswith(path + '.') and 'slicing' in sub_element: - sub_slice_name = sub_element.get('sliceName') - slice_info['nested_slices'].append({ - 'path': sub_element['path'], - 'sliceName': sub_slice_name, - 'discriminator': sub_element.get('slicing', {}).get('discriminator', []) - }) - slices.append(slice_info) - - logger.debug(f"StructureDefinition for {resource_type}: {len(elements)} elements, {len(must_support_paths)} must-support paths, {len(slices)} slices") - return { - 'elements': elements, - 'must_support_paths': sorted(list(set(must_support_paths))), - 'slices': slices, - 'fallback_used': False - } - -# --- Other Service Functions --- -def _build_package_index(download_dir): - """Builds an index of canonical URLs to package details from .index.json files.""" - index = {} - try: - for tgz_file in os.listdir(download_dir): - if not tgz_file.endswith('.tgz'): - continue - tgz_path = os.path.join(download_dir, tgz_file) - try: - with tarfile.open(tgz_path, "r:gz") as tar: - index_file = next((m for m in tar.getmembers() if m.name == 'package/.index.json'), None) - if index_file: - fileobj = tar.extractfile(index_file) - if fileobj: - content = json.loads(fileobj.read().decode('utf-8-sig')) - package_name = content.get('package-id', '') - package_version = content.get('version', '') - for file_entry in content.get('files', []): - canonical = file_entry.get('canonical') - filename = file_entry.get('filename') - if canonical and filename: - index[canonical] = { - 'package_name': package_name, - 'package_version': package_version, - 'filename': filename - } - fileobj.close() - except Exception as e: - logger.warning(f"Failed to index {tgz_file}: {e}") - except Exception as e: - logger.error(f"Error building package index: {e}") - return index - -def _find_definition_details(url, download_dir): - """Finds package details for a canonical URL.""" - index = current_app.config.get('PACKAGE_INDEX') - if index is None: - index = _build_package_index(download_dir) - current_app.config['PACKAGE_INDEX'] = index - return index.get(url) - -def _load_definition(details, download_dir): - """Loads a StructureDefinition from package details.""" - if not details: - return None - tgz_path = os.path.join(download_dir, construct_tgz_filename(details['package_name'], details['package_version'])) - try: - with tarfile.open(tgz_path, "r:gz") as tar: - member_path = f"package/{details['filename']}" - member = next((m for m in tar.getmembers() if m.name == member_path), None) - if member: - fileobj = tar.extractfile(member) - if fileobj: - data = json.loads(fileobj.read().decode('utf-8-sig')) - fileobj.close() - return data - except Exception as e: - logger.error(f"Failed to load definition {details['filename']} from {tgz_path}: {e}") - return None - -# def download_package(name, version): -# """Downloads a single FHIR package.""" -# download_dir = _get_download_dir() -# if not download_dir: return None, "Download dir error" -# filename = construct_tgz_filename(name, version) -# if not filename: return None, "Filename construction error" -# save_path = os.path.join(download_dir, filename) -# if os.path.exists(save_path): -# logger.info(f"Package already exists: {save_path}") -# return save_path, None -# package_url = f"{FHIR_REGISTRY_BASE_URL}/{name}/{version}" -# try: -# with requests.get(package_url, stream=True, timeout=60) as r: -# r.raise_for_status() -# with open(save_path, 'wb') as f: -# for chunk in r.iter_content(chunk_size=8192): f.write(chunk) -# logger.info(f"Downloaded {filename}") -# return save_path, None -# except requests.exceptions.RequestException as e: -# logger.error(f"Download failed for {name}#{version}: {e}") -# return None, f"Download error: {e}" -# except IOError as e: -# logger.error(f"File write error for {save_path}: {e}") -# return None, f"File write error: {e}" - -def download_package(name, version, dependency_mode='none'): - """Downloads a FHIR package by name and version to the configured directory.""" - download_dir = _get_download_dir() - if not download_dir: - return None, ["Could not determine download directory"] - tgz_filename = construct_tgz_filename(name, version) - if not tgz_filename: - return None, [f"Could not construct filename for {name}#{version}"] - download_path = os.path.join(download_dir, tgz_filename) - errors = [] - - # Check if already downloaded - if os.path.exists(download_path): - logger.info(f"Package {name}#{version} already downloaded at {download_path}") - return download_path, [] - - # Primary download URL - primary_url = f"{FHIR_REGISTRY_BASE_URL}/{name}/{version}" - logger.info(f"Attempting download of {name}#{version} from {primary_url}") - - try: - response = requests.get(primary_url, timeout=30) - response.raise_for_status() - with open(download_path, 'wb') as f: - f.write(response.content) - logger.info(f"Successfully downloaded {name}#{version} to {download_path}") - save_package_metadata(name, version, dependency_mode, []) - return download_path, [] - except requests.exceptions.HTTPError as e: - if e.response.status_code == 404: - logger.warning(f"Primary download failed (404) for {name}#{version} at {primary_url}. Attempting fallback URL.") - else: - error_msg = f"Download error for {name}#{version}: {str(e)}" - logger.error(error_msg, exc_info=True) - errors.append(error_msg) - return None, errors - except requests.exceptions.RequestException as e: - error_msg = f"Download error for {name}#{version}: {str(e)}" - logger.error(error_msg, exc_info=True) - errors.append(error_msg) - return None, errors - except Exception as e: - error_msg = f"Unexpected error downloading {name}#{version}: {str(e)}" - logger.error(error_msg, exc_info=True) - errors.append(error_msg) - return None, errors - - # Fallback: Try the package's URL from the normalized package data - if errors and "404" in errors[0]: - logger.info(f"Looking up alternative download URL for {name}#{version}") - try: - # Access the in-memory cache from the Flask app config - normalized_packages = current_app.config.get('MANUAL_PACKAGE_CACHE', []) - package_data = next((pkg for pkg in normalized_packages if pkg.get('name') == name), None) - if not package_data: - error_msg = f"Package {name} not found in cache for fallback download." - logger.error(error_msg) - errors.append(error_msg) - return None, errors - - package_url = package_data.get('url') - if not package_url: - error_msg = f"No alternative URL found for {name}#{version}." - logger.error(error_msg) - errors.append(error_msg) - return None, errors - - # Construct a download URL using the package's URL - # Assuming the URL is a base (e.g., https://packages.simplifier.net/fhir.ieb.core) - # and we append the version to form the download URL - # This may need adjustment based on the actual format of 'url' - fallback_url = f"{package_url.rstrip('/')}/{version}.tgz" - logger.info(f"Attempting fallback download of {name}#{version} from {fallback_url}") - - response = requests.get(fallback_url, timeout=30) - response.raise_for_status() - with open(download_path, 'wb') as f: - f.write(response.content) - logger.info(f"Successfully downloaded {name}#{version} using fallback URL to {download_path}") - save_package_metadata(name, version, dependency_mode, []) - return download_path, [] - except requests.exceptions.HTTPError as e: - error_msg = f"Fallback download error for {name}#{version} at {fallback_url}: {str(e)}" - logger.error(error_msg, exc_info=True) - errors.append(error_msg) - return None, errors - except requests.exceptions.RequestException as e: - error_msg = f"Fallback download network error for {name}#{version}: {str(e)}" - logger.error(error_msg, exc_info=True) - errors.append(error_msg) - return None, errors - except Exception as e: - error_msg = f"Unexpected error during fallback download of {name}#{version}: {str(e)}" - logger.error(error_msg, exc_info=True) - errors.append(error_msg) - return None, errors - - return None, errors - -def extract_dependencies(tgz_path): - """Extracts dependencies from package.json.""" - package_json_path = "package/package.json" - dependencies = {} - error_message = None - if not tgz_path or not os.path.exists(tgz_path): return None, "File not found" - try: - with tarfile.open(tgz_path, "r:gz") as tar: - try: - pkg_member = tar.getmember(package_json_path) - with tar.extractfile(pkg_member) as f: - pkg_data = json.load(f) - dependencies = pkg_data.get('dependencies', {}) - except KeyError: error_message = "package.json not found" - except (json.JSONDecodeError, tarfile.TarError) as e: error_message = f"Error reading package.json: {e}" - except tarfile.TarError as e: error_message = f"Error opening tarfile: {e}" - except Exception as e: error_message = f"Unexpected error: {e}" - return dependencies, error_message - -def extract_used_types(tgz_path): - """Extracts all resource types and referenced types from the package resources.""" - used_types = set() - if not tgz_path or not os.path.exists(tgz_path): - logger.error(f"Cannot extract used types: File not found at {tgz_path}") - return used_types - try: - with tarfile.open(tgz_path, "r:gz") as tar: - for member in tar: - if not (member.isfile() and member.name.startswith('package/') and member.name.lower().endswith('.json')): - continue - if os.path.basename(member.name).lower() in ['package.json', '.index.json', 'validation-summary.json', 'validation-oo.json']: - continue - fileobj = None - try: - fileobj = tar.extractfile(member) - if fileobj: - content_bytes = fileobj.read() - content_string = content_bytes.decode('utf-8-sig') - data = json.loads(content_string) - if not isinstance(data, dict): continue - resource_type = data.get('resourceType') - if not resource_type: continue - used_types.add(resource_type) - if resource_type == 'StructureDefinition': - sd_type = data.get('type') - if sd_type: used_types.add(sd_type) - base_def = data.get('baseDefinition') - if base_def: - base_type = base_def.split('/')[-1] - if base_type and base_type[0].isupper(): used_types.add(base_type) - elements = data.get('snapshot', {}).get('element', []) or data.get('differential', {}).get('element', []) - for element in elements: - if isinstance(element, dict) and 'type' in element: - for t in element.get('type', []): - code = t.get('code') - if code and code[0].isupper(): used_types.add(code) - for profile_uri in t.get('targetProfile', []): - if profile_uri: - profile_type = profile_uri.split('/')[-1] - if profile_type and profile_type[0].isupper(): used_types.add(profile_type) - else: - profiles = data.get('meta', {}).get('profile', []) - for profile_uri in profiles: - if profile_uri: - profile_type = profile_uri.split('/')[-1] - if profile_type and profile_type[0].isupper(): used_types.add(profile_type) - if resource_type == 'ValueSet': - for include in data.get('compose', {}).get('include', []): - system = include.get('system') - if system and system.startswith('http://hl7.org/fhir/'): - type_name = system.split('/')[-1] - if type_name and type_name[0].isupper() and not type_name.startswith('sid'): - used_types.add(type_name) - if resource_type == 'CapabilityStatement': - for rest_item in data.get('rest', []): - for resource_item in rest_item.get('resource', []): - res_type = resource_item.get('type') - if res_type and res_type[0].isupper(): used_types.add(res_type) - profile_uri = resource_item.get('profile') - if profile_uri: - profile_type = profile_uri.split('/')[-1] - if profile_type and profile_type[0].isupper(): used_types.add(profile_type) - except json.JSONDecodeError as e: - logger.warning(f"Could not parse JSON in {member.name}: {e}") - except UnicodeDecodeError as e: - logger.warning(f"Could not decode {member.name}: {e}") - except Exception as e: - logger.warning(f"Could not process member {member.name}: {e}") - finally: - if fileobj: - fileobj.close() - except tarfile.ReadError as e: - logger.error(f"Tar ReadError extracting used types from {tgz_path}: {e}") - except tarfile.TarError as e: - logger.error(f"TarError extracting used types from {tgz_path}: {e}") - except FileNotFoundError: - logger.error(f"Package file not found: {tgz_path}") - except Exception as e: - logger.error(f"Error extracting used types from {tgz_path}: {e}", exc_info=True) - core_non_resource_types = { - 'string', 'boolean', 'integer', 'decimal', 'uri', 'url', 'canonical', 'base64Binary', 'instant', - 'date', 'dateTime', 'time', 'code', 'oid', 'id', 'markdown', 'unsignedInt', 'positiveInt', 'xhtml', - 'Element', 'BackboneElement', 'Resource', 'DomainResource', 'DataType' - } - final_used_types = {t for t in used_types if t not in core_non_resource_types and t[0].isupper()} - logger.debug(f"Extracted used types from {os.path.basename(tgz_path)}: {final_used_types}") - return final_used_types - -def map_types_to_packages(used_types, all_dependencies, download_dir): - """Maps used types to packages by checking .index.json files.""" - type_to_package = {} - processed_types = set() - for (pkg_name, pkg_version), _ in all_dependencies.items(): - tgz_filename = construct_tgz_filename(pkg_name, pkg_version) - tgz_path = os.path.join(download_dir, tgz_filename) - if not os.path.exists(tgz_path): - logger.warning(f"Package {tgz_filename} not found for type mapping") - continue - try: - with tarfile.open(tgz_path, "r:gz") as tar: - index_file = next((m for m in tar.getmembers() if m.name == 'package/.index.json'), None) - if index_file: - fileobj = tar.extractfile(index_file) - if fileobj: - content = json.loads(fileobj.read().decode('utf-8-sig')) - for file_entry in content.get('files', []): - resource_type = file_entry.get('resourceType') - filename = file_entry.get('filename') - if resource_type == 'StructureDefinition' and filename.endswith('.json'): - sd_name = os.path.splitext(os.path.basename(filename))[0] - if sd_name in used_types: - type_to_package[sd_name] = (pkg_name, pkg_version) - processed_types.add(sd_name) - logger.debug(f"Mapped type '{sd_name}' to package '{pkg_name}#{pkg_version}'") - except Exception as e: - logger.warning(f"Failed to process .index.json for {pkg_name}#{pkg_version}: {e}") - for t in used_types - processed_types: - for (pkg_name, pkg_version), _ in all_dependencies.items(): - if t.lower() in pkg_name.lower(): - type_to_package[t] = (pkg_name, pkg_version) - processed_types.add(t) - logger.debug(f"Fallback: Mapped type '{t}' to package '{pkg_name}#{pkg_version}'") - break - canonical_name, canonical_version = CANONICAL_PACKAGE - for t in used_types - processed_types: - type_to_package[t] = CANONICAL_PACKAGE - logger.debug(f"Fallback: Mapped type '{t}' to canonical package {canonical_name}#{canonical_version}") - logger.debug(f"Final type-to-package mapping: {type_to_package}") - return type_to_package - -def import_package_and_dependencies(initial_name, initial_version, dependency_mode='recursive'): - """Orchestrates recursive download and dependency extraction.""" - logger.info(f"Starting import of {initial_name}#{initial_version} with mode {dependency_mode}") - download_dir = _get_download_dir() - if not download_dir: - logger.error("Download directory not accessible") - return { - 'requested': (initial_name, initial_version), - 'processed': set(), - 'downloaded': {}, - 'all_dependencies': {}, - 'dependencies': [], - 'errors': ['Download directory not accessible'] - } - - results = { - 'requested': (initial_name, initial_version), - 'processed': set(), - 'downloaded': {}, - 'all_dependencies': {}, - 'dependencies': [], - 'errors': [] - } - pending_queue = [(initial_name, initial_version)] - queued_or_processed_lookup = set([(initial_name, initial_version)]) - all_found_dependencies = set() - - while pending_queue: - name, version = pending_queue.pop(0) - package_id_tuple = (name, version) - if package_id_tuple in results['processed']: - logger.debug(f"Skipping already processed package: {name}#{version}") - continue - logger.info(f"Processing package {name}#{version}") - save_path, dl_error = download_package(name, version) - if dl_error: - logger.error(f"Download failed for {name}#{version}: {dl_error}") - results['errors'].append(f"Download failed for {name}#{version}: {dl_error}") - continue - tgz_filename = os.path.basename(save_path) - logger.info(f"Downloaded {tgz_filename}") - results['downloaded'][package_id_tuple] = save_path - logger.info(f"Extracting dependencies from {tgz_filename}") - dependencies, dep_error = extract_dependencies(save_path) - if dep_error: - logger.error(f"Dependency extraction failed for {name}#{version}: {dep_error}") - results['errors'].append(f"Dependency extraction failed for {name}#{version}: {dep_error}") - results['processed'].add(package_id_tuple) - continue - elif dependencies is None: - logger.error(f"Critical error in dependency extraction for {name}#{version}") - results['errors'].append(f"Dependency extraction returned critical error for {name}#{version}.") - results['processed'].add(package_id_tuple) - continue - results['all_dependencies'][package_id_tuple] = dependencies - results['processed'].add(package_id_tuple) - current_package_deps = [] - for dep_name, dep_version in dependencies.items(): - if isinstance(dep_name, str) and isinstance(dep_version, str) and dep_name and dep_version: - dep_tuple = (dep_name, dep_version) - current_package_deps.append({"name": dep_name, "version": dep_version}) - if dep_tuple not in all_found_dependencies: - all_found_dependencies.add(dep_tuple) - results['dependencies'].append({"name": dep_name, "version": dep_version}) - if dep_tuple not in queued_or_processed_lookup: - should_queue = False - if dependency_mode == 'recursive': - should_queue = True - logger.info(f"Queueing dependency {dep_name}#{dep_version} (recursive mode)") - elif dependency_mode == 'patch-canonical' and dep_tuple == CANONICAL_PACKAGE: - should_queue = True - logger.info(f"Queueing canonical dependency {dep_name}#{dep_version} (patch-canonical mode)") - if should_queue: - logger.debug(f"Adding dependency to queue ({dependency_mode}): {dep_name}#{dep_version}") - pending_queue.append(dep_tuple) - queued_or_processed_lookup.add(dep_tuple) - logger.info(f"Saving metadata for {name}#{version}") - save_package_metadata(name, version, dependency_mode, current_package_deps) - if dependency_mode == 'tree-shaking' and package_id_tuple == (initial_name, initial_version): - logger.info(f"Performing tree-shaking for {initial_name}#{initial_version}") - used_types = extract_used_types(save_path) - if used_types: - type_to_package = map_types_to_packages(used_types, results['all_dependencies'], download_dir) - tree_shaken_deps = set(type_to_package.values()) - {package_id_tuple} - if CANONICAL_PACKAGE not in tree_shaken_deps: - tree_shaken_deps.add(CANONICAL_PACKAGE) - logger.info(f"Ensuring canonical package {CANONICAL_PACKAGE[0]}#{CANONICAL_PACKAGE[1]} for tree-shaking") - for dep_tuple in tree_shaken_deps: - if dep_tuple not in queued_or_processed_lookup: - logger.info(f"Queueing tree-shaken dependency {dep_tuple[0]}#{dep_tuple[1]}") - pending_queue.append(dep_tuple) - queued_or_processed_lookup.add(dep_tuple) - results['dependencies'] = [{"name": d[0], "version": d[1]} for d in all_found_dependencies] - logger.info(f"Completed import of {initial_name}#{initial_version}. Processed {len(results['processed'])} packages, downloaded {len(results['downloaded'])}, with {len(results['errors'])} errors") - return results - -# --- Validation Route --- -@services_bp.route('/validate-sample', methods=['POST']) -@swag_from({ - 'tags': ['Validation'], - 'summary': 'Validate a FHIR resource or bundle.', - 'description': 'Validates a given FHIR resource or bundle against profiles defined in a specified FHIR package. Uses HAPI FHIR for validation if a profile is specified, otherwise falls back to local StructureDefinition checks.', - 'security': [{'ApiKeyAuth': []}], # Assuming API key is desired - 'consumes': ['application/json'], - 'parameters': [ - { - 'name': 'validation_payload', # Changed name - 'in': 'body', - 'required': True, - 'schema': { - 'type': 'object', - 'required': ['package_name', 'version', 'sample_data'], - 'properties': { - 'package_name': {'type': 'string', 'example': 'hl7.fhir.us.core'}, - 'version': {'type': 'string', 'example': '6.1.0'}, - 'sample_data': {'type': 'string', 'description': 'A JSON string of the FHIR resource or Bundle to validate.'}, - # 'include_dependencies': {'type': 'boolean', 'default': True} # This seems to be a server-side decision now - } - } - } - ], - 'responses': { - '200': { - 'description': 'Validation result.', - 'schema': { # Define the schema of the validation_result dictionary - 'type': 'object', - 'properties': { - 'valid': {'type': 'boolean'}, - 'errors': {'type': 'array', 'items': {'type': 'string'}}, - 'warnings': {'type': 'array', 'items': {'type': 'string'}}, - 'details': {'type': 'array', 'items': {'type': 'object'}}, # more specific if known - 'resource_type': {'type': 'string'}, - 'resource_id': {'type': 'string'}, - 'profile': {'type': 'string', 'nullable': True}, - 'summary': {'type': 'object'} - } - } - }, - '400': {'description': 'Invalid request (e.g., missing fields, invalid JSON).'}, - '404': {'description': 'Specified package for validation not found.'}, - '500': {'description': 'Server error during validation.'} - } -}) -def validate_sample(): - """Validates a FHIR sample against a package profile.""" - logger.debug("Received validate-sample request") - data = request.get_json(silent=True) - if not data: - logger.error("No JSON data provided or invalid JSON in validate-sample request") - return jsonify({ - 'valid': False, - 'errors': ["No JSON data provided or invalid JSON"], - 'warnings': [], - 'results': {} - }), 400 - - package_name = data.get('package_name') - version = data.get('version') - sample_data = data.get('sample_data') - - logger.debug(f"Request params: package_name={package_name}, version={version}, sample_data_length={len(sample_data) if sample_data else 0}") - if not package_name or not version or not sample_data: - logger.error(f"Missing required fields: package_name={package_name}, version={version}, sample_data={'provided' if sample_data else 'missing'}") - return jsonify({ - 'valid': False, - 'errors': ["Missing required fields: package_name, version, or sample_data"], - 'warnings': [], - 'results': {} - }), 400 - - # Verify download directory access - download_dir = _get_download_dir() - if not download_dir: - logger.error("Cannot access download directory") - return jsonify({ - 'valid': False, - 'errors': ["Server configuration error: cannot access package directory"], - 'warnings': [], - 'results': {} - }), 500 - - # Verify package file exists - tgz_filename = construct_tgz_filename(package_name, version) - tgz_path = os.path.join(download_dir, tgz_filename) - logger.debug(f"Checking package file: {tgz_path}") - if not os.path.exists(tgz_path): - logger.error(f"Package file not found: {tgz_path}") - return jsonify({ - 'valid': False, - 'errors': [f"Package not found: {package_name}#{version}. Please import the package first."], - 'warnings': [], - 'results': {} - }), 400 - - try: - # Parse JSON sample - sample = json.loads(sample_data) - resource_type = sample.get('resourceType') - if not resource_type: - logger.error("Sample JSON missing resourceType") - return jsonify({ - 'valid': False, - 'errors': ["Sample JSON missing resourceType"], - 'warnings': [], - 'results': {} - }), 400 - - logger.debug(f"Validating {resource_type} against {package_name}#{version}") - # Validate resource or bundle - if resource_type == 'Bundle': - result = validate_bundle_against_profile(package_name, version, sample) - else: - result = validate_resource_against_profile(package_name, version, sample) - - logger.info(f"Validation result for {resource_type} against {package_name}#{version}: valid={result['valid']}, errors={len(result['errors'])}, warnings={len(result['warnings'])}") - return jsonify(result) - except json.JSONDecodeError as e: - logger.error(f"Invalid JSON in sample_data: {e}") - return jsonify({ - 'valid': False, - 'errors': [f"Invalid JSON: {str(e)}"], - 'warnings': [], - 'results': {} - }), 400 - except Exception as e: - logger.error(f"Validation failed: {e}", exc_info=True) - return jsonify({ - 'valid': False, - 'errors': [f"Validation failed: {str(e)}"], - 'warnings': [], - 'results': {} - }), 500 - -def run_gofsh(input_path, output_dir, output_style, log_level, fhir_version=None, fishing_trip=False, dependencies=None, indent_rules=False, meta_profile='only-one', alias_file=None, no_alias=False): - """Run GoFSH with advanced options and return FSH output and optional comparison report.""" - # Use a temporary output directory for initial GoFSH run - temp_output_dir = tempfile.mkdtemp() - os.chmod(temp_output_dir, 0o777) - - cmd = ["gofsh", input_path, "-o", temp_output_dir, "-s", output_style, "-l", log_level] - if fhir_version: - cmd.extend(["-u", fhir_version]) - if dependencies: - for dep in dependencies: - cmd.extend(["--dependency", dep.strip()]) - if indent_rules: - cmd.append("--indent") - if no_alias: - cmd.append("--no-alias") - if alias_file: - cmd.extend(["--alias-file", alias_file]) - if meta_profile != 'only-one': - cmd.extend(["--meta-profile", meta_profile]) - - # Set environment to disable TTY interactions - env = os.environ.copy() - env["NODE_NO_READLINE"] = "1" - env["NODE_NO_INTERACTIVE"] = "1" - env["TERM"] = "dumb" - env["CI"] = "true" - env["FORCE_COLOR"] = "0" - env["NODE_ENV"] = "production" - - # Create a wrapper script in /tmp - wrapper_script = "/tmp/gofsh_wrapper.sh" - output_file = "/tmp/gofsh_output.log" - try: - with open(wrapper_script, 'w') as f: - f.write("#!/bin/bash\n") - # Redirect /dev/tty writes to /dev/null - f.write("exec 3>/dev/null\n") - f.write(" ".join([f'"{arg}"' for arg in cmd]) + f" {output_file} 2>&1\n") - os.chmod(wrapper_script, 0o755) - - # Log the wrapper script contents for debugging - with open(wrapper_script, 'r') as f: - logger.debug(f"Wrapper script contents:\n{f.read()}") - except Exception as e: - logger.error(f"Failed to create wrapper script {wrapper_script}: {str(e)}", exc_info=True) - return None, None, f"Failed to create wrapper script: {str(e)}" - - try: - # Log directory contents before execution - logger.debug(f"Temp output directory contents before GoFSH: {os.listdir(temp_output_dir)}") - - result = subprocess.run( - [wrapper_script], - check=True, - env=env - ) - # Read output from the log file - with open(output_file, 'r', encoding='utf-8') as f: - output = f.read() - logger.debug(f"GoFSH output:\n{output}") - - # Prepare final output directory - if os.path.exists(output_dir): - shutil.rmtree(output_dir) - os.makedirs(output_dir, exist_ok=True) - os.chmod(output_dir, 0o777) - - # Copy .fsh files, sushi-config.yaml, and input JSON to final output directory - copied_files = [] - for root, _, files in os.walk(temp_output_dir): - for file in files: - src_path = os.path.join(root, file) - if file.endswith(".fsh") or file == "sushi-config.yaml": - relative_path = os.path.relpath(src_path, temp_output_dir) - dst_path = os.path.join(output_dir, relative_path) - os.makedirs(os.path.dirname(dst_path), exist_ok=True) - shutil.copy2(src_path, dst_path) - copied_files.append(relative_path) - - # Copy input JSON to final directory - input_filename = os.path.basename(input_path) - dst_input_path = os.path.join(output_dir, "input", input_filename) - os.makedirs(os.path.dirname(dst_input_path), exist_ok=True) - shutil.copy2(input_path, dst_input_path) - copied_files.append(os.path.join("input", input_filename)) - - # Create a minimal sushi-config.yaml if missing - sushi_config_path = os.path.join(output_dir, "sushi-config.yaml") - if not os.path.exists(sushi_config_path): - minimal_config = { - "id": "fhirflare.temp", - "canonical": "http://fhirflare.org", - "name": "FHIRFLARETempIG", - "version": "0.1.0", - "fhirVersion": fhir_version or "4.0.1", - "FSHOnly": True, - "dependencies": dependencies or [] - } - with open(sushi_config_path, 'w') as f: - json.dump(minimal_config, f, indent=2) - copied_files.append("sushi-config.yaml") - - # Run GoFSH with --fshing-trip in a fresh temporary directory - comparison_report = None - if fishing_trip: - fishing_temp_dir = tempfile.mkdtemp() - os.chmod(fishing_temp_dir, 0o777) - gofsh_fishing_cmd = ["gofsh", input_path, "-o", fishing_temp_dir, "-s", output_style, "-l", log_level, "--fshing-trip"] - if fhir_version: - gofsh_fishing_cmd.extend(["-u", fhir_version]) - if dependencies: - for dep in dependencies: - gofsh_fishing_cmd.extend(["--dependency", dep.strip()]) - if indent_rules: - gofsh_fishing_cmd.append("--indent") - if no_alias: - gofsh_fishing_cmd.append("--no-alias") - if alias_file: - gofsh_fishing_cmd.extend(["--alias-file", alias_file]) - if meta_profile != 'only-one': - gofsh_fishing_cmd.extend(["--meta-profile", meta_profile]) - - try: - with open(wrapper_script, 'w') as f: - f.write("#!/bin/bash\n") - f.write("exec 3>/dev/null\n") - f.write("exec >/dev/null 2>&1\n") # Suppress all output to /dev/tty - f.write(" ".join([f'"{arg}"' for arg in gofsh_fishing_cmd]) + f" {output_file} 2>&1\n") - os.chmod(wrapper_script, 0o755) - - logger.debug(f"GoFSH fishing-trip wrapper script contents:\n{open(wrapper_script, 'r').read()}") - - result = subprocess.run( - [wrapper_script], - check=True, - env=env - ) - with open(output_file, 'r', encoding='utf-8') as f: - fishing_output = f.read() - logger.debug(f"GoFSH fishing-trip output:\n{fishing_output}") - - # Copy fshing-trip-comparison.html to final directory - for root, _, files in os.walk(fishing_temp_dir): - for file in files: - if file.endswith(".html") and "fshing-trip-comparison" in file.lower(): - src_path = os.path.join(root, file) - dst_path = os.path.join(output_dir, file) - shutil.copy2(src_path, dst_path) - copied_files.append(file) - with open(dst_path, 'r', encoding='utf-8') as f: - comparison_report = f.read() - except subprocess.CalledProcessError as e: - error_output = "" - if os.path.exists(output_file): - with open(output_file, 'r', encoding='utf-8') as f: - error_output = f.read() - logger.error(f"GoFSH fishing-trip failed: {error_output}") - return None, None, f"GoFSH fishing-trip failed: {error_output}" - finally: - if os.path.exists(fishing_temp_dir): - shutil.rmtree(fishing_temp_dir, ignore_errors=True) - - # Read FSH files from final output directory - fsh_content = [] - for root, _, files in os.walk(output_dir): - for file in files: - if file.endswith(".fsh"): - with open(os.path.join(root, file), 'r', encoding='utf-8') as f: - fsh_content.append(f.read()) - fsh_output = "\n\n".join(fsh_content) - - # Log copied files - logger.debug(f"Copied files to final output directory: {copied_files}") - - logger.info(f"GoFSH executed successfully for {input_path}") - return fsh_output, comparison_report, None - except subprocess.CalledProcessError as e: - error_output = "" - if os.path.exists(output_file): - with open(output_file, 'r', encoding='utf-8') as f: - error_output = f.read() - logger.error(f"GoFSH failed: {error_output}") - return None, None, f"GoFSH failed: {error_output}" - except Exception as e: - logger.error(f"Error running GoFSH: {str(e)}", exc_info=True) - return None, None, f"Error running GoFSH: {str(e)}" - finally: - # Clean up temporary files - if os.path.exists(wrapper_script): - os.remove(wrapper_script) - if os.path.exists(output_file): - os.remove(output_file) - if os.path.exists(temp_output_dir): - shutil.rmtree(temp_output_dir, ignore_errors=True) - -def process_fhir_input(input_mode, fhir_file, fhir_text, alias_file=None): - """Process user input (file or text) and save to temporary files.""" - temp_dir = tempfile.mkdtemp() - input_file = None - alias_path = None - - try: - if input_mode == 'file' and fhir_file: - content = fhir_file.read().decode('utf-8') - file_type = 'json' if content.strip().startswith('{') else 'xml' - input_file = os.path.join(temp_dir, f"input.{file_type}") - with open(input_file, 'w') as f: - f.write(content) - elif input_mode == 'text' and fhir_text: - content = fhir_text.strip() - file_type = 'json' if content.strip().startswith('{') else 'xml' - input_file = os.path.join(temp_dir, f"input.{file_type}") - with open(input_file, 'w') as f: - f.write(content) - else: - return None, None, None, "No input provided" - - # Basic validation - if file_type == 'json': - try: - json.loads(content) - except json.JSONDecodeError: - return None, None, None, "Invalid JSON format" - elif file_type == 'xml': - try: - ET.fromstring(content) - except ET.ParseError: - return None, None, None, "Invalid XML format" - - # Process alias file if provided - if alias_file: - alias_content = alias_file.read().decode('utf-8') - alias_path = os.path.join(temp_dir, "aliases.fsh") - with open(alias_path, 'w') as f: - f.write(alias_content) - - logger.debug(f"Processed input: {(input_file, alias_path)}") - return input_file, temp_dir, alias_path, None - except Exception as e: - logger.error(f"Error processing input: {str(e)}", exc_info=True) - return None, None, None, f"Error processing input: {str(e)}" - -# --- ADD THIS NEW FUNCTION TO services.py --- -def find_and_extract_search_params(tgz_path, base_resource_type): - """Finds and extracts SearchParameter resources relevant to a given base resource type from a FHIR package tgz file.""" - search_params = [] - if not tgz_path or not os.path.exists(tgz_path): - logger.error(f"Package file not found for SearchParameter extraction: {tgz_path}") - return search_params - logger.debug(f"Searching for SearchParameters based on '{base_resource_type}' in {os.path.basename(tgz_path)}") - try: - with tarfile.open(tgz_path, "r:gz") as tar: - for member in tar: - if not (member.isfile() and member.name.startswith('package/') and member.name.lower().endswith('.json')): - continue - if os.path.basename(member.name).lower() in ['package.json', '.index.json', 'validation-summary.json', 'validation-oo.json']: - continue - fileobj = None - try: - fileobj = tar.extractfile(member) - if fileobj: - content_bytes = fileobj.read() - content_string = content_bytes.decode('utf-8-sig') - data = json.loads(content_string) - if isinstance(data, dict) and data.get('resourceType') == 'SearchParameter': - sp_bases = data.get('base', []) - if base_resource_type in sp_bases: - param_info = { - 'id': data.get('id'), - 'url': data.get('url'), - 'name': data.get('name'), - 'description': data.get('description'), - 'code': data.get('code'), - 'type': data.get('type'), - 'expression': data.get('expression'), - 'base': sp_bases, - 'conformance': 'N/A', - 'is_mandatory': False - } - search_params.append(param_info) - logger.debug(f"Found relevant SearchParameter: {param_info.get('name')} (ID: {param_info.get('id')}) for base {base_resource_type}") - except json.JSONDecodeError as e: - logger.debug(f"Could not parse JSON for SearchParameter in {member.name}, skipping: {e}") - except UnicodeDecodeError as e: - logger.warning(f"Could not decode UTF-8 for SearchParameter in {member.name}, skipping: {e}") - except tarfile.TarError as e: - logger.warning(f"Tar error reading member {member.name} for SearchParameter, skipping: {e}") - except Exception as e: - logger.warning(f"Could not read/parse potential SearchParameter {member.name}, skipping: {e}", exc_info=False) - finally: - if fileobj: - fileobj.close() - except tarfile.ReadError as e: - logger.error(f"Tar ReadError extracting SearchParameters from {tgz_path}: {e}") - except tarfile.TarError as e: - logger.error(f"TarError extracting SearchParameters from {tgz_path}: {e}") - except FileNotFoundError: - logger.error(f"Package file not found during SearchParameter extraction: {tgz_path}") - except Exception as e: - logger.error(f"Unexpected error extracting SearchParameters from {tgz_path}: {e}", exc_info=True) - logger.info(f"Found {len(search_params)} SearchParameters relevant to '{base_resource_type}' in {os.path.basename(tgz_path)}") - return search_params -# --- END OF NEW FUNCTION --- - -# --- Full Replacement Function (Corrected Prefix Definitions & Unabbreviated) --- - -def generate_push_stream(package_name, version, fhir_server_url, include_dependencies, - auth_type, auth_token, resource_types_filter, skip_files, - dry_run, verbose, force_upload, packages_dir): - """ - Generates NDJSON stream for the push IG operation. - Handles canonical resources (search by URL, POST/PUT), - skips identical resources (unless force_upload is true), and specified files. - """ - # --- Variable Initializations --- - pushed_packages_info = [] - success_count = 0 - failure_count = 0 - skipped_count = 0 - post_count = 0 - put_count = 0 - total_resources_attempted = 0 - processed_resources = set() - failed_uploads_details = [] - skipped_resources_details = [] - filter_set = set(resource_types_filter) if resource_types_filter else None - skip_files_set = set(skip_files) if skip_files else set() - - try: - # --- Start Messages --- - operation_mode = " (DRY RUN)" if dry_run else "" - force_mode = " (FORCE UPLOAD)" if force_upload else "" - yield json.dumps({"type": "start", "message": f"Starting push{operation_mode}{force_mode} for {package_name}#{version} to {fhir_server_url}"}) + "\n" - if filter_set: - yield json.dumps({"type": "info", "message": f"Filtering for resource types: {', '.join(sorted(list(filter_set)))}"}) + "\n" - if skip_files_set: - yield json.dumps({"type": "info", "message": f"Skipping {len(skip_files_set)} specific files."}) + "\n" - yield json.dumps({"type": "info", "message": f"Include Dependencies: {'Yes' if include_dependencies else 'No'}"}) + "\n" - - # --- Define packages_to_push --- - packages_to_push = [] - primary_tgz_filename = construct_tgz_filename(package_name, version) - primary_tgz_path = os.path.join(packages_dir, primary_tgz_filename) - - if not os.path.exists(primary_tgz_path): - yield json.dumps({"type": "error", "message": f"Primary package file not found: {primary_tgz_filename}"}) + "\n" - raise FileNotFoundError(f"Primary package file not found: {primary_tgz_path}") - - packages_to_push.append((package_name, version, primary_tgz_path)) - logger.debug(f"Added primary package to push list: {package_name}#{version}") - - if include_dependencies: - yield json.dumps({"type": "info", "message": "Including dependencies based on import metadata..."}) + "\n" - metadata = get_package_metadata(package_name, version) - if metadata and metadata.get("imported_dependencies"): - dependencies_to_include = metadata["imported_dependencies"] - logger.info(f"Found {len(dependencies_to_include)} dependencies in metadata to potentially include.") - for dep in dependencies_to_include: - dep_name = dep.get("name") - dep_version = dep.get("version") - if dep_name and dep_version: - dep_tgz_filename = construct_tgz_filename(dep_name, dep_version) - dep_tgz_path = os.path.join(packages_dir, dep_tgz_filename) - if os.path.exists(dep_tgz_path): - if (dep_name, dep_version, dep_tgz_path) not in packages_to_push: - packages_to_push.append((dep_name, dep_version, dep_tgz_path)) - logger.debug(f"Added dependency package to push list: {dep_name}#{dep_version}") - else: - yield json.dumps({"type": "warning", "message": f"Dependency package file not found, cannot include: {dep_tgz_filename}"}) + "\n" - logger.warning(f"Dependency package file listed in metadata but not found locally: {dep_tgz_path}") - else: - yield json.dumps({"type": "warning", "message": "Include Dependencies checked, but no dependency metadata found. Only pushing primary."}) + "\n" - logger.warning(f"No dependency metadata found for {package_name}#{version} despite include_dependencies=True") - - # --- Resource Extraction & Filtering --- - resources_to_upload = [] - seen_resource_files = set() - - for pkg_name, pkg_version, pkg_path in packages_to_push: - yield json.dumps({"type": "progress", "message": f"Extracting resources from: {pkg_name}#{pkg_version}..."}) + "\n" - try: - with tarfile.open(pkg_path, "r:gz") as tar: - for member in tar.getmembers(): - if not (member.isfile() and member.name.startswith("package/") and member.name.lower().endswith(".json")): - continue - basename_lower = os.path.basename(member.name).lower() - if basename_lower in ["package.json", ".index.json", "validation-summary.json", "validation-oo.json"]: - continue - - normalized_member_name = member.name.replace("\\", "/") - if normalized_member_name in skip_files_set or member.name in skip_files_set: - if verbose: - yield json.dumps({"type": "info", "message": f"Skipping file due to filter: {member.name}"}) + "\n" - continue - - if member.name in seen_resource_files: - if verbose: - yield json.dumps({"type": "info", "message": f"Skipping already seen file: {member.name}"}) + "\n" - continue - seen_resource_files.add(member.name) - - try: - with tar.extractfile(member) as f: - resource_content = f.read().decode("utf-8-sig") - resource_data = json.loads(resource_content) - - if isinstance(resource_data, dict) and "resourceType" in resource_data and "id" in resource_data: - resource_type_val = resource_data.get("resourceType") - if filter_set and resource_type_val not in filter_set: - if verbose: - yield json.dumps({"type": "info", "message": f"Skipping resource type {resource_type_val} due to filter: {member.name}"}) + "\n" - continue - resources_to_upload.append({ - "data": resource_data, - "source_package": f"{pkg_name}#{pkg_version}", - "source_filename": member.name - }) - else: - yield json.dumps({"type": "warning", "message": f"Skipping invalid/incomplete resource structure in file: {member.name}"}) + "\n" - except json.JSONDecodeError as json_e: - yield json.dumps({"type": "warning", "message": f"JSON parse error in file {member.name}: {json_e}"}) + "\n" - except UnicodeDecodeError as uni_e: - yield json.dumps({"type": "warning", "message": f"Encoding error in file {member.name}: {uni_e}"}) + "\n" - except KeyError: - yield json.dumps({"type": "warning", "message": f"File not found within archive: {member.name}"}) + "\n" - except Exception as extract_e: - yield json.dumps({"type": "warning", "message": f"Error processing file {member.name}: {extract_e}"}) + "\n" - except tarfile.ReadError as tar_read_e: - error_msg = f"Tar ReadError reading package {pkg_name}#{pkg_version}: {tar_read_e}. Skipping package." - yield json.dumps({"type": "error", "message": error_msg}) + "\n" - failure_count += 1 - failed_uploads_details.append({"resource": f"Package: {pkg_name}#{pkg_version}", "error": f"Read Error: {tar_read_e}"}) - continue - except tarfile.TarError as tar_e: - error_msg = f"TarError reading package {pkg_name}#{pkg_version}: {tar_e}. Skipping package." - yield json.dumps({"type": "error", "message": error_msg}) + "\n" - failure_count += 1 - failed_uploads_details.append({"resource": f"Package: {pkg_name}#{pkg_version}", "error": f"Tar Error: {tar_e}"}) - continue - except Exception as pkg_e: - error_msg = f"Unexpected error reading package {pkg_name}#{pkg_version}: {pkg_e}. Skipping package." - yield json.dumps({"type": "error", "message": error_msg}) + "\n" - failure_count += 1 - failed_uploads_details.append({"resource": f"Package: {pkg_name}#{pkg_version}", "error": f"Unexpected: {pkg_e}"}) - logger.error(f"Error reading package {pkg_path}: {pkg_e}", exc_info=True) - continue - - total_resources_attempted = len(resources_to_upload) - yield json.dumps({"type": "info", "message": f"Found {total_resources_attempted} resources matching filters across selected packages."}) + "\n" - - if total_resources_attempted == 0: - yield json.dumps({"type": "warning", "message": "No resources found to upload after filtering."}) + "\n" - else: - # --- Resource Upload Loop Setup --- - session = requests.Session() - base_url = fhir_server_url.rstrip("/") - headers = {"Content-Type": "application/fhir+json", "Accept": "application/fhir+json"} - # MODIFIED: Enhanced authentication handling - if auth_type in ["bearerToken", "basic"] and auth_token: - # Log the Authorization header (mask sensitive data) - auth_display = "Basic " if auth_type == "basic" else (auth_token[:10] + "..." if len(auth_token) > 10 else auth_token) - yield json.dumps({"type": "info", "message": f"Using {auth_type} auth with header: Authorization: {auth_display}"}) + "\n" - headers["Authorization"] = auth_token # Use auth_token for both Bearer and Basic - elif auth_type == "apiKey": - internal_api_key = None - try: - internal_api_key = current_app.config.get("API_KEY") - except RuntimeError: - logger.warning("Cannot access current_app config outside of request context for API Key.") - if internal_api_key: - headers["X-API-Key"] = internal_api_key - yield json.dumps({"type": "info", "message": "Using internal API Key authentication."}) + "\n" - else: - yield json.dumps({"type": "warning", "message": "API Key auth selected, but no internal key configured/accessible."}) + "\n" - else: - yield json.dumps({"type": "info", "message": "Using no authentication."}) + "\n" - - # --- Main Upload Loop --- - for i, resource_info in enumerate(resources_to_upload, 1): - local_resource = resource_info["data"] - source_pkg = resource_info["source_package"] - resource_type = local_resource.get("resourceType") - resource_id = local_resource.get("id") - resource_log_id = f"{resource_type}/{resource_id}" - canonical_url = local_resource.get("url") - canonical_version = local_resource.get("version") - is_canonical_type = resource_type in CANONICAL_RESOURCE_TYPES - - if resource_log_id in processed_resources: - if verbose: - yield json.dumps({"type": "info", "message": f"Skipping duplicate ID in processing list: {resource_log_id}"}) + "\n" - continue - processed_resources.add(resource_log_id) - - if dry_run: - dry_run_action = "check/PUT" - if is_canonical_type and canonical_url: - dry_run_action = "search/POST/PUT" - yield json.dumps({"type": "progress", "message": f"[DRY RUN] Would {dry_run_action} {resource_log_id} ({i}/{total_resources_attempted}) from {source_pkg}"}) + "\n" - success_count += 1 - pkg_found = False - for p in pushed_packages_info: - if p["id"] == source_pkg: - p["resource_count"] += 1 - pkg_found = True - break - if not pkg_found: - pushed_packages_info.append({"id": source_pkg, "resource_count": 1}) - continue - - existing_resource_id = None - existing_resource_data = None - action = "PUT" - target_url = f"{base_url}/{resource_type}/{resource_id}" - skip_resource = False - - if is_canonical_type and canonical_url: - action = "SEARCH_POST_PUT" - search_params = {"url": canonical_url} - if canonical_version: - search_params["version"] = canonical_version - search_url = f"{base_url}/{resource_type}" - if verbose: - yield json.dumps({"type": "info", "message": f"Canonical Type: Searching {search_url} with params {search_params}"}) + "\n" - - try: - search_response = session.get(search_url, params=search_params, headers=headers, timeout=20) - search_response.raise_for_status() - search_bundle = search_response.json() - - if search_bundle.get("resourceType") == "Bundle" and "entry" in search_bundle: - entries = search_bundle.get("entry", []) - if len(entries) == 1: - existing_resource_data = entries[0].get("resource") - if existing_resource_data: - existing_resource_id = existing_resource_data.get("id") - if existing_resource_id: - action = "PUT" - target_url = f"{base_url}/{resource_type}/{existing_resource_id}" - if verbose: - yield json.dumps({"type": "info", "message": f"Found existing canonical resource ID: {existing_resource_id}"}) + "\n" - else: - yield json.dumps({"type": "warning", "message": f"Found canonical {canonical_url}|{canonical_version} but lacks ID. Skipping update."}) + "\n" - action = "SKIP" - skip_resource = True - skipped_count += 1 - skipped_resources_details.append({"resource": resource_log_id, "reason": "Found canonical match without ID"}) - else: - yield json.dumps({"type": "warning", "message": f"Search for {canonical_url}|{canonical_version} entry lacks resource data. Assuming not found."}) + "\n" - action = "POST" - target_url = f"{base_url}/{resource_type}" - elif len(entries) == 0: - action = "POST" - target_url = f"{base_url}/{resource_type}" - if verbose: - yield json.dumps({"type": "info", "message": f"Canonical not found by URL/Version. Planning POST."}) + "\n" - else: - ids_found = [e.get("resource", {}).get("id", "unknown") for e in entries] - yield json.dumps({"type": "error", "message": f"Conflict: Found {len(entries)} matches for {canonical_url}|{canonical_version} (IDs: {', '.join(ids_found)}). Skipping."}) + "\n" - action = "SKIP" - skip_resource = True - failure_count += 1 - failed_uploads_details.append({"resource": resource_log_id, "error": f"Conflict: Multiple matches ({len(entries)}) for canonical URL/Version"}) - else: - yield json.dumps({"type": "warning", "message": f"Search for {canonical_url}|{canonical_version} returned non-Bundle/empty. Assuming not found."}) + "\n" - action = "POST" - target_url = f"{base_url}/{resource_type}" - - except requests.exceptions.RequestException as search_err: - yield json.dumps({"type": "warning", "message": f"Search failed for {resource_log_id}: {search_err}. Defaulting to PUT by ID."}) + "\n" - action = "PUT" - target_url = f"{base_url}/{resource_type}/{resource_id}" - except json.JSONDecodeError as json_err: - yield json.dumps({"type": "warning", "message": f"Failed parse search result for {resource_log_id}: {json_err}. Defaulting PUT by ID."}) + "\n" - action = "PUT" - target_url = f"{base_url}/{resource_type}/{resource_id}" - except Exception as e: - yield json.dumps({"type": "warning", "message": f"Unexpected canonical search error for {resource_log_id}: {e}. Defaulting PUT by ID."}) + "\n" - action = "PUT" - target_url = f"{base_url}/{resource_type}/{resource_id}" - - if action == "PUT" and not force_upload and not skip_resource: - resource_to_compare = existing_resource_data - if not resource_to_compare: - try: - if verbose: - yield json.dumps({"type": "info", "message": f"Checking existing (PUT target): {target_url}"}) + "\n" - get_response = session.get(target_url, headers=headers, timeout=15) - if get_response.status_code == 200: - resource_to_compare = get_response.json() - if verbose: - yield json.dumps({"type": "info", "message": f"Found resource by ID for comparison."}) + "\n" - elif get_response.status_code == 404: - if verbose: - yield json.dumps({"type": "info", "message": f"Resource {resource_log_id} not found by ID ({target_url}). Proceeding with PUT create."}) + "\n" - else: - yield json.dumps({"type": "warning", "message": f"Comparison check failed (GET {get_response.status_code}). Attempting PUT."}) + "\n" - except Exception as get_err: - yield json.dumps({"type": "warning", "message": f"Comparison check failed (Error during GET by ID: {get_err}). Attempting PUT."}) + "\n" - - if resource_to_compare: - try: - if are_resources_semantically_equal(local_resource, resource_to_compare): - yield json.dumps({"type": "info", "message": f"Skipping {resource_log_id} (Identical content)"}) + "\n" - skip_resource = True - skipped_count += 1 - skipped_resources_details.append({"resource": resource_log_id, "reason": "Identical content"}) - elif verbose: - yield json.dumps({"type": "info", "message": f"{resource_log_id} exists but differs. Updating."}) + "\n" - except Exception as comp_err: - yield json.dumps({"type": "warning", "message": f"Comparison failed for {resource_log_id}: {comp_err}. Proceeding with PUT."}) + "\n" - - elif action == "PUT" and force_upload: - if verbose: - yield json.dumps({"type": "info", "message": f"Force Upload enabled, skipping comparison for {resource_log_id}."}) + "\n" - - if not skip_resource: - http_method = action if action in ["POST", "PUT"] else "PUT" - log_action = f"{http_method}ing" - yield json.dumps({"type": "progress", "message": f"{log_action} {resource_log_id} ({i}/{total_resources_attempted}) to {target_url}..."}) + "\n" - - try: - if http_method == "POST": - response = session.post(target_url, json=local_resource, headers=headers, timeout=30) - post_count += 1 - else: - response = session.put(target_url, json=local_resource, headers=headers, timeout=30) - put_count += 1 - - response.raise_for_status() - - success_msg = f"{http_method} successful for {resource_log_id} (Status: {response.status_code})" - if http_method == "POST" and response.status_code == 201: - location = response.headers.get("Location") - if location: - match = re.search(f"{resource_type}/([^/]+)/_history", location) - new_id = match.group(1) if match else "unknown" - success_msg += f" -> New ID: {new_id}" - else: - success_msg += " (No Location header)" - yield json.dumps({"type": "success", "message": success_msg}) + "\n" - success_count += 1 - pkg_found_success = False - for p in pushed_packages_info: - if p["id"] == source_pkg: - p["resource_count"] += 1 - pkg_found_success = True - break - if not pkg_found_success: - pushed_packages_info.append({"id": source_pkg, "resource_count": 1}) - - except requests.exceptions.HTTPError as http_err: - outcome_text = "" - status_code = http_err.response.status_code if http_err.response is not None else "N/A" - try: - outcome = http_err.response.json() - if outcome and outcome.get("resourceType") == "OperationOutcome": - issues = outcome.get("issue", []) - outcome_text = "; ".join([f"{i.get('severity', 'info')}: {i.get('diagnostics', i.get('details', {}).get('text', 'No details'))}" for i in issues]) if issues else "OperationOutcome with no issues." - else: - outcome_text = http_err.response.text[:200] if http_err.response is not None else "No response body" - except ValueError: - outcome_text = http_err.response.text[:200] if http_err.response is not None else "No response body (or not JSON)" - error_msg = f"Failed {http_method} {resource_log_id} (Status: {status_code}): {outcome_text or str(http_err)}" - yield json.dumps({"type": "error", "message": error_msg}) + "\n" - failure_count += 1 - failed_uploads_details.append({"resource": resource_log_id, "error": error_msg}) - except requests.exceptions.Timeout: - error_msg = f"Timeout during {http_method} {resource_log_id}" - yield json.dumps({"type": "error", "message": error_msg}) + "\n" - failure_count += 1 - failed_uploads_details.append({"resource": resource_log_id, "error": "Timeout"}) - except requests.exceptions.ConnectionError as conn_err: - error_msg = f"Connection error during {http_method} {resource_log_id}: {conn_err}" - yield json.dumps({"type": "error", "message": error_msg}) + "\n" - failure_count += 1 - failed_uploads_details.append({"resource": resource_log_id, "error": f"Connection Error: {conn_err}"}) - except requests.exceptions.RequestException as req_err: - error_msg = f"Request error during {http_method} {resource_log_id}: {str(req_err)}" - yield json.dumps({"type": "error", "message": error_msg}) + "\n" - failure_count += 1 - failed_uploads_details.append({"resource": resource_log_id, "error": f"Request Error: {req_err}"}) - except Exception as e: - error_msg = f"Unexpected error during {http_method} {resource_log_id}: {str(e)}" - yield json.dumps({"type": "error", "message": error_msg}) + "\n" - failure_count += 1 - failed_uploads_details.append({"resource": resource_log_id, "error": f"Unexpected: {e}"}) - logger.error(f"[API Push Stream] Upload error for {resource_log_id}: {e}", exc_info=True) - else: - pkg_found_skipped = False - for p in pushed_packages_info: - if p["id"] == source_pkg: - pkg_found_skipped = True - break - if not pkg_found_skipped: - pushed_packages_info.append({"id": source_pkg, "resource_count": 0}) - - # --- Final Summary --- - final_status = "success" if failure_count == 0 else "partial" if success_count > 0 else "failure" - dry_run_prefix = "[DRY RUN] " if dry_run else "" - force_prefix = "[FORCE UPLOAD] " if force_upload else "" - if total_resources_attempted == 0 and failure_count == 0: - summary_message = f"{dry_run_prefix}Push finished: No matching resources found to process." - final_status = "success" - else: - summary_message = f"{dry_run_prefix}{force_prefix}Push finished: {post_count} POSTed, {put_count} PUT, {failure_count} failed, {skipped_count} skipped ({total_resources_attempted} resources attempted)." - - summary = { - "status": final_status, - "message": summary_message, - "target_server": fhir_server_url, - "package_name": package_name, - "version": version, - "included_dependencies": include_dependencies, - "resources_attempted": total_resources_attempted, - "success_count": success_count, - "post_count": post_count, - "put_count": put_count, - "failure_count": failure_count, - "skipped_count": skipped_count, - "validation_failure_count": 0, - "failed_details": failed_uploads_details, - "skipped_details": skipped_resources_details, - "pushed_packages_summary": pushed_packages_info, - "dry_run": dry_run, - "force_upload": force_upload, - "resource_types_filter": resource_types_filter, - "skip_files_filter": sorted(list(skip_files_set)) if skip_files_set else None - } - yield json.dumps({"type": "complete", "data": summary}) + "\n" - logger.info(f"[API Push Stream] Completed {package_name}#{version}. Status: {final_status}. {summary_message}") - - except FileNotFoundError as fnf_err: - logger.error(f"[API Push Stream] Setup error: {str(fnf_err)}", exc_info=False) - error_response = {"status": "error", "message": f"Setup error: {str(fnf_err)}"} - try: - yield json.dumps({"type": "error", "message": error_response["message"]}) + "\n" - yield json.dumps({"type": "complete", "data": error_response}) + "\n" - except Exception as yield_e: - logger.error(f"Error yielding final setup error: {yield_e}") - except Exception as e: - logger.error(f"[API Push Stream] Critical error during setup or stream generation: {str(e)}", exc_info=True) - error_response = {"status": "error", "message": f"Server error during push setup: {str(e)}"} - try: - yield json.dumps({"type": "error", "message": error_response["message"]}) + "\n" - yield json.dumps({"type": "complete", "data": error_response}) + "\n" - except Exception as yield_e: - logger.error(f"Error yielding final critical error: {yield_e}") - -# --- END generate_push_stream FUNCTION --- - -def are_resources_semantically_equal(resource1, resource2): - """ - Compares two FHIR resources, ignoring metadata like versionId, lastUpdated, - source, and the text narrative. - Logs differing JSON strings if comparison fails and DeepDiff is unavailable. - Returns True if they are semantically equal, False otherwise. - """ - if not isinstance(resource1, dict) or not isinstance(resource2, dict): - return False - if resource1.get('resourceType') != resource2.get('resourceType'): - # Log difference if needed, or just return False - # logger.debug(f"Resource types differ: {resource1.get('resourceType')} vs {resource2.get('resourceType')}") - return False - - # Create deep copies to avoid modifying the originals - try: - copy1 = json.loads(json.dumps(resource1)) - copy2 = json.loads(json.dumps(resource2)) - except Exception as e: - logger.error(f"Compare Error: Failed deep copy: {e}") - return False # Cannot compare if copying fails - - # Keys to ignore within the 'meta' tag during comparison - # --- UPDATED: Added 'source' to the list --- - keys_to_ignore_in_meta = ['versionId', 'lastUpdated', 'source'] - # --- END UPDATE --- - - # Remove meta fields to ignore from copy1 - if 'meta' in copy1: - for key in keys_to_ignore_in_meta: - copy1['meta'].pop(key, None) - # Remove meta tag entirely if it's now empty - if not copy1['meta']: - copy1.pop('meta', None) - - # Remove meta fields to ignore from copy2 - if 'meta' in copy2: - for key in keys_to_ignore_in_meta: - copy2['meta'].pop(key, None) - # Remove meta tag entirely if it's now empty - if not copy2['meta']: - copy2.pop('meta', None) - - # Remove narrative text element from both copies - copy1.pop('text', None) - copy2.pop('text', None) - - # --- Comparison --- - try: - # Convert cleaned copies to sorted, indented JSON strings for comparison & logging - # Using indent=2 helps readability when logging the strings. - json_str1 = json.dumps(copy1, sort_keys=True, indent=2) - json_str2 = json.dumps(copy2, sort_keys=True, indent=2) - - # Perform the comparison - are_equal = (json_str1 == json_str2) - - # --- Debug Logging if Comparison Fails --- - if not are_equal: - resource_id = resource1.get('id', 'UNKNOWN_ID') # Get ID safely - resource_type = resource1.get('resourceType', 'UNKNOWN_TYPE') # Get Type safely - log_prefix = f"Comparison Failed for {resource_type}/{resource_id} (after ignoring meta.source)" - logger.debug(log_prefix) - - # Attempt to use DeepDiff for a structured difference report - try: - from deepdiff import DeepDiff - # Configure DeepDiff for potentially better comparison - # ignore_order=True is important for lists/arrays - # significant_digits might help with float issues if needed - # report_repetition=True might help spot array differences - diff = DeepDiff(copy1, copy2, ignore_order=True, report_repetition=True, verbose_level=0) - # Only log if diff is not empty - if diff: - logger.debug(f"DeepDiff details: {diff}") - else: - # This case suggests deepdiff found them equal but string comparison failed - odd. - logger.debug(f"JSON strings differed, but DeepDiff found no differences.") - # Log JSON strings if deepdiff shows no difference (or isn't available) - logger.debug(f"--- {resource_type}/{resource_id} Resource 1 (Local/Cleaned) --- START ---") - logger.debug(json_str1) - logger.debug(f"--- {resource_type}/{resource_id} Resource 1 (Local/Cleaned) --- END ---") - logger.debug(f"--- {resource_type}/{resource_id} Resource 2 (Server/Cleaned) --- START ---") - logger.debug(json_str2) - logger.debug(f"--- {resource_type}/{resource_id} Resource 2 (Server/Cleaned) --- END ---") - - except ImportError: - # DeepDiff not available, log the differing JSON strings - logger.debug(f"DeepDiff not available. Logging differing JSON strings.") - logger.debug(f"--- {resource_type}/{resource_id} Resource 1 (Local/Cleaned) --- START ---") - logger.debug(json_str1) - logger.debug(f"--- {resource_type}/{resource_id} Resource 1 (Local/Cleaned) --- END ---") - logger.debug(f"--- {resource_type}/{resource_id} Resource 2 (Server/Cleaned) --- START ---") - logger.debug(json_str2) - logger.debug(f"--- {resource_type}/{resource_id} Resource 2 (Server/Cleaned) --- END ---") - except Exception as diff_err: - # Error during deepdiff itself - logger.error(f"Error during deepdiff calculation for {resource_type}/{resource_id}: {diff_err}") - # Fallback to logging JSON strings - logger.debug(f"--- {resource_type}/{resource_id} Resource 1 (Local/Cleaned) --- START ---") - logger.debug(json_str1) - logger.debug(f"--- {resource_type}/{resource_id} Resource 1 (Local/Cleaned) --- END ---") - logger.debug(f"--- {resource_type}/{resource_id} Resource 2 (Server/Cleaned) --- START ---") - logger.debug(json_str2) - logger.debug(f"--- {resource_type}/{resource_id} Resource 2 (Server/Cleaned) --- END ---") - - # --- END DEBUG LOGGING --- - - return are_equal - - except Exception as e: - # Catch errors during JSON dumping or final comparison steps - resource_id_err = resource1.get('id', 'UNKNOWN_ID') - resource_type_err = resource1.get('resourceType', 'UNKNOWN_TYPE') - logger.error(f"Error during final comparison step for {resource_type_err}/{resource_id_err}: {e}", exc_info=True) - return False # Treat comparison errors as 'not equal' to be safe -# --- END FUNCTION --- - -# --- Service Function for Test Data Upload (with Conditional Upload) --- -def process_and_upload_test_data(server_info, options, temp_file_dir): - """ - Parses test data files, optionally validates, builds dependency graph, - sorts, and uploads resources individually (conditionally or simple PUT) or as a transaction bundle. - Yields NDJSON progress updates. - """ - files_processed_count = 0 - resource_map = {} - error_count = 0 - errors = [] - processed_filenames = set() - verbose = True - resources_uploaded_count = 0 - resources_parsed_list = [] - sorted_resources_ids = [] - validation_errors_count = 0 - validation_warnings_count = 0 - validation_failed_resources = set() - adj = defaultdict(list) - rev_adj = defaultdict(list) - in_degree = defaultdict(int) - nodes = set() - - try: - yield json.dumps({"type": "progress", "message": f"Scanning upload directory..."}) + "\n" - - # --- 1. List and Process Files --- - files_to_parse = [] - initial_files = [os.path.join(temp_file_dir, f) for f in os.listdir(temp_file_dir) if os.path.isfile(os.path.join(temp_file_dir, f))] - files_processed_count = len(initial_files) - for file_path in initial_files: - filename = os.path.basename(file_path) - if filename.lower().endswith('.zip'): - yield json.dumps({"type": "progress", "message": f"Extracting ZIP: {filename}..."}) + "\n" - try: - with zipfile.ZipFile(file_path, 'r') as zip_ref: - extracted_count = 0 - for member in zip_ref.namelist(): - if member.endswith('/') or member.startswith('__MACOSX') or member.startswith('.'): continue - member_filename = os.path.basename(member) - if not member_filename: continue - if member_filename.lower().endswith(('.json', '.xml')): - target_path = os.path.join(temp_file_dir, member_filename) - if not os.path.exists(target_path): - with zip_ref.open(member) as source, open(target_path, "wb") as target: - shutil.copyfileobj(source, target) - files_to_parse.append(target_path) - extracted_count += 1 - else: - yield json.dumps({"type": "warning", "message": f"Skipped extracting '{member_filename}' from ZIP, file exists."}) + "\n" - yield json.dumps({"type": "info", "message": f"Extracted {extracted_count} JSON/XML files from {filename}."}) + "\n" - processed_filenames.add(filename) - except zipfile.BadZipFile: - error_msg = f"Invalid ZIP: {filename}" - yield json.dumps({"type": "error", "message": error_msg}) + "\n" - errors.append(error_msg) - error_count += 1 - except Exception as e: - error_msg = f"Error extracting ZIP {filename}: {e}" - yield json.dumps({"type": "error", "message": error_msg}) + "\n" - errors.append(error_msg) - error_count += 1 - elif filename.lower().endswith(('.json', '.xml')): - files_to_parse.append(file_path) - yield json.dumps({"type": "info", "message": f"Found {len(files_to_parse)} JSON/XML files to parse."}) + "\n" - - # --- 2. Parse JSON/XML Files --- - temp_resources_parsed = [] - for file_path in files_to_parse: - filename = os.path.basename(file_path) - if filename in processed_filenames: - continue - processed_filenames.add(filename) - yield json.dumps({"type": "progress", "message": f"Parsing {filename}..."}) + "\n" - try: - with open(file_path, 'r', encoding='utf-8-sig') as f: - content = f.read() - parsed_content_list = [] - if filename.lower().endswith('.json'): - try: - parsed_json = json.loads(content) - if isinstance(parsed_json, dict) and parsed_json.get('resourceType') == 'Bundle': - for entry_idx, entry in enumerate(parsed_json.get('entry', [])): - resource = entry.get('resource') - if isinstance(resource, dict) and 'resourceType' in resource and 'id' in resource: - parsed_content_list.append(resource) - elif resource: - yield json.dumps({"type": "warning", "message": f"Skipping invalid resource #{entry_idx+1} in Bundle {filename}."}) + "\n" - elif isinstance(parsed_json, dict) and 'resourceType' in parsed_json and 'id' in parsed_json: - parsed_content_list.append(parsed_json) - elif isinstance(parsed_json, list): - yield json.dumps({"type": "warning", "message": f"File {filename} contains JSON array."}) + "\n" - for item_idx, item in enumerate(parsed_json): - if isinstance(item, dict) and 'resourceType' in item and 'id' in item: - parsed_content_list.append(item) - else: - yield json.dumps({"type": "warning", "message": f"Skipping invalid item #{item_idx+1} in JSON array {filename}."}) + "\n" - else: - raise ValueError("Not valid FHIR Resource/Bundle.") - except json.JSONDecodeError as e: - raise ValueError(f"Invalid JSON: {e}") - elif filename.lower().endswith('.xml'): - if FHIR_RESOURCES_AVAILABLE: - try: - root = ET.fromstring(content) - resource_type = root.tag - if not resource_type: - raise ValueError("XML root tag missing.") - temp_dict = basic_fhir_xml_to_dict(content) - if temp_dict: - model_class = get_fhir_model_class(resource_type) - fhir_resource = model_class(**temp_dict) - resource_dict = fhir_resource.dict(exclude_none=True) - if 'id' in resource_dict: - parsed_content_list.append(resource_dict) - yield json.dumps({"type": "info", "message": f"Parsed/validated XML: {filename}"}) + "\n" - else: - yield json.dumps({"type": "warning", "message": f"Parsed XML {filename} missing 'id'. Skipping."}) + "\n" - else: - raise ValueError("Basic XML to Dict failed.") - except (ET.ParseError, FHIRValidationError, ValueError, NotImplementedError, Exception) as e: - raise ValueError(f"Invalid/Unsupported FHIR XML: {e}") - else: - parsed_content = basic_fhir_xml_to_dict(content) - if parsed_content and parsed_content.get("resourceType") and parsed_content.get("id"): - yield json.dumps({"type": "warning", "message": f"Parsed basic XML (no validation): {filename}"}) + "\n" - parsed_content_list.append(parsed_content) - else: - yield json.dumps({"type": "warning", "message": f"Basic XML parse failed or missing type/id: {filename}. Skipping."}) + "\n" - continue - if parsed_content_list: - temp_resources_parsed.extend(parsed_content_list) - else: - yield json.dumps({"type": "warning", "message": f"Skipping {filename}: No valid content."}) + "\n" - except (IOError, ValueError, Exception) as e: - error_msg = f"Error processing file {filename}: {e}" - yield json.dumps({"type": "error", "message": error_msg}) + "\n" - errors.append(error_msg) - error_count += 1 - logger.error(f"Error processing file {filename}", exc_info=True) - - # Populate Resource Map - for resource in temp_resources_parsed: - res_type = resource.get('resourceType') - res_id = resource.get('id') - if res_type and res_id: - full_id = f"{res_type}/{res_id}" - if full_id not in resource_map: - resource_map[full_id] = resource - else: - yield json.dumps({"type": "warning", "message": f"Duplicate ID: {full_id}. Using first."}) + "\n" - else: - yield json.dumps({"type": "warning", "message": f"Parsed resource missing type/id: {str(resource)[:100]}..."}) + "\n" - resources_parsed_list = list(resource_map.values()) - yield json.dumps({"type": "info", "message": f"Parsed {len(resources_parsed_list)} unique resources."}) + "\n" - - # --- 2.5 Pre-Upload Validation Step --- - if options.get('validate_before_upload'): - validation_package_id = options.get('validation_package_id') - if not validation_package_id or '#' not in validation_package_id: - raise ValueError("Validation package ID missing/invalid.") - val_pkg_name, val_pkg_version = validation_package_id.split('#', 1) - yield json.dumps({"type": "progress", "message": f"Starting validation against {val_pkg_name}#{val_pkg_version}..."}) + "\n" - validated_resources_map = {} - for resource in resources_parsed_list: - full_id = f"{resource.get('resourceType')}/{resource.get('id')}" - yield json.dumps({"type": "validation_info", "message": f"Validating {full_id}..."}) + "\n" - try: - validation_report = validate_resource_against_profile(val_pkg_name, val_pkg_version, resource, include_dependencies=False) - for warning in validation_report.get('warnings', []): - yield json.dumps({"type": "validation_warning", "message": f"{full_id}: {warning}"}) + "\n" - validation_warnings_count += 1 - if not validation_report.get('valid', False): - validation_failed_resources.add(full_id) - validation_errors_count += 1 - for error in validation_report.get('errors', []): - error_detail = f"Validation Error ({full_id}): {error}" - yield json.dumps({"type": "validation_error", "message": error_detail}) + "\n" - errors.append(error_detail) - if options.get('error_handling', 'stop') == 'stop': - raise ValueError(f"Validation failed for {full_id} (stop on error).") - else: - validated_resources_map[full_id] = resource - except Exception as val_err: - error_msg = f"Validation error {full_id}: {val_err}" - yield json.dumps({"type": "error", "message": error_msg}) + "\n" - errors.append(error_msg) - error_count += 1 - validation_failed_resources.add(full_id) - validation_errors_count += 1 - logger.error(f"Validation exception {full_id}", exc_info=True) - if options.get('error_handling', 'stop') == 'stop': - raise ValueError(f"Validation exception for {full_id} (stop on error).") - yield json.dumps({"type": "info", "message": f"Validation complete. Errors: {validation_errors_count}, Warnings: {validation_warnings_count}."}) + "\n" - resource_map = validated_resources_map - nodes = set(resource_map.keys()) - yield json.dumps({"type": "info", "message": f"Proceeding with {len(nodes)} valid resources."}) + "\n" - else: - yield json.dumps({"type": "info", "message": "Pre-upload validation skipped."}) + "\n" - nodes = set(resource_map.keys()) - - # --- 3. Build Dependency Graph --- - yield json.dumps({"type": "progress", "message": "Building dependency graph..."}) + "\n" - dependency_count = 0 - external_refs = defaultdict(list) - for full_id, resource in resource_map.items(): - refs_list = [] - find_references(resource, refs_list) - if refs_list: - if verbose: - yield json.dumps({"type": "info", "message": f"Processing {len(refs_list)} refs in {full_id}"}) + "\n" - for ref_str in refs_list: - target_full_id = None - if isinstance(ref_str, str) and '/' in ref_str and not ref_str.startswith('#'): - parts = ref_str.split('/') - if len(parts) == 2 and parts[0] and parts[1]: - target_full_id = ref_str - elif len(parts) > 2: - try: - parsed_url = urlparse(ref_str) - if parsed_url.path: - path_parts = parsed_url.path.strip('/').split('/') - if len(path_parts) >= 2 and path_parts[-2] and path_parts[-1]: - target_full_id = f"{path_parts[-2]}/{path_parts[-1]}" - except: - pass - if target_full_id and target_full_id != full_id: - if target_full_id in resource_map: - if target_full_id not in adj[full_id]: - adj[full_id].append(target_full_id) - rev_adj[target_full_id].append(full_id) - in_degree[full_id] += 1 - dependency_count += 1 - if verbose: - yield json.dumps({"type": "info", "message": f" Dep Added: {full_id} -> {target_full_id}"}) + "\n" - else: - target_failed_validation = options.get('validate_before_upload') and target_full_id in validation_failed_resources - if not target_failed_validation and verbose: - yield json.dumps({"type": "warning", "message": f"Ref '{ref_str}' in {full_id} points outside processed set ({target_full_id})."}) + "\n" - external_refs[full_id].append(ref_str) - yield json.dumps({"type": "info", "message": f"Graph built for {len(nodes)} resources. Internal Deps: {dependency_count}."}) + "\n" - - # --- 4. Perform Topological Sort --- - yield json.dumps({"type": "progress", "message": "Sorting resources by dependency..."}) + "\n" - sorted_resources_ids = [] - queue = deque([node for node in nodes if in_degree[node] == 0]) - processed_count = 0 - while queue: - u = queue.popleft() - sorted_resources_ids.append(u) - processed_count += 1 - if u in rev_adj: - for v in rev_adj[u]: - in_degree[v] -= 1 - if in_degree[v] == 0: - queue.append(v) - if processed_count != len(nodes): - cycle_nodes = sorted([node for node in nodes if in_degree[node] > 0]) - error_msg = f"Circular dependency detected. Involved: {', '.join(cycle_nodes[:10])}{'...' if len(cycle_nodes) > 10 else ''}" - yield json.dumps({"type": "error", "message": error_msg}) + "\n" - errors.append(error_msg) - error_count += 1 - raise ValueError("Circular dependency detected") - yield json.dumps({"type": "info", "message": f"Topological sort successful. Order determined for {len(sorted_resources_ids)} resources."}) + "\n" - - # --- 5. Upload Sorted Resources --- - if not sorted_resources_ids: - yield json.dumps({"type": "info", "message": "No valid resources remaining to upload."}) + "\n" - else: - upload_mode = options.get('upload_mode', 'individual') - error_handling_mode = options.get('error_handling', 'stop') - use_conditional = options.get('use_conditional_uploads', False) and upload_mode == 'individual' - session = requests.Session() - base_url = server_info['url'].rstrip('/') - upload_headers = {'Content-Type': 'application/fhir+json', 'Accept': 'application/fhir+json'} - if server_info['auth_type'] in ['bearerToken', 'basic'] and server_info.get('auth_token'): - # Log the Authorization header (mask sensitive data) - auth_header = server_info['auth_token'] - if auth_header.startswith('Basic '): - auth_display = 'Basic ' - else: - auth_display = auth_header[:10] + '...' if len(auth_header) > 10 else auth_header - yield json.dumps({"type": "info", "message": f"Using {server_info['auth_type']} auth with header: Authorization: {auth_display}"}) + "\n" - upload_headers['Authorization'] = server_info['auth_token'] # FIXED: Use server_info['auth_token'] - else: - yield json.dumps({"type": "info", "message": "Using no auth."}) + "\n" - - if upload_mode == 'transaction': - # --- Transaction Bundle Upload --- - yield json.dumps({"type": "progress", "message": f"Preparing transaction bundle for {len(sorted_resources_ids)} resources..."}) + "\n" - transaction_bundle = {"resourceType": "Bundle", "type": "transaction", "entry": []} - for full_id in sorted_resources_ids: - resource = resource_map.get(full_id) - if resource: - res_type = resource.get('resourceType') - res_id = resource.get('id') - entry = { - "fullUrl": f"{base_url}/{res_type}/{res_id}", - "resource": resource, - "request": {"method": "PUT", "url": f"{res_type}/{res_id}"} - } - transaction_bundle["entry"].append(entry) - if not transaction_bundle["entry"]: - yield json.dumps({"type": "warning", "message": "No valid entries for transaction."}) + "\n" - else: - yield json.dumps({"type": "progress", "message": f"Uploading transaction bundle ({len(transaction_bundle['entry'])} entries)..."}) + "\n" - try: - response = session.post(base_url, json=transaction_bundle, headers=upload_headers, timeout=120) - response.raise_for_status() - response_bundle = response.json() - current_bundle_success = 0 - current_bundle_errors = 0 - for entry in response_bundle.get("entry", []): - entry_response = entry.get("response", {}) - status = entry_response.get("status", "") - location = entry_response.get("location", "N/A") - resource_ref = location.split('/')[-3] + '/' + location.split('/')[-1] if status.startswith("201") and '/_history/' in location else location - if status.startswith("200") or status.startswith("201"): - current_bundle_success += 1 - else: - current_bundle_errors += 1 - outcome = entry.get("resource") - outcome_text = f"Status: {status}" - if outcome and outcome.get('resourceType') == 'OperationOutcome': - try: - issue_texts = [] - for issue in outcome.get('issue', []): - severity = issue.get('severity', 'info') - diag = issue.get('diagnostics') or issue.get('details', {}).get('text', 'No details') - issue_texts.append(f"{severity}: {diag}") - if issue_texts: - outcome_text += "; " + "; ".join(issue_texts) - except Exception as parse_err: - logger.warning(f"Could not parse OperationOutcome details: {parse_err}") - error_msg = f"Txn entry failed for '{resource_ref}'. {outcome_text}" - yield json.dumps({"type": "error", "message": error_msg}) + "\n" - errors.append(error_msg) - if error_handling_mode == 'stop': - break - resources_uploaded_count += current_bundle_success - error_count += current_bundle_errors - yield json.dumps({"type": "success", "message": f"Txn processed. Success: {current_bundle_success}, Errors: {current_bundle_errors}."}) + "\n" - if current_bundle_errors > 0 and error_handling_mode == 'stop': - raise ValueError("Stopping due to transaction error.") - except requests.exceptions.HTTPError as e: - outcome_text = "" - if e.response is not None: - try: - outcome = e.response.json() - if outcome and outcome.get('resourceType') == 'OperationOutcome': - issue_texts = [] - for issue in outcome.get('issue', []): - severity = issue.get('severity', 'info') - diag = issue.get('diagnostics') or issue.get('details', {}).get('text', 'No details') - issue_texts.append(f"{severity}: {diag}") - if issue_texts: - outcome_text = "; ".join(issue_texts) - else: - outcome_text = e.response.text[:300] - else: - outcome_text = e.response.text[:300] - except ValueError: - outcome_text = e.response.text[:300] - else: - outcome_text = "No response body." - error_msg = f"Txn POST failed (Status: {e.response.status_code if e.response is not None else 'N/A'}): {outcome_text or str(e)}" - yield json.dumps({"type": "error", "message": error_msg}) + "\n" - errors.append(error_msg) - error_count += len(transaction_bundle["entry"]) - raise ValueError("Stopping due to transaction POST error.") - except requests.exceptions.RequestException as e: - error_msg = f"Network error posting txn: {e}" - yield json.dumps({"type": "error", "message": error_msg}) + "\n" - errors.append(error_msg) - error_count += len(transaction_bundle["entry"]) - raise ValueError("Stopping due to transaction network error.") - except Exception as e: - error_msg = f"Error processing txn response: {e}" - yield json.dumps({"type": "error", "message": error_msg}) + "\n" - errors.append(error_msg) - error_count += len(transaction_bundle["entry"]) - logger.error("Txn response error", exc_info=True) - raise ValueError("Stopping due to txn response error.") - - else: - # --- Individual Resource Upload --- - yield json.dumps({"type": "progress", "message": f"Starting individual upload ({'conditional' if use_conditional else 'simple PUT'})..."}) + "\n" - for i, full_id in enumerate(sorted_resources_ids): - resource_to_upload = resource_map.get(full_id) - if not resource_to_upload: - continue - res_type = resource_to_upload.get('resourceType') - res_id = resource_to_upload.get('id') - target_url_put = f"{base_url}/{res_type}/{res_id}" - target_url_post = f"{base_url}/{res_type}" - - current_headers = upload_headers.copy() - action_log_prefix = f"Uploading {full_id} ({i+1}/{len(sorted_resources_ids)})" - etag = None - resource_exists = False - method = "PUT" - target_url = target_url_put - log_action = "Uploading (PUT)" # Defaults for simple PUT - - # --- Conditional Logic --- - if use_conditional: - yield json.dumps({"type": "progress", "message": f"{action_log_prefix}: Checking existence..."}) + "\n" - try: - get_response = session.get(target_url_put, headers=current_headers, timeout=15) - if get_response.status_code == 200: - resource_exists = True - etag = get_response.headers.get('ETag') - if etag: - current_headers['If-Match'] = etag - log_action = "Updating (conditional)" - yield json.dumps({"type": "info", "message": f" Resource exists. ETag: {etag}. Will use conditional PUT."}) + "\n" - else: - log_action = "Updating (no ETag)" - yield json.dumps({"type": "warning", "message": f" Resource exists but no ETag found. Will use simple PUT."}) + "\n" - method = "PUT" - target_url = target_url_put - elif get_response.status_code == 404: - resource_exists = False - method = "PUT" - target_url = target_url_put # Use PUT for creation with specific ID - log_action = "Creating (PUT)" - yield json.dumps({"type": "info", "message": f" Resource not found. Will use PUT to create."}) + "\n" - else: - get_response.raise_for_status() - except requests.exceptions.HTTPError as http_err: - error_msg = f"Error checking existence for {full_id} (Status: {http_err.response.status_code}). Cannot proceed conditionally." - yield json.dumps({"type": "error", "message": error_msg}) + "\n" - errors.append(f"{full_id}: {error_msg}") - error_count += 1 - if error_handling_mode == 'stop': - raise ValueError("Stopping due to existence check error.") - continue - except requests.exceptions.RequestException as req_err: - error_msg = f"Network error checking existence for {full_id}: {req_err}. Cannot proceed conditionally." - yield json.dumps({"type": "error", "message": error_msg}) + "\n" - errors.append(f"{full_id}: {error_msg}") - error_count += 1 - if error_handling_mode == 'stop': - raise ValueError("Stopping due to existence check network error.") - continue - - # --- Perform Upload Action --- - try: - yield json.dumps({"type": "progress", "message": f"{action_log_prefix}: {log_action}..."}) + "\n" - if method == "POST": - response = session.post(target_url, json=resource_to_upload, headers=current_headers, timeout=30) - else: - response = session.put(target_url, json=resource_to_upload, headers=current_headers, timeout=30) - response.raise_for_status() - - status_code = response.status_code - success_msg = f"{log_action} successful for {full_id} (Status: {status_code})" - if method == "POST" and status_code == 201: - location = response.headers.get('Location') - success_msg += f" Loc: {location}" if location else "" - yield json.dumps({"type": "success", "message": success_msg}) + "\n" - resources_uploaded_count += 1 - - except requests.exceptions.HTTPError as e: - status_code = e.response.status_code if e.response is not None else 'N/A' - outcome_text = "" - if e.response is not None: - try: - outcome = e.response.json() - if outcome and outcome.get('resourceType') == 'OperationOutcome': - issue_texts = [] - for issue in outcome.get('issue', []): - severity = issue.get('severity', 'info') - diag = issue.get('diagnostics') or issue.get('details', {}).get('text', 'No details') - issue_texts.append(f"{severity}: {diag}") - if issue_texts: - outcome_text = "; ".join(issue_texts) - else: - outcome_text = e.response.text[:200] - else: - outcome_text = e.response.text[:200] - except ValueError: - outcome_text = e.response.text[:200] - else: - outcome_text = "No response body." - error_prefix = "Conditional update failed" if status_code == 412 else f"{method} failed" - error_msg = f"{error_prefix} for {full_id} (Status: {status_code}): {outcome_text or str(e)}" - yield json.dumps({"type": "error", "message": error_msg}) + "\n" - errors.append(f"{full_id}: {error_msg}") - error_count += 1 - if error_handling_mode == 'stop': - raise ValueError(f"Stopping due to {method} error.") - except requests.exceptions.Timeout: - error_msg = f"Timeout during {method} for {full_id}" - yield json.dumps({"type": "error", "message": error_msg}) + "\n" - errors.append(f"{full_id}: {error_msg}") - error_count += 1 - if error_handling_mode == 'stop': - raise ValueError("Stopping due to upload timeout.") - except requests.exceptions.ConnectionError as e: - error_msg = f"Connection error during {method} for {full_id}: {e}" - yield json.dumps({"type": "error", "message": error_msg}) + "\n" - errors.append(f"{full_id}: {error_msg}") - error_count += 1 - if error_handling_mode == 'stop': - raise ValueError("Stopping due to connection error.") - except requests.exceptions.RequestException as e: - error_msg = f"Request error during {method} for {full_id}: {str(e)}" - yield json.dumps({"type": "error", "message": error_msg}) + "\n" - errors.append(f"{full_id}: {error_msg}") - error_count += 1 - if error_handling_mode == 'stop': - raise ValueError("Stopping due to request error.") - except Exception as e: - error_msg = f"Unexpected error during {method} for {full_id}: {str(e)}" - yield json.dumps({"type": "error", "message": error_msg}) + "\n" - errors.append(f"{full_id}: {error_msg}") - error_count += 1 - logger.error(f"Upload error for {full_id}", exc_info=True) - if error_handling_mode == 'stop': - raise ValueError("Stopping due to unexpected upload error.") - - yield json.dumps({"type": "info", "message": f"Individual upload loop finished."}) + "\n" - - except ValueError as ve: - logger.error(f"Processing stopped: {ve}") - except Exception as e: - logger.error(f"Critical error: {e}", exc_info=True) - error_count += 1 - errors.append(f"Critical Error: {str(e)}") - yield json.dumps({"type": "error", "message": f"Critical error: {str(e)}"}) + "\n" - - # --- Final Summary --- - final_status = "unknown" - total_errors = error_count + validation_errors_count - if total_errors > 0: - final_status = "failure" if resources_uploaded_count == 0 else "partial" - elif resource_map or resources_parsed_list: - final_status = "success" - elif files_processed_count > 0: - final_status = "success" - else: - final_status = "success" - summary_message = f"Processing finished. Status: {final_status}. Files: {files_processed_count}, Parsed: {len(resources_parsed_list)}, Validation Errors: {validation_errors_count}, Validation Warnings: {validation_warnings_count}, Uploaded: {resources_uploaded_count}, Upload Errors: {error_count}." - summary = { - "status": final_status, - "message": summary_message, - "files_processed": files_processed_count, - "resources_parsed": len(resources_parsed_list), - "validation_errors": validation_errors_count, - "validation_warnings": validation_warnings_count, - "resources_uploaded": resources_uploaded_count, - "error_count": error_count, - "errors": errors - } - yield json.dumps({"type": "complete", "data": summary}) + "\n" - logger.info(f"[Upload Test Data] Completed. Status: {final_status}. {summary_message}") - -# --- END Service Function --- - -# --- CORRECTED retrieve_bundles function with NEW logic --- -def retrieve_bundles(fhir_server_url, resources, output_zip, validate_references=False, fetch_reference_bundles=False, auth_type='none', auth_token=None): - """ - Retrieve FHIR bundles and save to a ZIP file. - Optionally fetches referenced resources, either individually by ID or as full bundles by type. - Supports authentication for custom FHIR servers. - Yields NDJSON progress updates. - """ - temp_dir = None - try: - total_initial_bundles = 0 - fetched_individual_references = 0 - fetched_type_bundles = 0 - retrieved_references_or_types = set() - - temp_dir = tempfile.mkdtemp(prefix="fhir_retrieve_") - logger.debug(f"Created temporary directory for bundle retrieval: {temp_dir}") - yield json.dumps({"type": "progress", "message": f"Starting bundle retrieval for {len(resources)} resource types"}) + "\n" - if validate_references: - yield json.dumps({"type": "info", "message": f"Reference fetching ON (Mode: {'Full Type Bundles' if fetch_reference_bundles else 'Individual Resources'})"}) + "\n" - else: - yield json.dumps({"type": "info", "message": "Reference fetching OFF"}) + "\n" - - # Determine Base URL and Headers for Proxy - base_proxy_url = f"{current_app.config['APP_BASE_URL'].rstrip('/')}/fhir" - headers = {'Accept': 'application/fhir+json, application/fhir+xml;q=0.9, */*;q=0.8'} - is_custom_url = fhir_server_url != '/fhir' and fhir_server_url is not None and fhir_server_url.startswith('http') - if is_custom_url: - headers['X-Target-FHIR-Server'] = fhir_server_url.rstrip('/') - if auth_type in ['bearer', 'basic'] and auth_token: - auth_display = 'Basic ' if auth_type == 'basic' else (auth_token[:10] + '...' if len(auth_token) > 10 else auth_token) - yield json.dumps({"type": "info", "message": f"Using {auth_type} auth with header: Authorization: {auth_display}"}) + "\n" - headers['Authorization'] = auth_token - else: - yield json.dumps({"type": "info", "message": "Using no authentication for custom URL"}) + "\n" - logger.debug(f"Will use proxy with X-Target-FHIR-Server: {headers['X-Target-FHIR-Server']}") - else: - yield json.dumps({"type": "info", "message": "Using no authentication for local HAPI server"}) + "\n" - logger.debug("Will use proxy targeting local HAPI server") - - # Fetch Initial Bundles - initial_bundle_files = [] - for resource_type in resources: - url = f"{base_proxy_url}/{quote(resource_type)}" - yield json.dumps({"type": "progress", "message": f"Fetching bundle for {resource_type} via proxy..."}) + "\n" - logger.debug(f"Sending GET request to proxy {url} with headers: {json.dumps(headers)}") - try: - response = requests.get(url, headers=headers, timeout=60) - logger.debug(f"Proxy response for {resource_type}: HTTP {response.status_code}") - if response.status_code != 200: - error_detail = f"Proxy returned HTTP {response.status_code}." - try: error_detail += f" Body: {response.text[:200]}..." - except: pass - yield json.dumps({"type": "error", "message": f"Failed to fetch {resource_type}: {error_detail}"}) + "\n" - logger.error(f"Failed to fetch {resource_type} via proxy {url}: {error_detail}") - continue - try: - bundle = response.json() - except ValueError as e: - yield json.dumps({"type": "error", "message": f"Invalid JSON response for {resource_type}: {str(e)}"}) + "\n" - logger.error(f"Invalid JSON from proxy for {resource_type} at {url}: {e}, Response: {response.text[:500]}") - continue - if not isinstance(bundle, dict) or bundle.get('resourceType') != 'Bundle': - yield json.dumps({"type": "error", "message": f"Expected Bundle for {resource_type}, got {bundle.get('resourceType', 'unknown')}"}) + "\n" - logger.error(f"Expected Bundle for {resource_type}, got {bundle.get('resourceType', 'unknown')}") - continue - if not bundle.get('entry'): - yield json.dumps({"type": "warning", "message": f"No entries found in bundle for {resource_type}"}) + "\n" - - # Save the bundle - output_file = os.path.join(temp_dir, f"{resource_type}_bundle.json") - try: - with open(output_file, 'w', encoding='utf-8') as f: - json.dump(bundle, f, indent=2) - logger.debug(f"Wrote bundle to {output_file}") - initial_bundle_files.append(output_file) - total_initial_bundles += 1 - yield json.dumps({"type": "success", "message": f"Saved bundle for {resource_type}"}) + "\n" - except IOError as e: - yield json.dumps({"type": "error", "message": f"Failed to save bundle file for {resource_type}: {e}"}) + "\n" - logger.error(f"Failed to write bundle file {output_file}: {e}") - continue - except requests.RequestException as e: - yield json.dumps({"type": "error", "message": f"Error connecting to proxy for {resource_type}: {str(e)}"}) + "\n" - logger.error(f"Error retrieving bundle for {resource_type} via proxy {url}: {e}") - continue - except Exception as e: - yield json.dumps({"type": "error", "message": f"Unexpected error fetching {resource_type}: {str(e)}"}) + "\n" - logger.error(f"Unexpected error during initial fetch for {resource_type} at {url}: {e}", exc_info=True) - continue - - # Fetch Referenced Resources (Conditionally) - if validate_references and initial_bundle_files: - yield json.dumps({"type": "progress", "message": "Scanning retrieved bundles for references..."}) + "\n" - all_references = set() - references_by_type = defaultdict(set) - - # Scan for References - for bundle_file_path in initial_bundle_files: - try: - with open(bundle_file_path, 'r', encoding='utf-8') as f: - bundle = json.load(f) - for entry in bundle.get('entry', []): - resource = entry.get('resource') - if resource: - current_refs = [] - find_references(resource, current_refs) - for ref_str in current_refs: - if isinstance(ref_str, str) and '/' in ref_str and not ref_str.startswith('#'): - all_references.add(ref_str) - try: - ref_type = ref_str.split('/')[0] - if ref_type: - references_by_type[ref_type].add(ref_str) - except Exception: - pass - except Exception as e: - yield json.dumps({"type": "warning", "message": f"Could not scan references in {os.path.basename(bundle_file_path)}: {e}"}) + "\n" - logger.warning(f"Error processing references in {bundle_file_path}: {e}") - - # Fetch Logic - if not all_references: - yield json.dumps({"type": "info", "message": "No references found to fetch."}) + "\n" - else: - if fetch_reference_bundles: - # Fetch Full Bundles by Type - unique_ref_types = sorted(list(references_by_type.keys())) - yield json.dumps({"type": "progress", "message": f"Fetching full bundles for {len(unique_ref_types)} referenced types..."}) + "\n" - logger.info(f"Fetching full bundles for referenced types: {unique_ref_types}") - - for ref_type in unique_ref_types: - if ref_type in retrieved_references_or_types: - continue - - url = f"{base_proxy_url}/{quote(ref_type)}" - yield json.dumps({"type": "progress", "message": f"Fetching full bundle for type {ref_type} via proxy..."}) + "\n" - logger.debug(f"Sending GET request for full type bundle {ref_type} to proxy {url} with headers: {json.dumps(headers)}") - try: - response = requests.get(url, headers=headers, timeout=180) - logger.debug(f"Proxy response for {ref_type} bundle: HTTP {response.status_code}") - if response.status_code != 200: - error_detail = f"Proxy returned HTTP {response.status_code}." - try: error_detail += f" Body: {response.text[:200]}..." - except: pass - yield json.dumps({"type": "warning", "message": f"Failed to fetch full bundle for {ref_type}: {error_detail}"}) + "\n" - logger.warning(f"Failed to fetch full bundle {ref_type} via proxy {url}: {error_detail}") - retrieved_references_or_types.add(ref_type) - continue - - try: - bundle = response.json() - except ValueError as e: - yield json.dumps({"type": "warning", "message": f"Invalid JSON for full {ref_type} bundle: {str(e)}"}) + "\n" - logger.warning(f"Invalid JSON response from proxy for full {ref_type} bundle at {url}: {e}") - retrieved_references_or_types.add(ref_type) - continue - - if not isinstance(bundle, dict) or bundle.get('resourceType') != 'Bundle': - yield json.dumps({"type": "warning", "message": f"Expected Bundle for full {ref_type} fetch, got {bundle.get('resourceType', 'unknown')}"}) + "\n" - logger.warning(f"Expected Bundle for full {ref_type} fetch, got {bundle.get('resourceType', 'unknown')}") - retrieved_references_or_types.add(ref_type) - continue - - # Save the full type bundle - output_file = os.path.join(temp_dir, f"ref_{ref_type}_BUNDLE.json") - try: - with open(output_file, 'w', encoding='utf-8') as f: - json.dump(bundle, f, indent=2) - logger.debug(f"Wrote full type bundle to {output_file}") - fetched_type_bundles += 1 - retrieved_references_or_types.add(ref_type) - yield json.dumps({"type": "success", "message": f"Saved full bundle for type {ref_type}"}) + "\n" - except IOError as e: - yield json.dumps({"type": "warning", "message": f"Failed to save full bundle file for {ref_type}: {e}"}) + "\n" - logger.error(f"Failed to write full bundle file {output_file}: {e}") - retrieved_references_or_types.add(ref_type) - except requests.RequestException as e: - yield json.dumps({"type": "warning", "message": f"Error connecting to proxy for full {ref_type} bundle: {str(e)}"}) + "\n" - logger.warning(f"Error retrieving full {ref_type} bundle via proxy: {e}") - retrieved_references_or_types.add(ref_type) - except Exception as e: - yield json.dumps({"type": "warning", "message": f"Unexpected error fetching full {ref_type} bundle: {str(e)}"}) + "\n" - logger.warning(f"Unexpected error during full {ref_type} bundle fetch: {e}", exc_info=True) - retrieved_references_or_types.add(ref_type) - else: - # Fetch Individual Referenced Resources - yield json.dumps({"type": "progress", "message": f"Fetching {len(all_references)} unique referenced resources individually..."}) + "\n" - logger.info(f"Fetching {len(all_references)} unique referenced resources by ID.") - for ref in sorted(list(all_references)): - if ref in retrieved_references_or_types: - continue - - try: - ref_parts = ref.split('/') - if len(ref_parts) != 2 or not ref_parts[0] or not ref_parts[1]: - logger.warning(f"Skipping invalid reference format: {ref}") - continue - ref_type, ref_id = ref_parts - - search_param = quote(f"_id={ref_id}") - url = f"{base_proxy_url}/{quote(ref_type)}?{search_param}" - yield json.dumps({"type": "progress", "message": f"Fetching referenced {ref_type}/{ref_id} via proxy..."}) + "\n" - logger.debug(f"Sending GET request for referenced {ref} to proxy {url} with headers: {json.dumps(headers)}") - - response = requests.get(url, headers=headers, timeout=60) - logger.debug(f"Proxy response for referenced {ref}: HTTP {response.status_code}") - - if response.status_code != 200: - error_detail = f"Proxy returned HTTP {response.status_code}." - try: error_detail += f" Body: {response.text[:200]}..." - except: pass - yield json.dumps({"type": "warning", "message": f"Failed to fetch referenced {ref}: {error_detail}"}) + "\n" - logger.warning(f"Failed to fetch referenced {ref} via proxy {url}: {error_detail}") - retrieved_references_or_types.add(ref) - continue - - try: - bundle = response.json() - except ValueError as e: - yield json.dumps({"type": "warning", "message": f"Invalid JSON for referenced {ref}: {str(e)}"}) + "\n" - logger.warning(f"Invalid JSON from proxy for ref {ref} at {url}: {e}") - retrieved_references_or_types.add(ref) - continue - - if not isinstance(bundle, dict) or bundle.get('resourceType') != 'Bundle': - yield json.dumps({"type": "warning", "message": f"Expected Bundle for referenced {ref}, got {bundle.get('resourceType', 'unknown')}"}) + "\n" - retrieved_references_or_types.add(ref) - continue - - if not bundle.get('entry'): - yield json.dumps({"type": "info", "message": f"Referenced resource {ref} not found on server."}) + "\n" - logger.info(f"Referenced resource {ref} not found via search {url}") - retrieved_references_or_types.add(ref) - continue - - # Save the bundle containing the single referenced resource - output_file = os.path.join(temp_dir, f"ref_{ref_type}_{ref_id}.json") - try: - with open(output_file, 'w', encoding='utf-8') as f: - json.dump(bundle, f, indent=2) - logger.debug(f"Wrote referenced resource bundle to {output_file}") - fetched_individual_references += 1 - retrieved_references_or_types.add(ref) - yield json.dumps({"type": "success", "message": f"Saved referenced resource {ref}"}) + "\n" - except IOError as e: - yield json.dumps({"type": "warning", "message": f"Failed to save file for referenced {ref}: {e}"}) + "\n" - logger.error(f"Failed to write file {output_file}: {e}") - retrieved_references_or_types.add(ref) - except requests.RequestException as e: - yield json.dumps({"type": "warning", "message": f"Network error fetching referenced {ref}: {str(e)}"}) + "\n" - logger.warning(f"Network error retrieving referenced {ref} via proxy: {e}") - retrieved_references_or_types.add(ref) - except Exception as e: - yield json.dumps({"type": "warning", "message": f"Unexpected error fetching referenced {ref}: {str(e)}"}) + "\n" - logger.warning(f"Unexpected error during reference fetch for {ref}: {e}", exc_info=True) - retrieved_references_or_types.add(ref) - - # Create Final ZIP File - yield json.dumps({"type": "progress", "message": f"Creating ZIP file {os.path.basename(output_zip)}..."}) + "\n" - files_to_zip = [f for f in os.listdir(temp_dir) if f.endswith('.json')] - if not files_to_zip: - yield json.dumps({"type": "warning", "message": "No bundle files were successfully retrieved to include in ZIP."}) + "\n" - logger.warning(f"No JSON files found in {temp_dir} to include in ZIP.") - else: - logger.debug(f"Found {len(files_to_zip)} JSON files to include in ZIP: {files_to_zip}") - try: - with zipfile.ZipFile(output_zip, 'w', zipfile.ZIP_DEFLATED) as zipf: - for filename in files_to_zip: - file_path = os.path.join(temp_dir, filename) - if os.path.exists(file_path): - zipf.write(file_path, filename) - else: - logger.error(f"File {file_path} disappeared before adding to ZIP.") - yield json.dumps({"type": "success", "message": f"ZIP file created: {os.path.basename(output_zip)} with {len(files_to_zip)} files."}) + "\n" - except Exception as e: - yield json.dumps({"type": "error", "message": f"Failed to create ZIP file: {e}"}) + "\n" - logger.error(f"Error creating ZIP file {output_zip}: {e}", exc_info=True) - - # Final Completion Message - completion_message = ( - f"Bundle retrieval finished. Initial bundles: {total_initial_bundles}, " - f"Referenced items fetched: {fetched_individual_references if not fetch_reference_bundles else fetched_type_bundles} " - f"({'individual resources' if not fetch_reference_bundles else 'full type bundles'})" - ) - yield json.dumps({ - "type": "complete", - "message": completion_message, - "data": { - "total_initial_bundles": total_initial_bundles, - "fetched_individual_references": fetched_individual_references, - "fetched_type_bundles": fetched_type_bundles, - "reference_mode": "individual" if validate_references and not fetch_reference_bundles else "type_bundle" if validate_references and fetch_reference_bundles else "off" - } - }) + "\n" - - except Exception as e: - yield json.dumps({"type": "error", "message": f"Critical error during retrieval setup: {str(e)}"}) + "\n" - logger.error(f"Unexpected error in retrieve_bundles setup: {e}", exc_info=True) - yield json.dumps({"type": "complete", "message": f"Retrieval failed: {str(e)}", "data": {"total_initial_bundles": 0, "fetched_individual_references": 0, "fetched_type_bundles": 0}}) + "\n" - finally: - if temp_dir and os.path.exists(temp_dir): - try: - shutil.rmtree(temp_dir) - logger.debug(f"Successfully removed temporary directory: {temp_dir}") - except Exception as cleanup_e: - logger.error(f"Error removing temporary directory {temp_dir}: {cleanup_e}", exc_info=True) -# --- End corrected retrieve_bundles function --- - -def split_bundles(input_zip_path, output_zip): - """Split FHIR bundles from a ZIP file into individual resource JSON files and save to a ZIP.""" - try: - total_resources = 0 - temp_dir = tempfile.mkdtemp() - yield json.dumps({"type": "progress", "message": f"Starting bundle splitting from ZIP"}) + "\n" - - # Extract input ZIP - with zipfile.ZipFile(input_zip_path, 'r') as zip_ref: - zip_ref.extractall(temp_dir) - yield json.dumps({"type": "progress", "message": f"Extracted input ZIP to temporary directory"}) + "\n" - - # Process JSON files - for filename in os.listdir(temp_dir): - if not filename.endswith('.json'): - continue - input_file = os.path.join(temp_dir, filename) - try: - with open(input_file, 'r', encoding='utf-8') as f: - bundle = json.load(f) - if bundle.get('resourceType') != 'Bundle': - yield json.dumps({"type": "error", "message": f"Skipping {filename}: Not a Bundle"}) + "\n" - continue - yield json.dumps({"type": "progress", "message": f"Processing bundle {filename}"}) + "\n" - index = 1 - for entry in bundle.get('entry', []): - resource = entry.get('resource') - if not resource or not resource.get('resourceType'): - yield json.dumps({"type": "error", "message": f"Invalid resource in {filename} at entry {index}"}) + "\n" - continue - resource_type = resource['resourceType'] - output_file = os.path.join(temp_dir, f"{resource_type}-{index}.json") - with open(output_file, 'w', encoding='utf-8') as f: - json.dump(resource, f, indent=2) - total_resources += 1 - yield json.dumps({"type": "success", "message": f"Saved {resource_type}-{index}.json"}) + "\n" - index += 1 - except Exception as e: - yield json.dumps({"type": "error", "message": f"Error processing {filename}: {str(e)}"}) + "\n" - logger.error(f"Error splitting bundle {filename}: {e}", exc_info=True) - - # Create output ZIP - with zipfile.ZipFile(output_zip, 'w', zipfile.ZIP_DEFLATED) as zipf: - for filename in os.listdir(temp_dir): - if filename.endswith('.json') and '-' in filename: - zipf.write(os.path.join(temp_dir, filename), filename) - yield json.dumps({ - "type": "complete", - "message": f"Bundle splitting completed. Extracted {total_resources} resources.", - "data": {"total_resources": total_resources} - }) + "\n" - except Exception as e: - yield json.dumps({"type": "error", "message": f"Unexpected error during splitting: {str(e)}"}) + "\n" - logger.error(f"Unexpected error in split_bundles: {e}", exc_info=True) - finally: - if os.path.exists(temp_dir): - for filename in os.listdir(temp_dir): - os.remove(os.path.join(temp_dir, filename)) - os.rmdir(temp_dir) - - -# --- Standalone Test --- -if __name__ == '__main__': - logger.info("Running services.py directly for testing.") - class MockFlask: - class Config(dict): - pass - config = Config() - instance_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'instance')) - mock_app = MockFlask() - test_download_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'instance', DOWNLOAD_DIR_NAME)) - mock_app.config['FHIR_PACKAGES_DIR'] = test_download_dir - os.makedirs(test_download_dir, exist_ok=True) - logger.info(f"Using test download directory: {test_download_dir}") - print("\n--- Testing Filename Parsing ---") - test_files = [ - "hl7.fhir.r4.core-4.0.1.tgz", - "hl7.fhir.us.core-6.1.0.tgz", - "fhir.myig.patient-1.2.3-beta.tgz", - "my.company.fhir.Terminologies-0.1.0.tgz", - "package-with-hyphens-in-name-1.0.tgz", - "noversion.tgz", - "badformat-1.0", - "hl7.fhir.au.core-1.1.0-preview.tgz", - ] - for tf in test_files: - p_name, p_ver = parse_package_filename(tf) - print(f"'{tf}' -> Name: '{p_name}', Version: '{p_ver}'") - pkg_name_to_test = "hl7.fhir.au.core" - pkg_version_to_test = "1.1.0-preview" - print(f"\n--- Testing Import: {pkg_name_to_test}#{pkg_version_to_test} ---") - import_results = import_package_and_dependencies(pkg_name_to_test, pkg_version_to_test, dependency_mode='recursive') - print("\nImport Results:") - print(f" Requested: {import_results['requested']}") - print(f" Downloaded Count: {len(import_results['downloaded'])}") - print(f" Unique Dependencies Found: {len(import_results['dependencies'])}") - print(f" Errors: {len(import_results['errors'])}") - for error in import_results['errors']: - print(f" - {error}") - if (pkg_name_to_test, pkg_version_to_test) in import_results['downloaded']: - test_tgz_path = import_results['downloaded'][(pkg_name_to_test, pkg_version_to_test)] - print(f"\n--- Testing Processing: {test_tgz_path} ---") - processing_results = process_package_file(test_tgz_path) - print("\nProcessing Results:") - print(f" Resource Types Info Count: {len(processing_results.get('resource_types_info', []))}") - print(f" Profiles with MS Elements: {sum(1 for r in processing_results.get('resource_types_info', []) if r.get('must_support'))}") - print(f" Optional Extensions w/ MS: {sum(1 for r in processing_results.get('resource_types_info', []) if r.get('optional_usage'))}") - print(f" Must Support Elements Dict Count: {len(processing_results.get('must_support_elements', {}))}") - print(f" Examples Dict Count: {len(processing_results.get('examples', {}))}") - print(f" Complies With Profiles: {processing_results.get('complies_with_profiles', [])}") - print(f" Imposed Profiles: {processing_results.get('imposed_profiles', [])}") - print(f" Processing Errors: {processing_results.get('errors', [])}") - else: - print(f"\n--- Skipping Processing Test (Import failed for {pkg_name_to_test}#{pkg_version_to_test}) ---") - diff --git a/setup_linux.sh b/setup_linux.sh deleted file mode 100644 index 2c05ae7..0000000 --- a/setup_linux.sh +++ /dev/null @@ -1,233 +0,0 @@ -#!/bin/bash - -# --- Configuration --- -REPO_URL="https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git" -CLONE_DIR="hapi-fhir-jpaserver" -SOURCE_CONFIG_DIR="hapi-fhir-Setup" # Assuming this is relative to the script's parent -CONFIG_FILE="application.yaml" - -# --- Define Paths --- -# Note: Adjust SOURCE_CONFIG_PATH if SOURCE_CONFIG_DIR is not a sibling directory -# This assumes the script is run from a directory, and hapi-fhir-setup is at the same level -SOURCE_CONFIG_PATH="../${SOURCE_CONFIG_DIR}/target/classes/${CONFIG_FILE}" -DEST_CONFIG_PATH="${CLONE_DIR}/target/classes/${CONFIG_FILE}" - -APP_MODE="" - -# --- Error Handling Function --- -handle_error() { - echo "------------------------------------" - echo "An error occurred: $1" - echo "Script aborted." - echo "------------------------------------" - # Removed 'read -p "Press Enter to exit..."' as it's not typical for non-interactive CI/CD - exit 1 -} - -# === Prompt for Installation Mode === -get_mode_choice() { - echo "Select Installation Mode:" - echo "1. Standalone (Includes local HAPI FHIR Server - Requires Git & Maven)" - echo "2. Lite (Excludes local HAPI FHIR Server - No Git/Maven needed)" - - while true; do - read -r -p "Enter your choice (1 or 2): " choice - case "$choice" in - 1) - APP_MODE="standalone" - break - ;; - 2) - APP_MODE="lite" - break - ;; - *) - echo "Invalid input. Please try again." - ;; - esac - done - echo "Selected Mode: $APP_MODE" - echo -} - -# Call the function to get mode choice -get_mode_choice - -# === Conditionally Execute HAPI Setup === -if [ "$APP_MODE" = "standalone" ]; then - echo "Running Standalone setup including HAPI FHIR..." - echo - - # --- Step 0: Clean up previous clone (optional) --- - echo "Checking for existing directory: $CLONE_DIR" - if [ -d "$CLONE_DIR" ]; then - echo "Found existing directory, removing it..." - rm -rf "$CLONE_DIR" - if [ $? -ne 0 ]; then - handle_error "Failed to remove existing directory: $CLONE_DIR" - fi - echo "Existing directory removed." - else - echo "Directory does not exist, proceeding with clone." - fi - echo - - # --- Step 1: Clone the HAPI FHIR server repository --- - echo "Cloning repository: $REPO_URL into $CLONE_DIR..." - git clone "$REPO_URL" "$CLONE_DIR" - if [ $? -ne 0 ]; then - handle_error "Failed to clone repository. Check Git installation and network connection." - fi - echo "Repository cloned successfully." - echo - - # --- Step 2: Navigate into the cloned directory --- - echo "Changing directory to $CLONE_DIR..." - cd "$CLONE_DIR" || handle_error "Failed to change directory to $CLONE_DIR." - echo "Current directory: $(pwd)" - echo - - # --- Step 3: Build the HAPI server using Maven --- - echo "===> Starting Maven build (Step 3)..." - mvn clean package -DskipTests=true -Pboot - if [ $? -ne 0 ]; then - echo "ERROR: Maven build failed." - cd .. - handle_error "Maven build process resulted in an error." - fi - echo "Maven build completed successfully." - echo - - # --- Step 4: Copy the configuration file --- - echo "===> Starting file copy (Step 4)..." - echo "Copying configuration file..." - # Corrected SOURCE_CONFIG_PATH to be relative to the new current directory ($CLONE_DIR) - # This assumes the original script's SOURCE_CONFIG_PATH was relative to its execution location - # If SOURCE_CONFIG_DIR is ../hapi-fhir-setup relative to script's original location: - # Then from within CLONE_DIR, it becomes ../../hapi-fhir-setup - # We defined SOURCE_CONFIG_PATH earlier relative to the script start. - # So, when inside CLONE_DIR, the path from original script location should be used. - # The original script had: set SOURCE_CONFIG_PATH=..\%SOURCE_CONFIG_DIR%\target\classes\%CONFIG_FILE% - # And then: xcopy "%SOURCE_CONFIG_PATH%" "target\classes\" - # This implies SOURCE_CONFIG_PATH is relative to the original script's location, not the $CLONE_DIR - # Therefore, we need to construct the correct relative path from *within* $CLONE_DIR back to the source. - # Assuming the script is in dir X, and SOURCE_CONFIG_DIR is ../hapi-fhir-setup from X. - # So, hapi-fhir-setup is a sibling of X's parent. - # If CLONE_DIR is also in X, then from within CLONE_DIR, the path is ../ + original SOURCE_CONFIG_PATH - # For simplicity and robustness, let's use an absolute path or a more clearly defined relative path from the start. - # The original `SOURCE_CONFIG_PATH=..\%SOURCE_CONFIG_DIR%\target\classes\%CONFIG_FILE%` implies - # that `hapi-fhir-setup` is a sibling of the directory where the script *is being run from*. - - # Let's assume the script is run from the root of FHIRFLARE-IG-Toolkit. - # And hapi-fhir-setup is also in the root, next to this script. - # Then SOURCE_CONFIG_PATH would be ./hapi-fhir-setup/target/classes/application.yaml - # And from within ./hapi-fhir-jpaserver/, the path would be ../hapi-fhir-setup/target/classes/application.yaml - - # The original batch file sets SOURCE_CONFIG_PATH as "..\%SOURCE_CONFIG_DIR%\target\classes\%CONFIG_FILE%" - # And COPIES it to "target\classes\" *while inside CLONE_DIR*. - # This means the source path is relative to where the *cd %CLONE_DIR%* happened from. - # Let's make it relative to the script's initial execution directory. - INITIAL_SCRIPT_DIR=$(pwd) - ABSOLUTE_SOURCE_CONFIG_PATH="${INITIAL_SCRIPT_DIR}/../${SOURCE_CONFIG_DIR}/target/classes/${CONFIG_FILE}" # This matches the ..\ logic - - echo "Source: $ABSOLUTE_SOURCE_CONFIG_PATH" - echo "Destination: target/classes/$CONFIG_FILE" - - if [ ! -f "$ABSOLUTE_SOURCE_CONFIG_PATH" ]; then - echo "WARNING: Source configuration file not found at $ABSOLUTE_SOURCE_CONFIG_PATH." - echo "The script will continue, but the server might use default configuration." - else - cp "$ABSOLUTE_SOURCE_CONFIG_PATH" "target/classes/" - if [ $? -ne 0 ]; then - echo "WARNING: Failed to copy configuration file. Check if the source file exists and permissions." - echo "The script will continue, but the server might use default configuration." - else - echo "Configuration file copied successfully." - fi - fi - echo - - # --- Step 5: Navigate back to the parent directory --- - echo "===> Changing directory back (Step 5)..." - cd .. || handle_error "Failed to change back to the parent directory." - echo "Current directory: $(pwd)" - echo - -else # APP_MODE is "lite" - echo "Running Lite setup, skipping HAPI FHIR build..." - # Ensure the hapi-fhir-jpaserver directory doesn't exist or is empty if Lite mode is chosen - if [ -d "$CLONE_DIR" ]; then - echo "Found existing HAPI directory ($CLONE_DIR) in Lite mode. Removing it..." - rm -rf "$CLONE_DIR" - fi - # Create empty target directories expected by Dockerfile COPY, even if not used - mkdir -p "${CLONE_DIR}/target/classes" - mkdir -p "${CLONE_DIR}/custom" # This was in the original batch, ensure it's here - # Create a placeholder empty WAR file and application.yaml to satisfy Dockerfile COPY - touch "${CLONE_DIR}/target/ROOT.war" - touch "${CLONE_DIR}/target/classes/application.yaml" - echo "Placeholder files and directories created for Lite mode build in $CLONE_DIR." - echo -fi - -# === Modify docker-compose.yml to set APP_MODE === -echo "Updating docker-compose.yml with APP_MODE=$APP_MODE..." -DOCKER_COMPOSE_TMP="docker-compose.yml.tmp" -DOCKER_COMPOSE_ORIG="docker-compose.yml" - -cat << EOF > "$DOCKER_COMPOSE_TMP" -version: '3.8' -services: - fhirflare: - build: - context: . - dockerfile: Dockerfile - ports: - - "5000:5000" - - "8080:8080" # Keep port exposed, even if Tomcat isn't running useful stuff in Lite - volumes: - - ./instance:/app/instance - - ./static/uploads:/app/static/uploads - - ./instance/hapi-h2-data/:/app/h2-data # Keep volume mounts consistent - - ./logs:/app/logs - environment: - - FLASK_APP=app.py - - FLASK_ENV=development - - NODE_PATH=/usr/lib/node_modules - - APP_MODE=${APP_MODE} - - APP_BASE_URL=http://localhost:5000 - - HAPI_FHIR_URL=http://localhost:8080/fhir - command: supervisord -c /etc/supervisord.conf -EOF - -if [ ! -f "$DOCKER_COMPOSE_TMP" ]; then - handle_error "Failed to create temporary docker-compose file ($DOCKER_COMPOSE_TMP)." -fi - -# Replace the original docker-compose.yml -mv "$DOCKER_COMPOSE_TMP" "$DOCKER_COMPOSE_ORIG" -echo "docker-compose.yml updated successfully." -echo - -# --- Step 6: Build Docker images --- -echo "===> Starting Docker build (Step 6)..." -docker-compose build --no-cache -if [ $? -ne 0 ]; then - handle_error "Docker Compose build failed. Check Docker installation and docker-compose.yml file." -fi -echo "Docker images built successfully." -echo - -# --- Step 7: Start Docker containers --- -echo "===> Starting Docker containers (Step 7)..." -docker-compose up -d -if [ $? -ne 0 ]; then - handle_error "Docker Compose up failed. Check Docker installation and container configurations." -fi -echo "Docker containers started successfully." -echo - -echo "====================================" -echo "Script finished successfully! (Mode: $APP_MODE)" -echo "====================================" -exit 0 diff --git a/supervisord.conf b/supervisord.conf deleted file mode 100644 index cdf08db..0000000 --- a/supervisord.conf +++ /dev/null @@ -1,36 +0,0 @@ -[supervisord] -nodaemon=true -logfile=/app/logs/supervisord.log -logfile_maxbytes=50MB -logfile_backups=10 -pidfile=/app/logs/supervisord.pid - -[program:flask] -command=/app/venv/bin/python /app/app.py -directory=/app -environment=FLASK_APP="app.py",FLASK_ENV="development",NODE_PATH="/usr/lib/node_modules" -autostart=true -autorestart=true -startsecs=10 -stopwaitsecs=10 -stdout_logfile=/app/logs/flask.log -stdout_logfile_maxbytes=10MB -stdout_logfile_backups=5 -stderr_logfile=/app/logs/flask_err.log -stderr_logfile_maxbytes=10MB -stderr_logfile_backups=5 - -[program:tomcat] -command=/usr/local/tomcat/bin/catalina.sh run -directory=/usr/local/tomcat -environment=SPRING_CONFIG_LOCATION="file:/usr/local/tomcat/conf/application.yaml",NODE_PATH="/usr/lib/node_modules" -autostart=false -autorestart=false -startsecs=30 -stopwaitsecs=30 -stdout_logfile=/app/logs/tomcat.log -stdout_logfile_maxbytes=10MB -stdout_logfile_backups=5 -stderr_logfile=/app/logs/tomcat_err.log -stderr_logfile_maxbytes=10MB -stderr_logfile_backups=5 \ No newline at end of file diff --git a/templates/_flash_messages.html b/templates/_flash_messages.html deleted file mode 100644 index c1f0288..0000000 --- a/templates/_flash_messages.html +++ /dev/null @@ -1,24 +0,0 @@ -{# templates/_flash_messages.html #} - -{# Check if there are any flashed messages #} -{% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {# Loop through messages and display them as Bootstrap alerts #} - {% for category, message in messages %} - {# Map Flask message categories (e.g., 'error', 'success', 'warning') to Bootstrap alert classes #} - {% set alert_class = 'alert-info' %} {# Default class #} - {% if category == 'error' or category == 'danger' %} - {% set alert_class = 'alert-danger' %} - {% elif category == 'success' %} - {% set alert_class = 'alert-success' %} - {% elif category == 'warning' %} - {% set alert_class = 'alert-warning' %} - {% endif %} - - - {% endfor %} - {% endif %} -{% endwith %} diff --git a/templates/_form_helpers.html b/templates/_form_helpers.html deleted file mode 100644 index f3534a4..0000000 --- a/templates/_form_helpers.html +++ /dev/null @@ -1,42 +0,0 @@ -{# app/templates/_form_helpers.html #} -{% macro render_field(field, label_visible=true) %} -
- {% if field.type == "BooleanField" %} -
- {{ field(class="form-check-input" + (" is-invalid" if field.errors else ""), **kwargs) }} - {% if label_visible and field.label %} - - {% endif %} - {% if field.description %} - {{ field.description }} - {% endif %} - {% if field.errors %} -
- {% for error in field.errors %} - {{ error }}
- {% endfor %} -
- {% endif %} -
- {% else %} - {% if label_visible and field.label %} - {{ field.label(class="form-label") }} - {% endif %} - {% set css_class = 'form-control ' + kwargs.pop('class', '') %} - {% if field.errors %} - {% set css_class = css_class + ' is-invalid' %} - {% endif %} - {{ field(class=css_class, **kwargs) }} - {% if field.description %} - {{ field.description }} - {% endif %} - {% if field.errors %} -
- {% for error in field.errors %} - {{ error }}
- {% endfor %} -
- {% endif %} - {% endif %} -
-{% endmacro %} \ No newline at end of file diff --git a/templates/_fsh_output.html b/templates/_fsh_output.html deleted file mode 100644 index a3927fe..0000000 --- a/templates/_fsh_output.html +++ /dev/null @@ -1,60 +0,0 @@ -{% from "_form_helpers.html" import render_field %} -
-
-
-
-
- {{ form.hidden_tag() }} - {{ render_field(form.package) }} - {{ render_field(form.input_mode) }} - - - {{ render_field(form.output_style) }} - {{ render_field(form.log_level) }} - {{ render_field(form.fhir_version) }} - {{ render_field(form.fishing_trip) }} - {{ render_field(form.dependencies, placeholder="One per line, e.g., hl7.fhir.us.core@6.1.0") }} - {{ render_field(form.indent_rules) }} - {{ render_field(form.meta_profile) }} - {{ render_field(form.alias_file) }} - {{ render_field(form.no_alias) }} -
- {{ form.submit(class="btn btn-success", id="submit-btn") }} - Back -
-
-
-
-
-
-{% if error %} -
{{ error }}
-{% endif %} -{% if fsh_output %} -
Conversion successful!
-

FSH Output

-
{{ fsh_output }}
-Download FSH -{% if comparison_report %} -

Fishing Trip Comparison Report

-Click here for SUSHI Validation -
-
- {% if comparison_report.differences %} -

Differences found in round-trip validation:

-
    - {% for diff in comparison_report.differences %} -
  • {{ diff.path }}: {{ diff.description }}
  • - {% endfor %} -
- {% else %} -

No differences found in round-trip validation.

- {% endif %} -
-
-{% endif %} -{% endif %} \ No newline at end of file diff --git a/templates/_search_results_table.html b/templates/_search_results_table.html deleted file mode 100644 index 91dbe6d..0000000 --- a/templates/_search_results_table.html +++ /dev/null @@ -1,113 +0,0 @@ -{# templates/_search_results_table.html #} -{# This partial template renders the search results table and pagination #} - -{% if packages %} - - - - - - - - - - {# Add Dependencies header if needed based on your data #} - {# #} - - - - {% for pkg in packages %} - - - - - - - - {# Add Dependencies data if needed #} - {# #} - - {% endfor %} - -
PackageLatestAuthorFHIRVersionsDependencies
- - {# Link to package details page - adjust if endpoint name is different #} - - {{ pkg.name }} - - {{ pkg.display_version }}{{ pkg.author or '' }}{{ pkg.fhir_version or '' }}{{ pkg.version_count }}{{ pkg.dependencies | join(', ') if pkg.dependencies else '' }}
- - {# Pagination Controls - Ensure 'pagination' object is passed from the route #} - {% if pagination and pagination.pages > 1 %} - - {% endif %} {# End pagination nav #} - -{% elif request and request.args.get('search') %} - {# Message when search term is present but no results found #} -

No packages found matching your search term.

-{% else %} - {# Initial message before any search #} -

Start typing in the search box above to find packages.

-{% endif %} diff --git a/templates/about.html b/templates/about.html deleted file mode 100644 index 8eb644f..0000000 --- a/templates/about.html +++ /dev/null @@ -1,66 +0,0 @@ -{% extends "base.html" %} - -{% block content %} -
- -

About FHIRFLARE IG Toolkit{% if app_mode == 'lite' %} (Lite Version){% endif %}

-
-

- A comprehensive toolkit designed for developers and implementers working with FHIR® Implementation Guides (IGs). -

-
-
- -
-
-
-

Overview

-

The FHIRFLARE IG Toolkit is a web application built to simplify the lifecycle of managing, processing, validating, and deploying FHIR Implementation Guides. It provides a central, user-friendly interface to handle common tasks associated with FHIR IGs, streamlining workflows and improving productivity.

-

Whether you're downloading the latest IG versions, checking compliance, converting resources to FHIR Shorthand (FSH), or pushing guides to a test server, this toolkit aims to be an essential companion.

- {% if app_mode == 'lite' %} - - {% else %} - - {% endif %} - - -

Core Features

-
    -
  • IG Package Management: Import FHIR IG packages directly from the registry using various version formats (e.g., `1.1.0-preview`, `current`), with flexible dependency handling (Recursive, Patch Canonical, Tree Shaking). View, process, unload, or delete downloaded packages, with detection of duplicate dependencies.
  • -
  • IG Processing & Viewing: Extract and display key information from processed IGs, including defined profiles, referenced resource types, must-support elements, and examples. Visualize profile relationships like `compliesWithProfile` and `imposeProfile`.
  • -
  • FHIR Validation: Validate individual FHIR resources or entire Bundles against the profiles defined within a selected IG. Provides detailed error and warning feedback. (Note: Validation accuracy is still under development, especially for complex constraints).
  • -
  • FHIR Server Interaction: -
      -
    • Push processed IGs (including dependencies) to a target FHIR server with real-time console feedback.
    • -
    • Explore FHIR server capabilities and interact with resources using the "FHIR API Explorer" (GET/POST/PUT/DELETE) and "FHIR UI Operations" pages, supporting both the local HAPI server (in Standalone mode) and external servers.
    • -
    -
  • -
  • FHIR Shorthand (FSH) Conversion: Convert FHIR JSON or XML resources to FSH using the integrated GoFSH tool. Offers advanced options like context package selection, various output styles, FHIR version selection, dependency loading, alias file usage, and round-trip validation ("Fishing Trip") with SUSHI. Includes a loading indicator during conversion.
  • -
  • API Support: Provides basic API endpoints for programmatic import and push operations.
  • -
- -

Technology

-

The toolkit leverages a combination of technologies:

-
    -
  • Backend: Python with the Flask web framework and SQLAlchemy for database interaction (SQLite).
  • -
  • Frontend: HTML, Bootstrap 5 for styling, and JavaScript for interactivity and dynamic content loading. Uses Lottie-Web for animations.
  • -
  • FHIR Tooling: Integrates GoFSH and SUSHI (via Node.js) for FSH conversion and validation. Utilizes the HAPI FHIR server (in Standalone mode) for robust FHIR validation and operations.
  • -
  • Deployment: Runs within a Docker container managed by Docker Compose and Supervisor, ensuring a consistent environment.
  • -
- -

Get Involved

-

This is an open-source project. Contributions, feedback, and bug reports are welcome!

- - -
-
-
-{% endblock %} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html deleted file mode 100644 index 4d1d846..0000000 --- a/templates/base.html +++ /dev/null @@ -1,905 +0,0 @@ - - - - - - - - - - - - - - {% if app_mode == 'lite' %}(Lite Version) {% endif %}{% if title %}{{ title }} - {% endif %}{{ site_name }} - - - - - -
-
- {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} -
- {% for category, message in messages %} - - {% endfor %} -
- {% endif %} - {% endwith %} - {% block content %}{% endblock %} -
-
- - - - - - - - {% block scripts %}{% endblock %} - - diff --git a/templates/config_hapi.html b/templates/config_hapi.html deleted file mode 100644 index 2a3e6c5..0000000 --- a/templates/config_hapi.html +++ /dev/null @@ -1,234 +0,0 @@ -{% extends "base.html" %} -{% block content %} -
-

HAPI FHIR Configuration Manager

- {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} - - {% endfor %} - {% endif %} - {% endwith %} -
-

Loading configuration...

-
- Loading... -
-
- -
-
- - - -
-
-
-
- - -{% endblock %} \ No newline at end of file diff --git a/templates/cp_downloaded_igs.html b/templates/cp_downloaded_igs.html deleted file mode 100644 index 18e8e18..0000000 --- a/templates/cp_downloaded_igs.html +++ /dev/null @@ -1,168 +0,0 @@ -{% extends "base.html" %} - -{% block content %} -
- FHIRFLARE Ig Toolkit -

Manage & Process FHIR Packages

-
-

- This is the starting Point for your Journey through the IG's -

- -
-
- -
- - -
-

Manage FHIR Packages

- -
- -
- -
-
-
Downloaded Packages ({{ packages|length }})
-
- {% if packages %} -
-

Risk: = Duplicate Dependencies

- - - - - - {% for pkg in packages %} - {% set is_processed = (pkg.name, pkg.version) in processed_ids %} - {% set is_duplicate = pkg.name in duplicate_groups %} - {% set group_color = group_colors[pkg.name] if (is_duplicate and pkg.name in group_colors) else 'bg-warning' if is_duplicate else '' %} - - - - - - {% endfor %} - -
Package NameVersionActions
- {{ pkg.name }} - {% if is_duplicate %} - Duplicate - {% endif %} - {{ pkg.version }} -
- {% if is_processed %} - Processed - {% else %} -
- {{ form.csrf_token }} - - -
- {% endif %} -
- {{ form.csrf_token }} - - -
-
-
-
- {% if duplicate_groups %} -

Duplicate dependencies detected: - {% for name, versions in duplicate_groups.items() %} - {% set group_color = group_colors[name] if name in group_colors else 'bg-warning' %} - {{ name }} ({{ versions|join(', ') }}) - {% endfor %} -

- {% else %} -

No duplicates detected.

- {% endif %} - {% else %} -

No downloaded FHIR packages found.

- {% endif %} -
-
-
- - -
-
-
Processed Packages ({{ processed_list|length }})
-
- {% if processed_list %} -

- MS = Contains Must Support Elements
- Optional MS Ext = Optional Extension with Must Support Sub-Elements -

-

Resource Types in the list will be both Profile and Base Type:

-
- - - - - - {% for processed_ig in processed_list %} - - - - - - - {% endfor %} - -
Package NameVersionResource TypesActions
{{ processed_ig.package_name }}{{ processed_ig.version }} - {% set types_info = processed_ig.resource_types_info %} - {% if types_info %} -
- {% for type_info in types_info %} - {{ type_info.name }} - {% endfor %} -
- {% else %} - N/A - {% endif %} -
-
- View -
- {{ form.csrf_token }} - - -
-
-
-
- {% else %} -

No packages recorded as processed yet.

- {% endif %} -
-
-
-
-
-{% endblock %} - -{% block scripts %} -{{ super() }} -{% endblock %} \ No newline at end of file diff --git a/templates/cp_push_igs.html b/templates/cp_push_igs.html deleted file mode 100644 index 9af92bf..0000000 --- a/templates/cp_push_igs.html +++ /dev/null @@ -1,501 +0,0 @@ -{% extends "base.html" %} - -{# Import form helpers for CSRF token and field rendering #} -{% from "_form_helpers.html" import render_field %} - -{% block content %} -
-
- {# Left Column: Downloaded IGs List & Report Area #} -
-

Downloaded IGs

- {% if packages %} -
- - - - - - - - - {% for pkg in packages %} - {% set name = pkg.name %} - {% set version = pkg.version %} - {% set duplicate_group = (duplicate_groups or {}).get(name) %} - {% set color_class = group_colors[name] if (duplicate_group and group_colors and name in group_colors) else '' %} - - - - - {% endfor %} - -
Package NameVersion
{{ name }}{{ version }}
-
- {% if duplicate_groups %} - - {% endif %} - {% else %} -

No packages downloaded yet. Use the "Import IG" tab.

- {% endif %} - - {# Push Response Area #} -
-
-

Push Report

- -
-
- Report summary will appear here after pushing... - {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} - - {% endfor %} - {% endif %} - {% endwith %} -
-
-
{# End Left Column #} - - {# Right Column: Push IGs Form and Console #} -
-

Push IGs to FHIR Server

-
- {{ form.csrf_token if form else '' }} - - {# Package Selection #} -
- - -
- - {# Dependency Mode Display #} -
- - -
- - {# FHIR Server URL #} -
- - -
- - {# Authentication Section #} -
-
- - -
- -
- - {# Checkboxes Row #} -
-
-
- - -
-
-
-
- - - Force upload all resources. -
-
-
-
- - - Simulate only. -
-
-
-
- - - Show detailed log. -
-
-
- - {# Resource Type Filter #} -
- - -
- - {# Skip Files Filter #} -
- - -
- - -
- - {# Live Console #} -
-

Live Console

-
- Console output will appear here... -
-
-
{# End Right Column #} -
{# End row #} -
{# End container-fluid #} - - -{% endblock %} \ No newline at end of file diff --git a/templates/cp_view_processed_ig.html b/templates/cp_view_processed_ig.html deleted file mode 100644 index b5c8faf..0000000 --- a/templates/cp_view_processed_ig.html +++ /dev/null @@ -1,1338 +0,0 @@ -{% extends "base.html" %} - -{% block content %} -
- FHIRFLARE Ig Toolkit -

{{ title }}

-
-

- View details of the processed FHIR Implementation Guide. -

-
-
- -
-
-

{{ title }}

- Back to Package List -
- - {% if processed_ig %} -
-
Package Details
-
-
-
Package Name
-
{{ processed_ig.package_name }}
-
Package Version
-
{{ processed_ig.version }}
-
Processed At
-
{{ processed_ig.processed_date.strftime('%Y-%m-%d %H:%M:%S UTC') }}
-
-
-
- - {% if config.DISPLAY_PROFILE_RELATIONSHIPS %} -
-
Profile Relationships
-
-
Complies With
- {% if complies_with_profiles %} -
    - {% for profile in complies_with_profiles %} -
  • {{ profile }}
  • - {% endfor %} -
- {% else %} -

No profiles declared as compatible.

- {% endif %} - -
Required Dependent Profiles (Must Also Validate Against)
- {% if imposed_profiles %} -
    - {% for profile in imposed_profiles %} -
  • {{ profile }}
  • - {% endfor %} -
- {% else %} -

No imposed profiles.

- {% endif %} -
-
- {% endif %} - -
-
Resource Types Found / Defined
-
- {% if profile_list or base_list %} -

- - MS = Contains Must Support Elements
- Optional MS Ext = Optional Extension with Must Support Sub-Elements -
-

- {% if profile_list %} -

Examples = Examples will be displayed when selecting profile Types if contained in the IG

-
Profiles Defined ({{ profile_list|length }}):
- - {% else %} -

No profiles defined.

- {% endif %} - {% if base_list %} -
Base Resource Types Referenced ({{ base_list|length }}):
- - {% else %} -

No base resource types referenced.

- {% endif %} - {% else %} -

No resource type information extracted or stored.

- {% endif %} -
-
- - - - - - {% else %} - - {% endif %} -
- - - - - - - - -{% endblock %} \ No newline at end of file diff --git a/templates/fhir_ui.html b/templates/fhir_ui.html deleted file mode 100644 index bd33b58..0000000 --- a/templates/fhir_ui.html +++ /dev/null @@ -1,473 +0,0 @@ -{% extends "base.html" %} - -{% block content %} -
- FHIRFLARE IG Toolkit -

FHIR API Explorer

-
-

- Interact with FHIR servers using GET, POST, PUT, or DELETE requests. Toggle between local HAPI or a custom server to explore resources or perform searches. -

-
-
- -
-
-
Send FHIR Request
-
-
- {{ form.hidden_tag() }} -
- -
- - -
- Toggle to use local HAPI (http://localhost:8080/fhir) or enter a custom FHIR server URL. -
- -
- - - Enter a resource path (e.g., Patient, Observation/example) or '_search' for search queries. -
-
- -
- - - - - - - - -
-
- - -
-
-
- - -
- - -{% endblock %} \ No newline at end of file diff --git a/templates/fhir_ui_operations.html b/templates/fhir_ui_operations.html deleted file mode 100644 index c8d1425..0000000 --- a/templates/fhir_ui_operations.html +++ /dev/null @@ -1,1957 +0,0 @@ -{% extends "base.html" %} - -{% block content %} - - -
- FHIRFLARE IG Toolkit -

FHIR UI Operations

-
-

- Explore FHIR server operations by selecting resource types or system operations. Toggle between local HAPI or a custom server to interact with FHIR metadata, resources, and server-wide operations. -

- -
-
- -
-
-
FHIR Operations Configuration
-
-
- {{ form.hidden_tag() }} -
- -
- - -
- Toggle to use local HAPI (/fhir proxy) or enter a custom FHIR server URL. -
- - -
- - - - -
-
-
- - -{% endblock %} \ No newline at end of file diff --git a/templates/fsh_converter.html b/templates/fsh_converter.html deleted file mode 100644 index 1e08299..0000000 --- a/templates/fsh_converter.html +++ /dev/null @@ -1,225 +0,0 @@ -{% extends "base.html" %} -{% from "_form_helpers.html" import render_field %} - -{% block content %} -
- FHIRFLARE IG Toolkit -

FSH Converter

-
-

- Convert FHIR JSON or XML resources to FHIR Shorthand (FSH) using GoFSH. -

- -
-
- - -
-
-
-

Waiting, don't leave this page...

-
-
- -
-

Convert FHIR to FSH

-
-
-
-
-
-
- {{ form.hidden_tag() }} - {{ render_field(form.package) }} - {{ render_field(form.input_mode) }} - - - {{ render_field(form.output_style) }} - {{ render_field(form.log_level) }} - {{ render_field(form.fhir_version) }} - {{ render_field(form.fishing_trip) }} - {{ render_field(form.dependencies, placeholder="One per line, e.g., hl7.fhir.us.core@6.1.0") }} - {{ render_field(form.indent_rules) }} - {{ render_field(form.meta_profile) }} - {{ render_field(form.alias_file) }} - {{ render_field(form.no_alias) }} -
- {{ form.submit(class="btn btn-success", id="submit-btn") }} - Back -
-
-
-
-
-
-
-
- - - -{% endblock %} \ No newline at end of file diff --git a/templates/import_ig.html b/templates/import_ig.html deleted file mode 100644 index adc9e4a..0000000 --- a/templates/import_ig.html +++ /dev/null @@ -1,145 +0,0 @@ -{% extends "base.html" %} -{% from "_form_helpers.html" import render_field %} - -{% block content %} -
- FHIRFLARE IG Toolkit -

Import FHIR Implementation Guides

-
-

- Import new FHIR Implementation Guides to the system for viewing. -

-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

Importing Implementation Guide... Please wait.

-

-
-
- -
-

Import a New IG

-
-
-
-
-
- {{ form.hidden_tag() }} - {{ render_field(form.package_name) }} - {{ render_field(form.package_version) }} - {{ render_field(form.dependency_mode) }} -
- {{ form.submit(class="btn btn-success", id="submit-btn") }} - Back -
-
-
-
-
-
-
- - -{% endblock %} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html deleted file mode 100644 index 5a6a1ad..0000000 --- a/templates/index.html +++ /dev/null @@ -1,168 +0,0 @@ -{% extends "base.html" %} - -{% block body_class %}fire-animation-page{% endblock %} - -{% block content %} -
-
- FHIRFLARE IG Toolkit -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - - -
-
- - - - - - - - - - -
-
-
-

Welcome to {{ site_name }}

-
-

Simple tool for importing, viewing, and validating FHIR Implementation Guides.

-

Streamline Your FHIR Workflow

-
-
- -
-
- -
-
-
-
IG Management
-

Import and manage FHIR Implementation Guides.

- -
-
-
- -
-
-
-
Validation & Testing
-

Validate and test FHIR resources.

- -
-
-
- -
-
-
-
API & Tools
-

Explore FHIR APIs and convert resources.

- -
-
-
-
- -
- - - - -{% endblock %} \ No newline at end of file diff --git a/templates/manual_import_ig.html b/templates/manual_import_ig.html deleted file mode 100644 index 466d3b0..0000000 --- a/templates/manual_import_ig.html +++ /dev/null @@ -1,194 +0,0 @@ -{% extends "base.html" %} -{% from "_form_helpers.html" import render_field %} - -{% block content %} -
- {% include "_flash_messages.html" %} - -

Import FHIR Implementation Guides

-
-
-

Import new FHIR Implementation Guides to the system via file or URL.

-
-
- -
-
-

Import a New IG

-
- {{ form.hidden_tag() }} -
- {{ render_field(form.import_mode, class="form-select") }} -
-
-
- -
- - No file selected -
- {% if form.tgz_file.errors %} -
- {% for error in form.tgz_file.errors %} -

{{ error }}

- {% endfor %} -
- {% endif %} -
-
-
-
- {{ render_field(form.tgz_url, class="form-control url-input") }} -
-
-
- {{ render_field(form.dependency_mode, class="form-select") }} -
-
- {{ form.resolve_dependencies(type="checkbox", class="form-check-input") }} - - {% if form.resolve_dependencies.errors %} -
- {% for error in form.resolve_dependencies.errors %} -

{{ error }}

- {% endfor %} -
- {% endif %} -
-
- {{ form.submit(class="btn btn-success", id="submit-btn") }} - Back -
-
-
-
-
- - - - -{% endblock %} \ No newline at end of file diff --git a/templates/package.canonicals.html b/templates/package.canonicals.html deleted file mode 100644 index 5cccc47..0000000 --- a/templates/package.canonicals.html +++ /dev/null @@ -1,7 +0,0 @@ -
    - {% for canonical in canonicals %} -
  • - {{ canonical }} -
  • - {% endfor %} -
\ No newline at end of file diff --git a/templates/package.dependents.html b/templates/package.dependents.html deleted file mode 100644 index b240ce2..0000000 --- a/templates/package.dependents.html +++ /dev/null @@ -1,31 +0,0 @@ -{% if dependents %} - - - - - - - - - - - - - {% for dep in dependents %} - - - - - - - - - {% endfor %} - -
PackageLatestAuthorFHIRVersionsCanonical
- - {{ dep.name }} - {{ dep.version }}{{ dep.author }}{{ dep.fhir_version }}{{ dep.version_count }}{{ dep.canonical }}
-{% else %} -

No dependent packages found for this package.

-{% endif %} \ No newline at end of file diff --git a/templates/package.logs.html b/templates/package.logs.html deleted file mode 100644 index 6cf0354..0000000 --- a/templates/package.logs.html +++ /dev/null @@ -1,22 +0,0 @@ -{% if logs %} - - - - - - - - - - {% for log in logs %} - - - - - - {% endfor %} - -
VersionPublication DateWhen
{{ log.version }}{{ log.pubDate }}{{ log.when }}
-{% else %} -

No version history found for this package.

-{% endif %} \ No newline at end of file diff --git a/templates/package.problems.html b/templates/package.problems.html deleted file mode 100644 index 3714532..0000000 --- a/templates/package.problems.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - {% for problem in problems %} - - - - - {% endfor %} - -
PackageDependency
{{ problem.package }}{{ problem.dependency }}
\ No newline at end of file diff --git a/templates/package_details.html b/templates/package_details.html deleted file mode 100644 index 1f6d817..0000000 --- a/templates/package_details.html +++ /dev/null @@ -1,124 +0,0 @@ -{% extends "base.html" %} - -{% block content %} -
-
-
- {# Package Header #} -

{{ package_json.name }} v{{ package_json.version }}

-

- Latest Version: {{ package_json.version }} - {% if latest_official_version and latest_official_version != package_json.version %} - | Latest Official: {{ latest_official_version }} - {% endif %} -

- - {# Install Commands #} -
- -
- {% set registry_base = package_json.registry | default('https://packages.simplifier.net') %} - {% set registry_url = registry_base | replace('/rssfeed', '') %} - - -
-
-
- -
- {% set registry_base = package_json.registry | default('https://packages.simplifier.net') %} - {% set registry_url = registry_base | replace('/rssfeed', '') %} - - -
-
-
- -
- {% set registry_base = package_json.registry | default('https://packages.simplifier.net') %} - {% set registry_url = registry_base | replace('/rssfeed', '') %} - - -
-
- - {# Description #} -
Description
-

{{ package_json.description | default('No description provided.', true) }}

- - {# Dependencies #} -
Dependencies
- {% if dependencies %} - - - - - - - - - {% for dep in dependencies %} - - - - - {% endfor %} - -
PackageVersion
{{ dep.name }}{{ dep.version }}
- {% else %} -

No dependencies found for this package.

- {% endif %} - - {# Dependents #} -
Dependents
-
- Loading... -
- - {# Logs #} -
Logs
-
- Loading... -
-
- -
-

Versions ({{ versions | length }})

-
    - {% for version in versions %} -
  • - {{ version }} - {% if version == latest_official_version %} - - {% endif %} -
  • - {% else %} -
  • No versions found.
  • - {% endfor %} -
-
-
-
- - -{% endblock %} \ No newline at end of file diff --git a/templates/retrieve_split_data.html b/templates/retrieve_split_data.html deleted file mode 100644 index c930322..0000000 --- a/templates/retrieve_split_data.html +++ /dev/null @@ -1,690 +0,0 @@ -{% extends "base.html" %} -{% from "_form_helpers.html" import render_field %} - -{% block content %} -
-

Retrieve & Split Data

-
-

- Retrieve FHIR bundles from a server, then download as a ZIP file. Split uploaded or retrieved bundle ZIPs into individual resources, downloaded as a ZIP file. -

-
-
- -
-
-
-

Retrieve Bundles

-
-
- {% if form.errors %} -
-

Please correct the following errors:

-
    - {% for field, errors in form.errors.items() %} -
  • {{ form[field].label.text }}: {{ errors|join(', ') }}
  • - {% endfor %} -
-
- {% endif %} -
- {{ form.hidden_tag() }} -
- -
- - {{ form.fhir_server_url(class="form-control", id="fhirServerUrl", style="display: none;", placeholder="e.g., https://fhir.hl7.org.au/aucore/fhir/DEFAULT", **{'aria-describedby': 'fhirServerHelp'}) }} -
- Toggle to use local HAPI (/fhir proxy) or enter a custom FHIR server URL. -
- - {# Authentication Section (Shown for Custom URL) #} - - - {# Checkbox Row #} -
-
- {{ render_field(form.validate_references, id='validate_references_checkbox') }} -
- -
- - - - - -
-
-
Retrieval Log
-
-
- Retrieval output will appear here... -
-
-
-
-
- -
-
-

Split Bundles

-
-
-
- {{ form.hidden_tag() }} -
- -
- - -
-
- - -
-
- {{ render_field(form.split_bundle_zip, class="form-control") }} - - -
-
-
Splitting Log
-
-
- Splitting output will appear here... -
-
-
-
-
-
- - -{% endblock %} \ No newline at end of file diff --git a/templates/search_and_import_ig.html b/templates/search_and_import_ig.html deleted file mode 100644 index 5ff6914..0000000 --- a/templates/search_and_import_ig.html +++ /dev/null @@ -1,461 +0,0 @@ -{% extends "base.html" %} -{% from "_form_helpers.html" import render_field %} - -{% block extra_head %} {# Assuming base.html has an 'extra_head' block for additional head elements #} - -{% endblock %} - -{% block content %} -{# Main page content for searching and importing packages #} - -
- {# Flash messages area - Ensure _flash_messages.html exists and is included in base.html or rendered here #} - {% include "_flash_messages.html" %} - - {# Display warning if package fetching failed on initial load #} - {% if fetch_failed %} -
- Unable to fetch packages from registries. Showing a fallback list. Please try again later or contact support. -
- {% endif %} - -
- {# Left Column: Search Packages Area #} -
-
-
- {# Header with Refresh Button #} -
-

Search Packages

- -
- - {# Cache Status Timestamp #} -
- {% if last_cached_timestamp %} - Package list last fetched: {{ last_cached_timestamp.strftime('%Y-%m-%d %H:%M:%S %Z') if last_cached_timestamp else 'Never' }} - {% if fetch_failed %} (Fetch Failed){% endif %} - {% elif is_fetching %} {# Show text spinner specifically during initial fetch state triggered by backend #} - Fetching package list... - {% else %} - Never fetched or cache cleared. - {% endif %} -
- - {# Search Input with HTMX #} -
- - - -
-
- - {# Search Results Area (populated by HTMX) #} -
- {% include '_search_results_table.html' %} {# Includes the initial table state or updated results #} -
-
-
-
- - {# Right Column: Import Form, Log Window, Animation, Warning #} -
-
-
- {# Import Form #} -

Import a New IG

-
{# Form ID used by JS #} - {{ form.hidden_tag() }} {# Include CSRF token if using Flask-WTF #} - {{ render_field(form.package_name, class="form-control") }} - {{ render_field(form.package_version, class="form-control") }} - {{ render_field(form.dependency_mode, class="form-select") }} -
- {# Import Button triggers HTMX POST #} - - - Back {# Simple link back #} -
-
- - {# Live Log Output Window #} -
-
Live Log Output
-
-

Logs from caching or import actions will appear here.

-
- {# Indicator shown while connecting to SSE #} - -
- - {# Animation Window (Hidden by default) #} - - - {# Warning Text (Hidden by default) #} - - -
{# End card-body #} -
{# End card #} -
{# End col-md-3 #} -
{# End row #} -
{# End container #} - - - - -{% endblock %} \ No newline at end of file diff --git a/templates/upload_test_data.html b/templates/upload_test_data.html deleted file mode 100644 index 02f5d68..0000000 --- a/templates/upload_test_data.html +++ /dev/null @@ -1,302 +0,0 @@ -{% extends "base.html" %} -{% from "_form_helpers.html" import render_field %} - -{% block content %} -
- FHIRFLARE IG Toolkit -

{{ title }}

-
-

- Upload FHIR test data (JSON, XML, or ZIP containing JSON/XML) to a target server. The tool will attempt to parse resources, determine dependencies based on references, and upload them in the correct order. Optionally validate resources before uploading. -

-
-
- -
-
-
-
-
-

Upload Configuration

-
-
- {% if form.errors %} -
-

Please correct the following errors:

-
    - {% for field, errors in form.errors.items() %} -
  • {{ form[field].label.text }}: {{ errors|join(', ') }}
  • - {% endfor %} -
-
- {% endif %} - -
- {{ form.csrf_token }} - - {{ render_field(form.fhir_server_url, class="form-control form-control-lg") }} - -
-
- {{ render_field(form.auth_type, class="form-select") }} -
- -
- - {{ render_field(form.test_data_file, class="form-control") }} - Select one or more .json, .xml files, or a single .zip file containing them. - -
-
- {{ render_field(form.validate_before_upload) }} - It is suggested to not validate against more than 500 files -
- -
- -
-
- {{ render_field(form.upload_mode, class="form-select") }} -
-
- {{ render_field(form.use_conditional_uploads) }} -
-
- {{ render_field(form.error_handling, class="form-select") }} -
-
- - -
-
-
- -
-
-

Processing Log & Results

-
-
-
- Processing output will appear here... -
-
-
-
-
-
-
-
- -{% endblock %} - -{% block scripts %} -{{ super() }} - -{% endblock %} \ No newline at end of file diff --git a/templates/validate_sample.html b/templates/validate_sample.html deleted file mode 100644 index e85034d..0000000 --- a/templates/validate_sample.html +++ /dev/null @@ -1,376 +0,0 @@ - -{% extends "base.html" %} - -{% block content %} -
- FHIRFLARE IG Toolkit -

Validate FHIR Sample

-
-

- Validate a FHIR resource or bundle against a selected Implementation Guide. (ALPHA - TEST Fhir pathing is complex and the logic is WIP, please report anomalies you find in Github issues alongside your sample json REMOVE PHI) -

- -
-
- -
-
-
Validation Form
-
-
- {{ form.hidden_tag() }} -
- - Select a package from the list below or enter a new one (e.g., hl7.fhir.us.core). - -
- -
- - {{ form.package_name(class="form-control", id=form.package_name.id) }} - {% for error in form.package_name.errors %} -
{{ error }}
- {% endfor %} -
-
- - {{ form.version(class="form-control", id=form.version.id) }} - {% for error in form.version.errors %} -
{{ error }}
- {% endfor %} -
-
- -
- {{ form.include_dependencies(class="form-check-input") }} - {{ form.include_dependencies.label(class="form-check-label") }} -
-
-
- - {{ form.mode(class="form-select") }} -
-
- - {{ form.sample_input(class="form-control", rows=10, placeholder="Paste your FHIR JSON here...") }} - {% for error in form.sample_input.errors %} -
{{ error }}
- {% endfor %} -
- -
-
-
- - -
- - -{% endblock %} \ No newline at end of file diff --git a/tests/test_app.py b/tests/test_app.py deleted file mode 100644 index 996d1e9..0000000 --- a/tests/test_app.py +++ /dev/null @@ -1,648 +0,0 @@ -import unittest -import os -import sys -import json -import tarfile -import shutil -import io -import requests -import time -import subprocess -from unittest.mock import patch, MagicMock, mock_open, call -from flask import Flask, session -from flask.testing import FlaskClient -from datetime import datetime, timezone - -# Add the parent directory (/app) to sys.path -APP_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) -if APP_DIR not in sys.path: - sys.path.insert(0, APP_DIR) - -from app import app, db, ProcessedIg -import services - -# Helper function to parse NDJSON stream -def parse_ndjson(byte_stream): - decoded_stream = byte_stream.decode('utf-8').strip() - if not decoded_stream: - return [] - lines = decoded_stream.split('\n') - return [json.loads(line) for line in lines if line.strip()] - -class DockerComposeContainer: - """ - A class that follows the Testcontainers pattern for managing Docker Compose environments. - This implementation uses subprocess to call docker-compose directly since we're not - installing the testcontainers-python package. - """ - - def __init__(self, compose_file_path): - """ - Initialize with the path to the docker-compose.yml file - - Args: - compose_file_path: Path to the docker-compose.yml file - """ - self.compose_file = compose_file_path - self.compose_dir = os.path.dirname(os.path.abspath(compose_file_path)) - self.containers_up = False - self.service_ports = {} - self._container_ids = {} - - def __enter__(self): - """Start containers when entering context""" - self.start() - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - """Stop containers when exiting context""" - self.stop() - - def with_service_port(self, service_name, port): - """ - Map a service port (following the testcontainers builder pattern) - - Args: - service_name: Name of the service in docker-compose.yml - port: Port number to expose - - Returns: - self for chaining - """ - self.service_ports[service_name] = port - return self - - def start(self): - """Start the Docker Compose environment""" - if self.containers_up: - return self - - print("Starting Docker Compose environment...") - result = subprocess.run( - ['docker-compose', '-f', self.compose_file, 'up', '-d'], - cwd=self.compose_dir, - capture_output=True, - text=True - ) - - if result.returncode != 0: - error_msg = f"Failed to start Docker Compose environment: {result.stderr}" - print(error_msg) - raise RuntimeError(error_msg) - - # Store container IDs for later use - self._get_container_ids() - - self.containers_up = True - self._wait_for_services() - return self - - def _get_container_ids(self): - """Get the container IDs for all services""" - result = subprocess.run( - ['docker-compose', '-f', self.compose_file, 'ps', '-q'], - cwd=self.compose_dir, - capture_output=True, - text=True - ) - - if result.returncode != 0: - return - - container_ids = result.stdout.strip().split('\n') - if not container_ids: - return - - # Get service names for each container - for container_id in container_ids: - if not container_id: - continue - - inspect_result = subprocess.run( - ['docker', 'inspect', '--format', '{{index .Config.Labels "com.docker.compose.service"}}', container_id], - capture_output=True, - text=True - ) - - if inspect_result.returncode == 0: - service_name = inspect_result.stdout.strip() - self._container_ids[service_name] = container_id - - def get_container_id(self, service_name): - """ - Get the container ID for a specific service - - Args: - service_name: Name of the service in docker-compose.yml - - Returns: - Container ID as string or None if not found - """ - return self._container_ids.get(service_name) - - def get_service_host(self, service_name): - """ - Get the host for a specific service - for Docker Compose we just use localhost - - Args: - service_name: Name of the service in docker-compose.yml - - Returns: - Host as string (usually localhost) - """ - return "localhost" - - def get_service_url(self, service_name, path=""): - """ - Get the URL for a specific service - - Args: - service_name: Name of the service in docker-compose.yml - path: Optional path to append to the URL - - Returns: - URL as string - """ - port = self.service_ports.get(service_name) - if not port: - raise ValueError(f"No port mapping defined for service {service_name}") - - url = f"http://{self.get_service_host(service_name)}:{port}" - if path: - # Ensure path starts with / - if not path.startswith('/'): - path = f"/{path}" - url = f"{url}{path}" - - return url - - def get_logs(self, service_name): - """ - Get logs for a specific service - - Args: - service_name: Name of the service in docker-compose.yml - - Returns: - Logs as string - """ - container_id = self.get_container_id(service_name) - if not container_id: - return f"No container found for service {service_name}" - - result = subprocess.run( - ['docker', 'logs', container_id], - capture_output=True, - text=True - ) - - return result.stdout - - def stop(self): - """Stop the Docker Compose environment""" - if not self.containers_up: - return - - print("Stopping Docker Compose environment...") - result = subprocess.run( - ['docker-compose', '-f', self.compose_file, 'down'], - cwd=self.compose_dir, - capture_output=True, - text=True - ) - - if result.returncode != 0: - print(f"Warning: Error stopping Docker Compose: {result.stderr}") - - self.containers_up = False - - def _wait_for_services(self): - """Wait for all services to be ready""" - print("Waiting for services to be ready...") - - # Wait for HAPI FHIR server - if 'fhir' in self.service_ports: - self._wait_for_http_service( - self.get_service_url('fhir', 'fhir/metadata'), - "HAPI FHIR server" - ) - - # Wait for FHIRFLARE application - if 'fhirflare' in self.service_ports: - self._wait_for_http_service( - self.get_service_url('fhirflare'), - "FHIRFLARE application" - ) - - # Give additional time for services to stabilize - time.sleep(5) - - def _wait_for_http_service(self, url, service_name, max_retries=30, retry_interval=2): - """ - Wait for an HTTP service to be ready - - Args: - url: URL to check - service_name: Name of the service for logging - max_retries: Maximum number of retries - retry_interval: Interval between retries in seconds - """ - for attempt in range(max_retries): - try: - response = requests.get(url, timeout=5) - if response.status_code == 200: - print(f"{service_name} is ready after {attempt + 1} attempts") - return True - except requests.RequestException: - pass - - print(f"Waiting for {service_name} (attempt {attempt + 1}/{max_retries})...") - time.sleep(retry_interval) - - print(f"Warning: {service_name} did not become ready in time") - return False - -class TestFHIRFlareIGToolkit(unittest.TestCase): - @classmethod - def setUpClass(cls): - # Define the Docker Compose container - compose_file_path = os.path.join(os.path.dirname(__file__), 'docker-compose.yml') - cls.container = DockerComposeContainer(compose_file_path) \ - .with_service_port('fhir', 8080) \ - .with_service_port('fhirflare', 5000) - - # Start the containers - cls.container.start() - - # Configure app for testing - app.config['TESTING'] = True - app.config['WTF_CSRF_ENABLED'] = False - app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:' - cls.test_packages_dir = os.path.join(os.path.dirname(__file__), 'test_fhir_packages_temp') - app.config['FHIR_PACKAGES_DIR'] = cls.test_packages_dir - app.config['SECRET_KEY'] = 'test-secret-key' - app.config['API_KEY'] = 'test-api-key' - app.config['VALIDATE_IMPOSED_PROFILES'] = True - app.config['DISPLAY_PROFILE_RELATIONSHIPS'] = True - app.config['HAPI_FHIR_URL'] = cls.container.get_service_url('fhir', 'fhir') # Point to containerized HAPI FHIR - - cls.app_context = app.app_context() - cls.app_context.push() - db.create_all() - cls.client = app.test_client() - - @classmethod - def tearDownClass(cls): - cls.app_context.pop() - if os.path.exists(cls.test_packages_dir): - shutil.rmtree(cls.test_packages_dir) - - # Stop Docker Compose environment - cls.container.stop() - - def setUp(self): - if os.path.exists(self.test_packages_dir): - shutil.rmtree(self.test_packages_dir) - os.makedirs(self.test_packages_dir, exist_ok=True) - with self.app_context: - for item in db.session.query(ProcessedIg).all(): - db.session.delete(item) - db.session.commit() - - def tearDown(self): - pass - - # Helper Method - def create_mock_tgz(self, filename, files_content): - tgz_path = os.path.join(app.config['FHIR_PACKAGES_DIR'], filename) - with tarfile.open(tgz_path, "w:gz") as tar: - for name, content in files_content.items(): - if isinstance(content, (dict, list)): - data_bytes = json.dumps(content).encode('utf-8') - elif isinstance(content, str): - data_bytes = content.encode('utf-8') - else: - raise TypeError(f"Unsupported type for mock file '{name}': {type(content)}") - file_io = io.BytesIO(data_bytes) - tarinfo = tarfile.TarInfo(name=name) - tarinfo.size = len(data_bytes) - tarinfo.mtime = int(datetime.now(timezone.utc).timestamp()) - tar.addfile(tarinfo, file_io) - return tgz_path - - # --- Phase 1 Tests --- - - def test_01_navigate_fhir_path(self): - resource = { - "resourceType": "Patient", - "name": [{"given": ["John"]}], - "identifier": [{"system": "http://hl7.org/fhir/sid/us-ssn", "sliceName": "us-ssn"}], - "extension": [{"url": "http://hl7.org/fhir/StructureDefinition/patient-birthPlace", "valueAddress": {"city": "Boston"}}] - } - self.assertEqual(services.navigate_fhir_path(resource, "Patient.name[0].given"), ["John"]) - self.assertEqual(services.navigate_fhir_path(resource, "Patient.identifier:us-ssn.system"), "http://hl7.org/fhir/sid/us-ssn") - self.assertEqual(services.navigate_fhir_path(resource, "Patient.extension", extension_url="http://hl7.org/fhir/StructureDefinition/patient-birthPlace")["valueAddress"]["city"], "Boston") - with patch('fhirpath.evaluate', side_effect=Exception("fhirpath error")): - self.assertEqual(services.navigate_fhir_path(resource, "Patient.name[0].given"), ["John"]) - - # --- Basic Page Rendering Tests --- - - def test_03_homepage(self): - # Connect to the containerized application - response = requests.get(self.container.get_service_url('fhirflare')) - self.assertEqual(response.status_code, 200) - self.assertIn('FHIRFLARE IG Toolkit', response.text) - - def test_04_import_ig_page(self): - response = requests.get(self.container.get_service_url('fhirflare', 'import-ig')) - self.assertEqual(response.status_code, 200) - self.assertIn('Import IG', response.text) - self.assertIn('Package Name', response.text) - self.assertIn('Package Version', response.text) - self.assertIn('name="dependency_mode"', response.text) - - # --- API Integration Tests --- - - def test_30_load_ig_to_hapi_integration(self): - """Test loading an IG to the containerized HAPI FHIR server""" - pkg_name = 'hl7.fhir.us.core' - pkg_version = '6.1.0' - filename = f'{pkg_name}-{pkg_version}.tgz' - self.create_mock_tgz(filename, { - 'package/package.json': {'name': pkg_name, 'version': pkg_version}, - 'package/StructureDefinition-us-core-patient.json': { - 'resourceType': 'StructureDefinition', - 'id': 'us-core-patient', - 'url': 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient', - 'name': 'USCorePatientProfile', - 'type': 'Patient', - 'status': 'active' - } - }) - - # Load IG to HAPI - response = self.client.post( - '/api/load-ig-to-hapi', - data=json.dumps({'package_name': pkg_name, 'version': pkg_version}), - content_type='application/json', - headers={'X-API-Key': 'test-api-key'} - ) - - self.assertEqual(response.status_code, 200) - data = json.loads(response.data) - self.assertEqual(data['status'], 'success') - - # Verify the resource was loaded by querying the HAPI FHIR server directly - hapi_response = requests.get(self.container.get_service_url('fhir', 'fhir/StructureDefinition/us-core-patient')) - self.assertEqual(hapi_response.status_code, 200) - resource = hapi_response.json() - self.assertEqual(resource['resourceType'], 'StructureDefinition') - self.assertEqual(resource['id'], 'us-core-patient') - - def test_31_validate_sample_with_hapi_integration(self): - """Test validating a sample against the containerized HAPI FHIR server""" - # First, load the necessary StructureDefinition - pkg_name = 'hl7.fhir.us.core' - pkg_version = '6.1.0' - filename = f'{pkg_name}-{pkg_version}.tgz' - self.create_mock_tgz(filename, { - 'package/package.json': {'name': pkg_name, 'version': pkg_version}, - 'package/StructureDefinition-us-core-patient.json': { - 'resourceType': 'StructureDefinition', - 'id': 'us-core-patient', - 'url': 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient', - 'name': 'USCorePatientProfile', - 'type': 'Patient', - 'status': 'active', - 'snapshot': { - 'element': [ - {'path': 'Patient', 'min': 1, 'max': '1'}, - {'path': 'Patient.name', 'min': 1, 'max': '*'}, - {'path': 'Patient.identifier', 'min': 0, 'max': '*', 'mustSupport': True} - ] - } - } - }) - - # Load IG to HAPI - self.client.post( - '/api/load-ig-to-hapi', - data=json.dumps({'package_name': pkg_name, 'version': pkg_version}), - content_type='application/json', - headers={'X-API-Key': 'test-api-key'} - ) - - # Validate a sample that's missing a required element - sample_resource = { - 'resourceType': 'Patient', - 'id': 'test-patient', - 'meta': {'profile': ['http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient']} - # Missing required 'name' element - } - - response = self.client.post( - '/api/validate-sample', - data=json.dumps({ - 'package_name': pkg_name, - 'version': pkg_version, - 'sample_data': json.dumps(sample_resource), - 'mode': 'single', - 'include_dependencies': True - }), - content_type='application/json', - headers={'X-API-Key': 'test-api-key'} - ) - - self.assertEqual(response.status_code, 200) - data = json.loads(response.data) - self.assertFalse(data['valid']) - # Check for validation error related to missing name - found_name_error = any('name' in error for error in data['errors']) - self.assertTrue(found_name_error, f"Expected error about missing name element, got: {data['errors']}") - - def test_32_push_ig_to_hapi_integration(self): - """Test pushing multiple resources from an IG to the containerized HAPI FHIR server""" - pkg_name = 'test.push.pkg' - pkg_version = '1.0.0' - filename = f'{pkg_name}-{pkg_version}.tgz' - - # Create a test package with multiple resources - self.create_mock_tgz(filename, { - 'package/package.json': {'name': pkg_name, 'version': pkg_version}, - 'package/Patient-test1.json': { - 'resourceType': 'Patient', - 'id': 'test1', - 'name': [{'family': 'Test', 'given': ['Patient']}] - }, - 'package/Observation-test1.json': { - 'resourceType': 'Observation', - 'id': 'test1', - 'status': 'final', - 'code': {'coding': [{'system': 'http://loinc.org', 'code': '12345-6'}]} - } - }) - - # Push the IG to HAPI - response = self.client.post( - '/api/push-ig', - data=json.dumps({ - 'package_name': pkg_name, - 'version': pkg_version, - 'fhir_server_url': self.container.get_service_url('fhir', 'fhir'), - 'include_dependencies': False - }), - content_type='application/json', - headers={'X-API-Key': 'test-api-key', 'Accept': 'application/x-ndjson'} - ) - - self.assertEqual(response.status_code, 200) - streamed_data = parse_ndjson(response.data) - complete_msg = next((item for item in streamed_data if item.get('type') == 'complete'), None) - self.assertIsNotNone(complete_msg, "Complete message not found in streamed response") - summary = complete_msg.get('data', {}) - self.assertTrue(summary.get('success_count') >= 2, f"Expected at least 2 successful resources, got {summary.get('success_count')}") - - # Verify resources were loaded by querying the HAPI FHIR server directly - patient_response = requests.get(self.container.get_service_url('fhir', 'fhir/Patient/test1')) - self.assertEqual(patient_response.status_code, 200) - patient = patient_response.json() - self.assertEqual(patient['resourceType'], 'Patient') - self.assertEqual(patient['id'], 'test1') - - observation_response = requests.get(self.container.get_service_url('fhir', 'fhir/Observation/test1')) - self.assertEqual(observation_response.status_code, 200) - observation = observation_response.json() - self.assertEqual(observation['resourceType'], 'Observation') - self.assertEqual(observation['id'], 'test1') - - # --- Existing API Tests --- - - @patch('app.list_downloaded_packages') - @patch('app.services.process_package_file') - @patch('app.services.import_package_and_dependencies') - @patch('os.path.exists') - def test_40_api_import_ig_success(self, mock_os_exists, mock_import, mock_process, mock_list_pkgs): - pkg_name = 'api.test.pkg' - pkg_version = '1.2.3' - filename = f'{pkg_name}-{pkg_version}.tgz' - pkg_path = os.path.join(app.config['FHIR_PACKAGES_DIR'], filename) - mock_import.return_value = {'requested': (pkg_name, pkg_version), 'processed': {(pkg_name, pkg_version)}, 'downloaded': {(pkg_name, pkg_version): pkg_path}, 'all_dependencies': {}, 'dependencies': [], 'errors': []} - mock_process.return_value = {'resource_types_info': [], 'must_support_elements': {}, 'examples': {}, 'complies_with_profiles': ['http://prof.com/a'], 'imposed_profiles': [], 'errors': []} - mock_os_exists.return_value = True - mock_list_pkgs.return_value = ([{'name': pkg_name, 'version': pkg_version, 'filename': filename}], [], {}) - response = self.client.post( - '/api/import-ig', - data=json.dumps({'package_name': pkg_name, 'version': pkg_version, 'dependency_mode': 'direct', 'api_key': 'test-api-key'}), - content_type='application/json' - ) - self.assertEqual(response.status_code, 200) - data = json.loads(response.data) - self.assertEqual(data['status'], 'success') - self.assertEqual(data['complies_with_profiles'], ['http://prof.com/a']) - - @patch('app.services.import_package_and_dependencies') - def test_41_api_import_ig_failure(self, mock_import): - mock_import.return_value = {'requested': ('bad.pkg', '1.0'), 'processed': set(), 'downloaded': {}, 'all_dependencies': {}, 'dependencies': [], 'errors': ['HTTP error: 404 Not Found']} - response = self.client.post( - '/api/import-ig', - data=json.dumps({'package_name': 'bad.pkg', 'version': '1.0', 'api_key': 'test-api-key'}), - content_type='application/json' - ) - self.assertEqual(response.status_code, 404) - data = json.loads(response.data) - self.assertIn('Failed to import bad.pkg#1.0: HTTP error: 404 Not Found', data['message']) - - def test_42_api_import_ig_invalid_key(self): - response = self.client.post( - '/api/import-ig', - data=json.dumps({'package_name': 'a', 'version': '1', 'api_key': 'wrong'}), - content_type='application/json' - ) - self.assertEqual(response.status_code, 401) - - def test_43_api_import_ig_missing_key(self): - response = self.client.post( - '/api/import-ig', - data=json.dumps({'package_name': 'a', 'version': '1'}), - content_type='application/json' - ) - self.assertEqual(response.status_code, 401) - - # --- API Push Tests --- - - @patch('os.path.exists', return_value=True) - @patch('app.services.get_package_metadata') - @patch('tarfile.open') - @patch('requests.Session') - def test_50_api_push_ig_success(self, mock_session, mock_tarfile_open, mock_get_metadata, mock_os_exists): - pkg_name = 'push.test.pkg' - pkg_version = '1.0.0' - filename = f'{pkg_name}-{pkg_version}.tgz' - fhir_server_url = self.container.get_service_url('fhir', 'fhir') - mock_get_metadata.return_value = {'imported_dependencies': []} - mock_tar = MagicMock() - mock_patient = {'resourceType': 'Patient', 'id': 'pat1'} - mock_obs = {'resourceType': 'Observation', 'id': 'obs1', 'status': 'final'} - patient_member = MagicMock(spec=tarfile.TarInfo) - patient_member.name = 'package/Patient-pat1.json' - patient_member.isfile.return_value = True - obs_member = MagicMock(spec=tarfile.TarInfo) - obs_member.name = 'package/Observation-obs1.json' - obs_member.isfile.return_value = True - mock_tar.getmembers.return_value = [patient_member, obs_member] - def mock_extractfile(member): - if member.name == 'package/Patient-pat1.json': - return io.BytesIO(json.dumps(mock_patient).encode('utf-8')) - if member.name == 'package/Observation-obs1.json': - return io.BytesIO(json.dumps(mock_obs).encode('utf-8')) - return None - mock_tar.extractfile.side_effect = mock_extractfile - mock_tarfile_open.return_value.__enter__.return_value = mock_tar - mock_session_instance = MagicMock() - mock_put_response = MagicMock(status_code=200) - mock_put_response.raise_for_status.return_value = None - mock_session_instance.put.return_value = mock_put_response - mock_session.return_value = mock_session_instance - self.create_mock_tgz(filename, {'package/dummy.txt': 'content'}) - response = self.client.post( - '/api/push-ig', - data=json.dumps({ - 'package_name': pkg_name, - 'version': pkg_version, - 'fhir_server_url': fhir_server_url, - 'include_dependencies': False, - 'api_key': 'test-api-key' - }), - content_type='application/json', - headers={'X-API-Key': 'test-api-key', 'Accept': 'application/x-ndjson'} - ) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.mimetype, 'application/x-ndjson') - streamed_data = parse_ndjson(response.data) - complete_msg = next((item for item in streamed_data if item.get('type') == 'complete'), None) - self.assertIsNotNone(complete_msg) - summary = complete_msg.get('data', {}) - self.assertEqual(summary.get('status'), 'success') - self.assertEqual(summary.get('success_count'), 2) - self.assertEqual(len(summary.get('failed_details')), 0) - mock_os_exists.assert_called_with(os.path.join(self.test_packages_dir, filename)) - - # --- Helper method to debug container issues --- - - def test_99_print_container_logs_on_failure(self): - """Helper test that prints container logs in case of failures""" - # This test should always pass but will print logs if other tests fail - try: - if hasattr(self, 'container') and self.container.containers_up: - for service_name in ['fhir', 'db', 'fhirflare']: - if service_name in self.container._container_ids: - print(f"\n=== Logs for {service_name} ===") - print(self.container.get_logs(service_name)) - except Exception as e: - print(f"Error getting container logs: {e}") - - # This assertion always passes - this test is just for debug info - self.assertTrue(True) - -if __name__ == '__main__': - unittest.main() \ No newline at end of file diff --git a/tests/upload_samples/Bundle-transaction-ex.json b/tests/upload_samples/Bundle-transaction-ex.json deleted file mode 100644 index 22432af..0000000 --- a/tests/upload_samples/Bundle-transaction-ex.json +++ /dev/null @@ -1,431 +0,0 @@ -{ - "resourceType" : "Bundle", - "id" : "transaction-ex", - "type" : "transaction", - "entry" : [{ - "fullUrl" : "urn:uuid:64eb2d39-8da6-4c1d-b4c7-a6d3e916cd5b", - "resource" : { - "resourceType" : "Patient", - "id" : "example-patient", - "meta" : { - "profile" : ["urn://example.com/ph-core/fhir/StructureDefinition/ph-core-patient"] - }, - "text" : { - "status" : "generated", - "div" : "
Juan Dela Cruz is a male patient born on 1 January 1980, residing in Manila, NCR, Philippines.
" - }, - "extension" : [{ - "extension" : [{ - "url" : "code", - "valueCodeableConcept" : { - "coding" : [{ - "system" : "urn:iso:std:iso:3166", - "code" : "PH", - "display" : "Philippines" - }] - } - }, - { - "url" : "period", - "valuePeriod" : { - "start" : "2020-01-01", - "end" : "2023-01-01" - } - }], - "url" : "http://hl7.org/fhir/StructureDefinition/patient-nationality" - }, - { - "url" : "http://hl7.org/fhir/StructureDefinition/patient-religion", - "valueCodeableConcept" : { - "coding" : [{ - "system" : "http://terminology.hl7.org/CodeSystem/v3-ReligiousAffiliation", - "code" : "1007", - "display" : "Atheism" - }] - } - }, - { - "url" : "urn://example.com/ph-core/fhir/StructureDefinition/indigenous-people", - "valueBoolean" : true - }, - { - "url" : "urn://example.com/ph-core/fhir/StructureDefinition/indigenous-group", - "valueCodeableConcept" : { - "coding" : [{ - "system" : "urn://example.com/ph-core/fhir/CodeSystem/indigenous-groups", - "code" : "Ilongots", - "display" : "Ilongots" - }] - } - }, - { - "url" : "urn://example.com/ph-core/fhir/StructureDefinition/race", - "valueCodeableConcept" : { - "coding" : [{ - "system" : "http://terminology.hl7.org/CodeSystem/v3-Race", - "code" : "2036-2", - "display" : "Filipino" - }] - } - }], - "identifier" : [{ - "system" : "http://philhealth.gov.ph/fhir/Identifier/philhealth-id", - "value" : "63-584789845-5" - }], - "active" : true, - "name" : [{ - "family" : "Dela Cruz", - "given" : ["Juan Jane", - "Dela Fuente"] - }], - "gender" : "male", - "birthDate" : "1985-06-15", - "address" : [{ - "extension" : [{ - "url" : "urn://example.com/ph-core/fhir/StructureDefinition/city-municipality", - "valueCoding" : { - "system" : "urn://example.com/ph-core/fhir/CodeSystem/PSGC", - "code" : "1380200000", - "display" : "City of Las Piñas" - } - }, - { - "url" : "urn://example.com/ph-core/fhir/StructureDefinition/city-municipality", - "valueCoding" : { - "system" : "urn://example.com/ph-core/fhir/CodeSystem/PSGC", - "code" : "1380100000", - "display" : "City of Caloocan" - } - }, - { - "url" : "urn://example.com/ph-core/fhir/StructureDefinition/province", - "valueCoding" : { - "system" : "urn://example.com/ph-core/fhir/CodeSystem/PSGC", - "code" : "0402100000", - "display" : "Cavite" - } - }, - { - "url" : "urn://example.com/ph-core/fhir/StructureDefinition/province", - "valueCoding" : { - "system" : "urn://example.com/ph-core/fhir/CodeSystem/PSGC", - "code" : "0403400000", - "display" : "Laguna" - } - }, - { - "url" : "urn://example.com/ph-core/fhir/StructureDefinition/province", - "valueCoding" : { - "system" : "urn://example.com/ph-core/fhir/CodeSystem/PSGC", - "code" : "0405800000", - "display" : "Rizal" - } - }, - { - "url" : "urn://example.com/ph-core/fhir/StructureDefinition/province", - "valueCoding" : { - "system" : "urn://example.com/ph-core/fhir/CodeSystem/PSGC", - "code" : "1704000000", - "display" : "Marinduque" - } - }, - { - "url" : "urn://example.com/ph-core/fhir/StructureDefinition/province", - "valueCoding" : { - "system" : "urn://example.com/ph-core/fhir/CodeSystem/PSGC", - "code" : "0402100000", - "display" : "Cavite" - } - }, - { - "url" : "urn://example.com/ph-core/fhir/StructureDefinition/province", - "valueCoding" : { - "system" : "urn://example.com/ph-core/fhir/CodeSystem/PSGC", - "code" : "1705100000", - "display" : "Occidental Mindoro" - } - }], - "line" : ["123 Mabini Street", - "Barangay Malinis"], - "city" : "Quezon City", - "district" : "NCR", - "postalCode" : "1100", - "country" : "PH" - }] - }, - "request" : { - "method" : "POST", - "url" : "Patient" - } - }, - { - "fullUrl" : "urn:uuid:60b7132e-7cfd-44bc-83c2-de140dc8aaae", - "resource" : { - "resourceType" : "Encounter", - "id" : "example-encounter", - "meta" : { - "profile" : ["urn://example.com/ph-core/fhir/StructureDefinition/ph-core-encounter"] - }, - "text" : { - "status" : "generated", - "div" : "
An ambulatory encounter for Juan Dela Cruz that has been completed.
" - }, - "status" : "finished", - "class" : { - "system" : "http://terminology.hl7.org/CodeSystem/v3-ActCode", - "code" : "AMB", - "display" : "ambulatory" - }, - "subject" : { - "reference" : "urn:uuid:64eb2d39-8da6-4c1d-b4c7-a6d3e916cd5b" - } - }, - "request" : { - "method" : "POST", - "url" : "Encounter" - } - }, - { - "fullUrl" : "urn:uuid:1a391d1e-a068-479a-88e3-e3d52c3a6f64", - "resource" : { - "resourceType" : "Condition", - "id" : "example-condition", - "text" : { - "status" : "generated", - "div" : "
Juan Dela Cruz has an active diagnosis of Type 2 Diabetes Mellitus.
" - }, - "clinicalStatus" : { - "coding" : [{ - "system" : "http://terminology.hl7.org/CodeSystem/condition-clinical", - "code" : "active", - "display" : "Active" - }] - }, - "code" : { - "coding" : [{ - "system" : "http://snomed.info/sct", - "code" : "44054006", - "display" : "Diabetes mellitus type 2" - }] - }, - "subject" : { - "reference" : "urn:uuid:64eb2d39-8da6-4c1d-b4c7-a6d3e916cd5b" - }, - "encounter" : { - "reference" : "urn:uuid:60b7132e-7cfd-44bc-83c2-de140dc8aaae" - } - }, - "request" : { - "method" : "POST", - "url" : "Condition" - } - }, - { - "fullUrl" : "urn:uuid:024dcb47-cc23-407a-839b-b4634e95abae", - "resource" : { - "resourceType" : "Medication", - "id" : "example-medication", - "meta" : { - "profile" : ["urn://example.com/ph-core/fhir/StructureDefinition/ph-core-medication"] - }, - "text" : { - "status" : "generated", - "div" : "
A medication resource has been created, but no specific details are provided.
" - } - }, - "request" : { - "method" : "POST", - "url" : "Medication" - } - }, - { - "fullUrl" : "urn:uuid:013f46df-f245-4a2f-beaf-9eb2c47fb1a3", - "resource" : { - "resourceType" : "Observation", - "id" : "blood-pressure", - "meta" : { - "profile" : ["urn://example.com/ph-core/fhir/StructureDefinition/ph-core-observation", - "http://hl7.org/fhir/StructureDefinition/vitalsigns", - "http://hl7.org/fhir/StructureDefinition/bp"] - }, - "text" : { - "status" : "generated", - "div" : "
On 17 September 2012, a blood pressure observation was recorded for Juan Dela Cruz. The systolic pressure was 107 mmHg (Normal), and the diastolic pressure was 60 mmHg (Below low normal). The measurement was taken from the right arm and performed by a practitioner.
" - }, - "identifier" : [{ - "system" : "urn:ietf:rfc:3986", - "value" : "urn:uuid:187e0c12-8dd2-67e2-99b2-bf273c878281" - }], - "basedOn" : [{ - "identifier" : { - "system" : "https://acme.org/identifiers", - "value" : "1234" - } - }], - "status" : "final", - "category" : [{ - "coding" : [{ - "system" : "http://terminology.hl7.org/CodeSystem/observation-category", - "code" : "vital-signs", - "display" : "Vital Signs" - }] - }], - "code" : { - "coding" : [{ - "system" : "http://loinc.org", - "code" : "85354-9", - "display" : "Blood pressure panel with all children optional" - }], - "text" : "Blood pressure systolic & diastolic" - }, - "subject" : { - "reference" : "urn:uuid:64eb2d39-8da6-4c1d-b4c7-a6d3e916cd5b" - }, - "effectiveDateTime" : "2012-09-17", - "performer" : [{ - "reference" : "urn:uuid:a036fd4c-c950-497b-8905-0d2c5ec6f1d4" - }], - "interpretation" : [{ - "coding" : [{ - "system" : "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation", - "code" : "L", - "display" : "Low" - }], - "text" : "Below low normal" - }], - "bodySite" : { - "coding" : [{ - "system" : "http://snomed.info/sct", - "code" : "85050009", - "display" : "Bone structure of humerus" - }] - }, - "component" : [{ - "code" : { - "coding" : [{ - "system" : "http://loinc.org", - "code" : "8480-6", - "display" : "Systolic blood pressure" - }] - }, - "valueQuantity" : { - "value" : 107, - "unit" : "mmHg", - "system" : "http://unitsofmeasure.org", - "code" : "mm[Hg]" - }, - "interpretation" : [{ - "coding" : [{ - "system" : "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation", - "code" : "N", - "display" : "Normal" - }], - "text" : "Normal" - }] - }, - { - "code" : { - "coding" : [{ - "system" : "http://loinc.org", - "code" : "8462-4", - "display" : "Diastolic blood pressure" - }] - }, - "valueQuantity" : { - "value" : 60, - "unit" : "mmHg", - "system" : "http://unitsofmeasure.org", - "code" : "mm[Hg]" - }, - "interpretation" : [{ - "coding" : [{ - "system" : "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation", - "code" : "L", - "display" : "Low" - }], - "text" : "Below low normal" - }] - }] - }, - "request" : { - "method" : "POST", - "url" : "Observation" - } - }, - { - "fullUrl" : "urn:uuid:b43c67e7-d9c4-48bb-a1b4-55769eeb9066", - "resource" : { - "resourceType" : "AllergyIntolerance", - "id" : "example-allergy", - "text" : { - "status" : "generated", - "div" : "
Juan Dela Cruz has a high criticality, active allergy to Benethamine penicillin.
" - }, - "clinicalStatus" : { - "coding" : [{ - "system" : "http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical", - "code" : "active", - "display" : "Active" - }] - }, - "criticality" : "high", - "code" : { - "coding" : [{ - "system" : "http://snomed.info/sct", - "code" : "294494002", - "display" : "Benethamine penicillin allergy" - }] - }, - "patient" : { - "reference" : "urn:uuid:64eb2d39-8da6-4c1d-b4c7-a6d3e916cd5b" - } - }, - "request" : { - "method" : "POST", - "url" : "AllergyIntolerance" - } - }, - { - "fullUrl" : "urn:uuid:a036fd4c-c950-497b-8905-0d2c5ec6f1d4", - "resource" : { - "resourceType" : "Practitioner", - "id" : "example-practitioner", - "meta" : { - "profile" : ["urn://example.com/ph-core/fhir/StructureDefinition/ph-core-practitioner"] - }, - "text" : { - "status" : "generated", - "div" : "
Dr. Maria Clara Santos is a female practitioner born on May 15, 1985. She resides at 1234 Mabini Street, Manila, NCR, 1000, Philippines. She can be contacted via mobile at +63-912-345-6789 or by email at maria.santos@example.ph.
" - }, - "name" : [{ - "family" : "Santos", - "given" : ["Maria", - "Clara"] - }], - "telecom" : [{ - "system" : "phone", - "value" : "+63-912-345-6789", - "use" : "mobile" - }, - { - "system" : "email", - "value" : "maria.santos@example.ph", - "use" : "work" - }], - "address" : [{ - "use" : "home", - "line" : ["1234 Mabini Street"], - "city" : "Manila", - "state" : "NCR", - "postalCode" : "1000", - "country" : "PH" - }], - "gender" : "female", - "birthDate" : "1985-05-15" - }, - "request" : { - "method" : "POST", - "url" : "Practitioner" - } - }] -} \ No newline at end of file diff --git a/tests/upload_samples/PHCDI.r4-0.1.0.tgz b/tests/upload_samples/PHCDI.r4-0.1.0.tgz deleted file mode 100644 index beea6f903efb62f0af760cf556e4a6e4b990a37d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 593179 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

&Hchf!~b+`6$N3-mn^G0*W;I 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�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%0AHY2$X#;(TmWC8 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

*g>_Y)Z? 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%0Dat)7MM%-E(X3`R{|90zy~yo}wl35_wBN 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

*3~l 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

Zxlr8O+|k4HXz{Ez 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

hts&W!uZM3f#LrqqP(uKF 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%0A>$>YCo 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

eJ3e*fiUaq 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%0AynzCt$K;r{Rn3E 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%0DrB(IE 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

{tmIn+nP;`=bkqM`p!2-VfG&ZS(c~CBZ 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 Q82|}hsk#E19VOcT0gFjB;Q#;t diff --git a/tests/upload_samples/validation.log b/tests/upload_samples/validation.log deleted file mode 100644 index 0bc8823..0000000 --- a/tests/upload_samples/validation.log +++ /dev/null @@ -1,25 +0,0 @@ -2025-07-31 12:34:21,943 - services - DEBUG - Received validate-sample request -2025-07-31 12:34:21,944 - services - DEBUG - Request params: package_name=example.fhir.ph.core.r4, version=0.1.0, sample_data_length=713 -2025-07-31 12:34:21,944 - services - DEBUG - Using FHIR_PACKAGES_DIR from current_app config: /app/instance/fhir_packages -2025-07-31 12:34:21,944 - services - DEBUG - Checking package file: /app/instance/fhir_packages/example.fhir.ph.core.r4-0.1.0.tgz -2025-07-31 12:34:21,944 - services - DEBUG - Validating AllergyIntolerance against example.fhir.ph.core.r4#0.1.0 -2025-07-31 12:34:21,944 - services - DEBUG - Using FHIR_PACKAGES_DIR from current_app config: /app/instance/fhir_packages -2025-07-31 12:34:21,945 - services - DEBUG - Searching for SD matching 'AllergyIntolerance' with profile 'None' in example.fhir.ph.core.r4-0.1.0.tgz -2025-07-31 12:34:21,956 - services - INFO - SD matching identifier 'AllergyIntolerance' or profile 'None' not found within archive example.fhir.ph.core.r4-0.1.0.tgz -2025-07-31 12:34:21,956 - services - INFO - Validation result for AllergyIntolerance against example.fhir.ph.core.r4#0.1.0: valid=False, errors=1, warnings=0 -2025-07-31 12:34:21,957 - werkzeug - INFO - 10.0.0.102 - - [31/Jul/2025 12:34:21] "POST /api/validate-sample HTTP/1.1" 200 - -2025-07-31 12:34:24,510 - werkzeug - INFO - 10.0.2.245 - - [31/Jul/2025 12:34:24] "GET / HTTP/1.1" 200 - -2025-07-31 12:34:27,378 - werkzeug - INFO - 10.0.2.245 - - [31/Jul/2025 12:34:27] "GET / HTTP/1.1" 200 - -2025-07-31 12:34:34,510 - werkzeug - INFO - 10.0.2.245 - - [31/Jul/2025 12:34:34] "GET / HTTP/1.1" 200 - -2025-07-31 12:34:36,799 - __main__ - DEBUG - Scanning packages directory: /app/instance/fhir_packages -2025-07-31 12:34:36,800 - __main__ - DEBUG - Found 8 .tgz files: ['PHCDI.r4-0.1.0.tgz', 'hl7.fhir.uv.ips-1.1.0.tgz', 'hl7.fhir.r4.core-4.0.1.tgz', 'fhir.dicom-2022.4.20221006.tgz', 'hl7.terminology.r4-5.0.0.tgz', 'example.fhir.ph.core.r4-0.1.0.tgz', 'hl7.terminology.r4-6.4.0.tgz', 'hl7.fhir.uv.extensions.r4-5.2.0.tgz'] -2025-07-31 12:34:36,813 - __main__ - DEBUG - Added package: PHCDI.r4#0.1.0 -2025-07-31 12:34:36,837 - __main__ - DEBUG - Added package: hl7.fhir.uv.ips#1.1.0 -2025-07-31 12:34:37,378 - werkzeug - INFO - 10.0.2.245 - - [31/Jul/2025 12:34:37] "GET / HTTP/1.1" 200 - -2025-07-31 12:34:37,514 - __main__ - DEBUG - Added package: hl7.fhir.r4.core#4.0.1 -2025-07-31 12:34:37,622 - __main__ - DEBUG - Added package: fhir.dicom#2022.4.20221006 -2025-07-31 12:34:38,008 - __main__ - DEBUG - Added package: hl7.terminology.r4#5.0.0 -2025-07-31 12:34:38,015 - __main__ - DEBUG - Added package: example.fhir.ph.core.r4#0.1.0 -2025-07-31 12:34:38,413 - __main__ - DEBUG - Added package: hl7.terminology.r4#6.4.0 -2025-07-31 12:34:38,524 - __main__ - DEBUG - Added package: hl7.fhir.uv.extensions.r4#5.2.0 -2025-07-31 12:34:38,525 - __main__ - DEBUG - Set package choices: [('', 'None'), ('PHCDI.r4#0.1.0', 'PHCDI.r4#0.1.0'), ('example.fhir.ph.core.r4#0.1.0', 'example.fhir.ph.core.r4#0.1.0'), ('fhir.dicom#2022.4.20221006', 'fhir.dicom#2022.4.20221006'), ('hl7.fhir.r4.core#4.0.1', 'hl7.fhir.r4.core#4.0.1'), ('hl7.fhir.uv.extensions.r4#5.2.0', 'hl7.fhir.uv.extensions.r4#5.2.0'), ('hl7.fhir.uv.ips#1.1.0', 'hl7.fhir.uv.ips#1.1.0'), ('hl7.terminology.r4#5.0.0', 'hl7.terminology.r4#5.0.0'), ('hl7.terminology.r4#6.4.0', 'hl7.terminology.r4#6.4.0')] \ No newline at end of file