HUGE RELEASE

Added _ hapi server.
Added goFSH
added FHIR operations UI and FHIR based resful Client
Logfiles for seperate applications for persistance and log rollover

to do: advanced FSH commands
Error reporting hooks
This commit is contained in:
Joshua Hare 2025-04-17 17:17:44 +10:00
parent 8a39603554
commit 4129d7ad7b
39 changed files with 2611 additions and 174 deletions

View File

@ -1,13 +1,24 @@
FROM python:3.9-slim
WORKDIR /app
# Base image with Python and Java
FROM tomcat:10.1-jdk17
# Install build dependencies and Node.js 18
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 python3-pip python3-venv curl \
&& 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
# Set up Python environment
WORKDIR /app
RUN python3 -m venv /app/venv
ENV PATH="/app/venv/bin:$PATH"
# Copy Flask files
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
COPY services.py .
COPY forms.py .
@ -15,10 +26,22 @@ COPY templates/ templates/
COPY static/ static/
COPY tests/ tests/
# Ensure /tmp is writable as a fallback
RUN mkdir -p /tmp && chmod 777 /tmp
# Ensure /tmp, /app/h2-data, /app/static/uploads, and /app/logs are writable
RUN mkdir -p /tmp /app/h2-data /app/static/uploads /app/logs && chmod 777 /tmp /app/h2-data /app/static/uploads /app/logs
EXPOSE 5000
ENV FLASK_APP=app.py
ENV FLASK_ENV=development
CMD ["flask", "run", "--host=0.0.0.0"]
# Copy pre-built HAPI WAR and configuration
COPY hapi-fhir-jpaserver/target/ROOT.war /usr/local/tomcat/webapps/
COPY hapi-fhir-jpaserver/target/classes/application.yaml /usr/local/tomcat/conf/
COPY hapi-fhir-jpaserver/custom/ /usr/local/tomcat/webapps/custom/
# Install supervisord
RUN pip install supervisor
# Configure supervisord
COPY supervisord.conf /etc/supervisord.conf
# Expose ports
EXPOSE 5000 8080
# Start supervisord
CMD ["supervisord", "-c", "/etc/supervisord.conf"]

131
app.py
View File

@ -2,7 +2,7 @@ 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
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, Response, current_app, session, send_file
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from flask_wtf.csrf import CSRFProtect
@ -13,7 +13,9 @@ import requests
import re
import services # Restore full module import
from services import services_bp # Keep Blueprint import
from forms import IgImportForm, ValidationForm
from forms import IgImportForm, ValidationForm, FSHConverterForm
from wtforms import SubmitField
import tempfile
# Set up logging
logging.basicConfig(level=logging.DEBUG)
@ -27,6 +29,7 @@ app.config['FHIR_PACKAGES_DIR'] = '/app/instance/fhir_packages'
app.config['API_KEY'] = os.environ.get('API_KEY', 'your-fallback-api-key-here')
app.config['VALIDATE_IMPOSED_PROFILES'] = True
app.config['DISPLAY_PROFILE_RELATIONSHIPS'] = True
app.config['UPLOAD_FOLDER'] = '/app/static/uploads' # For GoFSH output
# Ensure directories exist and are writable
instance_path = '/app/instance'
@ -40,6 +43,7 @@ try:
logger.debug(f"Flask instance folder path: {instance_folder_path}")
os.makedirs(instance_folder_path, exist_ok=True)
os.makedirs(packages_path, exist_ok=True)
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
logger.debug(f"Directories created/verified: Instance: {instance_folder_path}, Packages: {packages_path}")
except Exception as e:
logger.error(f"Failed to create/verify directories: {e}", exc_info=True)
@ -891,5 +895,128 @@ def create_db():
with app.app_context():
create_db()
class FhirRequestForm(FlaskForm):
submit = SubmitField('Send Request')
@app.route('/fhir')
def fhir_ui():
form = FhirRequestForm()
return render_template('fhir_ui.html', form=form, site_name='FHIRFLARE IG Toolkit', now=datetime.datetime.now())
@app.route('/fhir-ui-operations')
def fhir_ui_operations():
form = FhirRequestForm()
return render_template('fhir_ui_operations.html', form=form, site_name='FHIRFLARE IG Toolkit', now=datetime.datetime.now())
@app.route('/fhir/<path:subpath>', methods=['GET', 'POST', 'PUT', 'DELETE'])
def proxy_hapi(subpath):
# Clean subpath to remove r4/, fhir/, leading/trailing slashes
clean_subpath = subpath.replace('r4/', '').replace('fhir/', '').strip('/')
hapi_url = f"http://localhost:8080/fhir/{clean_subpath}" if clean_subpath else "http://localhost:8080/fhir"
headers = {k: v for k, v in request.headers.items() if k != 'Host'}
logger.debug(f"Proxying request: {request.method} {hapi_url}")
try:
response = requests.request(
method=request.method,
url=hapi_url,
headers=headers,
data=request.get_data(),
cookies=request.cookies,
allow_redirects=False
)
response.raise_for_status()
# Strip hop-by-hop headers to avoid chunked encoding issues
response_headers = {
k: v for k, v in response.headers.items()
if k.lower() not in (
'transfer-encoding', 'connection', 'content-encoding',
'content-length', 'keep-alive', 'proxy-authenticate',
'proxy-authorization', 'te', 'trailers', 'upgrade'
)
}
# Ensure Content-Length matches the actual body
response_headers['Content-Length'] = str(len(response.content))
logger.debug(f"Response: {response.status_code} {response.reason}")
return response.content, response.status_code, response_headers.items()
except requests.RequestException as e:
logger.error(f"Proxy error: {str(e)}")
return jsonify({'error': str(e)}), response.status_code if 'response' in locals() else 500
@app.route('/fsh-converter', methods=['GET', 'POST'])
def fsh_converter():
form = FSHConverterForm()
error = None
fsh_output = None
# Populate package choices
packages = []
packages_dir = app.config['FHIR_PACKAGES_DIR']
if os.path.exists(packages_dir):
for filename in os.listdir(packages_dir):
if filename.endswith('.tgz'):
try:
with tarfile.open(os.path.join(packages_dir, filename), 'r:gz') as tar:
package_json = tar.extractfile('package/package.json')
if package_json:
pkg_info = json.load(package_json)
name = pkg_info.get('name')
version = pkg_info.get('version')
if name and version:
packages.append((f"{name}#{version}", f"{name}#{version}"))
except Exception as e:
logger.warning(f"Error reading package {filename}: {e}")
continue
form.package.choices = [('', 'None')] + sorted(packages, key=lambda x: x[0])
if form.validate_on_submit():
input_mode = form.input_mode.data
fhir_file = form.fhir_file.data
fhir_text = form.fhir_text.data
output_style = form.output_style.data
log_level = form.log_level.data
fhir_version = form.fhir_version.data or None
# Process input
input_path, temp_dir, error = services.process_fhir_input(input_mode, fhir_file, fhir_text)
if 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
if temp_dir and os.path.exists(temp_dir):
import shutil
shutil.rmtree(temp_dir)
if 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
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())
return render_template('fsh_converter.html', form=form, error=error, site_name='FHIRFLARE IG Toolkit', now=datetime.datetime.now())
@app.route('/download-fsh')
def download_fsh():
fsh_output = session.get('fsh_output', '')
if not fsh_output:
flash('No FSH output available for download.', 'error')
return redirect(url_for('fsh_converter'))
temp_file = os.path.join(app.config['UPLOAD_FOLDER'], 'output.fsh')
with open(temp_file, 'w', encoding='utf-8') as f:
f.write(fsh_output)
return send_file(temp_file, as_attachment=True, download_name='output.fsh')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False)

19
docker-compose.yml Normal file
View File

@ -0,0 +1,19 @@
version: '3.8'
services:
fhirflare:
build:
context: .
dockerfile: Dockerfile
ports:
- "5000:5000"
- "8080:8080"
volumes:
- ./instance:/app/instance
- ./static/uploads:/app/static/uploads
- ./hapi-fhir-jpaserver/target/h2-data:/app/h2-data
- ./logs:/app/logs
environment:
- FLASK_APP=app.py
- FLASK_ENV=development
- NODE_PATH=/usr/lib/node_modules
command: supervisord -c /etc/supervisord.conf

View File

