UI, Core logic, FHIR UI
GOFSH integration
THEME FIXES
This commit is contained in:
Joshua Hare 2025-04-19 01:05:14 +10:00
parent 7a5c21dd36
commit 833bc12d4d
27 changed files with 55393 additions and 1297 deletions

26
DockerCommands.MD Normal file
View File

@ -0,0 +1,26 @@
Docker Commands.MD
<HAPI-server.>
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
<rest-of-the-app:>
docker-compose build --no-cache
docker-compose up -d
<useful-stuff:>
cp <CONTAINERID>:/app/PATH/Filename.ext . - . copies to the root folder you ran it from
docker exec -it <CONTAINERID> bash - to get a bash - session in the container -

View File

@ -1,15 +1,15 @@
# Base image with Python and Java
FROM tomcat:10.1-jdk17
# Install build dependencies and Node.js 18
# 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 \
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 GoFSH globally
RUN npm install -g gofsh
# Install specific versions of GoFSH and SUSHI
RUN npm install -g gofsh fsh-sushi
# Set up Python environment
WORKDIR /app

743
README.md
View File

@ -1,308 +1,573 @@
# FHIRFLARE IG Toolkit
FHIRFLARE IG Toolkit
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). It offers a user-friendly interface for importing IG packages, extracting metadata, validating FHIR resources or bundles, pushing IGs to FHIR servers, and converting FHIR resources to FHIR Shorthand (FSH) using GoFSH with advanced features. The toolkit includes a live console for real-time feedback and a waiting spinner for FSH conversion, making it an essential tool for FHIR developers and implementers.
The application runs in a Dockerized environment with a Flask frontend, SQLite database, and an embedded HAPI FHIR server, ensuring consistent deployment and easy setup across platforms.
Features
## Overview
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, 1.1.2-ballot, current).
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, work in progress).
Push IGs: Upload IG resources to a FHIR server (e.g., HAPI FHIR) with real-time console output and optional validation against imposed profiles.
Profile Relationships: Display and validate compliesWithProfile and imposeProfile extensions in the UI.
FSH Converter: Convert FHIR JSON/XML resources to FHIR Shorthand (FSH) using GoFSH, with advanced options:
Package selection for context (e.g., hl7.fhir.au.core#1.1.0-preview).
Input modes: File upload or text input.
Output styles: file-per-definition, group-by-fsh-type, group-by-profile, single-file.
Log levels: error, warn, info, debug.
FHIR versions: R4, R4B, R5, or auto-detect.
Fishing Trip: Round-trip validation with SUSHI, generating a comparison report (fshing-trip-comparison.html) accessible via a "Click here for SUSHI Validation" badge button.
Dependencies: Load additional FHIR packages (e.g., hl7.fhir.us.core@6.1.0).
Indented Rules: Output FSH with context path indentation for readability.
Meta Profile Handling: Control meta.profile usage (only-one, first, none).
Alias File: Load existing FSH aliases (e.g., $MyAlias = http://example.org).
No Alias: Disable automatic alias generation.
Waiting Spinner: Displays a themed animation (light/dark) during FSH execution to indicate processing.
The FHIRFLARE IG Toolkit is a Flask-based web application designed to simplify the management, processing, validation, and deployment of FHIR Implementation Guides (IGs). It allows users to import IG packages, process them to extract resource types and profiles, validate FHIR resources or bundles against IGs, and push IGs to a FHIR server. The application features a user-friendly interface with a live console for real-time feedback during operations like pushing IGs or validating resources.
## Features
API Support: RESTful API endpoints for importing, pushing, and retrieving IG metadata, including profile relationships.
Live Console: Real-time logs for push, validation, and FSH conversion operations.
Configurable Behavior: Enable/disable imposed profile validation and UI display of profile relationships.
- **Import IGs**: Download FHIR IG packages and their dependencies from a package registry, supporting flexible version formats (e.g., `1.2.3`, `1.1.0-preview`, `1.1.2-ballot`, `current`).
- **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 (`compliesWithProfile` and `imposeProfile`) from IGs.
- **Validate FHIR Resources/Bundles**: Validate single FHIR resources or bundles against selected IGs, with detailed error and warning reports (alpha feature, work in progress).
- **Push IGs**: Upload IG resources to a FHIR server with real-time console output, including validation against imposed profiles.
- **Profile Relationships**: Support for `structuredefinition-compliesWithProfile` and `structuredefinition-imposeProfile` extensions, with validation and UI display.
- **API Support**: RESTful API endpoints for importing, pushing, and validating IGs, including profile relationship metadata.
- **Live Console**: Displays real-time logs during push and validation operations.
- **Configurable Behavior**: Options to enable/disable imposed profile validation and UI display of profile relationships.
Technology Stack
## Technology Stack
Python 3.12+: Core backend language.
Flask 2.3.3: Web framework for the application.
Flask-SQLAlchemy 3.0.5: ORM for SQLite database management.
Flask-WTF 1.2.1: Form creation, validation, and CSRF protection.
Jinja2: Templating engine for HTML rendering.
Bootstrap 5.3.3: Responsive frontend framework.
JavaScript (ES6): Client-side scripting for interactive features (e.g., live console, form toggles, waiting spinner).
Lottie-Web 5.12.2: Renders JSON-based animations for the FSH converter waiting spinner.
SQLite: Lightweight database for processed IG metadata.
Docker: Containerization with Flask and HAPI FHIR server.
Node.js 18+: For GoFSH and SUSHI, used in the FSH Converter feature.
GoFSH: Tool for converting FHIR resources to FHIR Shorthand (FSH).
SUSHI: FSH compiler for round-trip validation in Fishing Trip.
Requests 2.31.0: HTTP requests to FHIR servers.
Tarfile: Handling .tgz package files.
Logging: Pythons built-in logging for debugging.
The FHIRFLARE IG Toolkit is built using the following technologies:
Prerequisites
- **Python 3.9+**: Core programming language for the backend.
- **Flask 2.0+**: Lightweight web framework for building the application.
- **Flask-SQLAlchemy**: ORM for managing the SQLite database.
- **Flask-WTF**: Handles form creation, validation, and CSRF protection.
- **Jinja2**: Templating engine for rendering HTML pages.
- **Bootstrap 5**: Frontend framework for responsive UI design.
- **JavaScript (ES6)**: Client-side scripting for interactive features like the live console and JSON validation preview.
- **SQLite**: Lightweight database for storing processed IG metadata.
- **Docker**: Containerization for consistent deployment.
- **Requests**: Python library for making HTTP requests to FHIR servers.
- **Tarfile**: Python library for handling `.tgz` package files.
- **Logging**: Python's built-in logging for debugging and monitoring.
Docker: Required for containerized deployment.
Git: For cloning repositories.
Maven: For building the HAPI FHIR server.
Windows (if using batch files): For running Build and Run for first time.bat and Run.bat.
Linux/MacOS (if using manual steps): For running equivalent commands.
## Prerequisites
Setup Instructions
The toolkit can be set up using batch files (Windows) or manual steps (cross-platform).
Using Batch Files (Windows)
First-Time Setup and Build:
- **Python 3.9+**: Ensure Python is installed on your system.
- **Docker**: Required for containerized deployment.
- **pip**: Python package manager for installing dependencies.
Run Build and Run for first time.bat:
cd "<project folder>"
git clone 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
## Setup Instructions
This clones the HAPI FHIR server, copies configuration, builds the project, and starts the containers.
1. **Clone the Repository**:
```bash
git clone <repository-url>
cd fhirflare-ig-toolkit
```
2. **Install Dependencies**:
Create a virtual environment and install the required packages:
```bash
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install -r requirements.txt
```
Subsequent Runs:
3. **Set Environment Variables**:
Set the `FLASK_SECRET_KEY` and `API_KEY` for security and CSRF protection:
```bash
export FLASK_SECRET_KEY='your-secure-secret-key'
export API_KEY='your-api-key'
```
Run Run.bat:
cd "<project folder>"
docker-compose up -d
4. **Initialize the Database**:
Ensure the `instance` directory is writable for SQLite:
```bash
mkdir -p instance
chmod -R 777 instance
```
This starts the Flask app (port 5000) and HAPI FHIR server (port 8080).
5. **Run the Application Locally**:
Start the Flask development server:
```bash
export FLASK_APP=app.py
flask run
```
The application will be available at `http://localhost:5000`.
6. **Run with Docker**:
Build and run the application using Docker, mounting the `instance` directory for persistence:
```bash
docker build -t flare-fhir-ig-toolkit .
docker run -p 5000:5000 -e FLASK_SECRET_KEY='your-secure-secret-key' -e API_KEY='your-api-key' -v $(pwd)/instance:/app/instance flare-fhir-ig-toolkit
```
Access the application at `http://localhost:5000`.
Access the Application:
## Usage
Flask UI: http://localhost:5000
HAPI FHIR server: http://localhost:8080
1. **Import an IG**:
- Navigate to the "Import IG" tab.
- Enter a package name (e.g., `hl7.fhir.au.core`) and version (e.g., `1.1.0-preview`, `1.1.2-ballot`, or `current`).
- Choose a dependency mode (e.g., Recursive, Tree Shaking).
- Click "Import" to download the package and its dependencies.
Manual Setup (Linux/MacOS/Windows)
Preparation:
cd <project folder>
git clone 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
2. **Manage IGs**:
- Go to the "Manage FHIR Packages" tab to view downloaded and processed IGs.
- Process IGs to extract metadata, unload processed IGs from the database, or delete packages from the filesystem.
- Duplicates are highlighted for resolution (e.g., multiple versions of the same package).
Build:
mvn clean package -DskipTests=true -Pboot
docker-compose build --no-cache
3. **View Processed IGs**:
- After processing an IG, view its details, including resource types, profiles, must-support elements, examples, and profile relationships (`compliesWithProfile` and `imposeProfile`).
- Profile relationships are displayed if enabled via `DISPLAY_PROFILE_RELATIONSHIPS`.
Run:
docker-compose up -d
4. **Validate FHIR Resources/Bundles**:
- Navigate to the "Validate FHIR Sample" tab.
- Select or enter a package (e.g., `hl7.fhir.au.core#1.1.0-preview`).
- Choose validation mode (Single Resource or Bundle).
- Paste FHIR JSON (e.g., a Patient or AllergyIntolerance resource).
- Submit to validate, viewing errors and warnings in the report.
- Note: Validation is in alpha; report issues to GitHub (remove PHI).
Access the Application:
5. **Push IGs to a FHIR Server**:
- Navigate to the "Push IGs" tab.
- Select a package, enter a FHIR server URL (e.g., `http://hapi.fhir.org/baseR4`), and choose whether to include dependencies.
- Click "Push to FHIR Server" to upload resources, with validation against imposed profiles (if enabled via `VALIDATE_IMPOSED_PROFILES`) and progress shown in the live console.
Flask UI: http://localhost:5000
HAPI FHIR server: http://localhost:8080
6. **API Usage**:
- **Import IG**: `POST /api/import-ig`
```bash
curl -X POST http://localhost:5000/api/import-ig \
-H "Content-Type: application/json" \
-d '{"package_name": "hl7.fhir.au.core", "version": "1.1.0-preview", "api_key": "your-api-key"}'
```
Response includes `complies_with_profiles`, `imposed_profiles`, and duplicates.
- **Push IG**: `POST /api/push-ig`
```bash
curl -X POST http://localhost:5000/api/push-ig \
-H "Content-Type: application/json" \
-H "Accept: application/x-ndjson" \
-d '{"package_name": "hl7.fhir.au.core", "version": "1.1.0-preview", "fhir_server_url": "http://hapi.fhir.org/baseR4", "include_dependencies": true, "api_key": "your-api-key"}'
```
Resources are validated against imposed profiles before pushing.
- **Validate Resource/Bundle**: Not yet exposed via API; use the UI at `/validate-sample`.
Local Development (Without Docker)
Clone the Repository:
git clone https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit.git
cd FHIRFLARE-IG-Toolkit
## Configuration Options
Install Dependencies:
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install -r requirements.txt
- **`VALIDATE_IMPOSED_PROFILES`**: Set to `True` (default) to validate resources against imposed profiles during push operations. Set to `False` to skip:
```python
app.config['VALIDATE_IMPOSED_PROFILES'] = False
```
- **`DISPLAY_PROFILE_RELATIONSHIPS`**: Set to `True` (default) to show `compliesWithProfile` and `imposeProfile` in the UI. Set to `False` to hide:
```python
app.config['DISPLAY_PROFILE_RELATIONSHIPS'] = False
```
- **`FHIR_PACKAGES_DIR`**: Directory for storing `.tgz` packages and metadata (default: `/app/instance/fhir_packages`).
- **`SECRET_KEY`**: Required for CSRF protection and session security:
```python
app.config['SECRET_KEY'] = 'your-secure-secret-key'
```
Install Node.js, GoFSH, and SUSHI (for FSH Converter):
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo bash -
sudo apt-get install -y nodejs
npm install -g gofsh fsh-sushi
## Testing
Set Environment Variables:
export FLASK_SECRET_KEY='your-secure-secret-key'
export API_KEY='your-api-key'
The project includes a test suite to ensure reliability, covering UI, API, database, file operations, and security features.
Initialize Directories:
mkdir -p instance static/uploads logs
chmod -R 777 instance static/uploads logs
### Test Prerequisites
Run the Application:
export FLASK_APP=app.py
flask run
- **pytest**: For running tests.
- **unittest-mock**: For mocking dependencies.
Access at http://localhost:5000.
Usage
Import an IG
Install test dependencies:
```bash
Navigate to Import IG (/import-ig).
Enter a package name (e.g., hl7.fhir.au.core) and version (e.g., 1.1.0-preview).
Choose a dependency mode:
Recursive: Import all dependencies.
Patch Canonical: Import only canonical FHIR packages.
Tree Shaking: Import only used dependencies.
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/<id>), including:
Resource types and profiles.
Must-support elements and examples.
Profile relationships (compliesWithProfile, imposeProfile) if enabled (DISPLAY_PROFILE_RELATIONSHIPS).
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 package, enter a FHIR server URL (e.g., http://localhost:8080/fhir), and choose whether to include dependencies.
Click Push to FHIR Server to upload resources, with validation against imposed profiles (if enabled via VALIDATE_IMPOSED_PROFILES).
Monitor progress in the live console.
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.
Example Input:
{
"resourceType": "Patient",
"id": "banks-mia-leanne",
"meta": {
"profile": ["http://hl7.org.au/fhir/core/StructureDefinition/au-core-patient"]
},
"name": [
{
"family": "Banks",
"given": ["Mia", "Leanne"]
}
]
}
Example Output:
Profile: AUCorePatient
Parent: Patient
* name 1..*
* name.family 1..1
* name.given 1..*
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 servers 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.
API Usage
Import IG
curl -X POST http://localhost:5000/api/import-ig \
-H "Content-Type: application/json" \
-d '{"package_name": "hl7.fhir.au.core", "version": "1.1.0-preview", "api_key": "your-api-key"}'
Returns complies_with_profiles, imposed_profiles, and duplicate info.
Push IG
curl -X POST http://localhost:5000/api/push-ig \
-H "Content-Type: application/json" \
-H "Accept: application/x-ndjson" \
-d '{"package_name": "hl7.fhir.au.core", "version": "1.1.0-preview", "fhir_server_url": "http://localhost:8080/fhir", "include_dependencies": true, "api_key": "your-api-key"}'
Validates resources against imposed profiles (if enabled).
Validate Resource/Bundle
Not yet exposed via API; use the UI at /validate-sample.
Configuration Options
VALIDATE_IMPOSED_PROFILES:
Default: True
Validates resources against imposed profiles during push.
Set to False to skip:
app.config['VALIDATE_IMPOSED_PROFILES'] = False
DISPLAY_PROFILE_RELATIONSHIPS:
Default: True
Shows compliesWithProfile and imposeProfile in the UI.
Set to False to hide:
app.config['DISPLAY_PROFILE_RELATIONSHIPS'] = False
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:
app.config['SECRET_KEY'] = 'your-secure-secret-key'
API_KEY:
Required for API authentication:
app.config['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
Running Tests
cd <project folder>
pytest tests/test_app.py -v
1. **Navigate to Project Root**:
```bash
cd /path/to/fhirflare-ig-toolkit
```
Test Coverage
2. **Run Tests**:
```bash
pytest tests/test_app.py -v
```
- `-v` provides verbose output.
- Tests are in `tests/test_app.py`, covering 27 cases.
UI Pages: Homepage, Import IG, Manage IGs, Push IGs, Validate Sample, View Processed IG, FSH Converter.
API Endpoints: POST /api/import-ig, POST /api/push-ig, GET /get-structure, GET /get-example.
Database: IG processing, unloading, viewing.
File Operations: Package processing, deletion, FSH output.
Security: CSRF protection, flash messages, secret key.
FSH Converter: Form submission, file/text input, GoFSH execution, Fishing Trip comparison.
### Test Coverage
Tests include:
- **UI Pages**:
- Homepage, Import IG, Manage IGs, Push IGs, Validate Sample, View Processed IG.
- Form rendering, submissions, and error handling (e.g., invalid JSON, CSRF).
- **API Endpoints**:
- `POST /api/import-ig`: Success, invalid key, duplicates, profile relationships.
- `POST /api/push-ig`: Success, validation, errors.
- `GET /get-structure`, `GET /get-example`: Success and failure cases.
- **Database**:
- Processing, unloading, and viewing IGs.
- **File Operations**:
- Package processing, deletion.
- **Security**:
- CSRF protection, flash messages, secret key.
### Example Test Output
```
Example Test Output
================================================================ test session starts =================================================================
platform linux -- Python 3.9.22, pytest-8.3.5, pluggy-1.5.0
platform linux -- Python 3.12, pytest-8.3.5, pluggy-1.5.0
rootdir: /app/tests
collected 27 items
test_app.py::TestFHIRFlareIGToolkit::test_homepage PASSED [ 3%]
test_app.py::TestFHIRFlareIGToolkit::test_import_ig_page PASSED [ 7%]
test_app.py::TestFHIRFlareIGToolkit::test_fsh_converter_page PASSED [ 11%]
...
test_app.py::TestFHIRFlareIGToolkit::test_validate_sample_page PASSED [ 85%]
test_app.py::TestFHIRFlareIGToolkit::test_validate_sample_success PASSED [ 88%]
...
============================================================= 27 passed in 1.23s ==============================================================
```
### Troubleshooting Tests
Troubleshooting Tests
- **ModuleNotFoundError**: Ensure `app.py`, `services.py`, and `forms.py` are in `/app/`. Run tests from the project root.
- **TemplateNotFound**: Verify templates (`validate_sample.html`, etc.) are in `/app/templates/`.
- **Database Errors**: Ensure `instance/fhir_ig.db` is writable (`chmod 777 instance`).
- **Mock Failures**: Check `tests/test_app.py` for correct mocking of `services.py` functions.
ModuleNotFoundError: Ensure app.py, services.py, forms.py are in /app/.
TemplateNotFound: Verify templates are in /app/templates/.
Database Errors: Ensure instance/fhir_ig.db is writable (chmod 777 instance).
Mock Failures: Check tests/test_app.py for correct mocking.
## Development Notes
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, and flexible versioning, making it a versatile platform for FHIR developers.
Technical Decisions
### Background
Flask: Lightweight and flexible for web development.
SQLite: Simple for development; consider PostgreSQL for production.
Bootstrap 5.3.3: Responsive UI with custom styling for duplicates, FSH output, and waiting spinner.
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: Real-time feedback for complex operations.
Validation: Alpha feature with ongoing FHIRPath improvements.
The toolkit addresses the need for a user-friendly FHIR IG management tool, with recent enhancements for resource validation and flexible version handling (e.g., `1.1.0-preview`).
Recent Updates
### Technical Decisions
Waiting Spinner for FSH Converter (April 2025):
Added a themed (light/dark) Lottie animation spinner during FSH execution to indicate processing.
Path: templates/fsh_converter.html, static/animations/loading-dark.json, static/animations/loading-light.json, static/js/lottie-web.min.js.
- **Flask**: Lightweight and flexible for web development.
- **SQLite**: Simple for development; consider PostgreSQL for production.
- **Bootstrap 5**: Responsive UI with custom CSS for duplicate highlighting.
- **Flask-WTF**: Robust form validation and CSRF protection.
- **Docker**: Ensures consistent deployment.
- **Flexible Versioning**: Supports non-standard version formats for FHIR IGs (e.g., `-preview`, `-ballot`).
- **Validation**: Alpha feature for validating FHIR resources/bundles, with ongoing improvements to FHIRPath handling.
### Recent Updates
Advanced FSH Converter (April 2025):
Added support for GoFSH advanced options: --fshing-trip (round-trip validation with SUSHI), --dependency (additional packages), --indent (indented rules), --meta-profile (only-one, first, none), --alias-file (custom aliases), --no-alias (disable alias generation).
Displays Fishing Trip comparison reports via a badge button.
Path: templates/fsh_converter.html, app.py, services.py, forms.py.
- **Version Format Support**: Added support for flexible IG version formats (e.g., `1.1.0-preview`, `1.1.2-ballot`, `current`) in `forms.py`.
- **CSRF Protection**: Fixed missing CSRF tokens in `cp_downloaded_igs.html` and `cp_push_igs.html`, ensuring secure form submissions.
- **Form Handling**: Updated `validate_sample.html` and `app.py` to use `version` instead of `package_version`, aligning with `ValidationForm`.
- **Validation Feature**: Added alpha support for validating FHIR resources/bundles against IGs, with error/warning reports (UI only).
### Known Issues and Workarounds
FSH Converter (April 2025):
Added /fsh-converter page for FHIR to FSH conversion using GoFSH.
Path: templates/fsh_converter.html, app.py, services.py, forms.py.
- **CSRF Errors**: Ensure `SECRET_KEY` is set and forms include `{{ form.csrf_token }}`. Check logs for `flask_wtf.csrf` errors.
- **Version Validation**: Previously restricted to `x.y.z`; now supports suffixes like `-preview`. Report any import issues.
- **Validation Accuracy**: Resource validation is alpha; FHIRPath logic may miss complex constraints. Report anomalies to GitHub (remove PHI).
- **Package Parsing**: Non-standard `.tgz` filenames may parse incorrectly. Fallback treats them as name-only packages.
- **Permissions**: Ensure `instance` directory is writable (`chmod -R 777 instance`) to avoid database or file errors.
### Future Improvements
Favicon Fix (April 2025):
Resolved 404 for /favicon.ico on /fsh-converter by ensuring static/favicon.ico is served.
Added fallback /favicon.ico route in app.py.
- [ ] **Sorting Versions**: Sort package versions in `/view-igs` (e.g., ascending).
- [ ] **Duplicate Resolution**: Add options to keep latest version or merge resources.
- [ ] **Production Database**: Support PostgreSQL for scalability.
- [ ] **Validation Enhancements**: Improve FHIRPath handling for complex constraints; add API endpoint for validation.
- [ ] **Error Reporting**: Enhance UI feedback for validation errors with specific element paths.
**Completed Items**:
- ~~Testing: Comprehensive test suite for UI, API, and database.~~
- ~~Inbound API: `POST /api/import-ig` with dependency and profile support.~~
- ~~Outbound API: `POST /api/push-ig` with validation and feedback.~~
- ~~Flexible Versioning: Support for `-preview`, `-ballot`, etc.~~
- ~~CSRF Fixes: Secured forms in `cp_downloaded_igs.html`, `cp_push_igs.html`.~~
- ~~Resource Validation: UI for validating resources/bundles (alpha).~~
Menu Item (April 2025):
Added “FSH Converter” to the navbar in base.html.
### Far-Distant Improvements
- **Cache Service**: Use Redis to cache IG metadata for faster queries.
- **Database Optimization**: Add composite index on `ProcessedIg.package_name` and `ProcessedIg.version` for efficient lookups.
UPLOAD_FOLDER Fix (April 2025):
Fixed 500 error on /fsh-converter by setting app.config['UPLOAD_FOLDER'] = '/app/static/uploads'.
## Directory Structure
- `app.py`: Main Flask application.
- `services.py`: Logic for IG import, processing, validation, and pushing.
- `forms.py`: Form definitions for import and validation.
- `templates/`: HTML templates (`validate_sample.html`, `cp_downloaded_igs.html`, etc.).
- `instance/`: SQLite database (`fhir_ig.db`) and packages (`fhir_packages/`).
- `tests/test_app.py`: Test suite with 27 cases.
- `requirements.txt`: Python dependencies.
- `Dockerfile`: Docker configuration.
Validation (April 2025):
Alpha support for validating resources/bundles in /validate-sample.
Path: templates/validate_sample.html, app.py, services.py.
## Contributing
Contributions are welcome! To contribute:
1. Fork the repository.
2. Create a feature branch (`git checkout -b feature/your-feature`).
3. Commit changes (`git commit -m "Add your feature"`).
4. Push to your branch (`git push origin feature/your-feature`).
5. Open a Pull Request.
CSRF Protection: Fixed missing CSRF tokens in cp_downloaded_igs.html, cp_push_igs.html.
Version Support: Added flexible version formats (e.g., 1.1.0-preview) in forms.py.
Ensure code follows style guidelines and includes tests.
Known Issues and Workarounds
## Troubleshooting
Favicon 404: Clear browser cache or verify /app/static/favicon.ico:
docker exec -it <container_name> curl http://localhost:5000/static/favicon.ico
- **CSRF Errors**: Verify `SECRET_KEY` is set and forms include `{{ form.csrf_token }}`. Check browser DevTools for POST data.
- **Import Fails**: Confirm package name/version (e.g., `hl7.fhir.au.core#1.1.0-preview`) and internet connectivity.
- **Validation Errors**: Alpha feature; report issues to GitHub with JSON samples (remove PHI).
- **Database Issues**: Ensure `instance/fhir_ig.db` is writable (`chmod 777 instance`).
- **Docker Volume**: Mount `instance` directory to persist data:
```bash
docker run -v $(pwd)/instance:/app/instance ...
```
## License
CSRF Errors: Set FLASK_SECRET_KEY and ensure {{ form.hidden_tag() }} in forms.
Licensed under the Apache 2.0 License. See `LICENSE` for details.
Import Fails: Check package name/version and connectivity.
Validation Accuracy: Alpha feature; FHIRPath may miss complex constraints. 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 <container_name> sushi --version
Future Improvements
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).
Spinner Enhancements: Customize spinner animation speed or size.
Completed Items
Testing suite with 27 cases.
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.
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, and FSH conversion
├── 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
│ ├── hl7.fhir.au.base-5.1.0-preview.metadata.json
│ ├── hl7.fhir.au.base-5.1.0-preview.tgz
│ ├── hl7.fhir.au.core-1.1.0-preview.metadata.json
│ ├── hl7.fhir.au.core-1.1.0-preview.tgz
│ ├── hl7.fhir.r4.core-4.0.1.metadata.json
│ ├── hl7.fhir.r4.core-4.0.1.tgz
│ ├── hl7.fhir.uv.extensions.r4-5.2.0.metadata.json
│ ├── hl7.fhir.uv.extensions.r4-5.2.0.tgz
│ ├── hl7.fhir.uv.ipa-1.0.0.metadata.json
│ ├── hl7.fhir.uv.ipa-1.0.0.tgz
│ ├── hl7.fhir.uv.smart-app-launch-2.0.0.metadata.json
│ ├── hl7.fhir.uv.smart-app-launch-2.0.0.tgz
│ ├── hl7.fhir.uv.smart-app-launch-2.1.0.metadata.json
│ ├── hl7.fhir.uv.smart-app-launch-2.1.0.tgz
│ ├── hl7.terminology.r4-5.0.0.metadata.json
│ ├── hl7.terminology.r4-5.0.0.tgz
│ ├── hl7.terminology.r4-6.2.0.metadata.json
│ └── hl7.terminology.r4-6.2.0.tgz
├── 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
│ └── fsh_output/
│ ├── sushi-config.yaml # SUSHI configuration
│ └── input/
│ └── fsh/
│ ├── aliases.fsh # FSH aliases
│ ├── index.txt # FSH index
│ └── instances/
│ └── banks-mia-leanne.fsh # Example FSH instance
├── 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
│ ├── validate_sample.html # UI for validating resources/bundles
│ └── _form_helpers.html # Form helper macros
├── tests/
│ └── test_app.py # Test suite with 27 cases
└── hapi-fhir-jpaserver/ # HAPI FHIR server resources
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 <container_name> 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 filenamesgrass 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 <container_name> sushi --version
License
Licensed under the Apache 2.0 License. See LICENSE.md for details.

1
Starting Normal file
View File

@ -0,0 +1 @@
=== Docker containers (Step 7)...

186
app.py
View File

@ -2,7 +2,8 @@ import sys
import os
sys.path.append(os.path.abspath(os.path.dirname(__file__)))
import datetime
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, Response, current_app, session, send_file
import shutil
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, Response, current_app, session, send_file, make_response
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from flask_wtf.csrf import CSRFProtect
@ -943,70 +944,168 @@ def proxy_hapi(subpath):
logger.error(f"Proxy error: {str(e)}")
return jsonify({'error': str(e)}), response.status_code if 'response' in locals() else 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()
error = None
fsh_output = None
error = None
comparison_report = None
# Populate package choices
# --- Populate package choices ---
packages = []
packages_dir = app.config['FHIR_PACKAGES_DIR']
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):
for filename in os.listdir(packages_dir):
if filename.endswith('.tgz'):
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:
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)
# 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:
packages.append((f"{name}#{version}", f"{name}#{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 reading package {filename}: {e}")
continue
form.package.choices = [('', 'None')] + sorted(packages, key=lambda x: x[0])
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}")
if form.validate_on_submit():
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
fhir_file = form.fhir_file.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 or None
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
# Process input
input_path, temp_dir, error = services.process_fhir_input(input_mode, fhir_file, fhir_text)
if error:
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')
return render_template('fsh_converter.html', form=form, error=error, site_name='FHIRFLARE IG Toolkit', now=datetime.datetime.now())
# Run GoFSH
output_dir = os.path.join(app.config['UPLOAD_FOLDER'], 'fsh_output')
os.makedirs(output_dir, exist_ok=True)
fsh_output, error = services.run_gofsh(input_path, output_dir, output_style, log_level, fhir_version)
# Clean up temporary files
logger.error(f"Input processing error: {error}")
if temp_dir and os.path.exists(temp_dir):
import shutil
shutil.rmtree(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 error:
if gofsh_error:
error = gofsh_error
flash(error, 'error')
return render_template('fsh_converter.html', form=form, error=error, site_name='FHIRFLARE IG Toolkit', now=datetime.datetime.now())
# Store output for download
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')
return render_template('fsh_converter.html', form=form, fsh_output=fsh_output, site_name='FHIRFLARE IG Toolkit', now=datetime.datetime.now())
logger.info("FSH conversion successful")
return render_template('fsh_converter.html', form=form, error=error, site_name='FHIRFLARE IG Toolkit', now=datetime.datetime.now())
# 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', '')
fsh_output = session.get('fsh_output')
if not fsh_output:
flash('No FSH output available for download.', 'error')
return redirect(url_for('fsh_converter'))
@ -1017,6 +1116,19 @@ def download_fsh():
return send_file(temp_file, as_attachment=True, download_name='output.fsh')
@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)

View File