@ -1,9 +1,10 @@
# forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, SelectField, TextAreaField, BooleanField, SubmitField
from wtforms.validators import DataRequired, Regexp, ValidationError
from wtforms import StringField, SelectField, TextAreaField, BooleanField, SubmitField, FileField
from wtforms.validators import DataRequired, Regexp, ValidationError, Optional
import json
# Existing forms (IgImportForm, ValidationForm) remain unchanged
class IgImportForm(FlaskForm):
package_name = StringField('Package Name', validators=[
DataRequired(),
@ -45,4 +46,57 @@ def validate_json(data, mode):
except json.JSONDecodeError:
raise ValidationError("Invalid JSON format.")
except ValueError as e:
raise ValidationError(str(e))
raise ValidationError(str(e))
class FSHConverterForm(FlaskForm):
package = SelectField('FHIR Package (Optional)', choices=[('', 'None')], validators=[Optional()])
input_mode = SelectField('Input Mode', choices=[
('file', 'Upload File'),
('text', 'Paste Text')
], validators=[DataRequired()])
fhir_file = FileField('FHIR Resource File (JSON/XML)', validators=[Optional()])
fhir_text = TextAreaField('FHIR Resource Text (JSON/XML)', validators=[Optional()])
output_style = SelectField('Output Style', choices=[
('file-per-definition', 'File per Definition'),
('group-by-fsh-type', 'Group by FSH Type'),
('group-by-profile', 'Group by Profile'),
('single-file', 'Single File')
], validators=[DataRequired()])
log_level = SelectField('Log Level', choices=[
('error', 'Error'),
('warn', 'Warn'),
('info', 'Info'),
('debug', 'Debug')
], validators=[DataRequired()])
fhir_version = SelectField('FHIR Version', choices=[
('', 'Auto-detect'),
('4.0.1', 'R4'),
('4.3.0', 'R4B'),
('5.0.0', 'R5')
], validators=[Optional()])
submit = SubmitField('Convert to FSH')
def validate(self, extra_validators=None):
if not super().validate(extra_validators):
return False
if self.input_mode.data == 'file' and not self.fhir_file.data:
self.fhir_file.errors.append('File is required when input mode is Upload File.')
return False
if self.input_mode.data == 'text' and not self.fhir_text.data:
self.fhir_text.errors.append('Text input is required when input mode is Paste Text.')
return False
if self.input_mode.data == 'text' and self.fhir_text.data:
try:
content = self.fhir_text.data.strip()
if 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.')
return False
except (json.JSONDecodeError, ET.ParseError):
self.fhir_text.errors.append('Invalid JSON or XML format.')
return False
return True

Binary file not shown.

View File

@ -0,0 +1,22 @@
{
"package_name": "hl7.fhir.au.base",
"version": "5.1.0-preview",
"dependency_mode": "recursive",
"imported_dependencies": [
{
"name": "hl7.fhir.r4.core",
"version": "4.0.1"
},
{
"name": "hl7.terminology.r4",
"version": "6.2.0"
},
{
"name": "hl7.fhir.uv.extensions.r4",
"version": "5.2.0"
}
],
"complies_with_profiles": [],
"imposed_profiles": [],
"timestamp": "2025-04-17T04:04:45.070781+00:00"
}

View File

@ -1,7 +1,7 @@
{
"package_name": "hl7.fhir.au.core",
"version": "1.1.0-preview",
"dependency_mode": "tree-shaking",
"dependency_mode": "recursive",
"imported_dependencies": [
{
"name": "hl7.fhir.r4.core",
@ -30,5 +30,5 @@
],
"complies_with_profiles": [],
"imposed_profiles": [],
"timestamp": "2025-04-15T00:27:41.653977+00:00"
"timestamp": "2025-04-17T04:04:20.523471+00:00"
}

View File

@ -1,9 +1,9 @@
{
"package_name": "hl7.fhir.r4.core",
"version": "4.0.1",
"dependency_mode": "tree-shaking",
"dependency_mode": "recursive",
"imported_dependencies": [],
"complies_with_profiles": [],
"imposed_profiles": [],
"timestamp": "2025-04-15T00:27:55.537600+00:00"
"timestamp": "2025-04-17T04:04:29.230227+00:00"
}

View File

@ -0,0 +1,14 @@
{
"package_name": "hl7.fhir.uv.extensions.r4",
"version": "5.2.0",
"dependency_mode": "recursive",
"imported_dependencies": [
{
"name": "hl7.fhir.r4.core",
"version": "4.0.1"
}
],
"complies_with_profiles": [],
"imposed_profiles": [],
"timestamp": "2025-04-17T04:04:41.588025+00:00"
}

View File

@ -0,0 +1,22 @@
{
"package_name": "hl7.fhir.uv.ipa",
"version": "1.0.0",
"dependency_mode": "recursive",
"imported_dependencies": [
{
"name": "hl7.fhir.r4.core",
"version": "4.0.1"
},
{
"name": "hl7.terminology.r4",
"version": "5.0.0"
},
{
"name": "hl7.fhir.uv.smart-app-launch",
"version": "2.0.0"
}
],
"complies_with_profiles": [],
"imposed_profiles": [],
"timestamp": "2025-04-17T04:04:49.395594+00:00"
}

Binary file not shown.

View File

@ -0,0 +1,14 @@
{
"package_name": "hl7.fhir.uv.smart-app-launch",
"version": "2.0.0",
"dependency_mode": "recursive",
"imported_dependencies": [
{
"name": "hl7.fhir.r4.core",
"version": "4.0.1"
}
],
"complies_with_profiles": [],
"imposed_profiles": [],
"timestamp": "2025-04-17T04:04:56.492512+00:00"
}

View File

@ -0,0 +1,18 @@
{
"package_name": "hl7.fhir.uv.smart-app-launch",
"version": "2.1.0",
"dependency_mode": "recursive",
"imported_dependencies": [
{
"name": "hl7.fhir.r4.core",
"version": "4.0.1"
},
{
"name": "hl7.terminology.r4",
"version": "5.0.0"
}
],
"complies_with_profiles": [],
"imposed_profiles": [],
"timestamp": "2025-04-17T04:04:46.943079+00:00"
}

View File

@ -0,0 +1,14 @@
{
"package_name": "hl7.terminology.r4",
"version": "5.0.0",
"dependency_mode": "recursive",
"imported_dependencies": [
{
"name": "hl7.fhir.r4.core",
"version": "4.0.1"
}
],
"complies_with_profiles": [],
"imposed_profiles": [],
"timestamp": "2025-04-17T04:04:54.857273+00:00"
}

Binary file not shown.

View File

@ -0,0 +1,14 @@
{
"package_name": "hl7.terminology.r4",
"version": "6.2.0",
"dependency_mode": "recursive",
"imported_dependencies": [
{
"name": "hl7.fhir.r4.core",
"version": "4.0.1"
}
],
"complies_with_profiles": [],
"imposed_profiles": [],
"timestamp": "2025-04-17T04:04:37.703082+00:00"
}

Binary file not shown.

4
logs/flask.log Normal file
View File

@ -0,0 +1,4 @@
* Serving Flask app 'app'
* Debug mode: off
* Serving Flask app 'app'
* Debug mode: off

258
logs/flask_err.log Normal file
View File

@ -0,0 +1,258 @@
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
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 -
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]
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()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/venv/lib/python3.12/site-packages/flask/app.py", line 1486, in full_dispatch_request
rv = self.handle_user_exception(e)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/venv/lib/python3.12/site-packages/flask/app.py", line 1484, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
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 -
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
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 -

17
logs/supervisord.log Normal file
View File

@ -0,0 +1,17 @@
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)

1
logs/supervisord.pid Normal file
View File

@ -0,0 +1 @@
1

368
logs/tomcat.log Normal file
View File

@ -0,0 +1,368 @@
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.3.5)
2025-04-17T06:25:41.168Z INFO 8 --- [ main] ca.uhn.fhir.jpa.starter.Application : Starting Application using Java 17.0.14 with PID 8 (/usr/local/tomcat/webapps/ROOT/WEB-INF/classes started by root in /usr/local/tomcat)
2025-04-17T06:25:41.173Z INFO 8 --- [ main] ca.uhn.fhir.jpa.starter.Application : No active profile set, falling back to 1 default profile: "default"
2025-04-17T06:25:43.114Z INFO 8 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2025-04-17T06:25:43.465Z INFO 8 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 338 ms. Found 52 JPA repository interfaces.
2025-04-17T06:25:47.008Z WARN 8 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'ca.uhn.fhir.jpa.config.BeanPostProcessorConfig' of type [ca.uhn.fhir.jpa.config.BeanPostProcessorConfig$$SpringCGLIB$$0] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). The currently created BeanPostProcessor [persistenceExceptionTranslationPostProcessor] is declared through a non-static factory method on that class; consider declaring it as static instead.
2025-04-17T06:25:47.312Z INFO 8 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 6089 ms
2025-04-17T06:25:47.688Z INFO 8 --- [ main] ca.uhn.fhir.util.VersionUtil : HAPI FHIR version 8.0.0 - Rev 091beb6d18
2025-04-17T06:25:47.701Z INFO 8 --- [ main] ca.uhn.fhir.context.FhirContext : Creating new FHIR context for FHIR version [R4]
2025-04-17T06:25:47.820Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to allow contains searches
2025-04-17T06:25:47.821Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to deny multiple deletes
2025-04-17T06:25:47.822Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to deny external references
2025-04-17T06:25:47.823Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to enable DAO scheduling
2025-04-17T06:25:47.823Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to disable delete expunges
2025-04-17T06:25:47.824Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to enable expunges
2025-04-17T06:25:47.824Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to allow overriding default search params
2025-04-17T06:25:47.825Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to disable auto-creating placeholder references
2025-04-17T06:25:47.825Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to auto-version references at paths []
2025-04-17T06:25:47.826Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to enable value set pre-expansion
2025-04-17T06:25:47.826Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to enable value set pre-expansion task
2025-04-17T06:25:47.826Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured for pre-expand value set default count of 1000
2025-04-17T06:25:47.826Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured for pre-expand value set max count of 1000
2025-04-17T06:25:47.826Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured for maximum expansion size of 1000
2025-04-17T06:25:47.878Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to have a maximum fetch size of 'unlimited'
2025-04-17T06:25:47.879Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to cache search results for 60000 milliseconds
2025-04-17T06:25:47.879Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to use 'ALPHANUMERIC' Client ID Strategy
2025-04-17T06:25:48.139Z INFO 8 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: HAPI_PU]
2025-04-17T06:25:48.494Z INFO 8 --- [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 6.6.4.Final
2025-04-17T06:25:48.547Z INFO 8 --- [ main] .f.j.l.FilteringSqlLoggerImplContributor : Adding service: SqlStatementFilteringLogger
2025-04-17T06:25:48.743Z INFO 8 --- [ main] o.h.c.internal.RegionFactoryInitiator : HHH000026: Second-level cache disabled
2025-04-17T06:25:49.196Z INFO 8 --- [ main] o.h.e.boot.internal.EnversServiceImpl : Envers integration enabled? : true
2025-04-17T06:25:49.780Z INFO 8 --- [ main] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer
2025-04-17T06:25:49.878Z INFO 8 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2025-04-17T06:25:51.263Z INFO 8 --- [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection conn1: url=jdbc:h2:file:/app/h2-data/fhir user=SA
2025-04-17T06:25:51.269Z INFO 8 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2025-04-17T06:25:51.433Z INFO 8 --- [ main] org.hibernate.orm.connections.pooling : HHH10001005: Database info:
Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)']
Database driver: undefined/unknown
Database version: 2.3.232
Autocommit mode: undefined/unknown
Isolation level: undefined/unknown
Minimum pool size: undefined/unknown
Maximum pool size: undefined/unknown
2025-04-17T06:25:53.533Z INFO 8 --- [ main] b.i.HibernateSearchPreIntegrationService : HSEARCH000034: Hibernate Search version 7.2.1.Final
2025-04-17T06:25:53.763Z INFO 8 --- [ main] o.h.e.c.i.m.AuditMetadataGenerator : Adding properties for entity: ca.uhn.fhir.jpa.entity.MdmLink
2025-04-17T06:25:58.698Z INFO 8 --- [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
2025-04-17T06:25:59.508Z INFO 8 --- [ main] irLocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'HAPI_PU'
2025-04-17T06:26:01.166Z INFO 8 --- [ main] .u.f.c.s.DefaultProfileValidationSupport : Loading structure definitions from classpath: /org/hl7/fhir/r4/model/profile/profiles-resources.xml
2025-04-17T06:26:01.191Z INFO 8 --- [ main] ca.uhn.fhir.util.XmlUtil : FHIR XML procesing will use StAX implementation 'Woodstox' version '6.4.0'
2025-04-17T06:26:02.561Z INFO 8 --- [ main] .u.f.c.s.DefaultProfileValidationSupport : Loading structure definitions from classpath: /org/hl7/fhir/r4/model/profile/profiles-types.xml
2025-04-17T06:26:02.707Z INFO 8 --- [ main] .u.f.c.s.DefaultProfileValidationSupport : Loading structure definitions from classpath: /org/hl7/fhir/r4/model/profile/profiles-others.xml
2025-04-17T06:26:03.137Z INFO 8 --- [ main] .u.f.c.s.DefaultProfileValidationSupport : Loading structure definitions from classpath: /org/hl7/fhir/r4/model/extension/extension-definitions.xml
2025-04-17T06:26:04.442Z INFO 8 --- [ main] o.s.d.j.r.query.QueryEnhancerFactory : Hibernate is in classpath; If applicable, HQL parser will be used.
2025-04-17T06:26:10.654Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Creating Local Scheduler
2025-04-17T06:26:10.739Z INFO 8 --- [ main] org.quartz.impl.StdSchedulerFactory : Using default implementation for ThreadExecutor
2025-04-17T06:26:10.772Z INFO 8 --- [ main] org.quartz.core.SchedulerSignalerImpl : Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
2025-04-17T06:26:10.772Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : Quartz Scheduler v.2.3.2 created.
2025-04-17T06:26:10.774Z INFO 8 --- [ main] org.quartz.simpl.RAMJobStore : RAMJobStore initialized.
2025-04-17T06:26:10.776Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : Scheduler meta-data: Quartz Scheduler (v2.3.2) 'hapi-fhir-jpa-scheduler' with instanceId 'NON_CLUSTERED'
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 4 threads.
Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
2025-04-17T06:26:10.776Z INFO 8 --- [ main] org.quartz.impl.StdSchedulerFactory : Quartz scheduler 'hapi-fhir-jpa-scheduler' initialized from an externally provided properties instance.
2025-04-17T06:26:10.776Z INFO 8 --- [ main] org.quartz.impl.StdSchedulerFactory : Quartz scheduler version: 2.3.2
2025-04-17T06:26:10.777Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : JobFactory set to: ca.uhn.fhir.jpa.sched.AutowiringSpringBeanJobFactory@493c19fd
2025-04-17T06:26:10.777Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : Scheduler hapi-fhir-jpa-scheduler_$_NON_CLUSTERED paused.
2025-04-17T06:26:10.778Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Creating Clustered Scheduler
2025-04-17T06:26:10.780Z INFO 8 --- [ main] org.quartz.impl.StdSchedulerFactory : Using default implementation for ThreadExecutor
2025-04-17T06:26:10.790Z INFO 8 --- [ main] org.quartz.core.SchedulerSignalerImpl : Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
2025-04-17T06:26:10.790Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : Quartz Scheduler v.2.3.2 created.
2025-04-17T06:26:10.791Z INFO 8 --- [ main] org.quartz.simpl.RAMJobStore : RAMJobStore initialized.
2025-04-17T06:26:10.791Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : Scheduler meta-data: Quartz Scheduler (v2.3.2) 'hapi-fhir-jpa-scheduler' with instanceId 'NON_CLUSTERED'
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 4 threads.
Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
2025-04-17T06:26:10.791Z INFO 8 --- [ main] org.quartz.impl.StdSchedulerFactory : Quartz scheduler 'hapi-fhir-jpa-scheduler' initialized from an externally provided properties instance.
2025-04-17T06:26:10.791Z INFO 8 --- [ main] org.quartz.impl.StdSchedulerFactory : Quartz scheduler version: 2.3.2
2025-04-17T06:26:10.792Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : JobFactory set to: ca.uhn.fhir.jpa.sched.AutowiringSpringBeanJobFactory@493c19fd
2025-04-17T06:26:10.792Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : Scheduler hapi-fhir-jpa-scheduler_$_NON_CLUSTERED paused.
2025-04-17T06:26:11.644Z INFO 8 --- [ main] c.u.f.log.terminology_troubleshooting : Performing initial retrieval for non-expiring cache: org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@2604b187
2025-04-17T06:26:11.645Z INFO 8 --- [ main] c.u.f.log.terminology_troubleshooting : Initial retrieval for non-expiring cache org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@2604b187 succeeded in 1ms
2025-04-17T06:26:11.658Z INFO 8 --- [ main] c.u.f.log.terminology_troubleshooting : Performing initial retrieval for non-expiring cache: org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@2604b187
2025-04-17T06:26:11.661Z INFO 8 --- [ main] c.u.f.log.terminology_troubleshooting : Initial retrieval for non-expiring cache org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@2604b187 succeeded in 3ms
2025-04-17T06:26:11.663Z INFO 8 --- [ main] c.u.f.log.terminology_troubleshooting : Performing initial retrieval for non-expiring cache: org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@2604b187
2025-04-17T06:26:11.664Z INFO 8 --- [ main] c.u.f.log.terminology_troubleshooting : Initial retrieval for non-expiring cache org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@2604b187 succeeded in 1ms
2025-04-17T06:26:11.665Z INFO 8 --- [ main] c.u.f.log.terminology_troubleshooting : Performing initial retrieval for non-expiring cache: org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@2604b187
2025-04-17T06:26:11.666Z INFO 8 --- [ main] c.u.f.log.terminology_troubleshooting : Initial retrieval for non-expiring cache org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@2604b187 succeeded in 1ms
2025-04-17T06:26:11.668Z INFO 8 --- [ main] c.u.f.log.terminology_troubleshooting : Performing initial retrieval for non-expiring cache: org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@2604b187
2025-04-17T06:26:11.669Z INFO 8 --- [ main] c.u.f.log.terminology_troubleshooting : Initial retrieval for non-expiring cache org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@2604b187 succeeded in 1ms
2025-04-17T06:26:11.671Z INFO 8 --- [ main] c.u.f.log.terminology_troubleshooting : Performing initial retrieval for non-expiring cache: org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@2604b187
2025-04-17T06:26:11.672Z INFO 8 --- [ main] c.u.f.log.terminology_troubleshooting : Initial retrieval for non-expiring cache org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@2604b187 succeeded in 1ms
2025-04-17T06:26:11.820Z INFO 8 --- [ main] ca.uhn.fhir.context.FhirContext : Creating new FHIR context for FHIR version [R4]
2025-04-17T06:26:12.275Z INFO 8 --- [ main] c.u.f.j.starter.common.StarterJpaConfig : CORS is enabled on this server
2025-04-17T06:26:12.280Z INFO 8 --- [ main] c.u.f.j.starter.common.StarterJpaConfig : CORS allows the following origins: *
2025-04-17T06:26:12.330Z INFO 8 --- [ main] ca.uhn.fhir.context.FhirContext : Creating new FHIR context for FHIR version [R5]
2025-04-17T06:26:20.902Z INFO 8 --- [ main] ca.uhn.fhir.context.FhirContext : Creating new FHIR context for FHIR version [R4]
2025-04-17T06:26:22.510Z WARN 8 --- [ main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2025-04-17T06:26:23.228Z INFO 8 --- [ main] .s.i.SubscriptionSubmitInterceptorLoader : Subscriptions are disabled on this server. Subscriptions will not be activated and incoming resources will not be matched against subscriptions.
2025-04-17T06:26:24.173Z INFO 8 --- [ main] org.quartz.impl.StdSchedulerFactory : Using default implementation for ThreadExecutor
2025-04-17T06:26:24.190Z INFO 8 --- [ main] org.quartz.core.SchedulerSignalerImpl : Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
2025-04-17T06:26:24.190Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : Quartz Scheduler v.2.3.2 created.
2025-04-17T06:26:24.190Z INFO 8 --- [ main] org.quartz.simpl.RAMJobStore : RAMJobStore initialized.
2025-04-17T06:26:24.190Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : Scheduler meta-data: Quartz Scheduler (v2.3.2) 'quartzScheduler' with instanceId 'NON_CLUSTERED'
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
2025-04-17T06:26:24.190Z INFO 8 --- [ main] org.quartz.impl.StdSchedulerFactory : Quartz scheduler 'quartzScheduler' initialized from an externally provided properties instance.
2025-04-17T06:26:24.190Z INFO 8 --- [ main] org.quartz.impl.StdSchedulerFactory : Quartz scheduler version: 2.3.2
2025-04-17T06:26:24.190Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : JobFactory set to: org.springframework.scheduling.quartz.SpringBeanJobFactory@3d31c83e
2025-04-17T06:26:24.251Z INFO 8 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 1 endpoint beneath base path '/actuator'
2025-04-17T06:26:24.502Z INFO 8 --- [ main] o.s.s.quartz.SchedulerFactoryBean : Starting Quartz Scheduler now
2025-04-17T06:26:24.503Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : Scheduler quartzScheduler_$_NON_CLUSTERED started.
2025-04-17T06:26:24.509Z INFO 8 --- [ main] ca.uhn.fhir.log.batch_troubleshooting : Registering Batch2 Job Definition: BULK_IMPORT_PULL / 1
2025-04-17T06:26:24.510Z INFO 8 --- [ main] ca.uhn.fhir.log.batch_troubleshooting : Registering Batch2 Job Definition: REINDEX / 1
2025-04-17T06:26:24.510Z INFO 8 --- [ main] ca.uhn.fhir.log.batch_troubleshooting : Registering Batch2 Job Definition: REINDEX / 2
2025-04-17T06:26:24.510Z INFO 8 --- [ main] ca.uhn.fhir.log.batch_troubleshooting : Registering Batch2 Job Definition: DELETE_EXPUNGE / 1
2025-04-17T06:26:24.510Z INFO 8 --- [ main] ca.uhn.fhir.log.batch_troubleshooting : Registering Batch2 Job Definition: BULK_EXPORT / 1
2025-04-17T06:26:24.510Z INFO 8 --- [ main] ca.uhn.fhir.log.batch_troubleshooting : Registering Batch2 Job Definition: BULK_EXPORT / 2
2025-04-17T06:26:24.510Z INFO 8 --- [ main] ca.uhn.fhir.log.batch_troubleshooting : Registering Batch2 Job Definition: termCodeSystemVersionDeleteJob / 1
2025-04-17T06:26:24.510Z INFO 8 --- [ main] ca.uhn.fhir.log.batch_troubleshooting : Registering Batch2 Job Definition: termCodeSystemDeleteJob / 1
2025-04-17T06:26:24.510Z INFO 8 --- [ main] ca.uhn.fhir.log.batch_troubleshooting : Registering Batch2 Job Definition: bulkImportJob / 1
2025-04-17T06:26:24.512Z INFO 8 --- [ main] .j.s.m.m.s.MatchingQueueSubscriberLoader : Subscription Matching Subscriber subscribed to Matching Channel ca.uhn.fhir.jpa.subscription.channel.subscription.BroadcastingSubscribableChannelWrapper with name subscription-matching
2025-04-17T06:26:25.538Z INFO 8 --- [ main] c.u.f.j.s.registry.JpaSearchParamCache : Have 0 unique search params
2025-04-17T06:26:25.611Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling 15 jobs in application
2025-04-17T06:26:25.614Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling local job ca.uhn.fhir.jpa.term.TermDeferredStorageSvcImpl$Job with interval 5000ms
2025-04-17T06:26:25.623Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling local job ca.uhn.fhir.jpa.cache.ResourceChangeListenerCacheRefresherImpl with interval 00:00:10.000
2025-04-17T06:26:25.625Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling clustered job ca.uhn.fhir.jpa.search.reindex.ResourceReindexingSvcImpl with interval 00:00:10.000
2025-04-17T06:26:25.627Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling clustered job ca.uhn.fhir.jpa.bulk.export.svc.BulkDataExportJobSchedulingHelperImpl$PurgeExpiredFilesJob with interval 01:00:00
2025-04-17T06:26:25.630Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling local job ca.uhn.fhir.jpa.bulk.imprt.svc.BulkDataImportSvcImpl$ActivationJob with interval 00:00:10.000
2025-04-17T06:26:25.632Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling clustered job ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl with interval 00:00:10.000
2025-04-17T06:26:25.633Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling clustered job ca.uhn.fhir.jpa.term.TermReadSvcImpl with interval 00:10:00
2025-04-17T06:26:25.634Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling local job ca.uhn.fhir.jpa.term.TermReindexingSvcImpl with interval 00:01:00.000
2025-04-17T06:26:25.636Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling clustered job ca.uhn.fhir.jpa.search.SearchUrlJobMaintenanceSvcImpl$SearchUrlMaintenanceJob with interval 00:10:00
2025-04-17T06:26:25.638Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling clustered job ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl with interval 00:01:00.000
2025-04-17T06:26:25.639Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling local job ca.uhn.fhir.jpa.util.ResourceCountCache with interval 00:10:00
2025-04-17T06:26:25.640Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling local job ca.uhn.fhir.jpa.subscription.triggering.SubscriptionTriggeringSvcImpl with interval 5000ms
2025-04-17T06:26:25.642Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling clustered job ca.uhn.fhir.jpa.subscription.async.AsyncResourceModifiedProcessingSchedulerSvc with interval 5000ms
2025-04-17T06:26:25.642Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling clustered job ca.uhn.fhir.batch2.coordinator.ReductionStepExecutorServiceImpl$ReductionStepExecutorScheduledJob with interval 00:00:10.000
2025-04-17T06:26:25.642Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling clustered job ca.uhn.fhir.batch2.maintenance.JobMaintenanceServiceImpl$JobMaintenanceScheduledJob with interval 00:01:00.000
2025-04-17T06:26:25.643Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Starting task schedulers for context application
2025-04-17T06:26:25.643Z INFO 8 --- [ main] c.uhn.fhir.jpa.sched.BaseHapiScheduler : Starting scheduler hapi-fhir-jpa-scheduler-local
2025-04-17T06:26:25.643Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : Scheduler hapi-fhir-jpa-scheduler_$_NON_CLUSTERED started.
2025-04-17T06:26:25.643Z INFO 8 --- [ main] c.uhn.fhir.jpa.sched.BaseHapiScheduler : Starting scheduler hapi-fhir-jpa-scheduler-clustered
2025-04-17T06:26:25.644Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : Scheduler hapi-fhir-jpa-scheduler_$_NON_CLUSTERED started.
2025-04-17T06:26:25.644Z WARN 8 --- [ main] u.f.j.u.PartitionedIdModeVerificationSvc : Dialect is not a HAPI FHIR dialect: org.hibernate.dialect.H2Dialect, version: 2.3.232
2025-04-17T06:26:25.648Z INFO 8 --- [ main] ca.uhn.fhir.jpa.starter.Application : Started Application in 45.501 seconds (process running for 67.034)
2025-04-17T06:26:25.684Z INFO 8 --- [ler-clustered-2] .s.BulkDataExportJobSchedulingHelperImpl : Finished bulk export job deletion with nothing to do
2025-04-17T06:26:25.767Z INFO 8 --- [ main] ca.uhn.fhir.rest.server.RestfulServer : Initializing HAPI FHIR restful server running in R4 mode
2025-04-17T06:26:25.769Z INFO 8 --- [ main] ca.uhn.fhir.rest.server.RestfulServer : Added 147 resource provider(s). Total 147
2025-04-17T06:26:26.644Z INFO 8 --- [ main] ca.uhn.fhir.rest.server.RestfulServer : Added 5 plain provider(s). Total 5
2025-04-17T06:26:26.655Z INFO 8 --- [ main] ca.uhn.fhir.rest.server.RestfulServer : Removing RESTful methods for: class ca.uhn.fhir.jpa.provider.JpaCapabilityStatementProvider
2025-04-17T06:26:26.656Z INFO 8 --- [ main] ca.uhn.fhir.rest.server.RestfulServer : OperationDefinition binding of ca.uhn.fhir.rest.server.method.ReadMethodBinding@7c3ae39a was removed
2025-04-17T06:26:26.656Z INFO 8 --- [ main] ca.uhn.fhir.rest.server.RestfulServer : OperationDefinition binding of ca.uhn.fhir.rest.server.method.ReadMethodBinding@7594beb2 was removed
2025-04-17T06:26:26.721Z INFO 8 --- [ main] ca.uhn.fhir.rest.server.RestfulServer : A FHIR has been lit on this server
2025-04-17T07:05:48.691Z INFO 8 --- [ Thread-5] org.quartz.core.QuartzScheduler : Scheduler quartzScheduler_$_NON_CLUSTERED paused.
2025-04-17T07:05:48.694Z INFO 8 --- [ Thread-5] o.s.s.quartz.SchedulerFactoryBean : Shutting down Quartz Scheduler
2025-04-17T07:05:48.694Z INFO 8 --- [ Thread-5] org.quartz.core.QuartzScheduler : Scheduler quartzScheduler_$_NON_CLUSTERED shutting down.
2025-04-17T07:05:48.694Z INFO 8 --- [ Thread-5] org.quartz.core.QuartzScheduler : Scheduler quartzScheduler_$_NON_CLUSTERED paused.
2025-04-17T07:05:48.694Z INFO 8 --- [ Thread-5] org.quartz.core.QuartzScheduler : Scheduler quartzScheduler_$_NON_CLUSTERED shutdown complete.
2025-04-17T07:05:48.712Z INFO 8 --- [ Thread-5] .j.s.m.m.s.MatchingQueueSubscriberLoader : Destroying matching Channel ca.uhn.fhir.jpa.subscription.channel.subscription.BroadcastingSubscribableChannelWrapper with name subscription-matching
2025-04-17T07:05:48.735Z INFO 8 --- [ Thread-5] c.u.f.log.terminology_troubleshooting : Performing initial retrieval for non-expiring cache: org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@2604b187
2025-04-17T07:05:48.840Z INFO 8 --- [ Thread-5] c.u.f.log.terminology_troubleshooting : Initial retrieval for non-expiring cache org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@2604b187 succeeded in 105ms
2025-04-17T07:05:48.920Z INFO 8 --- [ Thread-5] c.u.f.j.sched.BaseSchedulerServiceImpl : Shutting down task scheduler...
2025-04-17T07:05:48.920Z INFO 8 --- [ Thread-5] org.quartz.core.QuartzScheduler : Scheduler hapi-fhir-jpa-scheduler_$_NON_CLUSTERED shutting down.
2025-04-17T07:05:48.920Z INFO 8 --- [ Thread-5] org.quartz.core.QuartzScheduler : Scheduler hapi-fhir-jpa-scheduler_$_NON_CLUSTERED paused.
2025-04-17T07:05:49.143Z INFO 8 --- [ Thread-5] org.quartz.core.QuartzScheduler : Scheduler hapi-fhir-jpa-scheduler_$_NON_CLUSTERED shutdown complete.
2025-04-17T07:05:49.143Z INFO 8 --- [ Thread-5] org.quartz.core.QuartzScheduler : Scheduler hapi-fhir-jpa-scheduler_$_NON_CLUSTERED shutting down.
2025-04-17T07:05:49.143Z INFO 8 --- [ Thread-5] org.quartz.core.QuartzScheduler : Scheduler hapi-fhir-jpa-scheduler_$_NON_CLUSTERED paused.
2025-04-17T07:05:49.643Z INFO 8 --- [ Thread-5] org.quartz.core.QuartzScheduler : Scheduler hapi-fhir-jpa-scheduler_$_NON_CLUSTERED shutdown complete.
2025-04-17T07:05:49.664Z INFO 8 --- [ Thread-5] irLocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'HAPI_PU'
2025-04-17T07:05:49.679Z INFO 8 --- [ Thread-5] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2025-04-17T07:05:49.694Z INFO 8 --- [ Thread-5] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.3.5)
2025-04-17T07:08:24.261Z INFO 8 --- [ main] ca.uhn.fhir.jpa.starter.Application : Starting Application using Java 17.0.14 with PID 8 (/usr/local/tomcat/webapps/ROOT/WEB-INF/classes started by root in /usr/local/tomcat)
2025-04-17T07:08:24.266Z INFO 8 --- [ main] ca.uhn.fhir.jpa.starter.Application : No active profile set, falling back to 1 default profile: "default"
2025-04-17T07:08:26.438Z INFO 8 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2025-04-17T07:08:26.907Z INFO 8 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 446 ms. Found 52 JPA repository interfaces.
2025-04-17T07:08:30.475Z WARN 8 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'ca.uhn.fhir.jpa.config.BeanPostProcessorConfig' of type [ca.uhn.fhir.jpa.config.BeanPostProcessorConfig$$SpringCGLIB$$0] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). The currently created BeanPostProcessor [persistenceExceptionTranslationPostProcessor] is declared through a non-static factory method on that class; consider declaring it as static instead.
2025-04-17T07:08:30.845Z INFO 8 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 6539 ms
2025-04-17T07:08:31.211Z INFO 8 --- [ main] ca.uhn.fhir.util.VersionUtil : HAPI FHIR version 8.0.0 - Rev 091beb6d18
2025-04-17T07:08:31.228Z INFO 8 --- [ main] ca.uhn.fhir.context.FhirContext : Creating new FHIR context for FHIR version [R4]
2025-04-17T07:08:31.384Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to allow contains searches
2025-04-17T07:08:31.384Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to deny multiple deletes
2025-04-17T07:08:31.384Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to deny external references
2025-04-17T07:08:31.384Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to enable DAO scheduling
2025-04-17T07:08:31.384Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to disable delete expunges
2025-04-17T07:08:31.384Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to enable expunges
2025-04-17T07:08:31.384Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to allow overriding default search params
2025-04-17T07:08:31.385Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to disable auto-creating placeholder references
2025-04-17T07:08:31.385Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to auto-version references at paths []
2025-04-17T07:08:31.385Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to enable value set pre-expansion
2025-04-17T07:08:31.385Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to enable value set pre-expansion task
2025-04-17T07:08:31.386Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured for pre-expand value set default count of 1000
2025-04-17T07:08:31.386Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured for pre-expand value set max count of 1000
2025-04-17T07:08:31.387Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured for maximum expansion size of 1000
2025-04-17T07:08:31.440Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to have a maximum fetch size of 'unlimited'
2025-04-17T07:08:31.440Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to cache search results for 60000 milliseconds
2025-04-17T07:08:31.441Z INFO 8 --- [ main] c.u.f.j.s.common.FhirServerConfigCommon : Server configured to use 'ALPHANUMERIC' Client ID Strategy
2025-04-17T07:08:31.669Z INFO 8 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: HAPI_PU]
2025-04-17T07:08:31.967Z INFO 8 --- [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 6.6.4.Final
2025-04-17T07:08:32.007Z INFO 8 --- [ main] .f.j.l.FilteringSqlLoggerImplContributor : Adding service: SqlStatementFilteringLogger
2025-04-17T07:08:32.136Z INFO 8 --- [ main] o.h.c.internal.RegionFactoryInitiator : HHH000026: Second-level cache disabled
2025-04-17T07:08:32.571Z INFO 8 --- [ main] o.h.e.boot.internal.EnversServiceImpl : Envers integration enabled? : true
2025-04-17T07:08:33.153Z INFO 8 --- [ main] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer
2025-04-17T07:08:33.239Z INFO 8 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2025-04-17T07:08:34.098Z INFO 8 --- [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection conn1: url=jdbc:h2:file:/app/h2-data/fhir user=SA
2025-04-17T07:08:34.102Z INFO 8 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2025-04-17T07:08:34.192Z INFO 8 --- [ main] org.hibernate.orm.connections.pooling : HHH10001005: Database info:
Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)']
Database driver: undefined/unknown
Database version: 2.3.232
Autocommit mode: undefined/unknown
Isolation level: undefined/unknown
Minimum pool size: undefined/unknown
Maximum pool size: undefined/unknown
2025-04-17T07:08:35.793Z INFO 8 --- [ main] b.i.HibernateSearchPreIntegrationService : HSEARCH000034: Hibernate Search version 7.2.1.Final
2025-04-17T07:08:35.986Z INFO 8 --- [ main] o.h.e.c.i.m.AuditMetadataGenerator : Adding properties for entity: ca.uhn.fhir.jpa.entity.MdmLink
2025-04-17T07:08:40.053Z INFO 8 --- [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
2025-04-17T07:08:40.874Z INFO 8 --- [ main] irLocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'HAPI_PU'
2025-04-17T07:08:42.403Z INFO 8 --- [ main] .u.f.c.s.DefaultProfileValidationSupport : Loading structure definitions from classpath: /org/hl7/fhir/r4/model/profile/profiles-resources.xml
2025-04-17T07:08:42.428Z INFO 8 --- [ main] ca.uhn.fhir.util.XmlUtil : FHIR XML procesing will use StAX implementation 'Woodstox' version '6.4.0'
2025-04-17T07:08:43.725Z INFO 8 --- [ main] .u.f.c.s.DefaultProfileValidationSupport : Loading structure definitions from classpath: /org/hl7/fhir/r4/model/profile/profiles-types.xml
2025-04-17T07:08:43.859Z INFO 8 --- [ main] .u.f.c.s.DefaultProfileValidationSupport : Loading structure definitions from classpath: /org/hl7/fhir/r4/model/profile/profiles-others.xml
2025-04-17T07:08:44.102Z INFO 8 --- [ main] .u.f.c.s.DefaultProfileValidationSupport : Loading structure definitions from classpath: /org/hl7/fhir/r4/model/extension/extension-definitions.xml
2025-04-17T07:08:45.368Z INFO 8 --- [ main] o.s.d.j.r.query.QueryEnhancerFactory : Hibernate is in classpath; If applicable, HQL parser will be used.
2025-04-17T07:08:51.064Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Creating Local Scheduler
2025-04-17T07:08:51.147Z INFO 8 --- [ main] org.quartz.impl.StdSchedulerFactory : Using default implementation for ThreadExecutor
2025-04-17T07:08:51.181Z INFO 8 --- [ main] org.quartz.core.SchedulerSignalerImpl : Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
2025-04-17T07:08:51.182Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : Quartz Scheduler v.2.3.2 created.
2025-04-17T07:08:51.183Z INFO 8 --- [ main] org.quartz.simpl.RAMJobStore : RAMJobStore initialized.
2025-04-17T07:08:51.186Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : Scheduler meta-data: Quartz Scheduler (v2.3.2) 'hapi-fhir-jpa-scheduler' with instanceId 'NON_CLUSTERED'
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 4 threads.
Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
2025-04-17T07:08:51.187Z INFO 8 --- [ main] org.quartz.impl.StdSchedulerFactory : Quartz scheduler 'hapi-fhir-jpa-scheduler' initialized from an externally provided properties instance.
2025-04-17T07:08:51.187Z INFO 8 --- [ main] org.quartz.impl.StdSchedulerFactory : Quartz scheduler version: 2.3.2
2025-04-17T07:08:51.187Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : JobFactory set to: ca.uhn.fhir.jpa.sched.AutowiringSpringBeanJobFactory@28e7a4df
2025-04-17T07:08:51.187Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : Scheduler hapi-fhir-jpa-scheduler_$_NON_CLUSTERED paused.
2025-04-17T07:08:51.188Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Creating Clustered Scheduler
2025-04-17T07:08:51.190Z INFO 8 --- [ main] org.quartz.impl.StdSchedulerFactory : Using default implementation for ThreadExecutor
2025-04-17T07:08:51.198Z INFO 8 --- [ main] org.quartz.core.SchedulerSignalerImpl : Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
2025-04-17T07:08:51.198Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : Quartz Scheduler v.2.3.2 created.
2025-04-17T07:08:51.199Z INFO 8 --- [ main] org.quartz.simpl.RAMJobStore : RAMJobStore initialized.
2025-04-17T07:08:51.199Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : Scheduler meta-data: Quartz Scheduler (v2.3.2) 'hapi-fhir-jpa-scheduler' with instanceId 'NON_CLUSTERED'
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 4 threads.
Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
2025-04-17T07:08:51.199Z INFO 8 --- [ main] org.quartz.impl.StdSchedulerFactory : Quartz scheduler 'hapi-fhir-jpa-scheduler' initialized from an externally provided properties instance.
2025-04-17T07:08:51.199Z INFO 8 --- [ main] org.quartz.impl.StdSchedulerFactory : Quartz scheduler version: 2.3.2
2025-04-17T07:08:51.199Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : JobFactory set to: ca.uhn.fhir.jpa.sched.AutowiringSpringBeanJobFactory@28e7a4df
2025-04-17T07:08:51.199Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : Scheduler hapi-fhir-jpa-scheduler_$_NON_CLUSTERED paused.
2025-04-17T07:08:51.912Z INFO 8 --- [ main] c.u.f.log.terminology_troubleshooting : Performing initial retrieval for non-expiring cache: org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@823fb59
2025-04-17T07:08:51.914Z INFO 8 --- [ main] c.u.f.log.terminology_troubleshooting : Initial retrieval for non-expiring cache org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@823fb59 succeeded in 2ms
2025-04-17T07:08:51.927Z INFO 8 --- [ main] c.u.f.log.terminology_troubleshooting : Performing initial retrieval for non-expiring cache: org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@823fb59
2025-04-17T07:08:51.928Z INFO 8 --- [ main] c.u.f.log.terminology_troubleshooting : Initial retrieval for non-expiring cache org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@823fb59 succeeded in 1ms
2025-04-17T07:08:51.929Z INFO 8 --- [ main] c.u.f.log.terminology_troubleshooting : Performing initial retrieval for non-expiring cache: org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@823fb59
2025-04-17T07:08:51.930Z INFO 8 --- [ main] c.u.f.log.terminology_troubleshooting : Initial retrieval for non-expiring cache org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@823fb59 succeeded in 1ms
2025-04-17T07:08:51.932Z INFO 8 --- [ main] c.u.f.log.terminology_troubleshooting : Performing initial retrieval for non-expiring cache: org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@823fb59
2025-04-17T07:08:51.932Z INFO 8 --- [ main] c.u.f.log.terminology_troubleshooting : Initial retrieval for non-expiring cache org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@823fb59 succeeded in 0ms
2025-04-17T07:08:51.933Z INFO 8 --- [ main] c.u.f.log.terminology_troubleshooting : Performing initial retrieval for non-expiring cache: org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@823fb59
2025-04-17T07:08:51.933Z INFO 8 --- [ main] c.u.f.log.terminology_troubleshooting : Initial retrieval for non-expiring cache org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@823fb59 succeeded in 0ms
2025-04-17T07:08:51.934Z INFO 8 --- [ main] c.u.f.log.terminology_troubleshooting : Performing initial retrieval for non-expiring cache: org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@823fb59
2025-04-17T07:08:51.935Z INFO 8 --- [ main] c.u.f.log.terminology_troubleshooting : Initial retrieval for non-expiring cache org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain$FetchAllKey@823fb59 succeeded in 1ms
2025-04-17T07:08:52.063Z INFO 8 --- [ main] ca.uhn.fhir.context.FhirContext : Creating new FHIR context for FHIR version [R4]
2025-04-17T07:08:52.472Z INFO 8 --- [ main] c.u.f.j.starter.common.StarterJpaConfig : CORS is enabled on this server
2025-04-17T07:08:52.477Z INFO 8 --- [ main] c.u.f.j.starter.common.StarterJpaConfig : CORS allows the following origins: *
2025-04-17T07:08:52.528Z INFO 8 --- [ main] ca.uhn.fhir.context.FhirContext : Creating new FHIR context for FHIR version [R5]
2025-04-17T07:09:00.375Z INFO 8 --- [ main] ca.uhn.fhir.context.FhirContext : Creating new FHIR context for FHIR version [R4]
2025-04-17T07:09:03.002Z WARN 8 --- [ main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2025-04-17T07:09:03.862Z INFO 8 --- [ main] .s.i.SubscriptionSubmitInterceptorLoader : Subscriptions are disabled on this server. Subscriptions will not be activated and incoming resources will not be matched against subscriptions.
2025-04-17T07:09:04.932Z INFO 8 --- [ main] org.quartz.impl.StdSchedulerFactory : Using default implementation for ThreadExecutor
2025-04-17T07:09:04.955Z INFO 8 --- [ main] org.quartz.core.SchedulerSignalerImpl : Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
2025-04-17T07:09:04.956Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : Quartz Scheduler v.2.3.2 created.
2025-04-17T07:09:04.956Z INFO 8 --- [ main] org.quartz.simpl.RAMJobStore : RAMJobStore initialized.
2025-04-17T07:09:04.956Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : Scheduler meta-data: Quartz Scheduler (v2.3.2) 'quartzScheduler' with instanceId 'NON_CLUSTERED'
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
2025-04-17T07:09:04.956Z INFO 8 --- [ main] org.quartz.impl.StdSchedulerFactory : Quartz scheduler 'quartzScheduler' initialized from an externally provided properties instance.
2025-04-17T07:09:04.956Z INFO 8 --- [ main] org.quartz.impl.StdSchedulerFactory : Quartz scheduler version: 2.3.2
2025-04-17T07:09:04.956Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : JobFactory set to: org.springframework.scheduling.quartz.SpringBeanJobFactory@4af6d469
2025-04-17T07:09:05.031Z INFO 8 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 1 endpoint beneath base path '/actuator'
2025-04-17T07:09:05.376Z INFO 8 --- [ main] o.s.s.quartz.SchedulerFactoryBean : Starting Quartz Scheduler now
2025-04-17T07:09:05.377Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : Scheduler quartzScheduler_$_NON_CLUSTERED started.
2025-04-17T07:09:05.385Z INFO 8 --- [ main] ca.uhn.fhir.log.batch_troubleshooting : Registering Batch2 Job Definition: BULK_IMPORT_PULL / 1
2025-04-17T07:09:05.386Z INFO 8 --- [ main] ca.uhn.fhir.log.batch_troubleshooting : Registering Batch2 Job Definition: REINDEX / 1
2025-04-17T07:09:05.387Z INFO 8 --- [ main] ca.uhn.fhir.log.batch_troubleshooting : Registering Batch2 Job Definition: REINDEX / 2
2025-04-17T07:09:05.387Z INFO 8 --- [ main] ca.uhn.fhir.log.batch_troubleshooting : Registering Batch2 Job Definition: DELETE_EXPUNGE / 1
2025-04-17T07:09:05.387Z INFO 8 --- [ main] ca.uhn.fhir.log.batch_troubleshooting : Registering Batch2 Job Definition: BULK_EXPORT / 1
2025-04-17T07:09:05.387Z INFO 8 --- [ main] ca.uhn.fhir.log.batch_troubleshooting : Registering Batch2 Job Definition: BULK_EXPORT / 2
2025-04-17T07:09:05.387Z INFO 8 --- [ main] ca.uhn.fhir.log.batch_troubleshooting : Registering Batch2 Job Definition: termCodeSystemVersionDeleteJob / 1
2025-04-17T07:09:05.387Z INFO 8 --- [ main] ca.uhn.fhir.log.batch_troubleshooting : Registering Batch2 Job Definition: termCodeSystemDeleteJob / 1
2025-04-17T07:09:05.387Z INFO 8 --- [ main] ca.uhn.fhir.log.batch_troubleshooting : Registering Batch2 Job Definition: bulkImportJob / 1
2025-04-17T07:09:05.390Z INFO 8 --- [ main] .j.s.m.m.s.MatchingQueueSubscriberLoader : Subscription Matching Subscriber subscribed to Matching Channel ca.uhn.fhir.jpa.subscription.channel.subscription.BroadcastingSubscribableChannelWrapper with name subscription-matching
2025-04-17T07:09:07.149Z INFO 8 --- [ main] c.u.f.j.s.registry.JpaSearchParamCache : Have 0 unique search params
2025-04-17T07:09:07.246Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling 15 jobs in application
2025-04-17T07:09:07.252Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling local job ca.uhn.fhir.jpa.term.TermDeferredStorageSvcImpl$Job with interval 5000ms
2025-04-17T07:09:07.264Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling local job ca.uhn.fhir.jpa.cache.ResourceChangeListenerCacheRefresherImpl with interval 00:00:10.000
2025-04-17T07:09:07.266Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling clustered job ca.uhn.fhir.jpa.search.reindex.ResourceReindexingSvcImpl with interval 00:00:10.000
2025-04-17T07:09:07.268Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling clustered job ca.uhn.fhir.jpa.bulk.export.svc.BulkDataExportJobSchedulingHelperImpl$PurgeExpiredFilesJob with interval 01:00:00
2025-04-17T07:09:07.271Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling local job ca.uhn.fhir.jpa.bulk.imprt.svc.BulkDataImportSvcImpl$ActivationJob with interval 00:00:10.000
2025-04-17T07:09:07.273Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling clustered job ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl with interval 00:00:10.000
2025-04-17T07:09:07.275Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling clustered job ca.uhn.fhir.jpa.term.TermReadSvcImpl with interval 00:10:00
2025-04-17T07:09:07.276Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling local job ca.uhn.fhir.jpa.term.TermReindexingSvcImpl with interval 00:01:00.000
2025-04-17T07:09:07.278Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling clustered job ca.uhn.fhir.jpa.search.SearchUrlJobMaintenanceSvcImpl$SearchUrlMaintenanceJob with interval 00:10:00
2025-04-17T07:09:07.280Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling clustered job ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl with interval 00:01:00.000
2025-04-17T07:09:07.282Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling local job ca.uhn.fhir.jpa.util.ResourceCountCache with interval 00:10:00
2025-04-17T07:09:07.285Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling local job ca.uhn.fhir.jpa.subscription.triggering.SubscriptionTriggeringSvcImpl with interval 5000ms
2025-04-17T07:09:07.288Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling clustered job ca.uhn.fhir.jpa.subscription.async.AsyncResourceModifiedProcessingSchedulerSvc with interval 5000ms
2025-04-17T07:09:07.289Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling clustered job ca.uhn.fhir.batch2.coordinator.ReductionStepExecutorServiceImpl$ReductionStepExecutorScheduledJob with interval 00:00:10.000
2025-04-17T07:09:07.289Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Scheduling clustered job ca.uhn.fhir.batch2.maintenance.JobMaintenanceServiceImpl$JobMaintenanceScheduledJob with interval 00:01:00.000
2025-04-17T07:09:07.289Z INFO 8 --- [ main] c.u.f.j.sched.BaseSchedulerServiceImpl : Starting task schedulers for context application
2025-04-17T07:09:07.289Z INFO 8 --- [ main] c.uhn.fhir.jpa.sched.BaseHapiScheduler : Starting scheduler hapi-fhir-jpa-scheduler-local
2025-04-17T07:09:07.290Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : Scheduler hapi-fhir-jpa-scheduler_$_NON_CLUSTERED started.
2025-04-17T07:09:07.290Z INFO 8 --- [ main] c.uhn.fhir.jpa.sched.BaseHapiScheduler : Starting scheduler hapi-fhir-jpa-scheduler-clustered
2025-04-17T07:09:07.290Z INFO 8 --- [ main] org.quartz.core.QuartzScheduler : Scheduler hapi-fhir-jpa-scheduler_$_NON_CLUSTERED started.
2025-04-17T07:09:07.290Z WARN 8 --- [ main] u.f.j.u.PartitionedIdModeVerificationSvc : Dialect is not a HAPI FHIR dialect: org.hibernate.dialect.H2Dialect, version: 2.3.232
2025-04-17T07:09:07.295Z INFO 8 --- [ main] ca.uhn.fhir.jpa.starter.Application : Started Application in 44.05 seconds (process running for 60.148)
2025-04-17T07:09:07.384Z INFO 8 --- [ler-clustered-2] .s.BulkDataExportJobSchedulingHelperImpl : Finished bulk export job deletion with nothing to do
2025-04-17T07:09:07.446Z INFO 8 --- [ main] ca.uhn.fhir.rest.server.RestfulServer : Initializing HAPI FHIR restful server running in R4 mode
2025-04-17T07:09:07.448Z INFO 8 --- [ main] ca.uhn.fhir.rest.server.RestfulServer : Added 147 resource provider(s). Total 147
2025-04-17T07:09:08.404Z INFO 8 --- [ main] ca.uhn.fhir.rest.server.RestfulServer : Added 5 plain provider(s). Total 5
2025-04-17T07:09:08.422Z INFO 8 --- [ main] ca.uhn.fhir.rest.server.RestfulServer : Removing RESTful methods for: class ca.uhn.fhir.jpa.provider.JpaCapabilityStatementProvider
2025-04-17T07:09:08.423Z INFO 8 --- [ main] ca.uhn.fhir.rest.server.RestfulServer : OperationDefinition binding of ca.uhn.fhir.rest.server.method.ReadMethodBinding@711dda46 was removed
2025-04-17T07:09:08.423Z INFO 8 --- [ main] ca.uhn.fhir.rest.server.RestfulServer : OperationDefinition binding of ca.uhn.fhir.rest.server.method.ReadMethodBinding@231712f was removed
2025-04-17T07:09:08.489Z INFO 8 --- [ main] ca.uhn.fhir.rest.server.RestfulServer : A FHIR has been lit on this server

83
logs/tomcat_err.log Normal file
View File

@ -0,0 +1,83 @@
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

View File

@ -8,6 +8,9 @@ from flask import current_app, Blueprint, request, jsonify
from collections import defaultdict
from pathlib import Path
import datetime
import subprocess
import tempfile
import xml.etree.ElementTree as ET
# Define Blueprint
services_bp = Blueprint('services', __name__)
@ -1438,3 +1441,72 @@ if __name__ == '__main__':
else:
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]
if fhir_version:
cmd.extend(["-u", fhir_version])
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
check=True
)
# Read all FSH files from the 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())
logger.info(f"GoFSH executed successfully for {input_path}")
return "\n\n".join(fsh_content), None
except subprocess.CalledProcessError as e:
logger.error(f"GoFSH failed: {e.stderr}")
return None, f"GoFSH failed: {e.stderr}"
except Exception as e:
logger.error(f"Error running GoFSH: {str(e)}", exc_info=True)
return None, f"Error running GoFSH: {str(e)}"
def process_fhir_input(input_mode, fhir_file, fhir_text):
"""Process user input (file or text) and save to a temporary file."""
temp_dir = tempfile.mkdtemp()
temp_file = 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:
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:
f.write(content)
else:
return None, None, "No input provided"
# Basic validation
if file_type == 'json':
try:
json.loads(content)
except json.JSONDecodeError:
return None, None, "Invalid JSON format"
elif file_type == 'xml':
try:
ET.fromstring(content)
except ET.ParseError:
return None, None, "Invalid XML format"
logger.debug(f"Processed input: {temp_file}")
return temp_file, temp_dir, 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)}"

View File

@ -0,0 +1,5 @@
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,2 @@
Name Type File
banks-mia-leanne Instance instances/banks-mia-leanne.fsh

View File

@ -0,0 +1,49 @@
Instance: banks-mia-leanne
InstanceOf: Patient
Usage: #example
* meta.profile = "http://hl7.org.au/fhir/core/StructureDefinition/au-core-patient"
* extension[0].url = "http://hl7.org/fhir/StructureDefinition/individual-genderIdentity"
* extension[=].extension.url = "value"
* extension[=].extension.valueCodeableConcept = $sct#446141000124107 "Identifies as female gender"
* extension[+].url = "http://hl7.org/fhir/StructureDefinition/individual-pronouns"
* extension[=].extension.url = "value"
* extension[=].extension.valueCodeableConcept = $loinc#LA29519-8 "she/her/her/hers/herself"
* extension[+].url = "http://hl7.org/fhir/StructureDefinition/individual-recordedSexOrGender"
* extension[=].extension[0].url = "value"
* extension[=].extension[=].valueCodeableConcept = $sct#248152002
* extension[=].extension[=].valueCodeableConcept.text = "Female"
* extension[=].extension[+].url = "type"
* extension[=].extension[=].valueCodeableConcept = $sct#1515311000168102 "Biological sex at birth"
* identifier.extension[0].url = "http://hl7.org.au/fhir/StructureDefinition/ihi-status"
* identifier.extension[=].valueCoding = $ihi-status-1#active
* identifier.extension[+].url = "http://hl7.org.au/fhir/StructureDefinition/ihi-record-status"
* identifier.extension[=].valueCoding = $ihi-record-status-1#verified "verified"
* identifier.type = $v2-0203#NI
* identifier.type.text = "IHI"
* identifier.system = "http://ns.electronichealth.net.au/id/hi/ihi/1.0"
* identifier.value = "8003608333647261"
* name.use = #usual
* name.family = "Banks"
* name.given[0] = "Mia"
* name.given[+] = "Leanne"
* telecom[0].system = #phone
* telecom[=].value = "0270102724"
* telecom[=].use = #work
* telecom[+].system = #phone
* telecom[=].value = "0491574632"
* telecom[=].use = #mobile
* telecom[+].system = #phone
* telecom[=].value = "0270107520"
* telecom[=].use = #home
* telecom[+].system = #email
* telecom[=].value = "mia.banks@myownpersonaldomain.com"
* telecom[+].system = #phone
* telecom[=].value = "270107520"
* telecom[=].use = #home
* gender = #female
* birthDate = "1983-08-25"
* address.line = "50 Sebastien St"
* address.city = "Minjary"
* address.state = "NSW"
* address.postalCode = "2720"
* address.country = "AU"