@ -3,6 +3,8 @@ from flask_wtf import FlaskForm
from wtforms import StringField, SelectField, TextAreaField, BooleanField, SubmitField, FileField
from wtforms.validators import DataRequired, Regexp, ValidationError, Optional
import json
import xml.etree.ElementTree as ET
import re
# Existing forms (IgImportForm, ValidationForm) remain unchanged
class IgImportForm(FlaskForm):
@ -68,12 +70,22 @@ class FSHConverterForm(FlaskForm):
('info', 'Info'),
('debug', 'Debug')
], validators=[DataRequired()])
fhir_version = SelectField('FHIR Version', choices=[
fhir_version = SelectField('FXML 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):
@ -91,7 +103,6 @@ class FSHConverterForm(FlaskForm):
if content.startswith('{'):
json.loads(content)
elif content.startswith('<'):
import xml.etree.ElementTree as ET
ET.fromstring(content)
else:
self.fhir_text.errors.append('Text input must be valid JSON or XML.')
@ -99,4 +110,15 @@ class FSHConverterForm(FlaskForm):
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.split('\n'):
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
if self.alias_file.data:
content = self.alias_file.data.read().decode('utf-8')
if not content.strip().endswith('.fsh'):
self.alias_file.errors.append('Alias file must be a valid FSH file (.fsh).')
return False
return True

View File

@ -2,3 +2,11 @@
* Debug mode: off
* Serving Flask app 'app'
* Debug mode: off
* Serving Flask app 'app'
* Debug mode: off
* Serving Flask app 'app'
* Debug mode: off
* Serving Flask app 'app'
* Debug mode: off
* Serving Flask app 'app'
* Debug mode: off

View File

@ -5,129 +5,34 @@ DEBUG:__main__:Flask instance folder path: /app/instance
DEBUG:__main__:Directories created/verified: Instance: /app/instance, Packages: /app/instance/fhir_packages
DEBUG:__main__:Attempting to create database tables for URI: sqlite:////app/instance/fhir_ig.db
INFO:__main__:Database tables created successfully (if they didn't exist).
DEBUG:__main__:Instance path configuration: /app/instance
DEBUG:__main__:Database URI: sqlite:////app/instance/fhir_ig.db
DEBUG:__main__:Packages path: /app/instance/fhir_packages
DEBUG:__main__:Flask instance folder path: /app/instance
DEBUG:__main__:Directories created/verified: Instance: /app/instance, Packages: /app/instance/fhir_packages
DEBUG:__main__:Attempting to create database tables for URI: sqlite:////app/instance/fhir_ig.db
INFO:__main__:Database tables created successfully (if they didn't exist).
INFO:werkzeug:WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://172.19.0.2:5000
* Running on http://172.18.0.2:5000
INFO:werkzeug:Press CTRL+C to quit
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 06:25:30] "GET /fhir-ui-operations HTTP/1.1" 200 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 06:25:30] "GET /static/FHIRFLARE.png HTTP/1.1" 200 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 06:25:31] "GET /static/favicon.ico HTTP/1.1" 200 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 06:25:36] "GET /fsh-converter HTTP/1.1" 200 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 06:25:36] "GET /static/FHIRFLARE.png HTTP/1.1" 304 -
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 14:38:13] "GET / HTTP/1.1" 200 -
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 14:38:14] "GET /static/FHIRFLARE.png HTTP/1.1" 304 -
DEBUG:__main__:Scanning packages directory: /app/instance/fhir_packages
DEBUG:services:Parsed 'hl7.fhir.au.base-5.1.0-preview.tgz' -> name='hl7.fhir.au.base-5.1.0', version='preview'
DEBUG:services:Parsed 'hl7.fhir.au.core-1.1.0-preview.tgz' -> name='hl7.fhir.au.core-1.1.0', version='preview'
DEBUG:services:Parsed 'hl7.fhir.r4.core-4.0.1.tgz' -> name='hl7.fhir.r4.core', version='4.0.1'
DEBUG:services:Parsed 'hl7.fhir.uv.extensions.r4-5.2.0.tgz' -> name='hl7.fhir.uv.extensions.r4', version='5.2.0'
DEBUG:services:Parsed 'hl7.fhir.uv.ipa-1.0.0.tgz' -> name='hl7.fhir.uv.ipa', version='1.0.0'
DEBUG:services:Parsed 'hl7.fhir.uv.smart-app-launch-2.0.0.tgz' -> name='hl7.fhir.uv.smart-app-launch', version='2.0.0'
DEBUG:services:Parsed 'hl7.fhir.uv.smart-app-launch-2.1.0.tgz' -> name='hl7.fhir.uv.smart-app-launch', version='2.1.0'
DEBUG:services:Parsed 'hl7.terminology.r4-5.0.0.tgz' -> name='hl7.terminology.r4', version='5.0.0'
DEBUG:services:Parsed 'hl7.terminology.r4-6.2.0.tgz' -> name='hl7.terminology.r4', version='6.2.0'
DEBUG:__main__:Found packages: [{'name': 'hl7.fhir.au.base', 'version': '5.1.0-preview', 'filename': 'hl7.fhir.au.base-5.1.0-preview.tgz'}, {'name': 'hl7.fhir.au.core', 'version': '1.1.0-preview', 'filename': 'hl7.fhir.au.core-1.1.0-preview.tgz'}, {'name': 'hl7.fhir.r4.core', 'version': '4.0.1', 'filename': 'hl7.fhir.r4.core-4.0.1.tgz'}, {'name': 'hl7.fhir.uv.extensions.r4', 'version': '5.2.0', 'filename': 'hl7.fhir.uv.extensions.r4-5.2.0.tgz'}, {'name': 'hl7.fhir.uv.ipa', 'version': '1.0.0', 'filename': 'hl7.fhir.uv.ipa-1.0.0.tgz'}, {'name': 'hl7.fhir.uv.smart-app-launch', 'version': '2.0.0', 'filename': 'hl7.fhir.uv.smart-app-launch-2.0.0.tgz'}, {'name': 'hl7.fhir.uv.smart-app-launch', 'version': '2.1.0', 'filename': 'hl7.fhir.uv.smart-app-launch-2.1.0.tgz'}, {'name': 'hl7.terminology.r4', 'version': '5.0.0', 'filename': 'hl7.terminology.r4-5.0.0.tgz'}, {'name': 'hl7.terminology.r4', 'version': '6.2.0', 'filename': 'hl7.terminology.r4-6.2.0.tgz'}]
DEBUG:__main__:Errors during package listing: []
DEBUG:__main__:Duplicate groups: {'hl7.fhir.uv.smart-app-launch': ['2.0.0', '2.1.0'], 'hl7.terminology.r4': ['5.0.0', '6.2.0']}
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 06:25:56] "GET /view-igs HTTP/1.1" 200 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 06:25:56] "GET /static/FHIRFLARE.png HTTP/1.1" 304 -
DEBUG:__main__:Viewing IG hl7.fhir.au.core-1.1.0#preview: 25 profiles, 17 base resources, 1 optional elements
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 06:25:59] "GET /view-ig/1 HTTP/1.1" 200 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 06:25:59] "GET /static/FHIRFLARE.png HTTP/1.1" 304 -
DEBUG:__main__:Attempting to find SD for 'Practitioner' in hl7.fhir.au.core-1.1.0-preview.tgz
DEBUG:services:Searching for SD matching 'Practitioner' with profile 'None' in hl7.fhir.au.core-1.1.0-preview.tgz
DEBUG:services:Found SD: id=au-core-allergyintolerance, name=AUCoreAllergyIntolerance, type=AllergyIntolerance, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-allergyintolerance, path=package/StructureDefinition-au-core-allergyintolerance.json
DEBUG:services:Found SD: id=au-core-bloodpressure, name=AUCoreBloodPressure, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-bloodpressure, path=package/StructureDefinition-au-core-bloodpressure.json
DEBUG:services:Found SD: id=au-core-bodyheight, name=AUCoreBodyHeight, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-bodyheight, path=package/StructureDefinition-au-core-bodyheight.json
DEBUG:services:Found SD: id=au-core-bodytemp, name=AUCoreBodyTemperature, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-bodytemp, path=package/StructureDefinition-au-core-bodytemp.json
DEBUG:services:Found SD: id=au-core-bodyweight, name=AUCoreBodyWeight, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-bodyweight, path=package/StructureDefinition-au-core-bodyweight.json
DEBUG:services:Found SD: id=au-core-condition, name=AUCoreCondition, type=Condition, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-condition, path=package/StructureDefinition-au-core-condition.json
DEBUG:services:Found SD: id=au-core-diagnosticresult-path, name=AUCorePathologyResult, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-diagnosticresult-path, path=package/StructureDefinition-au-core-diagnosticresult-path.json
DEBUG:services:Found SD: id=au-core-diagnosticresult, name=AUCoreDiagnosticResult, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-diagnosticresult, path=package/StructureDefinition-au-core-diagnosticresult.json
DEBUG:services:Found SD: id=au-core-encounter, name=AUCoreEncounter, type=Encounter, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-encounter, path=package/StructureDefinition-au-core-encounter.json
DEBUG:services:Found SD: id=au-core-heartrate, name=AUCoreHeartRate, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-heartrate, path=package/StructureDefinition-au-core-heartrate.json
DEBUG:services:Found SD: id=au-core-immunization, name=AUCoreImmunization, type=Immunization, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-immunization, path=package/StructureDefinition-au-core-immunization.json
DEBUG:services:Found SD: id=au-core-location, name=AUCoreLocation, type=Location, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-location, path=package/StructureDefinition-au-core-location.json
DEBUG:services:Found SD: id=au-core-medication, name=AUCoreMedication, type=Medication, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-medication, path=package/StructureDefinition-au-core-medication.json
DEBUG:services:Found SD: id=au-core-medicationrequest, name=AUCoreMedicationRequest, type=MedicationRequest, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-medicationrequest, path=package/StructureDefinition-au-core-medicationrequest.json
DEBUG:services:Found SD: id=au-core-medicationstatement, name=AUCoreMedicationStatement, type=MedicationStatement, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-medicationstatement, path=package/StructureDefinition-au-core-medicationstatement.json
DEBUG:services:Found SD: id=au-core-organization, name=AUCoreOrganization, type=Organization, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-organization, path=package/StructureDefinition-au-core-organization.json
DEBUG:services:Found SD: id=au-core-patient, name=AUCorePatient, type=Patient, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-patient, path=package/StructureDefinition-au-core-patient.json
DEBUG:services:Found SD: id=au-core-practitioner, name=AUCorePractitioner, type=Practitioner, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-practitioner, path=package/StructureDefinition-au-core-practitioner.json
INFO:services:Found matching SD for 'Practitioner' at path: package/StructureDefinition-au-core-practitioner.json
DEBUG:services:Found SD: id=au-core-practitionerrole, name=AUCorePractitionerRole, type=PractitionerRole, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-practitionerrole, path=package/StructureDefinition-au-core-practitionerrole.json
INFO:services:Found matching SD for 'Practitioner' at path: package/StructureDefinition-au-core-practitionerrole.json
DEBUG:services:Found SD: id=au-core-procedure, name=AUCoreProcedure, type=Procedure, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-procedure, path=package/StructureDefinition-au-core-procedure.json
DEBUG:services:Found SD: id=au-core-relatedperson, name=AUCoreRelatedPerson, type=RelatedPerson, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-relatedperson, path=package/StructureDefinition-au-core-relatedperson.json
DEBUG:services:Found SD: id=au-core-resprate, name=AUCoreRespirationRate, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-resprate, path=package/StructureDefinition-au-core-resprate.json
DEBUG:services:Found SD: id=au-core-rsg-sexassignedab, name=AUCoreSexAssignedAtBirth, type=Extension, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-rsg-sexassignedab, path=package/StructureDefinition-au-core-rsg-sexassignedab.json
DEBUG:services:Found SD: id=au-core-smokingstatus, name=AUCoreSmokingStatus, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-smokingstatus, path=package/StructureDefinition-au-core-smokingstatus.json
DEBUG:services:Found SD: id=au-core-waistcircum, name=AUCoreWaistCircumference, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-waistcircum, path=package/StructureDefinition-au-core-waistcircum.json
DEBUG:__main__:Retrieved 0 Must Support paths for 'Practitioner' from processed IG hl7.fhir.au.core-1.1.0#preview
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 06:26:04] "GET /get-structure?package_name=hl7.fhir.au.core-1.1.0&package_version=preview&resource_type=Practitioner HTTP/1.1" 200 -
DEBUG:__main__:Attempting to find SD for 'au-core-practitioner' in hl7.fhir.au.core-1.1.0-preview.tgz
DEBUG:services:Searching for SD matching 'au-core-practitioner' with profile 'None' in hl7.fhir.au.core-1.1.0-preview.tgz
DEBUG:services:Found SD: id=au-core-allergyintolerance, name=AUCoreAllergyIntolerance, type=AllergyIntolerance, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-allergyintolerance, path=package/StructureDefinition-au-core-allergyintolerance.json
DEBUG:services:Found SD: id=au-core-bloodpressure, name=AUCoreBloodPressure, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-bloodpressure, path=package/StructureDefinition-au-core-bloodpressure.json
DEBUG:services:Found SD: id=au-core-bodyheight, name=AUCoreBodyHeight, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-bodyheight, path=package/StructureDefinition-au-core-bodyheight.json
DEBUG:services:Found SD: id=au-core-bodytemp, name=AUCoreBodyTemperature, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-bodytemp, path=package/StructureDefinition-au-core-bodytemp.json
DEBUG:services:Found SD: id=au-core-bodyweight, name=AUCoreBodyWeight, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-bodyweight, path=package/StructureDefinition-au-core-bodyweight.json
DEBUG:services:Found SD: id=au-core-condition, name=AUCoreCondition, type=Condition, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-condition, path=package/StructureDefinition-au-core-condition.json
DEBUG:services:Found SD: id=au-core-diagnosticresult-path, name=AUCorePathologyResult, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-diagnosticresult-path, path=package/StructureDefinition-au-core-diagnosticresult-path.json
DEBUG:services:Found SD: id=au-core-diagnosticresult, name=AUCoreDiagnosticResult, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-diagnosticresult, path=package/StructureDefinition-au-core-diagnosticresult.json
DEBUG:services:Found SD: id=au-core-encounter, name=AUCoreEncounter, type=Encounter, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-encounter, path=package/StructureDefinition-au-core-encounter.json
DEBUG:services:Found SD: id=au-core-heartrate, name=AUCoreHeartRate, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-heartrate, path=package/StructureDefinition-au-core-heartrate.json
DEBUG:services:Found SD: id=au-core-immunization, name=AUCoreImmunization, type=Immunization, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-immunization, path=package/StructureDefinition-au-core-immunization.json
DEBUG:services:Found SD: id=au-core-location, name=AUCoreLocation, type=Location, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-location, path=package/StructureDefinition-au-core-location.json
DEBUG:services:Found SD: id=au-core-medication, name=AUCoreMedication, type=Medication, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-medication, path=package/StructureDefinition-au-core-medication.json
DEBUG:services:Found SD: id=au-core-medicationrequest, name=AUCoreMedicationRequest, type=MedicationRequest, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-medicationrequest, path=package/StructureDefinition-au-core-medicationrequest.json
DEBUG:services:Found SD: id=au-core-medicationstatement, name=AUCoreMedicationStatement, type=MedicationStatement, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-medicationstatement, path=package/StructureDefinition-au-core-medicationstatement.json
DEBUG:services:Found SD: id=au-core-organization, name=AUCoreOrganization, type=Organization, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-organization, path=package/StructureDefinition-au-core-organization.json
DEBUG:services:Found SD: id=au-core-patient, name=AUCorePatient, type=Patient, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-patient, path=package/StructureDefinition-au-core-patient.json
DEBUG:services:Found SD: id=au-core-practitioner, name=AUCorePractitioner, type=Practitioner, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-practitioner, path=package/StructureDefinition-au-core-practitioner.json
INFO:services:Found matching SD for 'au-core-practitioner' at path: package/StructureDefinition-au-core-practitioner.json
DEBUG:services:Found SD: id=au-core-practitionerrole, name=AUCorePractitionerRole, type=PractitionerRole, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-practitionerrole, path=package/StructureDefinition-au-core-practitionerrole.json
INFO:services:Found matching SD for 'au-core-practitioner' at path: package/StructureDefinition-au-core-practitionerrole.json
DEBUG:services:Found SD: id=au-core-procedure, name=AUCoreProcedure, type=Procedure, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-procedure, path=package/StructureDefinition-au-core-procedure.json
DEBUG:services:Found SD: id=au-core-relatedperson, name=AUCoreRelatedPerson, type=RelatedPerson, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-relatedperson, path=package/StructureDefinition-au-core-relatedperson.json
DEBUG:services:Found SD: id=au-core-resprate, name=AUCoreRespirationRate, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-resprate, path=package/StructureDefinition-au-core-resprate.json
DEBUG:services:Found SD: id=au-core-rsg-sexassignedab, name=AUCoreSexAssignedAtBirth, type=Extension, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-rsg-sexassignedab, path=package/StructureDefinition-au-core-rsg-sexassignedab.json
DEBUG:services:Found SD: id=au-core-smokingstatus, name=AUCoreSmokingStatus, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-smokingstatus, path=package/StructureDefinition-au-core-smokingstatus.json
DEBUG:services:Found SD: id=au-core-waistcircum, name=AUCoreWaistCircumference, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-waistcircum, path=package/StructureDefinition-au-core-waistcircum.json
DEBUG:__main__:Retrieved 5 Must Support paths for 'au-core-practitioner' from processed IG hl7.fhir.au.core-1.1.0#preview
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 06:26:15] "GET /get-structure?package_name=hl7.fhir.au.core-1.1.0&package_version=preview&resource_type=au-core-practitioner HTTP/1.1" 200 -
DEBUG:__main__:Attempting to find SD for 'au-core-patient' in hl7.fhir.au.core-1.1.0-preview.tgz
DEBUG:services:Searching for SD matching 'au-core-patient' with profile 'None' in hl7.fhir.au.core-1.1.0-preview.tgz
DEBUG:services:Found SD: id=au-core-allergyintolerance, name=AUCoreAllergyIntolerance, type=AllergyIntolerance, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-allergyintolerance, path=package/StructureDefinition-au-core-allergyintolerance.json
DEBUG:services:Found SD: id=au-core-bloodpressure, name=AUCoreBloodPressure, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-bloodpressure, path=package/StructureDefinition-au-core-bloodpressure.json
DEBUG:services:Found SD: id=au-core-bodyheight, name=AUCoreBodyHeight, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-bodyheight, path=package/StructureDefinition-au-core-bodyheight.json
DEBUG:services:Found SD: id=au-core-bodytemp, name=AUCoreBodyTemperature, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-bodytemp, path=package/StructureDefinition-au-core-bodytemp.json
DEBUG:services:Found SD: id=au-core-bodyweight, name=AUCoreBodyWeight, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-bodyweight, path=package/StructureDefinition-au-core-bodyweight.json
DEBUG:services:Found SD: id=au-core-condition, name=AUCoreCondition, type=Condition, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-condition, path=package/StructureDefinition-au-core-condition.json
DEBUG:services:Found SD: id=au-core-diagnosticresult-path, name=AUCorePathologyResult, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-diagnosticresult-path, path=package/StructureDefinition-au-core-diagnosticresult-path.json
DEBUG:services:Found SD: id=au-core-diagnosticresult, name=AUCoreDiagnosticResult, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-diagnosticresult, path=package/StructureDefinition-au-core-diagnosticresult.json
DEBUG:services:Found SD: id=au-core-encounter, name=AUCoreEncounter, type=Encounter, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-encounter, path=package/StructureDefinition-au-core-encounter.json
DEBUG:services:Found SD: id=au-core-heartrate, name=AUCoreHeartRate, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-heartrate, path=package/StructureDefinition-au-core-heartrate.json
DEBUG:services:Found SD: id=au-core-immunization, name=AUCoreImmunization, type=Immunization, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-immunization, path=package/StructureDefinition-au-core-immunization.json
DEBUG:services:Found SD: id=au-core-location, name=AUCoreLocation, type=Location, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-location, path=package/StructureDefinition-au-core-location.json
DEBUG:services:Found SD: id=au-core-medication, name=AUCoreMedication, type=Medication, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-medication, path=package/StructureDefinition-au-core-medication.json
DEBUG:services:Found SD: id=au-core-medicationrequest, name=AUCoreMedicationRequest, type=MedicationRequest, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-medicationrequest, path=package/StructureDefinition-au-core-medicationrequest.json
DEBUG:services:Found SD: id=au-core-medicationstatement, name=AUCoreMedicationStatement, type=MedicationStatement, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-medicationstatement, path=package/StructureDefinition-au-core-medicationstatement.json
DEBUG:services:Found SD: id=au-core-organization, name=AUCoreOrganization, type=Organization, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-organization, path=package/StructureDefinition-au-core-organization.json
DEBUG:services:Found SD: id=au-core-patient, name=AUCorePatient, type=Patient, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-patient, path=package/StructureDefinition-au-core-patient.json
INFO:services:Found matching SD for 'au-core-patient' at path: package/StructureDefinition-au-core-patient.json
DEBUG:services:Found SD: id=au-core-practitioner, name=AUCorePractitioner, type=Practitioner, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-practitioner, path=package/StructureDefinition-au-core-practitioner.json
DEBUG:services:Found SD: id=au-core-practitionerrole, name=AUCorePractitionerRole, type=PractitionerRole, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-practitionerrole, path=package/StructureDefinition-au-core-practitionerrole.json
DEBUG:services:Found SD: id=au-core-procedure, name=AUCoreProcedure, type=Procedure, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-procedure, path=package/StructureDefinition-au-core-procedure.json
DEBUG:services:Found SD: id=au-core-relatedperson, name=AUCoreRelatedPerson, type=RelatedPerson, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-relatedperson, path=package/StructureDefinition-au-core-relatedperson.json
DEBUG:services:Found SD: id=au-core-resprate, name=AUCoreRespirationRate, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-resprate, path=package/StructureDefinition-au-core-resprate.json
DEBUG:services:Found SD: id=au-core-rsg-sexassignedab, name=AUCoreSexAssignedAtBirth, type=Extension, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-rsg-sexassignedab, path=package/StructureDefinition-au-core-rsg-sexassignedab.json
DEBUG:services:Found SD: id=au-core-smokingstatus, name=AUCoreSmokingStatus, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-smokingstatus, path=package/StructureDefinition-au-core-smokingstatus.json
DEBUG:services:Found SD: id=au-core-waistcircum, name=AUCoreWaistCircumference, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-waistcircum, path=package/StructureDefinition-au-core-waistcircum.json
DEBUG:__main__:Retrieved 19 Must Support paths for 'au-core-patient' from processed IG hl7.fhir.au.core-1.1.0#preview
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 06:26:16] "GET /get-structure?package_name=hl7.fhir.au.core-1.1.0&package_version=preview&resource_type=au-core-patient HTTP/1.1" 200 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 06:26:19] "GET /get-example?package_name=hl7.fhir.au.core-1.1.0&package_version=preview&filename=package/example/Patient-banks-mia-leanne.json HTTP/1.1" 200 -
DEBUG:services:Processed input: /tmp/tmph9efztgj/input.json
ERROR:app:Exception on /fsh-converter [POST]
DEBUG:__main__:Found 9 .tgz files: ['hl7.fhir.au.base-5.1.0-preview.tgz', 'hl7.fhir.au.core-1.1.0-preview.tgz', 'hl7.fhir.r4.core-4.0.1.tgz', 'hl7.fhir.uv.extensions.r4-5.2.0.tgz', 'hl7.fhir.uv.ipa-1.0.0.tgz', 'hl7.fhir.uv.smart-app-launch-2.0.0.tgz', 'hl7.fhir.uv.smart-app-launch-2.1.0.tgz', 'hl7.terminology.r4-5.0.0.tgz', 'hl7.terminology.r4-6.2.0.tgz']
DEBUG:__main__:Added package: hl7.fhir.au.base#5.1.0-preview
DEBUG:__main__:Added package: hl7.fhir.au.core#1.1.0-preview
DEBUG:__main__:Added package: hl7.fhir.r4.core#4.0.1
DEBUG:__main__:Added package: hl7.fhir.uv.extensions.r4#5.2.0
DEBUG:__main__:Added package: hl7.fhir.uv.ipa#1.0.0
DEBUG:__main__:Added package: hl7.fhir.uv.smart-app-launch#2.0.0
DEBUG:__main__:Added package: hl7.fhir.uv.smart-app-launch#2.1.0
DEBUG:__main__:Added package: hl7.terminology.r4#5.0.0
DEBUG:__main__:Added package: hl7.terminology.r4#6.2.0
DEBUG:__main__:Set package choices: [('', 'None'), ('hl7.fhir.au.base#5.1.0-preview', 'hl7.fhir.au.base#5.1.0-preview'), ('hl7.fhir.au.core#1.1.0-preview', 'hl7.fhir.au.core#1.1.0-preview'), ('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.ipa#1.0.0', 'hl7.fhir.uv.ipa#1.0.0'), ('hl7.fhir.uv.smart-app-launch#2.0.0', 'hl7.fhir.uv.smart-app-launch#2.0.0'), ('hl7.fhir.uv.smart-app-launch#2.1.0', 'hl7.fhir.uv.smart-app-launch#2.1.0'), ('hl7.terminology.r4#5.0.0', 'hl7.terminology.r4#5.0.0'), ('hl7.terminology.r4#6.2.0', 'hl7.terminology.r4#6.2.0')]
DEBUG:__main__:Handling GET request for FSH converter page.
ERROR:app:Exception on /fsh-converter [GET]
Traceback (most recent call last):
File "/app/venv/lib/python3.12/site-packages/flask/app.py", line 2190, in wsgi_app
response = self.full_dispatch_request()
@ -141,34 +46,18 @@ Traceback (most recent call last):
File "/app/venv/lib/python3.12/site-packages/flask/app.py", line 1469, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/app.py", line 985, in fsh_converter
output_dir = os.path.join(app.config['UPLOAD_FOLDER'], 'fsh_output')
~~~~~~~~~~^^^^^^^^^^^^^^^^^
KeyError: 'UPLOAD_FOLDER'
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 06:28:22] "POST /fsh-converter HTTP/1.1" 500 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 06:28:22] "GET /favicon.ico HTTP/1.1" 404 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 06:45:46] "GET /fsh-converter HTTP/1.1" 200 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 06:45:46] "GET /static/FHIRFLARE.png HTTP/1.1" 304 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 06:45:46] "GET /static/favicon.ico HTTP/1.1" 304 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 06:45:48] "GET / HTTP/1.1" 200 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 06:45:48] "GET /static/FHIRFLARE.png HTTP/1.1" 304 -
DEBUG:__main__:Scanning packages directory: /app/instance/fhir_packages
DEBUG:services:Parsed 'hl7.fhir.au.base-5.1.0-preview.tgz' -> name='hl7.fhir.au.base-5.1.0', version='preview'
DEBUG:services:Parsed 'hl7.fhir.au.core-1.1.0-preview.tgz' -> name='hl7.fhir.au.core-1.1.0', version='preview'
DEBUG:services:Parsed 'hl7.fhir.r4.core-4.0.1.tgz' -> name='hl7.fhir.r4.core', version='4.0.1'
DEBUG:services:Parsed 'hl7.fhir.uv.extensions.r4-5.2.0.tgz' -> name='hl7.fhir.uv.extensions.r4', version='5.2.0'
DEBUG:services:Parsed 'hl7.fhir.uv.ipa-1.0.0.tgz' -> name='hl7.fhir.uv.ipa', version='1.0.0'
DEBUG:services:Parsed 'hl7.fhir.uv.smart-app-launch-2.0.0.tgz' -> name='hl7.fhir.uv.smart-app-launch', version='2.0.0'
DEBUG:services:Parsed 'hl7.fhir.uv.smart-app-launch-2.1.0.tgz' -> name='hl7.fhir.uv.smart-app-launch', version='2.1.0'
DEBUG:services:Parsed 'hl7.terminology.r4-5.0.0.tgz' -> name='hl7.terminology.r4', version='5.0.0'
DEBUG:services:Parsed 'hl7.terminology.r4-6.2.0.tgz' -> name='hl7.terminology.r4', version='6.2.0'
DEBUG:__main__:Found packages: [{'name': 'hl7.fhir.au.base', 'version': '5.1.0-preview', 'filename': 'hl7.fhir.au.base-5.1.0-preview.tgz'}, {'name': 'hl7.fhir.au.core', 'version': '1.1.0-preview', 'filename': 'hl7.fhir.au.core-1.1.0-preview.tgz'}, {'name': 'hl7.fhir.r4.core', 'version': '4.0.1', 'filename': 'hl7.fhir.r4.core-4.0.1.tgz'}, {'name': 'hl7.fhir.uv.extensions.r4', 'version': '5.2.0', 'filename': 'hl7.fhir.uv.extensions.r4-5.2.0.tgz'}, {'name': 'hl7.fhir.uv.ipa', 'version': '1.0.0', 'filename': 'hl7.fhir.uv.ipa-1.0.0.tgz'}, {'name': 'hl7.fhir.uv.smart-app-launch', 'version': '2.0.0', 'filename': 'hl7.fhir.uv.smart-app-launch-2.0.0.tgz'}, {'name': 'hl7.fhir.uv.smart-app-launch', 'version': '2.1.0', 'filename': 'hl7.fhir.uv.smart-app-launch-2.1.0.tgz'}, {'name': 'hl7.terminology.r4', 'version': '5.0.0', 'filename': 'hl7.terminology.r4-5.0.0.tgz'}, {'name': 'hl7.terminology.r4', 'version': '6.2.0', 'filename': 'hl7.terminology.r4-6.2.0.tgz'}]
DEBUG:__main__:Errors during package listing: []
DEBUG:__main__:Duplicate groups: {'hl7.fhir.uv.smart-app-launch': ['2.0.0', '2.1.0'], 'hl7.terminology.r4': ['5.0.0', '6.2.0']}
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 06:45:52] "GET /view-igs HTTP/1.1" 200 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 06:45:52] "GET /static/FHIRFLARE.png HTTP/1.1" 304 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 06:45:57] "GET /fsh-converter HTTP/1.1" 200 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 06:45:57] "GET /static/FHIRFLARE.png HTTP/1.1" 304 -
File "/app/app.py", line 1061, in fsh_converter
response = make_response(render_template( # <<< CORRECTED: Using make_response
^^^^^^^^^^^^^
NameError: name 'make_response' is not defined
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 14:38:21] "GET /fsh-converter HTTP/1.1" 500 -
DEBUG:__main__:Instance path configuration: /app/instance
DEBUG:__main__:Database URI: sqlite:////app/instance/fhir_ig.db
DEBUG:__main__:Packages path: /app/instance/fhir_packages
DEBUG:__main__:Flask instance folder path: /app/instance
DEBUG:__main__:Directories created/verified: Instance: /app/instance, Packages: /app/instance/fhir_packages
DEBUG:__main__:Attempting to create database tables for URI: sqlite:////app/instance/fhir_ig.db
INFO:__main__:Database tables created successfully (if they didn't exist).
DEBUG:__main__:Instance path configuration: /app/instance
DEBUG:__main__:Database URI: sqlite:////app/instance/fhir_ig.db
DEBUG:__main__:Packages path: /app/instance/fhir_packages
@ -179,80 +68,458 @@ INFO:__main__:Database tables created successfully (if they didn't exist).
INFO:werkzeug:WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://172.19.0.2:5000
* Running on http://172.18.0.2:5000
INFO:werkzeug:Press CTRL+C to quit
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 07:09:09] "GET /fsh-converter HTTP/1.1" 200 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 07:09:09] "GET /static/FHIRFLARE.png HTTP/1.1" 200 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 07:09:09] "GET /static/favicon.ico HTTP/1.1" 200 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 07:09:13] "GET /fsh-converter HTTP/1.1" 200 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 07:09:13] "GET /static/FHIRFLARE.png HTTP/1.1" 304 -
DEBUG:__main__:Attempting to find SD for 'au-core-allergyintolerance' in hl7.fhir.au.core-1.1.0-preview.tgz
DEBUG:services:Searching for SD matching 'au-core-allergyintolerance' with profile 'None' in hl7.fhir.au.core-1.1.0-preview.tgz
DEBUG:services:Found SD: id=au-core-allergyintolerance, name=AUCoreAllergyIntolerance, type=AllergyIntolerance, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-allergyintolerance, path=package/StructureDefinition-au-core-allergyintolerance.json
INFO:services:Found matching SD for 'au-core-allergyintolerance' at path: package/StructureDefinition-au-core-allergyintolerance.json
DEBUG:services:Found SD: id=au-core-bloodpressure, name=AUCoreBloodPressure, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-bloodpressure, path=package/StructureDefinition-au-core-bloodpressure.json
DEBUG:services:Found SD: id=au-core-bodyheight, name=AUCoreBodyHeight, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-bodyheight, path=package/StructureDefinition-au-core-bodyheight.json
DEBUG:services:Found SD: id=au-core-bodytemp, name=AUCoreBodyTemperature, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-bodytemp, path=package/StructureDefinition-au-core-bodytemp.json
DEBUG:services:Found SD: id=au-core-bodyweight, name=AUCoreBodyWeight, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-bodyweight, path=package/StructureDefinition-au-core-bodyweight.json
DEBUG:services:Found SD: id=au-core-condition, name=AUCoreCondition, type=Condition, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-condition, path=package/StructureDefinition-au-core-condition.json
DEBUG:services:Found SD: id=au-core-diagnosticresult-path, name=AUCorePathologyResult, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-diagnosticresult-path, path=package/StructureDefinition-au-core-diagnosticresult-path.json
DEBUG:services:Found SD: id=au-core-diagnosticresult, name=AUCoreDiagnosticResult, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-diagnosticresult, path=package/StructureDefinition-au-core-diagnosticresult.json
DEBUG:services:Found SD: id=au-core-encounter, name=AUCoreEncounter, type=Encounter, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-encounter, path=package/StructureDefinition-au-core-encounter.json
DEBUG:services:Found SD: id=au-core-heartrate, name=AUCoreHeartRate, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-heartrate, path=package/StructureDefinition-au-core-heartrate.json
DEBUG:services:Found SD: id=au-core-immunization, name=AUCoreImmunization, type=Immunization, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-immunization, path=package/StructureDefinition-au-core-immunization.json
DEBUG:services:Found SD: id=au-core-location, name=AUCoreLocation, type=Location, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-location, path=package/StructureDefinition-au-core-location.json
DEBUG:services:Found SD: id=au-core-medication, name=AUCoreMedication, type=Medication, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-medication, path=package/StructureDefinition-au-core-medication.json
DEBUG:services:Found SD: id=au-core-medicationrequest, name=AUCoreMedicationRequest, type=MedicationRequest, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-medicationrequest, path=package/StructureDefinition-au-core-medicationrequest.json
DEBUG:services:Found SD: id=au-core-medicationstatement, name=AUCoreMedicationStatement, type=MedicationStatement, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-medicationstatement, path=package/StructureDefinition-au-core-medicationstatement.json
DEBUG:services:Found SD: id=au-core-organization, name=AUCoreOrganization, type=Organization, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-organization, path=package/StructureDefinition-au-core-organization.json
DEBUG:services:Found SD: id=au-core-patient, name=AUCorePatient, type=Patient, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-patient, path=package/StructureDefinition-au-core-patient.json
DEBUG:services:Found SD: id=au-core-practitioner, name=AUCorePractitioner, type=Practitioner, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-practitioner, path=package/StructureDefinition-au-core-practitioner.json
DEBUG:services:Found SD: id=au-core-practitionerrole, name=AUCorePractitionerRole, type=PractitionerRole, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-practitionerrole, path=package/StructureDefinition-au-core-practitionerrole.json
DEBUG:services:Found SD: id=au-core-procedure, name=AUCoreProcedure, type=Procedure, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-procedure, path=package/StructureDefinition-au-core-procedure.json
DEBUG:services:Found SD: id=au-core-relatedperson, name=AUCoreRelatedPerson, type=RelatedPerson, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-relatedperson, path=package/StructureDefinition-au-core-relatedperson.json
DEBUG:services:Found SD: id=au-core-resprate, name=AUCoreRespirationRate, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-resprate, path=package/StructureDefinition-au-core-resprate.json
DEBUG:services:Found SD: id=au-core-rsg-sexassignedab, name=AUCoreSexAssignedAtBirth, type=Extension, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-rsg-sexassignedab, path=package/StructureDefinition-au-core-rsg-sexassignedab.json
DEBUG:services:Found SD: id=au-core-smokingstatus, name=AUCoreSmokingStatus, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-smokingstatus, path=package/StructureDefinition-au-core-smokingstatus.json
DEBUG:services:Found SD: id=au-core-waistcircum, name=AUCoreWaistCircumference, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-waistcircum, path=package/StructureDefinition-au-core-waistcircum.json
DEBUG:__main__:Retrieved 8 Must Support paths for 'au-core-allergyintolerance' from processed IG hl7.fhir.au.core-1.1.0#preview
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 07:09:27] "GET /get-structure?package_name=hl7.fhir.au.core-1.1.0&package_version=preview&resource_type=au-core-allergyintolerance HTTP/1.1" 200 -
DEBUG:__main__:Attempting to find SD for 'au-core-patient' in hl7.fhir.au.core-1.1.0-preview.tgz
DEBUG:services:Searching for SD matching 'au-core-patient' with profile 'None' in hl7.fhir.au.core-1.1.0-preview.tgz
DEBUG:services:Found SD: id=au-core-allergyintolerance, name=AUCoreAllergyIntolerance, type=AllergyIntolerance, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-allergyintolerance, path=package/StructureDefinition-au-core-allergyintolerance.json
DEBUG:services:Found SD: id=au-core-bloodpressure, name=AUCoreBloodPressure, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-bloodpressure, path=package/StructureDefinition-au-core-bloodpressure.json
DEBUG:services:Found SD: id=au-core-bodyheight, name=AUCoreBodyHeight, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-bodyheight, path=package/StructureDefinition-au-core-bodyheight.json
DEBUG:services:Found SD: id=au-core-bodytemp, name=AUCoreBodyTemperature, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-bodytemp, path=package/StructureDefinition-au-core-bodytemp.json
DEBUG:services:Found SD: id=au-core-bodyweight, name=AUCoreBodyWeight, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-bodyweight, path=package/StructureDefinition-au-core-bodyweight.json
DEBUG:services:Found SD: id=au-core-condition, name=AUCoreCondition, type=Condition, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-condition, path=package/StructureDefinition-au-core-condition.json
DEBUG:services:Found SD: id=au-core-diagnosticresult-path, name=AUCorePathologyResult, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-diagnosticresult-path, path=package/StructureDefinition-au-core-diagnosticresult-path.json
DEBUG:services:Found SD: id=au-core-diagnosticresult, name=AUCoreDiagnosticResult, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-diagnosticresult, path=package/StructureDefinition-au-core-diagnosticresult.json
DEBUG:services:Found SD: id=au-core-encounter, name=AUCoreEncounter, type=Encounter, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-encounter, path=package/StructureDefinition-au-core-encounter.json
DEBUG:services:Found SD: id=au-core-heartrate, name=AUCoreHeartRate, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-heartrate, path=package/StructureDefinition-au-core-heartrate.json
DEBUG:services:Found SD: id=au-core-immunization, name=AUCoreImmunization, type=Immunization, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-immunization, path=package/StructureDefinition-au-core-immunization.json
DEBUG:services:Found SD: id=au-core-location, name=AUCoreLocation, type=Location, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-location, path=package/StructureDefinition-au-core-location.json
DEBUG:services:Found SD: id=au-core-medication, name=AUCoreMedication, type=Medication, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-medication, path=package/StructureDefinition-au-core-medication.json
DEBUG:services:Found SD: id=au-core-medicationrequest, name=AUCoreMedicationRequest, type=MedicationRequest, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-medicationrequest, path=package/StructureDefinition-au-core-medicationrequest.json
DEBUG:services:Found SD: id=au-core-medicationstatement, name=AUCoreMedicationStatement, type=MedicationStatement, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-medicationstatement, path=package/StructureDefinition-au-core-medicationstatement.json
DEBUG:services:Found SD: id=au-core-organization, name=AUCoreOrganization, type=Organization, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-organization, path=package/StructureDefinition-au-core-organization.json
DEBUG:services:Found SD: id=au-core-patient, name=AUCorePatient, type=Patient, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-patient, path=package/StructureDefinition-au-core-patient.json
INFO:services:Found matching SD for 'au-core-patient' at path: package/StructureDefinition-au-core-patient.json
DEBUG:services:Found SD: id=au-core-practitioner, name=AUCorePractitioner, type=Practitioner, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-practitioner, path=package/StructureDefinition-au-core-practitioner.json
DEBUG:services:Found SD: id=au-core-practitionerrole, name=AUCorePractitionerRole, type=PractitionerRole, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-practitionerrole, path=package/StructureDefinition-au-core-practitionerrole.json
DEBUG:services:Found SD: id=au-core-procedure, name=AUCoreProcedure, type=Procedure, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-procedure, path=package/StructureDefinition-au-core-procedure.json
DEBUG:services:Found SD: id=au-core-relatedperson, name=AUCoreRelatedPerson, type=RelatedPerson, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-relatedperson, path=package/StructureDefinition-au-core-relatedperson.json
DEBUG:services:Found SD: id=au-core-resprate, name=AUCoreRespirationRate, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-resprate, path=package/StructureDefinition-au-core-resprate.json
DEBUG:services:Found SD: id=au-core-rsg-sexassignedab, name=AUCoreSexAssignedAtBirth, type=Extension, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-rsg-sexassignedab, path=package/StructureDefinition-au-core-rsg-sexassignedab.json
DEBUG:services:Found SD: id=au-core-smokingstatus, name=AUCoreSmokingStatus, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-smokingstatus, path=package/StructureDefinition-au-core-smokingstatus.json
DEBUG:services:Found SD: id=au-core-waistcircum, name=AUCoreWaistCircumference, type=Observation, url=http://hl7.org.au/fhir/core/StructureDefinition/au-core-waistcircum, path=package/StructureDefinition-au-core-waistcircum.json
DEBUG:__main__:Retrieved 19 Must Support paths for 'au-core-patient' from processed IG hl7.fhir.au.core-1.1.0#preview
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 07:09:28] "GET /get-structure?package_name=hl7.fhir.au.core-1.1.0&package_version=preview&resource_type=au-core-patient HTTP/1.1" 200 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 07:09:32] "GET /get-example?package_name=hl7.fhir.au.core-1.1.0&package_version=preview&filename=package/example/Patient-banks-mia-leanne.json HTTP/1.1" 200 -
DEBUG:services:Processed input: /tmp/tmpgwv6g5vl/input.json
INFO:services:GoFSH executed successfully for /tmp/tmpgwv6g5vl/input.json
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 07:10:23] "POST /fsh-converter HTTP/1.1" 200 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 07:10:23] "GET /static/FHIRFLARE.png HTTP/1.1" 304 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 07:11:09] "GET /download-fsh HTTP/1.1" 200 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 07:16:19] "GET /fhir HTTP/1.1" 200 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 07:16:19] "GET /static/FHIRFLARE.png HTTP/1.1" 304 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 07:16:56] "GET /fsh-converter HTTP/1.1" 200 -
INFO:werkzeug:172.19.0.1 - - [17/Apr/2025 07:16:56] "GET /static/FHIRFLARE.png HTTP/1.1" 304 -
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 14:45:38] "GET / HTTP/1.1" 200 -
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 14:45:38] "GET /static/FHIRFLARE.png HTTP/1.1" 304 -
DEBUG:__main__:Scanning packages directory: /app/instance/fhir_packages
DEBUG:__main__:Found 9 .tgz files: ['hl7.fhir.au.base-5.1.0-preview.tgz', 'hl7.fhir.au.core-1.1.0-preview.tgz', 'hl7.fhir.r4.core-4.0.1.tgz', 'hl7.fhir.uv.extensions.r4-5.2.0.tgz', 'hl7.fhir.uv.ipa-1.0.0.tgz', 'hl7.fhir.uv.smart-app-launch-2.0.0.tgz', 'hl7.fhir.uv.smart-app-launch-2.1.0.tgz', 'hl7.terminology.r4-5.0.0.tgz', 'hl7.terminology.r4-6.2.0.tgz']
DEBUG:__main__:Added package: hl7.fhir.au.base#5.1.0-preview
DEBUG:__main__:Added package: hl7.fhir.au.core#1.1.0-preview
DEBUG:__main__:Added package: hl7.fhir.r4.core#4.0.1
DEBUG:__main__:Added package: hl7.fhir.uv.extensions.r4#5.2.0
DEBUG:__main__:Added package: hl7.fhir.uv.ipa#1.0.0
DEBUG:__main__:Added package: hl7.fhir.uv.smart-app-launch#2.0.0
DEBUG:__main__:Added package: hl7.fhir.uv.smart-app-launch#2.1.0
DEBUG:__main__:Added package: hl7.terminology.r4#5.0.0
DEBUG:__main__:Added package: hl7.terminology.r4#6.2.0
DEBUG:__main__:Set package choices: [('', 'None'), ('hl7.fhir.au.base#5.1.0-preview', 'hl7.fhir.au.base#5.1.0-preview'), ('hl7.fhir.au.core#1.1.0-preview', 'hl7.fhir.au.core#1.1.0-preview'), ('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.ipa#1.0.0', 'hl7.fhir.uv.ipa#1.0.0'), ('hl7.fhir.uv.smart-app-launch#2.0.0', 'hl7.fhir.uv.smart-app-launch#2.0.0'), ('hl7.fhir.uv.smart-app-launch#2.1.0', 'hl7.fhir.uv.smart-app-launch#2.1.0'), ('hl7.terminology.r4#5.0.0', 'hl7.terminology.r4#5.0.0'), ('hl7.terminology.r4#6.2.0', 'hl7.terminology.r4#6.2.0')]
DEBUG:__main__:Handling GET request for FSH converter page.
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 14:45:46] "GET /fsh-converter HTTP/1.1" 200 -
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 14:45:46] "GET /static/FHIRFLARE.png HTTP/1.1" 304 -
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 14:45:46] "GET /static/js/lottie.min.js HTTP/1.1" 304 -
DEBUG:__main__:Instance path configuration: /app/instance
DEBUG:__main__:Database URI: sqlite:////app/instance/fhir_ig.db
DEBUG:__main__:Packages path: /app/instance/fhir_packages
DEBUG:__main__:Flask instance folder path: /app/instance
DEBUG:__main__:Directories created/verified: Instance: /app/instance, Packages: /app/instance/fhir_packages
DEBUG:__main__:Attempting to create database tables for URI: sqlite:////app/instance/fhir_ig.db
INFO:__main__:Database tables created successfully (if they didn't exist).
DEBUG:__main__:Instance path configuration: /app/instance
DEBUG:__main__:Database URI: sqlite:////app/instance/fhir_ig.db
DEBUG:__main__:Packages path: /app/instance/fhir_packages
DEBUG:__main__:Flask instance folder path: /app/instance
DEBUG:__main__:Directories created/verified: Instance: /app/instance, Packages: /app/instance/fhir_packages
DEBUG:__main__:Attempting to create database tables for URI: sqlite:////app/instance/fhir_ig.db
INFO:__main__:Database tables created successfully (if they didn't exist).
INFO:werkzeug:WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://172.18.0.2:5000
INFO:werkzeug:Press CTRL+C to quit
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 14:50:05] "GET / HTTP/1.1" 200 -
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 14:50:06] "GET /static/FHIRFLARE.png HTTP/1.1" 304 -
DEBUG:__main__:Scanning packages directory: /app/instance/fhir_packages
DEBUG:__main__:Found 9 .tgz files: ['hl7.fhir.au.base-5.1.0-preview.tgz', 'hl7.fhir.au.core-1.1.0-preview.tgz', 'hl7.fhir.r4.core-4.0.1.tgz', 'hl7.fhir.uv.extensions.r4-5.2.0.tgz', 'hl7.fhir.uv.ipa-1.0.0.tgz', 'hl7.fhir.uv.smart-app-launch-2.0.0.tgz', 'hl7.fhir.uv.smart-app-launch-2.1.0.tgz', 'hl7.terminology.r4-5.0.0.tgz', 'hl7.terminology.r4-6.2.0.tgz']
DEBUG:__main__:Added package: hl7.fhir.au.base#5.1.0-preview
DEBUG:__main__:Added package: hl7.fhir.au.core#1.1.0-preview
DEBUG:__main__:Added package: hl7.fhir.r4.core#4.0.1
DEBUG:__main__:Added package: hl7.fhir.uv.extensions.r4#5.2.0
DEBUG:__main__:Added package: hl7.fhir.uv.ipa#1.0.0
DEBUG:__main__:Added package: hl7.fhir.uv.smart-app-launch#2.0.0
DEBUG:__main__:Added package: hl7.fhir.uv.smart-app-launch#2.1.0
DEBUG:__main__:Added package: hl7.terminology.r4#5.0.0
DEBUG:__main__:Added package: hl7.terminology.r4#6.2.0
DEBUG:__main__:Set package choices: [('', 'None'), ('hl7.fhir.au.base#5.1.0-preview', 'hl7.fhir.au.base#5.1.0-preview'), ('hl7.fhir.au.core#1.1.0-preview', 'hl7.fhir.au.core#1.1.0-preview'), ('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.ipa#1.0.0', 'hl7.fhir.uv.ipa#1.0.0'), ('hl7.fhir.uv.smart-app-launch#2.0.0', 'hl7.fhir.uv.smart-app-launch#2.0.0'), ('hl7.fhir.uv.smart-app-launch#2.1.0', 'hl7.fhir.uv.smart-app-launch#2.1.0'), ('hl7.terminology.r4#5.0.0', 'hl7.terminology.r4#5.0.0'), ('hl7.terminology.r4#6.2.0', 'hl7.terminology.r4#6.2.0')]
DEBUG:__main__:Handling GET request for FSH converter page.
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 14:50:13] "GET /fsh-converter HTTP/1.1" 200 -
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 14:50:13] "GET /static/FHIRFLARE.png HTTP/1.1" 304 -
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 14:50:13] "GET /static/js/lottie.min.js HTTP/1.1" 304 -
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 14:50:13] "GET /static/animations/loading-dark.json HTTP/1.1" 304 -
DEBUG:__main__:Instance path configuration: /app/instance
DEBUG:__main__:Database URI: sqlite:////app/instance/fhir_ig.db
DEBUG:__main__:Packages path: /app/instance/fhir_packages
DEBUG:__main__:Flask instance folder path: /app/instance
DEBUG:__main__:Directories created/verified: Instance: /app/instance, Packages: /app/instance/fhir_packages
DEBUG:__main__:Attempting to create database tables for URI: sqlite:////app/instance/fhir_ig.db
INFO:__main__:Database tables created successfully (if they didn't exist).
DEBUG:__main__:Instance path configuration: /app/instance
DEBUG:__main__:Database URI: sqlite:////app/instance/fhir_ig.db
DEBUG:__main__:Packages path: /app/instance/fhir_packages
DEBUG:__main__:Flask instance folder path: /app/instance
DEBUG:__main__:Directories created/verified: Instance: /app/instance, Packages: /app/instance/fhir_packages
DEBUG:__main__:Attempting to create database tables for URI: sqlite:////app/instance/fhir_ig.db
INFO:__main__:Database tables created successfully (if they didn't exist).
INFO:werkzeug:WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://172.18.0.2:5000
INFO:werkzeug:Press CTRL+C to quit
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 14:53:18] "GET / HTTP/1.1" 200 -
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 14:53:18] "GET /static/FHIRFLARE.png HTTP/1.1" 304 -
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 14:53:18] "GET /static/favicon.ico HTTP/1.1" 304 -
DEBUG:__main__:Scanning packages directory: /app/instance/fhir_packages
DEBUG:__main__:Found 9 .tgz files: ['hl7.fhir.au.base-5.1.0-preview.tgz', 'hl7.fhir.au.core-1.1.0-preview.tgz', 'hl7.fhir.r4.core-4.0.1.tgz', 'hl7.fhir.uv.extensions.r4-5.2.0.tgz', 'hl7.fhir.uv.ipa-1.0.0.tgz', 'hl7.fhir.uv.smart-app-launch-2.0.0.tgz', 'hl7.fhir.uv.smart-app-launch-2.1.0.tgz', 'hl7.terminology.r4-5.0.0.tgz', 'hl7.terminology.r4-6.2.0.tgz']
DEBUG:__main__:Added package: hl7.fhir.au.base#5.1.0-preview
DEBUG:__main__:Added package: hl7.fhir.au.core#1.1.0-preview
DEBUG:__main__:Added package: hl7.fhir.r4.core#4.0.1
DEBUG:__main__:Added package: hl7.fhir.uv.extensions.r4#5.2.0
DEBUG:__main__:Added package: hl7.fhir.uv.ipa#1.0.0
DEBUG:__main__:Added package: hl7.fhir.uv.smart-app-launch#2.0.0
DEBUG:__main__:Added package: hl7.fhir.uv.smart-app-launch#2.1.0
DEBUG:__main__:Added package: hl7.terminology.r4#5.0.0
DEBUG:__main__:Added package: hl7.terminology.r4#6.2.0
DEBUG:__main__:Set package choices: [('', 'None'), ('hl7.fhir.au.base#5.1.0-preview', 'hl7.fhir.au.base#5.1.0-preview'), ('hl7.fhir.au.core#1.1.0-preview', 'hl7.fhir.au.core#1.1.0-preview'), ('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.ipa#1.0.0', 'hl7.fhir.uv.ipa#1.0.0'), ('hl7.fhir.uv.smart-app-launch#2.0.0', 'hl7.fhir.uv.smart-app-launch#2.0.0'), ('hl7.fhir.uv.smart-app-launch#2.1.0', 'hl7.fhir.uv.smart-app-launch#2.1.0'), ('hl7.terminology.r4#5.0.0', 'hl7.terminology.r4#5.0.0'), ('hl7.terminology.r4#6.2.0', 'hl7.terminology.r4#6.2.0')]
DEBUG:__main__:Handling GET request for FSH converter page.
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 14:53:24] "GET /fsh-converter HTTP/1.1" 200 -
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 14:53:24] "GET /static/FHIRFLARE.png HTTP/1.1" 304 -
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 14:53:24] "GET /static/js/lottie.min.js HTTP/1.1" 304 -
DEBUG:__main__:Instance path configuration: /app/instance
DEBUG:__main__:Database URI: sqlite:////app/instance/fhir_ig.db
DEBUG:__main__:Packages path: /app/instance/fhir_packages
DEBUG:__main__:Flask instance folder path: /app/instance
DEBUG:__main__:Directories created/verified: Instance: /app/instance, Packages: /app/instance/fhir_packages
DEBUG:__main__:Attempting to create database tables for URI: sqlite:////app/instance/fhir_ig.db
INFO:__main__:Database tables created successfully (if they didn't exist).
DEBUG:__main__:Instance path configuration: /app/instance
DEBUG:__main__:Database URI: sqlite:////app/instance/fhir_ig.db
DEBUG:__main__:Packages path: /app/instance/fhir_packages
DEBUG:__main__:Flask instance folder path: /app/instance
DEBUG:__main__:Directories created/verified: Instance: /app/instance, Packages: /app/instance/fhir_packages
DEBUG:__main__:Attempting to create database tables for URI: sqlite:////app/instance/fhir_ig.db
INFO:__main__:Database tables created successfully (if they didn't exist).
INFO:werkzeug:WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://172.18.0.2:5000
INFO:werkzeug:Press CTRL+C to quit
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 14:57:25] "GET / HTTP/1.1" 200 -
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 14:57:25] "GET /static/FHIRFLARE.png HTTP/1.1" 304 -
DEBUG:__main__:Scanning packages directory: /app/instance/fhir_packages
DEBUG:__main__:Found 9 .tgz files: ['hl7.fhir.au.base-5.1.0-preview.tgz', 'hl7.fhir.au.core-1.1.0-preview.tgz', 'hl7.fhir.r4.core-4.0.1.tgz', 'hl7.fhir.uv.extensions.r4-5.2.0.tgz', 'hl7.fhir.uv.ipa-1.0.0.tgz', 'hl7.fhir.uv.smart-app-launch-2.0.0.tgz', 'hl7.fhir.uv.smart-app-launch-2.1.0.tgz', 'hl7.terminology.r4-5.0.0.tgz', 'hl7.terminology.r4-6.2.0.tgz']
DEBUG:__main__:Added package: hl7.fhir.au.base#5.1.0-preview
DEBUG:__main__:Added package: hl7.fhir.au.core#1.1.0-preview
DEBUG:__main__:Added package: hl7.fhir.r4.core#4.0.1
DEBUG:__main__:Added package: hl7.fhir.uv.extensions.r4#5.2.0
DEBUG:__main__:Added package: hl7.fhir.uv.ipa#1.0.0
DEBUG:__main__:Added package: hl7.fhir.uv.smart-app-launch#2.0.0
DEBUG:__main__:Added package: hl7.fhir.uv.smart-app-launch#2.1.0
DEBUG:__main__:Added package: hl7.terminology.r4#5.0.0
DEBUG:__main__:Added package: hl7.terminology.r4#6.2.0
DEBUG:__main__:Set package choices: [('', 'None'), ('hl7.fhir.au.base#5.1.0-preview', 'hl7.fhir.au.base#5.1.0-preview'), ('hl7.fhir.au.core#1.1.0-preview', 'hl7.fhir.au.core#1.1.0-preview'), ('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.ipa#1.0.0', 'hl7.fhir.uv.ipa#1.0.0'), ('hl7.fhir.uv.smart-app-launch#2.0.0', 'hl7.fhir.uv.smart-app-launch#2.0.0'), ('hl7.fhir.uv.smart-app-launch#2.1.0', 'hl7.fhir.uv.smart-app-launch#2.1.0'), ('hl7.terminology.r4#5.0.0', 'hl7.terminology.r4#5.0.0'), ('hl7.terminology.r4#6.2.0', 'hl7.terminology.r4#6.2.0')]
DEBUG:__main__:Handling GET request for FSH converter page.
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 14:57:30] "GET /fsh-converter HTTP/1.1" 200 -
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 14:57:30] "GET /static/FHIRFLARE.png HTTP/1.1" 304 -
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 14:57:30] "GET /static/js/lottie.min.js HTTP/1.1" 304 -
DEBUG:__main__:Instance path configuration: /app/instance
DEBUG:__main__:Database URI: sqlite:////app/instance/fhir_ig.db
DEBUG:__main__:Packages path: /app/instance/fhir_packages
DEBUG:__main__:Flask instance folder path: /app/instance
DEBUG:__main__:Directories created/verified: Instance: /app/instance, Packages: /app/instance/fhir_packages
DEBUG:__main__:Attempting to create database tables for URI: sqlite:////app/instance/fhir_ig.db
INFO:__main__:Database tables created successfully (if they didn't exist).
DEBUG:__main__:Instance path configuration: /app/instance
DEBUG:__main__:Database URI: sqlite:////app/instance/fhir_ig.db
DEBUG:__main__:Packages path: /app/instance/fhir_packages
DEBUG:__main__:Flask instance folder path: /app/instance
DEBUG:__main__:Directories created/verified: Instance: /app/instance, Packages: /app/instance/fhir_packages
DEBUG:__main__:Attempting to create database tables for URI: sqlite:////app/instance/fhir_ig.db
INFO:__main__:Database tables created successfully (if they didn't exist).
INFO:werkzeug:WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://172.18.0.2:5000
INFO:werkzeug:Press CTRL+C to quit
DEBUG:__main__:Scanning packages directory: /app/instance/fhir_packages
DEBUG:__main__:Found 9 .tgz files: ['hl7.fhir.au.base-5.1.0-preview.tgz', 'hl7.fhir.au.core-1.1.0-preview.tgz', 'hl7.fhir.r4.core-4.0.1.tgz', 'hl7.fhir.uv.extensions.r4-5.2.0.tgz', 'hl7.fhir.uv.ipa-1.0.0.tgz', 'hl7.fhir.uv.smart-app-launch-2.0.0.tgz', 'hl7.fhir.uv.smart-app-launch-2.1.0.tgz', 'hl7.terminology.r4-5.0.0.tgz', 'hl7.terminology.r4-6.2.0.tgz']
DEBUG:__main__:Added package: hl7.fhir.au.base#5.1.0-preview
DEBUG:__main__:Added package: hl7.fhir.au.core#1.1.0-preview
DEBUG:__main__:Added package: hl7.fhir.r4.core#4.0.1
DEBUG:__main__:Added package: hl7.fhir.uv.extensions.r4#5.2.0
DEBUG:__main__:Added package: hl7.fhir.uv.ipa#1.0.0
DEBUG:__main__:Added package: hl7.fhir.uv.smart-app-launch#2.0.0
DEBUG:__main__:Added package: hl7.fhir.uv.smart-app-launch#2.1.0
DEBUG:__main__:Added package: hl7.terminology.r4#5.0.0
DEBUG:__main__:Added package: hl7.terminology.r4#6.2.0
DEBUG:__main__:Set package choices: [('', 'None'), ('hl7.fhir.au.base#5.1.0-preview', 'hl7.fhir.au.base#5.1.0-preview'), ('hl7.fhir.au.core#1.1.0-preview', 'hl7.fhir.au.core#1.1.0-preview'), ('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.ipa#1.0.0', 'hl7.fhir.uv.ipa#1.0.0'), ('hl7.fhir.uv.smart-app-launch#2.0.0', 'hl7.fhir.uv.smart-app-launch#2.0.0'), ('hl7.fhir.uv.smart-app-launch#2.1.0', 'hl7.fhir.uv.smart-app-launch#2.1.0'), ('hl7.terminology.r4#5.0.0', 'hl7.terminology.r4#5.0.0'), ('hl7.terminology.r4#6.2.0', 'hl7.terminology.r4#6.2.0')]
DEBUG:__main__:Handling GET request for FSH converter page.
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 15:00:57] "GET /fsh-converter HTTP/1.1" 200 -
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 15:00:57] "GET /static/FHIRFLARE.png HTTP/1.1" 200 -
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 15:00:58] "GET /static/js/lottie.min.js HTTP/1.1" 200 -
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 15:00:58] "GET /static/animations/loading-dark.json HTTP/1.1" 200 -
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 15:00:58] "GET /static/favicon.ico HTTP/1.1" 200 -
DEBUG:__main__:Scanning packages directory: /app/instance/fhir_packages
DEBUG:__main__:Found 9 .tgz files: ['hl7.fhir.au.base-5.1.0-preview.tgz', 'hl7.fhir.au.core-1.1.0-preview.tgz', 'hl7.fhir.r4.core-4.0.1.tgz', 'hl7.fhir.uv.extensions.r4-5.2.0.tgz', 'hl7.fhir.uv.ipa-1.0.0.tgz', 'hl7.fhir.uv.smart-app-launch-2.0.0.tgz', 'hl7.fhir.uv.smart-app-launch-2.1.0.tgz', 'hl7.terminology.r4-5.0.0.tgz', 'hl7.terminology.r4-6.2.0.tgz']
DEBUG:__main__:Added package: hl7.fhir.au.base#5.1.0-preview
DEBUG:__main__:Added package: hl7.fhir.au.core#1.1.0-preview
DEBUG:__main__:Added package: hl7.fhir.r4.core#4.0.1
DEBUG:__main__:Added package: hl7.fhir.uv.extensions.r4#5.2.0
DEBUG:__main__:Added package: hl7.fhir.uv.ipa#1.0.0
DEBUG:__main__:Added package: hl7.fhir.uv.smart-app-launch#2.0.0
DEBUG:__main__:Added package: hl7.fhir.uv.smart-app-launch#2.1.0
DEBUG:__main__:Added package: hl7.terminology.r4#5.0.0
DEBUG:__main__:Added package: hl7.terminology.r4#6.2.0
DEBUG:__main__:Set package choices: [('', 'None'), ('hl7.fhir.au.base#5.1.0-preview', 'hl7.fhir.au.base#5.1.0-preview'), ('hl7.fhir.au.core#1.1.0-preview', 'hl7.fhir.au.core#1.1.0-preview'), ('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.ipa#1.0.0', 'hl7.fhir.uv.ipa#1.0.0'), ('hl7.fhir.uv.smart-app-launch#2.0.0', 'hl7.fhir.uv.smart-app-launch#2.0.0'), ('hl7.fhir.uv.smart-app-launch#2.1.0', 'hl7.fhir.uv.smart-app-launch#2.1.0'), ('hl7.terminology.r4#5.0.0', 'hl7.terminology.r4#5.0.0'), ('hl7.terminology.r4#6.2.0', 'hl7.terminology.r4#6.2.0')]
DEBUG:__main__:Processing input: mode=text, has_file=False, has_text=True, has_alias=False
DEBUG:services:Processed input: ('/tmp/tmp_21icnfn/input.json', None)
DEBUG:__main__:Running GoFSH with input: /tmp/tmp_21icnfn/input.json, output_dir: /app/static/uploads/fsh_output
DEBUG:services:Wrapper script contents:
#!/bin/bash
exec 3>/dev/null
"gofsh" "/tmp/tmp_21icnfn/input.json" "-o" "/tmp/tmpn9r4c9rl" "-s" "file-per-definition" "-l" "error" </dev/null >/tmp/gofsh_output.log 2>&1
DEBUG:services:Temp output directory contents before GoFSH: []
DEBUG:services:GoFSH output:
╔═════════════════════════ GoFSH RESULTS ═════════════════════════╗
║ ╭────────────────────┬───────────────────┬────────────────────╮ ║
║ │ Profiles │ Extensions │ Logicals │ ║
║ ├────────────────────┼───────────────────┼────────────────────┤ ║
║ │ 0 │ 0 │ 0 │ ║
║ ╰────────────────────┴───────────────────┴────────────────────╯ ║
║ ╭────────────────────┬───────────────────┬────────────────────╮ ║
║ │ Resources │ ValueSets │ CodeSystems │ ║
║ ├────────────────────┼───────────────────┼────────────────────┤ ║
║ │ 0 │ 0 │ 0 │ ║
║ ╰────────────────────┴───────────────────┴────────────────────╯ ║
║ ╭────────────────────┬───────────────────┬────────────────────╮ ║
║ │ Instances │ Invariants │ Mappings │ ║
║ ├────────────────────┼───────────────────┼────────────────────┤ ║
║ │ 1 │ 0 │ 0 │ ║
║ ╰────────────────────┴───────────────────┴────────────────────╯ ║
║ ╭────────────────────┬───────────────────┬────────────────────╮ ║
║ │ Aliases │ │ │ ║
║ ├────────────────────┼───────────────────┼────────────────────┤ ║
║ │ 3 │ │ │ ║
║ ╰────────────────────┴───────────────────┴────────────────────╯ ║
║ ║
╠═════════════════════════════════════════════════════════════════╣
║ Not bad, but it cod be batter! 0 Errors 2 Warnings ║
╚═════════════════════════════════════════════════════════════════╝
DEBUG:services:GoFSH fishing-trip wrapper script contents:
#!/bin/bash
exec 3>/dev/null
exec >/dev/null 2>&1
"gofsh" "/tmp/tmp_21icnfn/input.json" "-o" "/tmp/tmpllyiv7dp" "-s" "file-per-definition" "-l" "error" "--fshing-trip" </dev/null >/tmp/gofsh_output.log 2>&1
DEBUG:services:GoFSH fishing-trip output:
╔═════════════════════════ GoFSH RESULTS ═════════════════════════╗
║ ╭────────────────────┬───────────────────┬────────────────────╮ ║
║ │ Profiles │ Extensions │ Logicals │ ║
║ ├────────────────────┼───────────────────┼────────────────────┤ ║
║ │ 0 │ 0 │ 0 │ ║
║ ╰────────────────────┴───────────────────┴────────────────────╯ ║
║ ╭────────────────────┬───────────────────┬────────────────────╮ ║
║ │ Resources │ ValueSets │ CodeSystems │ ║
║ ├────────────────────┼───────────────────┼────────────────────┤ ║
║ │ 0 │ 0 │ 0 │ ║
║ ╰────────────────────┴───────────────────┴────────────────────╯ ║
║ ╭────────────────────┬───────────────────┬────────────────────╮ ║
║ │ Instances │ Invariants │ Mappings │ ║
║ ├────────────────────┼───────────────────┼────────────────────┤ ║
║ │ 1 │ 0 │ 0 │ ║
║ ╰────────────────────┴───────────────────┴────────────────────╯ ║
║ ╭────────────────────┬───────────────────┬────────────────────╮ ║
║ │ Aliases │ │ │ ║
║ ├────────────────────┼───────────────────┼────────────────────┤ ║
║ │ 3 │ │ │ ║
║ ╰────────────────────┴───────────────────┴────────────────────╯ ║
║ ║
╠═════════════════════════════════════════════════════════════════╣
║ Something smells fishy... 0 Errors 2 Warnings ║
╚═════════════════════════════════════════════════════════════════╝
╔═════════════════════════════════════════════════════════════════╗
║ Generating round trip results via SUSHI ║
╚═════════════════════════════════════════════════════════════════╝
info Running SUSHI v3.15.0 (implements FHIR Shorthand specification v3.0.0)
info Arguments:
info /tmp/tmpllyiv7dp
info No output path specified. Output to /tmp/tmpllyiv7dp
info Using configuration file: /tmp/tmpllyiv7dp/sushi-config.yaml
warn The FSHOnly property is set to true, so no output specific to IG creation will be generated. The following properties are unused and only relevant for IG creation: id, name. Consider removing these properties from sushi-config.yaml.
File: /tmp/tmpllyiv7dp/sushi-config.yaml
info Importing FSH text...
info Preprocessed 2 documents with 3 aliases.
info Imported 0 definitions and 1 instances.
info Loaded virtual package sushi-r5forR4#1.0.0 with 7 resources
info Resolved hl7.fhir.uv.tools.r4#latest to concrete version 0.5.0
info Loaded hl7.fhir.uv.tools.r4#0.5.0 with 88 resources
info Resolved hl7.terminology.r4#latest to concrete version 6.2.0
info Loaded hl7.terminology.r4#6.2.0 with 4323 resources
info Resolved hl7.fhir.uv.extensions.r4#latest to concrete version 5.2.0
info Loaded hl7.fhir.uv.extensions.r4#5.2.0 with 759 resources
info Loaded hl7.fhir.r4.core#4.0.1 with 4581 resources
info Loaded virtual package sushi-local#LOCAL with 0 resources
info Converting FSH to FHIR resources...
info Converted 1 FHIR instances.
info Exporting FHIR resources as JSON...
info Exported 1 FHIR resources as JSON.
info Exporting FSH definitions only. No IG related content will be exported.
========================= SUSHI RESULTS ===========================
| ------------------------------------------------------------- |
| | Profiles | Extensions | Logicals | Resources | |
| |-------------------------------------------------------------| |
| | 0 | 0 | 0 | 0 | |
| ------------------------------------------------------------- |
| ------------------------------------------------------------- |
| | ValueSets | CodeSystems | Instances | |
| |-------------------------------------------------------------| |
| | 0 | 0 | 1 | |
| ------------------------------------------------------------- |
| |
===================================================================
| A bit pitchy, but tuna-ble. 0 Errors 1 Warning |
===================================================================
DEBUG:services:Copied files to final output directory: ['sushi-config.yaml', 'input/fsh/aliases.fsh', 'input/fsh/instances/vkc.fsh', 'input/input.json', 'fshing-trip-comparison.html']
INFO:services:GoFSH executed successfully for /tmp/tmp_21icnfn/input.json
DEBUG:__main__:Successfully removed temp directory: /tmp/tmp_21icnfn
INFO:__main__:FSH conversion successful
DEBUG:__main__:Returning partial HTML for AJAX POST request.
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 15:02:41] "POST /fsh-converter HTTP/1.1" 200 -
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 15:02:41] "GET /static/animations/loading-dark.json HTTP/1.1" 304 -
DEBUG:__main__:Scanning packages directory: /app/instance/fhir_packages
DEBUG:__main__:Found 9 .tgz files: ['hl7.fhir.au.base-5.1.0-preview.tgz', 'hl7.fhir.au.core-1.1.0-preview.tgz', 'hl7.fhir.r4.core-4.0.1.tgz', 'hl7.fhir.uv.extensions.r4-5.2.0.tgz', 'hl7.fhir.uv.ipa-1.0.0.tgz', 'hl7.fhir.uv.smart-app-launch-2.0.0.tgz', 'hl7.fhir.uv.smart-app-launch-2.1.0.tgz', 'hl7.terminology.r4-5.0.0.tgz', 'hl7.terminology.r4-6.2.0.tgz']
DEBUG:__main__:Added package: hl7.fhir.au.base#5.1.0-preview
DEBUG:__main__:Added package: hl7.fhir.au.core#1.1.0-preview
DEBUG:__main__:Added package: hl7.fhir.r4.core#4.0.1
DEBUG:__main__:Added package: hl7.fhir.uv.extensions.r4#5.2.0
DEBUG:__main__:Added package: hl7.fhir.uv.ipa#1.0.0
DEBUG:__main__:Added package: hl7.fhir.uv.smart-app-launch#2.0.0
DEBUG:__main__:Added package: hl7.fhir.uv.smart-app-launch#2.1.0
DEBUG:__main__:Added package: hl7.terminology.r4#5.0.0
DEBUG:__main__:Added package: hl7.terminology.r4#6.2.0
DEBUG:__main__:Set package choices: [('', 'None'), ('hl7.fhir.au.base#5.1.0-preview', 'hl7.fhir.au.base#5.1.0-preview'), ('hl7.fhir.au.core#1.1.0-preview', 'hl7.fhir.au.core#1.1.0-preview'), ('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.ipa#1.0.0', 'hl7.fhir.uv.ipa#1.0.0'), ('hl7.fhir.uv.smart-app-launch#2.0.0', 'hl7.fhir.uv.smart-app-launch#2.0.0'), ('hl7.fhir.uv.smart-app-launch#2.1.0', 'hl7.fhir.uv.smart-app-launch#2.1.0'), ('hl7.terminology.r4#5.0.0', 'hl7.terminology.r4#5.0.0'), ('hl7.terminology.r4#6.2.0', 'hl7.terminology.r4#6.2.0')]
DEBUG:__main__:Processing input: mode=text, has_file=False, has_text=True, has_alias=False
DEBUG:services:Processed input: ('/tmp/tmp_k0pky49/input.json', None)
DEBUG:__main__:Running GoFSH with input: /tmp/tmp_k0pky49/input.json, output_dir: /app/static/uploads/fsh_output
DEBUG:services:Wrapper script contents:
#!/bin/bash
exec 3>/dev/null
"gofsh" "/tmp/tmp_k0pky49/input.json" "-o" "/tmp/tmpvnwz2fsh" "-s" "file-per-definition" "-l" "error" </dev/null >/tmp/gofsh_output.log 2>&1
DEBUG:services:Temp output directory contents before GoFSH: []
DEBUG:__main__:Scanning packages directory: /app/instance/fhir_packages
DEBUG:__main__:Found 9 .tgz files: ['hl7.fhir.au.base-5.1.0-preview.tgz', 'hl7.fhir.au.core-1.1.0-preview.tgz', 'hl7.fhir.r4.core-4.0.1.tgz', 'hl7.fhir.uv.extensions.r4-5.2.0.tgz', 'hl7.fhir.uv.ipa-1.0.0.tgz', 'hl7.fhir.uv.smart-app-launch-2.0.0.tgz', 'hl7.fhir.uv.smart-app-launch-2.1.0.tgz', 'hl7.terminology.r4-5.0.0.tgz', 'hl7.terminology.r4-6.2.0.tgz']
DEBUG:__main__:Added package: hl7.fhir.au.base#5.1.0-preview
DEBUG:__main__:Added package: hl7.fhir.au.core#1.1.0-preview
DEBUG:__main__:Added package: hl7.fhir.r4.core#4.0.1
DEBUG:__main__:Added package: hl7.fhir.uv.extensions.r4#5.2.0
DEBUG:__main__:Added package: hl7.fhir.uv.ipa#1.0.0
DEBUG:__main__:Added package: hl7.fhir.uv.smart-app-launch#2.0.0
DEBUG:__main__:Added package: hl7.fhir.uv.smart-app-launch#2.1.0
DEBUG:__main__:Added package: hl7.terminology.r4#5.0.0
DEBUG:services:GoFSH output:
╔═════════════════════════ GoFSH RESULTS ═════════════════════════╗
║ ╭────────────────────┬───────────────────┬────────────────────╮ ║
║ │ Profiles │ Extensions │ Logicals │ ║
║ ├────────────────────┼───────────────────┼────────────────────┤ ║
║ │ 0 │ 0 │ 0 │ ║
║ ╰────────────────────┴───────────────────┴────────────────────╯ ║
║ ╭────────────────────┬───────────────────┬────────────────────╮ ║
║ │ Resources │ ValueSets │ CodeSystems │ ║
║ ├────────────────────┼───────────────────┼────────────────────┤ ║
║ │ 0 │ 0 │ 0 │ ║
║ ╰────────────────────┴───────────────────┴────────────────────╯ ║
║ ╭────────────────────┬───────────────────┬────────────────────╮ ║
║ │ Instances │ Invariants │ Mappings │ ║
║ ├────────────────────┼───────────────────┼────────────────────┤ ║
║ │ 1 │ 0 │ 0 │ ║
║ ╰────────────────────┴───────────────────┴────────────────────╯ ║
║ ╭────────────────────┬───────────────────┬────────────────────╮ ║
║ │ Aliases │ │ │ ║
║ ├────────────────────┼───────────────────┼────────────────────┤ ║
║ │ 3 │ │ │ ║
║ ╰────────────────────┴───────────────────┴────────────────────╯ ║
║ ║
╠═════════════════════════════════════════════════════════════════╣
║ Not bad, but it cod be batter! 0 Errors 2 Warnings ║
╚═════════════════════════════════════════════════════════════════╝
DEBUG:__main__:Added package: hl7.terminology.r4#6.2.0
DEBUG:__main__:Set package choices: [('', 'None'), ('hl7.fhir.au.base#5.1.0-preview', 'hl7.fhir.au.base#5.1.0-preview'), ('hl7.fhir.au.core#1.1.0-preview', 'hl7.fhir.au.core#1.1.0-preview'), ('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.ipa#1.0.0', 'hl7.fhir.uv.ipa#1.0.0'), ('hl7.fhir.uv.smart-app-launch#2.0.0', 'hl7.fhir.uv.smart-app-launch#2.0.0'), ('hl7.fhir.uv.smart-app-launch#2.1.0', 'hl7.fhir.uv.smart-app-launch#2.1.0'), ('hl7.terminology.r4#5.0.0', 'hl7.terminology.r4#5.0.0'), ('hl7.terminology.r4#6.2.0', 'hl7.terminology.r4#6.2.0')]
DEBUG:__main__:Handling GET request for FSH converter page.
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 15:03:11] "GET /fsh-converter HTTP/1.1" 200 -
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 15:03:11] "GET /static/FHIRFLARE.png HTTP/1.1" 200 -
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 15:03:11] "GET /static/js/lottie.min.js HTTP/1.1" 200 -
DEBUG:services:GoFSH fishing-trip wrapper script contents:
#!/bin/bash
exec 3>/dev/null
exec >/dev/null 2>&1
"gofsh" "/tmp/tmp_k0pky49/input.json" "-o" "/tmp/tmp_hia6e3c" "-s" "file-per-definition" "-l" "error" "--fshing-trip" </dev/null >/tmp/gofsh_output.log 2>&1
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 15:03:11] "GET /static/animations/loading-light.json HTTP/1.1" 200 -
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 15:03:12] "GET /static/favicon.ico HTTP/1.1" 200 -
DEBUG:services:GoFSH fishing-trip output:
╔═════════════════════════ GoFSH RESULTS ═════════════════════════╗
║ ╭────────────────────┬───────────────────┬────────────────────╮ ║
║ │ Profiles │ Extensions │ Logicals │ ║
║ ├────────────────────┼───────────────────┼────────────────────┤ ║
║ │ 0 │ 0 │ 0 │ ║
║ ╰────────────────────┴───────────────────┴────────────────────╯ ║
║ ╭────────────────────┬───────────────────┬────────────────────╮ ║
║ │ Resources │ ValueSets │ CodeSystems │ ║
║ ├────────────────────┼───────────────────┼────────────────────┤ ║
║ │ 0 │ 0 │ 0 │ ║
║ ╰────────────────────┴───────────────────┴────────────────────╯ ║
║ ╭────────────────────┬───────────────────┬────────────────────╮ ║
║ │ Instances │ Invariants │ Mappings │ ║
║ ├────────────────────┼───────────────────┼────────────────────┤ ║
║ │ 1 │ 0 │ 0 │ ║
║ ╰────────────────────┴───────────────────┴────────────────────╯ ║
║ ╭────────────────────┬───────────────────┬────────────────────╮ ║
║ │ Aliases │ │ │ ║
║ ├────────────────────┼───────────────────┼────────────────────┤ ║
║ │ 3 │ │ │ ║
║ ╰────────────────────┴───────────────────┴────────────────────╯ ║
║ ║
╠═════════════════════════════════════════════════════════════════╣
║ A bit pitchy, but may be tuna-ble. 0 Errors 2 Warnings ║
╚═════════════════════════════════════════════════════════════════╝
╔═════════════════════════════════════════════════════════════════╗
║ Generating round trip results via SUSHI ║
╚═════════════════════════════════════════════════════════════════╝
info Running SUSHI v3.15.0 (implements FHIR Shorthand specification v3.0.0)
info Arguments:
info /tmp/tmp_hia6e3c
info No output path specified. Output to /tmp/tmp_hia6e3c
info Using configuration file: /tmp/tmp_hia6e3c/sushi-config.yaml
warn The FSHOnly property is set to true, so no output specific to IG creation will be generated. The following properties are unused and only relevant for IG creation: id, name. Consider removing these properties from sushi-config.yaml.
File: /tmp/tmp_hia6e3c/sushi-config.yaml
info Importing FSH text...
info Preprocessed 2 documents with 3 aliases.
info Imported 0 definitions and 1 instances.
info Loaded virtual package sushi-r5forR4#1.0.0 with 7 resources
info Resolved hl7.fhir.uv.tools.r4#latest to concrete version 0.5.0
info Loaded hl7.fhir.uv.tools.r4#0.5.0 with 88 resources
info Resolved hl7.terminology.r4#latest to concrete version 6.2.0
info Loaded hl7.terminology.r4#6.2.0 with 4323 resources
info Resolved hl7.fhir.uv.extensions.r4#latest to concrete version 5.2.0
info Loaded hl7.fhir.uv.extensions.r4#5.2.0 with 759 resources
info Loaded hl7.fhir.r4.core#4.0.1 with 4581 resources
info Loaded virtual package sushi-local#LOCAL with 0 resources
info Converting FSH to FHIR resources...
info Converted 1 FHIR instances.
info Exporting FHIR resources as JSON...
info Exported 1 FHIR resources as JSON.
info Exporting FSH definitions only. No IG related content will be exported.
========================= SUSHI RESULTS ===========================
| ------------------------------------------------------------- |
| | Profiles | Extensions | Logicals | Resources | |
| |-------------------------------------------------------------| |
| | 0 | 0 | 0 | 0 | |
| ------------------------------------------------------------- |
| ------------------------------------------------------------- |
| | ValueSets | CodeSystems | Instances | |
| |-------------------------------------------------------------| |
| | 0 | 0 | 1 | |
| ------------------------------------------------------------- |
| |
===================================================================
| Don't get stuck in the doldrums. 0 Errors 1 Warning |
===================================================================
DEBUG:services:Copied files to final output directory: ['sushi-config.yaml', 'input/fsh/aliases.fsh', 'input/fsh/instances/vkc.fsh', 'input/input.json', 'fshing-trip-comparison.html']
INFO:services:GoFSH executed successfully for /tmp/tmp_k0pky49/input.json
DEBUG:__main__:Successfully removed temp directory: /tmp/tmp_k0pky49
INFO:__main__:FSH conversion successful
DEBUG:__main__:Returning partial HTML for AJAX POST request.
INFO:werkzeug:172.18.0.1 - - [18/Apr/2025 15:03:28] "POST /fsh-converter HTTP/1.1" 200 -