View File

@ -0,0 +1,6 @@
canonical: http://example.org
fhirVersion: 4.0.1
FSHOnly: true
applyExtensionMetadataToRoot: false
id: example
name: Example

55
static/uploads/output.fsh Normal file
View File

@ -0,0 +1,55 @@
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
Instance: banks-mia-leanne
InstanceOf: Patient
Usage: #example
* meta.profile = "http://hl7.org.au/fhir/core/StructureDefinition/au-core-patient"
* extension[0].url = "http://hl7.org/fhir/StructureDefinition/individual-genderIdentity"
* extension[=].extension.url = "value"
* extension[=].extension.valueCodeableConcept = $sct#446141000124107 "Identifies as female gender"
* extension[+].url = "http://hl7.org/fhir/StructureDefinition/individual-pronouns"
* extension[=].extension.url = "value"
* extension[=].extension.valueCodeableConcept = $loinc#LA29519-8 "she/her/her/hers/herself"
* extension[+].url = "http://hl7.org/fhir/StructureDefinition/individual-recordedSexOrGender"
* extension[=].extension[0].url = "value"
* extension[=].extension[=].valueCodeableConcept = $sct#248152002
* extension[=].extension[=].valueCodeableConcept.text = "Female"
* extension[=].extension[+].url = "type"
* extension[=].extension[=].valueCodeableConcept = $sct#1515311000168102 "Biological sex at birth"
* identifier.extension[0].url = "http://hl7.org.au/fhir/StructureDefinition/ihi-status"
* identifier.extension[=].valueCoding = $ihi-status-1#active
* identifier.extension[+].url = "http://hl7.org.au/fhir/StructureDefinition/ihi-record-status"
* identifier.extension[=].valueCoding = $ihi-record-status-1#verified "verified"
* identifier.type = $v2-0203#NI
* identifier.type.text = "IHI"
* identifier.system = "http://ns.electronichealth.net.au/id/hi/ihi/1.0"
* identifier.value = "8003608333647261"
* name.use = #usual
* name.family = "Banks"
* name.given[0] = "Mia"
* name.given[+] = "Leanne"
* telecom[0].system = #phone
* telecom[=].value = "0270102724"
* telecom[=].use = #work
* telecom[+].system = #phone
* telecom[=].value = "0491574632"
* telecom[=].use = #mobile
* telecom[+].system = #phone
* telecom[=].value = "0270107520"
* telecom[=].use = #home
* telecom[+].system = #email
* telecom[=].value = "mia.banks@myownpersonaldomain.com"
* telecom[+].system = #phone
* telecom[=].value = "270107520"
* telecom[=].use = #home
* gender = #female
* birthDate = "1983-08-25"
* address.line = "50 Sebastien St"
* address.city = "Minjary"
* address.state = "NSW"
* address.postalCode = "2720"
* address.country = "AU"