View File

@ -1,17 +1,56 @@
2025-04-17 06:25:17,514 CRIT Supervisor is running as root. Privileges were not dropped because no user is specified in the config file. If you intend to run as root, you can set user=root in the config file to avoid this message.
2025-04-17 06:25:17,521 INFO supervisord started with pid 1
2025-04-17 06:25:18,537 INFO spawned: 'flask' with pid 7
2025-04-17 06:25:18,542 INFO spawned: 'tomcat' with pid 8
2025-04-17 06:25:29,060 INFO success: flask entered RUNNING state, process has stayed up for > than 10 seconds (startsecs)
2025-04-17 06:25:48,549 INFO success: tomcat entered RUNNING state, process has stayed up for > than 30 seconds (startsecs)
2025-04-17 07:05:48,346 WARN received SIGTERM indicating exit request
2025-04-17 07:05:48,354 INFO waiting for flask, tomcat to die
2025-04-17 07:05:51,475 WARN stopped: tomcat (exit status 143)
2025-04-17 07:05:51,483 INFO waiting for flask to die
2025-04-17 07:05:52,495 WARN stopped: flask (terminated by SIGTERM)
2025-04-17 07:08:06,100 CRIT Supervisor is running as root. Privileges were not dropped because no user is specified in the config file. If you intend to run as root, you can set user=root in the config file to avoid this message.
2025-04-17 07:08:06,109 INFO supervisord started with pid 1
2025-04-17 07:08:07,117 INFO spawned: 'flask' with pid 7
2025-04-17 07:08:07,122 INFO spawned: 'tomcat' with pid 8
2025-04-17 07:08:17,381 INFO success: flask entered RUNNING state, process has stayed up for > than 10 seconds (startsecs)
2025-04-17 07:08:37,990 INFO success: tomcat entered RUNNING state, process has stayed up for > than 30 seconds (startsecs)
2025-04-18 14:38:01,410 CRIT Supervisor is running as root. Privileges were not dropped because no user is specified in the config file. If you intend to run as root, you can set user=root in the config file to avoid this message.
2025-04-18 14:38:01,416 INFO supervisord started with pid 1
2025-04-18 14:38:02,423 INFO spawned: 'flask' with pid 7
2025-04-18 14:38:02,429 INFO spawned: 'tomcat' with pid 8
2025-04-18 14:38:12,439 INFO success: flask entered RUNNING state, process has stayed up for > than 10 seconds (startsecs)
2025-04-18 14:38:32,593 INFO success: tomcat entered RUNNING state, process has stayed up for > than 30 seconds (startsecs)
2025-04-18 14:40:50,270 WARN received SIGTERM indicating exit request
2025-04-18 14:40:50,271 INFO waiting for flask, tomcat to die
2025-04-18 14:40:51,200 WARN stopped: tomcat (exit status 143)
2025-04-18 14:40:51,204 WARN stopped: flask (terminated by SIGTERM)
2025-04-18 14:45:28,046 CRIT Supervisor is running as root. Privileges were not dropped because no user is specified in the config file. If you intend to run as root, you can set user=root in the config file to avoid this message.
2025-04-18 14:45:28,051 INFO supervisord started with pid 1
2025-04-18 14:45:29,057 INFO spawned: 'flask' with pid 8
2025-04-18 14:45:29,060 INFO spawned: 'tomcat' with pid 9
2025-04-18 14:45:39,314 INFO success: flask entered RUNNING state, process has stayed up for > than 10 seconds (startsecs)
2025-04-18 14:45:59,118 INFO success: tomcat entered RUNNING state, process has stayed up for > than 30 seconds (startsecs)
2025-04-18 14:48:15,641 WARN received SIGTERM indicating exit request
2025-04-18 14:48:15,642 INFO waiting for flask, tomcat to die
2025-04-18 14:48:16,755 WARN stopped: tomcat (exit status 143)
2025-04-18 14:48:16,762 WARN stopped: flask (terminated by SIGTERM)
2025-04-18 14:50:03,021 CRIT Supervisor is running as root. Privileges were not dropped because no user is specified in the config file. If you intend to run as root, you can set user=root in the config file to avoid this message.
2025-04-18 14:50:03,030 INFO supervisord started with pid 1
2025-04-18 14:50:04,035 INFO spawned: 'flask' with pid 7
2025-04-18 14:50:04,041 INFO spawned: 'tomcat' with pid 8
2025-04-18 14:50:14,298 INFO success: flask entered RUNNING state, process has stayed up for > than 10 seconds (startsecs)
2025-04-18 14:50:34,529 INFO success: tomcat entered RUNNING state, process has stayed up for > than 30 seconds (startsecs)
2025-04-18 14:51:37,046 WARN received SIGTERM indicating exit request
2025-04-18 14:51:37,047 INFO waiting for flask, tomcat to die
2025-04-18 14:51:38,220 WARN stopped: tomcat (exit status 143)
2025-04-18 14:51:38,225 WARN stopped: flask (terminated by SIGTERM)
2025-04-18 14:53:15,586 CRIT Supervisor is running as root. Privileges were not dropped because no user is specified in the config file. If you intend to run as root, you can set user=root in the config file to avoid this message.
2025-04-18 14:53:15,594 INFO supervisord started with pid 1
2025-04-18 14:53:16,598 INFO spawned: 'flask' with pid 7
2025-04-18 14:53:16,601 INFO spawned: 'tomcat' with pid 8
2025-04-18 14:53:27,378 INFO success: flask entered RUNNING state, process has stayed up for > than 10 seconds (startsecs)
2025-04-18 14:53:47,426 INFO success: tomcat entered RUNNING state, process has stayed up for > than 30 seconds (startsecs)
2025-04-18 14:55:41,989 WARN received SIGTERM indicating exit request
2025-04-18 14:55:41,989 INFO waiting for flask, tomcat to die
2025-04-18 14:55:43,011 WARN stopped: tomcat (exit status 143)
2025-04-18 14:55:43,018 WARN stopped: flask (terminated by SIGTERM)
2025-04-18 14:57:22,683 CRIT Supervisor is running as root. Privileges were not dropped because no user is specified in the config file. If you intend to run as root, you can set user=root in the config file to avoid this message.
2025-04-18 14:57:22,688 INFO supervisord started with pid 1
2025-04-18 14:57:23,693 INFO spawned: 'flask' with pid 7
2025-04-18 14:57:23,697 INFO spawned: 'tomcat' with pid 8
2025-04-18 14:57:34,418 INFO success: flask entered RUNNING state, process has stayed up for > than 10 seconds (startsecs)
2025-04-18 14:57:53,722 INFO success: tomcat entered RUNNING state, process has stayed up for > than 30 seconds (startsecs)
2025-04-18 14:59:18,910 WARN received SIGTERM indicating exit request
2025-04-18 14:59:18,925 INFO waiting for flask, tomcat to die
2025-04-18 14:59:20,423 WARN stopped: tomcat (exit status 143)
2025-04-18 14:59:20,430 WARN stopped: flask (terminated by SIGTERM)
2025-04-18 15:00:50,025 CRIT Supervisor is running as root. Privileges were not dropped because no user is specified in the config file. If you intend to run as root, you can set user=root in the config file to avoid this message.
2025-04-18 15:00:50,043 INFO supervisord started with pid 1
2025-04-18 15:00:51,049 INFO spawned: 'flask' with pid 7
2025-04-18 15:00:51,053 INFO spawned: 'tomcat' with pid 8
2025-04-18 15:01:01,646 INFO success: flask entered RUNNING state, process has stayed up for > than 10 seconds (startsecs)
2025-04-18 15:01:21,365 INFO success: tomcat entered RUNNING state, process has stayed up for > than 30 seconds (startsecs)