36
supervisord.conf Normal file
View File

@ -0,0 +1,36 @@
[supervisord]
nodaemon=true
logfile=/app/logs/supervisord.log
logfile_maxbytes=50MB
logfile_backups=10
pidfile=/app/logs/supervisord.pid
[program:flask]
command=/app/venv/bin/python /app/app.py
directory=/app
environment=FLASK_APP="app.py",FLASK_ENV="development",NODE_PATH="/usr/lib/node_modules"
autostart=true
autorestart=true
startsecs=10
stopwaitsecs=10
stdout_logfile=/app/logs/flask.log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=5
stderr_logfile=/app/logs/flask_err.log
stderr_logfile_maxbytes=10MB
stderr_logfile_backups=5
[program:tomcat]
command=/usr/local/tomcat/bin/catalina.sh run
directory=/usr/local/tomcat
environment=SPRING_CONFIG_LOCATION="file:/usr/local/tomcat/conf/application.yaml",NODE_PATH="/usr/lib/node_modules"
autostart=true
autorestart=true
startsecs=30
stopwaitsecs=30
stdout_logfile=/app/logs/tomcat.log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=5
stderr_logfile=/app/logs/tomcat_err.log
stderr_logfile_maxbytes=10MB
stderr_logfile_backups=5

View File

@ -32,6 +32,15 @@
<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>
</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>
</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>
</li>
</ul>
</div>
</div>

303
templates/fhir_ui.html Normal file
View File

@ -0,0 +1,303 @@
{% extends "base.html" %}
{% block content %}
<div class="px-4 py-5 my-5 text-center">
<img class="d-block mx-auto mb-4" src="{{ url_for('static', filename='FHIRFLARE.png') }}" alt="FHIRFLARE IG Toolkit" width="192" height="192">
<h1 class="display-5 fw-bold text-body-emphasis">FHIR API Explorer</h1>
<div class="col-lg-6 mx-auto">
<p class="lead mb-4">
Interact with FHIR servers using GET, POST, PUT, or DELETE requests. Toggle between local HAPI or a custom server to explore resources or perform searches.
</p>
<div class="d-grid gap-2 d-sm-flex justify-content-sm-center">
<a href="{{ url_for('index') }}" class="btn btn-primary btn-lg px-4 gap-3">Back to Home</a>
<a href="{{ url_for('validate_sample') }}" class="btn btn-outline-secondary btn-lg px-4">Validate FHIR Sample</a>
<a href="{{ url_for('fhir_ui_operations') }}" class="btn btn-outline-secondary btn-lg px-4">FHIR UI Operations</a>
</div>
</div>
</div>
<div class="container mt-4">
<div class="card">
<div class="card-header"><i class="bi bi-cloud-arrow-down me-2"></i>Send FHIR Request</div>
<div class="card-body">
<form id="fhirRequestForm" onsubmit="return false;">
{{ form.hidden_tag() }}
<div class="mb-3">
<label class="form-label">FHIR Server</label>
<div class="input-group">
<button type="button" class="btn btn-outline-primary" id="toggleServer">
<span id="toggleLabel">Use Local HAPI</span>
</button>
<input type="text" class="form-control" id="fhirServerUrl" name="fhir_server_url" placeholder="e.g., https://hapi.fhir.org/baseR4" style="display: none;" aria-describedby="fhirServerHelp">
</div>
<small id="fhirServerHelp" class="form-text text-muted">Toggle to use local HAPI (http://localhost:8080/fhir) or enter a custom FHIR server URL.</small>
</div>
<div class="mb-3">
<label for="fhirPath" class="form-label">FHIR Path</label>
<input type="text" class="form-control" id="fhirPath" name="fhir_path" placeholder="e.g., Patient/wang-li" required aria-describedby="fhirPathHelp">
<small id="fhirPathHelp" class="form-text text-muted">Enter a resource path (e.g., Patient, Observation/example) or '_search' for search queries.</small>
</div>
<div class="mb-3">
<label class="form-label">Request Type</label>
<div class="d-flex gap-2 flex-wrap">
<input type="radio" class="btn-check" name="method" id="get" value="GET" checked>
<label class="btn btn-outline-success" for="get"><span class="badge bg-success">GET</span></label>
<input type="radio" class="btn-check" name="method" id="post" value="POST">
<label class="btn btn-outline-primary" for="post"><span class="badge bg-primary">POST</span></label>
<input type="radio" class="btn-check" name="method" id="put" value="PUT">
<label class="btn btn-outline-warning" for="put"><span class="badge bg-warning text-dark">PUT</span></label>
<input type="radio" class="btn-check" name="method" id="delete" value="DELETE">
<label class="btn btn-outline-danger" for="delete"><span class="badge bg-danger">DELETE</span></label>
</div>
</div>
<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>
</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>
</div>
<button type="button" class="btn btn-primary" id="sendRequest">Send Request</button>
</form>
</div>
</div>
<div class="card mt-4" id="responseCard" style="display: none;">
<div class="card-header">Response</div>
<div class="card-body">
<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>
</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>
</div>
<pre id="responseBody" class="border p-2 bg-light" style="max-height: 400px; overflow-y: auto;"></pre>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('fhirRequestForm');
const sendButton = document.getElementById('sendRequest');
const fhirPath = document.getElementById('fhirPath');
const requestBody = document.getElementById('requestBody');
const requestBodyGroup = document.getElementById('requestBodyGroup');
const jsonError = document.getElementById('jsonError');
const responseCard = document.getElementById('responseCard');
const responseStatus = document.getElementById('responseStatus');
const responseHeaders = document.getElementById('responseHeaders');
const responseBody = document.getElementById('responseBody');
const methodRadios = document.getElementsByName('method');
const toggleServerButton = document.getElementById('toggleServer');
const toggleLabel = document.getElementById('toggleLabel');
const fhirServerUrl = document.getElementById('fhirServerUrl');
const copyRequestBodyButton = document.getElementById('copyRequestBody');
const copyResponseHeadersButton = document.getElementById('copyResponseHeaders');
const copyResponseBodyButton = document.getElementById('copyResponseBody');
let useLocalHapi = true;
// Show/hide request body
function updateRequestBodyVisibility() {
const selectedMethod = document.querySelector('input[name="method"]:checked').value;
requestBodyGroup.style.display = (selectedMethod === 'POST' || selectedMethod === 'PUT') ? 'block' : 'none';
if (selectedMethod !== 'POST' && selectedMethod !== 'PUT') {
requestBody.value = '';
jsonError.style.display = 'none';
}
}
// Validate request body (JSON or form-encoded)
function validateRequestBody(method, path) {
if (!requestBody.value.trim()) {
requestBody.classList.remove('is-invalid');
jsonError.style.display = 'none';
return method === 'GET' ? null : '';
}
const isSearch = path.endsWith('_search');
if (method === 'POST' && isSearch) {
return requestBody.value; // Allow form-encoded for _search
}
try {
JSON.parse(requestBody.value);
requestBody.classList.remove('is-invalid');
jsonError.style.display = 'none';
return requestBody.value;
} catch (e) {
requestBody.classList.add('is-invalid');
jsonError.textContent = `Invalid JSON: ${e.message}`;
jsonError.style.display = 'block';
return null;
}
}
// Clean FHIR path
function cleanFhirPath(path) {
const cleaned = path.replace(/^(r4\/|fhir\/)*/i, '').replace(/^\/+|\/+$/g, '');
console.log(`Cleaned path: ${path} -> ${cleaned}`);
return cleaned;
}
// Toggle server
function toggleServer() {
useLocalHapi = !useLocalHapi;
toggleLabel.textContent = useLocalHapi ? 'Use Local HAPI' : 'Use Custom URL';
fhirServerUrl.style.display = useLocalHapi ? 'none' : 'block';
fhirServerUrl.value = '';
console.log(`Server toggled: ${useLocalHapi ? 'Local HAPI' : 'Custom URL'}`);
}
// Copy to clipboard
async function copyToClipboard(text, button) {
try {
await navigator.clipboard.writeText(text);
button.innerHTML = '<i class="bi bi-check"></i>';
setTimeout(() => {
button.innerHTML = '<i class="bi bi-clipboard"></i>';
}, 2000);
} catch (err) {
console.error('Copy failed:', err);
alert('Failed to copy to clipboard');
}
}
// Bind toggle event
toggleServerButton.addEventListener('click', toggleServer);
// Bind copy events
copyRequestBodyButton.addEventListener('click', () => {
copyToClipboard(requestBody.value, copyRequestBodyButton);
});
copyResponseHeadersButton.addEventListener('click', () => {
copyToClipboard(responseHeaders.textContent, copyResponseHeadersButton);
});
copyResponseBodyButton.addEventListener('click', () => {
copyToClipboard(responseBody.textContent, copyResponseBodyButton);
});
// Update visibility on method change
methodRadios.forEach(radio => {
radio.addEventListener('change', updateRequestBodyVisibility);
});
// Validate body on input
requestBody.addEventListener('input', () => validateRequestBody(
document.querySelector('input[name="method"]:checked').value,
fhirPath.value
));
// Send request
sendButton.addEventListener('click', async function() {
if (!fhirPath.value.trim()) {
fhirPath.classList.add('is-invalid');
console.log('Path validation failed: empty');
return;
}
fhirPath.classList.remove('is-invalid');
const method = document.querySelector('input[name="method"]:checked').value;
console.log(`Sending request: Method=${method}, Input Path=${fhirPath.value}, Server=${useLocalHapi ? 'Local HAPI' : fhirServerUrl.value}`);
const data = validateRequestBody(method, fhirPath.value);
if ((method === 'POST' || method === 'PUT') && data === null) {
console.log('Body validation failed');
sendButton.disabled = false;
sendButton.textContent = 'Send Request';
return;
}
sendButton.disabled = true;
sendButton.textContent = 'Sending...';
responseCard.style.display = 'none';
const cleanPath = cleanFhirPath(fhirPath.value);
let fetchUrl, headers = {
'Accept': 'application/fhir+json'
};
if (useLocalHapi) {
fetchUrl = `/fhir/${cleanPath}`;
if (method === 'POST' || method === 'PUT') {
headers['Content-Type'] = cleanPath.endsWith('_search') ? 'application/x-www-form-urlencoded' : 'application/fhir+json';
headers['X-CSRFToken'] = '{{ form.csrf_token._value() }}';
}
} else {
if (!fhirServerUrl.value.trim()) {
fhirServerUrl.classList.add('is-invalid');
console.log('Server URL validation failed: empty');
sendButton.disabled = false;
sendButton.textContent = 'Send Request';
return;
}
fhirServerUrl.classList.remove('is-invalid');
fetchUrl = `${fhirServerUrl.value.replace(/\/+$/, '')}/${cleanPath}`;
if (method === 'POST' || method === 'PUT') {
headers['Content-Type'] = cleanPath.endsWith('_search') ? 'application/x-www-form-urlencoded' : 'application/fhir+json';
}
}
console.log('Fetch details:', {
url: fetchUrl,
method,
headers,
body: data ? data.substring(0, 100) + '...' : null
});
try {
const response = await fetch(fetchUrl, {
method: method,
headers: headers,
body: method === 'GET' ? undefined : data
});
console.log('Response status:', response.status, response.statusText);
const responseHeadersObj = {};
response.headers.forEach((value, key) => responseHeadersObj[key] = value);
const contentType = responseHeadersObj['content-type'] || '';
const text = await response.text();
console.log('Response headers:', responseHeadersObj);
console.log('Response body (first 200 chars):', text.substring(0, 200));
responseCard.style.display = 'block';
responseStatus.textContent = `${response.status} ${response.statusText}`;
responseStatus.className = `badge ${response.status < 300 ? 'bg-success' : response.status < 400 ? 'bg-info' : 'bg-danger'}`;
responseHeaders.textContent = JSON.stringify(responseHeadersObj, null, 2);
if (contentType.includes('application/fhir+json') || contentType.includes('application/json')) {
try {
responseBody.textContent = JSON.stringify(JSON.parse(text), null, 2);
} catch (e) {
console.warn('Failed to parse JSON:', e.message);
responseBody.textContent = text;
}
} else {
responseBody.textContent = text;
}
} catch (error) {
console.error('Fetch error:', error.name, error.message, error.stack);
responseCard.style.display = 'block';
responseStatus.textContent = 'Error';
responseStatus.className = 'badge bg-danger';
responseHeaders.textContent = '';
responseBody.textContent = `Error: ${error.message}`;
} finally {
sendButton.disabled = false;
sendButton.textContent = 'Send Request';
}
});
// Initialize visibility
updateRequestBodyVisibility();
});
</script>
{% endblock %}

View File