File diff suppressed because it is too large Load Diff

View File

@ -1,83 +1,259 @@
17-Apr-2025 06:25:19.367 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version name: Apache Tomcat/10.1.40
17-Apr-2025 06:25:19.375 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server built: Apr 1 2025 17:20:53 UTC
17-Apr-2025 06:25:19.375 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version number: 10.1.40.0
17-Apr-2025 06:25:19.375 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Name: Linux
17-Apr-2025 06:25:19.375 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Version: 5.15.167.4-microsoft-standard-WSL2
17-Apr-2025 06:25:19.375 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Architecture: amd64
17-Apr-2025 06:25:19.376 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Java Home: /opt/java/openjdk
17-Apr-2025 06:25:19.376 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Version: 17.0.14+7
17-Apr-2025 06:25:19.376 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Vendor: Eclipse Adoptium
17-Apr-2025 06:25:19.376 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_BASE: /usr/local/tomcat
17-Apr-2025 06:25:19.376 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_HOME: /usr/local/tomcat
17-Apr-2025 06:25:19.400 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties
17-Apr-2025 06:25:19.401 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
17-Apr-2025 06:25:19.401 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djdk.tls.ephemeralDHKeySize=2048
17-Apr-2025 06:25:19.401 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.protocol.handler.pkgs=org.apache.catalina.webresources
17-Apr-2025 06:25:19.401 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dsun.io.useCanonCaches=false
17-Apr-2025 06:25:19.401 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dorg.apache.catalina.security.SecurityListener.UMASK=0027
17-Apr-2025 06:25:19.401 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang=ALL-UNNAMED
17-Apr-2025 06:25:19.401 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang.reflect=ALL-UNNAMED
17-Apr-2025 06:25:19.401 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.io=ALL-UNNAMED
17-Apr-2025 06:25:19.401 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util=ALL-UNNAMED
17-Apr-2025 06:25:19.401 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util.concurrent=ALL-UNNAMED
17-Apr-2025 06:25:19.402 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
17-Apr-2025 06:25:19.402 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.base=/usr/local/tomcat
17-Apr-2025 06:25:19.402 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.home=/usr/local/tomcat
17-Apr-2025 06:25:19.402 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.io.tmpdir=/usr/local/tomcat/temp
17-Apr-2025 06:25:19.411 INFO [main] org.apache.catalina.core.AprLifecycleListener.lifecycleEvent Loaded Apache Tomcat Native library [2.0.8] using APR version [1.7.2].
17-Apr-2025 06:25:19.416 INFO [main] org.apache.catalina.core.AprLifecycleListener.initializeSSL OpenSSL successfully initialized [OpenSSL 3.0.13 30 Jan 2024]
17-Apr-2025 06:25:19.821 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["http-nio-8080"]
17-Apr-2025 06:25:19.882 INFO [main] org.apache.catalina.startup.Catalina.load Server initialization in [794] milliseconds
17-Apr-2025 06:25:20.017 INFO [main] org.apache.catalina.core.StandardService.startInternal Starting service [Catalina]
17-Apr-2025 06:25:20.017 INFO [main] org.apache.catalina.core.StandardEngine.startInternal Starting Servlet engine: [Apache Tomcat/10.1.40]
17-Apr-2025 06:25:20.048 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive [/usr/local/tomcat/webapps/ROOT.war]
17-Apr-2025 06:25:38.970 INFO [main] org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
17-Apr-2025 06:26:26.803 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [/usr/local/tomcat/webapps/ROOT.war] has finished in [66,753] ms
17-Apr-2025 06:26:26.804 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/tomcat/webapps/custom]
17-Apr-2025 06:26:26.847 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/tomcat/webapps/custom] has finished in [42] ms
17-Apr-2025 06:26:26.854 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
17-Apr-2025 06:26:26.893 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [67014] milliseconds
17-Apr-2025 07:05:48.360 INFO [Thread-5] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["http-nio-8080"]
17-Apr-2025 07:05:48.381 INFO [Thread-5] org.apache.catalina.core.StandardService.stopInternal Stopping service [Catalina]
17-Apr-2025 07:05:49.774 SEVERE [Thread-5] org.apache.catalina.loader.WebappClassLoaderBase.checkThreadLocalMapForLeaks The web application [ROOT] created a ThreadLocal with key of type [org.springframework.boot.SpringBootExceptionHandler.LoggedExceptionHandlerThreadLocal] (value [org.springframework.boot.SpringBootExceptionHandler$LoggedExceptionHandlerThreadLocal@60f94e9]) and a value of type [org.springframework.boot.SpringBootExceptionHandler] (value [org.springframework.boot.SpringBootExceptionHandler@46b9f0e9]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.
17-Apr-2025 07:05:49.827 INFO [Thread-5] org.apache.coyote.AbstractProtocol.stop Stopping ProtocolHandler ["http-nio-8080"]
17-Apr-2025 07:05:49.841 INFO [Thread-5] org.apache.coyote.AbstractProtocol.destroy Destroying ProtocolHandler ["http-nio-8080"]
17-Apr-2025 07:08:07.777 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version name: Apache Tomcat/10.1.40
17-Apr-2025 07:08:07.785 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server built: Apr 1 2025 17:20:53 UTC
17-Apr-2025 07:08:07.786 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version number: 10.1.40.0
17-Apr-2025 07:08:07.786 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Name: Linux
17-Apr-2025 07:08:07.786 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Version: 5.15.167.4-microsoft-standard-WSL2
17-Apr-2025 07:08:07.786 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Architecture: amd64
17-Apr-2025 07:08:07.787 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Java Home: /opt/java/openjdk
17-Apr-2025 07:08:07.787 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Version: 17.0.14+7
17-Apr-2025 07:08:07.787 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Vendor: Eclipse Adoptium
17-Apr-2025 07:08:07.787 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_BASE: /usr/local/tomcat
17-Apr-2025 07:08:07.787 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_HOME: /usr/local/tomcat
17-Apr-2025 07:08:07.813 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties
17-Apr-2025 07:08:07.813 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
17-Apr-2025 07:08:07.814 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djdk.tls.ephemeralDHKeySize=2048
17-Apr-2025 07:08:07.814 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.protocol.handler.pkgs=org.apache.catalina.webresources
17-Apr-2025 07:08:07.814 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dsun.io.useCanonCaches=false
17-Apr-2025 07:08:07.814 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dorg.apache.catalina.security.SecurityListener.UMASK=0027
17-Apr-2025 07:08:07.814 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang=ALL-UNNAMED
17-Apr-2025 07:08:07.815 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang.reflect=ALL-UNNAMED
17-Apr-2025 07:08:07.815 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.io=ALL-UNNAMED
17-Apr-2025 07:08:07.815 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util=ALL-UNNAMED
17-Apr-2025 07:08:07.815 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util.concurrent=ALL-UNNAMED
17-Apr-2025 07:08:07.815 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
17-Apr-2025 07:08:07.815 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.base=/usr/local/tomcat
17-Apr-2025 07:08:07.815 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.home=/usr/local/tomcat
17-Apr-2025 07:08:07.815 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.io.tmpdir=/usr/local/tomcat/temp
17-Apr-2025 07:08:07.826 INFO [main] org.apache.catalina.core.AprLifecycleListener.lifecycleEvent Loaded Apache Tomcat Native library [2.0.8] using APR version [1.7.2].
17-Apr-2025 07:08:07.831 INFO [main] org.apache.catalina.core.AprLifecycleListener.initializeSSL OpenSSL successfully initialized [OpenSSL 3.0.13 30 Jan 2024]
17-Apr-2025 07:08:08.209 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["http-nio-8080"]
17-Apr-2025 07:08:08.251 INFO [main] org.apache.catalina.startup.Catalina.load Server initialization in [759] milliseconds
17-Apr-2025 07:08:08.340 INFO [main] org.apache.catalina.core.StandardService.startInternal Starting service [Catalina]
17-Apr-2025 07:08:08.340 INFO [main] org.apache.catalina.core.StandardEngine.startInternal Starting Servlet engine: [Apache Tomcat/10.1.40]
17-Apr-2025 07:08:08.368 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive [/usr/local/tomcat/webapps/ROOT.war]
17-Apr-2025 07:08:22.061 INFO [main] org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
17-Apr-2025 07:09:08.525 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [/usr/local/tomcat/webapps/ROOT.war] has finished in [60,155] ms
17-Apr-2025 07:09:08.528 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/tomcat/webapps/custom]
17-Apr-2025 07:09:08.565 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/tomcat/webapps/custom] has finished in [37] ms
17-Apr-2025 07:09:08.575 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
17-Apr-2025 07:09:08.662 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [60427] milliseconds
18-Apr-2025 14:38:02.924 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version name: Apache Tomcat/10.1.40
18-Apr-2025 14:38:02.930 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server built: Apr 1 2025 17:20:53 UTC
18-Apr-2025 14:38:02.930 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version number: 10.1.40.0
18-Apr-2025 14:38:02.930 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Name: Linux
18-Apr-2025 14:38:02.930 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Version: 5.15.167.4-microsoft-standard-WSL2
18-Apr-2025 14:38:02.930 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Architecture: amd64
18-Apr-2025 14:38:02.931 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Java Home: /opt/java/openjdk
18-Apr-2025 14:38:02.931 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Version: 17.0.14+7
18-Apr-2025 14:38:02.931 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Vendor: Eclipse Adoptium
18-Apr-2025 14:38:02.931 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_BASE: /usr/local/tomcat
18-Apr-2025 14:38:02.931 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_HOME: /usr/local/tomcat
18-Apr-2025 14:38:02.945 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties
18-Apr-2025 14:38:02.945 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
18-Apr-2025 14:38:02.945 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djdk.tls.ephemeralDHKeySize=2048
18-Apr-2025 14:38:02.945 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.protocol.handler.pkgs=org.apache.catalina.webresources
18-Apr-2025 14:38:02.945 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dsun.io.useCanonCaches=false
18-Apr-2025 14:38:02.946 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dorg.apache.catalina.security.SecurityListener.UMASK=0027
18-Apr-2025 14:38:02.946 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang=ALL-UNNAMED
18-Apr-2025 14:38:02.946 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang.reflect=ALL-UNNAMED
18-Apr-2025 14:38:02.946 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.io=ALL-UNNAMED
18-Apr-2025 14:38:02.946 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util=ALL-UNNAMED
18-Apr-2025 14:38:02.946 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util.concurrent=ALL-UNNAMED
18-Apr-2025 14:38:02.946 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
18-Apr-2025 14:38:02.946 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.base=/usr/local/tomcat
18-Apr-2025 14:38:02.947 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.home=/usr/local/tomcat
18-Apr-2025 14:38:02.947 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.io.tmpdir=/usr/local/tomcat/temp
18-Apr-2025 14:38:02.952 INFO [main] org.apache.catalina.core.AprLifecycleListener.lifecycleEvent Loaded Apache Tomcat Native library [2.0.8] using APR version [1.7.2].
18-Apr-2025 14:38:02.956 INFO [main] org.apache.catalina.core.AprLifecycleListener.initializeSSL OpenSSL successfully initialized [OpenSSL 3.0.13 30 Jan 2024]
18-Apr-2025 14:38:03.277 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["http-nio-8080"]
18-Apr-2025 14:38:03.304 INFO [main] org.apache.catalina.startup.Catalina.load Server initialization in [599] milliseconds
18-Apr-2025 14:38:03.391 INFO [main] org.apache.catalina.core.StandardService.startInternal Starting service [Catalina]
18-Apr-2025 14:38:03.392 INFO [main] org.apache.catalina.core.StandardEngine.startInternal Starting Servlet engine: [Apache Tomcat/10.1.40]
18-Apr-2025 14:38:03.427 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive [/usr/local/tomcat/webapps/ROOT.war]
18-Apr-2025 14:38:17.518 INFO [main] org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
18-Apr-2025 14:39:04.055 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [/usr/local/tomcat/webapps/ROOT.war] has finished in [60,627] ms
18-Apr-2025 14:39:04.057 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/tomcat/webapps/custom]
18-Apr-2025 14:39:04.087 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/tomcat/webapps/custom] has finished in [29] ms
18-Apr-2025 14:39:04.096 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
18-Apr-2025 14:39:04.146 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [60844] milliseconds
18-Apr-2025 14:40:50.273 INFO [Thread-5] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["http-nio-8080"]
18-Apr-2025 14:40:50.283 INFO [Thread-5] org.apache.catalina.core.StandardService.stopInternal Stopping service [Catalina]
18-Apr-2025 14:40:50.970 SEVERE [Thread-5] org.apache.catalina.loader.WebappClassLoaderBase.checkThreadLocalMapForLeaks The web application [ROOT] created a ThreadLocal with key of type [org.springframework.boot.SpringBootExceptionHandler.LoggedExceptionHandlerThreadLocal] (value [org.springframework.boot.SpringBootExceptionHandler$LoggedExceptionHandlerThreadLocal@29edc901]) and a value of type [org.springframework.boot.SpringBootExceptionHandler] (value [org.springframework.boot.SpringBootExceptionHandler@2c8e3172]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.
18-Apr-2025 14:40:50.981 INFO [Thread-5] org.apache.coyote.AbstractProtocol.stop Stopping ProtocolHandler ["http-nio-8080"]
18-Apr-2025 14:40:50.993 INFO [Thread-5] org.apache.coyote.AbstractProtocol.destroy Destroying ProtocolHandler ["http-nio-8080"]
18-Apr-2025 14:45:29.517 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version name: Apache Tomcat/10.1.40
18-Apr-2025 14:45:29.521 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server built: Apr 1 2025 17:20:53 UTC
18-Apr-2025 14:45:29.522 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version number: 10.1.40.0
18-Apr-2025 14:45:29.522 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Name: Linux
18-Apr-2025 14:45:29.522 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Version: 5.15.167.4-microsoft-standard-WSL2
18-Apr-2025 14:45:29.522 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Architecture: amd64
18-Apr-2025 14:45:29.523 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Java Home: /opt/java/openjdk
18-Apr-2025 14:45:29.523 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Version: 17.0.14+7
18-Apr-2025 14:45:29.523 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Vendor: Eclipse Adoptium
18-Apr-2025 14:45:29.523 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_BASE: /usr/local/tomcat
18-Apr-2025 14:45:29.523 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_HOME: /usr/local/tomcat
18-Apr-2025 14:45:29.532 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties
18-Apr-2025 14:45:29.533 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
18-Apr-2025 14:45:29.533 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djdk.tls.ephemeralDHKeySize=2048
18-Apr-2025 14:45:29.533 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.protocol.handler.pkgs=org.apache.catalina.webresources
18-Apr-2025 14:45:29.533 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dsun.io.useCanonCaches=false
18-Apr-2025 14:45:29.533 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dorg.apache.catalina.security.SecurityListener.UMASK=0027
18-Apr-2025 14:45:29.533 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang=ALL-UNNAMED
18-Apr-2025 14:45:29.533 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang.reflect=ALL-UNNAMED
18-Apr-2025 14:45:29.533 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.io=ALL-UNNAMED
18-Apr-2025 14:45:29.533 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util=ALL-UNNAMED
18-Apr-2025 14:45:29.533 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util.concurrent=ALL-UNNAMED
18-Apr-2025 14:45:29.533 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
18-Apr-2025 14:45:29.534 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.base=/usr/local/tomcat
18-Apr-2025 14:45:29.534 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.home=/usr/local/tomcat
18-Apr-2025 14:45:29.534 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.io.tmpdir=/usr/local/tomcat/temp
18-Apr-2025 14:45:29.553 INFO [main] org.apache.catalina.core.AprLifecycleListener.lifecycleEvent Loaded Apache Tomcat Native library [2.0.8] using APR version [1.7.2].
18-Apr-2025 14:45:29.557 INFO [main] org.apache.catalina.core.AprLifecycleListener.initializeSSL OpenSSL successfully initialized [OpenSSL 3.0.13 30 Jan 2024]
18-Apr-2025 14:45:29.908 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["http-nio-8080"]
18-Apr-2025 14:45:29.937 INFO [main] org.apache.catalina.startup.Catalina.load Server initialization in [626] milliseconds
18-Apr-2025 14:45:29.997 INFO [main] org.apache.catalina.core.StandardService.startInternal Starting service [Catalina]
18-Apr-2025 14:45:29.998 INFO [main] org.apache.catalina.core.StandardEngine.startInternal Starting Servlet engine: [Apache Tomcat/10.1.40]
18-Apr-2025 14:45:30.021 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive [/usr/local/tomcat/webapps/ROOT.war]
18-Apr-2025 14:45:43.372 INFO [main] org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
18-Apr-2025 14:46:30.445 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [/usr/local/tomcat/webapps/ROOT.war] has finished in [60,422] ms
18-Apr-2025 14:46:30.448 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/tomcat/webapps/custom]
18-Apr-2025 14:46:30.489 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/tomcat/webapps/custom] has finished in [40] ms
18-Apr-2025 14:46:30.497 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
18-Apr-2025 14:46:30.524 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [60589] milliseconds
18-Apr-2025 14:48:15.644 INFO [Thread-5] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["http-nio-8080"]
18-Apr-2025 14:48:15.649 INFO [Thread-5] org.apache.catalina.core.StandardService.stopInternal Stopping service [Catalina]
18-Apr-2025 14:48:16.499 SEVERE [Thread-5] org.apache.catalina.loader.WebappClassLoaderBase.checkThreadLocalMapForLeaks The web application [ROOT] created a ThreadLocal with key of type [org.springframework.boot.SpringBootExceptionHandler.LoggedExceptionHandlerThreadLocal] (value [org.springframework.boot.SpringBootExceptionHandler$LoggedExceptionHandlerThreadLocal@68d0e5a1]) and a value of type [org.springframework.boot.SpringBootExceptionHandler] (value [org.springframework.boot.SpringBootExceptionHandler@6bf4dac9]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.
18-Apr-2025 14:48:16.515 INFO [Thread-5] org.apache.coyote.AbstractProtocol.stop Stopping ProtocolHandler ["http-nio-8080"]
18-Apr-2025 14:48:16.526 INFO [Thread-5] org.apache.coyote.AbstractProtocol.destroy Destroying ProtocolHandler ["http-nio-8080"]
18-Apr-2025 14:50:04.628 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version name: Apache Tomcat/10.1.40
18-Apr-2025 14:50:04.636 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server built: Apr 1 2025 17:20:53 UTC
18-Apr-2025 14:50:04.636 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version number: 10.1.40.0
18-Apr-2025 14:50:04.636 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Name: Linux
18-Apr-2025 14:50:04.636 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Version: 5.15.167.4-microsoft-standard-WSL2
18-Apr-2025 14:50:04.636 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Architecture: amd64
18-Apr-2025 14:50:04.636 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Java Home: /opt/java/openjdk
18-Apr-2025 14:50:04.636 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Version: 17.0.14+7
18-Apr-2025 14:50:04.637 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Vendor: Eclipse Adoptium
18-Apr-2025 14:50:04.637 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_BASE: /usr/local/tomcat
18-Apr-2025 14:50:04.637 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_HOME: /usr/local/tomcat
18-Apr-2025 14:50:04.650 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties
18-Apr-2025 14:50:04.651 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
18-Apr-2025 14:50:04.651 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djdk.tls.ephemeralDHKeySize=2048
18-Apr-2025 14:50:04.651 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.protocol.handler.pkgs=org.apache.catalina.webresources
18-Apr-2025 14:50:04.652 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dsun.io.useCanonCaches=false
18-Apr-2025 14:50:04.652 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dorg.apache.catalina.security.SecurityListener.UMASK=0027
18-Apr-2025 14:50:04.652 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang=ALL-UNNAMED
18-Apr-2025 14:50:04.652 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang.reflect=ALL-UNNAMED
18-Apr-2025 14:50:04.652 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.io=ALL-UNNAMED
18-Apr-2025 14:50:04.652 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util=ALL-UNNAMED
18-Apr-2025 14:50:04.652 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util.concurrent=ALL-UNNAMED
18-Apr-2025 14:50:04.652 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
18-Apr-2025 14:50:04.652 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.base=/usr/local/tomcat
18-Apr-2025 14:50:04.653 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.home=/usr/local/tomcat
18-Apr-2025 14:50:04.653 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.io.tmpdir=/usr/local/tomcat/temp
18-Apr-2025 14:50:04.656 INFO [main] org.apache.catalina.core.AprLifecycleListener.lifecycleEvent Loaded Apache Tomcat Native library [2.0.8] using APR version [1.7.2].
18-Apr-2025 14:50:04.661 INFO [main] org.apache.catalina.core.AprLifecycleListener.initializeSSL OpenSSL successfully initialized [OpenSSL 3.0.13 30 Jan 2024]
18-Apr-2025 14:50:05.024 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["http-nio-8080"]
18-Apr-2025 14:50:05.054 INFO [main] org.apache.catalina.startup.Catalina.load Server initialization in [708] milliseconds
18-Apr-2025 14:50:05.116 INFO [main] org.apache.catalina.core.StandardService.startInternal Starting service [Catalina]
18-Apr-2025 14:50:05.117 INFO [main] org.apache.catalina.core.StandardEngine.startInternal Starting Servlet engine: [Apache Tomcat/10.1.40]
18-Apr-2025 14:50:05.141 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive [/usr/local/tomcat/webapps/ROOT.war]
18-Apr-2025 14:50:23.688 INFO [main] org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
18-Apr-2025 14:51:15.950 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [/usr/local/tomcat/webapps/ROOT.war] has finished in [70,808] ms
18-Apr-2025 14:51:15.956 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/tomcat/webapps/custom]
18-Apr-2025 14:51:15.985 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/tomcat/webapps/custom] has finished in [30] ms
18-Apr-2025 14:51:15.992 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
18-Apr-2025 14:51:16.020 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [70970] milliseconds
18-Apr-2025 14:51:37.050 INFO [Thread-5] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["http-nio-8080"]
18-Apr-2025 14:51:37.058 INFO [Thread-5] org.apache.catalina.core.StandardService.stopInternal Stopping service [Catalina]
18-Apr-2025 14:51:38.039 SEVERE [Thread-5] org.apache.catalina.loader.WebappClassLoaderBase.checkThreadLocalMapForLeaks The web application [ROOT] created a ThreadLocal with key of type [org.springframework.boot.SpringBootExceptionHandler.LoggedExceptionHandlerThreadLocal] (value [org.springframework.boot.SpringBootExceptionHandler$LoggedExceptionHandlerThreadLocal@1665f69a]) and a value of type [org.springframework.boot.SpringBootExceptionHandler] (value [org.springframework.boot.SpringBootExceptionHandler@67158754]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.
18-Apr-2025 14:51:38.047 INFO [Thread-5] org.apache.coyote.AbstractProtocol.stop Stopping ProtocolHandler ["http-nio-8080"]
18-Apr-2025 14:51:38.057 INFO [Thread-5] org.apache.coyote.AbstractProtocol.destroy Destroying ProtocolHandler ["http-nio-8080"]
18-Apr-2025 14:53:17.329 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version name: Apache Tomcat/10.1.40
18-Apr-2025 14:53:17.336 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server built: Apr 1 2025 17:20:53 UTC
18-Apr-2025 14:53:17.336 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version number: 10.1.40.0
18-Apr-2025 14:53:17.336 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Name: Linux
18-Apr-2025 14:53:17.336 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Version: 5.15.167.4-microsoft-standard-WSL2
18-Apr-2025 14:53:17.336 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Architecture: amd64
18-Apr-2025 14:53:17.336 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Java Home: /opt/java/openjdk
18-Apr-2025 14:53:17.337 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Version: 17.0.14+7
18-Apr-2025 14:53:17.337 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Vendor: Eclipse Adoptium
18-Apr-2025 14:53:17.337 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_BASE: /usr/local/tomcat
18-Apr-2025 14:53:17.337 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_HOME: /usr/local/tomcat
18-Apr-2025 14:53:17.351 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties
18-Apr-2025 14:53:17.351 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
18-Apr-2025 14:53:17.352 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djdk.tls.ephemeralDHKeySize=2048
18-Apr-2025 14:53:17.352 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.protocol.handler.pkgs=org.apache.catalina.webresources
18-Apr-2025 14:53:17.352 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dsun.io.useCanonCaches=false
18-Apr-2025 14:53:17.352 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dorg.apache.catalina.security.SecurityListener.UMASK=0027
18-Apr-2025 14:53:17.353 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang=ALL-UNNAMED
18-Apr-2025 14:53:17.353 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang.reflect=ALL-UNNAMED
18-Apr-2025 14:53:17.353 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.io=ALL-UNNAMED
18-Apr-2025 14:53:17.353 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util=ALL-UNNAMED
18-Apr-2025 14:53:17.353 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util.concurrent=ALL-UNNAMED
18-Apr-2025 14:53:17.353 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
18-Apr-2025 14:53:17.353 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.base=/usr/local/tomcat
18-Apr-2025 14:53:17.354 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.home=/usr/local/tomcat
18-Apr-2025 14:53:17.354 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.io.tmpdir=/usr/local/tomcat/temp
18-Apr-2025 14:53:17.359 INFO [main] org.apache.catalina.core.AprLifecycleListener.lifecycleEvent Loaded Apache Tomcat Native library [2.0.8] using APR version [1.7.2].
18-Apr-2025 14:53:17.364 INFO [main] org.apache.catalina.core.AprLifecycleListener.initializeSSL OpenSSL successfully initialized [OpenSSL 3.0.13 30 Jan 2024]
18-Apr-2025 14:53:17.765 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["http-nio-8080"]
18-Apr-2025 14:53:17.807 INFO [main] org.apache.catalina.startup.Catalina.load Server initialization in [770] milliseconds
18-Apr-2025 14:53:17.902 INFO [main] org.apache.catalina.core.StandardService.startInternal Starting service [Catalina]
18-Apr-2025 14:53:17.903 INFO [main] org.apache.catalina.core.StandardEngine.startInternal Starting Servlet engine: [Apache Tomcat/10.1.40]
18-Apr-2025 14:53:17.949 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive [/usr/local/tomcat/webapps/ROOT.war]
18-Apr-2025 14:53:31.647 INFO [main] org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
18-Apr-2025 14:54:10.823 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [/usr/local/tomcat/webapps/ROOT.war] has finished in [52,873] ms
18-Apr-2025 14:54:10.824 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/tomcat/webapps/custom]
18-Apr-2025 14:54:10.839 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/tomcat/webapps/custom] has finished in [15] ms
18-Apr-2025 14:54:10.844 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
18-Apr-2025 14:54:10.883 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [53079] milliseconds
18-Apr-2025 14:55:42.003 INFO [Thread-5] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["http-nio-8080"]
18-Apr-2025 14:55:42.011 INFO [Thread-5] org.apache.catalina.core.StandardService.stopInternal Stopping service [Catalina]
18-Apr-2025 14:55:42.785 SEVERE [Thread-5] org.apache.catalina.loader.WebappClassLoaderBase.checkThreadLocalMapForLeaks The web application [ROOT] created a ThreadLocal with key of type [org.springframework.boot.SpringBootExceptionHandler.LoggedExceptionHandlerThreadLocal] (value [org.springframework.boot.SpringBootExceptionHandler$LoggedExceptionHandlerThreadLocal@4499a303]) and a value of type [org.springframework.boot.SpringBootExceptionHandler] (value [org.springframework.boot.SpringBootExceptionHandler@55a8471b]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.
18-Apr-2025 14:55:42.796 INFO [Thread-5] org.apache.coyote.AbstractProtocol.stop Stopping ProtocolHandler ["http-nio-8080"]
18-Apr-2025 14:55:42.806 INFO [Thread-5] org.apache.coyote.AbstractProtocol.destroy Destroying ProtocolHandler ["http-nio-8080"]
18-Apr-2025 14:57:24.205 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version name: Apache Tomcat/10.1.40
18-Apr-2025 14:57:24.211 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server built: Apr 1 2025 17:20:53 UTC
18-Apr-2025 14:57:24.211 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version number: 10.1.40.0
18-Apr-2025 14:57:24.211 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Name: Linux
18-Apr-2025 14:57:24.212 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Version: 5.15.167.4-microsoft-standard-WSL2
18-Apr-2025 14:57:24.212 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Architecture: amd64
18-Apr-2025 14:57:24.212 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Java Home: /opt/java/openjdk
18-Apr-2025 14:57:24.212 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Version: 17.0.14+7
18-Apr-2025 14:57:24.212 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Vendor: Eclipse Adoptium
18-Apr-2025 14:57:24.212 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_BASE: /usr/local/tomcat
18-Apr-2025 14:57:24.213 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_HOME: /usr/local/tomcat
18-Apr-2025 14:57:24.225 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties
18-Apr-2025 14:57:24.225 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
18-Apr-2025 14:57:24.225 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djdk.tls.ephemeralDHKeySize=2048
18-Apr-2025 14:57:24.226 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.protocol.handler.pkgs=org.apache.catalina.webresources
18-Apr-2025 14:57:24.226 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dsun.io.useCanonCaches=false
18-Apr-2025 14:57:24.226 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dorg.apache.catalina.security.SecurityListener.UMASK=0027
18-Apr-2025 14:57:24.226 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang=ALL-UNNAMED
18-Apr-2025 14:57:24.226 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang.reflect=ALL-UNNAMED
18-Apr-2025 14:57:24.227 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.io=ALL-UNNAMED
18-Apr-2025 14:57:24.227 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util=ALL-UNNAMED
18-Apr-2025 14:57:24.227 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util.concurrent=ALL-UNNAMED
18-Apr-2025 14:57:24.227 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
18-Apr-2025 14:57:24.227 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.base=/usr/local/tomcat
18-Apr-2025 14:57:24.228 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.home=/usr/local/tomcat
18-Apr-2025 14:57:24.228 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.io.tmpdir=/usr/local/tomcat/temp
18-Apr-2025 14:57:24.233 INFO [main] org.apache.catalina.core.AprLifecycleListener.lifecycleEvent Loaded Apache Tomcat Native library [2.0.8] using APR version [1.7.2].
18-Apr-2025 14:57:24.238 INFO [main] org.apache.catalina.core.AprLifecycleListener.initializeSSL OpenSSL successfully initialized [OpenSSL 3.0.13 30 Jan 2024]
18-Apr-2025 14:57:24.647 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["http-nio-8080"]
18-Apr-2025 14:57:24.679 INFO [main] org.apache.catalina.startup.Catalina.load Server initialization in [714] milliseconds
18-Apr-2025 14:57:24.756 INFO [main] org.apache.catalina.core.StandardService.startInternal Starting service [Catalina]
18-Apr-2025 14:57:24.756 INFO [main] org.apache.catalina.core.StandardEngine.startInternal Starting Servlet engine: [Apache Tomcat/10.1.40]
18-Apr-2025 14:57:24.793 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive [/usr/local/tomcat/webapps/ROOT.war]
18-Apr-2025 14:57:47.875 INFO [main] org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
18-Apr-2025 14:58:39.770 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [/usr/local/tomcat/webapps/ROOT.war] has finished in [74,974] ms
18-Apr-2025 14:58:39.772 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/tomcat/webapps/custom]
18-Apr-2025 14:58:39.797 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/tomcat/webapps/custom] has finished in [25] ms
18-Apr-2025 14:58:39.810 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
18-Apr-2025 14:58:39.865 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [75190] milliseconds
18-Apr-2025 14:59:18.937 INFO [Thread-5] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["http-nio-8080"]
18-Apr-2025 14:59:18.944 INFO [Thread-5] org.apache.catalina.core.StandardService.stopInternal Stopping service [Catalina]
18-Apr-2025 14:59:19.923 SEVERE [Thread-5] org.apache.catalina.loader.WebappClassLoaderBase.checkThreadLocalMapForLeaks The web application [ROOT] created a ThreadLocal with key of type [org.springframework.boot.SpringBootExceptionHandler.LoggedExceptionHandlerThreadLocal] (value [org.springframework.boot.SpringBootExceptionHandler$LoggedExceptionHandlerThreadLocal@831147a]) and a value of type [org.springframework.boot.SpringBootExceptionHandler] (value [org.springframework.boot.SpringBootExceptionHandler@1c2c8399]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.
18-Apr-2025 14:59:19.942 INFO [Thread-5] org.apache.coyote.AbstractProtocol.stop Stopping ProtocolHandler ["http-nio-8080"]
18-Apr-2025 14:59:19.958 INFO [Thread-5] org.apache.coyote.AbstractProtocol.destroy Destroying ProtocolHandler ["http-nio-8080"]
18-Apr-2025 15:00:51.636 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version name: Apache Tomcat/10.1.40
18-Apr-2025 15:00:51.643 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server built: Apr 1 2025 17:20:53 UTC
18-Apr-2025 15:00:51.643 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version number: 10.1.40.0
18-Apr-2025 15:00:51.644 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Name: Linux
18-Apr-2025 15:00:51.644 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Version: 5.15.167.4-microsoft-standard-WSL2
18-Apr-2025 15:00:51.644 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Architecture: amd64
18-Apr-2025 15:00:51.644 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Java Home: /opt/java/openjdk
18-Apr-2025 15:00:51.644 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Version: 17.0.14+7
18-Apr-2025 15:00:51.644 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Vendor: Eclipse Adoptium
18-Apr-2025 15:00:51.645 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_BASE: /usr/local/tomcat
18-Apr-2025 15:00:51.645 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_HOME: /usr/local/tomcat
18-Apr-2025 15:00:51.655 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties
18-Apr-2025 15:00:51.655 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
18-Apr-2025 15:00:51.656 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djdk.tls.ephemeralDHKeySize=2048
18-Apr-2025 15:00:51.656 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.protocol.handler.pkgs=org.apache.catalina.webresources
18-Apr-2025 15:00:51.656 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dsun.io.useCanonCaches=false
18-Apr-2025 15:00:51.656 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dorg.apache.catalina.security.SecurityListener.UMASK=0027
18-Apr-2025 15:00:51.656 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang=ALL-UNNAMED
18-Apr-2025 15:00:51.656 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang.reflect=ALL-UNNAMED
18-Apr-2025 15:00:51.656 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.io=ALL-UNNAMED
18-Apr-2025 15:00:51.656 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util=ALL-UNNAMED
18-Apr-2025 15:00:51.657 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util.concurrent=ALL-UNNAMED
18-Apr-2025 15:00:51.657 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
18-Apr-2025 15:00:51.657 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.base=/usr/local/tomcat
18-Apr-2025 15:00:51.657 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.home=/usr/local/tomcat
18-Apr-2025 15:00:51.657 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.io.tmpdir=/usr/local/tomcat/temp
18-Apr-2025 15:00:51.665 INFO [main] org.apache.catalina.core.AprLifecycleListener.lifecycleEvent Loaded Apache Tomcat Native library [2.0.8] using APR version [1.7.2].
18-Apr-2025 15:00:51.668 INFO [main] org.apache.catalina.core.AprLifecycleListener.initializeSSL OpenSSL successfully initialized [OpenSSL 3.0.13 30 Jan 2024]
18-Apr-2025 15:00:51.974 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["http-nio-8080"]
18-Apr-2025 15:00:51.998 INFO [main] org.apache.catalina.startup.Catalina.load Server initialization in [592] milliseconds
18-Apr-2025 15:00:52.059 INFO [main] org.apache.catalina.core.StandardService.startInternal Starting service [Catalina]
18-Apr-2025 15:00:52.060 INFO [main] org.apache.catalina.core.StandardEngine.startInternal Starting Servlet engine: [Apache Tomcat/10.1.40]
18-Apr-2025 15:00:52.081 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive [/usr/local/tomcat/webapps/ROOT.war]
18-Apr-2025 15:01:15.491 INFO [main] org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
18-Apr-2025 15:02:15.015 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [/usr/local/tomcat/webapps/ROOT.war] has finished in [82,933] ms
18-Apr-2025 15:02:15.016 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/tomcat/webapps/custom]
18-Apr-2025 15:02:15.044 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/tomcat/webapps/custom] has finished in [29] ms
18-Apr-2025 15:02:15.049 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
18-Apr-2025 15:02:15.126 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [83132] milliseconds