@ -0,0 +1,908 @@
{% extends "base.html" %}
{% block content %}
<style>
/* General Button Styles */
.btn-outline-dark-blue { border-color: #0a3d62; color: #0a3d62; }
.btn-outline-dark-blue:hover { background-color: #0a3d62; color: white; }
.btn-dark-blue { background-color: #0a3d62; border-color: #0a3d62; color: white; }
.btn-dark-blue.active { background-color: #083152; border-color: #083152; } /* Keep active style */
.btn-outline-success { border-color: #28a745; color: #28a745; }
.btn-outline-success:hover { background-color: #28a745; color: white; }
.btn-success { background-color: #28a745; border-color: #28a745; color: white; }
.btn-success.active { background-color: #1e7e34; border-color: #1e7e34; } /* Keep active style */
/* --- Rest of the styles remain unchanged --- */
/* Operation Block */
.opblock { margin-bottom: 10px; border: 1px solid #dee2e6; border-radius: 4px; background: #fff; }
.opblock-summary { display: flex; align-items: center; padding: 10px; cursor: pointer; background: #f8f9fa; border-bottom: 1px solid #dee2e6; }
.opblock-summary:hover { background: #e9ecef; }
.opblock-summary-method { width: 80px; text-align: center; color: white; padding: 5px; border-radius: 12px; font-size: 0.9em; margin-right: 10px; }
.opblock-summary-method.get { background: #61affe; }
.opblock-summary-method.post { background: #49cc90; }
.opblock-summary-method.put { background: #fca130; }
.opblock-summary-method.delete { background: #f93e3e; }
.opblock-summary-method.patch { background: #50e3c2; }
.opblock-summary-path { flex-grow: 1; font-family: monospace; font-size: 1em; margin-right: 10px; word-break: break-all; }
.opblock-summary-description { flex-grow: 2; font-size: 0.9em; color: #333; margin-right: 10px; }
.opblock-body { padding: 15px; display: none; border-top: 1px solid #dee2e6; }
.opblock.is-open .opblock-body { display: block; }
.opblock-section { margin-bottom: 20px; }
.opblock-section:last-child { margin-bottom: 0; }
.opblock-section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; border-bottom: 1px solid #eee; padding-bottom: 5px; }
.opblock-title { font-size: 1.1em; font-weight: bold; margin: 0; }
.arrow { width: 20px; height: 20px; transition: transform 0.2s; margin-left: auto; }
.opblock.is-open .arrow { transform: rotate(180deg); }
/* Try it out / Execute */
.try-out__btn { background-color: #007bff; color: white; border: none; padding: 5px 10px; border-radius: 4px; cursor: pointer; }
.try-out__btn.cancel { background-color: #dc3545; }
.execute__btn { background-color: #28a745; color: white; border: none; padding: 8px 15px; border-radius: 4px; cursor: pointer; margin-top: 15px; display: none; font-weight: bold; }
.execute__btn:disabled { background-color: #6c757d; cursor: not-allowed; }
/* Parameters */
.parameters-container { margin-bottom: 15px; }
.parameters-table { width: 100%; border-collapse: collapse; }
.parameters-table th, .parameters-table td { border: 1px solid #dee2e6; padding: 8px; text-align: left; vertical-align: top; }
.parameters-col_name { width: 20%; font-weight: bold; }
.parameters-col_description { width: 80%; }
.parameters-col_description input[type="text"],
.parameters-col_description input[type="number"] {
width: 100%; padding: 5px; border: 1px solid #ced4da; border-radius: 4px; box-sizing: border-box;
}
.parameters-col_description input.is-invalid { border-color: #dc3545; }
.parameter__name.required span { color: red; margin-left: 2px; font-weight: bold; }
.parameter__type { font-size: 0.9em; color: #6c757d; }
.parameter__in { font-size: 0.8em; color: #6c757d; }
.renderedMarkdown p { margin-bottom: 0.5rem; }
.renderedMarkdown p:last-child { margin-bottom: 0; }
/* Request Body */
.request-body-textarea { width: 100%; min-height: 100px; padding: 10px; border: 1px solid #ced4da; border-radius: 4px; font-family: monospace; box-sizing: border-box; margin-bottom: 10px; }
.content-type-wrapper label.visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
.content-type-wrapper select { padding: 0.3rem 0.6rem; font-size: 0.9em; border-radius: 4px; border: 1px solid #ced4da; }
/* Responses */
.responses-table { width: 100%; border-collapse: collapse; margin-top: 10px; }
.responses-table th, .responses-table td { border: 1px solid #dee2e6; padding: 8px; text-align: left; }
.response-col_status { width: 100px; font-weight: bold; }
.response-control-media-type { margin: 10px 0; padding: 5px; background-color: #f8f9fa; border-radius: 4px; }
.response-control-media-type__title { font-weight: bold; margin-right: 5px; }
.response-control-media-type__accept-message { font-size: 0.85em; color: #6c757d; margin-left: 10px; }
.model-example { margin-top: 10px; }
.tab { display: flex; gap: 5px; margin-bottom: 10px; }
.tablinks.badge { background: #dee2e6; color: #333; padding: 5px 10px; border-radius: 12px; cursor: pointer; font-size: 0.9em; border: none; }
.tablinks.badge.active { background: #0a3d62; color: white; }
.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; }
.highlight-code pre { margin: 0; padding: 0; background: transparent; border: none; font-family: inherit; font-size: inherit; color: inherit; }
/* Execution Response */
.execute-wrapper { margin-top: 20px; padding-top: 15px; border-top: 1px dashed #ccc; }
.execute-wrapper .opblock-section-header { border-bottom: none; margin-bottom: 5px; padding-bottom: 0; }
.execute-wrapper .response-format-select { padding: 0.3rem 0.6rem; font-size: 0.9em; border-radius: 4px; border: 1px solid #ced4da; }
.execute-wrapper pre.response-output-content { background: #f8f9fa; color: #212529; border: 1px solid #dee2e6; padding: 10px; border-radius: 4px; max-height: 400px; overflow: auto; font-family: monospace; white-space: pre-wrap; word-break: break-all; margin-top: 5px; }
.execute-wrapper div.response-output-narrative { display: none; border: 1px solid #dee2e6; padding: 15px; border-radius: 4px; margin-top: 5px; max-height: 400px; overflow: auto; background: #ffffff; color: #212529; line-height: 1.5; }
.execute-wrapper .response-status { margin-top: 8px; font-weight: bold; }
/* Request URL, Curl, Controls */
.request-url-section, .curl-section { margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #eee; }
.request-url-section:last-of-type, .curl-section:last-of-type { border-bottom: none; }
.request-url-output, .curl-output { background: #e9ecef; color: #212529; padding: 10px 15px; border: 1px solid #dee2e6; border-radius: 4px; font-family: monospace; font-size: 0.9em; white-space: pre-wrap; word-break: break-all; margin-top: 5px; }
.request-url-output code, .curl-output code { font-family: inherit; }
.curl-section .opblock-section-header { display: flex; justify-content: space-between; align-items: center; border-bottom: none; padding-bottom: 0; margin-bottom: 5px; }
.copy-curl-btn, .copy-response-btn, .download-response-btn { padding: 0.25rem 0.5rem; font-size: 0.8em; line-height: 1.5; border-radius: 0.2rem; }
.copy-curl-btn i, .copy-response-btn i, .download-response-btn i { margin-right: 4px; vertical-align: text-bottom; }
.execute-wrapper .response-controls { display: flex; align-items: center; gap: 5px; margin-left: auto; }
.execute-wrapper .opblock-section-header h4 { margin-right: 10px; }
.opblock-section h5.opblock-title { font-weight: bold; margin: 0; font-size: 1rem; }
</style>
<div class="px-4 py-5 my-5 text-center">
<img class="d-block mx-auto mb-4" src="{{ url_for('static', filename='FHIRFLARE.png') }}" alt="FHIRFLARE IG Toolkit" width="192" height="192">
<h1 class="display-5 fw-bold text-body-emphasis">FHIR UI Operations</h1>
<div class="col-lg-6 mx-auto">
<p class="lead mb-4">
Explore FHIR server operations by selecting resource types or system operations. Toggle between local HAPI or a custom server to interact with FHIR metadata, resources, and server-wide operations.
</p>
<div class="d-grid gap-2 d-sm-flex justify-content-sm-center">
<a href="{{ url_for('index') }}" class="btn btn-primary btn-lg px-4 gap-3">Back to Home</a>
<a href="{{ url_for('fhir_ui') }}" class="btn btn-outline-secondary btn-lg px-4">FHIR API Explorer</a>
<a href="{{ url_for('validate_sample') }}" class="btn btn-outline-secondary btn-lg px-4">Validate FHIR Sample</a>
</div>
</div>
</div>
<div class="container mt-4">
<div class="card">
<div class="card-header"><i class="bi bi-gear me-2"></i>FHIR Operations Configuration</div>
<div class="card-body">
<form id="fhirOperationsForm">
{{ form.hidden_tag() }}
<div class="mb-3">
<label class="form-label fw-bold">FHIR Server</label>
<div class="input-group">
<button type="button" class="btn btn-outline-primary" id="toggleServer">
<span id="toggleLabel">Use Local HAPI</span> </button>
<input type="text" class="form-control" id="fhirServerUrl" name="fhir_server_url" placeholder="Enter FHIR Base URL e.g., https://hapi.fhir.org/baseR4" style="display: none;" aria-describedby="fhirServerHelp">
</div>
<small id="fhirServerHelp" class="form-text text-muted">Toggle to use local HAPI (/fhir proxy) or enter a custom FHIR server URL.</small>
</div>
<button type="button" class="btn btn-primary mb-3" id="fetchMetadata">Fetch Metadata</button>
</form>
<div class="banner3 mt-3" id="resourceTypes" style="display: none;">
<h5>Resource Types and Operations</h5>
<div class="d-flex flex-wrap gap-2" id="resourceButtons"></div>
</div>
<div id="swagger-ui" class="mt-4" style="display: none;">
<h5>Queries for <span id="selectedResource" class="fw-bold">Selected Resource</span></h5>
<div id="queryList"></div>
</div>
</div>
</div>
</div>
<script>
// --- Utility Functions (Unchanged) ---
function escapeXml(unsafe) {
if (typeof unsafe !== 'string') return String(unsafe);
// Corrected escape map
return unsafe.replace(/[<>&'"]/g, c => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;', "'": '&apos;', '"': '&quot;' })[c] || c);
}
function jsonToFhirXml(json, indentLevel = 0) {
const PADDING = ' ';
const currentIndent = PADDING.repeat(indentLevel);
const nextIndent = PADDING.repeat(indentLevel + 1);
try {
const obj = typeof json === 'string' ? JSON.parse(json) : json;
if (!obj || typeof obj !== 'object') return `${currentIndent}<error>Invalid input: Not an object</error>`;
if (!Object.keys(obj).length) return `${currentIndent}<OperationOutcome xmlns="http://hl7.org/fhir">\n${nextIndent}<issue>\n${PADDING}${nextIndent}<severity value="information"/>\n${PADDING}${nextIndent}<code value="informational"/>\n${PADDING}${nextIndent}<diagnostics value="Successful operation with no content"/>\n${nextIndent}</issue>\n${currentIndent}</OperationOutcome>`;
if (!obj.resourceType) {
obj.resourceType = (obj.type && obj.entry) ? 'Bundle' : null;
if (!obj.resourceType) return `${currentIndent}<error>Invalid FHIR resource: Missing resourceType</error>`;
}
let xml = `${currentIndent}<${obj.resourceType} xmlns="http://hl7.org/fhir">\n`;
function buildXmlRecursive(currentObj, level) {
let innerXml = '';
const itemIndent = PADDING.repeat(level);
const childLevel = level + 1;
for (const key in currentObj) {
if (key === 'xmlns' || key === 'resourceType') continue;
const value = currentObj[key];
if (value === null || value === undefined) continue;
if (Array.isArray(value)) {
value.forEach(item => {
if (typeof item === 'object' && item !== null) {
innerXml += `${itemIndent}<${key}>\n${buildXmlRecursive(item, childLevel)}${itemIndent}</${key}>\n`;
} else {
innerXml += `${itemIndent}<${key} value="${escapeXml(item)}"/>\n`;
}
});
} else if (typeof value === 'object') {
innerXml += `${itemIndent}<${key}>\n${buildXmlRecursive(value, childLevel)}${itemIndent}</${key}>\n`;
} else {
if (key === 'div' && typeof value === 'string' && value.trim().startsWith('<div')) {
innerXml += `${itemIndent}<${key} xmlns="http://www.w3.org/1999/xhtml">${value}</${key}>\n`;
} else {
innerXml += `${itemIndent}<${key} value="${escapeXml(value)}"/>\n`;
}
}
}
return innerXml;
}
xml += buildXmlRecursive(obj, indentLevel + 1);
xml += `${currentIndent}</${obj.resourceType}>`;
return xml;
} catch (e) {
console.error("JSON to XML conversion error:", e);
return `${currentIndent}<error xmlns="http://local/error">\n${nextIndent}<message>Failed to convert to XML</message>\n${nextIndent}<detail>${escapeXml(e.message)}</detail>\n${currentIndent}</error>`;
}
}
function xmlToFhirJson(xmlString) {
try {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlString, "application/xml");
const parseError = xmlDoc.querySelector("parsererror");
if (parseError) {
console.error("XML Parsing Error:", parseError.textContent);
return JSON.stringify({ error: "Failed to parse XML", details: parseError.textContent }, null, 2);
}
const root = xmlDoc.documentElement;
if (!root) return JSON.stringify({ error: "No root element found in XML" }, null, 2);
const jsonResult = { resourceType: root.tagName };
function buildJsonFromNode(node) {
const nodeObj = {};
let textContent = "";
for (const child of node.childNodes) {
if (child.nodeType === Node.ELEMENT_NODE) {
const tag = child.tagName;
const childValue = buildJsonFromNode(child);
if (nodeObj.hasOwnProperty(tag)) {
if (!Array.isArray(nodeObj[tag])) nodeObj[tag] = [nodeObj[tag]];
nodeObj[tag].push(childValue);
} else {
nodeObj[tag] = childValue;
}
} else if (child.nodeType === Node.TEXT_NODE) {
textContent += child.nodeValue.trim();
}
}
if (Object.keys(nodeObj).length) return nodeObj;
if (node.hasAttribute('value')) return node.getAttribute('value');
if (textContent) return textContent;
if (node.nodeType === Node.ELEMENT_NODE && !node.childNodes.length && !node.attributes.length) return null;
return {};
}
const rootContent = buildJsonFromNode(root);
if (typeof rootContent === 'object' && rootContent !== null) Object.assign(jsonResult, rootContent);
else jsonResult._value = rootContent;
const idNode = root.querySelector(':scope > id[value]');
if (idNode) jsonResult.id = idNode.getAttribute('value');
const textNode = root.querySelector(':scope > text');
if (textNode) {
jsonResult.text = {};
const statusNode = textNode.querySelector(':scope > status[value]');
if (statusNode) jsonResult.text.status = statusNode.getAttribute('value');
const divNode = textNode.querySelector(':scope > div[xmlns="http://www.w3.org/1999/xhtml"]');
if (divNode) jsonResult.text.div = divNode.innerHTML;
}
return JSON.stringify(jsonResult, null, 2);
} catch (e) {
console.error("XML to JSON conversion error:", e);
return JSON.stringify({ error: `Failed to convert XML to JSON: ${e.message}` }, null, 2);
}
}
async function copyToClipboard(text, buttonElement) {
if (!navigator.clipboard) {
alert('Clipboard API not available.');
return;
}
try {
await navigator.clipboard.writeText(text);
const originalHtml = buttonElement.innerHTML;
buttonElement.innerHTML = `<i class="bi bi-check-lg"></i> Copied!`;
buttonElement.disabled = true;
setTimeout(() => {
buttonElement.innerHTML = originalHtml;
buttonElement.disabled = false;
}, 1500);
} catch (err) {
alert(`Failed to copy text: ${err}`);
}
}
function downloadFile(content, filename, mimeType = 'application/octet-stream') {
try {
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const anchor = document.createElement('a');
anchor.href = url;
anchor.download = filename;
document.body.appendChild(anchor);
anchor.click();
document.body.removeChild(anchor);
URL.revokeObjectURL(url);
} catch (error) {
alert(`Failed to initiate download: ${error}`);
}
}
function generateCurlCommand(method, url, headers, body) {
let curl = `curl -X ${method.toUpperCase()} '${url}'`;
for (const key in headers) {
const lowerKey = key.toLowerCase();
if (['user-agent', 'connection', 'host', 'origin', 'referer', 'x-csrftoken'].includes(lowerKey)) continue;
const value = (headers[key] || '').replace(/'/g, "'\\''");
curl += ` \\\n -H '${key}: ${value}'`;
}
if (body !== undefined && body !== null && body !== '') {
const escapedBody = body.replace(/'/g, "'\\''");
const contentType = headers['Content-Type'] || '';
const dataFlag = contentType.includes('application/x-www-form-urlencoded') ? '-d' : '--data-binary';
curl += ` \\\n ${dataFlag} '${escapedBody}'`;
}
return curl;
}
// --- Main Application Logic ---
document.addEventListener('DOMContentLoaded', () => {
// --- DOM Element References ---
const fhirOperationsForm = document.getElementById('fhirOperationsForm');
const fetchMetadataButton = document.getElementById('fetchMetadata');
const toggleServerButton = document.getElementById('toggleServer');
const toggleLabel = document.getElementById('toggleLabel');
const fhirServerUrlInput = document.getElementById('fhirServerUrl');
const resourceTypesDisplayDiv = document.getElementById('resourceTypes');
const resourceButtonsContainer = document.getElementById('resourceButtons');
const swaggerUiContainer = document.getElementById('swagger-ui');
const selectedResourceSpan = document.getElementById('selectedResource');
const queryListContainer = document.getElementById('queryList');
// --- State Variables ---
let isUsingLocalHapi = true; // Default state
let currentSelectedResource = '';
let availableSystemOperations = [];
let fetchedMetadataCache = null;
// --- Helper Function to Update Toggle Button/Input UI ---
function updateServerToggleUI() {
if (!toggleLabel || !fhirServerUrlInput) {
console.error("Toggle UI elements not found!");
return;
}
toggleLabel.textContent = isUsingLocalHapi ? 'Use Local HAPI' : 'Use Custom URL';
fhirServerUrlInput.style.display = isUsingLocalHapi ? 'none' : 'block';
// Ensure input is cleared *only* when switching TO local
// No clearing needed here, handled in toggleServerSelection if needed
fhirServerUrlInput.classList.remove('is-invalid');
}
// --- Server Toggle Functionality ---
function toggleServerSelection() {
isUsingLocalHapi = !isUsingLocalHapi; // Flip the state first
updateServerToggleUI(); // Update the UI based on the new state
// Clear custom URL input value if we just switched TO local HAPI
if (isUsingLocalHapi) {
fhirServerUrlInput.value = '';
}
// Reset application state dependent on the server
if (resourceTypesDisplayDiv) resourceTypesDisplayDiv.style.display = 'none';
if (swaggerUiContainer) swaggerUiContainer.style.display = 'none';
fetchedMetadataCache = null; // Invalidate metadata cache
availableSystemOperations = []; // Clear parsed operations
console.log(`Server toggled: ${isUsingLocalHapi ? 'Local HAPI' : 'Custom URL'}`);
}
// Add event listener to the toggle button
if (toggleServerButton) {
toggleServerButton.addEventListener('click', toggleServerSelection);
} else {
console.error("Toggle server button (#toggleServer) not found!");
}
// --- Generate Query Examples (Unchanged) ---
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 || [];
const queries = [];
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 searchPostParams = [countQueryParam, formatParam];
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);
const baseSchema = { resourceType, id: "string", meta: { versionId: "string", lastUpdated: "instant" }, text: { status: "code", div: "xhtml" } };
const bundleSchema = { resourceType: "Bundle", type: "code", total: "unsignedInt", link: [{ relation: "string", url: "uri" }], entry: [{ resource: baseSchema }] };
const paramsSchema = { resourceType: "Parameters", parameter: [{ name: "string", valueString: "string" }] };
const outcomeSchema = { resourceType: "OperationOutcome", issue: [{ severity: "code", code: "code", diagnostics: "string" }] };
const bundleSearchExample = JSON.stringify({ resourceType: "Bundle", type: "searchset", total: 1, entry: [{ resource: baseExample }] }, null, 2);
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 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) },
'update': { label: `Update ${resourceType}`, method: 'PUT', path: `${resourceType}/:id`, description: `Update ${resourceType} with ID`, parameters: [idPathParam, formatParam], requestBody: true, schema: baseSchema, example: baseExampleString },
'patch': { label: `Patch ${resourceType}`, method: 'PATCH', path: `${resourceType}/:id`, description: `Patch ${resourceType} by ID`, parameters: [idPathParam, formatParam], requestBody: true, schema: paramsSchema, example: patchExample },
'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 },
};
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() {
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 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);
// --- 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)
});
addedPaths.add(metadataKey);
}
// --- Add Other Standard System Interactions ---
// Transaction/Batch
if (supportedInteractions.includes('transaction') || supportedInteractions.includes('batch')) {
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)
});
addedPaths.add(key);
}
}
// System History
if (supportedInteractions.includes('history-system')) {
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)
});
addedPaths.add(key);
}
}
// System Search (if supported, less common)
if (supportedInteractions.includes('search-system')) {
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)
});
addedPaths.add(key);
}
}
// --- Add System-Level Named Operations ---
(restSection.operation || []).forEach(op => {
const opName = op.name;
const opPath = opName.startsWith('$') ? opName : `$${opName}`;
const defUrl = 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');
}
methods.forEach(method => {
const key = `${method}/${opPath}`;
if (!addedPaths.has(key)) {
let label = `${method} /${opPath}`;
let description = doc;
// 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';
} else {
description = `${method}: /${opPath}`; // Fallback for operations like reindex, expunge, etc.
}
queries.push({
name: opName,
label: label,
method: method,
path: opPath,
description: description,
parameters: [formatParam],
requestBody: method !== 'GET',
schema: paramsSchema,
example: paramsExample
});
addedPaths.add(key);
}
});
});
return queries;
}
// --- Update Query List UI (FIXED Variable Scope) ---
function updateQueryListUI(resourceType) {
currentSelectedResource = resourceType;
selectedResourceSpan.textContent = resourceType === 'System' ? 'System Operations' : resourceType;
queryListContainer.innerHTML = '';
const queries = resourceType === 'System' ? getSystemLevelQueries() : getQueryExamplesForResourceType(resourceType);
if (!queries || !queries.length) {
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; })
.forEach((query, i) => {
const blockId = `opblock-${i}`;
const block = document.createElement('div');
block.id = blockId;
block.className = `opblock opblock-${query.method.toLowerCase()}`;
block.dataset.queryIndex = i;
block.dataset.queryData = JSON.stringify(query);
// Generate HTML (No changes needed here)
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>
${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 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);
// --- Get Element References ONCE per block ---
const opblockSummary = block.querySelector('.opblock-summary');
const opblockBody = block.querySelector('.opblock-body');
const tryButton = block.querySelector('.try-out__btn');
const executeButton = block.querySelector('.execute__btn');
const paramInputs = block.querySelectorAll('.parameters-section input');
const reqBodyTextarea = block.querySelector('.request-body-textarea');
const reqContentTypeSelect = block.querySelector('.request-content-type');
const executeWrapper = block.querySelector('.execute-wrapper');
// Ensure executeWrapper exists before querying within it
const respOutputPre = executeWrapper?.querySelector('.response-output-content');
const respOutputCode = respOutputPre?.querySelector('code');
const respNarrativeDiv = executeWrapper?.querySelector('.response-output-narrative');
const respStatusDiv = executeWrapper?.querySelector('.response-status');
const respFormatSelect = executeWrapper?.querySelector('.response-format-select');
const reqUrlOutput = executeWrapper?.querySelector('.request-url-output code');
const curlOutput = executeWrapper?.querySelector('.curl-output code');
const copyCurlButton = executeWrapper?.querySelector('.copy-curl-btn');
const copyRespButton = executeWrapper?.querySelector('.copy-response-btn');
const downloadRespButton = executeWrapper?.querySelector('.download-response-btn');
const exampleMediaTypeSelect = block.querySelector('.example-media-type-select');
const exampleTabs = block.querySelectorAll('.responses-section .tablinks.badge');
const examplePanel = block.querySelector('.responses-section .example-panel');
const schemaPanel = block.querySelector('.responses-section .schema-panel');
// --- Attach Event Listeners (using references above, with null checks) ---
if (opblockSummary) {
opblockSummary.addEventListener('click', (e) => {
if (e.target.closest('button, a, input, select')) return;
const wasOpen = block.classList.contains('is-open');
document.querySelectorAll('.opblock.is-open').forEach(openBlock => { if (openBlock !== block) { openBlock.classList.remove('is-open'); if(openBlock.querySelector('.opblock-body')) openBlock.querySelector('.opblock-body').style.display = 'none'; }});
block.classList.toggle('is-open', !wasOpen);
if(opblockBody) opblockBody.style.display = wasOpen ? 'none' : 'block';
});
}
if (tryButton && executeButton) {
tryButton.addEventListener('click', () => {
const isEditing = tryButton.classList.contains('cancel'); const enable = !isEditing;
paramInputs.forEach(input => { input.disabled = !enable; if (!enable) { input.value = input.dataset.example || ''; input.classList.remove('is-invalid'); } });
if (reqBodyTextarea) reqBodyTextarea.disabled = !enable; if (reqContentTypeSelect) reqContentTypeSelect.disabled = !enable; if (!enable && reqBodyTextarea) { reqBodyTextarea.value = (query.method === 'POST' && query.path.endsWith('_search')) ? query.example : ''; }
tryButton.textContent = enable ? 'Cancel' : 'Try it out'; tryButton.classList.toggle('cancel', enable); tryButton.classList.toggle('btn-danger', enable); tryButton.classList.toggle('btn-primary', !enable);
executeButton.style.display = enable ? 'inline-block' : 'none'; executeButton.disabled = !enable;
if (!enable && executeWrapper) {
executeWrapper.style.display = 'none';
if (reqUrlOutput) reqUrlOutput.textContent = ''; if (curlOutput) curlOutput.textContent = '';
if (respOutputCode) respOutputCode.textContent = ''; if (respNarrativeDiv) respNarrativeDiv.innerHTML = '';
if (respStatusDiv) respStatusDiv.textContent = '';
if (respFormatSelect) respFormatSelect.style.display = 'none';
if (copyRespButton) copyRespButton.style.display = 'none'; if (downloadRespButton) downloadRespButton.style.display = 'none';
}
});
}
if (reqContentTypeSelect && reqBodyTextarea && tryButton) {
const reqExampleCode = block.querySelector('.request-example-code code');
const reqExampleContainer = block.querySelector('.request-body-example');
reqContentTypeSelect.addEventListener('change', () => {
const type = reqContentTypeSelect.value; const isFormUrlEncoded = type === 'application/x-www-form-urlencoded'; const isEditing = tryButton.classList.contains('cancel');
if (reqExampleContainer) reqExampleContainer.style.display = isFormUrlEncoded ? 'none' : 'block';
if (reqBodyTextarea && !isEditing) { reqBodyTextarea.value = isFormUrlEncoded ? query.example : ''; reqBodyTextarea.placeholder = isFormUrlEncoded ? 'e.g., param1=value1&param2=value2' : 'Enter FHIR resource JSON or XML'; }
if (reqExampleCode && !isFormUrlEncoded) { const jsonExample = query.example && typeof query.example === 'string' && query.example.startsWith('{') ? query.example : '{}'; if (type === 'application/fhir+json') { reqExampleCode.className = 'language-json'; reqExampleCode.textContent = jsonExample; } else if (type === 'application/fhir+xml') { reqExampleCode.className = 'language-xml'; reqExampleCode.textContent = jsonToFhirXml(jsonExample); } }
});
reqContentTypeSelect.dispatchEvent(new Event('change'));
}
if (executeButton && executeWrapper && respStatusDiv && reqUrlOutput && curlOutput && respFormatSelect && copyRespButton && downloadRespButton && respOutputCode && respNarrativeDiv && respOutputPre) {
executeButton.addEventListener('click', async () => {
executeButton.disabled = true; executeButton.textContent = 'Executing...';
executeWrapper.style.display = 'block'; reqUrlOutput.textContent = 'Building request...'; curlOutput.textContent = 'Building request...'; respOutputCode.textContent = ''; respNarrativeDiv.innerHTML = ''; respNarrativeDiv.style.display = 'none'; respOutputPre.style.display = 'block'; respStatusDiv.textContent = 'Executing request...'; respStatusDiv.style.color = '#6c757d'; respFormatSelect.style.display = 'none'; copyRespButton.style.display = 'none'; downloadRespButton.style.display = 'none'; respFormatSelect.value = 'json';
const queryDef = JSON.parse(block.dataset.queryData); const method = queryDef.method; const headers = { 'Accept': 'application/fhir+json, application/fhir+xml;q=0.9, */*;q=0.8' }; let body; let path = queryDef.path;
// **FIX for Local URL:** Explicitly use '/fhir' if local, otherwise use input value
const baseUrl = isUsingLocalHapi ? '/fhir' : (fhirServerUrlInput.value.trim().replace(/\/+$/, '') || '/fhir'); // Fallback to /fhir if custom URL is empty
let url = `${baseUrl}`; let validParams = true; const missingParams = [];
block.querySelectorAll('.parameters-section tr[data-param-in="path"]').forEach(row => { const paramName = row.dataset.paramName; const input = row.querySelector('input'); const required = queryDef.parameters.find(p => p.name === paramName)?.required; const value = input.value.trim(); input.classList.remove('is-invalid'); if (!value && required) { validParams = false; missingParams.push(`${paramName} (path)`); input.classList.add('is-invalid'); } else if (value) { path = path.replace(`:${paramName}`, encodeURIComponent(value)); } else { path = path.replace(`:${paramName}`, ''); } }); if (path.includes(':')) { const remaining = path.match(/:(\w+)/g) || []; if (remaining.length) { validParams = false; missingParams.push(...remaining); }} url += path.startsWith('/') ? path : `/${path}`; const searchParams = new URLSearchParams(); block.querySelectorAll('.parameters-section tr[data-param-in="query"]').forEach(row => { const paramName = row.dataset.paramName; const input = row.querySelector('input'); const required = queryDef.parameters.find(p => p.name === paramName)?.required; const value = input.value.trim(); input.classList.remove('is-invalid'); if (value) searchParams.set(paramName, value); else if (required) { validParams = false; missingParams.push(`${paramName} (query)`); input.classList.add('is-invalid'); } });
if (!validParams) { const errorMsg = `Error: Missing required parameter(s): ${[...new Set(missingParams)].join(', ')}`; respStatusDiv.textContent = errorMsg; respStatusDiv.style.color = 'red'; reqUrlOutput.textContent = 'Error: Invalid parameters'; curlOutput.textContent = 'Error: Invalid parameters'; executeButton.disabled = false; executeButton.textContent = 'Execute'; return; } const queryString = searchParams.toString(); if (queryString) url += (url.includes('?') ? '&' : '?') + queryString;
if (queryDef.requestBody) { const contentType = reqContentTypeSelect ? reqContentTypeSelect.value : 'application/fhir+json'; headers['Content-Type'] = contentType; body = reqBodyTextarea ? reqBodyTextarea.value : ''; if (contentType === 'application/fhir+xml' && body.trim().startsWith('{')) { try { body = jsonToFhirXml(body); } catch (e) { console.warn("JSON->XML conversion failed", e); }} else if (contentType === 'application/fhir+json' && body.trim().startsWith('<')) { try { body = xmlToFhirJson(body); } catch (e) { console.warn("XML->JSON conversion failed", e); }} if (isUsingLocalHapi && ['POST', 'PUT', 'PATCH', 'DELETE'].includes(method) && fhirOperationsForm.querySelector('input[name="csrf_token"]')) { headers['X-CSRFToken'] = fhirOperationsForm.querySelector('input[name="csrf_token"]').value; } }
reqUrlOutput.textContent = url; curlOutput.textContent = generateCurlCommand(method, url, headers, body); console.log(`Executing: ${method} ${url}`); console.log("Headers:", headers); if (body !== undefined) console.log("Body:", (body || '').substring(0, 300) + (body.length > 300 ? "..." : ""));
let respData = { json: null, xml: null, narrative: null, text: null, status: 0, statusText: '', contentType: '' };
try {
const resp = await fetch(url, { method, headers, body }); respData.status = resp.status; respData.statusText = resp.statusText; respData.contentType = resp.headers.get('Content-Type') || ''; respData.text = await resp.text(); respStatusDiv.textContent = `Status: ${resp.status} ${resp.statusText}`; respStatusDiv.style.color = resp.ok ? 'green' : 'red';
if (respData.text) { if (respData.contentType.includes('json')) { try { respData.json = JSON.parse(respData.text); respData.xml = jsonToFhirXml(respData.json); if (respData.json.text?.div) respData.narrative = respData.json.text.div; } catch (e) {} } else if (respData.contentType.includes('xml')) { respData.xml = respData.text; respData.json = JSON.parse(xmlToFhirJson(respData.xml)); try { const p = new DOMParser(); const xd = p.parseFromString(respData.xml, "application/xml"); const nn = xd.querySelector("div[xmlns='http://www.w3.org/1999/xhtml']"); if (nn) respData.narrative = nn.outerHTML; } catch(e) {} } else { respData.json = { contentType: respData.contentType, content: respData.text }; respData.xml = `<data contentType="${escapeXml(respData.contentType)}">${escapeXml(respData.text)}</data>`; } } else if (resp.ok) { respData.json = { message: "Operation successful, no content returned." }; respData.xml = jsonToFhirXml({}); } else { respData.json = { error: `Request failed with status ${resp.status}`, detail: resp.statusText }; respData.xml = `<error>Request failed with status ${resp.status} ${escapeXml(resp.statusText)}</error>`; }
block.dataset.responseData = JSON.stringify(respData); respFormatSelect.style.display = 'inline-block'; copyRespButton.style.display = 'inline-block'; downloadRespButton.style.display = 'inline-block'; respFormatSelect.disabled = false; const narrativeOption = respFormatSelect.querySelector('option[value="narrative"]'); if (narrativeOption) narrativeOption.disabled = !respData.narrative; respFormatSelect.dispatchEvent(new Event('change'));
} catch (e) { console.error('Fetch Error:', e); respStatusDiv.textContent = `Network Error: ${e.message}`; respStatusDiv.style.color = 'red'; respOutputCode.textContent = `Request failed: ${e.message}\nURL: ${url}`; respOutputCode.className = 'language-text'; respFormatSelect.style.display = 'none'; copyRespButton.style.display = 'none'; downloadRespButton.style.display = 'none'; } finally { executeButton.disabled = false; executeButton.textContent = 'Execute'; }
});
}
if (respFormatSelect && respNarrativeDiv && respOutputPre && respOutputCode) {
respFormatSelect.addEventListener('change', () => { const format = respFormatSelect.value; try { const data = JSON.parse(block.dataset.responseData || '{}'); respNarrativeDiv.style.display = 'none'; respOutputPre.style.display = 'block'; switch (format) { case 'xml': respOutputCode.textContent = data.xml || data.text || '<No XML>'; respOutputCode.className = 'language-xml'; respOutputPre.style.whiteSpace = 'pre'; break; case 'narrative': if (data.narrative) { respNarrativeDiv.innerHTML = data.narrative; respNarrativeDiv.style.display = 'block'; respOutputPre.style.display = 'none'; } else { respOutputCode.textContent = '<Narrative unavailable>'; respOutputCode.className = 'language-text'; respOutputPre.style.whiteSpace = 'pre-wrap'; } break; case 'json': default: respOutputCode.textContent = data.json ? JSON.stringify(data.json, null, 2) : (data.text || '<No JSON>'); respOutputCode.className = 'language-json'; respOutputPre.style.whiteSpace = 'pre-wrap'; break; } } catch (e) {} });
}
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); }); });
}
if (exampleMediaTypeSelect) {
exampleMediaTypeSelect.addEventListener('change', () => updateStaticExampleFormat(block));
}
if (copyCurlButton && curlOutput) {
copyCurlButton.addEventListener('click', (e) => { if (curlOutput.textContent) copyToClipboard(curlOutput.textContent, e.currentTarget); });
}
if (copyRespButton && respFormatSelect) {
copyRespButton.addEventListener('click', (e) => { const format = respFormatSelect.value; let content = ''; if (format === 'narrative') { content = respNarrativeDiv ? respNarrativeDiv.innerHTML : ''; } else { content = respOutputCode ? respOutputCode.textContent : ''; } if (content) copyToClipboard(content, e.currentTarget); });
}
if (downloadRespButton && respFormatSelect) {
downloadRespButton.addEventListener('click', () => { const format = respFormatSelect.value; let content = ''; let ext = 'txt'; let mime = 'text/plain'; const data = JSON.parse(block.dataset.responseData || '{}'); const dateStamp = new Date().toISOString().replace(/[:.]/g, '-'); const filename = `response-${currentSelectedResource || 'system'}-${query.method}-${dateStamp}`; switch (format) { case 'json': content = data.json ? JSON.stringify(data.json, null, 2) : (data.text || ''); ext = 'json'; mime = 'application/json'; break; case 'xml': content = data.xml || data.text || ''; ext = 'xml'; mime = 'application/xml'; break; case 'narrative': content = respNarrativeDiv ? respNarrativeDiv.innerHTML : ''; ext = 'html'; mime = 'text/html'; break; } if (content) downloadFile(content, `${filename}.${ext}`, mime); });
}
updateStaticExampleFormat(block); // Initialize example format
}); // End forEach query
swaggerUiContainer.style.display = 'block';
} // End updateQueryListUI
// --- Update Static Example/Schema Format Display (Unchanged) ---
function updateStaticExampleFormat(block) {
const data = JSON.parse(block.dataset.queryData || '{}');
const mediaTypeSelect = block.querySelector('.example-media-type-select');
const examplePre = block.querySelector('.responses-section .example-code-display');
const exampleCode = examplePre?.querySelector('code');
const schemaPre = block.querySelector('.responses-section .schema-code-display');
const schemaCode = schemaPre?.querySelector('code');
if (!mediaTypeSelect || !exampleCode || !schemaCode || !examplePre || !schemaPre) return;
const mediaType = mediaTypeSelect.value; const example = data.example || ''; const isJsonLike = typeof example === 'string' && example.trim().startsWith('{'); const exampleJson = isJsonLike ? example : '{}'; const schemaJson = JSON.stringify(data.schema || {}, null, 2);
if (mediaType === 'application/fhir+json') { exampleCode.textContent = exampleJson; exampleCode.className = 'language-json'; schemaCode.textContent = schemaJson; schemaCode.className = 'language-json'; examplePre.style.whiteSpace = 'pre-wrap'; schemaPre.style.whiteSpace = 'pre-wrap'; }
else if (mediaType === 'application/fhir+xml') { exampleCode.textContent = isJsonLike ? jsonToFhirXml(exampleJson) : `<value>${escapeXml(example)}</value>`; exampleCode.className = 'language-xml'; schemaCode.textContent = jsonToFhirXml(data.schema || {}); schemaCode.className = 'language-xml'; examplePre.style.whiteSpace = 'pre'; schemaPre.style.whiteSpace = 'pre'; }
}
// --- Fetch Server Metadata (FIXED Local URL Handling) ---
if (fetchMetadataButton) {
fetchMetadataButton.addEventListener('click', async () => {
// Clear previous results immediately
if (resourceButtonsContainer) resourceButtonsContainer.innerHTML = '<span class="text-muted">Fetching...</span>';
if (resourceTypesDisplayDiv) resourceTypesDisplayDiv.style.display = 'block';
if (swaggerUiContainer) swaggerUiContainer.style.display = 'none'; // Hide old query list
fetchedMetadataCache = null; // Clear cache before fetch attempt
availableSystemOperations = [];
// Determine Base URL - FIXED
const customUrl = fhirServerUrlInput.value.trim().replace(/\/+$/, '');
const baseUrl = isUsingLocalHapi ? '/fhir' : customUrl; // Use '/fhir' proxy path if local
// Validate custom URL only if not using local HAPI
if (!isUsingLocalHapi && !baseUrl) { // Should only happen if customUrl is empty
fhirServerUrlInput.classList.add('is-invalid');
alert('Please enter a valid FHIR server URL.');
if (resourceButtonsContainer) resourceButtonsContainer.innerHTML = `<span class="text-danger">Error: Custom URL required.</span>`;
return;
}
// Basic format check for custom URL
if (!isUsingLocalHapi) {
try {
new URL(baseUrl); // Check if it's a parseable URL format
} catch (_) {
fhirServerUrlInput.classList.add('is-invalid');
alert('Invalid custom URL format. Please enter a valid URL (e.g., https://example.com/fhir).');
if (resourceButtonsContainer) resourceButtonsContainer.innerHTML = `<span class="text-danger">Error: Invalid custom URL format.</span>`;
return;
}
}
fhirServerUrlInput.classList.remove('is-invalid');
// Construct metadata URL (always add /metadata)
const url = `${baseUrl}/metadata`;
console.log(`Fetching metadata from: ${url}`);
fetchMetadataButton.disabled = true; fetchMetadataButton.textContent = 'Fetching...';
try {
const resp = await fetch(url, { method: 'GET', headers: { 'Accept': 'application/fhir+json' } });
if (!resp.ok) { const errText = await resp.text(); throw new Error(`HTTP ${resp.status} ${resp.statusText}: ${errText.substring(0, 500)}`); }
const data = await resp.json();
console.log('Metadata received:', data);
fetchedMetadataCache = data; // Cache successful fetch
displayMetadataAndResourceButtons(data); // Parse and display
} catch (e) {
console.error('Metadata fetch error:', e);
if (resourceButtonsContainer) resourceButtonsContainer.innerHTML = `<span class="text-danger">Error fetching metadata: ${e.message}</span>`;
if (resourceTypesDisplayDiv) resourceTypesDisplayDiv.style.display = 'block'; // Keep container visible to show error
if (swaggerUiContainer) swaggerUiContainer.style.display = 'none';
alert(`Error fetching metadata: ${e.message}`);
fetchedMetadataCache = null; // Clear cache on error
availableSystemOperations = [];
} finally {
fetchMetadataButton.disabled = false; fetchMetadataButton.textContent = 'Fetch Metadata';
}
});
} else {
console.error("Fetch Metadata button (#fetchMetadata) not found!");
}
// --- Display Metadata & Parse Operations (FIXED Method Parsing Logic) ---
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; }
const resourceTypes = rest.resource?.map(r => r.type).filter(Boolean) || [];
const interactions = rest.interaction?.map(i => i.code) || [];
// --- PARSE OPERATIONS (Revised Method/Level Detection) ---
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
// Determine Levels (same logic as before, seems reasonable)
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');
}
}
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(...)) { ... }
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'));
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'));
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));
resourceButtonsContainer.appendChild(resourceButton);
});
resourceTypesDisplayDiv.style.display = 'block'; // Show the buttons container
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'; }
}
// --- 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');
} else {
button.classList.add('btn-outline-dark-blue');
}
});
// 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
}
// --- Initial Page State Setup ---
resourceTypesDisplayDiv.style.display = 'none';
swaggerUiContainer.style.display = 'none';
updateServerToggleUI(); // Set the initial UI state correctly based on default isUsingLocalHapi = true
}); // End DOMContentLoaded Listener
</script>
{% endblock %}

View File

@ -0,0 +1,75 @@
{% extends "base.html" %}
{% from "_form_helpers.html" import render_field %}
{% block content %}
<div class="px-4 py-5 my-5 text-center">
<img class="d-block mx-auto mb-4" src="{{ url_for('static', filename='FHIRFLARE.png') }}" alt="FHIRFLARE IG Toolkit" width="192" height="192">
<h1 class="display-5 fw-bold text-body-emphasis">FSH Converter</h1>
<div class="col-lg-6 mx-auto">
<p class="lead mb-4">
Convert FHIR JSON or XML resources to FHIR Shorthand (FSH) using GoFSH.
</p>
<div class="d-grid gap-2 d-sm-flex justify-content-sm-center">
<a href="{{ url_for('index') }}" class="btn btn-primary btn-lg px-4 gap-3">Back to Home</a>
<a href="{{ url_for('view_igs') }}" class="btn btn-outline-secondary btn-lg px-4">View Downloaded IGs</a>
<a href="{{ url_for('validate_sample') }}" class="btn btn-outline-secondary btn-lg px-4">Validate Sample</a>
<a href="{{ url_for('fhir_ui_operations') }}" class="btn btn-outline-secondary btn-lg px-4">FHIR Operations</a>
</div>
</div>
</div>
<div class="container mt-4">
<h2><i class="bi bi-file-code me-2"></i>Convert FHIR to FSH</h2>
<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.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) }}
<div class="d-grid gap-2 d-sm-flex">
{{ form.submit(class="btn btn-success") }}
<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>
{% endif %}
</div>
<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';
}
});
// Trigger change on page load to set initial state
document.getElementById('input_mode').dispatchEvent(new Event('change'));
</script>
{% endblock %}