View File

@ -1,9 +1,11 @@
# services.py
import requests
import os
import tarfile
import json
import re
import logging
import shutil
from flask import current_app, Blueprint, request, jsonify
from collections import defaultdict
from pathlib import Path
@ -1442,71 +1444,245 @@ if __name__ == '__main__':
print(f"\n--- Skipping Processing Test (Import failed for {pkg_name_to_test}#{pkg_version_to_test}) ---")
# Add new functions for GoFSH integration
def run_gofsh(input_path, output_dir, output_style, log_level, fhir_version=None):
"""Run GoFSH on the input FHIR resource and return the FSH output."""
cmd = ["gofsh", input_path, "-o", output_dir, "-s", output_style, "-l", log_level]
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" </dev/null >{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(
cmd,
capture_output=True,
text=True,
check=True
[wrapper_script],
check=True,
env=env
)
# Read all FSH files from the output directory
# 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" </dev/null >{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 "\n\n".join(fsh_content), None
return fsh_output, comparison_report, None
except subprocess.CalledProcessError as e:
logger.error(f"GoFSH failed: {e.stderr}")
return None, f"GoFSH failed: {e.stderr}"
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, f"Error running GoFSH: {str(e)}"
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):
"""Process user input (file or text) and save to a temporary file."""
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()
temp_file = None
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'
temp_file = os.path.join(temp_dir, f"input.{file_type}")
with open(temp_file, 'w', encoding='utf-8') as f:
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.startswith('{') else 'xml'
temp_file = os.path.join(temp_dir, f"input.{file_type}")
with open(temp_file, 'w', encoding='utf-8') as f:
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, "No input provided"
return None, None, None, "No input provided"
# Basic validation
if file_type == 'json':
try:
json.loads(content)
except json.JSONDecodeError:
return None, None, "Invalid JSON format"
return None, None, None, "Invalid JSON format"
elif file_type == 'xml':
try:
ET.fromstring(content)
except ET.ParseError:
return None, None, "Invalid XML format"
return None, None, None, "Invalid XML format"
logger.debug(f"Processed input: {temp_file}")
return temp_file, temp_dir, None
# 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, f"Error processing input: {str(e)}"
return None, None, None, f"Error processing input: {str(e)}"

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

1
static/js/lottie.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,299 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>FSHing Trip Comparison</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/styles/github.min.css" />
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css" />
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html-ui.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
const targetElement = document.getElementById('diff');
const diff2htmlUi = new Diff2HtmlUI(targetElement);
diff2htmlUi.fileListToggle(false);
diff2htmlUi.synchronisedScroll();
diff2htmlUi.highlightCode();
const diffs = document.getElementsByClassName('d2h-file-wrapper');
for (const diff of diffs) {
diff.innerHTML = `
<details>
<summary>${diff.getElementsByClassName('d2h-file-name')[0].innerHTML}</summary>
${diff.innerHTML}
</details>`
}
});
</script>
</head>
<body style="text-align: center; font-family: 'Source Sans Pro', sans-serif">
<h1>FSHing Trip Comparison</a></h1>
<div id="diff">
<div class="d2h-file-list-wrapper d2h-light-color-scheme">
<div class="d2h-file-list-header">
<span class="d2h-file-list-title">Files changed (1)</span>
<a class="d2h-file-switch d2h-hide">hide</a>
<a class="d2h-file-switch d2h-show">show</a>
</div>
<ol class="d2h-file-list">
<li class="d2h-file-list-line">
<span class="d2h-file-name-wrapper">
<svg aria-hidden="true" class="d2h-icon d2h-moved" height="16" title="renamed" version="1.1"
viewBox="0 0 14 16" width="14">
<path d="M6 9H3V7h3V4l5 4-5 4V9z m8-7v12c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h12c0.55 0 1 0.45 1 1z m-1 0H1v12h12V2z"></path>
</svg> <a href="#d2h-150321" class="d2h-file-name">../tmp/{tmp_k0pky49/input.json → tmp_hia6e3c/fsh-generated/data}/fsh-index.json</a>
<span class="d2h-file-stats">
<span class="d2h-lines-added">+10</span>
<span class="d2h-lines-deleted">-0</span>
</span>
</span>
</li>
</ol>
</div><div class="d2h-wrapper d2h-light-color-scheme">
<div id="d2h-150321" class="d2h-file-wrapper" data-lang="json">
<div class="d2h-file-header">
<span class="d2h-file-name-wrapper">
<svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">
<path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>
</svg> <span class="d2h-file-name">../tmp/{tmp_k0pky49/input.json → tmp_hia6e3c/fsh-generated/data}/fsh-index.json</span>
<span class="d2h-tag d2h-moved d2h-moved-tag">RENAMED</span></span>
<label class="d2h-file-collapse">
<input class="d2h-file-collapse-input" type="checkbox" name="viewed" value="viewed">
Viewed
</label>
</div>
<div class="d2h-files-diff">
<div class="d2h-file-side-diff">
<div class="d2h-code-wrapper">
<table class="d2h-diff-table">
<tbody class="d2h-diff-tbody">
<tr>
<td class="d2h-code-side-linenumber d2h-info"></td>
<td class="d2h-info">
<div class="d2h-code-side-line">@@ -0,0 +1,10 @@</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-code-side-emptyplaceholder d2h-cntx d2h-emptyplaceholder">
</td>
<td class="d2h-cntx d2h-emptyplaceholder">
<div class="d2h-code-side-line d2h-code-side-emptyplaceholder">
<span class="d2h-code-line-prefix">&nbsp;</span>
<span class="d2h-code-line-ctn"><br></span>
</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-code-side-emptyplaceholder d2h-cntx d2h-emptyplaceholder">
</td>
<td class="d2h-cntx d2h-emptyplaceholder">
<div class="d2h-code-side-line d2h-code-side-emptyplaceholder">
<span class="d2h-code-line-prefix">&nbsp;</span>
<span class="d2h-code-line-ctn"><br></span>
</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-code-side-emptyplaceholder d2h-cntx d2h-emptyplaceholder">
</td>
<td class="d2h-cntx d2h-emptyplaceholder">
<div class="d2h-code-side-line d2h-code-side-emptyplaceholder">
<span class="d2h-code-line-prefix">&nbsp;</span>
<span class="d2h-code-line-ctn"><br></span>
</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-code-side-emptyplaceholder d2h-cntx d2h-emptyplaceholder">
</td>
<td class="d2h-cntx d2h-emptyplaceholder">
<div class="d2h-code-side-line d2h-code-side-emptyplaceholder">
<span class="d2h-code-line-prefix">&nbsp;</span>
<span class="d2h-code-line-ctn"><br></span>
</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-code-side-emptyplaceholder d2h-cntx d2h-emptyplaceholder">
</td>
<td class="d2h-cntx d2h-emptyplaceholder">
<div class="d2h-code-side-line d2h-code-side-emptyplaceholder">
<span class="d2h-code-line-prefix">&nbsp;</span>
<span class="d2h-code-line-ctn"><br></span>
</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-code-side-emptyplaceholder d2h-cntx d2h-emptyplaceholder">
</td>
<td class="d2h-cntx d2h-emptyplaceholder">
<div class="d2h-code-side-line d2h-code-side-emptyplaceholder">
<span class="d2h-code-line-prefix">&nbsp;</span>
<span class="d2h-code-line-ctn"><br></span>
</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-code-side-emptyplaceholder d2h-cntx d2h-emptyplaceholder">
</td>
<td class="d2h-cntx d2h-emptyplaceholder">
<div class="d2h-code-side-line d2h-code-side-emptyplaceholder">
<span class="d2h-code-line-prefix">&nbsp;</span>
<span class="d2h-code-line-ctn"><br></span>
</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-code-side-emptyplaceholder d2h-cntx d2h-emptyplaceholder">
</td>
<td class="d2h-cntx d2h-emptyplaceholder">
<div class="d2h-code-side-line d2h-code-side-emptyplaceholder">
<span class="d2h-code-line-prefix">&nbsp;</span>
<span class="d2h-code-line-ctn"><br></span>
</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-code-side-emptyplaceholder d2h-cntx d2h-emptyplaceholder">
</td>
<td class="d2h-cntx d2h-emptyplaceholder">
<div class="d2h-code-side-line d2h-code-side-emptyplaceholder">
<span class="d2h-code-line-prefix">&nbsp;</span>
<span class="d2h-code-line-ctn"><br></span>
</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-code-side-emptyplaceholder d2h-cntx d2h-emptyplaceholder">
</td>
<td class="d2h-cntx d2h-emptyplaceholder">
<div class="d2h-code-side-line d2h-code-side-emptyplaceholder">
<span class="d2h-code-line-prefix">&nbsp;</span>
<span class="d2h-code-line-ctn"><br></span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="d2h-file-side-diff">
<div class="d2h-code-wrapper">
<table class="d2h-diff-table">
<tbody class="d2h-diff-tbody">
<tr>
<td class="d2h-code-side-linenumber d2h-info"></td>
<td class="d2h-info">
<div class="d2h-code-side-line">&nbsp;</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-ins">
1
</td>
<td class="d2h-ins">
<div class="d2h-code-side-line">
<span class="d2h-code-line-prefix">+</span>
<span class="d2h-code-line-ctn">[</span>
</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-ins">
2
</td>
<td class="d2h-ins">
<div class="d2h-code-side-line">
<span class="d2h-code-line-prefix">+</span>
<span class="d2h-code-line-ctn"> {</span>
</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-ins">
3
</td>
<td class="d2h-ins">
<div class="d2h-code-side-line">
<span class="d2h-code-line-prefix">+</span>
<span class="d2h-code-line-ctn"> &quot;outputFile&quot;: &quot;Condition-vkc.json&quot;,</span>
</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-ins">
4
</td>
<td class="d2h-ins">
<div class="d2h-code-side-line">
<span class="d2h-code-line-prefix">+</span>
<span class="d2h-code-line-ctn"> &quot;fshName&quot;: &quot;vkc&quot;,</span>
</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-ins">
5
</td>
<td class="d2h-ins">
<div class="d2h-code-side-line">
<span class="d2h-code-line-prefix">+</span>
<span class="d2h-code-line-ctn"> &quot;fshType&quot;: &quot;Instance&quot;,</span>
</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-ins">
6
</td>
<td class="d2h-ins">
<div class="d2h-code-side-line">
<span class="d2h-code-line-prefix">+</span>
<span class="d2h-code-line-ctn"> &quot;fshFile&quot;: &quot;instances&#x2F;vkc.fsh&quot;,</span>
</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-ins">
7
</td>
<td class="d2h-ins">
<div class="d2h-code-side-line">
<span class="d2h-code-line-prefix">+</span>
<span class="d2h-code-line-ctn"> &quot;startLine&quot;: 1,</span>
</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-ins">
8
</td>
<td class="d2h-ins">
<div class="d2h-code-side-line">
<span class="d2h-code-line-prefix">+</span>
<span class="d2h-code-line-ctn"> &quot;endLine&quot;: 15</span>
</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-ins">
9
</td>
<td class="d2h-ins">
<div class="d2h-code-side-line">
<span class="d2h-code-line-prefix">+</span>
<span class="d2h-code-line-ctn"> }</span>
</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-ins">
10
</td>
<td class="d2h-ins">
<div class="d2h-code-side-line">
<span class="d2h-code-line-prefix">+</span>
<span class="d2h-code-line-ctn">]</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -1,5 +1,3 @@
Alias: $condition-clinical = http://terminology.hl7.org/CodeSystem/condition-clinical
Alias: $condition-category = http://terminology.hl7.org/CodeSystem/condition-category
Alias: $sct = http://snomed.info/sct
Alias: $loinc = http://loinc.org
Alias: $ihi-status-1 = https://healthterminologies.gov.au/fhir/CodeSystem/ihi-status-1
Alias: $ihi-record-status-1 = https://healthterminologies.gov.au/fhir/CodeSystem/ihi-record-status-1
Alias: $v2-0203 = http://terminology.hl7.org/CodeSystem/v2-0203

View File

@ -0,0 +1,15 @@
Instance: vkc
InstanceOf: Condition
Usage: #example
* meta.profile = "http://hl7.org.au/fhir/core/StructureDefinition/au-core-condition"
* clinicalStatus = $condition-clinical#active "Active"
* category = $condition-category#encounter-diagnosis "Encounter Diagnosis"
* severity = $sct#24484000 "Severe"
* code = $sct#317349009 "Vernal keratoconjunctivitis"
* bodySite = $sct#368601006 "Entire conjunctiva of left eye"
* subject = Reference(Patient/italia-sofia)
* onsetDateTime = "2023-10-01"
* recordedDate = "2023-10-02"
* recorder = Reference(PractitionerRole/generalpractitioner-guthridge-jarred)
* asserter = Reference(PractitionerRole/generalpractitioner-guthridge-jarred)
* note.text = "Itchy and burning eye, foreign body sensation. Mucoid discharge."

View File

@ -0,0 +1,74 @@
{
"resourceType": "Condition",
"id": "vkc",
"meta": {
"profile": [
"http://hl7.org.au/fhir/core/StructureDefinition/au-core-condition"
]
},
"clinicalStatus": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/condition-clinical",
"code": "active",
"display": "Active"
}
]
},
"category": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/condition-category",
"code": "encounter-diagnosis",
"display": "Encounter Diagnosis"
}
]
}
],
"severity": {
"coding": [
{
"system": "http://snomed.info/sct",
"code": "24484000",
"display": "Severe"
}
]
},
"code": {
"coding": [
{
"system": "http://snomed.info/sct",
"code": "317349009",
"display": "Vernal keratoconjunctivitis"
}
]
},
"bodySite": [
{
"coding": [
{
"system": "http://snomed.info/sct",
"code": "368601006",
"display": "Entire conjunctiva of left eye"
}
]
}
],
"subject": {
"reference": "Patient/italia-sofia"
},
"onsetDateTime": "2023-10-01",
"recordedDate": "2023-10-02",
"recorder": {
"reference": "PractitionerRole/generalpractitioner-guthridge-jarred"
},
"asserter": {
"reference": "PractitionerRole/generalpractitioner-guthridge-jarred"
},
"note": [
{
"text": "Itchy and burning eye, foreign body sensation. Mucoid discharge."
}
]
}

View File