View File

@ -1,155 +0,0 @@
// Function to build the hierarchical data structure for the tree
function buildTreeData(elements) {
const treeRoot = { children: {}, element: null, name: 'Root' };
const nodeMap = { 'Root': treeRoot };
console.log("Building tree with elements:", elements.length);
elements.forEach((el, index) => {
const path = el.path;
const id = el.id || null;
const sliceName = el.sliceName || null;
console.log(`Element ${index}: path=${path}, id=${id}, sliceName=${sliceName}`);
if (!path) {
console.warn(`Skipping element ${index} with no path`);
return;
}
const parts = path.split('.');
let currentPath = '';
let parentNode = treeRoot;
for (let i = 0; i < parts.length; i++) {
const part = parts[i];
currentPath = i === 0 ? part : `${currentPath}.${part}`;
// For extensions, append sliceName to path if present
let nodeKey = part;
if (part === 'extension' && i === parts.length - 1 && sliceName) {
nodeKey = `${part}:${sliceName}`;
currentPath = id || currentPath; // Use id for precise matching in extensions
}
if (!nodeMap[currentPath]) {
const newNode = {
children: {},
element: null,
name: nodeKey,
path: currentPath
};
let parentPath = i === 0 ? 'Root' : parts.slice(0, i).join('.');
parentNode = nodeMap[parentPath] || treeRoot;
parentNode.children[nodeKey] = newNode;
nodeMap[currentPath] = newNode;
console.log(`Created node: path=${currentPath}, name=${nodeKey}`);
}
if (i === parts.length - 1) {
const targetNode = nodeMap[currentPath];
targetNode.element = el;
console.log(`Assigned element to node: path=${currentPath}, id=${id}, sliceName=${sliceName}`);
}
parentNode = nodeMap[currentPath];
}
});
const treeData = Object.values(treeRoot.children);
console.log("Tree data constructed:", treeData);
return treeData;
}
// Function to render a single node (and its children recursively) as an <li>
function renderNodeAsLi(node, mustSupportPathsSet, level = 0) {
if (!node || !node.element) {
console.warn("Skipping render for invalid node:", node);
return '';
}
const el = node.element;
const path = el.path || 'N/A';
const id = el.id || null;
const sliceName = el.sliceName || null;
const min = el.min !== undefined ? el.min : '';
const max = el.max || '';
const short = el.short || '';
const definition = el.definition || '';
console.log(`Rendering node: path=${path}, id=${id}, sliceName=${sliceName}`);
console.log(` MustSupportPathsSet contains path: ${mustSupportPathsSet.has(path)}`);
if (id) {
console.log(` MustSupportPathsSet contains id: ${mustSupportPathsSet.has(id)}`);
}
// Check MS for path, id, or normalized extension path
let isMustSupport = mustSupportPathsSet.has(path) || (id && mustSupportPathsSet.has(id));
if (!isMustSupport && path.startsWith('Extension.extension')) {
const basePath = path.split(':')[0];
const baseId = id ? id.split(':')[0] : null;
isMustSupport = mustSupportPathsSet.has(basePath) || (baseId && mustSupportPathsSet.has(baseId));
console.log(` Extension check: basePath=${basePath}, baseId=${baseId}, isMustSupport=${isMustSupport}`);
}
console.log(` Final isMustSupport for ${path}: ${isMustSupport}`);
const liClass = isMustSupport ? 'list-group-item py-1 px-2 list-group-item-warning' : 'list-group-item py-1 px-2';
const mustSupportDisplay = isMustSupport ? '<i class="bi bi-check-circle-fill text-warning ms-1" title="Must Support"></i>' : '';
const hasChildren = Object.keys(node.children).length > 0;
const collapseId = `collapse-${path.replace(/[\.\:\/\[\]\(\)]/g, '-')}`;
const padding = level * 20;
const pathStyle = `padding-left: ${padding}px; white-space: nowrap;`;
let typeString = 'N/A';
if (el.type && el.type.length > 0) {
typeString = el.type.map(t => {
let s = t.code || '';
let profiles = t.targetProfile || t.profile || [];
if (profiles.length > 0) {
const targetTypes = profiles.map(p => (p || '').split('/').pop()).filter(Boolean).join(', ');
if (targetTypes) {
s += `(<span class="text-muted fst-italic" title="${profiles.join(', ')}">${targetTypes}</span>)`;
}
}
return s;
}).join(' | ');
}
let childrenHtml = '';
if (hasChildren) {
childrenHtml += `<ul class="collapse list-group list-group-flush structure-subtree" id="${collapseId}">`;
Object.values(node.children).sort((a, b) => (a.element?.path ?? a.name).localeCompare(b.element?.path ?? b.name)).forEach(childNode => {
childrenHtml += renderNodeAsLi(childNode, mustSupportPathsSet, level + 1);
});
childrenHtml += `</ul>`;
}
let itemHtml = `<li class="${liClass}">`;
itemHtml += `<div class="row gx-2 align-items-center ${hasChildren ? 'collapse-toggle' : ''}" ${hasChildren ? `data-bs-toggle="collapse" data-bs-target="#${collapseId}" role="button" aria-expanded="false" aria-controls="${collapseId}"` : ''}>`;
itemHtml += `<div class="col-lg-4 col-md-3 text-truncate" style="${pathStyle}">`;
itemHtml += `<span style="display: inline-block; width: 1.2em; text-align: center;">`;
if (hasChildren) {
itemHtml += `<i class="bi bi-chevron-right small toggle-icon"></i>`;
}
itemHtml += `</span>`;
itemHtml += `<code class="fw-bold ms-1" title="${path}">${node.name}</code>`;
itemHtml += `</div>`;
itemHtml += `<div class="col-lg-1 col-md-1 text-center text-muted small"><code>${min}..${max}</code></div>`;
itemHtml += `<div class="col-lg-3 col-md-3 text-truncate small">${typeString}</div>`;
itemHtml += `<div class="col-lg-1 col-md-1 text-center">${mustSupportDisplay}</div>`;
let descriptionTooltipAttrs = '';
if (definition) {
const escapedDefinition = definition
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/\n/g, ' ');
descriptionTooltipAttrs = `data-bs-toggle="tooltip" data-bs-placement="top" title="${escapedDefinition}"`;
}
itemHtml += `<div class="col-lg-3 col-md-4 text-muted text-truncate small" ${descriptionTooltipAttrs}>${short || (definition ? '(definition only)' : '')}</div>`;
itemHtml += `</div>`;
itemHtml += childrenHtml;
itemHtml += `</li>`;
return itemHtml;
}