@ -1,20 +1,15 @@
{# app/templates/_form_helpers.html #}
{% macro render_field(field, label_visible=true) %}
<div class="form-group mb-3"> {# Add margin bottom for spacing #}
<div class="form-group mb-3">
{% if field.type == "BooleanField" %}
<div class="form-check">
{{ field(class="form-check-input" + (" is-invalid" if field.errors else ""), **kwargs) }}
{% if label_visible and field.label %}
{{ field.label(class="form-label") }} {# Render label with Bootstrap class #}
<label class="form-check-label" for="{{ field.id }}">{{ field.label.text }}</label>
{% endif %}
{# Add is-invalid class if errors exist #}
{% set css_class = 'form-control ' + kwargs.pop('class', '') %}
{% if field.errors %}
{% set css_class = css_class + ' is-invalid' %}
{% if field.description %}
<small class="form-text text-muted">{{ field.description }}</small>
{% endif %}
{# Render the field itself, passing any extra attributes #}
{{ field(class=css_class, **kwargs) }}
{# Display validation errors #}
{% if field.errors %}
<div class="invalid-feedback">
{% for error in field.errors %}
@ -23,4 +18,25 @@
</div>
{% endif %}
</div>
{% 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 %}
<small class="form-text text-muted">{{ field.description }}</small>
{% endif %}
{% if field.errors %}
<div class="invalid-feedback">
{% for error in field.errors %}
{{ error }}<br>
{% endfor %}
</div>
{% endif %}
{% endif %}
</div>
{% endmacro %}

View File

@ -0,0 +1,60 @@
{% from "_form_helpers.html" import render_field %}
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-body">
<form id="fsh-converter-form" method="POST" enctype="multipart/form-data" class="form">
{{ form.hidden_tag() }}
{{ render_field(form.package) }}
{{ render_field(form.input_mode) }}
<div id="file-upload" style="display: none;">
{{ render_field(form.fhir_file) }}
</div>
<div id="text-input" style="display: none;">
{{ render_field(form.fhir_text) }}
</div>
{{ 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) }}
<div class="d-grid gap-2 d-sm-flex">
{{ form.submit(class="btn btn-success", id="submit-btn") }}
<a href="{{ url_for('index') }}" class="btn btn-secondary">Back</a>
</div>
</form>
</div>
</div>
</div>
</div>
{% if error %}
<div class="alert alert-danger mt-4">{{ error }}</div>
{% endif %}
{% if fsh_output %}
<div class="alert alert-success mt-4">Conversion successful!</div>
<h3 class="mt-4">FSH Output</h3>
<pre class="bg-light p-3">{{ fsh_output }}</pre>
<a href="{{ url_for('download_fsh') }}" class="btn btn-primary">Download FSH</a>
{% if comparison_report %}
<h3 class="mt-4">Fishing Trip Comparison Report</h3>
<a href="{{ url_for('static', filename='uploads/fsh_output/fshing-trip-comparison.html') }}" class="badge bg-primary text-white text-decoration-none mb-3" target="_blank">Click here for SUSHI Validation</a>
<div class="card">
<div class="card-body">
{% if comparison_report.differences %}
<p class="text-warning">Differences found in round-trip validation:</p>
<ul>
{% for diff in comparison_report.differences %}
<li>{{ diff.path }}: {{ diff.description }}</li>
{% endfor %}
</ul>
{% else %}
<p class="text-success">No differences found in round-trip validation.</p>
{% endif %}
</div>
</div>
{% endif %}
{% endif %}

View File

@ -5,48 +5,739 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" integrity="sha512-z3gLpd7yknf1YoNbCzqRKc4qyor8gaKU1qmn+CShxbuBusANI9QpRohGBreCFkKxLhei6S9CQXFEbbKuqLg0DA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
<title>{% if title %}{{ title }} - {% endif %}{{ site_name }}</title>
<style>
/* Default (Light Theme) Styles */
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: #f8f9fa;
color: #212529;
display: flex;
flex-direction: column;
min-height: 100vh;
margin: 0;
}
main {
flex-grow: 1;
}
h1, h5 {
font-weight: 600;
}
.navbar-light {
background-color: #ffffff !important;
border-bottom: 1px solid #e0e0e0;
}
.navbar-brand {
font-weight: bold;
color: #007bff !important;
}
.nav-link {
color: #333 !important;
transition: color 0.2s ease;
}
.nav-link:hover {
color: #007bff !important;
}
.nav-link.active {
color: #007bff !important;
font-weight: bold;
border-bottom: 2px solid #007bff;
}
.dropdown-menu {
list-style: none !important;
position: absolute;
background-color: #ffffff;
border: none;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
border-radius: 8px;
padding: 0;
min-width: 160px;
z-index: 1000;
}
.dropdown-item {
padding: 0.5rem 1rem;
transition: background-color 0.2s ease;
}
.dropdown-item:hover {
background-color: #e9ecef;
}
.dropdown-item.active {
background-color: #007bff;
color: white !important;
}
.card {
background-color: #ffffff;
border-radius: 10px;
overflow: hidden;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15) !important;
}
.text-muted {
color: #6c757d !important;
}
.form-check-input {
width: 1.25rem !important;
height: 1.25rem !important;
margin-top: 0.25rem;
display: inline-block !important;
border: 1px solid #6c757d;
}
.form-check-input:checked {
background-color: #007bff;
border-color: #007bff;
}
.form-check-label {
font-size: 1rem;
margin-left: 0.5rem;
vertical-align: middle;
}
.form-check-label i {
font-size: 1.2rem;
margin-left: 0.5rem;
}
.form-check {
margin-bottom: 1rem;
}
.navbar-controls {
gap: 0.5rem;
padding-right: 0;
}
/* Footer Styles */
footer {
background-color: #ffffff;
height: 56px;
display: flex;
align-items: center;
padding: 0.5rem 1rem;
border-top: 1px solid #e0e0e0;
}
.footer-left,
.footer-right {
display: inline-flex;
gap: 0.75rem;
align-items: center;
}
.footer-left a,
.footer-right a {
color: #007bff;
text-decoration: none;
font-size: 0.85rem;
}
.footer-left a:hover,
.footer-right a:hover {
text-decoration: underline;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
/* Alert Styles */
.alert {
border-radius: 8px;
}
.alert-danger {
background-color: #f8d7da;
color: #721c24;
border-color: #f5c6cb;
}
.alert-success {
background-color: #d4edda;
color: #155724;
border-color: #c3e6cb;
}
.alert-info {
background-color: #d1ecf1;
color: #0c5460;
border-color: #bee5eb;
}
/* --- Unified Button and Badge Styles --- */
/* General Button Theme (Primary, Secondary) */
.btn-primary {
background-color: #007bff;
border-color: #007bff;
color: white;
}
.btn-primary:hover {
background-color: #0056b3;
border-color: #004085;
}
.btn-outline-primary {
color: #007bff;
border-color: #007bff;
}
.btn-outline-primary:hover {
background-color: #007bff;
color: white;
}
.btn-secondary {
background-color: #6c757d;
border-color: #6c757d;
color: white;
}
.btn-secondary:hover {
background-color: #5a6268;
border-color: #545b62;
}
.btn-outline-secondary {
color: #6c757d;
border-color: #6c757d;
}
.btn-outline-secondary:hover {
background-color: #6c757d;
color: white;
}
/* --- Theme Styles for FSH Code Blocks --- */
pre, pre code {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 0.875em;
white-space: pre-wrap; /* Allow wrapping */
word-break: break-all; /* Break long lines */
}
/* Target pre blocks specifically used for FSH output if needed */
/* If the pre block has a specific class or ID, use that */
#fsh-output pre, /* Targets any pre within the output container */
._fsh_output pre /* Targets any pre within the partial template content */
{
background-color: #e9ecef; /* Light theme background */
color: #212529; /* Light theme text */
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 10px;
max-height: 600px; /* Adjust as needed */
overflow: auto;
}
#fsh-output pre code,
._fsh_output pre code {
background-color: transparent !important; /* Ensure code tag doesn't override pre bg */
padding: 0;
color: inherit; /* Inherit text color from pre */
font-size: inherit; /* Inherit font size from pre */
display: block; /* Make code fill pre */
}
/* Dark Theme Override */
html[data-theme="dark"] #fsh-output pre,
html[data-theme="dark"] ._fsh_output pre
{
background-color: #495057; /* Dark theme background */
color: #f8f9fa; /* Dark theme text */
border-color: #6c757d;
}
html[data-theme="dark"] #fsh-output pre code,
html[data-theme="dark"] ._fsh_output pre code {
color: inherit;
}
/* --- Theme Styles for FHIR UI Operations (fhir_ui_operations.html) --- */
/* Operation Block Container */
.opblock {
border: 1px solid #dee2e6; /* Light theme border */
background: #ffffff; /* Light theme background */
color: #212529; /* Light theme text */
}
html[data-theme="dark"] .opblock {
border-color: #495057; /* Dark theme border */
background: #343a40; /* Dark theme background (card bg) */
color: #f8f9fa; /* Dark theme text */
}
/* Operation Summary (Header) */
.opblock-summary {
background: #f8f9fa; /* Light theme summary bg */
border-bottom: 1px solid #dee2e6;
color: #212529; /* Default text color for summary */
}
.opblock-summary:hover {
background: #e9ecef;
}
html[data-theme="dark"] .opblock-summary {
background: #40464c; /* Darker summary bg */
border-bottom-color: #495057;
color: #f8f9fa;
}
html[data-theme="dark"] .opblock-summary:hover {
background: #495057;
}
/* Keep method badge colors fixed - DO NOT override .opblock-summary-method */
.opblock-summary-description {
color: #495057; /* Slightly muted description text */
}
html[data-theme="dark"] .opblock-summary-description {
color: #adb5bd; /* Lighter muted text for dark */
}
/* Operation Body (Expanded Section) */
.opblock-body {
border-top: 1px solid #dee2e6;
}
html[data-theme="dark"] .opblock-body {
border-top-color: #495057;
}
/* Section Headers within Body */
.opblock-section-header {
border-bottom: 1px solid #eee;
color: inherit; /* Inherit from .opblock */
}
html[data-theme="dark"] .opblock-section-header {
border-bottom-color: #495057;
}
.opblock-title {
color: inherit; /* Inherit from .opblock */
}
/* Parameters Table */
.parameters-table th,
.parameters-table td {
border: 1px solid #dee2e6;
color: inherit; /* Inherit from .opblock */
}
html[data-theme="dark"] .parameters-table th,
html[data-theme="dark"] .parameters-table td {
border-color: #495057;
}
.parameter__type,
.parameter__in {
color: #6c757d; /* Use standard muted color */
}
html[data-theme="dark"] .parameter__type,
html[data-theme="dark"] .parameter__in {
color: #adb5bd; /* Use standard dark muted color */
}
.parameters-col_description input[type="text"],
.parameters-col_description input[type="number"] {
background-color: #fff; /* Match light form controls */
border: 1px solid #ced4da;
color: #212529;
}
html[data-theme="dark"] .parameters-col_description input[type="text"],
html[data-theme="dark"] .parameters-col_description input[type="number"] {
background-color: #495057; /* Match dark form controls */
border-color: #6c757d;
color: #f8f9fa;
}
/* Request Body Textarea */
.request-body-textarea {
background-color: #fff; /* Match light form controls */
border: 1px solid #ced4da;
color: #212529;
}
html[data-theme="dark"] .request-body-textarea {
background-color: #495057; /* Match dark form controls */
border-color: #6c757d;
color: #f8f9fa;
}
/* Responses Table */
.responses-table th,
.responses-table td {
border: 1px solid #dee2e6;
color: inherit;
}
html[data-theme="dark"] .responses-table th,
html[data-theme="dark"] .responses-table td {
border-color: #495057;
}
/* Example/Schema Tabs & Controls */
.response-control-media-type {
background-color: #f8f9fa;
}
html[data-theme="dark"] .response-control-media-type {
background-color: #40464c; /* Match summary header bg */
}
.tablinks.badge { /* Default inactive tab */
background: #dee2e6 !important; /* Light grey inactive */
color: #333 !important;
}
.tablinks.badge.active { /* Default active tab */
background: #007bff !important; /* Use primary color */
color: white !important;
}
html[data-theme="dark"] .tablinks.badge { /* Dark inactive tab */
background: #6c757d !important; /* Dark grey inactive */
color: white !important;
}
html[data-theme="dark"] .tablinks.badge.active { /* Dark active tab */
background: #4dabf7 !important; /* Use dark primary color */
color: #000 !important;
}
/* Code/Output Blocks */
.highlight-code,
.request-url-output,
.curl-output,
.execute-wrapper pre.response-output-content {
background: #e9ecef; /* Light theme pre background */
color: #212529;
border: 1px solid #dee2e6;
}
html[data-theme="dark"] .highlight-code,
html[data-theme="dark"] .request-url-output,
html[data-theme="dark"] .curl-output,
html[data-theme="dark"] .execute-wrapper pre.response-output-content {
background: #495057; /* Dark theme pre background */
color: #f8f9fa;
border-color: #6c757d;
}
/* Narrative Response Box */
.execute-wrapper div.response-output-narrative {
background: #ffffff;
color: #212529;
border: 1px solid #dee2e6;
}
html[data-theme="dark"] .execute-wrapper div.response-output-narrative {
background: #343a40; /* Match card body */
color: #f8f9fa;
border: 1px solid #495057;
}
/* Response Status Text */
.execute-wrapper .response-status[style*="color: green"] { color: #198754 !important; }
.execute-wrapper .response-status[style*="color: red"] { color: #dc3545 !important; }
html[data-theme="dark"] .execute-wrapper .response-status[style*="color: green"] { color: #20c997 !important; }
html[data-theme="dark"] .execute-wrapper .response-status[style*="color: red"] { color: #e63946 !important; }
/* Specific Action Buttons (Success, Danger, Warning, Info) */
.btn-success { background-color: #198754; border-color: #198754; color: white; }
.btn-success:hover { background-color: #157347; border-color: #146c43; }
.btn-outline-success { color: #198754; border-color: #198754; }
.btn-outline-success:hover { background-color: #198754; color: white; }
.btn-danger { background-color: #dc3545; border-color: #dc3545; color: white; }
.btn-danger:hover { background-color: #bb2d3b; border-color: #b02a37; }
.btn-outline-danger { color: #dc3545; border-color: #dc3545; }
.btn-outline-danger:hover { background-color: #dc3545; color: white; }
.btn-warning { background-color: #ffc107; border-color: #ffc107; color: #000; }
.btn-warning:hover { background-color: #ffca2c; border-color: #ffc720; }
.btn-outline-warning { color: #ffc107; border-color: #ffc107; }
.btn-outline-warning:hover { background-color: #ffc107; color: #000; }
.btn-info { background-color: #0dcaf0; border-color: #0dcaf0; color: #000; }
.btn-info:hover { background-color: #31d2f2; border-color: #25cff2; }
.btn-outline-info { color: #0dcaf0; border-color: #0dcaf0; }
.btn-outline-info:hover { background-color: #0dcaf0; color: #000; }
/* General Badge Theme */
.badge {
padding: 0.4em 0.6em; /* Slightly larger padding */
font-size: 0.85em;
font-weight: 600;
}
/* Specific Badge Backgrounds (Add more as needed) */
.badge.bg-primary { background-color: #007bff !important; color: white !important; }
.badge.bg-secondary { background-color: #6c757d !important; color: white !important; }
.badge.bg-success { background-color: #198754 !important; color: white !important; }
.badge.bg-danger { background-color: #dc3545 !important; color: white !important; }
.badge.bg-warning { background-color: #ffc107 !important; color: #000 !important; }
.badge.bg-info { background-color: #0dcaf0 !important; color: #000 !important; }
.badge.bg-light { background-color: #f8f9fa !important; color: #000 !important; border: 1px solid #dee2e6; }
.badge.bg-dark { background-color: #343a40 !important; color: white !important; }
/* Copy Button Specific Style */
.btn-copy { /* Optional: Add a specific class if needed, or style .btn-outline-secondary directly */
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
line-height: 1.5;
}
/* --- Dark Theme Overrides --- */
html[data-theme="dark"] .btn-primary { background-color: #4dabf7; border-color: #4dabf7; color: #000; }
html[data-theme="dark"] .btn-primary:hover { background-color: #68bcef; border-color: #5fb7ea; }
html[data-theme="dark"] .btn-outline-primary { color: #4dabf7; border-color: #4dabf7; }
html[data-theme="dark"] .btn-outline-primary:hover { background-color: #4dabf7; color: #000; }
html[data-theme="dark"] .btn-secondary { background-color: #6c757d; border-color: #6c757d; color: white; }
html[data-theme="dark"] .btn-secondary:hover { background-color: #5a6268; border-color: #545b62; }
html[data-theme="dark"] .btn-outline-secondary { color: #adb5bd; border-color: #6c757d; } /* Lighter text/border */
html[data-theme="dark"] .btn-outline-secondary:hover { background-color: #6c757d; color: white; }
html[data-theme="dark"] .btn-success { background-color: #20c997; border-color: #20c997; color: #000; }
html[data-theme="dark"] .btn-success:hover { background-color: #3ce0ac; border-color: #36d8a3; }
html[data-theme="dark"] .btn-outline-success { color: #20c997; border-color: #20c997; }
html[data-theme="dark"] .btn-outline-success:hover { background-color: #20c997; color: #000; }
/* Keep danger as is */
html[data-theme="dark"] .btn-danger { background-color: #e63946; border-color: #e63946; color: white; }
html[data-theme="dark"] .btn-danger:hover { background-color: #c82b39; border-color: #bd2130; }
html[data-theme="dark"] .btn-outline-danger { color: #e63946; border-color: #e63946; }
html[data-theme="dark"] .btn-outline-danger:hover { background-color: #e63946; color: white; }
html[data-theme="dark"] .btn-warning { background-color: #ffca2c; border-color: #ffca2c; color: #000; }
html[data-theme="dark"] .btn-warning:hover { background-color: #ffd24a; border-color: #ffce3d; }
html[data-theme="dark"] .btn-outline-warning { color: #ffca2c; border-color: #ffca2c; }
html[data-theme="dark"] .btn-outline-warning:hover { background-color: #ffca2c; color: #000; }
html[data-theme="dark"] .btn-info { background-color: #22b8cf; border-color: #22b8cf; color: #000; }
html[data-theme="dark"] .btn-info:hover { background-color: #44cee3; border-color: #38cae0; }
html[data-theme="dark"] .btn-outline-info { color: #22b8cf; border-color: #22b8cf; }
html[data-theme="dark"] .btn-outline-info:hover { background-color: #22b8cf; color: #000; }
/* Dark Theme Badges */
html[data-theme="dark"] .badge.bg-primary { background-color: #4dabf7 !important; color: #000 !important; }
html[data-theme="dark"] .badge.bg-secondary { background-color: #6c757d !important; color: white !important; }
html[data-theme="dark"] .badge.bg-success { background-color: #20c997 !important; color: #000 !important; }
html[data-theme="dark"] .badge.bg-danger { background-color: #e63946 !important; color: white !important; }
html[data-theme="dark"] .badge.bg-warning { background-color: #ffca2c !important; color: #000 !important; }
html[data-theme="dark"] .badge.bg-info { background-color: #22b8cf !important; color: #000 !important; }
html[data-theme="dark"] .badge.bg-light { background-color: #495057 !important; color: #f8f9fa !important; border: 1px solid #6c757d; }
html[data-theme="dark"] .badge.bg-dark { background-color: #adb5bd !important; color: #000 !important; }
/* --- Themed Backgrounds for Code/Structure View (cp_view_processed_ig.html) --- */
/* Styling for <pre> blocks containing code */
#raw-structure-wrapper pre,
#example-content-wrapper pre {
background-color: #e9ecef; /* Light theme background */
color: #212529; /* Light theme text */
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 10px;
max-height: 400px; /* Keep existing max-height */
overflow-y: auto; /* Keep existing overflow */
white-space: pre-wrap; /* Ensure wrapping */
word-break: break-all; /* Break long strings */
}
/* Ensure code tag inside inherits background and has appropriate font */
#raw-structure-wrapper pre code,
#example-content-wrapper pre code {
background-color: transparent !important; /* Let pre handle background */
padding: 0;
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 0.875em;
color: inherit; /* Inherit text color from pre */
display: block; /* Make code block fill pre */
white-space: pre-wrap;
word-break: break-all;
}
/* Styling for the Structure Definition tree list items */
.structure-tree-root .list-group-item {
background-color: #ffffff; /* Light theme default background */
border-color: #dee2e6;
color: #212529;
}
.structure-tree-root .list-group-item-warning {
background-color: #fff3cd; /* Bootstrap light theme warning */
border-color: #ffeeba;
/*color: #856404; Ensure text is readable */
}
/* --- Dark Theme Overrides for Code/Structure View --- */
html[data-theme="dark"] #raw-structure-wrapper pre,
html[data-theme="dark"] #example-content-wrapper pre {
background-color: #495057; /* Dark theme background */
color: #f8f9fa; /* Dark theme text */
border-color: #6c757d;
}
/* Dark theme code text color already inherited, but explicit doesn't hurt */
html[data-theme="dark"] #raw-structure-wrapper pre code,
html[data-theme="dark"] #example-content-wrapper pre code {
color: #f8f9fa;
}
/* Dark theme structure list items */
html[data-theme="dark"] .structure-tree-root .list-group-item {
background-color: #343a40; /* Dark theme card background */
border-color: #495057;
color: #f8f9fa;
}
html[data-theme="dark"] .structure-tree-root .list-group-item-warning {
background-color: #664d03; /* Darker warning background */
border-color: #997404;
/*color: #ffecb5; Lighter text for contrast */
}
/* Dark Theme Styles */
html[data-theme="dark"] body {
background-color: #212529;
color: #f8f9fa;
}
html[data-theme="dark"] .navbar-light {
background-color: #343a40 !important;
border-bottom: 1px solid #495057;
}
html[data-theme="dark"] .navbar-brand {
color: #4dabf7 !important;
}
html[data-theme="dark"] .nav-link {
color: #f8f9fa !important;
}
html[data-theme="dark"] .nav-link:hover,
html[data-theme="dark"] .nav-link.active {
color: #4dabf7 !important;
border-bottom-color: #4dabf7;
}
html[data-theme="dark"] .dropdown-menu {
background-color: #495057;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
html[data-theme="dark"] .dropdown-item {
color: #f8f9fa;
}
html[data-theme="dark"] .dropdown-item:hover {
background-color: #6c757d;
}
html[data-theme="dark"] .dropdown-item.active {
background-color: #4dabf7;
color: white !important;
}
html[data-theme="dark"] .card {
background-color: #343a40;
border: 1px solid #495057;
}
html[data-theme="dark"] .text-muted {
color: #adb5bd !important;
}
html[data-theme="dark"] h1,
html[data-theme="dark"] h5 {
color: #f8f9fa;
}
html[data-theme="dark"] .form-label {
color: #f8f9fa;
}
html[data-theme="dark"] .form-control {
background-color: #495057;
color: #f8f9fa;
border-color: #6c757d;
}
html[data-theme="dark"] .form-control::placeholder {
color: #adb5bd;
}
html[data-theme="dark"] .form-select {
background-color: #495057;
color: #f8f9fa;
border-color: #6c757d;
}
html[data-theme="dark"] .alert-danger {
background-color: #dc3545;
color: white;
border-color: #dc3545;
}
html[data-theme="dark"] .alert-success {
background-color: #198754;
color: white;
border-color: #198754;
}
html[data-theme="dark"] .alert-info {
background-color: #0dcaf0;
color: white;
border-color: #0dcaf0;
}
html[data-theme="dark"] .form-check-input {
border-color: #adb5bd;
}
html[data-theme="dark"] .form-check-input:checked {
background-color: #4dabf7;
border-color: #4dabf7;
}
html[data-theme="dark"] footer {
background-color: #343a40;
border-top: 1px solid #495057;
}
html[data-theme="dark"] .footer-left a,
html[data-theme="dark"] .footer-right a {
color: #4dabf7;
}
@media (max-width: 576px) {
.navbar-controls {
flex-direction: column;
align-items: stretch;
width: 100%;
}
.navbar-controls .form-check {
margin-bottom: 0.5rem;
}
footer {
height: auto;
padding: 0.5rem;
flex-direction: column;
gap: 0.5rem;
}
.footer-left,
.footer-right {
flex-direction: column;
text-align: center;
gap: 0.5rem;
}
.footer-left a,
.footer-right a {
font-size: 0.8rem;
}
}
</style>
</head>
<body class="d-flex flex-column min-vh-100">
<nav class="navbar navbar-expand-lg navbar-dark bg-primary mb-4">
<nav class="navbar navbar-expand-lg navbar-light">
<div class="container-fluid">
<a class="navbar-brand" href="{{ url_for('index') }}">{{ site_name }}</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<div class="collapse navbar-collapse d-flex justify-content-between" id="navbarNav">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link {{ 'active' if request.endpoint == 'index' else '' }}" aria-current="page" href="{{ url_for('index') }}"><i class="bi bi-house-door me-1"></i> Home</a>
<a class="nav-link {{ 'active' if request.endpoint == 'index' else '' }}" aria-current="page" href="{{ url_for('index') }}"><i class="fas fa-house me-1"></i> Home</a>
</li>
<li class="nav-item">
<a class="nav-link {{ 'active' if request.endpoint == 'view_igs' else '' }}" href="{{ url_for('view_igs') }}"><i class="bi bi-journal-arrow-down me-1"></i> Manage FHIR Packages</a>
<a class="nav-link {{ 'active' if request.endpoint == 'view_igs' else '' }}" href="{{ url_for('view_igs') }}"><i class="fas fa-folder-open me-1"></i> Manage FHIR Packages</a>
</li>
<li class="nav-item">
<a class="nav-link {{ 'active' if request.endpoint == 'import_ig' else '' }}" href="{{ url_for('import_ig') }}"><i class="bi bi-download me-1"></i> Import IGs</a>
<a class="nav-link {{ 'active' if request.endpoint == 'import_ig' else '' }}" href="{{ url_for('import_ig') }}"><i class="fas fa-download me-1"></i> Import IGs</a>
</li>
<li class="nav-item">
<a class="nav-link {{ 'active' if request.endpoint == 'push_igs' else '' }}" href="{{ url_for('push_igs') }}"><i class="bi bi-upload me-1"></i> Push IGs</a>
<a class="nav-link {{ 'active' if request.endpoint == 'push_igs' else '' }}" href="{{ url_for('push_igs') }}"><i class="fas fa-upload me-1"></i> Push IGs</a>
</li>
<li class="nav-item">
<a class="nav-link {{ 'active' if request.endpoint == 'validate_sample' else '' }}" href="{{ url_for('validate_sample') }}"><i class="bi bi-check-circle me-1"></i> Validate FHIR Sample</a>
<a class="nav-link {{ 'active' if request.endpoint == 'validate_sample' else '' }}" href="{{ url_for('validate_sample') }}"><i class="fas fa-check-circle me-1"></i> Validate FHIR Sample</a>
</li>
<li class="nav-item">
<a class="nav-link {{ 'active' if request.endpoint == 'fhir_ui' else '' }}" href="{{ url_for('fhir_ui') }}"><i class="bi bi-cloud-arrow-down me-1"></i> FHIR API Explorer</a>
<a class="nav-link {{ 'active' if request.endpoint == 'fhir_ui' else '' }}" href="{{ url_for('fhir_ui') }}"><i class="fas fa-cloud-download-alt me-1"></i> FHIR API Explorer</a>
</li>
<li class="nav-item">
<a class="nav-link {{ 'active' if request.endpoint == 'fhir_ui_operations' else '' }}" href="{{ url_for('fhir_ui_operations') }}"><i class="bi bi-gear me-1"></i> FHIR UI Operations</a>
</li>
<li class="nav-item">
<a class="nav-link {{ 'active' if request.endpoint == 'fsh_converter' else '' }}" href="{{ url_for('fsh_converter') }}"><i class="bi bi-file-code me-1"></i> FSH Converter</a>
<a class="nav-link {{ 'active' if request.endpoint == 'fsh_converter' else '' }}" href="{{ url_for('fsh_converter') }}"><i class="fas fa-file-code me-1"></i> FSH Converter</a>
</li>
</ul>
<div class="navbar-controls d-flex align-items-center">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="themeToggle" onchange="toggleTheme()" aria-label="Toggle dark mode" {% if request.cookies.get('theme') == 'dark' %}checked{% endif %}>
<label class="form-check-label" for="themeToggle">
<i class="fas fa-sun"></i>
<i class="fas fa-moon d-none"></i>
</label>
</div>
</div>
</div>
</div>
</nav>
<main class="container flex-grow-1">
<main class="flex-grow-1">
<div class="container mt-4">
<!-- Flashed Messages Section -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
@ -57,11 +748,10 @@
'alert-success' if category == 'success' else
'alert-info' }}
alert-dismissible fade show" role="alert">
<!-- Add an icon based on the category -->
<i class="bi
{{ 'bi-exclamation-triangle-fill me-2' if category == 'error' else
'bi-check-circle-fill me-2' if category == 'success' else
'bi-info-circle-fill me-2' }}"></i>
<i class="fas
{{ 'fa-exclamation-triangle me-2' if category == 'error' else
'fa-check-circle me-2' if category == 'success' else
'fa-info-circle me-2' }}"></i>
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
@ -70,24 +760,64 @@
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
</main>
<footer class="footer mt-auto py-3 bg-light">
<div class="container text-center">
{% set current_year = now.year %}
<span class="text-muted">© {{ current_year }} {{ site_name }}. All rights reserved.</span>
<footer class="main-footer" role="contentinfo">
<div class="container-fluid">
<div class="d-flex justify-content-between align-items-center">
<div class="footer-left">
<a href="https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit" target="_blank" rel="noreferrer" aria-label="Project Github">Project Github</a>
<a href="/about" aria-label="Learn about this project">What is FHIRFLARE</a>
<a href="https://www.hl7.org/fhir/" target="_blank" rel="noreferrer" aria-label="Visit FHIR website">FHIR</a>
<a href="https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit/issues/new/choose" class="text-danger text-decoration-none" aria-label="FHIRFLARE support"><i class="fas fa-exclamation-circle me-1"></i> Raise an Issue</a>
</div>
<div class="footer-right">
<a href="https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit/discussions" target="_blank" rel="noreferrer" aria-label="Project Discussion">Project Discussions</a>
<a href="https://github.com/Sudo-JHare" aria-label="Developer">Developer</a>
<a href="https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit/blob/main/LICENSE.md" aria-label="License">License</a>
</div>
</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function () {
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) { return new bootstrap.Tooltip(tooltipTriggerEl) })
function toggleTheme() {
const html = document.documentElement;
const toggle = document.getElementById('themeToggle');
const sunIcon = document.querySelector('.fa-sun');
const moonIcon = document.querySelector('.fa-moon');
if (toggle.checked) {
html.setAttribute('data-theme', 'dark');
localStorage.setItem('theme', 'dark');
sunIcon.classList.add('d-none');
moonIcon.classList.remove('d-none');
} else {
html.removeAttribute('data-theme');
localStorage.setItem('theme', 'light');
sunIcon.classList.remove('d-none');
moonIcon.classList.add('d-none');
}
// Set cookie to persist theme
document.cookie = `theme=${toggle.checked ? 'dark' : 'light'}; path=/; max-age=31536000`;
}
document.addEventListener('DOMContentLoaded', () => {
const savedTheme = localStorage.getItem('theme') || (document.cookie.includes('theme=dark') ? 'dark' : 'light');
const themeToggle = document.getElementById('themeToggle');
const sunIcon = document.querySelector('.fa-sun');
const moonIcon = document.querySelector('.fa-moon');
if (savedTheme === 'dark' && themeToggle) {
document.documentElement.setAttribute('data-theme', 'dark');
themeToggle.checked = true;
sunIcon.classList.add('d-none');
moonIcon.classList.remove('d-none');
}
const tooltips = document.querySelectorAll('[data-bs-toggle="tooltip"]');
tooltips.forEach(t => new bootstrap.Tooltip(t));
});
</script>
{% endblock %}
{% block scripts %}{% endblock %}
</body>
</html>

View File

@ -147,7 +147,7 @@
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<span>Raw Structure Definition for <code id="raw-structure-title"></code></span>
<button type="button" class="btn btn-sm btn-outline-secondary ms-2" id="copy-raw-def-button"
<button type="button" class="btn btn-sm btn-outline-secondary btn-copy" id="copy-raw-def-button"
data-bs-toggle="tooltip" data-bs-placement="top" title="Copy Raw Definition JSON">
<i class="bi bi-clipboard" aria-hidden="true"></i>
<span class="visually-hidden">Copy Raw Definition JSON to clipboard</span>
@ -179,10 +179,10 @@
<div class="col-md-6">
<div class="d-flex justify-content-between align-items-center mb-1">
<h6>Raw Content</h6>
<button type="button" class="btn btn-sm btn-outline-secondary" id="copy-raw-content-button"
data-bs-toggle="tooltip" data-bs-placement="top" title="Copy Raw Content">
<button type="button" class="btn btn-sm btn-outline-secondary btn-copy" id="copy-raw-def-button"
data-bs-toggle="tooltip" data-bs-placement="top" title="Copy Raw Definition JSON">
<i class="bi bi-clipboard" aria-hidden="true"></i>
<span class="visually-hidden">Copy raw example content to clipboard</span>
<span class="visually-hidden">Copy Raw Definition JSON to clipboard</span>
</button>
</div>
<pre><code id="example-content-raw" class="p-2 d-block border bg-light" style="max-height: 400px; overflow-y: auto;"></code></pre>
@ -190,7 +190,7 @@
<div class="col-md-6">
<div class="d-flex justify-content-between align-items-center mb-1">
<h6>Pretty-Printed JSON</h6>
<button type="button" class="btn btn-sm btn-outline-secondary" id="copy-pretty-json-button"
<button type="button" class="btn btn-sm btn-outline-secondary btn-copy" id="copy-pretty-json-button"
data-bs-toggle="tooltip" data-bs-placement="top" title="Copy JSON">
<i class="bi bi-clipboard" aria-hidden="true"></i>
<span class="visually-hidden">Copy JSON example to clipboard</span>

View File

@ -56,7 +56,7 @@
<div class="mb-3" id="requestBodyGroup" style="display: none;">
<div class="d-flex align-items-center mb-2">
<label for="requestBody" class="form-label me-2 mb-0">Request Body</label>
<button type="button" class="btn btn-outline-secondary btn-sm" id="copyRequestBody" title="Copy to Clipboard"><i class="bi bi-clipboard"></i></button>
<button type="button" class="btn btn-outline-secondary btn-sm btn-copy" id="copyRequestBody" title="Copy to Clipboard"><i class="bi bi-clipboard"></i></button>
</div>
<textarea class="form-control" id="requestBody" name="request_body" rows="6" placeholder="For POST: JSON resource (e.g., {'resourceType': 'Patient', ...}) or search params (e.g., name=John&birthdate=gt2000)"></textarea>
<div id="jsonError" class="text-danger mt-1" style="display: none;"></div>
@ -72,12 +72,12 @@
<h5>Status: <span id="responseStatus" class="badge"></span></h5>
<div class="d-flex align-items-center mb-2">
<h6 class="me-2 mb-0">Headers</h6>
<button type="button" class="btn btn-outline-secondary btn-sm" id="copyResponseHeaders" title="Copy to Clipboard"><i class="bi bi-clipboard"></i></button>
<button type="button" class="btn btn-outline-secondary btn-sm btn-copy" id="copyResponseHeaders" title="Copy to Clipboard"><i class="bi bi-clipboard"></i></button>
</div>
<pre id="responseHeaders" class="border p-2 bg-light mb-3" style="max-height: 200px; overflow-y: auto;"></pre>
<div class="d-flex align-items-center mb-2">
<h6 class="me-2 mb-0">Body</h6>
<button type="button" class="btn btn-outline-secondary btn-sm" id="copyResponseBody" title="Copy to Clipboard"><i class="bi bi-clipboard"></i></button>
<button type="button" class="btn btn-outline-secondary btn-sm btn-copy" id="copyResponseBody" title="Copy to Clipboard"><i class="bi bi-clipboard"></i></button>
</div>
<pre id="responseBody" class="border p-2 bg-light" style="max-height: 400px; overflow-y: auto;"></pre>
</div>

View File

@ -76,7 +76,10 @@
.example-panel, .schema-panel { border: 1px solid #eee; border-radius: 4px; margin-top: -1px; }
/* Code Highlighting */
/*
.highlight-code { background: #333; color: #f8f8f2; padding: 10px; border-radius: 4px; overflow-x: auto; font-family: monospace; margin: 0; }
*/
/* Keep the .highlight-code pre rule below it if desired, although the theme styles should handle it */
.highlight-code pre { margin: 0; padding: 0; background: transparent; border: none; font-family: inherit; font-size: inherit; color: inherit; }
/* Execution Response */
@ -335,6 +338,58 @@ document.addEventListener('DOMContentLoaded', () => {
let currentSelectedResource = '';
let availableSystemOperations = [];
let fetchedMetadataCache = null;
let operationDefinitionCache = {}; // <<< ADD THIS LINE: Cache for fetched OperationDefinitions
// <<< ADD THIS NEW ASYNC FUNCTION >>>
async function fetchOperationDefinition(url) {
if (!url) return null; // No definition URL provided
// Check cache first
if (operationDefinitionCache.hasOwnProperty(url)) {
console.log(`Using cached OperationDefinition: ${url}`);
return operationDefinitionCache[url]; // Return cached definition (could be null if fetch failed before)
}
// Determine actual fetch URL
// Simplification: Assume the URL is directly fetchable or the /fhir proxy handles it if relative.
// Production code might need more robust logic to handle base URLs.
const fetchUrl = url;
console.log(`Fetching OperationDefinition: ${fetchUrl}`);
try {
const response = await fetch(fetchUrl, {
headers: { 'Accept': 'application/fhir+json' }
});
if (!response.ok) {
// Don't cache HTTP errors permanently, maybe allow retry later?
// For now, just log and return null.
console.error(`HTTP ${response.status} fetching OperationDefinition ${url}`);
operationDefinitionCache[url] = null; // Cache failure for this session
return null;
}
const definition = await response.json();
if (definition.resourceType !== 'OperationDefinition') {
console.error(`Expected OperationDefinition, got ${definition.resourceType} from ${url}`);
operationDefinitionCache[url] = null; // Cache invalid response
return null;
}
console.log(`Successfully fetched and parsed OperationDefinition: ${url}`);
operationDefinitionCache[url] = definition; // Cache successful fetch
return definition;
} catch (error) {
console.error(`Failed to fetch or parse OperationDefinition ${url}:`, error);
operationDefinitionCache[url] = null; // Cache fetch/parse failure
return null;
}
}
// <<< END OF NEW FUNCTION >>>
// --- Helper Function to Update Toggle Button/Input UI ---
function updateServerToggleUI() {
@ -374,25 +429,75 @@ document.addEventListener('DOMContentLoaded', () => {
console.error("Toggle server button (#toggleServer) not found!");
}
// --- Generate Query Examples (Unchanged) ---
// --- Generate Query Examples (Defining sinceParam) ---
function getQueryExamplesForResourceType(resourceType) {
if (!fetchedMetadataCache?.rest?.[0]) return [];
const restSection = fetchedMetadataCache.rest[0];
const resourceMetadata = restSection.resource?.find(r => r.type === resourceType);
const supportedInteractions = resourceMetadata?.interaction?.map(i => i.code) || [];
const resourceSpecificOperations = resourceMetadata?.operation || [];
const allSearchParameters = resourceMetadata?.searchParam || [];
if (!resourceMetadata) {
console.warn(`No metadata found for resource type: ${resourceType}`);
return [];
}
const supportedInteractions = resourceMetadata.interaction?.map(i => i.code) || [];
const resourceSpecificOperations = resourceMetadata.operation || [];
const allSearchParameters = resourceMetadata.searchParam || [];
const queries = [];
// --- Define Standard Parameters ---
const formatParam = { name: '_format', in: 'query', type: 'string', required: false, description: 'Response format (e.g., json, xml)', example: 'json' };
const idPathParam = { name: 'id', in: 'path', type: 'string', required: true, description: 'Resource logical ID', example: 'example' };
const versionIdPathParam = { name: 'version_id', in: 'path', type: 'string', required: true, description: 'Version ID', example: '1' };
const countQueryParam = { name: '_count', in: 'query', type: 'integer', required: false, description: 'Results per page', example: '10' };
const idQueryParam = { name: '_id', in: 'query', type: 'string', required: false, description: 'Search by ID', example: 'example' };
const lastUpdatedQueryParam = { name: '_lastUpdated', in: 'query', type: 'date', required: false, description: 'Last updated date', example: 'ge2024-01-01' };
const commonSearchParams = [idQueryParam, lastUpdatedQueryParam];
const specificSearchParams = allSearchParameters.filter(sp => !['_id', '_lastUpdated'].includes(sp.name)).slice(0, 3).map(sp => ({ name: sp.name, in: 'query', type: sp.type, required: false, description: sp.documentation || `Search by ${sp.name} (${sp.type})`, example: `${sp.type}-example` }));
const searchGetParams = [countQueryParam, formatParam, ...commonSearchParams, ...specificSearchParams];
const sinceParam = { name: '_since', in: 'query', type: 'instant', required: false, description: 'Only return updates after this time', example: new Date().toISOString() }; // <<< DEFINE sinceParam HERE
// --- Define Common Search Parameters Separately ---
const commonStandardParams = [
{ name: '_id', in: 'query', type: 'token', required: false, description: 'Search by resource logical ID', example: 'example' },
{ name: '_lastUpdated', in: 'query', type: 'date', required: false, description: 'Search by last updated date', example: 'ge2024-01-01' },
{ name: '_profile', in: 'query', type: 'uri', required: false, description: 'Search by profile canonical URL', example: ''},
{ name: '_tag', in: 'query', type: 'token', required: false, description: 'Search by tag (code or system|code)', example: ''},
{ name: '_security', in: 'query', type: 'token', required: false, description: 'Search by security label (system|code)', example: ''},
{ name: '_text', in: 'query', type: 'string', required: false, description: 'Search narrative content', example: ''},
{ name: '_content', in: 'query', type: 'string', required: false, description: 'Search all resource content', example: ''},
{ name: '_list', in: 'query', type: 'string', required: false, description: 'Search within a list', example: ''},
{ name: '_has', in: 'query', type: 'string', required: false, description: 'Reverse chaining search', example: ''},
{ name: '_type', in: 'query', type: 'string', required: false, description: 'Specify resource type (in compartment search)', example: resourceType },
{ name: '_source', in: 'query', type: 'uri', required: false, description: 'Search by source URI', example: ''},
{ name: '_filter', in: 'query', type: 'string', required: false, description: 'Advanced search filter', example: ''}
];
const commonStandardParamNames = new Set(commonStandardParams.map(p => p.name));
// --- Map ALL Search Parameters from Metadata ---
const specificSearchParams = allSearchParameters
.map(sp => ({
name: sp.name,
in: 'query',
type: sp.type,
required: false,
description: sp.documentation || `Search by ${sp.name} (${sp.type})`,
example: sp.type === 'reference' ? `${resourceType}/example` :
sp.type === 'token' ? 'system|code' :
sp.type === 'date' ? '2024-01-01' :
sp.type === 'number' ? '10' :
sp.type === 'quantity' ? '5|mg' :
sp.type === 'uri' ? 'http://example.com' :
''
}));
// Combine common params (_count, _format), specific params from metadata, and standard _params
const allUniqueParamsMap = new Map();
specificSearchParams.forEach(p => allUniqueParamsMap.set(p.name, p));
commonStandardParams.forEach(p => { if (!allUniqueParamsMap.has(p.name)) allUniqueParamsMap.set(p.name, p); });
if (!allUniqueParamsMap.has(countQueryParam.name)) allUniqueParamsMap.set(countQueryParam.name, countQueryParam);
if (!allUniqueParamsMap.has(formatParam.name)) allUniqueParamsMap.set(formatParam.name, formatParam);
const searchGetParams = Array.from(allUniqueParamsMap.values()).sort((a, b) => a.name.localeCompare(b.name));
const searchPostParams = [countQueryParam, formatParam];
// --- Define Example Schemas and Payloads ---
// (Keep existing schema/payload definitions)
const baseExample = { resourceType, id: "example", text: { status: "generated", div: `<div xmlns="http://www.w3.org/1999/xhtml">Example ${resourceType}</div>` } };
const baseExampleString = JSON.stringify(baseExample, null, 2);
const createExampleString = JSON.stringify({ ...baseExample, id: undefined }, null, 2);
@ -404,7 +509,13 @@ document.addEventListener('DOMContentLoaded', () => {
const bundleHistoryExample = JSON.stringify({ resourceType: "Bundle", type: "history", total: 1, entry: [{ resource: baseExample }] }, null, 2);
const patchExample = JSON.stringify({ resourceType: "Parameters", parameter: [{ name: "operation", part: [{ name: 'type', valueCode: 'replace' }, { name: 'path', valueString: `${resourceType}.active` }, { name: 'value', valueBoolean: false }] }] }, null, 2);
const deleteExample = JSON.stringify({ resourceType: "OperationOutcome", issue: [{ severity: "information", code: "informational", diagnostics: "Successful deletion" }] }, null, 2);
const postSearchExample = searchGetParams.filter(p => p.in === 'query' && p.example).map(p => `${p.name}=${encodeURIComponent(p.example)}`).join('&');
const postSearchExample = searchGetParams
.filter(p => p.in === 'query' && p.example && !['_count', '_format'].includes(p.name))
.map(p => `${p.name}=${encodeURIComponent(p.example)}`)
.join('&');
// --- Define Interaction Map ---
// (Use the defined sinceParam variable)
const interactionMap = {
'read': { label: `Read ${resourceType}`, method: 'GET', path: `${resourceType}/:id`, description: `Read a ${resourceType} by ID`, parameters: [idPathParam, formatParam], schema: baseSchema, example: baseExampleString },
'vread': { label: `VRead ${resourceType}`, method: 'GET', path: `${resourceType}/:id/_history/:version_id`, description: `Read specific version`, parameters: [idPathParam, versionIdPathParam, formatParam], schema: baseSchema, example: JSON.stringify({ ...baseExample, meta: { versionId: "1" } }, null, 2) },
@ -413,181 +524,178 @@ document.addEventListener('DOMContentLoaded', () => {
'delete': { label: `Delete ${resourceType}`, method: 'DELETE', path: `${resourceType}/:id`, description: `Delete ${resourceType}`, parameters: [idPathParam, formatParam], schema: outcomeSchema, example: deleteExample },
'create': { label: `Create ${resourceType}`, method: 'POST', path: `${resourceType}`, description: `Create new ${resourceType}`, parameters: [formatParam], requestBody: true, schema: baseSchema, example: createExampleString },
'search-type': { label: `Search ${resourceType} (GET)`, method: 'GET', path: `${resourceType}`, description: `Search ${resourceType} using GET`, parameters: searchGetParams, schema: bundleSchema, example: bundleSearchExample },
'history-type': { label: `Type History ${resourceType}`, method: 'GET', path: `${resourceType}/_history`, description: `History for all ${resourceType}`, parameters: [countQueryParam, formatParam, lastUpdatedQueryParam], schema: bundleSchema, example: bundleHistoryExample },
'history-instance': { label: `Instance History ${resourceType}`, method: 'GET', path: `${resourceType}/:id/_history`, description: `History for specific ${resourceType}`, parameters: [idPathParam, countQueryParam, formatParam, lastUpdatedQueryParam], schema: bundleSchema, example: bundleHistoryExample },
'history-type': { label: `Type History ${resourceType}`, method: 'GET', path: `${resourceType}/_history`, description: `History for all ${resourceType}`, parameters: [countQueryParam, formatParam, sinceParam], schema: bundleSchema, example: bundleHistoryExample }, // <<< Use sinceParam
'history-instance': { label: `Instance History ${resourceType}`, method: 'GET', path: `${resourceType}/:id/_history`, description: `History for specific ${resourceType}`, parameters: [idPathParam, countQueryParam, formatParam, sinceParam], schema: bundleSchema, example: bundleHistoryExample }, // <<< Use sinceParam
};
supportedInteractions.forEach(code => { if (interactionMap[code]) queries.push(interactionMap[code]); });
if (supportedInteractions.includes('search-type')) { queries.push({ label: `Search ${resourceType} (POST)`, method: 'POST', path: `${resourceType}/_search`, description: `Search ${resourceType} using POST`, parameters: searchPostParams, requestBody: true, schema: bundleSchema, example: postSearchExample }); }
resourceSpecificOperations.forEach(opDef => { const opName = opDef.name.startsWith('$') ? opDef.name : `$${opDef.name}`; const opDoc = opDef.documentation || `Execute ${opName} on ${resourceType}`; const opExample = JSON.stringify({ resourceType: "Parameters", parameter: [{ name: "example-param", valueString: "example-value" }] }, null, 2); const opMethod = opDef.definition?.toLowerCase().includes('get') ? 'GET' : 'POST'; const isInstanceLevel = opDef.definition?.toLowerCase().includes('/{id}/$'); queries.push({ label: `${opName} (${isInstanceLevel ? 'Instance' : 'Type'})`, method: opMethod, path: isInstanceLevel ? `${resourceType}/:id/${opName}` : `${resourceType}/${opName}`, description: opDoc, parameters: isInstanceLevel ? [idPathParam, formatParam] : [formatParam], requestBody: opMethod !== 'GET', schema: paramsSchema, example: opExample }); });
availableSystemOperations.forEach(op => { const opName = op.name.startsWith('$') ? op.name : `$${op.name}`; const opParams = [formatParam]; const opExample = JSON.stringify({ resourceType: "Parameters", parameter: [{ name: "info", valueString: `Input for ${opName}` }] }, null, 2); if (op.levels.includes('type')) { op.methods.forEach(method => { queries.push({ label: `${opName} (Type)`, method, path: `${resourceType}/${opName}`, description: op.documentation || `Type-level ${opName} on ${resourceType}`, parameters: opParams, requestBody: method !== 'GET', schema: paramsSchema, example: opExample }); }); } if (op.levels.includes('instance')) { op.methods.forEach(method => { queries.push({ label: `${opName} (Instance)`, method, path: `${resourceType}/:id/${opName}`, description: op.documentation || `Instance-level ${opName} on ${resourceType}`, parameters: [idPathParam, ...opParams], requestBody: method !== 'GET', schema: paramsSchema, example: opExample }); }); } });
return queries.filter((q, i, arr) => i === arr.findIndex(x => x.path === q.path && x.method === q.method));
}
// --- Generate System-Level Queries (Revised based on metadata example) ---
function getSystemLevelQueries() {
// --- Build Query List ---
// (Rest of the function remains the same)
supportedInteractions.forEach(code => { if (interactionMap[code]) queries.push(interactionMap[code]); });
if (supportedInteractions.includes('search-type')) { queries.push({ label: `Search ${resourceType} (POST)`, method: 'POST', path: `${resourceType}/_search`, description: `Search ${resourceType} using POST (parameters in x-www-form-urlencoded body)`, parameters: searchPostParams, requestBody: true, schema: bundleSchema, example: postSearchExample }); }
resourceSpecificOperations.forEach(opDef => { const opName = opDef.name.startsWith('$') ? opDef.name : `$${opDef.name}`; const opDoc = opDef.documentation || `Execute ${opName} on ${resourceType}`; const opExample = JSON.stringify({ resourceType: "Parameters", parameter: [{ name: "example-param", valueString: "example-value" }] }, null, 2); const opMethod = opDef.definition?.toLowerCase().includes('get') ? 'GET' : 'POST'; const isInstanceLevel = opDef.definition?.toLowerCase().includes('/{id}/$') || opDef.instance === true; const opParams = [formatParam]; if (isInstanceLevel) opParams.unshift(idPathParam); queries.push({ label: `${opName} (${isInstanceLevel ? 'Instance' : 'Type'})`, method: opMethod, path: isInstanceLevel ? `${resourceType}/:id/${opName}` : `${resourceType}/${opName}`, description: opDoc, parameters: opParams, requestBody: opMethod !== 'GET', schema: paramsSchema, example: opExample }); });
availableSystemOperations.forEach(op => { const opName = op.name.startsWith('$') ? op.name : `$${op.name}`; const opParams = [formatParam]; const opExample = JSON.stringify({ resourceType: "Parameters", parameter: [{ name: "info", valueString: `Input for ${opName}` }] }, null, 2); if (op.levels.includes('type')) { op.methods.forEach(method => { queries.push({ label: `${opName} (Type)`, method: method, path: `${resourceType}/${opName}`, description: op.documentation || `Type-level ${opName} on ${resourceType}`, parameters: opParams, requestBody: method !== 'GET', schema: paramsSchema, example: opExample }); }); } if (op.levels.includes('instance')) { op.methods.forEach(method => { queries.push({ label: `${opName} (Instance)`, method: method, path: `${resourceType}/:id/${opName}`, description: op.documentation || `Instance-level ${opName} on ${resourceType}`, parameters: [idPathParam, ...opParams], requestBody: method !== 'GET', schema: paramsSchema, example: opExample }); }); } });
return queries.filter((q, i, arr) => i === arr.findIndex(x => x.path === q.path && x.method === q.method));
} // End getQueryExamplesForResourceType
// --- Generate System-Level Queries (ASYNC VERSION fetching OperationDefinitions - CORRECTED) ---
async function getSystemLevelQueries() { // Added async keyword
if (!fetchedMetadataCache?.rest?.[0]) return [];
const restSection = fetchedMetadataCache.rest[0];
const supportedInteractions = restSection.interaction?.map(i => i.code) || [];
const queries = [];
const addedPaths = new Set(); // Track METHOD + PATH to avoid duplicates
const addedPaths = new Set();
// --- Define Common & Specific Parameters ---
const formatParam = { name: '_format', in: 'query', type: 'string', required: false, description: 'Response format', example: 'json' };
const countParam = { name: '_count', in: 'query', type: 'integer', required: false, description: 'Results per page', example: '10' };
const sinceParam = { name: '_since', in: 'query', type: 'instant', required: false, description: 'Changes after this time', example: new Date().toISOString() };
const contentParam = { name: '_content', in: 'query', type: 'string', required: false, description: 'Search content', example: 'example' };
const paramsSchema = { resourceType: "Parameters" };
const paramsExample = JSON.stringify({ resourceType: "Parameters", parameter: [] }, null, 2);
const defaultParamsSchema = { resourceType: "Parameters" }; // Default schema
const defaultParamsExample = JSON.stringify({ resourceType: "Parameters", parameter: [] }, null, 2); // Default example
// --- Add Metadata (Capabilities) Operation ---
// Always include GET /metadata as it's a standard FHIR endpoint
const metadataPath = 'metadata';
const metadataKey = `GET/${metadataPath}`;
if (!addedPaths.has(metadataKey)) {
queries.push({
name: 'metadata',
label: 'GET /metadata',
method: 'GET',
path: metadataPath,
description: 'server-capabilities: Fetch the server FHIR CapabilityStatement',
parameters: [formatParam],
schema: { resourceType: "CapabilityStatement" },
example: JSON.stringify(fetchedMetadataCache || { resourceType: "CapabilityStatement" }, null, 2)
});
queries.push({ name: 'metadata', label: 'GET /metadata', method: 'GET', path: metadataPath, description: 'server-capabilities: Fetch the server FHIR CapabilityStatement', parameters: [formatParam], schema: { resourceType: "CapabilityStatement" }, example: JSON.stringify(fetchedMetadataCache || { resourceType: "CapabilityStatement" }, null, 2) });
addedPaths.add(metadataKey);
}
// --- Add Other Standard System Interactions ---
// (Keep Transaction/Batch, History, Search sections as is)
// Transaction/Batch
if (supportedInteractions.includes('transaction') || supportedInteractions.includes('batch')) {
const path = '';
const key = `POST/${path}`;
const path = ''; const key = `POST/${path}`;
if (!addedPaths.has(key)) {
queries.push({
name: 'server-transaction',
label: 'POST /',
method: 'POST',
path: path,
description: 'server-transaction: Execute a FHIR Transaction (or FHIR Batch) Bundle',
parameters: [formatParam],
requestBody: true,
schema: { resourceType: "Bundle" },
example: JSON.stringify({ resourceType: "Bundle", type: "transaction", entry: [] }, null, 2)
});
queries.push({ name: 'server-transaction', label: 'POST /', method: 'POST', path: path, description: 'server-transaction: Execute a FHIR Transaction (or FHIR Batch) Bundle', parameters: [formatParam], requestBody: true, schema: { resourceType: "Bundle" }, example: JSON.stringify({ resourceType: "Bundle", type: "transaction", entry: [] }, null, 2) });
addedPaths.add(key);
}
}
// System History
if (supportedInteractions.includes('history-system')) {
const path = '_history';
const key = `GET/${path}`;
const path = '_history'; const key = `GET/${path}`;
if (!addedPaths.has(key)) {
queries.push({
name: 'server-history',
label: 'GET /_history',
method: 'GET',
path: path,
description: 'server-history: Fetch the resource change history across all resource types on the server',
parameters: [formatParam, countParam, sinceParam],
schema: { resourceType: "Bundle" },
example: JSON.stringify({ resourceType: "Bundle", type: "history" }, null, 2)
});
queries.push({ name: 'server-history', label: 'GET /_history', method: 'GET', path: path, description: 'server-history: Fetch the resource change history across all resource types on the server', parameters: [formatParam, countParam, sinceParam], schema: { resourceType: "Bundle" }, example: JSON.stringify({ resourceType: "Bundle", type: "history" }, null, 2) });
addedPaths.add(key);
}
}
// System Search (if supported, less common)
// System Search
if (supportedInteractions.includes('search-system')) {
const path = '';
const key = `GET/${path}`;
const path = ''; const key = `GET/${path}`;
if (!addedPaths.has(key)) {
queries.push({
name: 'search-system',
label: 'GET /',
method: 'GET',
path: path,
description: 'Search across all resource types (limited support usually)',
parameters: [formatParam, countParam, contentParam],
schema: { resourceType: "Bundle" },
example: JSON.stringify({ resourceType: "Bundle", type: "searchset" }, null, 2)
});
queries.push({ name: 'search-system', label: 'GET /', method: 'GET', path: path, description: 'Search across all resource types (limited support usually)', parameters: [formatParam, countParam, contentParam], schema: { resourceType: "Bundle" }, example: JSON.stringify({ resourceType: "Bundle", type: "searchset" }, null, 2) });
addedPaths.add(key);
}
}
// --- Add System-Level Named Operations ---
(restSection.operation || []).forEach(op => {
const opName = op.name;
// --- Add System-Level Named Operations (Fetching Definitions) ---
const operationPromises = (restSection.operation || [])
// Apply the filter using the 'op' parameter consistently
.filter(op => op.name && op.name !== 'metadata' && op.name !== 'capabilities') // <<< CORRECTED HERE
.map(async op => { // Use map with async to create promises
const opName = op.name; // Define opName *inside* map if needed later
const opPath = opName.startsWith('$') ? opName : `$${opName}`;
const defUrl = op.definition || '';
const definitionUrl = op.definition;
const doc = op.documentation || `Execute ${opName} at system level`;
// Skip metadata if already added
if (opName === 'metadata' || opName === 'capabilities') return;
// Determine methods based on operation name and definition
let methods = [];
if (['diff', 'meta', 'get-resource-counts'].includes(opName)) {
methods = ['GET', 'POST'];
} else if (['reindex', 'perform-reindexing-pass', 'mark-all-resources-for-reindexing', 'expunge', 'reindex-terminology', 'hapi.fhir.replace-references'].includes(opName)) {
methods = ['POST'];
} else {
methods = ['POST']; // Default to POST for unknown operations
if (defUrl.toLowerCase().includes('get')) methods.push('GET');
}
// (Keep existing method detection logic...)
if (['diff', 'meta', 'get-resource-counts'].includes(opName)) { methods = ['GET', 'POST']; }
else if (['reindex', 'perform-reindexing-pass', 'mark-all-resources-for-reindexing', 'expunge', 'reindex-terminology', 'hapi.fhir.replace-references'].includes(opName)) { methods = ['POST']; }
else { methods = ['POST']; if (op.definition?.toLowerCase().includes('get')) methods.push('GET'); }
methods.forEach(method => {
const operationQueries = []; // Store queries for this operation's methods
// Attempt to fetch the definition *once* per operation
const opDef = await fetchOperationDefinition(definitionUrl); // <<< AWAIT FETCH
for (const method of methods) { // Now loop through methods
const key = `${method}/${opPath}`;
if (!addedPaths.has(key)) {
let label = `${method} /${opPath}`;
let description = doc;
let operationParameters = [formatParam]; // Default
let operationExample = defaultParamsExample; // Default
let operationSchema = defaultParamsSchema; // Default
// Customize labels and descriptions to match expected output
if (opName === 'diff') {
description = 'Compare two resources or two versions of a single resource';
} else if (opName === 'get-resource-counts') {
description = 'Provides the number of resources currently stored on the server, broken down by resource type';
} else if (opName === 'hapi.fhir.replace-references') {
description = 'Repoints referencing resources to another resource instance';
if (opDef && opDef.parameter) {
try {
const parsedParams = opDef.parameter
.filter(p => p.use === 'in')
.map(p => ({
name: p.name || 'unknown',
in: (method === 'GET' || p.searchType) ? 'query' : 'body (Parameters)', // Basic guess
type: p.type || 'string',
required: (p.min || 0) > 0,
description: p.documentation || `Parameter ${p.name || ''}`,
example: `(${p.type || 'string'})`
}));
operationParameters = [formatParam, ...parsedParams];
operationSchema = opDef;
if (method !== 'GET' && parsedParams.length > 0) {
const exampleParamBody = { resourceType: "Parameters", parameter: parsedParams.map(p => ({ name: p.name, valueString: `example-${p.type}` })) };
operationExample = JSON.stringify(exampleParamBody, null, 2);
}
} catch (parseError) {
console.error(`Error parsing parameters for ${opName} from definition ${definitionUrl}:`, parseError);
operationParameters = [formatParam]; // Fallback
}
} else {
description = `${method}: /${opPath}`; // Fallback for operations like reindex, expunge, etc.
operationParameters = [formatParam]; // Fallback if fetch failed
}
queries.push({
name: opName,
label: label,
method: method,
path: opPath,
description: description,
parameters: [formatParam],
requestBody: method !== 'GET',
schema: paramsSchema,
example: paramsExample
// Customize description (optional)
if (opName === 'diff') description = 'Compare two resources or two versions of a single resource';
else if (opName === 'get-resource-counts') description = 'Provides the number of resources currently stored on the server, broken down by resource type';
// ... etc ...
operationQueries.push({
name: opName, label: label, method: method, path: opPath,
description: description, parameters: operationParameters,
requestBody: method !== 'GET', schema: operationSchema, example: operationExample
});
addedPaths.add(key);
}
});
});
} // end if !addedPaths
} // end for method
return operationQueries; // Return array of queries generated for this operation
}); // end map
// Wait for all fetches and processing to complete
const results = await Promise.all(operationPromises); // <<< AWAIT ALL PROMISES
// Flatten the results
results.forEach(opQueries => queries.push(...opQueries));
console.log("Generated system queries:", queries);
return queries;
}
} // End getSystemLevelQueries (async)
// --- Update Query List UI (FIXED Variable Scope) ---
function updateQueryListUI(resourceType) {
async function updateQueryListUI(resourceType) { // <<< Added async
currentSelectedResource = resourceType;
selectedResourceSpan.textContent = resourceType === 'System' ? 'System Operations' : resourceType;
queryListContainer.innerHTML = '';
queryListContainer.innerHTML = '<p class="text-muted">Loading operations...</p>'; // Show loading state
swaggerUiContainer.style.display = 'block'; // Show container early
// <<< Use await when calling the potentially async function
const queries = resourceType === 'System'
? await getSystemLevelQueries()
: getQueryExamplesForResourceType(resourceType); // Assume this one is still sync for now
queryListContainer.innerHTML = ''; // Clear loading message
const queries = resourceType === 'System' ? getSystemLevelQueries() : getQueryExamplesForResourceType(resourceType);
if (!queries || !queries.length) {
// (rest of the function remains the same)
queryListContainer.innerHTML = '<p>No operations defined or supported for this type.</p>';
swaggerUiContainer.style.display = 'block';
return;
}
queries.sort((a, b) => { const pathA = a.path || ''; const pathB = b.path || ''; if (pathA < pathB) return -1; if (pathA > pathB) return 1; return a.method < b.method ? -1 : a.method > b.method ? 1 : 0; })
queries.sort(/* ... */)
.forEach((query, i) => {
const blockId = `opblock-${i}`;
const block = document.createElement('div');
@ -600,10 +708,10 @@ document.addEventListener('DOMContentLoaded', () => {
block.innerHTML = `
<div class="opblock-summary"><span class="opblock-summary-method ${query.method.toLowerCase()}">${query.method}</span><span class="opblock-summary-path">${query.path}</span><span class="opblock-summary-description">${query.description}</span><svg class="arrow" viewBox="0 0 20 20"><path fill="currentColor" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"/></svg></div>
<div class="opblock-body">
<div class="opblock-section parameters-section"><div class="opblock-section-header"><h4 class="opblock-title">Parameters</h4><div class="try-out"><button class="btn btn-sm try-out__btn">Try it out</button></div></div><div class="parameters-container"><table class="parameters-table"><thead><tr><th class="parameters-col_name">Name</th><th class="parameters-col_description">Description</th></tr></thead><tbody>${query.parameters && query.parameters.length ? query.parameters.map(p => `<tr data-param-name="${p.name}" data-param-in="${p.in}"><td class="parameters-col_name"><div class="parameter__name ${p.required ? 'required' : ''}">${p.name}${p.required ? '<span>*</span>' : ''}</div><div class="parameter__type">${p.type}</div><div class="parameter__in">(${p.in})</div></td><td class="parameters-col_description"><div class="renderedMarkdown"><p>${p.description || 'No description'}</p></div>${p.example ? `<div class="renderedMarkdown"><p><i>Example:</i> ${p.example}</p></div>` : ''}<input type="${p.type === 'integer' ? 'number' : 'text'}" placeholder="${p.example || p.name}" data-example="${p.example || ''}" value="" aria-label="${p.name} parameter value" disabled></td></tr>`).join('') : `<tr><td colspan="2"><div class="renderedMarkdown"><p>No parameters defined.</p></div></td></tr>`}</tbody></table></div></div>
<div class="opblock-section parameters-section"><div class="opblock-section-header"><h4 class="opblock-title">Parameters</h4><div class="try-out"><button class="btn btn-sm btn-primary try-out__btn">Try it out</button></div></div><div class="parameters-container"><table class="parameters-table"><thead><tr><th class="parameters-col_name">Name</th><th class="parameters-col_description">Description</th></tr></thead><tbody>${query.parameters && query.parameters.length ? query.parameters.map(p => `<tr data-param-name="${p.name}" data-param-in="${p.in}"><td class="parameters-col_name"><div class="parameter__name ${p.required ? 'required' : ''}">${p.name}${p.required ? '<span>*</span>' : ''}</div><div class="parameter__type">${p.type}</div><div class="parameter__in">(${p.in})</div></td><td class="parameters-col_description"><div class="renderedMarkdown"><p>${p.description || 'No description'}</p></div>${p.example ? `<div class="renderedMarkdown"><p><i>Example:</i> ${p.example}</p></div>` : ''}<input type="${p.type === 'integer' ? 'number' : 'text'}" placeholder="${p.example || p.name}" data-example="${p.example || ''}" value="" aria-label="${p.name} parameter value" disabled></td></tr>`).join('') : `<tr><td colspan="2"><div class="renderedMarkdown"><p>No parameters defined.</p></div></td></tr>`}</tbody></table></div></div>
${query.requestBody ? `<div class="opblock-section request-body-section"><div class="opblock-section-header"><h4 class="opblock-title">Request Body</h4> <div class="content-type-wrapper"><label for="${blockId}-request-content-type" class="visually-hidden">Request Content Type</label><select id="${blockId}-request-content-type" class="content-type request-content-type" aria-label="Request body content type" disabled><option value="application/fhir+json" selected>application/fhir+json</option><option value="application/fhir+xml">application/fhir+xml</option>${query.method === 'POST' && query.path.endsWith('_search') ? '<option value="application/x-www-form-urlencoded">application/x-www-form-urlencoded</option>' : ''}</select></div></div><textarea class="request-body-textarea" placeholder="Enter request body..." aria-label="Request body input" disabled>${(query.method === 'POST' && query.path.endsWith('_search')) ? query.example : ''}</textarea><div class="model-example request-body-example" style="margin-top: 10px; ${(query.method === 'POST' && query.path.endsWith('_search')) ? 'display: none;' : ''}"><ul class="tab" role="tablist"><li class="tabitem active"><button class="tablinks badge active" data-name="example">Example Body</button></li></ul><div class="example-panel" style="display: block;"><div class="highlight-code"><pre class="request-example-code"><code class="language-json">${query.example && typeof query.example === 'string' && query.example.startsWith('{') ? query.example : '{}'}</code></pre></div></div></div></div> ` : ''}
<div class="opblock-section execute-section"><button class="btn execute__btn" disabled>Execute</button></div>
<div class="execute-wrapper" style="display: none;"><div class="opblock-section request-url-section"><h5 class="opblock-title">Request URL</h5><pre class="request-url-output"><code></code></pre></div><div class="opblock-section curl-section"><div class="opblock-section-header"><h5 class="opblock-title">Curl</h5><button type="button" class="btn btn-sm btn-secondary copy-curl-btn" title="Copy Curl Command"><i class="bi bi-clipboard"></i> Copy</button></div><pre class="curl-output"><code></code></pre></div><div class="opblock-section-header"><h4 class="opblock-title">Server Response</h4> <div class="response-controls"> <select class="response-format-select" aria-label="Response display format" style="display: none;"><option value="json">JSON</option><option value="xml">XML</option><option value="narrative">Narrative</option></select><button type="button" class="btn btn-sm btn-secondary copy-response-btn" title="Copy Response Body" style="display: none;"><i class="bi bi-clipboard"></i> Copy</button> <button type="button" class="btn btn-sm btn-secondary download-response-btn" title="Download Response Body" style="display: none;"><i class="bi bi-download"></i> Download</button></div></div><pre class="response-output-content"><code></code></pre><div class="response-output-narrative" style="display:none;"></div><div class="response-status" style="margin-top: 5px; font-weight: bold;"></div></div>
<div class="opblock-section execute-section"><button class="btn btn-success execute__btn" disabled>Execute</button></div>
<div class="execute-wrapper" style="display: none;"><div class="opblock-section request-url-section"><h5 class="opblock-title">Request URL</h5><pre class="request-url-output"><code></code></pre></div><div class="opblock-section curl-section"><div class="opblock-section-header"><h5 class="opblock-title">Curl</h5><button type="button" class="btn btn-sm btn-outline-secondary btn-copy copy-curl-btn" title="Copy Curl Command"><i class="bi bi-clipboard"></i> Copy</button></div><pre class="curl-output"><code></code></pre></div><div class="opblock-section-header"><h4 class="opblock-title">Server Response</h4> <div class="response-controls"> <select class="response-format-select" aria-label="Response display format" style="display: none;"><option value="json">JSON</option><option value="xml">XML</option><option value="narrative">Narrative</option></select><button type="button" class="btn btn-sm btn-outline-secondary btn-copy copy-response-btn" title="Copy Response Body" style="display: none;"><i class="bi bi-clipboard"></i> Copy</button> <button type="button" class="btn btn-sm btn-outline-secondary btn-copy download-response-btn" title="Download Response Body" style="display: none;"><i class="bi bi-download"></i> Download</button></div></div><pre class="response-output-content"><code></code></pre><div class="response-output-narrative" style="display:none;"></div><div class="response-status" style="margin-top: 5px; font-weight: bold;"></div></div>
<div class="opblock-section responses-section"><div class="opblock-section-header"><h4>Example Response / Schema</h4></div><table class="responses-table"><thead><tr class="responses-header"><td class="response-col_status">Code</td><td class="response-col_description">Description</td></tr></thead><tbody><tr class="response" data-code="200"><td class="response-col_status">200</td><td class="response-col_description"><div class="response-col_description__inner"><div class="renderedMarkdown"><p>Success</p></div></div><section class="response-controls"><div class="response-control-media-type"><label for="${blockId}-example-media-type" class="response-control-media-type__title">Example Format:</label><div class="content-type-wrapper d-inline-block"><select id="${blockId}-example-media-type" aria-label="Example Media Type" class="content-type example-media-type-select"><option value="application/fhir+json">JSON</option><option value="application/fhir+xml">XML</option></select></div><small class="response-control-media-type__accept-message">Controls example/schema format.</small></div></section><div class="model-example"><ul class="tab" role="tablist"><li class="tabitem active"><button class="tablinks badge active" data-name="example">Example Value</button></li><li class="tabitem"><button class="tablinks badge" data-name="schema">Schema</button></li></ul><div class="example-panel" style="display: block;"><div class="highlight-code"><pre class="example-code-display"><code class="language-json">${query.example && typeof query.example === 'string' ? query.example : '{}'}</code></pre></div></div><div class="schema-panel" style="display: none;"><div class="highlight-code"><pre class="schema-code-display"><code class="language-json">${JSON.stringify(query.schema || {}, null, 2)}</code></pre></div></div></div></td></tr><tr class="response" data-code="4xx/5xx"><td class="response-col_status">4xx/5xx</td><td class="response-col_description"><p>Error (e.g., Not Found, Server Error)</p></td></tr></tbody></table></div>
</div>`;
queryListContainer.appendChild(block);
@ -700,8 +808,28 @@ document.addEventListener('DOMContentLoaded', () => {
}
if (exampleTabs.length && examplePanel && schemaPanel) {
exampleTabs.forEach(tab => { tab.addEventListener('click', () => { exampleTabs.forEach(t => t.classList.remove('active')); tab.classList.add('active'); const target = tab.dataset.name; examplePanel.style.display = target === 'example' ? 'block' : 'none'; schemaPanel.style.display = target === 'schema' ? 'block' : 'none'; updateStaticExampleFormat(block); }); });
}
exampleTabs.forEach(tab => { // Outer forEach loop for each tab
tab.addEventListener('click', () => { // Add click listener to the current tab
// When a tab is clicked:
// 1. Make all tabs look inactive
exampleTabs.forEach(t => { // Loop through ALL tabs
t.classList.remove('active', 'bg-primary'); // Remove active state and primary bg
t.classList.add('bg-secondary'); // Make it look inactive (secondary bg)
});
// 2. Make the clicked tab look active
tab.classList.add('active', 'bg-primary'); // Add active state and primary bg
tab.classList.remove('bg-secondary'); // Remove inactive look
// 3. Show/hide the correct panel (Example or Schema)
const target = tab.dataset.name;
examplePanel.style.display = target === 'example' ? 'block' : 'none';
schemaPanel.style.display = target === 'schema' ? 'block' : 'none';
// 4. Update the content format if needed (e.g., JSON/XML)
updateStaticExampleFormat(block);
}); // << End of the arrow function for the click listener
}); // << End of the arrow function for the outer exampleTabs.forEach loop
} // << End of the if block
if (exampleMediaTypeSelect) {
exampleMediaTypeSelect.addEventListener('change', () => updateStaticExampleFormat(block));
@ -803,7 +931,7 @@ document.addEventListener('DOMContentLoaded', () => {
}
// --- Display Metadata & Parse Operations (FIXED Method Parsing Logic) ---
// --- Display Metadata & Parse Operations (Applying async to listeners) ---
function displayMetadataAndResourceButtons(data) {
const rest = data?.rest?.[0];
if (!rest) { resourceButtonsContainer.innerHTML = `<span class="text-danger">Error: Invalid CapabilityStatement (missing 'rest').</span>`; resourceTypesDisplayDiv.style.display = 'block'; swaggerUiContainer.style.display = 'none'; availableSystemOperations = []; return; }
@ -811,91 +939,85 @@ document.addEventListener('DOMContentLoaded', () => {
const interactions = rest.interaction?.map(i => i.code) || [];
// --- PARSE OPERATIONS (Revised Method/Level Detection) ---
// This part remains synchronous - parsing the already fetched metadata
availableSystemOperations = (rest.operation || []).map(op => {
const name = op.name || '';
const defUrl = op.definition || '';
let levels = new Set();
let methods = new Set(); // Start with empty set
let methods = new Set();
// Determine Levels (same logic as before, seems reasonable)
// (Keep existing level detection logic...)
if (defUrl.includes('System') || name.includes('system') || ['transaction', 'batch', 'metadata', 'history-system'].includes(name)) levels.add('system');
if (defUrl.includes('Type') || name.includes('type') || name.startsWith('$')) levels.add('type');
if (defUrl.includes('Instance') || name.includes('instance') || (name.startsWith('$') && !name.endsWith('-system'))) levels.add('instance');
if (!levels.size) { if (name.startsWith('$') || ['transaction', 'batch', 'metadata', 'history-system', 'capabilities'].includes(name)) levels.add('system'); else { levels.add('type'); levels.add('instance'); } }
// Determine Methods (Improved based on metadata example)
if (['metadata', 'history-system', 'history-type', 'history-instance', 'capabilities'].includes(name)) {
methods.add('GET');
} else if (name === 'transaction' || name === 'batch' || name === 'server-transaction') {
methods.add('POST');
levels = new Set(['system']); // Force system level for transaction/batch
} else if (['diff', 'meta', 'get-resource-counts'].includes(name)) {
// These support both GET and POST according to the example metadata
methods.add('GET');
methods.add('POST');
} else if (['reindex', 'perform-reindexing-pass', 'mark-all-resources-for-reindexing', 'expunge', 'reindex-terminology', 'hapi.fhir.replace-references'].includes(name)) {
// These are typically POST only
methods.add('POST');
} else {
// Default guess: If documentation or def URL mentions GET, add it, otherwise default to POST
methods.add('POST'); // Assume POST by default for unknown operations
if (op.documentation?.toLowerCase().includes(' http get') || defUrl.toLowerCase().includes('get')) {
methods.add('GET');
}
}
// (Keep existing method detection logic...)
if (['metadata', 'history-system', 'history-type', 'history-instance', 'capabilities'].includes(name)) { methods.add('GET'); }
else if (name === 'transaction' || name === 'batch' || name === 'server-transaction') { methods.add('POST'); levels = new Set(['system']); }
else if (['diff', 'meta', 'get-resource-counts'].includes(name)) { methods.add('GET'); methods.add('POST'); }
else if (['reindex', 'perform-reindexing-pass', 'mark-all-resources-for-reindexing', 'expunge', 'reindex-terminology', 'hapi.fhir.replace-references'].includes(name)) { methods.add('POST'); }
else { methods.add('POST'); if (op.documentation?.toLowerCase().includes(' http get') || defUrl.toLowerCase().includes('get')) { methods.add('GET'); } }
return { name, methods: Array.from(methods), documentation: op.documentation || '', definition: defUrl, levels: Array.from(levels) };
}).filter(op => op.name); // Filter out operations without a name
// Add standard interactions as fallback if not present as named operations (Optional, handled in generator)
// Example: if (!availableSystemOperations.some(...) && interactions.includes(...)) { ... }
}).filter(op => op.name);
console.log("Final Parsed Operations List:", availableSystemOperations);
resourceButtonsContainer.innerHTML = ''; // Clear previous buttons
const supportsSystemLevel = interactions.some(code => ['transaction', 'batch', 'history-system', 'search-system', 'capabilities'].includes(code)) || availableSystemOperations.some(op => op.levels.includes('system'));
// --- Create Buttons and Attach ASYNC Listeners ---
if (supportsSystemLevel) {
const systemButton = document.createElement('button');
systemButton.type = 'button'; systemButton.className = 'btn btn-outline-success btn-sm me-2 mb-2'; systemButton.textContent = 'System';
systemButton.addEventListener('click', (event) => handleResourceOrSystemButtonClick(event.target, 'System'));
// Make the event listener callback async and use await
systemButton.addEventListener('click', async (event) => { // <<< Added async
await handleResourceOrSystemButtonClick(event.target, 'System'); // <<< Added await
});
resourceButtonsContainer.appendChild(systemButton);
}
resourceTypes.sort().forEach(resType => {
const resourceButton = document.createElement('button');
resourceButton.type = 'button'; resourceButton.className = 'btn btn-outline-dark-blue btn-sm me-2 mb-2'; resourceButton.textContent = resType;
resourceButton.addEventListener('click', (event) => handleResourceOrSystemButtonClick(event.target, resType));
resourceButton.type = 'button'; resourceButton.className = 'btn btn-outline-primary btn-sm me-2 mb-2'; resourceButton.textContent = resType;
// Make the event listener callback async and use await
resourceButton.addEventListener('click', async (event) => { // <<< Added async
await handleResourceOrSystemButtonClick(event.target, resType); // <<< Added await
});
resourceButtonsContainer.appendChild(resourceButton);
});
resourceTypesDisplayDiv.style.display = 'block'; // Show the buttons container
// --- Trigger Action for First Button ---
const firstBtn = resourceButtonsContainer.querySelector('button');
if (firstBtn) { firstBtn.click(); }
else { resourceButtonsContainer.innerHTML = '<span class="text-warning">No resources or system operations found in metadata.</span>'; swaggerUiContainer.style.display = 'none'; }
if (firstBtn) {
// Instead of firstBtn.click(), directly call the async handler
// Use setTimeout to allow the current rendering cycle to finish first
setTimeout(async () => {
// Ensure the button isn't already active from a previous state if possible
if (!firstBtn.classList.contains('active')) {
await handleResourceOrSystemButtonClick(firstBtn, firstBtn.textContent);
}
// --- Handle Resource/System Button Clicks (FIXED color classes) ---
function handleResourceOrSystemButtonClick(clickedButton, resourceOrSystemName) {
resourceButtonsContainer.querySelectorAll('.btn').forEach(button => {
button.classList.remove('active', 'btn-success', 'btn-dark-blue', 'btn-outline-success', 'btn-outline-dark-blue'); // Remove all relevant classes
// Re-apply the correct OUTLINE class
if (button.textContent === 'System') {
button.classList.add('btn-outline-success');
}, 0);
} else {
button.classList.add('btn-outline-dark-blue');
resourceButtonsContainer.innerHTML = '<span class="text-warning">No resources or system operations found in metadata.</span>';
swaggerUiContainer.style.display = 'none';
}
} // End displayMetadataAndResourceButtons
// --- Handle Resource/System Button Clicks (NOW ASYNC) ---
async function handleResourceOrSystemButtonClick(clickedButton, resourceOrSystemName) { // <<< Added async
// (Keep the button class manipulation logic as is)
resourceButtonsContainer.querySelectorAll('.btn').forEach(button => {
// ... remove/add classes ...
});
// Activate the clicked button
clickedButton.classList.add('active');
if (resourceOrSystemName === 'System') {
clickedButton.classList.remove('btn-outline-success'); // Remove outline
clickedButton.classList.add('btn-success'); // Add solid
} else {
clickedButton.classList.remove('btn-outline-dark-blue'); // Remove outline
clickedButton.classList.add('btn-dark-blue'); // Add solid
}
updateQueryListUI(resourceOrSystemName); // Update the operation list
// ... add/remove classes ...
await updateQueryListUI(resourceOrSystemName); // <<< Use await
}
// --- Initial Page State Setup ---

File diff suppressed because it is too large Load Diff

View File

@ -18,13 +18,22 @@
</div>
</div>
<!-- Spinner Overlay -->
<div id="spinner-overlay" class="d-none position-fixed top-0 start-0 w-100 h-100 bg-dark bg-opacity-50 d-flex align-items-center justify-content-center" style="z-index: 1050;">
<div class="text-center">
<div id="spinner-animation" style="width: 200px; height: 200px;"></div>
<p class="text-white mt-3">Waiting, don't leave this page...</p>
</div>
</div>
<div class="container mt-4">
<h2><i class="bi bi-file-code me-2"></i>Convert FHIR to FSH</h2>
<div id="fsh-output">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-body">
<form method="POST" enctype="multipart/form-data" class="form">
<form id="fsh-converter-form" method="POST" enctype="multipart/form-data" class="form">
{{ form.hidden_tag() }}
{{ render_field(form.package) }}
{{ render_field(form.input_mode) }}
@ -37,8 +46,14 @@
{{ 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) }}
<div class="d-grid gap-2 d-sm-flex">
{{ form.submit(class="btn btn-success") }}
{{ form.submit(class="btn btn-success", id="submit-btn") }}
<a href="{{ url_for('index') }}" class="btn btn-secondary">Back</a>
</div>
</form>
@ -46,30 +61,163 @@
</div>
</div>
</div>
{% if error %}
<div class="alert alert-danger mt-4">{{ error }}</div>
{% endif %}
{% if fsh_output %}
<div class="alert alert-success mt-4">Conversion successful!</div>
<h3 class="mt-4">FSH Output</h3>
<pre class="bg-light p-3">{{ fsh_output }}</pre>
<a href="{{ url_for('download_fsh') }}" class="btn btn-primary">Download FSH</a>
{% endif %}
</div>
</div>
<script src="{{ url_for('static', filename='js/lottie.min.js') }}"></script>
<script>
document.getElementById('input_mode').addEventListener('change', function() {
const fileUpload = document.getElementById('file-upload');
const textInput = document.getElementById('text-input');
if (this.value === 'file') {
fileUpload.style.display = 'block';
textInput.style.display = 'none';
} else {
fileUpload.style.display = 'none';
textInput.style.display = 'block';
// --- Global Variables and Helper Functions (Defined *outside* DOMContentLoaded) ---
let currentLottieAnimation = null;
function loadLottieAnimation(theme) {
// console.log(`loadLottieAnimation called with theme: ${theme}`); // Optional debug log
const spinnerContainer = document.getElementById('spinner-animation');
// console.log(`Found spinnerContainer: ${!!spinnerContainer}`); // Optional debug log
if (!spinnerContainer) return;
if (currentLottieAnimation) {
// console.log("Destroying previous Lottie animation."); // Optional debug log
currentLottieAnimation.destroy();
currentLottieAnimation = null;
}
const animationPath = (theme === 'dark')
? '{{ url_for('static', filename='animations/loading-dark.json') }}'
: '{{ url_for('static', filename='animations/loading-light.json') }}';
// console.log(`Determined animation path: ${animationPath}`); // Optional debug log
try {
currentLottieAnimation = lottie.loadAnimation({ container: spinnerContainer, renderer: 'svg', loop: true, autoplay: true, path: animationPath });
// console.log("New Lottie animation loaded."); // Optional debug log
} catch(lottieError) {
console.error("Error loading Lottie animation:", lottieError);
}
}
// --- Function to handle input mode changes (for SELECT element) ---
function handleInputModeChange(selectElement) {
if (!selectElement) { console.error("handleInputModeChange called with no selectElement"); return; }
const selectedValue = selectElement.value; // Get value from the select element
console.log(`handleInputModeChange called for value: ${selectedValue}`); // Keep this log for now
const form = selectElement.closest('form');
if (!form) { console.error('Could not find parent form for input mode toggle.'); return; }
const fileUploadDiv = form.querySelector('#file-upload');
const textInputDiv = form.querySelector('#text-input');
// console.log(`Found fileUploadDiv: ${!!fileUploadDiv}, Found textInputDiv: ${!!textInputDiv}`); // Optional debug log
if (fileUploadDiv && textInputDiv) {
// Show/hide based on the SELECTED VALUE
if (selectedValue === 'file') {
fileUploadDiv.style.display = 'block';
textInputDiv.style.display = 'none';
} else { // Assuming 'text' is the other value
fileUploadDiv.style.display = 'none';
textInputDiv.style.display = 'block';
}
// console.log(`Set display - file: ${fileUploadDiv.style.display}, text: ${textInputDiv.style.display}`); // Optional debug log
} else {
console.error('Could not find file-upload or text-input divs within the form.');
}
}
// --- Function to initialize UI elements within a container (for SELECT element) ---
function initializeFshConverterUI(containerElement) {
console.log("Initializing FSH Converter UI within:", containerElement === document ? "document" : "outputContainer"); // Keep this log for now
// --- Input Mode Toggle Initialization ---
// Select the dropdown using its ID
const inputModeSelect = containerElement.querySelector('#input_mode'); // Use ID selector for the select
if (inputModeSelect) {
// Attach the listener directly to the select element
inputModeSelect.addEventListener('change', (event) => {
// Pass the select element itself to the handler
handleInputModeChange(event.target);
});
// Trigger change on page load to set initial state
document.getElementById('input_mode').dispatchEvent(new Event('change'));
// Set initial state based on the select element's current value
console.log(`Initial input mode detected: ${inputModeSelect.value}`); // Keep this log
handleInputModeChange(inputModeSelect); // Call handler directly with the select element
} else {
// This error should hopefully not appear now
console.error('Input mode select element (#input_mode) not found within container:', containerElement);
}
// Only load initial Lottie when initializing the whole document
if (containerElement === document) {
const currentTheme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light';
// Make sure loadLottieAnimation function exists before calling
if (typeof loadLottieAnimation === 'function') {
loadLottieAnimation(currentTheme);
} else {
console.error("loadLottieAnimation function not defined.");
}
}
} // End initializeFshConverterUI
// --- Main Execution on DOMContentLoaded ---
document.addEventListener('DOMContentLoaded', function() {
// --- Initial UI Setup ---
// Use setTimeout for the initial call to ensure form elements are ready
setTimeout(() => {
initializeFshConverterUI(document);
}, 50); // Using a slightly longer delay just in case 0 isn't enough
// --- Form submission with AJAX (Event Delegation) ---
const outputContainer = document.getElementById('fsh-output');
if (outputContainer) {
outputContainer.addEventListener('submit', async function(event) {
// Check if the event originated from our specific form
if (event.target && event.target.id === 'fsh-converter-form') {
event.preventDefault(); // Prevent default only if it's our form
const form = event.target;
const submitBtn = form.querySelector('#submit-btn');
const spinnerOverlay = document.getElementById('spinner-overlay'); // Spinner is outside the form
if (!submitBtn || !spinnerOverlay) {
console.error("Submit button or spinner overlay not found.");
return; // Stop if critical elements missing
}
submitBtn.disabled = true;
spinnerOverlay.classList.remove('d-none'); // Show spinner
const formData = new FormData(form);
try {
const response = await fetch('{{ url_for('fsh_converter') }}', { method: 'POST', headers: { 'X-Requested-With': 'XMLHttpRequest' }, body: formData });
if (!response.ok) { let errorText = response.statusText; try { const errorData = await response.json(); errorText = errorData.error || errorText; } catch (e) {} throw new Error(`Network response was not ok: ${response.status} ${errorText}`); }
const html = await response.text();
const themeNow = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light';
// Check function exists before calling, just in case
if (typeof loadLottieAnimation === 'function') {
loadLottieAnimation(themeNow); // Ensure animation theme is updated before showing result
}
outputContainer.innerHTML = html; // Replace content
initializeFshConverterUI(outputContainer); // Re-initialize controls for new content
// Hide spinner AFTER content replaced and potentially re-initialized
spinnerOverlay.classList.add('d-none');
} catch (error) {
console.error('Error during FSH conversion AJAX:', error);
if(spinnerOverlay) spinnerOverlay.classList.add('d-none');
// No need to re-enable submitBtn as the form was replaced
const alertDiv = document.createElement('div');
alertDiv.className = 'alert alert-danger mt-4';
alertDiv.textContent = `An error occurred during conversion: ${error.message}. Please check input and try again.`;
outputContainer.innerHTML = ''; // Clear previous content before showing only error
outputContainer.appendChild(alertDiv);
}
} // End if event target is form
}); // End submit listener
} else {
console.error("#fsh-output container not found for attaching delegated event listener.");
}
}); // End DOMContentLoaded Listener
</script>
{% endblock %}